Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ table with neither gets one partition. The numbers are in
| Oracle 23ai Free | Instant Client ODBC 23 | PASS | set `NLS_LANG=.AL32UTF8` for non-ASCII; no `SQL_C_SBIGINT`, so 64-bit ints are sent as numeric text. SQORA's rowset is fixed for the life of a cursor: changing `SQL_ATTR_ROW_ARRAY_SIZE` on an open cursor is accepted (`SQLSetStmtAttr` succeeds, `SQLGetStmtAttr` reads it back) and then goes wrong in one of three unannounced ways. Raising it segfaults inside `libsqora` on a later `SQLFetch` (`bcoReturnColData` through `bcoCacheFetch`, a per-rowset slot sized at execute time) -- on a `(NUMBER, CLOB)` cursor and equally on cursors with no LOB at all; whether a given raise dies depends on how deep the cursor already is, so the same raise survives after one rowset and crashes after twelve. Where it does not crash it can rewind, re-delivering rows already returned and dropping others while still ending on the right total. Lowering it never crashes and is no better: the driver keeps stepping by the original size and returns only the first N rows of each block, so 100,000 rows read at 1,024 and dropped to 128 come back as 13,440-14,336 depending on where the change falls, `SQL_NO_DATA` and no diagnostic. Reproduced with plain `SQLBindCol`/`SQLFetch`, nothing of ours on the stack. The reader therefore settles the rowset before the first fetch and never moves it (`fixed_rowset`), and a value that outgrew its bound buffer cannot be repaired once the rowset holds more than one row -- `SQLGetData` answers HY109 there, `SQLSetPos` HY109 and `SQLFetchScroll` HY106 at any size, although `SQL_GETDATA_EXTENSIONS` advertises `SQL_GD_BLOCK|SQL_GD_BOUND`; at a one-row rowset `SQLGetData` does re-read the whole value -- so a column with no real declared width -- CLOB, NCLOB, BLOB, LONG, all reported as 2,147,483,647 wide -- stays unbound and is read a row at a time with `SQLGetData`. Result sets without a LOB column keep the full block cursor, at the one size chosen before their first row. Also seen: Oracle 23.26 takes the standard multi-row `VALUES (…),(…)` form, prepared or direct, so the `INSERT ALL` fallback is never reached there; `SQL_C_SBIGINT` is refused on the read side too (07006), and `SQLGetTypeInfo(SQL_BIGINT)` returns nothing; an unconstrained `NUMBER` column is described `SQL_FLOAT` and read through a double (9223372036854775807 comes back 9223372036854780000, exact in `NUMBER(19)`); the empty string is NULL on both the literal and the bound path. This matters because ingest DDL spells an Arrow string as CLOB here, so ingest-then-read-back used to crash on the default path; the compat entry now writes 3,000 mixed-width strings (one in 250 is 9 KB) and reads them all back whole |
| IBM Db2 12.1 | Db2 clidriver (`libdb2.so`) | PASS | 32-bit `SQLLEN` (see `adbc.odbc.sqllen_32bit`); ingest DDL spells an Arrow string as the widest `VARCHAR`, not the `LONG VARCHAR` the driver's `SQLGetTypeInfo(SQL_LONGVARCHAR)` names (deprecated; will not sort, group or de-duplicate -- `SQL0134N` on `ORDER BY`, `GROUP BY`, `DISTINCT` and `UNION` -- and has no bulk-insert path: ~7k rows/s whatever the batch size against ~430k for `VARCHAR(32672)` in a 20,000-row test, 30-56x on a warm database and over 200x through `adbc_ingest` on a cold one; an earlier run recorded ~700x) |
| Db2 for i 7.5 (IBM i, PUB400.COM public server) | IBM i Access ODBC 1.1.0.29 (`libcwbodbc.so` 07.01.029, IBM i host servers) | PASS | a different engine and a different wire from the Db2 row above, not DRDA: IBM's own IBM i Access driver on the host-server ports, `SQL_DBMS_NAME` "DB2/400 SQL" 07.05.0015. **Hosted** — IBM i runs on Power hardware, there is no container and no emulator, and the entry points at [PUB400.COM](https://pub400.com), the free public IBM i (`PUB400_HOST`/`PUB400_USER`/`PUB400_PASSWORD`; `big_rows` 3,000, one connection at a time). Driver quirks: the `Driver=` value may be at most **35 characters** (`Key value in connection string too long. (30119)` at 36 — the driver's own installed path is 36, so a DSN-less connection needs the registered name or a short symlink); the libraries resolve their message catalogues and conversion tables under a compiled-in `/opt/ibm/iaccess`, and without that directory every diagnostic degrades to `CWBNL0202 - cwbodmsg.dll` and the real error is lost (root-free route: a `bwrap` mount namespace). Generated ingest DDL spells an Arrow string `VARCHAR(8000) CCSID 1208`, not the `CLOB` the driver's `SQLGetTypeInfo(SQL_LONGVARCHAR)` names (`ddl_string_type_name`, keyed on `SQL_DBMS_NAME` "DB2/400"): a CLOB column can be neither array-bound for writing nor bound at all for reading, so every row costs a round trip — 3,000 rows went in at **8 rows/s** and came back at **8 rows/s** against 926-1,070 and 1,636-8,052 as `VARCHAR(8000)`. The width is measured, not chosen: the widest `VARCHAR` the server reports (32,739) is refused in a four-column table (`SQL0101`, Db2 for i's row is at most 32,766 bytes) and describes too wide to bind, which puts the read back on `SQLGetData` — 120 rows/s at `VARCHAR(16000)`, 62 at `VARCHAR(32700)`. Server side: `CommitMode=0` (`*NONE`) is required, because a table created with plain `CREATE TABLE` in a user library is not journaled and cannot be changed under commitment control (`SQL7008 … not valid for operation`); `Naming=0` with `DefaultLibraries=<user>1`; `SSL=1` (host-server ports 9470-9479) verified as well as plain. The entry's `s` column is `VARCHAR(50) CCSID 1208` because a plain `VARCHAR` takes the job CCSID — 273, single-byte EBCDIC, on this host — and loses `héllo 🚀` to substitution characters; `CCSID 1208` is UTF-8 and round-trips it. Unquoted identifiers fold to upper case, `SQL_MAX_IDENTIFIER_LEN` is 18, and `INTEGER`, `DOUBLE`, `VARBINARY(n)`, `DATE`, `TIMESTAMP(6)`, `DECIMAL(10,3)` and `BOOLEAN` are all native 7.5 types needing no tolerance flag. First connect needs a password change PUB400 only offers on a 5250 screen: `ssh -p 2222` refuses an expired profile outright and the client's own `cwbCO_ChangePassword()` is refused by this host (`CWBSY1008 … rc=400`); ingest 926 rows/s, fetch 8.1k rows/s, ~110 ms network round trip |
| ClickHouse 26 | clickhouse-odbc 1.5 | PASS | a NULL parameter must be bound with a NULL value pointer — with a value buffer bound the driver ignores `SQL_NULL_DATA` and sends the parameter empty, which the server rejects for `Int32`/`Float64`/`Date`/`Decimal`/`Bool` and silently stores as `''` in `String` and as the epoch in `DateTime64` (`SQL_DESCRIBE_PARAMETER` is `N`; `SQLDescribeParam` answers `SQL_UNKNOWN_TYPE`); no affected-row counts on 1.5.5 (`SQLRowCount` answers 0 for every write; fixed on master by clickhouse-odbc#585, merged 2026-09-15, which answers −1 — the ODBC "unknown" — from the next release); `Nullable()` DDL wrapper on ingest; parameter arrays are the driver's own protocol — `SQLExecute` sends set 0 and each `SQLMoreResults` the next set (clickhouse-odbc#324) — and on 1.5.5 the first `SQLMoreResults` sends set 1 but answers `SQL_NO_DATA`, after which nothing advances: a 5-set array lands 2 rows under `SQL_SUCCESS`/`SQL_NO_DATA` (clickhouse-odbc#582), and a spec-conforming single `SQLExecute` lands 1; one HTTP request per execute — ~16 rows/s one row at a time, ~1k rows/s through the K-row `INSERT` ingest uses, with K bounded by the server's 1,000-field HTTP form limit |
| ClickHouse 26 | clickhouse-odbc 1.5 | PASS | a NULL parameter must be bound with a NULL value pointer on 1.5.5 — with a value buffer bound the driver ignores `SQL_NULL_DATA` and sends the parameter empty, which the server rejects for `Int32`/`Float64`/`Date`/`Decimal`/`Bool` and silently stores as `''` in `String` and as the epoch in `DateTime64`; fixed on master by our [clickhouse-odbc#586](https://github.com/ClickHouse/clickhouse-odbc/pull/586) (merged 2026-09-24), still present in 1.5.5.20260810 as re-checked on 2026-09-24, so the flag stays until a release carries it (`SQL_DESCRIBE_PARAMETER` is `N`; `SQLDescribeParam` answers `SQL_UNKNOWN_TYPE`); no affected-row counts on 1.5.5 (`SQLRowCount` answers 0 for every write; fixed on master by clickhouse-odbc#585, merged 2026-09-15, which answers −1 — the ODBC "unknown" — from the next release); `Nullable()` DDL wrapper on ingest; parameter arrays are the driver's own protocol — `SQLExecute` sends set 0 and each `SQLMoreResults` the next set (clickhouse-odbc#324) — and on 1.5.5 the first `SQLMoreResults` sends set 1 but answers `SQL_NO_DATA`, after which nothing advances: a 5-set array lands 2 rows under `SQL_SUCCESS`/`SQL_NO_DATA` (clickhouse-odbc#582), and a spec-conforming single `SQLExecute` lands 1; one HTTP request per execute — ~16 rows/s one row at a time, ~1k rows/s through the K-row `INSERT` ingest uses, with K bounded by the server's 1,000-field HTTP form limit |
| CockroachDB 26 | psqlodbc 16 (PG wire) | PASS | no quirks; declare a PRIMARY KEY or the synthesised hidden `rowid` shows up in `GetObjects`; `version()` is CockroachDB's own, so the PostgreSQL array-ingest form stays off and ingest uses the multi-row `INSERT` (verified); **no `ctid`** (`42703`), so partitioned reads use the key-range split — measured 5.1x at N=8 and 6.5x at N=16 over a single connection on 1 M rows. **`adbc_driver_postgresql` cannot read this server at all** (it needs binary `COPY TO`, which CockroachDB does not implement), so there is no native ADBC alternative to compare against |
| Percona Server 8.4 | MySQL Connector/ODBC 9.4 (MySQL wire) | PASS | drop-in MySQL fork: the `mysql` entry applies unchanged, no quirks; ingest 21.1k rows/s, fetch 1.18M rows/s |
| YugabyteDB 2026.1 (YSQL) | psqlodbc 16 (PG wire) | PASS | no quirks; YSQL is PostgreSQL 15, and its internal row id is a system column so `GetObjects` is unaffected; `version()` carries `-YB-`, so the PostgreSQL array-ingest form stays off (verified); **no `ctid`** (`0A000`), and its default `PRIMARY KEY (id)` is hash-partitioned, so partitioned reads split on `yb_hash_code()` — 2.6x over a single connection, and 2.2x better than a key range, which YugabyteDB would run as a `Seq Scan` with a `Storage Filter`. Against the native driver it reaches 1.18–1.22x at 4 M rows, i.e. on the 1.2x bar rather than over it, and 0.94x at 1 M: the server is the bottleneck there, not psqlodbc |
Expand Down
2 changes: 1 addition & 1 deletion docs/UPSTREAM.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ before it is filed.
| 2026-08-28 | Firebird ODBC (OdbcFb) | [FirebirdSQL/firebird-odbc-driver#301](https://github.com/FirebirdSQL/firebird-odbc-driver/issues/301) | `SQLPrepare` and `SQLExecDirect` discard `SQL_ATTR_ROWS_FETCHED_PTR` and `SQL_ATTR_ROW_STATUS_PTR` set before them, so a block cursor never learns how many rows a fetch returned; set after them they work. Reproduces on 3.0.1 and 3.5.0-rc1. Confirmed on Windows by F.D. Castel 2026-09-06 (cause: the implementation row descriptor was rebuilt on every prepare and cleared its `ROWS_PROCESSED_PTR` / `ARRAY_STATUS_PTR`). | **fix PR [#303](https://github.com/FirebirdSQL/firebird-odbc-driver/pull/303)** (two lines plus a regression test on the report's query), verified on Linux/unixODBC/Firebird 5.0.4 with its CI build on 2026-09-07: correct counters in all three attribute orderings |
| 2026-08-28 | taos-odbc (TDengine) | [taosdata/taos-connector-odbc#63](https://github.com/taosdata/taos-connector-odbc/issues/63) | Four "not implemented yet" gaps, each with a plain-ODBC program: `SQLBindCol` to `SQL_C_TYPE_TIMESTAMP` (while `SQLGetData` converts), `SQL_TYPE_TIMESTAMP` parameters, `BOOL` parameters from any C type but `SQL_C_SBIGINT`-as-`SQL_TINYINT`, and any `DECIMAL` column failing the whole `SELECT`. taos-odbc from source at 3.3.6.13. | open |
| 2026-08-28 | Apache Doris | [apache/doris#67301](https://github.com/apache/doris/issues/67301) | Server-side prepared `INSERT` refuses a string parameter sent with MySQL type `BLOB` — what MySQL Connector/ODBC sends for every `SQL_C_WCHAR` parameter — so a Unicode ODBC client cannot insert through prepared statements: `NullPointerException` in the FE on 2.1.0 (stack trace attached), `AnalysisException: Unsupported MySQL type: BLOB` on 4.1.3. Filed with the program and both versions' output. | fix PR open — [apache/doris#67306](https://github.com/apache/doris/pull/67306) by a Doris contributor, "closes #67301"; conflicts resolved at a maintainer's request on 2026-09-11 |
| 2026-08-29 | clickhouse-odbc | [ClickHouse/clickhouse-odbc#582](https://github.com/ClickHouse/clickhouse-odbc/issues/582) | Two parameter-binding defects on 1.5.5.20260810, each with a standalone C program: `SQL_NULL_DATA` is ignored whenever a value buffer is bound and an empty string is sent instead (silently `''` into `String`, the epoch into `DateTime64`, a server parse error for numeric, date, decimal and bool; `SQL_DEFAULT_PARAM` the same; a NULL value pointer works); and parameter arrays — the driver's own `SQLExecute` + `SQLMoreResults`-per-set protocol from #324 — stop after set 1 because `SQLMoreResults` executes it and answers `SQL_NO_DATA`, so a 5-set array lands 2 rows with no diagnostic. | open — the `SQL_NULL_DATA` half **sent upstream as a fix, [ClickHouse/clickhouse-odbc#586](https://github.com/ClickHouse/clickhouse-odbc/pull/586)** (ours, opened 2026-09-18): a helper that treats a parameter as NULL when the value pointer is null or the indicator is `SQL_NULL_DATA`, used at both sites in `driver/statement.cpp`, with two unit and two integration tests that fail on master; the parameter-array half is a separate mechanism (`SQLMoreResults` reports result sets, not parameter sets) and stays open for its own PR |
| 2026-08-29 | clickhouse-odbc | [ClickHouse/clickhouse-odbc#582](https://github.com/ClickHouse/clickhouse-odbc/issues/582) | Two parameter-binding defects on 1.5.5.20260810, each with a standalone C program: `SQL_NULL_DATA` is ignored whenever a value buffer is bound and an empty string is sent instead (silently `''` into `String`, the epoch into `DateTime64`, a server parse error for numeric, date, decimal and bool; `SQL_DEFAULT_PARAM` the same; a NULL value pointer works); and parameter arrays — the driver's own `SQLExecute` + `SQLMoreResults`-per-set protocol from #324 — stop after set 1 because `SQLMoreResults` executes it and answers `SQL_NO_DATA`, so a 5-set array lands 2 rows with no diagnostic. | **half fixed** — the `SQL_NULL_DATA` half is fixed by our **fix PR [ClickHouse/clickhouse-odbc#586](https://github.com/ClickHouse/clickhouse-odbc/pull/586)**, opened 2026-09-18 and **merged 2026-09-24** by the maintainer (slabko), merge commit `eb0fdb2654`: a helper that treats a parameter as NULL when the value pointer is null or the indicator is `SQL_NULL_DATA`, used at both sites in `driver/statement.cpp`, with two unit and two integration tests that failed on master. Not in a release: the reproducer was re-run against 1.5.5.20260810 on 2026-09-24 against ClickHouse 26.7.5.10 and still shows all five problems, so the shipped-in line waits for the next tag. The parameter-array half is a separate mechanism (`SQLMoreResults` reports result sets, not parameter sets) and stays open for its own PR, so the issue itself remains open upstream |
| 2026-08-29 | clickhouse-odbc | [ClickHouse/clickhouse-odbc#335 (comment)](https://github.com/ClickHouse/clickhouse-odbc/issues/335#issuecomment-5464127498) | Affected-row count, open since 2021: still 0 (not −1) for every write on 1.5.5 against 26.7.5.10; noted that the server already sends `written_rows` in `X-ClickHouse-Summary` on every HTTP response by default, which the driver could surface. | **fixed 2026-09-15** — [ClickHouse/clickhouse-odbc#585](https://github.com/ClickHouse/clickhouse-odbc/pull/585) (`SQLRowCount` → −1, the ODBC "unknown", where 1.5.5 answered 0) was opened and merged the same day, with a second maintainer's approval, and #335 closed as completed after five years. Verified on the PR's CI build and on the merged master build against ClickHouse 26.7.5.10: −1 after a literal `INSERT` and after a `DELETE`, the full ClickHouse compatibility entry unchanged. Ships in the first release after v1.5.5.20260810. The maintainer's reasoning, 2026-09-15: 0 was wrong and −1 is the honest value; `written_rows` from `X-ClickHouse-Summary` is not usable as an exact count because it is untrusted while `async_insert` is on (ClickHouse/ClickHouse#57768) and, as he then noted and we confirmed, it also counts rows written to materialized views (3 rows through `INSERT … SELECT` with two views reported 7), so −1 is the final answer |
| 2026-08-29 | OpenLink Virtuoso | [openlink/virtuoso-opensource#1470](https://github.com/openlink/virtuoso-opensource/issues/1470) | `virtodbc.so` (Linux, unixODBC) implements `SQL_C_WCHAR` as 4-byte `wchar_t` on both the parameter and the fetch side, not the 2-byte `SQLWCHAR` the driver manager defines: a UTF-16 `hello` stores as `??`, `héllo 🚀` as `0`, wide reads widen each UTF-8 byte — all `SQL_SUCCESS`. The Linux counterpart of #1469. | open |
| 2026-08-29 | OpenLink Virtuoso | [openlink/virtuoso-opensource#1471](https://github.com/openlink/virtuoso-opensource/issues/1471) | Any `Charset=` value but the exact literal `UTF-8` aborts the process inside `SQLDriverConnect` (`GPF: Dkbox.c:638 Double free`), including every charset Virtuoso itself lists; `Charset=UTF-8` connects but returns wide columns doubly encoded with the original byte length in the indicator. | open — the abort **sent upstream as a fix, [openlink/virtuoso-opensource#1477](https://github.com/openlink/virtuoso-opensource/pull/1477)** (ours, opened 2026-09-18): the cause is a double free, not a compare — `con_charset_name` aliases the parsed option buffer and two owners release it; the fix gives it a box of its own (`libsrc/Wi/CLIsql3.c`, +8/−1), after which every charset connects and an unknown one gets the intended `CL035` warning. The doubly encoded wide columns under `Charset=UTF-8` stay open |
Expand Down
Loading
Loading