Skip to content

SQL ↔ Python types

SQL typePython typeNotes
SMALLINT / INT / SERIALintArbitrary precision on the Python side.
BIGINT / BIGSERIALint8-byte two’s complement on the wire.
INT8 / SERIAL8intThe legacy 64-bit integer, a different wire format from BIGINT rather than an alias. See below.
FLOAT / SMALLFLOATfloatIEEE 754 double / single.
DECIMAL(p,s) / MONEYdecimal.DecimalExact precision preserved.
CHAR / NCHARstrFixed width, space-padded; trailing spaces stripped on decode.
VARCHAR / NVCHAR / LVARCHARstrVariable length. Decoded using client_locale.
BOOLEANbool
DATEdatetime.date
DATETIME YEAR TO …datetime.datetime / datetime.time / datetime.dateThe Python type depends on the field range.
INTERVAL DAY TO FRACTIONdatetime.timedelta
INTERVAL YEAR TO MONTHinformix_db.IntervalYMCustom 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.ClobLocatorOpaque server-side locators, 72 bytes. Read via cursor.read_blob_column, write via cursor.write_blob_column.
ROW(…)informix_db.RowValueRaw payload plus schema string, not decomposed into fields. See below.
SET(…) / MULTISET(…) / LIST(…)informix_db.CollectionValueRaw payload plus element schema, not iterable. See below.
NULLNone

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.

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.

Informix’s DATETIME YEAR TO X is field-range typed. The Python type returned depends on which fields are present:

RangePython type
YEAR TO YEAR through YEAR TO DAYdatetime.date
YEAR TO HOUR through YEAR TO FRACTION(5)datetime.datetime
HOUR TO HOUR through HOUR TO FRACTION(5)datetime.time

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.

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 months
ym.years # 2
ym.remainder_months # 3
str(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.

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 # str

They 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