Kinetica's ODBC driver executes parameter N with the value bound at N+1
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_LONG→SQL_INTEGER holding 42, parameter 2 as SQL_C_CHAR→SQL_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.