Oninit® 4gl2everywhere — End-to-end validation
A stock c4gl-compiled application drives the bridge through its full session lifecycle — LOGIN, server-info exchange, PROTOCOLS capability negotiation, DATABASE open, statement PREPARE with embedded DESCRIBE, cursor OPEN, FETCH, CLOSE, RELEASE, and DISCONNECT — with no recompilation, no source change, and no driver swap on the client side. The 4GL binary is unaware of the bridge.
Test harness
The harness is a 4GL program (exercise.4gl) that opens a cursor over an alltypes table and FOREACHes every row into host variables covering every supported SQL type: SMALLINT, INTEGER, INT8, SMALLFLOAT, FLOAT, DECIMAL, MONEY, DATETIME (YEAR TO SECOND), INTERVAL (DAY TO SECOND), CHAR, VARCHAR, DATE, and BOOLEAN. Each row is DISPLAYed to stdout so any divergence between expected and decoded values is visible in the run log.
Four backends are exercised by the same harness against the same 4GL binary: a built-in null mock for protocol bring-up plus the three production targets — MySQL / MariaDB, PostgreSQL, and Oracle — each loaded with the same fixture rows so the wire output is directly comparable across engines.
Null backend — protocol round-trip
The null mock supplies four canonical alltypes rows: full-value, two boundary-value rows covering the documented numeric extremes, an edge-case row of zeros and minimum-date sentinels, and an all-NULL row. The harness reads all four through the bridge in a single FOREACH and prints them.
| Row | Profile | Verifies |
|---|---|---|
| 1 | Full values | Standard non-NULL encoding for every column type. |
| 2 | Boundary values | Numeric extremes: SMALLINT minimum, INT maximum, INT8 near-maximum (9 223 372 036 854 775 806), DECIMAL(8,2) and MONEY(16,2) all-nines, DATETIME 9999-12-31 23:59:59, INTERVAL 99 23:59:59. |
| 3 | Zero / minimum | Zero numerics, empty CHAR / VARCHAR, DATETIME 0001-01-01 00:00:00, INTERVAL 0 00:00:00. |
| 4 | All NULL | Per-type NULL wire sentinels for every column. |
The captured run prints all four rows in 4GL DISPLAY format and ends with rows fetched: 4. The bridge's session log shows the complete cursor lifecycle — LOGIN through CLOSE / RELEASE — with no protocol errors, no spurious server replies, and no 4GL run-time errors.
Cross-database round-trip — MySQL, PostgreSQL, Oracle
The same 4GL binary is rerun against each real database backend in turn, pointed at a populated harness database. The bridge prepares the SELECT, forwards it to the target via the pluggable backend interface, then re-encodes each result row into the Informix tuple-row wire format the 4GL binary expects.
| Stage | Bridge action |
|---|---|
| LOGIN ASF | Decode the 4GL client's login envelope; reply with a server-info ASF body byte-exact for the SQLI handshake. |
| PROTOCOLS exchange | Echo the 9-byte capability bitmap so subsequent statements use the modern (post-9.0342) wire shape. |
| DBOPEN | Translate SQ_DBOPEN harness to the target's native database / schema verb — mysql_select_db(), SET search_path, or ALTER SESSION SET CURRENT_SCHEMA. |
| PREPARE + DESCRIBE | Run the SQL through the parser / IR / dialect emitter; reply with column metadata for the fourteen SELECT columns. |
| OPEN cursor | Submit the rewritten statement to the target; the bridge holds the result-set ready for FETCH. |
| FETCH | For each row, dispatch on the column's source type and emit the matching Informix wire bytes — integer signed BE, INT8 sign + low + high, packed-decimal control + base-100 BCD digits, DATETIME and INTERVAL packed BCD, fixed-width CHAR, length-prefixed VARCHAR, day-count DATE. |
| CLOSE / RELEASE / DISCONNECT | Close the result-set, release the prepared statement, and disconnect the underlying client library. |
Per-target type mapping highlights
The harness alltypes schema is dialect-mapped per backend. The bridge re-encodes each result column from the target's native text representation back into the Informix tuple-row format, so the 4GL binary cannot tell that the result set came from MySQL, Oracle, or PostgreSQL rather than from a real Informix server: the on-wire bytes match what a stock IDS sends for the equivalent query.
| Informix type | MySQL / MariaDB | PostgreSQL | Oracle |
|---|---|---|---|
| SMALLINT | SMALLINT | SMALLINT | NUMBER(5) |
| INTEGER | INT | INTEGER | NUMBER(10) |
| INT8 / BIGINT | BIGINT | BIGINT | NUMBER(19) |
| SMALLFLOAT | FLOAT | REAL | BINARY_FLOAT |
| FLOAT | DOUBLE | DOUBLE PRECISION | BINARY_DOUBLE |
| DECIMAL(p,s) | DECIMAL(p,s) | NUMERIC(p,s) | NUMBER(p,s) |
| MONEY(p,s) | DECIMAL(p,s) | NUMERIC(p,s) | NUMBER(p,s) |
| DATETIME YEAR TO SECOND | DATETIME | TIMESTAMP(0) | TIMESTAMP(0) |
| INTERVAL DAY(2) TO SECOND | BIGINT (microseconds) | INTERVAL DAY TO SECOND(0) | INTERVAL DAY(2) TO SECOND(0) |
| CHAR(n) | CHAR(n) | CHARACTER(n) | CHAR(n) |
| VARCHAR(n) | VARCHAR(n) | VARCHAR(n) | VARCHAR2(n) |
| DATE | DATE | DATE | DATE |
| BOOLEAN | TINYINT(1) | BOOLEAN | NUMBER(1) |
| SERIAL | INT AUTO_INCREMENT | SERIAL | NUMBER + sequence |
The captured run prints all three target rows in 4GL DISPLAY format and ends with rows fetched: 3 on each backend. Native typed-target columns — PostgreSQL and Oracle's INTERVAL DAY TO SECOND, every backend's TIMESTAMP, NUMERIC / NUMBER, BOOLEAN where present — round-trip byte-exact against the source data. Engineless type emulations (MySQL's INTERVAL-as-BIGINT-microseconds, TINYINT(1)-as-BOOLEAN, Oracle's NUMBER-without-precision) decode through the bridge's per-target text parser to the same wire-format output the 4GL binary expects.
NULL handling
Every column type's NULL wire shape is captured against a real Informix server and replayed by the bridge byte-for-byte. The 4GL DISPLAY of an all-NULL row through the bridge is indistinguishable from the same DISPLAY against a real Informix server.
| Column type | NULL wire shape |
|---|---|
| SMALLINT / INTEGER / DATE | Sign-bit minimum value (0x8000 / 0x80000000). |
| INT8 / BIGINT | Ten-byte sign + low + high tuple with sign cleared and low magnitude word equal to 0x80000000. |
| SMALLFLOAT / FLOAT | IEEE-754 quiet NaN with all bits set (four / eight bytes of 0xFF). |
| DECIMAL / MONEY / DATETIME / INTERVAL | All-zero column width: control byte 0x00 + zero-pad digit bytes. Distinguished from a non-NULL zero by the cleared sign bit on the control byte. |
| CHAR(N) | N space bytes; indistinguishable from a non-NULL CHAR of all spaces (the SQL NULL semantic is carried out-of-band on the SQLI wire for fixed-width CHAR). |
| VARCHAR | Two-byte sequence 0x01 0x00 — one-byte length prefix followed by a single NUL byte; distinct from an empty non-NULL VARCHAR (single zero length prefix). |
| BOOLEAN (rendered as SMALLINT) | SMALLINT NULL sentinel (the 4GL host variable for the BOOLEAN column is a SMALLINT in the harness). |
What the validation demonstrates
| Property | Evidence |
|---|---|
| Stock c4gl compatibility | Same 4GL binary, no recompilation, drives all four backends without a code change. |
| Three-backend parity | The harness emits identical 4GL DISPLAY output for the same fixture data across MySQL, PostgreSQL, and Oracle. The 4GL FETCH path cannot tell which engine produced the rows. |
| Wire-protocol fidelity | Full LOGIN through CLOSE / RELEASE handshake matches a captured Informix Dynamic Server reference; no spurious tokens, no skipped acks. |
| Per-type round-trip coverage | Every supported SQL type (fourteen columns) is FETCHed end-to-end through every backend; non-NULL and NULL paths are independently verified. |
| Pluggable backend dispatch | The same 4GL FOREACH lights up the in-process null mock or any of the three real database client libraries, selected per listener via YAML configuration alone. |
| Failure isolation | A broken session (malformed token, decode failure, backend timeout) returns a structured SQ_ERR with the canonical IFXBRIDGE:CATEGORY:DETAIL shape; the listener and other sessions are unaffected. |