How it works

A SQL-to-SQL compiler for incremental computation.

OpenIVM does not replace your engine. It reads your view definition and emits the SQL that keeps it up to date, so the same approach carries from DuckDB to Spark and beyond.

IVM in one equation.

Given a table T and a view V = Q(T), find a function f so that f(ΔT) = ΔV: compute the change to the view from the change to the table alone, instead of re-running Q.

OpenIVM follows DBSP, which gives such an f for arbitrary relational queries using two operators: differentiation D (ΔT = T′ − T) and integration I (T + ΔT = T′). Each operator is replaced by its incremental form, and the plan runs over deltas.

V = π(σ(T))⟶ΔV = π*(σ*(ΔT))

Selection and projection are their own incremental form; a join becomes three joins.

Changes add up: Rnew = Rold + ΔR. For regional_totals (region → total):

V{ US → 100, EU → 200 }
ΔV{ US → +50, JP → +300, EU → −200 }
V + ΔV{ US → 150, JP → 300 }

EU drops out: its only sale was deleted, so its row count reaches zero.

…and every algebraic step is plain SQL

additionUNION ALL+ grouped weight sums
multiplicationJOINweights multiply
integrationMERGE/ INSERT, DELETE

Every change is a weighted row.

A delta table has the same columns as its source, plus a signed weight. An INSERT writes +1, a DELETE writes −1, and an UPDATE writes both (old row −1, new row +1), inside the same transaction as the DML. Operators then work on these weights algebraically: aggregates sum weight × value, joins multiply weights.

Several views can share one delta table; each keeps its own cursor and reads only what it has not yet processed. Over DuckLake, there is no delta table at all: changes are derived from table snapshots, and the pre-refresh state is read through time travel.

SELECT * FROM openivm_delta_sales;

regionproductamountmultiplicity
USBolt50+1
JPGear300+1
EUGadget200−1

After two inserts and one delete. Timestamps omitted. Captured from the DuckDB extension.

A real engine underneath.

Most IVM compilers are detached prototypes that talk to a database through drivers. OpenIVM lives inside one: DuckDB parses, binds and plans the view, so the compiler gets schema validation, cardinality estimates and a cost model for free, and physical choices such as indexes or what to materialize stay tunable.

OpenIVM inside DuckDBA CREATE MATERIALIZED VIEW statement enters DuckDB's parser. OpenIVM's fallback parser strips the materialized-view options and stores metadata. After planning, OpenIVM's optimizer rules rebind scans to delta tables and apply delta rules. The incremental plan either runs natively in DuckDB or is compiled back to SQL for other engines such as PostgreSQL or Spark.DuckDBOpenIVM extensionCREATE MATERIALIZEDVIEW regional_totals …parserplanneroptimizerexecutorfallback parserstrips MV options,stores view metadataIVM optimizer rulesdelta model, lineage,operator rewritingLPTSplan → AST → CTEs→ target dialectrefresh SQLDuckDB · PostgreSQLSpark (soon) · …MV keywordsclean SQLlogical planincremental plannativeplan
OpenIVM is a DuckDB extension: it plugs into the parser and the optimizer without modifying the core engine, then compiles the incremental plan back to SQL through LPTS. Adapted from the SIGMOD 2024 demo paper.
  1. Normalize. Intercept CREATE MATERIALIZED VIEW, take the bound logical plan, and rewrite equivalent shapes into forms the delta rules handle.
  2. Model. Build a delta model: each operator's rule kind, the state it needs, and its lineage, a mapping from an affected output key, group or window partition back to the source columns that can change it.
  3. Rewrite. Turn source scans into deltas and propagate them operator by operator, restricting any recomputation to the exact domain lineage says was touched.
  4. Emit. LPTS (Logical Plan to SQL) turns the rewritten plan into a dialect-independent AST, flattens it into an ordered CTE program, and renders it for the target engine. OpenIVM decides what to compute; LPTS decides how to say it.

What a refresh actually runs.

One view definition becomes several SQL statements: compute ΔV from ΔT, fold ΔV into V, publish the rows that changed, then clear the consumed deltas. Below is what OpenIVM compiles for regional_totals, captured with SET openivm_files_path. Internal tables are named after a key derived from the view's catalog, schema and name, shown as <key>; catalog prefixes and literal timestamps are elided. It is ordinary SQL, which is what makes the approach portable.

A grouped aggregate over one table. This is all you write.

CREATE MATERIALIZED VIEW regional_totals AS
    SELECT region, SUM(amount) AS total, COUNT(*) AS cnt
    FROM sales
    GROUP BY region;

Maintain views across engines.

Modern pipelines span several systems: transactions in one, analytics in another. Because the maintenance steps are SQL, rendered by LPTS in each engine's dialect, they can be shipped to wherever the data lives instead of copying the data to wherever the IVM engine lives.

Cross-system IVM between DuckDB and PostgreSQLThe regional_totals view in DuckDB is defined over the sales table in an attached PostgreSQL instance. Updates to sales are captured in a delta table there. OpenIVM sends compiled queries to PostgreSQL, the incremental result flows back into the view's delta in DuckDB, and is merged into the view.CREATE MATERIALIZEDVIEW regional_totals ASSELECT region,SUM(amount) AS totalFROM p.salesGROUP BY region;DuckDBanalytical engineregional_totalsΔ regional_totalsPostgreSQL pattached transactional enginesalesΔ salescompiled queriesincremental resultmergecaptureupdates
Because the output is SQL, the maintenance steps can run where the data lives. Here PostgreSQL takes the transactional writes and DuckDB keeps the analytical view fresh. Adapted from the SIGMOD 2024 demo paper.

A view that only does the work that changed.

The view below is SELECT region, SUM(amount), COUNT(*) FROM sales GROUP BY region. Changes collect as signed deltas; a refresh folds them in. Insert then delete the same row and the deltas cancel out before any work is done.

Insert or delete a few rows, then refresh.

sales base table

regionamount

Δ sales pending deltas

±regionamount

No pending changes.

regional_totals materialized view

regiontotalcnt