Any text read as a timestamp comes back as today's date, under SQL_SUCCESS
What you see
Read a character column as SQL_C_TYPE_TIMESTAMP or SQL_C_TYPE_DATE and you get today's
date at midnight, under SQL_SUCCESS, whatever the string held. It happens to a text column,
a numeric-looking one and a real timestamp alike. ODBC specifies 22018 for a value that
cannot be parsed; this driver reports no error at all, so the wrong value flows into the
application as if it were the stored one.
The cause is in convert.c: the conversion fills a SIMPLE_TIME from localtime and never
parses the input. A TIMESTAMP column takes the same route by a second path — the driver's
result-set type map (opensearch_parse_result.cpp) has an entry for date but none for
timestamp, a type name the SQL plugin grew after the driver's last release, and anything
unmapped falls back to SQL_WVARCHAR with the type name unsupported. Its other type
tables, the ones behind SQLColumns and SQLGetTypeInfo, do map timestamp, so the two
metadata paths disagree about the same column. The workaround is to take such a column as
text and parse it yourself, which is what the compatibility entry does. Reported on
2026-08-29 as opensearch-project/sql-odbc#101; open.
A failed connect never returns
The second one costs a process. Point the driver at a closed port, or at a plaintext cluster
with useSSL=1, or give it an unknown auth= value, and SQLDriverConnect through unixODBC
sits at 100 % CPU until the process is killed — responseTimeout= makes no difference. The
network part does finish: the refused connect() is diagnosed within about two seconds,
after the AWS SDK's probe of the EC2 metadata endpoint. What never ends is the driver manager
collecting the diagnostics.
unixODBC prefers a driver's SQLError/SQLErrorW over SQLGetDiagRecW when it collects a
failed connect's records, and calls it in a do … while (SQL_SUCCEEDED(ret)) loop —
SQLError has no record number, so a driver must clear each error as it hands it out. This
driver exports SQLErrorW and its OPENSEARCHAPI_ConnectError returns the connection's
error on every call without ever clearing it: an strace of the hung process is 31,948 log
writes in 20 seconds, all of them the same function entering. Called directly through
dlopen, the same driver answers SQLGetDiagRecW correctly — 08001, then SQL_NO_DATA
for record 2 — and SQLErrorW with the same 08001 on call 1, 2 and 3. psqlodbc, which this
code descends from, has the same non-clearing ConnectError but exports neither SQLError
nor SQLErrorW, which is why it never loops. Reported on 2026-08-29 as
opensearch-project/sql-odbc#102; open.
There is a related trap behind it. The driver's ANSI SQLDriverConnect cannot connect at
all: CC_connect() asks for the SQL_ASCII client encoding, which its own
m_supported_client_encodings does not list, unless SQLDriverConnectW has set the
unicode-driver flag first. It fails through a path that logs rather than sets a diagnostic,
so you get SQL_ERROR with an empty diagnostic queue — while pyodbc, which calls
SQLDriverConnectW, connects to the same server with the same string. Everything after the
connect works through the ANSI entry points, emoji included.
The macOS package is x86_64 only
opensearch-project/sql-odbc has exactly one release, 1.5.0.0, whose single asset holds one
macOS .pkg and two Windows .msi files. The macOS dylib is single-arch x86_64, so in an
arm64 process dlopen() answers incompatible architecture (have 'x86_64', need 'arm64e' or
'arm64e.v1' or 'arm64' or 'arm64'). It loads under Rosetta (arch -x86_64 python3), which
an Intel-only stack can use. It also links /usr/lib/libiodbc, so it is an iODBC-width
driver and cannot be relinked into a unixODBC stack whatever install_name_tool says.
The POSIX branch has never been compiled
There is no Linux build to download, and the tree's WITH_UNIXODBC branch does not compile
as it stands. Five things are in the way, three of them real bugs: -Werror on a 2023 code
base; a vestigial #include "linux/kconfig.h" — a kernel header, which is also why the
CMakeLists puts a linux-headers include directory on the path; RAPIDJSON_SRC set inside
an if(WIN32) although the vendored copy is right there in libraries/rapidjson/include;
and opensearch_semaphore.cpp, whose constructor member-initialiser list is empty on any
platform but Windows and macOS, leaving a bare : before the body, and whose try_lock_for
calls sem_timedwait(&m_semaphore & ts) where it means sem_timedwait(&m_semaphore, &ts).
The fifth compiles and then crashes. The POSIX branch does sem_init(&m_semaphore, 0,
capacity) where WIN32 passes initial to CreateSemaphore and __APPLE__ passes it to
dispatch_semaphore_create. The result queue constructs its pop semaphore with an initial
count of 0 and a capacity, so on Linux that semaphore starts full: pop() succeeds on an
empty queue, std::queue::pop() on an empty queue corrupts it, and the next clear()
deletes a garbage pointer — the first close() segfaults. Passing initial fixes it. With
those five fixed the driver works, and reads about 120k rows/s.
One last thing to plan around: it is read-only twice over. The SQL plugin's grammar is
SELECT, SHOW and DESCRIBE — no CREATE TABLE, no INSERT — and the driver answers
SQLBindParameter with OpenSearch does not support parameters. Data goes in over the REST
_bulk API, and a parameterised query runs with a literal.
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/#opensearch, and the finding is on file with its reproduction in docs/UPSTREAM.md of the repository.