SQL ↔ Python types
| SQL type | Python type | Notes |
|---|---|---|
SMALLINT / INT / SERIAL | int | Arbitrary precision on the Python side. |
BIGINT / BIGSERIAL | int | 8-byte two’s complement on the wire. |
INT8 / SERIAL8 | int | The legacy 64-bit integer, a different wire format from BIGINT rather than an alias. See below. |
FLOAT / SMALLFLOAT | float | IEEE 754 double / single. |
DECIMAL(p,s) / MONEY | decimal.Decimal | Exact precision preserved. |
CHAR / NCHAR | str | Fixed width, space-padded; trailing spaces stripped on decode. |
VARCHAR / NVCHAR / LVARCHAR | str | Variable length. Decoded using client_locale. |
BOOLEAN | bool | |
DATE | datetime.date | |
DATETIME YEAR TO … | datetime.datetime / datetime.time / datetime.date | The Python type depends on the field range. |
INTERVAL DAY TO FRACTION | datetime.timedelta | |
INTERVAL YEAR TO MONTH | informix_db.IntervalYM | Custom type, because datetime.timedelta can’t represent year-month intervals. |
BYTE / TEXT (legacy in-row blobs) | bytes / str | |
BLOB / CLOB (smart-LOBs) | informix_db.BlobLocator / informix_db.ClobLocator | Opaque server-side locators, 72 bytes. Read via cursor.read_blob_column, write via cursor.write_blob_column. |
ROW(…) | informix_db.RowValue | Raw payload plus schema string, not decomposed into fields. See below. |
SET(…) / MULTISET(…) / LIST(…) | informix_db.CollectionValue | Raw payload plus element schema, not iterable. See below. |
NULL | None |
INT8 and SERIAL8 are not BIGINT
Section titled “INT8 and SERIAL8 are not BIGINT”Informix has two unrelated 64-bit integer types and they share nothing on the wire:
| Wire format | |
|---|---|
BIGINT / BIGSERIAL (type 52 / 53) | 8 bytes, two’s complement, big-endian |
INT8 / SERIAL8 (type 17 / 18) | 10 bytes, sign-magnitude, halves stored high-last |
Both decode to plain Python int, so this only matters if you’re reading the wire yourself. It’s worth knowing that INT8 stores +n and −n with identical magnitude bytes and the sign in a leading word. Decoding it as a signed 64-bit integer looks correct for every positive value and is wrong for every negative one.
INT8/SERIAL8 are common in schemas predating Informix 11.50, which is when BIGINT arrived.
LVARCHAR wire framing
Section titled “LVARCHAR wire framing”Worth knowing if you’re reading captures. Every Informix server we’ve tested describes LVARCHAR columns as UDTVAR (type 40, extended_name='lvarchar'), and the value arrives wrapped in a UDT envelope:
[1-byte null indicator][4-byte length][content]No padding, and the length field is present even when the indicator says NULL. NULL and empty string differ only in that indicator byte, since both carry a length of zero.
DATETIME field ranges
Section titled “DATETIME field ranges”Informix’s DATETIME YEAR TO X is field-range typed. The Python type returned depends on which fields are present:
| Range | Python type |
|---|---|
YEAR TO YEAR through YEAR TO DAY | datetime.date |
YEAR TO HOUR through YEAR TO FRACTION(5) | datetime.datetime |
HOUR TO HOUR through HOUR TO FRACTION(5) | datetime.time |
Decimal precision
Section titled “Decimal precision”DECIMAL columns preserve their declared precision and scale through the codec. A DECIMAL(10,2) column with value 123.45 decodes to Decimal("123.45") exactly, with no float intermediate.
For binding Decimal values into INSERT/UPDATE, the driver uses the column’s declared scale. Pass Decimal for exact values; float works but may cause rounding at the column scale.
NULL is None in both directions. Use IS NULL / IS NOT NULL in SQL, because WHERE x = ? with None returns no rows even where x IS NULL.
Type extensions
Section titled “Type extensions”informix_db.IntervalYM represents INTERVAL YEAR TO MONTH. It takes a single total month count, mirroring the server’s own representation, with years and remainder_months available as derived properties:
from informix_db import IntervalYM
ym = IntervalYM(27) # 2 years, 3 monthsym.years # 2ym.remainder_months # 3str(ym) # '2-03'
cur.execute("INSERT INTO contracts (term) VALUES (?)", (ym,))cur.execute("SELECT term FROM contracts WHERE id = ?", (1,))result = cur.fetchone()[0] # IntervalYM(months=27)Negative intervals are supported; the sign lives on months and propagates to both derived properties.
ROW and collection columns
Section titled “ROW and collection columns”informix_db.RowValue and informix_db.CollectionValue are returned for ROW, SET, MULTISET, and LIST columns. Both are opaque wrappers, not decomposed values:
row_val.raw # bytes: the server's textual representation, e.g. b"ROW('Alice',30)"row_val.schema # str: the column's declared schema
coll_val.raw # bytes, e.g. b'LIST{10,20,30}'coll_val.kind # 'set' | 'multiset' | 'list' | 'collection'coll_val.element_schema # strThey are not iterable and do not expose fields by name. Fully parsing a composite type means reimplementing JDBC’s IfxComplexInput, which we haven’t done.
If you need individual fields today, project them in SQL. The server does the decomposition for you and you get ordinary typed columns back:
cur.execute("SELECT person.name, person.age FROM staff") # str, int