Oninit® SQLMorph — Informix UDR
The headline output. Each input procedure produces a C source file and a CREATE FUNCTION install SQL script. The C registers under the same name as the source SPL so existing callers don't change. Build every UDR in a directory with the generic Makefile published at https://www.oninit.com/sqlmorph/Makefile.
What gets emitted
| File | Contents |
|---|---|
| <name>.c | C source with #include <mi.h> / <miami.h>, the UDREXPORT'd function, all SPL types mapped to mi_* counterparts. |
| Makefile | Not emitted per procedure — one canonical generic Makefile is published at https://www.oninit.com/sqlmorph/Makefile. Drop it into any bundle directory; it builds every .c and runs every *_install.sql. |
| <name>_install.sql | DROP PROCEDURE IF EXISTS … then CREATE PROCEDURE … EXTERNAL NAME '$INFORMIXDIR/extend/sqlmorph/<name>.so(<name>)' LANGUAGE C NOT VARIANT. |
| REPORT.md | Only emitted when constructs were flagged. Every entry has file:line, the construct, and a human-readable note explaining what to review. |
Type mapping
| SPL type | C type |
|---|---|
| SMALLINT / INT / INTEGER / SERIAL | mi_smallint / mi_integer (by value) |
| INT8 / BIGINT / SERIAL8 / BIGSERIAL | mi_int8 (by value) |
| FLOAT / DOUBLE PRECISION | mi_double_precision |
| SMALLFLOAT / REAL | mi_real |
| DECIMAL / NUMERIC / MONEY | mi_decimal * |
| CHAR / VARCHAR | mi_string * |
| LVARCHAR / NCHAR / NVARCHAR | mi_lvarchar * |
| DATE | mi_date (by value) |
| DATETIME | mi_datetime * |
| INTERVAL | mi_interval * |
| BOOLEAN | mi_boolean (by value) |
| TEXT / BYTE / CLOB / BLOB | MI_LO_HANDLE * |
Iterator UDRs — RETURN ... WITH RESUME
SPL procedures that yield rows via RETURN ... WITH RESUME — including the common FOREACH ... RETURN ... WITH RESUME; END FOREACH streaming pattern — compile to a reentrant Informix C UDR. The engine calls the function repeatedly; mi_fp_request(fparam) tells it which phase to run.
| Phase | What gets emitted |
|---|---|
| SET_INIT | Allocate iter_state_t via mi_alloc(sizeof, PER_COMMAND), zero-fill, run all pre-FOREACH initialisers (rewriting locals as state->name), open the cursor with mi_prepare + ONI_EXEC_PREP, and stash the state via mi_fp_setfuncstate. |
| SET_RETONE | On state->phase >= 1, run the post-yield body of the previous iteration (rotated to the next call's prologue). Then fetch a row; if the cursor is exhausted, run any post-FOREACH code, close the cursor, and call mi_fp_setisdone(fparam, MI_TRUE). Otherwise bind INTO targets, run the pre-yield body, set phase = 1, and return the yielded value. |
| SET_END | Close the cursor handle. The PER_COMMAND state struct itself is reclaimed by the engine. |
The auto-generated iter_state_t holds, in order: a phase counter, an MI_CONNECTION *conn when the body uses in-engine SQL, one field per DEFINE'd local (so values survive across yields), and one MI_STATEMENT *_curN_st per FOREACH cursor in source order. The install SQL gains a WITH (ITERATOR) modifier so the engine knows to dispatch in the request-loop pattern.
String concat (||). SPL a || b compiles to oni_concat((const char *)(a), (const char *)(b)) — a runtime helper in sqlmorph_util.c that allocates a fresh mi-buffer and concatenates two null-terminated strings (NULL operands treated as empty). Nested a || b || c chains nest the call. The reporter only flags a || when an operand is a numeric literal (needs sprintf-style pre-conversion to string).
Multi-column iterators. When the SPL declares RETURNING t1, t2, ... with RETURN a, b WITH RESUME, the emitted UDR returns MI_ROW *. SET_INIT derives a row descriptor via mi_fp_rettype(fparam, 0) + mi_row_desc_create and stashes it on the state. SET_RETONE assembles each yield's columns into state->_row_vals[] (with int-family values cast through (intptr_t), and float / mi_int8 values stored in state-resident scratch slots passed by address) and returns mi_row_create(state->conn, state->_row_desc, _row_vals, _row_nulls). SET_END destroys the row descriptor. The install SQL keeps the SPL RETURNING t1, t2 list verbatim — the row layout is derived at runtime from that signature.
Two iterator shapes ship in the conformance corpus: add_one (trivial single-row yield) and iter_orders (cursor-driven, post-yield body rotation).