A 35-character Driver=, a compiled-in path, and a CLOB at eight rows a second
Db2 for i is the database built into IBM i, a different engine and a different wire from
Db2 proper, reached by IBM's own IBM i Access ODBC driver over the host-server ports.
Adding it to a compatibility matrix that runs one workload through 53 databases' ODBC
drivers turned up several things that will each cost an afternoon if nobody has written
them down. Found on 2026-09-03, against driver 1.1.0.29 (libcwbodbc.so, SQL_DRIVER_VER
07.01.029) and DB2/400 SQL 07.05.0015 on the free public IBM i at PUB400.COM.
Every diagnostic reads CWBNL0202 - cwbodmsg.dll
[HY000] CWBNL0202 - cwbodmsg.dllCWBNL0202 - cwbodmsg.dllCWBNL0202 - cwbodmsg.dll (30119)
Unlike every other driver in the matrix, this one will not run from where you unpack it.
The libraries resolve their message catalogues, EBCDIC conversion tables and preferences
file under a compiled-in /opt/ibm/iaccess, and with that directory missing every
diagnostic — including the one that would tell you why a connect failed — degrades to the
line above and the real error code is lost. Installing the .deb as root puts the tree
there. To stay root-free, give the process a mount namespace where the path exists;
bubblewrap does it unprivileged, and /opt has to be a tmpfs first because the real one
is not writable:
P=<where you unpacked it>/opt/ibm/iaccess
bwrap --dev-bind / / --tmpfs /opt --bind $P /opt/ibm/iaccess \
--ro-bind $P/lib64/libcwbodbc.so /opt/ibm/cwbodbc.so -- <command>
The tmpfs hides every other /opt subdirectory, so re-bind the ones you need in the
same run. LD_LIBRARY_PATH must also contain the driver's own lib64: libcwbodbc.so
links against libcwbcore.so with no RPATH, so ldd says not found and unixODBC
reports Can't open lib.
Key value in connection string too long. (30119)
The Driver= value may be at most 35 characters. Longer and the driver answers that
before it looks at anything else — measured exactly: a 35-character path connects, a
36-character one does not. Which is why the second bind above exists. The driver's own
installed path, /opt/ibm/iaccess/lib64/libcwbodbc.so, is 36 characters, one over its own
limit. A short symlink does as well, and a normal root install uses the registered name
(Driver={IBM i Access ODBC Driver}, 24 characters) instead of a path.
The type the driver names is the type you cannot use
SQLGetTypeInfo(SQL_LONGVARCHAR) names CLOB, and a CLOB column is this driver's worst
case at both ends: it cannot be array-bound for writing and has no declared width to bind
for reading, so every row costs a network round trip. 3,000 rows of
(INTEGER, DOUBLE, <string>, DATE) over a ~110 ms link went in at 8 rows/s and came
back at 8 rows/s as CLOB(1M). Spelled VARCHAR(8000) CCSID 1208 the same table
ingests at 926–1,070 rows/s and reads back at 1,636–8,052.
8,000 is measured, not chosen. The widest VARCHAR the server reports (32,739) is refused
in a four-column table (SQL0101; Db2 for i's row is at most 32,766 bytes), and a column
that wide describes too wide to bind, which puts the read back on SQLGetData row by row —
120 rows/s at VARCHAR(16000), 62 at VARCHAR(32700).
CCSID 1208 is not decoration either. A plain VARCHAR takes the job's CCSID, 273 on this
host — single-byte EBCDIC — and héllo 🚀 comes back with substitution characters. At
CCSID 1208 it round-trips byte for byte, in a bound parameter and in statement text alike.
Server-side settings that are not optional
CommitMode=0 (*NONE) is required. Under the driver's default commitment control the
first INSERT into a table you just created fails:
[HY000] (-7008) [IBM][System i Access ODBC Driver][DB2 for i5/OS]
SQL7008 - ADBC_T in <lib> not valid for operation.
Nothing created with plain CREATE TABLE in a user library on IBM i is journaled, and a
non-journaled table cannot be changed under commitment control. Also worth knowing before
you name anything: unquoted identifiers fold to upper case, and SQL_MAX_IDENTIFIER_LEN
is 18.
One thing stands between you and the first connect: the temporary password the host mails
arrives already expired, and ssh refuses an expired profile outright while the client's
own cwbCO_ChangePassword() is refused by this host
(CWBSY1008 - General security error occurred rc=400). What works is a 5250 session,
which walks straight to the Change Password screen.
macOS: SSL=1 and pyarrow in one process
On macOS the driver segfaults inside SQLDriverConnect with SSL=1 whenever pyarrow's
libraries are loaded in the same process. Bisected: bare Python connects, with
adbc_driver_manager connects, with the bridge library connects, with pyarrow loaded it
crashes on SSL=1 and connects on SSL=0, and pyodbc alone connects on SSL=1.
libcwbcore.dylib is two-level with weak definitions and pyarrow's libraries carry their
own OpenSSL statically, so weak-symbol coalescing across images is the likely mechanism;
the exact symbol is not identified. The Linux driver, where the matrix runs SSL=1, does
not show it. The macOS package also has no unprivileged install — it goes to
/Library/IBMiAccess and needs an administrator — and the 35-character Driver= limit
applies there too. The matrix entry runs SSL=0 on macOS.
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/#ibmi, and the finding is on file with its reproduction in docs/UPSTREAM.md of the repository.