Vendor macOS drivers are built for iODBC's four-byte SQLWCHAR
There are two symptoms, and they arrive in that order.
[H000] [ (0) (SQLDriverConnect)
Every call fails with an empty diagnostic — no SQLSTATE anyone recognises, no message.
pyodbc fails the same way. Or, if the driver does connect, the first statement that errors on
the server — a DROP of a table that is not there, a typo — kills the process with SIGABRT,
nothing on stderr, no crash report. unixODBC's own isql dies the same way.
One type, two widths
macOS has two ODBC driver managers with different SQLWCHAR widths. unixODBC's is two
bytes (UTF-16); iODBC's is wchar_t, four bytes, one code point per unit. A driver compiled
against one cannot be loaded through the other, whatever install_name_tool says: the wide
entry points exchange text in the wrong unit size, and the diagnostic strings come back
unreadable — which is where the empty error comes from. Relinking such a driver is not a valid
recipe.
Three vendor drivers reach macOS only in that form. MySQL Connector/ODBC 26.7.1 for arm64
links @rpath/libiodbcinst.dylib; it is an iODBC driver. OpenLink Virtuoso's Homebrew build
7.2.17 is one. So is Arrow Flight SQL ODBC 0.9.7 for Apple Silicon, which fronts Flight SQL,
InfluxDB 3 and Dremio alike — one binary behind all three.
(OpenSearch's macOS pkg links /usr/lib/libiodbc too.)
The abort has a precise cause, and it is not the driver's. unixODBC reads a driver's wide
diagnostic on the first SQL_ERROR into a fixed 12-byte stack array — SQLWCHAR sqlstate[6]
in DriverManager/__info.c, extract_diag_error_w. A four-byte-SQLWCHAR driver writes 24
bytes there, and the stack protector aborts. Through iODBC the same call returns a proper
42S02. It was reported on 2026-08-25 as
lurcher/unixODBC#239; the maintainer closed
it and then committed a mismatch check the same day, so the crash is now caught rather than
silent. The undocumented driver widths were reported separately, to
openlink/virtuoso-opensource#1469
and dremio/warpdrive#16.
Related: The process dies with SIGABRT on the first SQL error, and it is the manager
The supported configuration
Build a second copy of the bridge against iODBC 3.52.16 and use the iODBC drivers through it,
keeping the unixODBC-built one for everything else. One build per driver manager. The connector
as downloaded also needs its quarantine flag cleared, rpaths for libiodbcinst.dylib and its
bundled libssl, and a fresh ad-hoc signature. With that in place, Virtuoso, Flight SQL,
InfluxDB 3 and Dremio all pass the workload, and Databend, Doris, GreptimeDB and StarRocks —
which fail inside MariaDB Connector/ODBC 3.2.9 at its connect-time DUAL probe or its
prepared/binary-literal path — pass through MySQL's own connector in all five languages. The price is
that pyodbc, odbc-api and arrow-odbc have no columns for those servers: they link unixODBC
and cannot load an iODBC driver at all.
Three width bugs on the way there
The unixODBC overflow above is the first. The second was ours: the bridge's UTF-8 ↔ SQLWCHAR
codecs assumed UTF-16 and wrote surrogate pairs into four-byte slots, which showed up as
[08S01] (2013) Lost connection to MySQL server during query with the server logging
Utf8Error { valid_up_to: 63 }. Every codec now honours sizeof(SQLWCHAR), and the test suite
is built a second time with a four-byte SQLWCHAR to keep it that way. The third is MySQL
Connector/ODBC 26.7.1's own: it writes UTF-16 code units into iODBC's four-byte slots
(surrogate pairs as two units) but does not read bound SQL_C_WCHAR parameters in four-byte
units consistently, so plain ASCII comes through inlined as garbage — text is bound narrow for
that connector on a four-byte build, at no measurable cost (2.0M rows/s fetch on Databend
either way). Virtuoso is the mirror image: on a four-byte build its wide path is correct end to
end while its narrow charset is single-byte, so forcing narrow there was the wrong answer.
A postscript that is not about width at all
The C# comparison column on macOS was empty for a long time for an unrelated reason worth
knowing. System.Data.Odbc loads libodbc.2.dylib by bare name, which against a from-source
unixODBC prefix fails with Dependency unixODBC with minimum version 2.3.1 is required.
DYLD_LIBRARY_PATH fixes it — but SIP strips DYLD_* from /bin/bash, so the variable has to
be exported inside the runner script rather than around it. Nothing about the drivers or the
bridge was involved.
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/#mysql, and the finding is on file with its reproduction in docs/UPSTREAM.md of the repository.