Quickstart
From zero to incremental in a few lines.
OpenIVM is under active testing and currently needs a source build against DuckDB v1.5.4. A DuckDB community extension is planned for the end of 2026.
1
Build
You need git, CMake ≥ 3.5, a C++ compiler, and optionally Ninja for faster builds.
git clone --recurse-submodules https://github.com/ila/openivm.git
cd openivm
GEN=ninja make # release build; plain `make` works without ninja
# The built DuckDB CLI already loads OpenIVM
./build/release/duckdb my.db2
Create a view and refresh it
Create a view, change the base table, refresh. Only the new rows are processed.
CREATE TABLE sales (region VARCHAR, product VARCHAR, amount INT);
INSERT INTO sales VALUES ('US', 'Widget', 100), ('EU', 'Gadget', 200);
CREATE MATERIALIZED VIEW regional_totals AS
SELECT region, SUM(amount) AS total, COUNT(*) AS cnt
FROM sales GROUP BY region;
-- Changes are captured as deltas automatically
INSERT INTO sales VALUES ('US', 'Bolt', 50), ('JP', 'Gear', 300);
DELETE FROM sales WHERE product = 'Gadget';
PRAGMA refresh('regional_totals');
SELECT * FROM regional_totals ORDER BY region;
-- JP | 300 | 1
-- US | 150 | 2 (EU is gone: its only row was deleted)Views with REFRESH EVERY are maintained by a background daemon.
-- Refresh in the background every 5 minutes
CREATE MATERIALIZED VIEW regional_totals REFRESH EVERY '5 minutes' AS
SELECT region, SUM(amount) AS total, COUNT(*) AS cnt
FROM sales GROUP BY region;
PRAGMA refresh_status('regional_totals'); -- interval, last / next refresh
-- Let OpenIVM pick incremental vs full per refresh
SET openivm_refresh_mode = 'auto';
SET openivm_adaptive_refresh = true; -- experimental learned cost model
PRAGMA refresh_cost('regional_totals'); -- incremental vs recompute estimateOver DuckLake, deltas come from native snapshots, with no duplicated storage.
INSTALL ducklake;
LOAD ducklake;
ATTACH ':memory:' AS dl (TYPE ducklake);
CREATE TABLE dl.orders (id INT, product VARCHAR, region VARCHAR, amount INT);
-- Views stack into pipelines; DuckLake snapshots provide the deltas
CREATE MATERIALIZED VIEW dl.product_totals REFRESH EVERY '5 minutes' AS
SELECT product, SUM(amount) AS total, COUNT(*) AS cnt
FROM dl.orders GROUP BY product;
CREATE MATERIALIZED VIEW dl.top_products REFRESH EVERY '10 minutes' AS
SELECT product, total FROM dl.product_totals WHERE total > 1000;
-- Refreshing product_totals also refreshes top_products
SET openivm_cascade_refresh = 'downstream'; -- or 'upstream', 'both', 'off'OpenIVM is a compiler: you can read exactly what it runs.
-- Write every compiled statement to disk
SET openivm_files_path = '/tmp/openivm';
CREATE MATERIALIZED VIEW regional_totals AS
SELECT region, SUM(amount) AS total FROM sales GROUP BY region;
PRAGMA refresh('regional_totals');
-- /tmp/openivm/openivm_compiled_queries_regional_totals.sql (DDL at CREATE time)
-- /tmp/openivm/openivm_upsert_queries_regional_totals.sql (the refresh SQL)
-- Peek at pending changes
SELECT * FROM openivm_delta_sales;The Spark integration, with quickstarts and best practices for dbt workloads, is in development. Open an issue to get involved.
3
Reference
| Pragma | What it does |
|---|---|
| PRAGMA refresh('v') | Refresh a materialized view |
| PRAGMA refresh_status('v') | Refresh interval, last / next refresh, status |
| PRAGMA refresh_cost('v') | Incremental vs full recompute cost estimate |
| PRAGMA refresh_history('v') | Past refreshes (feeds the learned cost model) |
| Setting | Default | Description |
|---|---|---|
| openivm_refresh_mode | incremental | 'incremental', 'full' or 'auto' |
| openivm_cascade_refresh | downstream | Pipeline cascade: 'off', 'upstream', 'downstream', 'both' |
| openivm_adaptive_refresh | false | Pick incremental vs full per refresh with a cost model |
| openivm_disable_daemon | false | Turn off background REFRESH EVERY maintenance |
| openivm_files_path | — | Directory for compiled SQL files |
| openivm_input_dialect | duckdb | Dialect of view bodies, e.g. 'spark' |