A Unicode string parameter cannot reach a MySQL-wire warehouse's prepare
Six servers, one connector, one recurring theme
Apache Doris, StarRocks, Databend, GreptimeDB, OceanBase and MatrixOne all serve the MySQL wire protocol, so MySQL Connector/ODBC drives all six with no driver of their own. What they do not all implement is MySQL's prepared statement protocol, and almost everything below follows from that. The connector probe at the end comes from the macOS campaign of 2026-08-24; the one finding that was filed went upstream on 2026-08-28.
Doris: the FE refuses the type the connector sends for every string
MySQL Connector/ODBC sends a bound SQL_C_WCHAR parameter — which is how a Unicode ODBC
client sends text — with MySQL type BLOB. Doris's server-side prepared INSERT refuses
that type, so a Unicode client cannot insert through prepared statements at all. On 2.1.0
the frontend answers with a bare
SQLExecDirectW: NullPointerException, msg: null (1105)
and on 4.1.3 with AnalysisException: Unsupported MySQL type: BLOB. Filed with the program
and both versions' output as
apache/doris#67301; a Doris contributor's fix
PR apache/doris#67306 is open and closes it.
Doris's prepare path is narrow to begin with: it answers COM_STMT_PREPARE only for a point
SELECT on a suitable table, and anything else gets
SQLPrepare: errCode = 2, detailMessage = Only support prepare SelectStmt point query now
NO_SSPS=1 is not a tuning knob here, it is the entry ticket
Set NO_SSPS=1 and Connector/ODBC stops using the server-side prepare protocol and
substitutes bound parameters into the SQL text, so every statement goes as a plain query.
Four of these six servers need it, each for its own reason:
- Databend refuses
COM_STMT_PREPAREoutright —Prepare is not support in Databend. (1105) (SQLPrepare)— so evenSELECT 1with no parameters fails. - StarRocks prepares nothing but a
SELECT:1295: … This command is not supported in the prepared statement protocol yet. - Doris, as above.
- GreptimeDB does answer the prepare, which is worse. It describes every parameter as
VAR_STRINGwhatever the target column is, then type-checks the value against the column and rejects the string it asked for:[HY000] (1210) (InvalidArguments): Expected type: Int32(Int32Type), actual: MYSQL_TYPE_STRING. No ODBC-side binding satisfies both halves.
On Windows the rule is broader still: Connector/ODBC 8.4.0 needs NO_SSPS=1 against every
non-MySQL server here, including OceanBase, which does not need it on Linux — without it
every bound parameter fails No data supplied for parameters in prepared statement.
NO_SSPS has a tail. In that mode the connector writes any parameter whose SQL type is
neither character nor numeric as a MySQL charset-introducer literal — _binary'2024-02-29'
for a date, the same for a timestamp or a binary. Introducers are MySQL and MariaDB syntax,
and Doris, StarRocks and GreptimeDB parsers all reject them, so dates, timestamps and binary
values have to be sent as ordinary quoted text instead.
OceanBase: MYSQL_TYPE_NULL is refused
On a server-side prepared INSERT, Connector/ODBC sends a NULL as MYSQL_TYPE_NULL until a
non-NULL execute has fixed that parameter's type. OceanBase CE 4.4.2 refuses that execute
with Object type error (4001) — the NULL-carrying execute itself, not the ones after it.
Once a value has gone through on that statement, later NULLs are accepted, and a fresh
statement starts over. MySQL is happy with the identical sequence, and NO_SSPS=1 avoids it
entirely. It shows up in bulk loading: a multi-row INSERT whose first execute carries a
NULL is refused, and the sets the server rejected come back as SQL_PARAM_ERROR.
MatrixOne: a BIT parameter array with NULLs takes the server down
This one is not the driver's. Against CREATE TABLE t (v bit), a parameter array mixing
values and NULLs corrupts the server's heap and kills the process:
malloc(): unaligned fastbin chunk detected
SIGABRT: abort
Six sets with two NULLs is enough. Single parameters are fine, NULL-free arrays are fine, and
the same NULL-mixed array into TINYINT, INT or VARCHAR is fine. Afterwards every
connection drops with Lost connection to MySQL server during query. It reproduces through
plain pyodbc and plain ODBC, so nothing in the ADBC stack is implicated. It was already fixed
upstream before it could be filed: matrixorigin/matrixone#27645
(2026-08-26) repaired the NULL handling this batch corrupted, and against the 2026-08-28
nightly the same 999-set array completes with the server alive. The released 4.2.0 image is
still the one that aborts, so send booleans as TINYINT there.
The DUAL probe
One last trap belongs to the other connector. MariaDB Connector/ODBC 3.2.9 runs a connect-time probe of its own:
SELECT 1 FROM DUAL WHERE @@sql_mode LIKE '%ansi_quotes%'
A server with no DUAL table cannot connect at all through it. Databend answers
[HY000] (1105) [ma-3.2.9] Unknown table "default"."default".DUAL, GreptimeDB
Table not found: greptime.public.dual, and both fail identically through pyodbc — the
failure is in SQLDriverConnect, before anything of yours runs.
Related: MariaDB Connector/ODBC 3.2 segfaults on a NULL DATE in a parameter array
Where this comes from: the adbcBridge compatibility matrix runs one workload through every database's ODBC driver on Linux, macOS and Windows and records each failure with its first error; the row for this database is at https://adbcbridge.org/matrix/#doris, and the finding is on file with its reproduction in docs/UPSTREAM.md of the repository.