SingleStore's ODBC connector loses SQLGetTypeInfo under ANSI_QUOTES
SingleStore serves the MySQL wire protocol and announces itself as 5.7.32, so MySQL
Connector/ODBC drives it. It also publishes a free ODBC driver of its own, a MariaDB
Connector/ODBC fork, and running both side by side through the same compatibility workload
on 2026-09-03 turned up two defects in the SingleStore driver. Both were filed on
2026-09-04 with plain-ODBC reproductions.
42S22 Unknown column 'json' in 'field list' (1054)
SQLGetTypeInfo(SQL_ALL_TYPES) -> 42S22 [ss-1.2.2][9.1.1]Unknown column 'json'
in 'field list' (1054) (SQLGetTypeInfo)
SQLGetTypeInfo(SQL_TYPE_TIMESTAMP) -> 42S22 … Unknown column 'datetime' …
The call succeeds on a fresh connection and fails on the same connection once
ANSI_QUOTES is in sql_mode. The named column is always the first type name the call
would have returned — json for SQL_ALL_TYPES, datetime for SQL_TYPE_TIMESTAMP —
which says exactly what is happening: the driver builds its type table from a template
of double-quoted string literals (ma_info.c), and ANSI_QUOTES turns every one of them
into an identifier. SQLColumns, SQLTables and ordinary queries are unaffected;
only SQLGetTypeInfo breaks, and MySQL Connector/ODBC is fine against the same server in
the same mode. Filed as
memsql/singlestore-odbc-connector#45.
The failure is silent to anything that treats a failing SQLGetTypeInfo as "this driver
has no such type" and falls through to a portable ANSI type name — which is the right
thing to do here. The only visible effect is the spelling in generated DDL: text and
tinyint(1) from the fallback where MySQL's connector's type table gives mediumtext and
bit(1). Both are valid SingleStore columns and both pass a read-back. The alternative is
not to set ANSI_QUOTES, which costs you double-quoted identifiers.
SQLColumns with a NULL catalog segfaults the process
On a connection opened without Database= in the connection string, SQLColumns with a
NULL CatalogName crashes the client on strdup(NULL) (ma_statement.c). The mechanism: TABLE_CAT falls back to the client library's
current-database field, which stays NULL even after a USE statement has selected a
database, and the driver copies it unconditionally. It only fires when at least one column
matches: zero matching rows return cleanly. Filed as
memsql/singlestore-odbc-connector#46.
Three things avoid it: pass an explicit catalog, put Database= in the connection string,
or ask for a table that does not exist.
What the driver gets right
It passes the whole workload — the emoji round trip, VARBINARY(10), DECIMAL(10,3),
NULL parameters, affected-row counts, catalog metadata and the batched ingest and read —
and it is the better of the two drivers on three counts. It reports SQL_DBMS_NAME
SingleStore and SQL_DBMS_VER 09.01.0001, the real build, where MySQL's connector
reports the wire's MySQL / 5.7.32. It describes a DATETIME(6) column as COLUMN_SIZE
26 / DECIMAL_DIGITS 6, where MySQL Connector/ODBC says 19/0 with a SQLGetTypeInfo row
of 21 — enough for pyodbc to store 13:45:10.123456 as 13:45:10.1, and exact through
this one. And it needs neither the PLUGIN_DIR= that MySQL Connector/ODBC needs here for
mysql_native_password, nor the LD_PRELOAD=…/libstdc++.so.6 that same connector needs
in any Linux process that has imported pyarrow.
Related: What MySQL Connector/ODBC does to a DATETIME(6), and six other findings
Two things about the server, while we are here
Tables are columnstore by default. @@default_table_type is columnstore, so a plain
CREATE TABLE gets one — SHOW CREATE TABLE adds SORT KEY __UNORDERED () and SHARD KEY
(), and no explicit key of either kind is needed. The same DDL spelled CREATE ROWSTORE
TABLE was run through the whole probe side by side and behaves identically on every point
the workload checks: the emoji, the binary bytes, microseconds, DECIMAL(10,3), the
all-NULL row, SQLColumns ordering and affected-row counts.
Measure twice. SingleStore compiles each query shape to machine code the first time it sees it, about 100 ms apiece, and caches the plan. A first run on a cold container measured ingest 75,107 rows/s against 109,123 with parameter arrays, which reads as "arrays are 45% faster"; the second run, plans cached, gave 163,686 and 159,329 — the two forms within 3% of each other. The first run was measuring the compiler. Warm, it is the fastest ingest of the twelve MySQL-wire entries in the matrix.
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/#singlestore, and the finding is on file with its reproduction in docs/UPSTREAM.md of the repository.