SQL_ERROR with no diagnostic, and a status array nobody writes
Two defects in sqliteodbc 0.99991 on Linux, both of the kind that make a caller doubt its own code rather than the driver, because in both cases the driver's answer is silence rather than an error you can quote. Found on 2026-08-25, running one workload through the 46 databases' ODBC drivers of that campaign.
SQLGetData(SQL_C_NUMERIC) returns SQL_ERROR with nothing behind it
Ask for a column as SQL_C_NUMERIC and you get SQL_ERROR. Then ask what went wrong and
the driver has nothing to say: SQLGetDiagRec returns zero diagnostic records. No
SQLSTATE, no message, no native code. There is nothing to search for, which is why this
note exists.
The driver is not confused about what happened. Request the same conversion the other way,
through SQLBindCol, and it answers properly:
HY003 invalid type 2
HY003 is the honest reply. It is only the SQLGetData
path that loses it. So the same unsupported conversion is a documented refusal through one
entry point and an unexplained failure through the other.
What to do: do not ask this driver for SQL_C_NUMERIC at all. Read decimal and
numeric columns as text with SQL_C_CHAR and parse them yourself; a driver's textual
rendering of a decimal is lossless, and it is exact into a 128-bit decimal for anything
within precision 38. That is the route adbcBridge takes for every driver, which is why
this defect costs it nothing.
SQL_ATTR_PARAM_STATUS_PTR is accepted and never written
Set a parameter status array before executing a parameter array, and the driver accepts
the attribute — SQLSetStmtAttr succeeds — and then never writes into it. A caller that
trusts the API reads back whatever was in that memory before the call: uninitialised
status values, indistinguishable from real ones.
This is a shape worth recognising, because it is not unique to this driver. An
out-parameter a driver accepts and never writes is invisible unless you prepare for it. The
generic defence is to pre-fill the memory with a value that cannot be mistaken for an
answer, so "not written" reads as unknown rather than as a plausible result. adbcBridge
does exactly that for row counts, for a different driver that never wrote SQLRowCount:
the variable is pre-filled with all-ones, which reads as -1 at either SQLLEN width, and
-1 is what ODBC and ADBC already use for an unavailable row count. Every driver in the
matrix that does write the out-parameter is unaffected — sqliteodbc among them, which
writes 0 after DDL and 1 after a single-row INSERT, though it answers 0 for
SQLRowCount on an open SELECT.
Related: Where PostgreSQL-wire servers part company with PostgreSQL
Three smaller ones in the same family
Recorded alongside those two, all from the same driver and all silent:
- a
TIMESTAMPwritten through a boundSQL_C_TYPE_TIMESTAMPis stored with three fractional digits, while the same value in statement text keeps six; - a second
SQLGetDataon a column that was already returned in full hands back a suffix underSQL_SUCCESSinstead ofSQL_NO_DATA; - the width the driver claims for every
TEXTcolumn is 65,536 characters, which is a type maximum and not a real bound.
The last one is survivable only because of something the driver gets right: its result set
is materialised in memory, and SQLFetchScroll(SQL_FETCH_ABSOLUTE) re-reads any row
correctly with a plain SQLFetch resuming afterwards. That makes the 65,536-char width
bindable — the truncated values can be re-read individually rather than forcing the whole
result set to row-at-a-time reads, which is what an unbounded column costs on drivers with
no such repair path.
The route around it
Where a native ADBC driver for the database exists, the best answer to an ODBC driver's
quirks is not to use it. adbcBridge delegates SQLite to adbc_driver_sqlite when that
driver is installed, so none of the above is on the path at all; the ODBC route is what
runs when it is not installed, and it is the one the matrix measures.
No vendor issue is filed for any of these. They sit in the "documented here, not yet reported" table of docs/UPSTREAM.md with their conditions and first errors, so there is no fix version to name; a reproduction contributed by anyone is welcome.
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/#sqlite, and the finding is on file with its reproduction in docs/UPSTREAM.md of the repository.