Verify Informix 14.10 and 12.10; make the version matrix reproducible

Closes out the field report that prompted 2026.05.08.2. Full integration
suite, same commit, all green:

  Informix 15.0.1.0.3DE     241 / 241
  Informix 14.10.FC7W1DE    241 / 241
  Informix 12.10.FC12W1DE   241 / 241

Including smart-LOB. The 28-type round-trip produces byte-identical wire
output on all three — same type codes, same encoded lengths, same values.
There is no 12-vs-14-vs-15 wire difference for anything we support.

Testing three versions by hand was tedious and undocumented, which is
part of why it never happened. Now:

  make ifx-legacy-up      12.10 on 9089, 14.10 on 9090, alongside 15 on 9088
  make ifx-legacy-setup   blobspace1 + sbspace1 in both
  make test-matrix        the suite against all three

tests/setup-spaces.sh absorbs the per-image differences that made this
annoying: 12.10/14.10 use a flat INFORMIXDIR while 15 nests a versioned
subdirectory; ONCONFIG is named differently across images; `bash -lc`
wipes INFORMIXDIR and makes every utility fail with a misleading
"Unable to read $INFORMIXDIR (/usr/informix)"; and 12.10 DE ships no
ontape at all (the level-0 archive turns out to be unnecessary anyway).
Without blobspace1/sbspace1, ~21 tests fail with errors that look like
driver bugs and aren't.

The documented workflow was validated from scratch — containers removed,
recreated via compose, spaces created via the script, matrix run — rather
than written up afterward from whichever commands happened to work. The
README claim this replaces was wrong precisely because nobody did that.
This commit is contained in:
Ryan Malloy 2026-08-27 00:36:35 -06:00
parent 87b5b7c354
commit 783b2bec97
5 changed files with 198 additions and 11 deletions

View File

@ -43,7 +43,7 @@ bytes 6-9 HIGH 32 bits, big-endian unsigned
### On the version question
The report arrived as "Informix 12 mangles result sets." It isn't a version issue. Running the same 28-type round-trip against **12.10.FC12W1DE** and **15.0.1.0.3DE** produced **byte-identical** wire output — same type codes, same encoded lengths, same values. Both servers were equally broken, and are now equally fixed.
The report arrived as "Informix 12 mangles result sets." It isn't a version issue. Running the same 28-type round-trip against **12.10.FC12W1DE**, **14.10.FC7W1DE**, and **15.0.1.0.3DE** produced **byte-identical** wire output — same type codes, same encoded lengths, same values. All three were equally broken, and are now equally fixed.
The reason it read as version-specific: `INT8`/`SERIAL8` dominate Informix 12-era schemas (`BIGINT` only arrived in 11.50), while our test fixtures use `BIGINT`. The bugs were always there; older schemas just walk into them far more often.
@ -55,9 +55,26 @@ Added `tests/test_type_framing.py` (20 integration tests) and `tests/test_int8_u
### Verified
- 251/251 integration tests on Informix 15.0.1.0.3DE
- 241/241 non-smart-LOB integration tests on Informix 12.10.FC12W1DE (the LOB tests need an sbspace that image doesn't have configured)
- New framing tests: 20/20 on both servers
Full integration suite, same commit, all green:
| Server | Result |
|---|---|
| 15.0.1.0.3DE | 241 / 241 |
| 14.10.FC7W1DE | 241 / 241 |
| 12.10.FC12W1DE | 241 / 241 |
New framing tests pass 34/34 on all three.
### Server matrix is now reproducible
Testing three versions by hand was tedious and undocumented, which is part of why it never happened. Added:
- `tests/docker-compose.legacy.yml` — 12.10 on 9089 and 14.10 on 9090, coexisting with the primary 15 container on 9088
- `tests/setup-spaces.sh` — creates `blobspace1` + `sbspace1` in any dev container. Handles the layout differences between images (12.10/14.10 use a flat `INFORMIXDIR`, 15 nests a versioned subdirectory; `ONCONFIG` is named differently; 12.10 DE ships no `ontape`). Without these spaces ~21 tests fail with errors that look like driver bugs and aren't.
- `make ifx-legacy-up` / `ifx-legacy-setup` / `ifx-legacy-down` / `ifx-spaces`
- `make test-matrix` — the suite against all three versions
The whole documented workflow was validated from scratch (containers destroyed, recreated via compose, spaces created via the script, matrix run) rather than written up after the fact from commands that happened to work.
### Also

View File

@ -79,6 +79,37 @@ ifx-status: ## Check container health and listener readiness
@docker ps --filter name=$(IFX_CONTAINER) --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
@nc -zv 127.0.0.1 9088 2>&1 | head -1
ifx-spaces: ## Create blobspace1 + sbspace1 in the Informix 15 container (needed for LOB tests)
tests/setup-spaces.sh $(IFX_CONTAINER)
# ----------------------------------------------------------------------------
# Server-compatibility matrix: Informix 12.10 / 14.10 alongside 15
# ----------------------------------------------------------------------------
# The legacy containers run on 9089 / 9090 so they coexist with the primary
# 15 container on 9088. The integration suite targets whichever server
# IFX_PORT names, so the matrix is just the same suite three times.
ifx-legacy-up: ## Start Informix 12.10 (9089) and 14.10 (9090) alongside 15
docker compose -f tests/docker-compose.legacy.yml up -d
@echo " 12.10 -> 127.0.0.1:9089"
@echo " 14.10 -> 127.0.0.1:9090"
@echo " Engine init takes ~1-3 min on first start; then: make ifx-legacy-setup"
ifx-legacy-setup: ## Create blobspace1 + sbspace1 in both legacy containers
tests/setup-spaces.sh informix-db-test-1210
tests/setup-spaces.sh informix-db-test-1410
ifx-legacy-down: ## Stop and remove the legacy containers
docker compose -f tests/docker-compose.legacy.yml down
test-matrix: ## Run the integration suite against 15 (9088), 14.10 (9090), and 12.10 (9089)
@echo "=== Informix 15 (9088) ==="
@IFX_PORT=9088 uv run pytest -m "integration and not benchmark" -q --no-header
@echo "=== Informix 14.10 (9090) ==="
@IFX_PORT=9090 uv run pytest -m "integration and not benchmark" -q --no-header
@echo "=== Informix 12.10 (9089) ==="
@IFX_PORT=9089 uv run pytest -m "integration and not benchmark" -q --no-header
# ----------------------------------------------------------------------------
# Phase 0 spike: re-capture wire traffic against the dev container
# ----------------------------------------------------------------------------

View File

@ -149,15 +149,30 @@ The fast-path RPC (`SQ_FPROUTINE` / `SQ_EXFPROUTINE`) bypasses PREPARE → EXECU
## Server compatibility
| Server | Status |
|---|---|
| **15.0.1.0.3DE** | Primary target. Full suite green (251 integration tests). |
| **12.10.FC12W1DE** | Verified 2026-05-08. Full suite green except smart-LOB, which needs an sbspace we haven't configured on that image — not a driver limitation. |
| **14.10** | Not yet tested. Expected to work; we'll confirm rather than assume. |
All three tested against the official IBM developer-edition Docker images, full integration suite, same commit:
Earlier releases of this README claimed the wire protocol was "stable across modern versions" and should "work against 12.10+ unmodified." That was never tested when written. It has since been measured: running the same 28-type round-trip against 12.10 and 15 produces **byte-identical** wire output, so the claim turned out to be true — but it was a guess at the time, and a user lost debugging time to it. Apologies.
| Server | Image | Integration suite |
|---|---|---|
| **15.0.1.0.3DE** | `icr.io/informix/informix-developer-database` | **241 / 241** |
| **14.10.FC7W1DE** | `ibmcom/informix-developer-database` | **241 / 241** |
| **12.10.FC12W1DE** | `ibmcom/informix-developer-database` | **241 / 241** |
One real caveat remains. SQLI negotiates some wire framing through a capability exchange (`SQ_PROTOCOLS`) that this driver does not yet perform; we hardcode the modern framing. That is correct for every server we have measured, but it is an assumption rather than a negotiation, and it is the first thing to suspect if you hit corrupt rows on a server we haven't listed above. Please open an issue with your server version if so.
Reproduce the whole matrix:
```bash
make ifx-up # Informix 15 on 9088
make ifx-legacy-up # 12.10 on 9089, 14.10 on 9090
make ifx-legacy-setup # blobspace1 + sbspace1 (smart-LOB tests need these)
make test-matrix # the suite against all three
```
The suite targets whichever server `IFX_PORT` names, so a single version is just `IFX_PORT=9089 uv run pytest -m "integration and not benchmark"`.
Beyond the suite passing, a 28-type round-trip (every scalar type we support, including the fiddly ones — `INT8`, `NCHAR`, `BOOLEAN`, `DATETIME YEAR TO FRACTION(5)`, `INTERVAL`, `DECIMAL`, `MONEY`, `LVARCHAR`) produces **byte-identical wire output** on all three versions: same type codes, same encoded lengths, same values.
Earlier releases of this README claimed the protocol was "stable across modern versions" and should "work against 12.10+ unmodified." That claim turned out to be correct, but it was written before anyone had run it against 12.10, and a user lost debugging time to a version-compatibility problem that didn't exist. It's measured now. Apologies for the earlier guess.
One caveat remains, and it's a real one. SQLI negotiates some wire framing through a capability exchange (`SQ_PROTOCOLS`) that this driver does not yet perform — we hardcode the modern framing. That is correct on all three servers above, but it is an assumption rather than a negotiation. If you hit corrupted rows on a server not in that table, this is the first thing to suspect; please open an issue with your server version and the `cursor.description` of the offending query.
For features that need server-side configuration (smart-LOBs, logged transactions), see [`docs/DECISION_LOG.md`](https://git.supported.systems/warehack.ing/informix-db/src/branch/main/docs/DECISION_LOG.md):
- Phase 7 — logged-DB transactions

View File

@ -0,0 +1,48 @@
# Older Informix versions, for the server-compatibility matrix in the README.
#
# The primary dev container (tests/docker-compose.yml) runs Informix 15 on
# 9088. These two run alongside it on 9089 / 9090 so all three can be tested
# without tearing anything down.
#
# These images live on Docker Hub under ibmcom/ rather than icr.io/informix/ —
# IBM stopped publishing the older developer editions to the newer registry.
# They're tagged rather than digest-pinned because the ibmcom repo is frozen
# (no new pushes since the icr.io move), so the tags are already immutable in
# practice.
#
# make ifx-legacy-up start both
# make ifx-legacy-setup create blobspace1 + sbspace1 (needed for LOB tests)
# make test-matrix run the integration suite against all three
# make ifx-legacy-down stop and remove
services:
ifx1210:
container_name: informix-db-test-1210
image: ibmcom/informix-developer-database:12.10.FC12W1DE
privileged: true
environment:
LICENSE: accept
SIZE: small
ports:
- "9089:9088"
healthcheck:
test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/9088 && exec 3<&- && exec 3>&-"]
interval: 5s
timeout: 3s
retries: 60
start_period: 30s
ifx1410:
container_name: informix-db-test-1410
image: ibmcom/informix-developer-database:14.10.FC7W1DE
privileged: true
environment:
LICENSE: accept
SIZE: small
ports:
- "9090:9088"
healthcheck:
test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/9088 && exec 3<&- && exec 3>&-"]
interval: 5s
timeout: 3s
retries: 60
start_period: 30s

76
tests/setup-spaces.sh Executable file
View File

@ -0,0 +1,76 @@
#!/usr/bin/env bash
# Create blobspace1 + sbspace1 in an Informix dev container.
#
# The developer-edition images ship with only rootdbs. BYTE/TEXT tests need a
# blobspace; BLOB/CLOB (smart-LOB) tests need an sbspace plus SBSPACENAME set.
# Without them, ~21 integration tests fail with errors that look like driver
# bugs but are pure server configuration.
#
# Usage: tests/setup-spaces.sh <container-name>
#
# Idempotent: re-running against a container that already has the spaces prints
# the server's "already exists" complaint and exits 0.
#
# Version quirks this handles, learned the hard way:
# * INFORMIXDIR differs — 12.10/14.10 use /opt/ibm/informix, 15 nests a
# versioned subdirectory (/opt/ibm/informix/v15.0.1.0.3).
# * ONCONFIG is named `onconfig` on some images and `onconfig.informix` on
# others; we probe for the file rather than guessing.
# * `bash -lc` wipes INFORMIXDIR from the environment on these images, which
# makes every utility fail with "Unable to read $INFORMIXDIR (/usr/informix)".
# Use `bash -c` and export explicitly.
# * 12.10 DE ships no `ontape`. The level-0 archive turns out to be
# unnecessary for the tests, so we attempt it and shrug if it's missing.
set -euo pipefail
CONTAINER="${1:?usage: setup-spaces.sh <container-name>}"
docker exec -u informix "$CONTAINER" bash -c '
set -u
# Locate INFORMIXDIR: either /opt/ibm/informix or a versioned subdir under it.
for cand in /opt/ibm/informix /opt/ibm/informix/v*; do
if [ -x "$cand/bin/onspaces" ]; then
export INFORMIXDIR="$cand"
break
fi
done
if [ -z "${INFORMIXDIR:-}" ]; then
echo "could not locate INFORMIXDIR (no bin/onspaces found)" >&2
exit 1
fi
export PATH="$INFORMIXDIR/bin:$PATH"
export INFORMIXSERVER=informix
export INFORMIXSQLHOSTS="$INFORMIXDIR/etc/sqlhosts"
# ONCONFIG name varies across images.
for cfg in onconfig onconfig.informix; do
if [ -f "$INFORMIXDIR/etc/$cfg" ]; then
export ONCONFIG="$cfg"
break
fi
done
: "${ONCONFIG:?no onconfig found in $INFORMIXDIR/etc}"
echo "INFORMIXDIR=$INFORMIXDIR ONCONFIG=$ONCONFIG"
SPACES=/opt/ibm/data/spaces
mkdir -p "$SPACES"
for f in blobspace1 sbspace1; do
[ -e "$SPACES/$f" ] || : > "$SPACES/$f"
chmod 660 "$SPACES/$f"
done
onspaces -c -b blobspace1 -g 1 -p "$SPACES/blobspace1" -o 0 -s 50000 2>&1 | grep -vE "^\s*$" || true
onspaces -c -S sbspace1 -p "$SPACES/sbspace1" -o 0 -s 50000 -Df "AVG_LO_SIZE=100" 2>&1 | grep -vE "^\s*$" || true
onmode -wm SBSPACENAME=sbspace1 2>&1 | tail -1 || true
# Level-0 archive. Informix warns it is required after adding a space; in
# practice the tests pass without it, and 12.10 DE has no ontape at all.
onmode -wm TAPEDEV=/dev/null >/dev/null 2>&1 || true
if command -v ontape >/dev/null 2>&1; then
ontape -s -L 0 2>&1 | tail -1 || true
else
echo "ontape not present on this image; skipping level-0 archive"
fi
'