Skip to content

Add apply mode for declarative, idempotent CREATE DDL#230

Open
ryannedolan wants to merge 1 commit into
linkedin:mainfrom
ryannedolan:rdolan/apply-mode-ddl
Open

Add apply mode for declarative, idempotent CREATE DDL#230
ryannedolan wants to merge 1 commit into
linkedin:mainfrom
ryannedolan:rdolan/apply-mode-ddl

Conversation

@ryannedolan
Copy link
Copy Markdown
Collaborator

@ryannedolan ryannedolan commented May 21, 2026

Summary

  • Adds a mode JDBC connection property: create (default, strict) or apply (declarative, idempotent).
  • In apply mode, plain CREATE converges the resource to the declared definition rather than failing on conflict — so checked-in .sql files become K8s-style reconcilable manifests for CI/CD.
  • Routes all five CREATE forms (VIEW, MATERIALIZED VIEW, TABLE, TRIGGER, DATABASE) through a single HoptimatorDdlUtils.effectiveMode(orReplace, conn) helper that resolves the connection mode and the statement's OR REPLACE flag into a DdlMode.
  • Existence-checks in processCreateMaterializedView / processCreateTable now key off the resolved DdlMode. SPECIFY (dry-run) preserves its original syntax-driven preview so !specify still reflects what a real run would do.
  • Data is never destroyed by any CREATE form. DROP stays imperative and metadata-only; prune-by-absence is explicitly out of scope.
  • Destructive-metadata-change detection (FORCE semantics) is deferred to a follow-up; in apply mode today, CREATE and CREATE OR REPLACE are equivalent because the deployer can't yet distinguish compatible vs. incompatible diffs.

Usage:

jdbc:hoptimator://...;mode=apply
Mode CREATE CREATE OR REPLACE
create (default) Fail if the resource exists. Update if it exists.
apply Converge. Idempotent. Same as CREATE.

Testing Done

  • Unit tests added — 7 new JUnit5 tests for effectiveMode / isApplyMode covering default, explicit create, apply, case insensitivity, unknown values, and null properties.
  • One pre-existing unit test that bypassed the executor was updated to pass DdlMode.UPDATE directly when supplying OR REPLACE SQL — the helper now treats DdlMode as authoritative.
  • New Quidem integration script k8s-apply-mode.id exercising idempotent re-CREATE for views and tables, the OR-REPLACE-equals-CREATE invariant under apply, and DROP still working. Wired into TestSqlScripts via run("k8s-apply-mode.id", "mode=apply").
  • ./gradlew :hoptimator-jdbc:test — 508 tests pass.
  • ./gradlew compileTestJava — all modules compile clean.
  • make integration-tests — deferred to CI (needs the K8s cluster).

Docs

docs/user-guide/ddl-reference.md gets a new "CREATE semantics" section describing the two modes, the JDBC property, the data-safety guarantee, and the out-of-scope list.

Introduces a `mode` connection property on the JDBC driver that
selects between the existing strict CREATE semantics (`mode=create`,
the default) and a new K8s-style apply mode (`mode=apply`). In apply
mode, plain `CREATE` converges the resource to the declared
definition rather than failing when it already exists, making
checked-in `.sql` files idempotent and reconcilable from CI.

The dispatch is centralized in a new `HoptimatorDdlUtils.effectiveMode`
helper that combines the statement's `OR REPLACE` flag with the
connection mode and resolves to `DdlMode.CREATE` or `DdlMode.UPDATE`.
All five `CREATE` execute paths (VIEW, MATERIALIZED VIEW, TABLE,
TRIGGER, DATABASE) route through it, and the existence-checks inside
`processCreateMaterializedView` / `processCreateTable` now key off
the resolved `DdlMode` so apply-mode plain CREATEs no longer hit the
"already exists" error. SPECIFY (dry-run) preserves its original
syntax-driven semantics so `!specify` previews still match what a
real run would do.

`CREATE OR REPLACE` keeps converging behavior in both modes;
destructive metadata-change detection (FORCE semantics) is left for a
follow-up once per-resource diff policy lands. Data is never
destroyed by any CREATE form — that safety guarantee is what makes
apply mode usable in production.

DROP remains strictly imperative; prune-by-absence is explicitly out
of scope.

Tested:
  - New JUnit5 coverage for `effectiveMode` / `isApplyMode` across
    default, explicit `create`, `apply`, case variants, unknown
    values, and null properties.
  - New Quidem integration script `k8s-apply-mode.id` exercising
    idempotent plain CREATE for views and tables, and DROP under
    apply mode (wired via `run(..., "mode=apply")`).
  - Adjusted one pre-existing unit test that bypassed the executor
    to pass `DdlMode.UPDATE` directly when supplying `OR REPLACE`
    SQL — the helper now treats `DdlMode` as authoritative.

Docs: `docs/user-guide/ddl-reference.md` describes the two modes,
the JDBC property, the data-safety guarantee, and the out-of-scope
list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant