← All notes

Notes · 2026-09-05

Where the native PostgreSQL and MySQL ADBC drivers stop on wire-compatible databases

One ADBC workload through the native PostgreSQL and MySQL ADBC drivers and through adbcBridge on 28 PostgreSQL-wire and MySQL-wire databases - 14 pass both ways, 14 pass only over ODBC - plus a delegation defect this run found in adbcBridge itself and its workaround.

Fourteen databases in the matrix speak the PostgreSQL wire protocol without being PostgreSQL, and fourteen speak MySQL's without being MySQL. Each of those has a native ADBC driver that can be pointed at it: the Apache Arrow project's PostgreSQL driver (adbc-driver-postgresql 1.12.0) and the ADBC Driver Foundry's MySQL driver (0.6.0). On 2026-09-05 the same seven-step workload ran through both native drivers and through adbcBridge over each database's own ODBC driver, with delegation turned off so the comparison is native protocol against ODBC:

connect · SELECT 1 · the entry's eight-column CREATE TABLE plus two INSERTs and a read · adbc_ingest of 1,000 rows (int32, float64, utf8, date32) with a count check · GetTableSchema · GetObjects · read the ingested table back.

Result

Native driver adbcBridge over ODBC
Both pass PostgreSQL, YugabyteDB, TimescaleDB, Citus, Cloudberry, MySQL, MariaDB, Percona, MariaDB ColumnStore, TiDB, Dolt, MatrixOne, SingleStore, OceanBase same 14
Native stops, ODBC passes CockroachDB, Materialize, QuestDB, RisingWave, openGauss, CrateDB, Spanner, ArcadeDB, YDB, Databend, GreptimeDB, Doris, StarRocks, MongoDB BI Connector
ODBC stops, native passes none

Where the native driver stops, and the first error it gives:

Database Step Error
CockroachDB SELECT 1 could not begin COPY: ERROR: unimplemented: binary format for COPY TO not implemented
CrateDB SELECT 1 could not begin COPY: ERROR: line 1:6: no viable alternative at input 'COPY ('
YDB SELECT 1 could not begin COPY: … RawStmt: alternative is not implemented yet : 138
openGauss SELECT 1 ReadRecord failed at row 0: Unexpected end of input (expected 2 bytes but found 0)
Materialize, QuestDB, RisingWave connect the type-resolver bootstrap SELECT oid, typname, typreceive, … FROM pg_catalog.pg_type is rejected
ArcadeDB connect Expected 5 or 6 columns from type resolver pg_type query but got 0
Spanner ingest Primary key must be defined for table "adbc_ing_np" (reads work)
Databend ingest SyntaxException on the driver's generated CREATE TABLE … INT NULL
StarRocks ingest Unexpected input 'NULL' on the same generated DDL
GreptimeDB DDL read invalid decimal precision/scale (1023, 0); ingest: Missing time index constraint
Doris connect Only support prepare SelectStmt or InsertStmt now
MongoDB BI Connector connect recv handshake response error: invalid connection attribute at index 0: EOF

None of these is a defect in the native drivers as such. The PostgreSQL driver reads every result through COPY … TO STDOUT (FORMAT binary) and learns types from pg_catalog, which is the fastest possible path against a real PostgreSQL server and simply is not implemented by the databases that only emulate its wire protocol. The MySQL driver generates MySQL DDL and uses server-side prepared statements, which Doris, StarRocks and Databend do not accept. The ODBC drivers those vendors ship know their own servers; adbcBridge inherits that. The two probes agree with the vendors' own documentation and with the entry notes in the matrix. The probe script and the raw results are kept with the compatibility sources.

A defect in adbcBridge this run found

adbcBridge can hand a connection to a native ADBC driver instead of opening ODBC at all (adbc.odbc.delegate, default auto). With adbc-driver-postgresql importable in the same environment, auto delegates every connection whose ODBC driver is psqlodbc, and it decides that before knowing what the server is. On CockroachDB, CrateDB, openGauss and YDB the native driver connects and the first SELECT then fails as in the table above; on Spanner reads work but adbc_ingest fails because the native driver's CREATE TABLE has no primary key (adbcBridge's own ingest adds one). Where the native driver cannot connect at all (Materialize, QuestDB, RisingWave, ArcadeDB) auto falls back to ODBC as designed.

Found on this run, fixed the same day. 0.1.1, released 2026-09-05, probes the native driver before handing a connection over — one SELECT version() through it at AdbcDatabaseInit, read back through its own result path — and stays on ODBC when that fails, with the reason in adbc.odbc.delegate.last_error. Re-run after the fix with delegation left on, all fourteen PostgreSQL-wire databases pass all seven steps through adbcBridge.

On a 0.1.0 install the workaround is one database option:

adbc.odbc.delegate=never

(adbcbridge.connect(..., delegate="never") in Python, or ADBC_ODBC_DELEGATE=never in the environment). The matrix's PostgreSQL-wire rows describe the ODBC path either way, which is what they always measured.