Open-source streaming SQL
Process streaming data with SQL.
Read events from Kafka and other sources, keep SQL results current as they arrive, and send them to a sink or query them directly.
- 01 / Source
- 02 / Query
- 03 / Result
- Rust implementation
- Apache Arrow data format
- DataFusion query engine
- Apache 2.0 engine license
The platform
Sources, queries,
and results.
Every pipeline reads from a source, runs a continuous query and delivers a result. What each connector and deployment mode supports is listed beside it in the docs.
- 01 Continuous queries Filter, aggregate and join events in SQL. Tumbling, hopping and session windows close on event time, once the watermark passes the end of the window.
- 02 Sources and sinks Kafka, PostgreSQL and MongoDB change streams, Delta Lake, Iceberg, NATS and local files, among others. Each connector states its delivery guarantee and the modes it runs in.
- 03 Results you can query Keep a result as a materialized view and read it with SQL over HTTP, or subscribe to a stream over WebSocket. Materialized views run embedded or on a single server.
- 04 Checkpoints and delivery State and source positions are checkpointed, and a restart resumes from the last committed checkpoint. Exactly-once output needs a replayable source and a sink that commits with the checkpoint; other pairs are at-least-once.
- 05 Deployment choices Embed the engine or run a server. Cluster support has mode-specific limits on sources, SQL and delivery.
How it works
A pipeline is three
SQL statements.
CREATE SOURCEnames the topic, its columns and the watermark: how long to wait for events that arrive late.CREATE STREAMis the continuous query. This one totals each symbol's volume per minute and emits a row when the watermark passes the end of the minute.CREATE SINKwrites every emitted row to another Kafka topic.
CREATE SOURCE trades (
symbol VARCHAR NOT NULL,
price DOUBLE NOT NULL,
volume BIGINT NOT NULL,
ts TIMESTAMP NOT NULL,
WATERMARK FOR ts AS ts - INTERVAL '5' SECOND
) FROM KAFKA (
'bootstrap.servers' = 'kafka:9092',
'topic' = 'trades',
'group.id' = 'minute-volume',
'startup.mode' = 'earliest'
) FORMAT JSON;
CREATE STREAM minute_volume AS
SELECT symbol,
tumble(ts, INTERVAL '1' MINUTE) AS minute,
SUM(volume) AS volume,
MAX(price) AS high
FROM trades
GROUP BY symbol, tumble(ts, INTERVAL '1' MINUTE)
EMIT ON WINDOW CLOSE;
CREATE SINK minute_volume_out FROM minute_volume
INTO KAFKA ('bootstrap.servers' = 'kafka:9092', 'topic' = 'minute-volume')
FORMAT JSON;
{"symbol":"AAPL","minute":"2026-10-18T09:30:00","volume":150,"high":189.7}
{"symbol":"AAPL","minute":"2026-10-18T09:31:00","volume":70,"high":189.6}
{"symbol":"MSFT","minute":"2026-10-18T09:30:00","volume":30,"high":411.2}
The last trade was MSFT at 09:32:30, so the watermark stands at 09:32:25 and the 09:32 window stays open until a later event arrives. Run this example with the steps in the README.
Deployment
One engine, three ways to run it.
-
Embedded
Add the
laminar-dbcrate to a Rust program. Pipelines run inside your process and checkpoint to a local directory. -
Single server
Run the
laminardbbinary or Docker image with a TOML file. It serves SQL over HTTP, WebSocket subscriptions and Prometheus metrics, with an optional PostgreSQL wire endpoint. -
Cluster
Several servers share checkpoints in S3, Google Cloud Storage or Azure storage and find each other by gossip or a static list. Sources must be Kafka, and materialized views and AI functions are not available.
Quick start
Run a continuous
query locally.
You need Docker. The built-in generator source emits numbered rows at a fixed rate, so nothing else has to be running.
The view keeps its totals current as rows arrive. Query it twice a few seconds apart and the numbers move. Counts come back as strings because 64-bit integers do not fit in JavaScript numbers.
sql = '''
CREATE SOURCE ticks FROM GENERATOR ('rows.per.second' = '10');
CREATE MATERIALIZED VIEW tick_totals AS
SELECT COUNT(*) AS rows_seen, MAX(seq) AS last_seq
FROM ticks;
'''
[server]
mode = "single"
bind = "0.0.0.0:8080"
console_token = "${LAMINAR_CONSOLE_TOKEN}"
[checkpoint]
url = "file:///var/lib/laminardb/checkpoints"
interval = "10s"
export LAMINAR_CONSOLE_TOKEN="$(openssl rand -hex 32)"
docker run --rm -d --name laminardb -p 127.0.0.1:8080:8080 \
-e LAMINAR_CONSOLE_TOKEN \
-v "$PWD/laminardb.toml:/etc/laminardb/laminardb.toml:ro" \
laminardb/laminardb-server:0.33.0
curl -s http://localhost:8080/api/v1/sql \
-H "Authorization: Bearer $LAMINAR_CONSOLE_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"sql": "SELECT * FROM tick_totals"}'
{"result_type":"query","data":[{"rows_seen":"46","last_seq":"45"}]}
Stop it with docker stop laminardb. The commands are for a Bash-compatible shell.
LaminarDB Console
Work with your running system.
The Console is a browser app for a LaminarDB server you run. Browse sources, streams and sinks, run SQL, follow checkpoints and Prometheus metrics, and see how objects connect. It calls the server's HTTP API with your console token.
LaminarDB is pre-1.0. Test your workload, delivery requirements and recovery path before relying on it in production. Read the production guidance.