Composite UDTs (ROW=22, COLLECTION=23, SET=19, MULTISET=20, LIST=21)
now decode into typed wrapper objects (informix_db.RowValue,
informix_db.CollectionValue) that expose schema + raw payload bytes.
The wire format is the now-familiar [byte ind][int length][bytes]
pattern (same as UDTVAR(lvarchar) from Phase 10). The bytes are a
TEXTUAL representation of the value when selected without the
extended-binary opt-in JDBC uses:
ROW value: b"ROW('Alice',30 )"
SET value: b"SET{'red','green','blue'}"
LIST value: b"LIST{10 ,20 ,30 }"
JDBC's binary-with-schema format runs ~30x larger (1420 bytes for a
2-field ROW vs. our 24). We don't request it — the textual form is
what the server returns by default and is sufficient for type
recognition.
Phase 12 ships type recognition only. Full recursive parsing into
Python tuples/lists/sets is deferred to Phase 13 (would require a
SQL-literal lexer + recursive type-driven decoding). Production
workloads that need typed field access today can project via SQL:
cur.execute("SELECT id, r.name, r.age FROM tbl")
Tests: 8 integration tests in test_composite_types.py covering ROW
recognition, NULL, sub-field projection workaround, long values
(>255 bytes — verifies 4-byte length prefix), SET/MULTISET/LIST
recognition, and null collections.
Total: 64 unit + 134 integration = 198 tests.
Lesson reinforced: once one UDT-shaped type is implemented (UDTVAR
in Phase 10, smart-LOB in Phase 9), every subsequent UDT-shaped type
is mostly a copy of the existing decoder branch. The hard part is
payload semantics, not framing.
122 lines
3.0 KiB
Python
122 lines
3.0 KiB
Python
"""informix-db — pure-Python driver for IBM Informix IDS over the SQLI wire protocol.
|
|
|
|
PEP 249 (DB-API 2.0) module surface. Phase 1 implements the connection
|
|
lifecycle (open + login + close) only; cursor/execute/fetch land in Phase 2.
|
|
|
|
Quick start::
|
|
|
|
import informix_db
|
|
|
|
conn = informix_db.connect(
|
|
host="127.0.0.1", port=9088,
|
|
user="informix", password="in4mix",
|
|
database="sysmaster", server="informix",
|
|
)
|
|
conn.close()
|
|
|
|
For the full design, see ``docs/PROTOCOL_NOTES.md`` and
|
|
``docs/DECISION_LOG.md``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from importlib.metadata import PackageNotFoundError, version
|
|
|
|
from .connections import Connection
|
|
from .converters import (
|
|
BlobLocator,
|
|
ClobLocator,
|
|
CollectionValue,
|
|
IntervalYM,
|
|
RowValue,
|
|
)
|
|
from .exceptions import (
|
|
DatabaseError,
|
|
DataError,
|
|
Error,
|
|
IntegrityError,
|
|
InterfaceError,
|
|
InternalError,
|
|
NotSupportedError,
|
|
OperationalError,
|
|
ProgrammingError,
|
|
Warning,
|
|
)
|
|
|
|
# PEP 249 module-level globals
|
|
apilevel = "2.0"
|
|
threadsafety = 1 # threads may share the module but not connections
|
|
paramstyle = "numeric" # locked in DECISION_LOG.md — matches Informix ESQL/C
|
|
|
|
try:
|
|
__version__ = version("informix-db")
|
|
except PackageNotFoundError:
|
|
# Editable install or running uninstalled; fall back to a sentinel.
|
|
__version__ = "0.0.0+local"
|
|
|
|
__all__ = [
|
|
"BlobLocator",
|
|
"ClobLocator",
|
|
"CollectionValue",
|
|
"Connection",
|
|
"DataError",
|
|
"DatabaseError",
|
|
"Error",
|
|
"IntegrityError",
|
|
"InterfaceError",
|
|
"InternalError",
|
|
"IntervalYM",
|
|
"NotSupportedError",
|
|
"OperationalError",
|
|
"ProgrammingError",
|
|
"RowValue",
|
|
"Warning",
|
|
"__version__",
|
|
"apilevel",
|
|
"connect",
|
|
"paramstyle",
|
|
"threadsafety",
|
|
]
|
|
|
|
|
|
def connect(
|
|
host: str,
|
|
port: int = 9088,
|
|
*,
|
|
user: str,
|
|
password: str | None,
|
|
database: str | None = None,
|
|
server: str = "informix",
|
|
connect_timeout: float | None = None,
|
|
read_timeout: float | None = None,
|
|
keepalive: bool = False,
|
|
client_locale: str = "en_US.8859-1",
|
|
env: dict[str, str] | None = None,
|
|
autocommit: bool = False,
|
|
) -> Connection:
|
|
"""Open a connection to an Informix server.
|
|
|
|
The ``server`` argument is the Informix DBSERVERNAME the listener
|
|
identifies itself as (default ``"informix"`` matches the IBM
|
|
Developer Edition Docker image). It's distinct from ``host`` (which
|
|
is the network address) and from ``database`` (which is the
|
|
database name to open after login).
|
|
|
|
``database`` may be ``None`` to log in without selecting a database;
|
|
the server still requires a successful login for this to work.
|
|
"""
|
|
return Connection(
|
|
host=host,
|
|
port=port,
|
|
user=user,
|
|
password=password,
|
|
database=database,
|
|
server=server,
|
|
connect_timeout=connect_timeout,
|
|
read_timeout=read_timeout,
|
|
keepalive=keepalive,
|
|
client_locale=client_locale,
|
|
env=env,
|
|
autocommit=autocommit,
|
|
)
|