Notes · 2026-08-25

Four conversions taos-odbc calls "not implemented yet", and one that aborts

taos-odbc at 3.3.6.13 has no TIMESTAMP_STRUCT conversion, one route into a BOOL parameter and no DECIMAL at all, and it aborts the whole process when a client asks for ODBC 3.80.

What you see

Bind a TDengine timestamp column and the whole result set fails:

#1 Column converstion to 'SQL_C_TYPE_TIMESTAMP[0x5d/93]' not implemented yet

SQLBindParameter answers the same way for a timestamp parameter. It is not a missing feature of the type — SQLGetData on the same column converts to TIMESTAMP_STRUCT perfectly well. taos-odbc looks a conversion up by the exact (C type, SQL type, TDengine type) triple and its table is sparse; the bound-column and bound-parameter routes are simply not in it.

Four such gaps, each with a plain-ODBC program, were filed on 2026-08-28 as taosdata/taos-connector-odbc#63, against the connector built from source at 3.3.6.13; open.

One connection keyword matters before any of this: without TIMESTAMP_AS_IS=1, taos-odbc's own setting, the driver describes every TIMESTAMP column as an SQL_WVARCHAR holding formatted text, so timestamps never arrive as timestamps at all.

The assert that takes the process with it

src/core/env.c, _env_set_odbc_version: given SQL_OV_ODBC3_80 it builds the 01S02 "substituted" diagnostic record as written — and then calls OA_NIY(0), which aborts the process. SQL_OV_ODBC3_80 is exactly what Rust's odbc-api sets, so a Rust program dies at environment setup, before it has issued a statement. Removing that one call makes the function return the SQL_SUCCESS_WITH_INFO its own code has already prepared; the other way round it is not to let odbc-api touch this driver at all. Through everything else the driver is fast — 933,052 rows/s from Rust on the 20,000-row fixture, against 403k rows/s through the compatibility run's own reader.

Every table starts with a TIMESTAMP, and that rules out generated DDL

CREATE TABLE t (i INT, ...)
-> First column must be timestamp    (0x80002641)

The key is range-checked as well as mandatory: non-NULL, distinct per row and inside the database's retention window. So feeding it integers does not help either — INSERT INTO t VALUES (1, ...) is Timestamp data out of range (0x8000060B), one microsecond after the epoch being far outside the default KEEP of 3650 days. Any generated CREATE TABLE that declares no timestamp column, and any positional INSERT that fills columns from the first one, is therefore impossible against TDengine; a bulk load has to go into a table whose first column is a timestamp, which the driver's real parameter binding handles.

Two more entry-level facts. Identifiers quote with a backtick, and "..." is a string literal — FROM "adbc_big" is syntax error near '"adbc_big"'. And an int64 payload whose values all fit in 32 bits gets bound as SQL_C_SLONG/SQL_INTEGER, which the driver's table maps only to a TDengine INT; values past 2³¹ go as SQL_C_SBIGINT/SQL_BIGINT and reach a BIGINT column.

On Windows it is the system code page

The Windows client package installs taos_odbc.dll as the TDengine driver. Two separate things go wrong there. First, the 3.4.2.5 client cannot speak the native protocol to a 3.3.6 server at all: TCP connects and the server drops the connection with read invalid packet in taosd's log. Connecting over websocket through taosadapter instead — a URL={ws://…} connection string against a published 6041 — reaches the server and passes every column but one.

That one is the NCHAR column, and it is the ANSI code page rather than anything TDengine-specific: taos_odbc takes the client character set from GetACP() and ignores CHARSET_FOR_COL_BIND, CHARSET_ENCODER_FOR_COL_BIND and a UCRT setlocale(".UTF-8") alike, so with a 1252 system code page reading héllo 🚀 fails

[iconv] Character set conversion for UTF-32LE to CP1252 failed

through SQL_C_CHAR and SQL_C_WCHAR both, pyodbc identical. Only a UTF-8 system code page changes that.

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

Finally, if you probe this driver with pyodbc, put it on the narrow path first (conn.setencoding(encoding="utf-8", ctype=pyodbc.SQL_CHAR)). pyodbc sends statement text as UTF-16 by default, and taos-odbc fails any statement carrying a non-ASCII character with conversion for 'UTF-8' to 'UTF-8' not found.

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