cur.execute("INSERT INTO t VALUES (?, ?, ?)", (42, "hello", 3.14))
cur.execute("INSERT INTO t VALUES (:1, :2)", (99, "world"))
cur.execute("UPDATE t SET name = ? WHERE id = ?", ("new", 2))
cur.execute("DELETE FROM t WHERE id = ?", (5,))
# all work end-to-end against a real Informix server
Two breakthroughs decoded from JDBC:
1. SQ_BIND PDU shape (chained with SQ_EXECUTE in one PDU, no separate
round trip):
[short SQ_ID=4][int SQ_BIND=5][short numparams]
for each param:
[short type][short indicator][short prec_or_encLen]
writePadded(rawbytes)
[short SQ_EXECUTE=7][short SQ_EOT]
2. Strings are sent as CHAR (type=0) not VARCHAR (type=13). The server
handles conversion to the actual column type via internal CIDESCRIBE
— we don't need to do it explicitly.
Per-type encoding (Phase 4 MVP):
int (32-bit) → IDS INT (type=2), prec=0x0a00 (packed width=10/scale=0),
4-byte BE
int (64-bit) → IDS BIGINT (type=52), prec=0x1300, 8-byte BE
str → IDS CHAR (type=0), prec=0, [short len][bytes][pad]
float → IDS FLOAT (type=3), prec=0, 8-byte IEEE 754
bool → IDS BOOL (type=45), prec=0, 1 byte
None → indicator=-1, no data
The integer "precision" field is PACKED — initially looked like a bug
(why would precision be 2560?) until I realized 0x0a00 = (10 << 8) | 0
= packed display-width and scale. Captured this surprise in
DECISION_LOG.md.
Critical fix to execute-path branching: parameterized INSERT also
returns nfields > 0 (server describes the would-be inserted row).
Switched from "branch on nfields" to "branch on SQL keyword" — JDBC
does the same via its IfxStatement / IfxPreparedStatement subclassing.
Numeric paramstyle support: cur.execute("... :1 ...", (val,)) works
by rewriting :N → ? before sending PREPARE. Trivial regex (doesn't
escape strings/comments — Phase 5 can add a proper SQL tokenizer).
Module changes:
src/informix_db/converters.py:
+ encode_param() dispatcher
+ _encode_int / _encode_bigint / _encode_str / _encode_float / _encode_bool
src/informix_db/cursors.py:
+ _build_bind_execute_pdu() — chains SQ_BIND + SQ_EXECUTE in one PDU
+ _execute_dml_with_params() — sends bind PDU, drains, releases
+ execute() now accepts parameters; rewrites :N → ?; branches by
SQL keyword (SELECT vs DML)
+ _NUMERIC_PLACEHOLDER_RE for paramstyle="numeric" support
Tests: 40 unit + 32 integration (8 new parameter tests + 1 updated
smoke) = 72 total, all green, ruff clean. New tests cover:
- INSERT with ? params
- INSERT with :N params
- INT + FLOAT + str round trip via INSERT then SELECT
- UPDATE with params in SET and WHERE
- DELETE with parameter in WHERE
- Unsupported param type (bytes) raises NotImplementedError
- Parameterized SELECT raises NotSupportedError (Phase 4.x)
- Dict/named params raise NotSupportedError
Known gaps (Phase 4.x / Phase 5):
- Parameterized SELECT (needs SQ_BIND before CURNAME+NFETCH)
- NULL row decoding for VARCHAR (currently surfaces empty string)
- Proper SQL tokenizer (so :N inside string literals is preserved)
- bytes/datetime/Decimal parameter types
informix-db
Pure-Python driver for IBM Informix IDS, speaking the SQLI wire protocol over raw sockets. No IBM Client SDK. No JVM. No native libraries.
Status
🟢 Phase 1 complete. connect() / close() work end-to-end against a real Informix server. Cursor / execute / fetch land in Phase 2.
To our knowledge this is the first pure-socket Informix driver in any language — every other Informix driver (IfxPy, the legacy informixdb, ODBC bridges, Perl DBD::Informix) wraps either IBM's CSDK or the JDBC JAR.
Quick start
import informix_db
with informix_db.connect(
host="127.0.0.1", port=9088,
user="informix", password="in4mix",
database="sysmaster", server="informix",
) as conn:
# cursor() / execute() / fetchone() arrive in Phase 2
pass
Test against the official Informix dev container
docker compose -f tests/docker-compose.yml up -d # IBM Developer Edition, pinned by digest
uv sync --extra dev
uv run pytest # 34 unit tests (no Docker needed)
uv run pytest -m integration # 6 integration tests (needs the container)
Phase 0 artifacts (still useful — they ARE the public reference)
docs/PROTOCOL_NOTES.md— byte-level wire-format reference, derived from packet captures + JDBC decompilation, validated against a real serverdocs/JDBC_NOTES.md— index into the decompiled IBM JDBC driver's wire-protocol classesdocs/DECISION_LOG.md— running rationale for protocol / auth / type decisionsdocs/CAPTURES/— socat hex-dump captures of three reference scenarios (connect, SELECT, full DML cycle)tests/reference/RefClient.java— re-runnable JDBC ground-truth client for capturing fresh traces
License
MIT.
Description
Pure-Python driver for IBM Informix IDS — speaks the SQLI wire protocol over a raw socket. No CSDK, no JVM, no native libraries.
https://informix-db.warehack.ing
Languages
Python
85.6%
MDX
8.1%
CSS
2.1%
Java
1.7%
Astro
1%
Other
1.4%