Architecture overview
The driver is six layers, each with a single responsibility, each testable in isolation.
┌──────────────────────────────────────────────────┐│ Connection / Pool │ ← public API├──────────────────────────────────────────────────┤│ Cursor │ ← PEP 249 surface├──────────────────────────────────────────────────┤│ ResultSet │ ← row iteration, prefetch├──────────────────────────────────────────────────┤│ Codec / Per-column │ ← decode SQL types → Python│ readers │├──────────────────────────────────────────────────┤│ Protocol / PDU framing │ ← SQLI PDUs over the wire├──────────────────────────────────────────────────┤│ IfxSocket (buffered) │ ← raw bytes, recv() management└──────────────────────────────────────────────────┘IfxSocket
Section titled “IfxSocket”The lowest layer. Wraps socket.socket with a connection-scoped read buffer (Phase 39). One recv(64K) per ~64 KB of incoming data; parsers read into the buffer via struct.unpack_from(buf, offset) rather than slicing copies.
Everything above this layer is bytes and bytearray arithmetic — no syscalls except through IfxSocket.read_exact(n) and IfxSocket.write_all(buf).
See The buffered reader → for why the buffer lives here and not on the parser.
Protocol / PDU framing
Section titled “Protocol / PDU framing”_protocol.py reads and writes SQLI PDUs. Each PDU is parsed into a typed Python representation: SqInfo, SqVersion, SqTuple, SqId, etc. The framing layer doesn’t know what the PDUs mean — only how to read and write the byte shapes.
The PDU types and their fields were reverse-engineered from three sources:
- The decompiled IBM JDBC driver (
com.informix.jdbc.IfxConnectionand theIfxProtocolclass hierarchy). - Annotated
socatcaptures of real client/server exchanges (docs/CAPTURES/). - Differential testing against IfxPy on identical data.
Codec / Per-column readers
Section titled “Codec / Per-column readers”converters.py and _resultset.py together. The codec layer maps Informix SQL types to Python types — see SQL ↔ Python types for the full table.
Phase 37 introduced per-column reader strategy: at PREPARE time, the driver builds a list of decoder functions (one per column) keyed by SQL type. At fetch time, decoding a row is [reader(payload) for reader in column_readers] — no per-column dispatch overhead.
Phase 38 went further with exec()-based codegen: for the hottest tables, the driver generates a flat decoder function with all readers inlined and dispatch decisions baked in. The generated function is the equivalent of unrolling the per-column dispatch into straight-line code.
ResultSet
Section titled “ResultSet”_resultset.py. Holds the cursor’s column descriptors, the pre-baked decoder list, and the in-flight prefetch state. Manages fetch_one, fetch_many, fetch_all semantics.
Most cursor calls land here: when you do cur.fetchone(), the cursor delegates to its ResultSet which reads the next SQ_TUPLE PDU, runs it through the per-column decoders, and returns a tuple.
Cursor
Section titled “Cursor”cursors.py. The PEP 249 cursor surface. execute(), executemany(), fetchone(), fetchmany(), fetchall(), description, rowcount, scrollable cursor support.
This is where parameter binding lives: a SQL statement with ? placeholders gets prepared via SQ_PREPARE, the driver introspects the parameter shape via SQ_DESCRIBE, and bound values get encoded according to the parameter types.
Connection / Pool
Section titled “Connection / Pool”connections.py and pool.py. The connection owns the IfxSocket, manages transaction state, and is the parent of all cursors. The pool is a wrapper around N connections with the usual acquire/release/timeout semantics.
aio.py mirrors all of the above with async def versions, implemented via thread-pool wrapping (see Async strategy →).
Why this layering
Section titled “Why this layering”Each boundary is testable in isolation:
IfxSockettests can use asocket.socketpair()and assert on byte streams.- Protocol tests parse known-good captures from
docs/CAPTURES/and verify the typed PDUs come out correctly. - Codec tests pass synthetic byte payloads to per-column readers and assert the Python output.
- ResultSet tests can use a fake protocol that emits canned PDU sequences.
- Cursor tests use a fake ResultSet.
When a regression appears, the layered structure narrows the search: a wire-format test failing means it’s the protocol layer; a row-tuple test failing with a corrupt-bytes input means the codec; a fetchall test failing means the ResultSet’s iteration logic. The 300+ test suite leans heavily on this isolation.