Notes · 2026-08-25

A NULL column that reads back as the previous row's value

IBM's Db2 clidriver (Db2 and Informix) and Actian Ingres' driver are built with a four-byte SQLLEN on 64-bit Linux, so indicators are read at the wrong stride and NULLs disappear.

The symptom is quiet and specific. You fetch a block of rows, and a column that is NULL in row 2 comes back holding row 1's text, padded out to the bound width. A NULL FLOAT8 comes back as 1e-323. A NULL date comes back as year 0.

What is actually happening

The driver was compiled with a 32-bit SQLLEN/SQLULEN on 64-bit Linux, while unixODBC, pyodbc and the calling application all use an 8-byte one. Every SQLLEN the driver writes is half as wide as the caller's storage, in two different ways:

Reading a single column one row at a time mostly looks correct, which is why simple probes pass and the damage only shows up with several columns or several rows per fetch.

Which drivers

IBM's Db2 CLI driver. The clidriver package's libdb2.so is built this way; IBM's 64-bit-SQLLEN build is the separate libdb2o.so, shipped only in the full server and data-server-driver packages. The same libdb2.so is how Informix is reached over DRDA, so Informix inherits it exactly.

Actian Ingres. libiiodbcdriver.1.so has a four-byte SQLLEN on 64-bit Linux too, found on 2026-09-03 when Ingres 10.1 was added: a NULL VARCHAR came back as row 1's text padded out to the bound width, a NULL FLOAT8 as 1e-323, a NULL ANSIDATE as year 0.

MDB Tools, the Microsoft Access driver, writes bound-column indicators four bytes wide and leaves the high half untouched, so after SQLFetch a NULL column reads 0x……ffffffff and every NULL looks like a 4 GB value — the same shape, from a completely unrelated driver.

Clients that assume the standard width do not survive it and say so in their own vocabulary. Rust's arrow-odbc panics while describing a column:

Failed to retrieve data type from ODBC driver. The SQLLEN could not be converted
to a 16 Bit integer

and .NET's System.Data.Odbc raises Arithmetic operation resulted in an overflow. on the same result set, while Go's alexbrainman/odbc reports SQLGetDiagRec failed: ret=-2.

What to do about it

There is no fix on the driver side to wait for; the layout is what the vendor shipped. A client has to read every driver-written SQLLEN at four-byte width — sign-extending the scalars, and walking indicator arrays with stride 4 — and zero-initialise those out-variables before the call, so a driver that answers without writing is not read as a stale value.

adbcBridge does this behind one flag, sqllen_32bit, documented in docs/reference/quirks.md. It is autodetected from SQL_DRIVER_NAME: the quirk turns on when the name contains db2 (excluding libdb2o/db2o., IBM's wide build), mdbtools or iiodbcdriver. The option adbc.odbc.sqllen_32bit pins it either way, on the database, connection or statement — useful for any other driver built the same way, and useful for confirming that the quirk is what is fixing a result set. The fast path is unchanged when it is off.

The related case: Virtuoso on Win64

There is a narrower version of the same bug. OpenLink Virtuoso's virtodbc.dll 7.2.17 on Windows x64 strides the bound-column indicator array by 4 bytes per row on a block cursor, while the rest of its SQLLEN use is normal. It is a read-side stride only, not a whole-driver width mismatch, and it is carried by its own flag (ind_stride_32bit) rather than by sqllen_32bit. The same driver also advertises the SQL_GD_* extensions and then fails SQLSetPos(SQL_POSITION) on a block cursor, so a truncated value cannot be repaired the documented way. Both are recorded in bench/BENCHMARKS-windows.md.

No vendor issue is filed for the SQLLEN width itself, so there is no fix version to name: the Db2, Informix and Ingres cases are recorded with their conditions in docs/COMPATIBILITY.md and docs/reference/quirks.md, and the Virtuoso stride sits in the "documented here, not yet reported" table of docs/UPSTREAM.md.

Related: Five things OpenLink Virtuoso's ODBC driver does silently and wrongly

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/#db2, and the finding is on file with its reproduction in docs/UPSTREAM.md of the repository.