From 3fa9d404791bdeb3d4ddfccce897d6fb4e235a69 Mon Sep 17 00:00:00 2001 From: singhpratech <42719720+singhpratech@users.noreply.github.com> Date: Fri, 4 Sep 2026 15:10:46 -0400 Subject: [PATCH] =?UTF-8?q?docs:=20UPSTREAM=20=E2=80=94=20psqlodbc=20#207?= =?UTF-8?q?=20fixed=20by=20PR=20#210=20(22003=20for=20out-of-range=20integ?= =?UTF-8?q?er=20conversions),=20merged=202026-09-04?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_013ZWm8DfxFwdn7LyyVAM9s5 --- bench/MATRIX_BENCHMARKS.md | 13 ++++++++++--- docs/UPSTREAM.md | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/bench/MATRIX_BENCHMARKS.md b/bench/MATRIX_BENCHMARKS.md index 2898578..a6d837b 100644 --- a/bench/MATRIX_BENCHMARKS.md +++ b/bench/MATRIX_BENCHMARKS.md @@ -1,14 +1,19 @@ # Per-database benchmarks -Generated by `bench/matrix_bench.py` on 2026-09-03. Table `(id int32, val double, txt varchar(20), dt date)`. **Ingest** = `adbc_ingest(mode="create")` of the row count in *Rows* on an autocommit connection (the driver batches the stream into one transaction itself), DDL + data + commit, row count verified; row-at-a-time (`adbc.odbc.array_binding=false`) vs *array* = the driver default, parameter arrays where the ODBC driver handles them; pyodbc = `executemany` (`fast_executemany` where the driver allows). **Fetch** = full read of the second *Rows* count, median of 3 after a warmup; `fetch_arrow_table()` vs pyodbc `fetchall()` -> `pyarrow.Table`; *native* = the same read with [native delegation](../README.md#native-delegation) handing the connection to the database's own ADBC driver. All rates are rows/s; higher is better. Servers run locally in Docker, so numbers reflect the ODBC driver + database, not the network. +Generated by `bench/matrix_bench.py` on 2026-09-04. Table `(id int32, val double, txt varchar(20), dt date)`. **Ingest** = `adbc_ingest(mode="create")` of the row count in *Rows* on an autocommit connection (the driver batches the stream into one transaction itself), DDL + data + commit, row count verified; row-at-a-time (`adbc.odbc.array_binding=false`) vs *array* = the driver default, parameter arrays where the ODBC driver handles them; pyodbc = `executemany` (`fast_executemany` where the driver allows). **Fetch** = full read of the second *Rows* count, median of 3 after a warmup; `fetch_arrow_table()` vs pyodbc `fetchall()` -> `pyarrow.Table`; *native* = the same read with [native delegation](../README.md#native-delegation) handing the connection to the database's own ADBC driver. All rates are rows/s; higher is better. Servers run locally in Docker, so numbers reflect the ODBC driver + database, not the network. | Database | Rows | Ingest | Ingest (array) | Ingest pyodbc | Fetch | Fetch pyodbc | Fetch vs pyodbc | Fetch native | |---|---:|---:|---:|---:|---:|---:|---:|---:| -| hana (HDB (via ODBC) 02.00.0088 00-1760424921) | 20,000 / 200,000 | 7,675 | 1,142,092 | — | 6,768,257 | — | — | — | -| exasol (EXASolution (via ODBC) 2025.01.0014) | 10,000 / 100,000 | 10,018 | 10,226 | 16,946 | 1,535,643 | 389,562 | 3.9× | — | | firebird (Firebird (via ODBC) 06.03.1812 LI-V Firebird 5.0) | 20,000 / 200,000 | 40,468 | 41,050 | — | 297,837 | — | — | — | | matrixone (MySQL (via ODBC) 8.0.30-MatrixOne-v4.2.0) | 10,000 / 100,000 | 97,521 | 67,034 | 4,639 | 1,919,466 | 888,285 | 2.2× | — | +| singlestore (MySQL (via ODBC) 5.7.32) | 10,000 / 100,000 | 100,745 | 201,384 | 18,767 | 1,676,765 | 785,127 | 2.1× | — | +| hana (HDB (via ODBC) 02.00.0088 00-1760424921) | 10,000 / 100,000 | 10,555 | 682,120 | 564,060 | 7,283,051 | 666,905 | 10.9× | — | +| exasol (EXASolution (via ODBC) 2025.01.0014) | 10,000 / 100,000 | 10,072 | 10,323 | 21,291 | 1,507,321 | 389,599 | 3.9× | — | +| altibase (Altibase (via ODBC) 7.3.0.1.4) | 10,000 / 100,000 | 31,485 | 959,383 | 421,608 | 2,387,341 | 770,776 | 3.1× | — | +| kinetica (Kinetica (via ODBC) 7.1.9.33.20240329114503) | — / 100,000 | read-only driver | — | — | 597,315 | — | — | — | +| ibmi (DB2/400 SQL (via ODBC) 07.05.0015) | 10,000 / 100,000 | 966 | 1,392 | 6,735 | 1,929 | — | — | — | +| ingres (INGRES (via ODBC) 10.00.0000) | 10,000 / 100,000 | 4,261 | 1,853 | 1,328 | — | — | — | — | | sqlite (SQLite (via ODBC) 3.45.1) | 10,000 / 100,000 | 343,225 | 394,035 | 189,214 | 517,403 | 559,219 | 0.9× | 3,284,704 | | duckdb (DuckDB (via ODBC) ) | 10,000 / 100,000 | 11,752 | 9,669 | 738 | 1,730,148 | 650,933 | 2.7× | — | | postgres (PostgreSQL (via ODBC) 16.0.15) | 10,000 / 100,000 | 52,585 | 61,029 | 16,594 | 1,347,913 | 722,001 | 1.9× | 5,052,935 | @@ -26,6 +31,8 @@ Failures: * **firebird** ingest_pyodbc: Error: ('0000', '[0000] [ (0) (SQLDriverConnect)') * **firebird** fetch_pyodbc: Error: ('0000', '[0000] [ (0) (SQLDriverConnect)') +* **ibmi** fetch_pyodbc: pyodbc timed out after 600s +* **ingres** fetch: OperationalError: UNKNOWN: [ODBC] SQLExecute failed Caveats for this run: Oracle and Db2 were measured while a CockroachDB container on the same host was stuck in schema-change GC at ~5 cores (pyodbc is equally slow on them here, so it is the host, not diff --git a/docs/UPSTREAM.md b/docs/UPSTREAM.md index 6e73552..530cee8 100644 --- a/docs/UPSTREAM.md +++ b/docs/UPSTREAM.md @@ -34,7 +34,7 @@ before it is filed. | 2026-08-29 | QuestDB | [questdb/questdb#7566](https://github.com/questdb/questdb/issues/7566) | A `BINARY` column in a simple-query result is sent with format code 1 (raw bytes) where PostgreSQL always sends text, so libpq/psycopg and psqlodbc truncate the value at its first `0x00`; the extended protocol returns it whole. Side by side with PostgreSQL 16 via `PQfformat`. | fix PR open — [questdb/questdb#7569](https://github.com/questdb/questdb/pull/7569) | | 2026-08-29 | QuestDB | [questdb/questdb#7567](https://github.com/questdb/questdb/issues/7567) | `DEALLOCATE ALL` fails with `Cannot invoke "io.questdb.cutlass.pgwire.PGPipelineEntry.close()" because "pe" is null` — even on a fresh autocommit connection with nothing prepared; a named `DEALLOCATE` works. | fix PR open — [questdb/questdb#7568](https://github.com/questdb/questdb/pull/7568) | | 2026-08-29 | CrateDB | [crate/crate#20085](https://github.com/crate/crate/issues/20085) | A multi-statement simple query returns each `CommandComplete` tagged with the leading text of the whole string (`BEGIN;INSERT 0`, `BEGIN;INSERT 1`) instead of per-statement tags (`BEGIN`, `INSERT 0 1`), so tag-parsing clients such as psqlodbc get no row count inside a transaction. | **fixed** — [crate/crate#20088](https://github.com/crate/crate/pull/20088) merged 2026-08-31, ships in 6.4.4 | -| 2026-08-29 | psqlodbc | [postgresql-interfaces/psqlodbc#207](https://github.com/postgresql-interfaces/psqlodbc/issues/207) | `SQLGetData`/`SQLBindCol` narrow an out-of-range `int8` to `SQL_C_SLONG` (and to `SQL_C_SSHORT`) silently — `9223372036854775807::bigint` reads as `-1` under `SQL_SUCCESS` where ODBC specifies `22003`; `pg_atol`/`pg_atoi` in `convert.c` store the low bits with no range check. Stock PostgreSQL 16. | open | +| 2026-08-29 | psqlodbc | [postgresql-interfaces/psqlodbc#207](https://github.com/postgresql-interfaces/psqlodbc/issues/207) | `SQLGetData`/`SQLBindCol` narrow an out-of-range `int8` to `SQL_C_SLONG` (and to `SQL_C_SSHORT`) silently — `9223372036854775807::bigint` reads as `-1` under `SQL_SUCCESS` where ODBC specifies `22003`; `pg_atol`/`pg_atoi` in `convert.c` store the low bits with no range check. Stock PostgreSQL 16. | **fixed** — [postgresql-interfaces/psqlodbc#210](https://github.com/postgresql-interfaces/psqlodbc/pull/210) by Dave Cramer, merged 2026-09-04 (`SQLGetData`/`SQLBindCol` now return `SQL_ERROR` with `22003` for an out-of-range integer conversion instead of the wrapped low bits); re-test against the merge commit pending | | 2026-08-29 | psqlodbc | [postgresql-interfaces/psqlodbc#208](https://github.com/postgresql-interfaces/psqlodbc/issues/208) | With `Protocol=7.4-0`, `SQLFreeHandle` of a prepared statement inside a transaction still sends `SAVEPOINT _per_query_svp_;DEALLOCATE …;RELEASE`; on a server without `SAVEPOINT` (QuestDB, Materialize) the error is swallowed, the server rolls the transaction back and `SQLEndTran(SQL_COMMIT)` returns `SQL_SUCCESS` for rows that are gone. Wire capture included. | **fixed** — [postgresql-interfaces/psqlodbc#209](https://github.com/postgresql-interfaces/psqlodbc/pull/209) by Dave Cramer, merged 2026-09-02 (rollback level 0 no longer savepoint-wraps the internal `DEALLOCATE`; the tested branch commit is the merged one); QuestDB confirmed, Materialize waits on its own fix | | 2026-09-01 | Materialize | [MaterializeInc/materialize#38605](https://github.com/MaterializeInc/materialize/discussions/38605) (bug-report discussion; their tracker) | `DEALLOCATE "Name"` does not unquote the identifier, so a statement prepared at the protocol level (`Parse`) with a mixed-case name — psqlodbc's `_PLAN0x…` — cannot be released: `26000 prepared statement "\"_PLAN0x7\"" does not exist`, and inside a transaction the failure aborts it; PostgreSQL 16 releases the same statement. Filed with a psycopg side-by-side against PostgreSQL 16. The server half of the psqlodbc#208 row-loss pair. | **fix in review** — [MaterializeInc/materialize#38606](https://github.com/MaterializeInc/materialize/pull/38606) by antiguru (Materialize), opened 2026-09-01; unquotes prepared-statement, portal and cursor names before lookup |