DEP/i — Schema-Migration & Deploy Platform

SteelFrame X application operation manual  ·  ← back to Operation Manuals  ·  Sign On

DEP/i is a DB2-for-i schema-migration / CI-CD deploy platform — the deploy tool itself, not the application it manages. It maintains a deploy history (MIGRATION_LOG), a "current deployed version" register (a global CREATE VARIABLE), a synthetic log-id generator (a SEQUENCE), a distinct TYPE, and an idempotent migration-runner procedure (SP_APPLY_MIGRATION) that every versioned migration is logged through. It has no 5250 screen: it is driven the way a real deploy CLI drives a database — an orchestrating job runs each version's DDL/DML batch inside a SAVEPOINT it takes itself, then calls the bookkeeping procedures to record the outcome and advance the version register. This manual is the reference for the operator who runs a deploy (and an idempotent re-deploy or a rollback) and for the developer maintaining the engine. It is grounded entirely in the committed source (sqlpl-app-dep/src/deploy_engine.sql, src/migrations.mjs, src/seed.mjs, and the test/dep_daily.mjs driver).

Contents

A. Overview & Architecture ↑ top

A.1 What it does

DEP/i deploys and evolves a database schema through versioned migrations, keeping an auditable record of every deploy and guaranteeing that a re-run is safe:

The worked example that ships with it deploys a small order-management schema (ORDDB: CUSTOMER / PRODUCT / ORDERS / ORDER_LINE) through six versions V1–V6, plus a deliberately bad V5 to prove rollback.

A.2 A deploy engine, not an application — and why there is no screen

Two things distinguish DEP/i from a normal SteelFrame X application:

Why the migration DDL runs in the caller, not inside the procedure. SQL PL cannot portably EXECUTE IMMEDIATE arbitrary caller-supplied DDL text in every engine, and this engine's PREPARE/EXECUTE IMMEDIATE support is a documented honest-reject surface. So the runner procedures own only the bookkeeping (log + version register) — the common code worth centralizing — while the per-version DDL (opaque, generated per version) runs as an external batch the caller wraps in a savepoint. This mirrors how real CI/CD deploy tools are structured, where the migration body is version-specific and the outcome logging is shared.

A.3 Component & flow

  ENGINE (DEPSYS)                 DRIVER (deploy CLI)            MANAGED SCHEMA (ORDDB)
  --------------                  ------------------            ----------------------
  MIGRATION_LOG (history)         for each version V:           V1 baseline (CREATE)
  DEPLOY_SEQ    (log-id seq)        SAVEPOINT SP_V              V2 ADD COLUMN / ALIAS
  CURRENT_VER   (VARIABLE)          run V's DDL batch  ------>  V3 TYPE / RENAME / grants
  VERNO_T       (TYPE)             confirm clean?               V4 audit / FK / staging
  DEPLOY_CFG    (config kv)          yes -> SP_LOG_MIGRATION_OK V5 BAD -> ROLLBACK -> FAILED
                                     no  -> ROLLBACK TO SP_V    V5 FIXED (clean then ADD)
  SP_LOG_MIGRATION_OK  --------->        SP_LOG_MIGRATION_FAILED V6 teardown (DROP/REVOKE)
  SP_LOG_MIGRATION_FAILED         confirm OK? bumpVer CURRENT_VER
  SP_APPLY_MIGRATION (config)     verify catalog (QSYS2.SYS*)

A single deploy step flows: driver takes SAVEPOINT SP_V → runs version V's DDL/DML batch against ORDDB → if clean, calls SP_LOG_MIGRATION_OK(V, name, detail) which inserts an OK row (log-id from DEPLOY_SEQ) → driver reads the returned status and, when OK, issues a top-level SET DEPSYS.CURRENT_VER = V → driver verifies the new shape in the DB2 catalog. If the batch fails, the driver instead does ROLLBACK TO SAVEPOINT SP_V and calls SP_LOG_MIGRATION_FAILED.

A.4 Object inventory

ObjectTypeRole
DEPSYSSchemaThe deploy engine's own schema (the tool itself).
MIGRATION_LOGTableDeploy history: one row per (version, name) attempt; STATUS OK/SKIPPED/FAILED.
DEPLOY_CFGTableKey/value deploy config, upserted idempotently by SP_APPLY_MIGRATION.
DEPLOY_SEQSequenceSynthetic LOGID generator (NEXT VALUE FOR).
CURRENT_VERVariableGlobal "highest applied version" register (CREATE VARIABLE).
VERNO_TTypeDistinct type (AS INTEGER) for version-number bookkeeping.
SP_LOG_MIGRATION_OKProcedureLogs an OK / SKIPPED outcome (idempotency check on the log).
SP_LOG_MIGRATION_FAILEDProcedureLogs a FAILED outcome after the caller's rollback.
SP_APPLY_MIGRATIONProcedureSelf-contained idempotent runner for pure config-seed migrations.
V1…V6MigrationsThe versioned migration scripts for the managed ORDDB schema (src/migrations.mjs).
dep_daily.mjsDriverThe deploy-CLI-shaped orchestrator that runs and verifies the whole cycle.

The engine catalogue is 1 schema + 3 tables + 1 sequence + 1 variable + 1 type + 3 procedures, driving 6 versioned migrations over the managed ORDDB schema. Sections D and F expand each.

B. Driving a Deploy (Access) ↑ top

B.1 How it is driven — there is no screen

DEP/i is a deploy engine, so it is driven by an orchestration job, not by an operator at a 5250 screen. Honestly stated: there is no DSPF, no subfile, no command-entry program — the operator surface is a deploy driver (the committed reference driver is test/dep_daily.mjs, which mirrors a real deploy CLI). Every action below is a SQL batch or a procedure CALL issued by that driver against schema DEPSYS (the engine) and ORDDB (the managed schema).

To do thisWhat the driver issues
Bootstrap the engine (once, first deploy)Run src/deploy_engine.sql as a batch (CREATE SCHEMA DEPSYS … the three procedures).
Apply a versioned structural migrationSAVEPOINT SP_V; run the version's DDL batch; on success CALL DEPSYS.SP_LOG_MIGRATION_OK(?,?,?,?).
Apply a pure config-seed migrationCALL DEPSYS.SP_APPLY_MIGRATION(verno, name, cfgkey, cfgval, ?) — self-contained & idempotent.
Record a failed migration (after rollback)ROLLBACK TO SAVEPOINT SP_V; CALL DEPSYS.SP_LOG_MIGRATION_FAILED(?,?,?).
Advance the version registerTop-level SET DEPSYS.CURRENT_VER = <verno> (see the note below on why this is caller-side).
Inspect current version / historyVALUES DEPSYS.CURRENT_VER; SELECT ... FROM DEPSYS.MIGRATION_LOG.

The three engine procedures take the migration's identity and detail as IN parameters and return the outcome status as an OUT parameter (SP_LOG_MIGRATION_OK and SP_APPLY_MIGRATION return P_STATUS = OK / SKIPPED; SP_LOG_MIGRATION_FAILED is fire-and-forget). The driver reads the status and decides whether to advance CURRENT_VER.

Why the driver — not the procedure — advances CURRENT_VER. On this engine, a global SQL variable created with CREATE VARIABLE cannot be read or written from inside a SQL-PL routine body at all (qualified or unqualified), even though it works correctly from ad-hoc top-level SQL — a confirmed platform limitation the source notes as SQLPL-PLAT-DEP-03. So SP_LOG_MIGRATION_OK deliberately does not touch the variable; the driver issues a top-level SET DEPSYS.CURRENT_VER = … of its own when the returned status is OK. This is a faithful workaround, not a design compromise: many real deploy tools keep the "last applied version" register in the orchestrating CLI/job rather than in the database layer anyway.

B.2 Controls & audit — the deploy log, idempotency, rollback

DEP/i's control posture is a durable deploy log + idempotency + savepoint rollback, all enforced in the data layer. It does not model a four-eyes maker–checker approval workflow: a deploy is applied by the driver as it runs each version. The controls it does have:

C. The Deploy Cycle & Migrations ↑ top

A DEP/i deploy is a sequence of versioned migrations applied in order. The engine is bootstrapped once (running deploy_engine.sql), then each version's migration is applied, logged, verified, and — on a re-trigger — safely re-run. The driver wraps every version the way a real deploy tool would: a pre-flight catalog probe to detect an already-applied step, a SAVEPOINT around risky DDL, and an outcome log.

-- bootstrap the engine (first deploy only)
run deploy_engine.sql            -- CREATE SCHEMA DEPSYS + tables/seq/var/type/procs

-- apply a structural version (driver pseudo-code)
SAVEPOINT SP_V4;
run V4 DDL batch;                -- ADD/DROP COLUMN, ADD CONSTRAINT, CREATE TRIGGER, RENAME ...
CALL DEPSYS.SP_LOG_MIGRATION_OK(4, 'V4_AUDIT_FK_AND_STAGING', 'applied', ?);
SET DEPSYS.CURRENT_VER = 4;      -- caller-side, only when status = OK

-- a bad version rolls back and logs FAILED
SAVEPOINT SP_V5;
run V5 DDL batch;                -- ADD CONSTRAINT the data violates -> fails
ROLLBACK TO SAVEPOINT SP_V5;
CALL DEPSYS.SP_LOG_MIGRATION_FAILED(5, 'V5_BAD_QTY_CHECK', 'CHECK re-validation failed');

C.1 Engine procedures & the version set

StepPurposeEngine object usedInputsOutputs / effect
BOOTSTRAP Create the deploy engine itself. deploy_engine.sql DEPSYS schema, MIGRATION_LOG, DEPLOY_CFG, DEPLOY_SEQ, CURRENT_VER=0, VERNO_T, the 3 procedures.
SP_LOG_MIGRATION_OK Log an OK (or idempotent SKIPPED) structural-migration outcome. MIGRATION_LOG, DEPLOY_SEQ P_VERNO, P_MIGNAME, P_DETAIL OUT P_STATUS OK/SKIPPED; one MIGRATION_LOG row.
SP_LOG_MIGRATION_FAILED Record a rolled-back migration. MIGRATION_LOG, DEPLOY_SEQ P_VERNO, P_MIGNAME, P_DETAIL One MIGRATION_LOG row STATUS='FAILED'.
SP_APPLY_MIGRATION Self-contained idempotent runner for a pure config-seed migration. MIGRATION_LOG, DEPLOY_CFG, DEPLOY_SEQ P_VERNO, P_MIGNAME, P_CFGKEY, P_CFGVAL OUT P_STATUS OK/SKIPPED; MERGE'd DEPLOY_CFG row + log row.
V1 — V1_BASELINE_SCHEMA Create the managed ORDDB baseline. CREATE SCHEMA/TABLE/VIEW/SEQ/FUNCTION/PROCEDURE, GRANT V1.sql + seed data ORDDB with 4 tables, view, sequence, function, procedure, grants; CURRENT_VER→1.
V2 — V2_ADD_SHIPPING_AND_ALIAS Add columns + a CHECK + an alias. ALTER TABLE ADD COLUMN/CONSTRAINT, CREATE ALIAS, CREATE OR REPLACE VIEW CUSTOMER.SHIPZIP, ORDERS.FREIGHT (DEFAULT 0, CK_FREIGHT), alias OORD; CURRENT_VER→2.
V3 — V3_TYPE_AND_RENAME Distinct type, column re-typing/rename, unique, column grants. CREATE TYPE, ALTER COLUMN SET DATA TYPE/DEFAULT, RENAME COLUMN, ADD UNIQUE, column-level GRANT MONEY_T; UNITPRC widened + DEFAULT 0.01; SHIPZIP→SHIPPOSTAL; UQ_PDESC; column grants; CURRENT_VER→3.
V4 — V4_AUDIT_FK_AND_STAGING Audit trail, deferred FKs, stronger CHECK, staging table, TRUNCATE. CREATE OR REPLACE TRIGGER, DROP/ADD CONSTRAINT, ADD FK, DROP COLUMN, RENAME TABLE, TRUNCATE RESTART IDENTITY ORD_AUDIT + trigger; CK_FREIGHT2; ORDER_LINE FKs; SHIPPOSTAL dropped; ORD_STAGE; CURRENT_VER→4.
V5 — V5_BAD_QTY_CHECK Deliberately bad — a CHECK the seeded data violates. ALTER TABLE ADD CONSTRAINT (fails), SAVEPOINT/ROLLBACK TO SAVEPOINT Rolled back; MIGRATION_LOG FAILED; CURRENT_VER stays 4.
V5 — V5_QTY_CHECK_FIXED The real fix: clean the bad data, then add the CHECK. DELETE, ALTER TABLE ADD CONSTRAINT CK_QTY_POS enforced; CURRENT_VER→5.
V6 — V6_TEARDOWN_STAGING Retire superseded objects. DROP TABLE IF EXISTS RESTRICT, DROP ALIAS IF EXISTS, REVOKE ORD_STAGE + OORD dropped; UPDATE(UNITPRC) grant revoked; CURRENT_VER→6.

C.2 Per-version detail (V1–V6)

V1 — baseline schema & seed

Creates the managed schema ORDDB with CUSTOMER, PRODUCT, ORDERS (identity PK, FK to CUSTOMER, DEFAULT OSTAT='O') and ORDER_LINE, a hint SEQUENCE, the V_OPEN_ORDERS view, the FN_ORDER_TOTAL function and SP_ORDER_TOTAL procedure, and table/EXECUTE/schema-level grants. Seed data lands two orders; order 1 has three lines (one a deliberately zero-quantity line, for V5). ORDER_LINE's FKs are deliberately deferred to V4 — see the ordering note in C.3.

Expected after V1:
  CURRENT_VER = 1
  FN_ORDER_TOTAL(1) = 225.00   10x12.50 + 4x25.00 + 0x7.50
  V_OPEN_ORDERS     = 2 rows

V2 — add shipping columns, a CHECK, an alias

ADD COLUMN SHIPZIP (nullable) and ADD COLUMN FREIGHT DECIMAL(7,2) NOT NULL DEFAULT 0 (existing rows backfill to 0), a CHECK (FREIGHT ≥ 0) constraint (CK_FREIGHT), a view redeploy projecting the new columns, and CREATE OR REPLACE ALIAS OORD FOR ORDERS. The driver's pre-flight probes SYSCOLUMNS for FREIGHT; if present it logs SKIPPED instead of re-adding. Data survives the rebuild (order 1 total still 225.00).

The alias exercises a confirmed platform gap, SQLPL-PLAT-DEP-04: an INSERT INTO <alias> (col-list) VALUES (…) fails to resolve the alias ("no such table"). The driver asserts the bug so it stays reproducible, then routes around it with the bare-VALUES form (INSERT INTO OORD VALUES (…)), which works.

V3 — distinct type, re-type/rename, unique, column grants

CREATE TYPE MONEY_T AS DECIMAL(11,2); ALTER COLUMN UNITPRC SET DATA TYPE DECIMAL(11,2) and SET DEFAULT 0.01; RENAME COLUMN SHIPZIP TO SHIPPOSTAL; ADD CONSTRAINT UQ_PDESC UNIQUE (PDESC); and a column-level GRANT UPDATE (UNITPRC), REFERENCES (PRODNO). Prices are unchanged by the widening; a fresh insert picks up the new default (0.01); the old column name is gone after the rename; the UNIQUE is enforced. Pre-flight probes SYSTYPES for MONEY_T.

V4 — audit trail, deferred FKs, stronger CHECK, staging, TRUNCATE

The heaviest version: create ORD_AUDIT (identity PK); CREATE OR REPLACE TRIGGER TR_ORD_STAT (AFTER UPDATE OF OSTAT, WHEN status changes — a same-status update is suppressed); DROP CONSTRAINT CK_FREIGHT then ADD CONSTRAINT CK_FREIGHT2 CHECK (FREIGHT ≥ 0 AND FREIGHT < 10000); add the deferred ORDER_LINE FKs to ORDERS/PRODUCT (now safe, see C.3); redeploy the view to stop referencing SHIPPOSTAL, then DROP COLUMN SHIPPOSTAL; and create + RENAME TABLE a staging table. The driver then exercises TRUNCATE TABLE and TRUNCATE TABLE … RESTART IDENTITY (identity resets to 1).

Two confirmed platform notes surface here. SQLPL-PLAT-DEP-05: an FK added to a native table via ALTER TABLE ADD CONSTRAINT is genuinely enforced (a bad insert is rejected) but does not appear in any catalog view (SYSCST/SYSKEYCST/SYSREFCST) — asserted honestly, not masked. And native-table UNIQUE/CHECK constraints are a documented non-cataloged deviation (buildCatalogs()'s own comment), so V5's guard uses MIGRATION_LOG as its idempotency source of truth rather than probing SYSCST.

V5 — the deliberately bad migration & its real fix

V5_BAD attempts ADD CONSTRAINT CK_QTY_POS CHECK (QTY > 0), which the pre-existing zero-quantity seed row (ORDNO 1, LINENO 3) violates on re-validation. The driver took a SAVEPOINT SP_V5 first; the ADD fails; it does ROLLBACK TO SAVEPOINT SP_V5 and calls SP_LOG_MIGRATION_FAILED. Afterward the schema (same column count) and data (same row count, the bad row exactly as it was) are byte-for-byte unchanged, no CK_QTY_POS is left behind, CURRENT_VER is still 4, and the session still transacts normally. V5_FIXED is the real roll-forward: DELETE FROM ORDER_LINE WHERE QTY ≤ 0, then the same ADD CONSTRAINT succeeds; CURRENT_VER→5.

V5-BAD proof of clean rollback:
  schema column count  = unchanged
  ORDER_LINE row count = unchanged, bad row still QTY=0
  MIGRATION_LOG        = one FAILED row (V5_BAD_QTY_CHECK)
  CURRENT_VER          = 4  (did NOT advance)

V6 — teardown

Retires the superseded staging objects: DROP TABLE IF EXISTS ORD_STAGE RESTRICT, DROP ALIAS IF EXISTS OORD, and REVOKE UPDATE (UNITPRC) ON PRODUCT FROM PUBLIC. The base ORDERS table (aliased earlier) is untouched by the alias drop; the column-level UPDATE grant is gone while the REFERENCES grant (not revoked) remains. DROP … IF EXISTS makes the whole version a clean no-op on re-run.

C.3 Ordering & dependencies

D. Data Files (the platform's own tables) ↑ top

These are the deploy platform's own objects, all in schema DEPSYS, grounded in src/deploy_engine.sql. They are the tool's bookkeeping — distinct from the managed application schema ORDDB (section C), which is data the tool operates on.

MIGRATION_LOG — Deploy history (PK LOGID)

FieldTypeMeaning
LOGIDINTSynthetic log id (PK), minted from DEPLOY_SEQ.
VERNOINTMigration version number (monotonic).
MIGNAMEVARCHAR(40)Short migration-step name (e.g. V4_AUDIT_FK_AND_STAGING).
STATUSCHAR(8)OK applied / SKIPPED already applied / FAILED rolled back.
APPLIEDTSTIMESTAMPWhen (DEFAULT CURRENT_TIMESTAMP — one-word spelling, see below).
DETAILVARCHAR(200)Free-text detail written by the runner.
SQLPL-PLAT-DEP-01. The DEFAULT uses the one-word CURRENT_TIMESTAMP spelling deliberately, not the two-word CURRENT TIMESTAMP form: a confirmed platform bug makes the two-word spelling break CREATE TABLE/ALTER TABLE DEFAULT clauses with a raw SQLite syntax error. Both are equally valid Db2-for-i SQL, so this is a faithful workaround (the same spelling is used in ORD_AUDIT.EVTTS).

DEPLOY_CFG — Deploy config key/value (PK CFGKEY)

FieldTypeMeaning
CFGKEYCHAR(20)Config key (PK), e.g. DEPLOY_ENV.
CFGVALVARCHAR(60)Config value, e.g. PRODUCTION.

Written only through SP_APPLY_MIGRATION's idempotent MERGE upsert. A re-applied key returns SKIPPED and does not overwrite the existing value.

DEPLOY_SEQ — Sequence

CREATE SEQUENCE DEPSYS.DEPLOY_SEQ START WITH 1 INCREMENT BY 1 — the generator every runner draws LOGID from via NEXT VALUE FOR, so all MIGRATION_LOG rows share one monotonic id sequence.

CURRENT_VER — Global variable

CREATE VARIABLE DEPSYS.CURRENT_VER INTEGER DEFAULT 0 — the tool's "highest applied version" register. Read as VALUES DEPSYS.CURRENT_VER and set as SET DEPSYS.CURRENT_VER = <n> from top-level SQL only; it is unreachable from inside a routine body (SQLPL-PLAT-DEP-03), so the driver owns advancing it (section B.1).

VERNO_T — Distinct type

CREATE TYPE DEPSYS.VERNO_T AS INTEGER — a distinct type used by the deploy tool's own version-number bookkeeping (and available to the managed schema). Demonstrates the CREATE TYPE ... AS <source> DevOps surface; the managed schema also ships its own distinct type ORDDB.MONEY_T in V3.

The managed schema (ORDDB) — for reference

The tool operates on a small order-management schema, evolved across V1–V6: CUSTOMER (CUSTNO PK, CNAME, CSTAT, +SHIPZIP→SHIPPOSTAL→dropped), PRODUCT (PRODNO PK, PDESC, UNITPRC widened to DECIMAL(11,2)), ORDERS (ORDNO identity PK, CUSTNO FK, ODATE, OSTAT DEFAULT 'O', +FREIGHT), ORDER_LINE (ORDNO+LINENO PK, PRODNO, QTY; deferred FKs added in V4), plus ORD_AUDIT, staging ORD_STAGE, view V_OPEN_ORDERS, alias OORD, function FN_ORDER_TOTAL, procedure SP_ORDER_TOTAL, sequence ORDNO_HINT_SEQ, type MONEY_T, and trigger TR_ORD_STAT. These are the subject of the deploy, not part of the engine.

Relationships

E. Operations Runbook ↑ top

E.1 Run a deploy

  1. Bootstrap the engine (first deploy on a fresh target only): run src/deploy_engine.sql as a batch. Confirm VALUES DEPSYS.CURRENT_VER = 0.
  2. For each version in order (V1→V6):
    1. Run the driver's pre-flight probe for that version (does the target column / type / table already exist?). If it does, log a SKIPPED via SP_LOG_MIGRATION_OK and move on — the step is already applied.
    2. Take a SAVEPOINT (for any risky/structural version).
    3. Run the version's DDL/DML batch against ORDDB.
    4. If clean, CALL DEPSYS.SP_LOG_MIGRATION_OK(verno, migname, detail, ?); read the OUT status; when it is OK, issue SET DEPSYS.CURRENT_VER = verno.
    5. Run the version's catalog verification checks (section F.3).
  3. For a pure config-seed step, call SP_APPLY_MIGRATION(verno, name, cfgkey, cfgval, ?) directly — it does the idempotency check, the MERGE, and the log in one call.

Pre-checks: the deploy job's schema path can resolve both DEPSYS and ORDDB; the engine exists (CURRENT_VER answers). Post-checks (whole deploy):

SELECT MIN(VERNO), MAX(VERNO), COUNT(DISTINCT VERNO)
  FROM DEPSYS.MIGRATION_LOG WHERE STATUS='OK';     -- expect 0|6|7 (clean 0..6, no gaps)
VALUES DEPSYS.CURRENT_VER;                          -- expect the final version (6)

E.2 Idempotent re-deploy

The entire pipeline is safe to re-run end-to-end (e.g. a CI re-trigger). On a second pass:

Verify no drift after a re-run: order totals still 225.00 / 250.00, row counts stable, and CURRENT_VER unchanged at 6.

E.3 Rollback & verification

A structural migration that fails must leave the target exactly as it was. The pattern — proven by the deliberately bad V5 — is SAVEPOINT before, ROLLBACK TO SAVEPOINT on failure, and log FAILED (never OK):

SituationBehaviourAction
A version's DDL batch failsThe driver holds a SAVEPOINT taken before it.ROLLBACK TO SAVEPOINT; CALL SP_LOG_MIGRATION_FAILED. Schema & data are byte-for-byte unchanged; CURRENT_VER does not advance.
Re-run a version already applied OKPre-flight probe / log check sees it.Returns SKIPPED; logs a SKIPPED row; no re-apply. Idempotent.
Re-run the whole pipeline after a CI re-triggerReplaceable objects re-CREATE OR REPLACE; structural steps SKIP.Safe; the log grows only SKIPPED rows and data does not drift.
A CHECK/constraint the existing data violatesThe ADD CONSTRAINT fails on re-validation (like V5_BAD's QTY>0).Roll back, clean the offending data (a real fix migration), then re-add the constraint (V5_FIXED). Log FAILED then OK.
Bad migration corrupted nothing, need proofNothing changed.Re-query column count, row count, the specific bad row, and SYSCST for the would-be constraint — all unchanged / absent. Session still transacts.

Verification after every migration (a real deploy tool's confirm step): query the DB2 catalog to confirm the intended shape landed — new tables in SYSTABLES, new columns in SYSCOLUMNS, sequences at their START in SYSSEQUENCES, types in SYSTYPES, and grants in SYSTABAUTH/SYSCOLAUTH. See F.3 for the exact queries and the two honest catalog gaps (native-table FKs and native UNIQUE/CHECK are enforced but not cataloged).

Because every apply attempt — OK, SKIPPED, or FAILED — is journaled to MIGRATION_LOG with a timestamp and detail, any deploy's history is fully reconstructable after the fact, including the single FAILED V5 attempt that was rolled back.

F. Developer Reference ↑ top

The complete deploy-engine surface, from src/deploy_engine.sql, plus the DevOps SQL exercised by the versioned migrations (src/migrations.mjs) and verified by the driver (test/dep_daily.mjs). Engine objects are in schema DEPSYS.

F.1 Engine procedures (3)

SP_LOG_MIGRATION_OK (IN P_VERNO INTEGER, IN P_MIGNAME VARCHAR(40), IN P_DETAIL VARCHAR(200); OUT P_STATUS CHAR(8))
SELECT COUNT(*) for an existing OK row with the same (VERNO, MIGNAME). If found → P_STATUS='SKIPPED' and insert a SKIPPED row; else P_STATUS='OK' and insert an OK row, LOGID from NEXT VALUE FOR DEPSYS.DEPLOY_SEQ. Deliberately does not touch CURRENT_VER (SQLPL-PLAT-DEP-03 — the caller advances it top-level).
SP_LOG_MIGRATION_FAILED (IN P_VERNO INTEGER, IN P_MIGNAME VARCHAR(40), IN P_DETAIL VARCHAR(200))
Fire-and-forget: insert one MIGRATION_LOG row with STATUS='FAILED'. Called by the driver after its own ROLLBACK TO SAVEPOINT, so the log records a failure that left no schema change.
SP_APPLY_MIGRATION (IN P_VERNO INTEGER, IN P_MIGNAME VARCHAR(40), IN P_CFGKEY CHAR(20), IN P_CFGVAL VARCHAR(60); OUT P_STATUS CHAR(8))
The genuinely self-contained idempotent runner for pure single-table config-seed migrations. Idempotency check on MIGRATION_LOG; if already OK → SKIPPED (log only). Otherwise a MERGE INTO DEPSYS.DEPLOY_CFG upsert (update-if-matched / insert-if-not) of the key/value, then P_STATUS='OK' and an OK log row. Structural versions' bookkeeping is instead split across the two LOG procedures because SQL PL cannot portably EXECUTE IMMEDIATE arbitrary DDL (see A.2).

F.2 The DevOps SQL surface exercised

The versioned migrations deliberately exercise the full schema-evolution surface a real deploy tool must emit. Each is real DDL/DML from src/migrations.mjs, verified in the catalog by the driver.

DDL object creation

SurfaceWhereExample
CREATE SCHEMAV1, engineCREATE SCHEMA ORDDB; / CREATE SCHEMA DEPSYS;
CREATE TABLE (PK/FK/CHECK/IDENTITY/DEFAULT)V1ORDNO INT GENERATED ALWAYS AS IDENTITY, OSTAT ... DEFAULT 'O', inline FK to CUSTOMER.
CREATE [OR REPLACE] VIEWV1/V2/V4CREATE OR REPLACE VIEW ORDDB.V_OPEN_ORDERS AS ... (redeployed as the schema evolves).
CREATE [OR REPLACE] SEQUENCEV1, engineCREATE OR REPLACE SEQUENCE ORDDB.ORDNO_HINT_SEQ START WITH 1000 INCREMENT BY 1;
CREATE [OR REPLACE] FUNCTION / PROCEDUREV1, engineFN_ORDER_TOTAL, SP_ORDER_TOTAL; the three DEPSYS procedures.
CREATE [OR REPLACE] TRIGGERV4TR_ORD_STAT AFTER UPDATE OF OSTAT ... WHEN (N.OSTAT <> O.OSTAT).
CREATE ALIASV2CREATE OR REPLACE ALIAS ORDDB.OORD FOR ORDDB.ORDERS;
CREATE VARIABLE (global)engineCREATE VARIABLE DEPSYS.CURRENT_VER INTEGER DEFAULT 0;
CREATE TYPE (distinct)V3, engineCREATE TYPE ORDDB.MONEY_T AS DECIMAL(11,2); / DEPSYS.VERNO_T AS INTEGER.

ALTER TABLE evolution

SurfaceWhereExample
ADD COLUMN (with/without DEFAULT)V2ADD COLUMN FREIGHT DECIMAL(7,2) NOT NULL DEFAULT 0 (existing rows backfill 0).
ADD CONSTRAINT CHECKV2/V4/V5CK_FREIGHT CHECK (FREIGHT >= 0); CK_QTY_POS CHECK (QTY > 0).
ADD CONSTRAINT UNIQUEV3UQ_PDESC UNIQUE (PDESC).
ADD CONSTRAINT FOREIGN KEYV4FK_OL_ORD ... REFERENCES ORDDB.ORDERS (ORDNO) (deferred; SQLPL-PLAT-DEP-02).
ALTER COLUMN SET DATA TYPEV3ALTER COLUMN UNITPRC SET DATA TYPE DECIMAL(11,2) (data survives).
ALTER COLUMN SET DEFAULTV3ALTER COLUMN UNITPRC SET DEFAULT 0.01.
RENAME COLUMNV3RENAME COLUMN SHIPZIP TO SHIPPOSTAL.
DROP CONSTRAINTV4DROP CONSTRAINT CK_FREIGHT (then add the stronger CK_FREIGHT2).
DROP COLUMNV4DROP COLUMN SHIPPOSTAL (view redeployed first).
RENAME TABLEV4RENAME TABLE ORDDB.ORD_STAGE_TMP TO ORD_STAGE.

Authorization, truncation & teardown

SurfaceWhereExample
GRANT (table / EXECUTE)V1GRANT SELECT, INSERT ON ORDDB.ORDERS TO PUBLIC; GRANT EXECUTE ON PROCEDURE ....
GRANT ... ON SCHEMA ... WITH GRANT OPTIONV1GRANT CREATEIN, ALTERIN, DROPIN ON SCHEMA ORDDB TO QPGMR WITH GRANT OPTION.
Column-level GRANTV3GRANT UPDATE (UNITPRC), REFERENCES (PRODNO) ON ORDDB.PRODUCT TO PUBLIC.
REVOKE (column-level)V6REVOKE UPDATE (UNITPRC) ON ORDDB.PRODUCT FROM PUBLIC.
TRUNCATE TABLEV4TRUNCATE TABLE ORDDB.ORD_STAGE (reset staging between deploys).
TRUNCATE ... RESTART IDENTITYV4TRUNCATE TABLE ORDDB.ORD_AUDIT RESTART IDENTITY (next AUDID = 1).
DROP ... IF EXISTS (RESTRICT)V6DROP TABLE IF EXISTS ORDDB.ORD_STAGE RESTRICT; DROP ALIAS IF EXISTS ORDDB.OORD.
MERGE (idempotent upsert)engineMERGE INTO DEPSYS.DEPLOY_CFG ... WHEN MATCHED THEN UPDATE ... WHEN NOT MATCHED THEN INSERT.
SAVEPOINT / ROLLBACK TO SAVEPOINTdriver (V5)SAVEPOINT SP_V5 ... ROLLBACK TO SAVEPOINT SP_V5.

F.3 Catalog verification queries

After each migration the driver confirms the deployed shape against the DB2 catalog. The real queries:

-- base tables present (V1)
SELECT COUNT(*) FROM QSYS2.SYSTABLES
  WHERE TABLE_SCHEMA='ORDDB' AND TABLE_TYPE='T'
    AND TABLE_NAME IN ('CUSTOMER','PRODUCT','ORDERS','ORDER_LINE');   -- 4
-- a column default (V1)
SELECT COLUMN_DEFAULT FROM QSYS2.SYSCOLUMNS
  WHERE TABLE_SCHEMA='ORDDB' AND TABLE_NAME='ORDERS' AND COLUMN_NAME='OSTAT';
-- sequence at its START (V1)
SELECT START, NEXT_AVAILABLE_VALUE FROM QSYS2.SYSSEQUENCES
  WHERE SEQUENCE_SCHEMA='ORDDB' AND SEQUENCE_NAME='ORDNO_HINT_SEQ';   -- 1000|1000
-- grants (V1): table privileges to PUBLIC
SELECT PRIVILEGE_TYPE FROM QSYS2.SYSTABAUTH
  WHERE TABLE_SCHEMA='ORDDB' AND TABLE_NAME='ORDERS' AND GRANTEE='*PUBLIC';   -- INSERT;SELECT
-- alias (V2), distinct type (V3), column grants (V3)
SELECT TABLE_TYPE FROM QSYS2.SYSTABLES WHERE ... TABLE_NAME='OORD';            -- A
SELECT SOURCE_TYPE FROM QSYS2.SYSTYPES WHERE USER_DEFINED_TYPE_NAME='MONEY_T'; -- DECIMAL...
SELECT COUNT(*) FROM QSYS2.SYSCOLAUTH
  WHERE TABLE_NAME='PRODUCT' AND COLUMN_NAME='UNITPRC' AND PRIVILEGE_TYPE='UPDATE';  -- 1

The engine's own registers are read with VALUES DEPSYS.CURRENT_VER and SELECT ... FROM DEPSYS.MIGRATION_LOG (grouped by STATUS to confirm the OK/SKIPPED/ FAILED counts per version).

Two honest catalog gaps (asserted by the driver, not masked): a FOREIGN KEY added to a native table via ALTER TABLE ADD CONSTRAINT is genuinely enforced but never appears in SYSCST/SYSKEYCST/SYSREFCST (SQLPL-PLAT-DEP-05); and native-table UNIQUE/CHECK constraints are a documented non-cataloged deviation. Where the catalog cannot confirm a constraint, the driver confirms enforcement directly (a violating insert is rejected) and uses MIGRATION_LOG as the idempotency source of truth.

F.4 Idempotency & rollback patterns

F.5 SQLSTATE & platform notes

DEP/i's engine procedures do not raise custom application SQLSTATEs (unlike a business app such as LOANSVC/i): a failed migration surfaces the engine's native SQL error from the failing DDL (e.g. a CHECK re-validation failure, a duplicate-object error, a foreign-key violation), which the driver captures from the batch result and routes to SP_LOG_MIGRATION_FAILED. The outcome is carried in the P_STATUS OUT parameter (OK/SKIPPED) and in MIGRATION_LOG.STATUS. The confirmed platform findings the source works around or asserts:

FindingEffectHandling in DEP/i
SQLPL-PLAT-DEP-01Two-word CURRENT TIMESTAMP breaks DEFAULT clauses.Use the one-word CURRENT_TIMESTAMP spelling (MIGRATION_LOG, ORD_AUDIT).
SQLPL-PLAT-DEP-02A live FK blocks rebuild ALTERs on the referenced table.Defer ORDER_LINE's FKs to V4, after ORDERS/PRODUCT rebuilds finish.
SQLPL-PLAT-DEP-03Global CREATE VARIABLE unreachable from inside a routine body.Runner does not touch CURRENT_VER; the driver advances it top-level.
SQLPL-PLAT-DEP-04INSERT INTO alias (col-list) VALUES fails ("no such table").Assert the bug, then write through the alias with the bare-VALUES form.
SQLPL-PLAT-DEP-05Native-table FK enforced but not visible in SYSCST/SYSKEYCST/SYSREFCST.Assert the gap; confirm enforcement directly via a rejected insert.
(documented deviation)Native-table UNIQUE/CHECK constraints not cataloged.Use MIGRATION_LOG as the idempotency source of truth for V5's guard.

G. Glossary ↑ top

Alias
A second name for a table (CREATE ALIAS OORD FOR ORDERS). Reads and writes route through to the base table; DEP/i uses one to demonstrate the alias DevOps surface (and a confirmed alias insert gap, SQLPL-PLAT-DEP-04).
Catalog (QSYS2.SYS*)
DB2 for i's system catalog views (SYSTABLES, SYSCOLUMNS, SYSSEQUENCES, SYSVARIABLES, SYSTYPES, SYSTABAUTH, SYSCOLAUTH) DEP/i queries to verify a migration deployed the intended shape.
CI/CD deploy tool
Continuous-integration / continuous-delivery tooling that applies schema changes automatically. DEP/i is such a tool for a DB2-for-i schema: it runs versioned migrations, logs them, and is safe to re-trigger.
CURRENT_VER (version register)
A global CREATE VARIABLE holding the highest applied version. Advanced top-level by the driver (not from inside a routine, per SQLPL-PLAT-DEP-03), only forward, only on OK.
Distinct type (CREATE TYPE)
A user-defined type based on a built-in source type (MONEY_T AS DECIMAL(11,2), VERNO_T AS INTEGER), giving a domain a distinct name.
Idempotent
Safe to run again with the same result. DEP/i achieves this per step (log check → SKIPPED, catalog pre-flight guards, CREATE OR REPLACE, MERGE, DROP IF EXISTS) and end-to-end (a full pipeline re-run only adds SKIPPED rows).
MERGE
An upsert: update the row if it matches, insert if not. Makes SP_APPLY_MIGRATION's config write idempotent belt-and-braces.
Migration (versioned)
A numbered schema-change script (V1…V6) applied in order. Each assumes the previous shape and is logged by name and version.
MIGRATION_LOG
The deploy history table: one row per (version, name) attempt with STATUS OK / SKIPPED / FAILED, a timestamp, and a detail — the auditable record and the idempotency source of truth.
Rollback (SAVEPOINT / ROLLBACK TO SAVEPOINT)
A savepoint marks a point in a transaction; rolling back to it undoes everything since, leaving schema and data byte-for-byte unchanged. DEP/i wraps risky migrations this way so a bad step logs FAILED without half-applying.
Sequence (CREATE SEQUENCE)
A monotonic number generator (DEPLOY_SEQ, ORDNO_HINT_SEQ) drawn from via NEXT VALUE FOR.
SKIPPED
The idempotent no-op outcome: a migration already applied OK is not re-applied; a SKIPPED row is logged instead.
SQL PL
SQL Procedural Language — DB2's procedural dialect. DEP/i's runner procedures are SQL PL; the migration DDL is issued as external batches (SQL PL cannot portably EXECUTE IMMEDIATE arbitrary DDL).
TRUNCATE ... RESTART IDENTITY
Empties a table and resets its identity counter to the start (next generated value = 1) — a common CI pattern for resetting a staging area between deploys.