Notes · 2026-09-03

SingleStore's ODBC connector loses SQLGetTypeInfo under ANSI_QUOTES

Two defects in SingleStore Connector/ODBC 1.2.2 with their exact errors - SQLGetTypeInfo failing 42S22 under ANSI_QUOTES, and a SQLColumns segfault on a NULL catalog - and what it gets right.

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 returnedjson 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.