Notes · 2026-08-24

A Unicode string parameter cannot reach a MySQL-wire warehouse's prepare

Six servers behind MySQL Connector/ODBC 9.4 - what their server-side prepare does with a BLOB-typed string, a MYSQL_TYPE_NULL, a BIT array of NULLs, and a connector probe that assumes DUAL.

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:

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.