Notes · 2026-09-03

Kinetica's ODBC driver executes parameter N with the value bound at N+1

Kinetica ODBC 7.1.9 accepts every SQLBindParameter and then shifts the values one position, sending the last as NULL under SQL_SUCCESS with an empty diagnostic queue.

Kinetica's ODBC driver (libKineticaODBC.so 7.1.9.16, a Simba Engine SDK build against Kinetica 7.1.9) does not refuse a bind the way a driver without parameter support does. It says yes to every one and then reads the wrong buffer. Bind three SQL_C_CHAR parameters '11', '22', '33' into a three-column table through unixODBC directly, with no pyodbc in the picture, and SQLExecute returns SQL_SUCCESS while the row lands as:

a = 22   b = 33   c = NULL

Parameter N is executed with the value bound at parameter N+1, and the last parameter is sent as NULL. The consequences at other widths are the same bug:

parameters what happens
1 the value is dropped and NULL is stored, under SQL_SUCCESS with an empty diagnostic queue
3 values shifted one column left, last column NULL — silently
8 HY000 (1040) Driver Error: type: %d — an unformatted printf template, reached by walking off the end of the parameter array

Mixing C types produces a message that names the shift. Parameter 1 as SQL_C_LONGSQL_INTEGER holding 42, parameter 2 as SQL_C_CHARSQL_VARCHAR holding "hello", into (i INTEGER, s VARCHAR(20)):

SQLDescribeParam(1) rc 0 type=12 size=1024      <- SQL_VARCHAR, for an INTEGER column
SQLExecute rc -1
  HY000 1040 [Kinetica][KineticaODBC] (1040) Driver Error: stod; Setting column 'i' with value: 'hello'

Parameter 2's text arriving at column 1, and stod because the driver converts every parameter from text whatever ValueType said. SQLDescribeParam is wrong in its own right — it reports SQL_VARCHAR(1024) for every parameter of every statement whatever the target column is. A single string parameter is worse still: SELECT ? bound to "x" segfaults the process.

The workaround is blunt: do not bind parameters against this driver. Kinetica itself is not the problem — SQLExecDirect of literal SQL is exact, and emoji, BYTES, DATE and BOOLEAN all round-trip through it — so a read-only client that builds its tables with literal SQL runs the entire read side unchanged.

DECIMAL(18,4), described three ways, none of them right

Kinetica has exactly one decimal type. DECIMAL(10,3), NUMERIC(10,3) and DECIMAL(18,4) in DDL are all stored as the same thing, and SHOW CREATE TABLE says which: "n" DECIMAL (18, 4). The driver then describes that column three mutually inconsistent ways:

call precision scale
SQLDescribeCol 38 0
SQLColumns 18 18
SQLGetTypeInfo (DECIMAL) 38

None is the column's. A reader that takes its precision and scale from SQLDescribeCol believes scale 0 and turns the 12.3450 the driver hands over into a 38-digit integer holding 12 — every fractional digit gone, silently, under SQL_SUCCESS. Both halves of the real pair are constants of the server, not of the column, which is the only reason substituting 18 and 4 outright can be correct.

WHERE 1=0 cannot describe a table with a BYTES column

The usual way to read a table's columns without reading its rows is SELECT * FROM t WHERE 1=0. Against Kinetica that fails for any table holding binary:

SELECT * FROM "ki_home"."adbc_t" WHERE 1=0
  HY000 (1050) [GPUdb]executeSql: Invalid attribute: BYTES(null) for table: SYSTEM.ITER
    reason: Invalid expression('BYTES(null)'), Unknown function: BYTES

This one is the server. Kinetica's planner constant-folds a provably false predicate and answers the query from SYSTEM.ITER, an empty pseudo-table, which cannot project a BYTES column. Every spelling of false folds the same way, and dropping the binary column makes it pass. LIMIT 0 is not folded that way and describes the real table, so use it for column discovery here.

And do not trust the version banner

SELECT VERSION() answers PostgreSQL 9.5 (Debian 9.0-1.pgdg90+1) ... — a hard-coded banner from the SQL engine's PostgreSQL-derived catalog layer, on a server that is not PostgreSQL and has none of its wire protocol. SELECT CURRENT_SCHEMA gives the real answer, ki_home.

On Windows the same driver mangles Unicode statement text

KineticaODBC.dll 7.1.9.16 exports the W entry points and then transcodes W statement text through the ANSI code page inside itself: the literal 'héllo 🚀' reaches the server as cp1252 bytes and every later read of that row fails HY000 (50311) "Error converting invalid input with source encoding UTF-8 using ICU", identically through pyodbc. Its narrow statement path hands UTF-8 through byte for byte and its wide fetch is correct, so sending statement text narrow while still fetching wide is what passes. A Windows host also needs enable_worker_http_servers = false in the server's gpudb.conf, with a restart, or the driver's multi-head inserter dials the container-internal worker URLs the server advertises and every INSERT times out with HY000 (1050).

Related: Most Windows-only ODBC failures are one thing wearing different clothes

Status

Found on 2026-09-03, the parameter shift reproduced through unixODBC with no pyodbc in the picture, and recorded in docs/UPSTREAM.md under "documented here, not yet reported"; no vendor issue is filed yet, so there is no fix version to name.

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/#kinetica, and the finding is on file with its reproduction in docs/UPSTREAM.md of the repository.