Notes · 2026-08-25

Two psqlodbc bugs that lost rows and narrowed integers, and two traps

psqlodbc silently discarded a committed transaction on servers without SAVEPOINT and narrowed an out-of-range int8 to -1; both are fixed, and two version-and-build traps remain.

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.