The SAP HANA ODBC client decodes narrow statement text as Latin-1
If you insert 'héllo 🚀' through SAP's HANA ODBC driver as a plain narrow statement and
then ask the server how long the string is, you get 11:
sent 68 c3a9 6c6c6f 20 f09f9a80 "héllo 🚀" (8 characters)
stored 68 c383c2a9 6c6c6f 20 c3b0c29fc29ac280 LENGTH(s) = 11
i.e. the eleven Latin-1 characters those bytes spell
The driver is taking each byte of the UTF-8 the driver manager handed it as one Latin-1
character and re-encoding it. On Linux unixODBC passes a narrow char* through as the
bytes it is, so every other driver in this matrix reads UTF-8 statement text correctly;
this one does not, and it does not on macOS or Windows either. A bound SQL_C_CHAR
parameter is decoded the same way. Measured against SAP HANA client ODBC 2.29.25
(libodbcHDB.so, SQL_DRIVER_VER 02.29.0025) and HANA Express 2.00.088.
Why it hides
The corruption is self-consistent inside narrow statements. A narrow LIKE 'héllo%'
still matches a narrow INSERT, because both are mangled identically, so a narrow-only
application can run for years without noticing. It shows the moment a statement literal
has to match a value sent as a bound parameter: bound text travels as SQL_C_WCHAR and
arrives correct, so the literal and the parameter are two different strings on the server.
Nothing in configuration reaches it. CHAR_AS_UTF8=TRUE, CHAR_SET=UTF8 and
charset=UTF8 all store the same mangled bytes, and unknown connection keywords are
accepted silently rather than refused; LC_ALL=C, en_US.UTF-8 and C.UTF-8 are
identical to each other. The wide entry points are correct — SQLExecDirectW with the
same statement stores héllo 🚀 exactly — so the workaround is to send caller statement
text through SQLExecDirectW / SQLPrepareW on every platform, not only on Windows.
The type table comes back in alphabetical order
SQLGetTypeInfo returns all 33 types sorted by TYPE_NAME, with DATA_TYPE unsorted,
against ODBC's ordering contract. That matters to anything that generates DDL by taking
the first row for a type:
SQL_TYPE_TIMESTAMP SECONDDATE size=19 CREATE_PARAMS=None
SQL_TYPE_TIMESTAMP TIMESTAMP size=23 CREATE_PARAMS=None
SQL_TYPE_TIMESTAMP TIMESTAMP size=27 CREATE_PARAMS=None
SECONDDATE is HANA's whole-second timestamp (COLUMN_SIZE 19, MAXIMUM_SCALE 0), and
it sorts before TIMESTAMP, so a generated timestamp column silently drops every
fractional second. Nothing in the metadata separates the two: SECONDDATE carries no
CREATE_PARAMS to ask a scale of, and HANA's TIMESTAMP takes no precision argument
either — TIMESTAMP(6) is 42000, 257 sql syntax error: incorrect syntax near "(",
because it is always seven fractional digits. Naming TIMESTAMP outright is the only
route. The same ordering puts BINTEXT before NCLOB.
CLOB is what the type table offers for a long string, and HANA will not sort it
SQLGetTypeInfo(SQL_LONGVARCHAR) names CLOB, and a CLOB column cannot be ordered or
de-duplicated:
ORDER BY -> HY000 264 invalid datatype: "V" LOB type in ORDER BY clause
DISTINCT -> HY000 264 invalid datatype: LOB type in distinct select clause
so a table created from the driver's own type names cannot be queried on its own string
column. SQL_VARCHAR is VARCHAR with CREATE_PARAMS length, and since HANA 2.0
merged VARCHAR into NVARCHAR it is fully Unicode, emoji included — VARCHAR(5000) is
a usable column where CLOB is not. Worth knowing if you read the table yourself: the
first row for SQL_WVARCHAR is ALPHANUM, a HANA-specific 127-character type.
Two more things that cost a statement
There is no multi-row VALUES at all. INSERT INTO t VALUES (1,'a'),(2,'b') is 42000,
257 sql syntax error: incorrect syntax near ",", with literals as with parameters; the
form HANA has is INSERT ... SELECT ... FROM DUMMY UNION ALL SELECT ... FROM DUMMY.
Without a multi-row insert, bulk loading falls back to one execute per row — 7,675 rows/s
against 1,142,092 for the same 20,000 rows through ODBC parameter arrays.
And if the server is remote, leave DATABASENAME out of the connection string. With it
set, HANA answers by redirecting the client to the address it knows itself by, which for
a containerised server is the container-internal one:
08S01 -10709 ... rc=10060 {172.18.0.2:39041}
Connecting straight to the tenant SQL port works from anywhere the port is reachable.
Status
Found on 2026-09-03 while adding HANA Express to the matrix, and both the Latin-1 decode
and the type-table ordering were reproduced on 2026-09-04 without any of this project's
code in the stack — in C, through isql, and through pyodbc. They are recorded in
docs/UPSTREAM.md under "documented here, not yet reported"; no vendor issue is filed yet,
so there is no fix version to point at.
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/#hana, and the finding is on file with its reproduction in docs/UPSTREAM.md of the repository.