Skip to content

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
└──────────────────────────────────────────────────┘

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.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:

  1. The decompiled IBM JDBC driver (com.informix.jdbc.IfxConnection and the IfxProtocol class hierarchy).
  2. Annotated socat captures of real client/server exchanges (docs/CAPTURES/).
  3. Differential testing against IfxPy on identical data.

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.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.

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.

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 →).

Each boundary is testable in isolation:

  • IfxSocket tests can use a socket.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.