Five things OpenLink Virtuoso's ODBC driver does silently and wrongly
Virtuoso is ODBC-native: port 1111 carries its own binary wire protocol and virtodbc.so
speaks it directly, so there is no separate client library between you and these defects.
Three of the five below return SQL_SUCCESS with no diagnostic, which is what makes them
worth writing down: nothing tells the application that the value it stored is not the value
it sent.
1. SQL_C_WCHAR is a four-byte wchar_t, not the driver manager's SQLWCHAR
virtodbc.so is a pure ANSI driver — it exports no …W entry points at all — and implements
SQL_C_WCHAR as four-byte wchar_t rather than the two-byte SQLWCHAR unixODBC's headers
define, on both the parameter and the fetch side. A UTF-16 buffer is therefore consumed four
bytes at a time. hello with indicator 10 stores as ??; a one-character string stores as
the empty string; héllo 🚀 stores as the single character 0. Every one of them
SQL_SUCCESS, no diagnostic. On the read side a two-byte SQLWCHAR fetch widens each UTF-8
byte to a four-byte unit (68 00 00 00 c3 00 00 00 …). Hand the same driver a wchar_t
array and the text round-trips exactly, emoji included — which is the proof of the width.
The narrow path is exact for UTF-8, so staying on it is the workaround. Reported on 2026-08-29 as openlink/virtuoso-opensource#1470, open. Its macOS counterpart was filed four days earlier as openlink/virtuoso-opensource#1469: the Homebrew 7.2.17 build is iODBC-width and nothing says so.
Related: The process dies with SIGABRT on the first SQL error, and it is the manager
A second defect sits next to this one: SQL_C_CHAR bound against SQL_WCHAR/SQL_WVARCHAR
with SQL_NTS stores the correct text followed by 100–150 characters of adjacent process
memory, different on every run, again with no diagnostic. An explicit byte length in place of
SQL_NTS stores the value correctly.
2. Any Charset= but the exact literal UTF-8 aborts the process
GPF: Dkbox.c:638 Double free
Client-side, inside SQLDriverConnect. UTF8, lowercase utf-8, UTF-16, and every charset
Virtuoso itself lists in DB.DBA.SYS_CHARSETS — ISO-8859-1, WINDOWS-1252, KOI8-R — all
abort. The one value that connects is not safe either: over a Charset=UTF-8 connection wide
NVARCHAR columns read back doubly encoded, each stored byte recoded as a single-byte
character, so héllo 🚀 comes back as héllo ð… while SQLGetData returns SQL_SUCCESS
and reports the original byte length in StrLen_or_Ind — an application that honours the
indicator gets the mojibake cut mid-sequence. The workaround is to put no Charset= in the
connection string at all. openlink/virtuoso-opensource#1471,
open.
3. SQL_C_SBIGINT parameters store 0
Whatever the value — 42 as much as 1234567890123 — and whether it is declared SQL_BIGINT,
SQL_NUMERIC or SQL_DECIMAL. The driver's conversion table has no SQL_C_SBIGINT. The read
side matches: SQLGetData and SQLBindCol with SQL_C_SBIGINT return SQL_SUCCESS with an
indicator of 0 and leave the buffer untouched, so a column declared BIGINT reads back as
zeros.
Sending the digits as SQL_C_CHAR text declared SQL_NUMERIC or SQL_DECIMAL is exact,
INT64_MIN and INT64_MAX included. SQL_NUMERIC is what makes it exact, not the text: the
same digits declared SQL_BIGINT, SQL_VARCHAR or SQL_CHAR also store 0. On the read
side SQL_C_CHAR and SQL_C_DOUBLE deliver the value; SQL_C_SLONG truncates silently
(9223372036854775807 arrives as -1). A related one: a SQL_C_SLONG parameter is read as
eight bytes — a C long on LP64 — whatever BufferLength says, so an int32 bound out of a
packed array picks up the word after it (SR346: Integer out of range for column i).
openlink/virtuoso-opensource#1472, open.
4. Date parameter arrays are strided by ColumnSize
virtodbc.so accepts SQL_ATTR_PARAMSET_SIZE, executes every set, reports the right
affected-row count and marks every element SQL_PARAM_SUCCESS — but for the datetime C
structs it steps a column-wise array by SQLBindParameter's ColumnSize argument rather than
by sizeof(SQL_DATE_STRUCT), which the specification says is what governs. ColumnSize 0
gives a stride of zero, so row 0's date is inserted into every row. Other values read at the
wrong offset and past the end of the buffer: four dates bound with ColumnSize 10 over a
packed array come back as 2024-01-01, 0197-08-05 and two more that change from run to run,
and a timestamp array bound with ColumnSize 23 over 16-byte elements ends in
-17837-65534-65535. SQL_C_CHAR, SQL_C_DOUBLE and SQL_C_SLONG arrays walk correctly, so
the damage is invisible unless a date, time or timestamp column is in the batch. Laying the
elements out ColumnSize bytes apart, or binding row-wise, is correct in every case; turning
parameter arrays off is the blunter fix.
openlink/virtuoso-opensource#1473, open.
5. On Windows, SQLSetPos and a four-byte indicator stride
virtodbc.dll 7.2.17 advertises the SQL_GD_* extensions, but SQLSetPos(SQL_POSITION)
fails on a block cursor, and the bound-column indicator array is written at a four-byte
stride there. Recorded with its conditions, not yet filed upstream.
Related: A NULL column that reads back as the previous row's value
Dates
The macOS width was found on 2026-08-25 and the four Linux defects on 2026-08-29. The
Windows pair is recorded in bench/BENCHMARKS-windows.md.
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/#virtuoso, and the finding is on file with its reproduction in docs/UPSTREAM.md of the repository.