Two psqlodbc bugs that lost rows and narrowed integers, and two traps
Fourteen servers, one driver
Fourteen entries in this compatibility matrix reach their server through psqlodbc, and most of them are not PostgreSQL. Driving the same workload through all of them turned up two defects in the driver — both now fixed — and two traps that are still there.
SQLEndTran(SQL_COMMIT) returns success for rows that are gone
On a manual-commit connection, a prepared INSERT executed through psqlodbc and then freed
before the commit loses its rows. Two SQLExecutes return SQL_SUCCESS,
SQLEndTran(SQL_COMMIT) returns SQL_SUCCESS, and count(*) is 0 — from the same
connection and from a fresh one. Freed after the commit, both rows are there.
The wire shows why. On SQLFreeHandle the driver sends
SAVEPOINT _per_query_svp_;DEALLOCATE "_PLAN0x…";RELEASE _per_query_svp_
as one query, regardless of Protocol=7.4-0 — the trailing digit is psqlodbc's level of
rollback on errors, and 0 is what turns its per-statement savepoints off. A server
without SAVEPOINT rejects it. QuestDB answers
ERROR: table does not exist [table=SAVEPOINT]; the transaction is aborted, the
error is never surfaced through ODBC, and the later COMMIT is answered with success.
Driven through libpq directly (psycopg 3: named or unnamed statements, text or binary
parameters, a plain DEALLOCATE inside the transaction) every combination keeps its rows,
so this is the driver's own path meeting a server that has no savepoints.
Found on 2026-08-25 and filed with a wire capture as
postgresql-interfaces/psqlodbc#208
on 2026-08-29. Fixed by
postgresql-interfaces/psqlodbc#209,
merged 2026-09-02:
rollback level 0 no longer savepoint-wraps the internal DEALLOCATE. QuestDB is confirmed
against the merged branch; Materialize waits on its own half of the pair. Until the fix is
in your build, on a manual-commit connection to a server without SAVEPOINT, release
statements after Commit(), never before.
Related: Where PostgreSQL-wire servers part company with PostgreSQL
9223372036854775807 reads back as -1
SQLGetData and SQLBindCol narrowed an out-of-range int8 to SQL_C_SLONG — and to
SQL_C_SSHORT — silently. 9223372036854775807::bigint came back as -1 under
SQL_SUCCESS, where ODBC specifies 22003. pg_atol and pg_atoi in convert.c stored
the low bits with no range check. This is stock PostgreSQL 16, not a fork's doing; it
matters most where a column you would expect to be 32-bit is not — CockroachDB's INTEGER
is 64-bit and reports column size 19.
Filed as
postgresql-interfaces/psqlodbc#207
on 2026-08-29. Fixed by
postgresql-interfaces/psqlodbc#210,
merged 2026-09-04:
SQLGetData and SQLBindCol now return SQL_ERROR with 22003 for an out-of-range
integer conversion instead of the wrapped low bits. A re-test against the merge commit is
pending here.
Trap 1: psqlodbc 18 asks a question 16 did not
psqlodbc 18.00.0002 sends SHOW DateStyle; at connect (connection.c:1109). YDB's
PostgreSQL layer answers
unrecognized configuration parameter "datestyle"
and the connect fails outright, so nothing downstream gets a chance to work around it. The 16.x line does not issue the statement, and the YDB entry passes through psqlodbc 16 on every operating system. Arguably this one is YDB's to implement rather than the driver's to drop; either way, if a PostgreSQL-wire server that worked stops connecting after a driver upgrade, this is the first thing to check.
Trap 2: the ANSI library answers HYC00 where the Unicode one works
A multi-row executemany (or adbc_ingest) fails with
[HYC00] (10) Unrecognized C_parameter type in copy_statement_with_parameters
while single statements and plain reads work. pyodbc through the same DSN fails the same way
on an executemany, and with 07006 Received an unsupported type from Postgres (14) on a
fetch.
make install of psqlodbc installs two libraries: psqlodbca.so, the ANSI build, and
psqlodbcw.so, the Unicode build. The ANSI build exports no SQL*W entry points and has no
SQL_C_WCHAR case in its parameter-copy path, so any bound wide parameter — which is how
text is sent here — is refused. Distro packages ship both files, and a DSN or a Driver=
value can point at either, which is how a working setup becomes a broken one with no change
to the code. Point Driver= (or the DSN's Driver line) at psqlodbcw.so.
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/#postgres, and the finding is on file with its reproduction in docs/UPSTREAM.md of the repository.