Skip to content

Archive compiled CREATE and refresh SQL in metadata tables (#83) - #102

Open
ila wants to merge 9 commits into
mainfrom
agent-mistress/live-75440dcf5ff2
Open

ila wants to merge 9 commits into
mainfrom
agent-mistress/live-75440dcf5ff2

Conversation

@ila

@ila ila commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Implements #83 (automatic archive of compiled SQL in metadata tables). Draft: not ready to merge until two independent feature reviews, any repairs, exact-revision validation and all CI workflows are done.

What it does

  • Always-on archive. Every CREATE MATERIALIZED VIEW and executed refresh program is stored in openivm_compiled_programs (one row per version) and openivm_compiled_statements (statements in execution order). The tables sit in the view's metadata catalog/schema, are created through the existing metadata init/migration path, and are independent of openivm_files_path. File export still works and can be used alongside the archive.
  • Versioning. Programs are keyed by the qualified MV identity (view_name key plus catalog, schema and SQL name) and by operation (create/refresh). A new version is stored only when the complete statement list differs verbatim from the latest stored version. Nothing is normalized; all versions are kept, including across DROP and recreate. Refresh ids match openivm_refresh_profile.refresh_id.
  • Outcomes.
    • committed: the transaction committed.
    • attempted: the run failed and rolled back.
    • unknown: a cross-catalog refresh failed after data statements ran, or COMMIT itself failed.
    • Compile-only calls (openivm_compile_with_facts) are never archived.
  • Transaction boundaries.
    • Native CREATE and autocommit refresh: the archive write is inside the operation's transaction. If the write fails, the operation rolls back with an error.
    • Explicit transactions: the write is part of the caller's program, so it rolls back with the caller.
    • Failed autocommit refresh: one separate metadata transaction records attempted/unknown. If that write also fails, the error says so.
    • DuckLake refresh and CREATE OR REPLACE: one metadata transaction after the commit. If it fails, the operation returns an error that says it committed, never plain success.
  • Batching. Writes are batched per operation; there is never a commit per statement. Connection-executed writes use bound parameters, because DuckDB parses long SQL literals slowly (~5 ms per 7 KB).

Behaviour to be aware of

  • Native incremental refresh programs embed the delta cutoff as a timestamp literal, and DuckLake programs embed snapshot ids. Without normalization, practically every successful incremental refresh stores a new version, about 7 KB in the benchmark workload. Retention is unbounded by design; prune with SQL as documented.
  • Unchanged incremental programs do recur when the state is unchanged, for example a retry after a failed attempt. Full-recompute programs also repeat, reusing one version.

Tests (all through the real archive paths)

All on the final binary:

  • metadata.test: 396 assertions.
  • transactional_lifecycle.test: 546 assertions.
  • ducklake_deltas.test: 238 assertions.

They cover CREATE, repeated refreshes, an unchanged program run twice (one version, text unchanged), changed programs (new version, with text and order asserted), full refresh, skipped refresh, compile-only, file export on/off, qualified and attached-catalog names, failure/rollback, explicit transactions, archive-write failures (native, DuckLake refresh, DuckLake replace), reopen, migration, and DROP/recreate.

CI on head 8cc4f984: all workflows pass (Linux amd64/arm64, macOS amd64/arm64, Windows MSVC/MinGW, Wasm ×3, Format, Tidy). The first CI run (47d82787) failed only on Windows, at one new assertion: the reference file is written in text mode (CRLF), so the comparison now normalizes the file's newlines. The archived text is unchanged.

Local full suite on the final revision: 91/92 test files passed (14,214 assertions); the remaining file, refresh_timezone.test, was skipped because it requires icu. Both integration scripts pass. It ran per file in batches because one monolithic run exceeded the session's 2-hour limit on a loaded machine.

Benchmark (loaded machine, noisy)

  • Workload: 200 × (insert 100 rows + refresh), interleaved against the previous-commit binary.
  • Wall time: mean 23.2 s vs 26.7 s (about +15%). The profiler shows the archive step at 8–12 ms per refresh, out of 110–120 ms.
  • window_running_aggregate_incremental.test: CPU time 22.4 s vs 27.8 s; wall time unchanged.

Found, not fixed (out of scope)

Dropping and recreating an aggregate MV after a database reopen fails with Could not commit creation of dependency ... openivm_index has been deleted. It reproduces identically on the baseline binary.

🤖 Generated with Claude Code

Reviewed head: 2c94d92; base: 9f65757
Independent review and configured validation passed.

Performance comparisons: not applicable to this non-performance task.

Required runtime checks incomplete: timeout is non-blocking, not a pass.

Two independent fresh reviewer sessions passed this exact revision.
Validation scope: configured command only; no additional runtime coverage claimed
Validation command: ["/Users/ila/Code/agent-mistress/.venv/bin/python", "/Users/ila/Code/agent-mistress/scripts/general_validation.py", "9f657576d1e47440d7e62de1befd47bf76b4a83e"]

Fixes #83

ila and others added 8 commits October 5, 2026 07:35
OpenIVM now always records the programs it runs for CREATE MATERIALIZED VIEW
and refresh in openivm_compiled_programs / openivm_compiled_statements, in the
view's metadata catalog, independently of openivm_files_path.

- A program is the complete statement list in execution order, keyed by the
  qualified MV identity and operation. A new version is stored only when the
  list differs verbatim from the latest stored version; state-specific cutoffs
  and snapshot IDs are not normalized. All versions are retained.
- Outcomes: committed, attempted (failed and rolled back) and unknown
  (cross-catalog refresh failed after data statements ran, or COMMIT failed).
  Compile-only calls are never archived.
- Native CREATE/refresh write the archive in their own transaction; explicit
  transactions include it in the caller's program. DuckLake refreshes and
  replacements record it in one metadata transaction after they commit and
  report an error, not success, if that write fails.
- Connection-executed archives use bound parameters; DuckDB parses long SQL
  literals slowly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reference file is written in text mode, so Windows translates newlines to
CRLF. Undo that before checking that the file holds the archived statements in
order; the archived text itself is unchanged.

Co-Authored-By: Claude Opus 5.5 <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.

Feature: automatically archive compiled SQL statements in metadata tables

1 participant