Five Windows-only ODBC failures are one thing wearing different clothes
What you see
Four strings, four drivers, one cause:
héllo 🚀 (Apache Ignite, wide fetch)
héllo (Informix, stored value)
HY000 (50311) Error converting invalid input with source encoding UTF-8 using ICU.
Rejected bytes began with: E96C (Kinetica, every read of the row)
[iconv] Character set conversion for UTF-32LE to CP1252 failed (TDengine, NCHAR column)
A fifth does not look like mojibake at all: the Arrow Flight SQL driver's Windows build
returns U+1F680 as U+F680 — the low 16 bits — through SQL_C_WCHAR, and ?
through SQL_C_CHAR. All five entries pass on Linux, and pyodbc reproduces each failure, so
none is a client-library bug.
What is actually happening
On Linux a driver's narrow entry points usually carry UTF-8. On Windows the narrow path is the system ANSI code page, cp1252 by default, and there are two ways onto it by accident.
The first is the driver manager, which maps every W call onto a driver that exports no W
entry points, converting through the ANSI code page. Apache Ignite's
ignite.odbc.dll is exactly such a driver, and its own narrow path is untouched UTF-8 in
both directions. So everything sent wide is mangled on the way in — a statement literal
'héllo' matches nothing — and everything read wide comes back double-encoded, while a
narrow SQLExecDirect and a SQL_C_CHAR read are byte-exact. Routing statement text and
fetches narrow passes the workload and is 2.5× faster: 775,660 rows/s against 316,230.
The second is a driver doing it to itself. Kinetica's KineticaODBC.dll does export the
W entry points and then transcodes wide statement text through the ANSI code page inside
itself: the literal 'héllo 🚀' reaches the server as cp1252 bytes and every later read of
that row fails the ICU conversion above. Its narrow statement path hands UTF-8 through byte
for byte, so sending statement text narrow fixes it — but its narrow fetch is not UTF-8, so
only half the Ignite treatment fits. TDengine's taos_odbc.dll is the same family: it takes
the character set from GetACP() and ignores its own CHARSET_FOR_COL_BIND keywords, and an
earlier build's iconv had no CP1252 → UTF-8 table at all, so statement text in the system
code page could not be converted and nothing executed.
Informix shows the third way in: the caller's own portability assumption. IBM's CLI driver
takes an astral parameter through SQL_C_CHAR byte-exact — wide UTF-16 gets -415 Data
conversion error — but a client that resets the Linux "narrow is UTF-8" rule wholesale on
Windows sends it wide and fails. It also needs DB2CODEPAGE=1208 in the environment, or the
driver reads those UTF-8 bytes as cp1252 and stores them double-encoded (héllo). The
Arrow Flight SQL driver has no correct text route in either width, but its SQL_C_BINARY
conversion of a character column hands the server's UTF-8 through byte-exact, so reading text
as binary is both correct and 1.8× faster than the wide read.
Two that look the same and are not
Firebird was a name. The driver answers SQL_DRIVER_NAME as OdbcFb on Linux and
FirebirdODBC on Windows — its DLL's name — so a workaround keyed on the Linux spelling
silently did not apply, and the run stopped at a guard: accepted a parameter array of 2 sets
but reported neither SQL_ATTR_PARAMS_PROCESSED_PTR nor SQL_ATTR_PARAM_STATUS_PTR. Matching
both names passes the whole workload.
Exasol was the registry. Windows' driver manager reads driver definitions from HKLM
only and answers a DLL path in DRIVER= with IM002, where unixODBC accepts one. Nothing is
wrong with EXAODBC.dll: install it machine-wide, name it by its registered name, and the
entry runs verbatim. The same rule caught psqlodbc.
What the repairs are called
Three flags in this project's quirk table carry all of the above, and name what you would otherwise build yourself:
narrow_params— bind string parameters asSQL_C_CHAR(UTF-8) even where the wide path is the default, including on Windows (Apache Ignite, Informix).narrow_sql— (Windows) send caller statement text through the narrowSQLExecDirect/SQLPrepareas UTF-8 (Apache Ignite; the fix Kinetica needs).text_as_binary— (Windows) read character columns asSQL_C_BINARYand take the bytes as UTF-8 (Arrow Flight SQL driver).
Above them sits one rule: on Windows the assumption "this driver's narrow path is UTF-8" is turned back off for every driver except Ignite, the one ANSI-only driver where it is true.
Why a UTF-8 system code page is the only general fix
Every repair above chooses which entry point the caller uses, which only helps while one of
the driver's paths is clean. Where the driver reads the code page itself —
GetACP(), in taos_odbc's case — and ignores every keyword offered to override it, no call
reaches the value intact: it is converted before either C type sees it, and the row is
already double-encoded in storage. Only a UTF-8 system code page changes that — set it before
you start debugging a driver.
The Ignite, Informix, Arrow Flight SQL, TDengine and Firebird findings were recorded in the Windows campaign of 2026-08-25; Kinetica's and Exasol's cells come from the batch measured 2026-09-03/04.
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/#ignite, and the finding is on file with its reproduction in docs/UPSTREAM.md of the repository.