"""Phase 4.x integration tests — NULL row decoding for all supported types. Per-type NULL sentinels (verified empirically — see DECISION_LOG.md): INT → 0x80000000 (INT_MIN) BIGINT → 0x8000000000000000 (LONG_MIN) REAL → all 0xFF (NaN bit pattern) FLOAT → all 0xFF VARCHAR → [byte 1][byte 0] (length=1, single nul) """ from __future__ import annotations import pytest import informix_db from tests.conftest import ConnParams pytestmark = pytest.mark.integration def _connect(conn_params: ConnParams) -> informix_db.Connection: return informix_db.connect( host=conn_params.host, port=conn_params.port, user=conn_params.user, password=conn_params.password, database=conn_params.database, server=conn_params.server, connect_timeout=10.0, read_timeout=10.0, ) def test_null_in_int_decoded_as_none(conn_params: ConnParams) -> None: with _connect(conn_params) as conn: cur = conn.cursor() cur.execute("CREATE TEMP TABLE t_null_int (id INTEGER, val INTEGER)") cur.execute("INSERT INTO t_null_int VALUES (?, ?)", (1, None)) cur.execute("SELECT val FROM t_null_int") assert cur.fetchone() == (None,) def test_null_in_bigint_decoded_as_none(conn_params: ConnParams) -> None: with _connect(conn_params) as conn: cur = conn.cursor() cur.execute("CREATE TEMP TABLE t_null_bi (id INTEGER, val BIGINT)") cur.execute("INSERT INTO t_null_bi VALUES (?, ?)", (1, None)) cur.execute("SELECT val FROM t_null_bi") assert cur.fetchone() == (None,) def test_null_in_float_decoded_as_none(conn_params: ConnParams) -> None: with _connect(conn_params) as conn: cur = conn.cursor() cur.execute("CREATE TEMP TABLE t_null_f (id INTEGER, val FLOAT)") cur.execute("INSERT INTO t_null_f VALUES (?, ?)", (1, None)) cur.execute("SELECT val FROM t_null_f") assert cur.fetchone() == (None,) def test_null_in_real_decoded_as_none(conn_params: ConnParams) -> None: with _connect(conn_params) as conn: cur = conn.cursor() cur.execute("CREATE TEMP TABLE t_null_r (id INTEGER, val REAL)") cur.execute("INSERT INTO t_null_r VALUES (?, ?)", (1, None)) cur.execute("SELECT val FROM t_null_r") assert cur.fetchone() == (None,) def test_null_in_varchar_decoded_as_none(conn_params: ConnParams) -> None: """The trickiest one — VARCHAR NULL is `[byte 1][byte 0]`, distinct from empty `''`.""" with _connect(conn_params) as conn: cur = conn.cursor() cur.execute("CREATE TEMP TABLE t_null_vc (id INTEGER, val VARCHAR(50))") cur.execute("INSERT INTO t_null_vc VALUES (?, ?)", (1, None)) cur.execute("SELECT val FROM t_null_vc") assert cur.fetchone() == (None,) def test_empty_varchar_distinct_from_null(conn_params: ConnParams) -> None: """Empty string `''` is encoded `[byte 0]` (length=0); must NOT be confused with NULL.""" with _connect(conn_params) as conn: cur = conn.cursor() cur.execute("CREATE TEMP TABLE t_empty_vc (id INTEGER, val VARCHAR(50))") cur.execute("INSERT INTO t_empty_vc VALUES (?, ?)", (1, "")) cur.execute("INSERT INTO t_empty_vc VALUES (?, ?)", (2, None)) cur.execute("SELECT id, val FROM t_empty_vc ORDER BY id") rows = cur.fetchall() assert rows == [(1, ""), (2, None)] def test_mixed_nulls_and_values_in_one_row(conn_params: ConnParams) -> None: """Mixed NULL + non-NULL columns in one row — proves per-column null detection.""" with _connect(conn_params) as conn: cur = conn.cursor() cur.execute( "CREATE TEMP TABLE t_mix (a INTEGER, b VARCHAR(20), c FLOAT, d BIGINT)" ) cur.execute("INSERT INTO t_mix VALUES (?, ?, ?, ?)", (1, None, 3.14, None)) cur.execute("INSERT INTO t_mix VALUES (?, ?, ?, ?)", (None, "two", None, 200)) cur.execute("SELECT a, b, c, d FROM t_mix ORDER BY a NULLS LAST") assert cur.fetchall() == [(1, None, 3.14, None), (None, "two", None, 200)]