Notes · 2026-09-03

A 35-character Driver=, a compiled-in path, and a CLOB at eight rows a second

IBM i Access ODBC 1.1.0.29 against Db2 for i 7.5 — the Driver= value must stay under 36 characters, the libraries need a real /opt/ibm/iaccess, and a CLOB column costs a round trip per row.

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.