# KalamDB: full documentation corpus
> Single-file Markdown corpus of current KalamDB and KoldStore documentation for
> LLM context windows, AI search engines, and developer agents. Prefer this file
> when you need dense technical detail in one request. Prefer
> [https://kalamdb.org/llms.txt](https://kalamdb.org/llms.txt) for a short index.
- Site: https://kalamdb.org
- Index: https://kalamdb.org/llms.txt
- Sitemap: https://kalamdb.org/sitemap.xml
- Generated: 2026-07-31
- Scope: current docs under `content/` (archived version folders omitted)
- License: Apache 2.0 for KalamDB / KoldStore product docs on this site
## How to use this file
- Read the index at `/llms.txt` first for routing.
- Use this corpus for install steps, SQL APIs, architecture, and SDK reference.
- Cite canonical page URLs included under each section header.
- KoldStore (PostgreSQL tiered storage) lives under `/docs/pg-koldstore`.
- pg_kalam (FDW to KalamDB) lives under `/docs/pg-kalam` — different product.
================================================================================
# Documentation Home
URL: https://kalamdb.org/docs
Source: content/index.mdx
> Browse KalamDB documentation by product area: server, pg_kalam, pg Koldstore, TypeScript SDK, Rust SDK, Dart SDK, and use cases.
# KalamDB Documentation
KalamDB docs are organized by the part of the stack you are working with. Start with the server docs for running KalamDB and SQL, then jump into an SDK, a PostgreSQL extension, or implementation patterns.
## Choose A Docs Area
- [Server](https://kalamdb.org/docs/server): run KalamDB, configure auth, use SQL, operate storage, and deploy production services.
- [CLI](https://kalamdb.org/docs/cli): install `kalam`, scaffold projects with `kalam init`, run `kalam dev`, manage migrations, and use the SQL client.
- [pg_kalam PostgreSQL Extension](https://kalamdb.org/docs/pg-kalam): connect PostgreSQL to KalamDB through the pg_kalam extension.
- [pg Koldstore](https://kalamdb.org/docs/pg-koldstore): tier hot PostgreSQL rows and cold Parquet on one table — install, manage, flush, merge scan, and `changes_since`.
- [TypeScript SDK](https://kalamdb.org/docs/ts-sdk): build browser apps, services, workers, and agents with the TypeScript packages.
- [Rust SDK](https://kalamdb.org/docs/rust-sdk): native async client for services and workers — install [`kalam-client`](https://crates.io/crates/kalam-client) from crates.io.
- [Dart / Flutter SDK](https://kalamdb.org/docs/dart-sdk): connect Flutter and Dart apps to KalamDB with realtime subscriptions.
- [Use Cases](https://kalamdb.org/docs/use-cases): runnable TypeScript example apps from the `examples/` folder.
## Start Here
1. [Run KalamDB locally in minutes](https://kalamdb.org/docs/server/getting-started)
2. [Create tables and query data with SQL](https://kalamdb.org/docs/server/sql-reference)
3. [Connect a TypeScript app or service](https://kalamdb.org/docs/ts-sdk/setup)
4. [Install the Rust SDK from crates.io](https://kalamdb.org/docs/rust-sdk/setup) and try the [Quickstart example](https://kalamdb.org/docs/rust-sdk/examples/quickstart)
5. [Use pg_kalam for PostgreSQL-facing workflows](https://kalamdb.org/docs/pg-kalam/getting-started)
6. [Try pg Koldstore for tiered hot/cold tables](https://kalamdb.org/docs/pg-koldstore/getting-started)
7. [Review production security settings](https://kalamdb.org/docs/server/security)
================================================================================
# Benchmarks
URL: https://kalamdb.org/docs/pg-koldstore/benchmarks
Source: content/pg-koldstore/benchmarks.mdx
> Published KoldStore storage and VACUUM FULL wins on a 10M-row sample, plus contextual DML and PK lookup trade-offs.
# Benchmarks
KoldStore is a **storage lifecycle** tool — not a universal query accelerator.
The primary published wins are footprint and whole-table maintenance after
flush. DML and cold-path lookups are contextual single samples, not release
guarantees.
Source sample: 10M wide rows, `hot_row_limit = 100000`, local PostgreSQL 16.13,
2026-07-31, single wiped pgrx instance per side (draft publication methodology
in the [koldstore repo](https://github.com/kalamdb/koldstore/tree/main/docs/benchmarks)).
## Storage and maintenance wins
| Result | Before → after flush | Trade-off |
|--------|----------------------|-----------|
| Total footprint (hot + cold) | 5.85 GiB → 671 MiB | **89%** smaller |
| Hot in PostgreSQL (heap + `__cl`) | 5.85 GiB → 72 MiB | **99%** smaller |
| Cold Parquet | — → ~599 MiB | Outside the database |
| Indexes (hot + `__cl`) | 415 MiB → 11.5 MiB | **97%** smaller |
| `VACUUM (FULL, ANALYZE)` | 227.02 s → 3.21 s | **~71×** faster |
The VACUUM timing is specifically `VACUUM (FULL, ANALYZE)` — a whole-table
rewrite. Routine autovacuum was disabled for the benchmark and is not covered
by that number.
## DML and query path (contextual)
Foreground DML and PK lookups are **not** guaranteed to improve.
| Operation | PostgreSQL only | KoldStore (WAL) | Notes |
|-----------|-----------------|-----------------|-------|
| INSERT | 47,202 ops/s | 101,325 ops/s | Noise / order — not a product win |
| UPDATE | 63,453 ops/s | 55,350 ops/s | Single sample; ~13% lower |
| DELETE | 36,905 ops/s | 140,874 ops/s | Do not claim faster from one sample |
| Hot-only PK lookup | 3,990 ops/s | 3,818 ops/s | ≈ same |
| Hot+cold PK lookup | 4,204 ops/s | 1,055 ops/s | ~75% slower vs full-heap baseline |
| Cold-only PK lookup | 4,182 ops/s | 653 ops/s | ~84% slower vs full-heap baseline |
Cold-path lookups compare Parquet merge against a full-heap PostgreSQL
baseline. Treat them as indicative.
## How to read the numbers
- Lead with storage + VACUUM when explaining the product
- Do not market single-sample INSERT/DELETE “speedups”
- Expect cold PK latency until cold lookup work lands on the roadmap
- Results vary by schema, hardware, storage backend, and workload
Product page summary: [/koldstore](https://kalamdb.org/koldstore).
================================================================================
# Change Feed
URL: https://kalamdb.org/docs/pg-koldstore/change-feed
Source: content/pg-koldstore/change-feed.mdx
> Use koldstore.changes_since for exclusive seq catch-up across hot and cold, including last_rows newest-N rewind.
# Change Feed
`koldstore.changes_since` is the packaged SQL change API for managed tables. It
merges the hot change-log mirror and cold Parquet into one **latest-state**
stream ordered by `seq`.
This is catch-up for clients (and aligns with KalamDB subscribe semantics for
`from` / newest-N). It is **not** a full temporal audit log of every historical
event.
## Signature
```sql
koldstore.changes_since(
table_name regclass,
since_seq bigint DEFAULT 0, -- exclusive resume (seq > since_seq)
limit_rows integer DEFAULT 1000, -- forward page size
last_rows integer DEFAULT NULL -- newest-N rewind when since_seq = 0
)
RETURNS TABLE (
seq bigint,
op smallint, -- 1=INSERT, 2=UPDATE, 3=DELETE
pk jsonb,
deleted boolean,
row_image jsonb,
source text -- 'hot' | 'cold'
)
```
## Modes
| Mode | Arguments | Behavior |
|------|-----------|----------|
| Resume | `since_seq > 0` | Rows with `seq > since_seq`, ascending, capped by `limit_rows`. **`last_rows` is ignored.** |
| Rewind | `since_seq = 0` and `last_rows` set | Newest N retained changes, delivered **oldest → newest** |
| From start | `since_seq = 0`, no `last_rows` | Start of retained history |
Precedence rule: a positive `since_seq` always wins over `last_rows`.
## Examples
Fence first when you need a strong boundary against the heap:
```sql
SELECT koldstore.wait_for_async_mirror();
```
### Page from the beginning
```sql
SELECT seq, op, pk, deleted, source
FROM koldstore.changes_since(
table_name => 'app.messages'::regclass,
since_seq => 0,
limit_rows => 100
);
```
### Resume after the last consumed seq
```sql
SELECT seq, op, pk, deleted, source
FROM koldstore.changes_since(
table_name => 'app.messages'::regclass,
since_seq => 184467440737095516, -- last seq you already applied
limit_rows => 500
);
```
Advance your cursor from the **highest** `seq` you successfully processed.
### Newest N (subscribe-style rewind)
```sql
SELECT seq, op, pk, deleted, source
FROM koldstore.changes_since(
table_name => 'app.messages'::regclass,
since_seq => 0,
limit_rows => 1000,
last_rows => 50
);
```
`last_rows` must be `<= limit_rows`.
### After flush
The same cursor continues to return flushed rows from cold (`source = 'cold'`).
Do not treat “mirror empty” as “caught up” if cold still holds retained history.
## Semantics and caveats
- **Exclusive** cursor: `seq > since_seq`, never inclusive.
- Authoritative `seq` values come only from the serialized WAL applier.
- Latest-state per PK — updates replace prior images; deletes appear as tombstones until pruned by flush retention rules.
- `since_seq = 0` means “start of retained history,” not “I fell behind retention.”
- A **positive** `since_seq` older than the retained cold floor raises a **retention-gap error** — do not treat an empty page as success in that case.
- Hot + cold merge; `source` tells you which tier contributed the row image.
## Direct mirror SQL (advanced)
You can inspect the hot mirror for debugging:
```sql
SELECT id, seq, op
FROM koldstore.messages__cl
WHERE seq > $1
ORDER BY seq
LIMIT 100;
```
Prefer `changes_since` in application code so cold history stays visible after
flush and retention-gap errors stay consistent.
## Next
- [SQL API Reference](https://kalamdb.org/docs/pg-koldstore/sql-api)
- [Under the Hood](https://kalamdb.org/docs/pg-koldstore/concepts)
================================================================================
# Under the Hood
URL: https://kalamdb.org/docs/pg-koldstore/concepts
Source: content/pg-koldstore/concepts.mdx
> How KoldStore captures committed WAL into a latest-state mirror, flushes to Parquet, and merges hot and cold with KoldMergeScan.
# Under the Hood
KoldStore keeps PostgreSQL authoritative for the hot tier and stores flushed
history as open Parquet plus catalog metadata. This page explains the moving
parts so API behavior makes sense.
## End-to-end path
```text
heap commit
→ logical slot (PK-only pgoutput)
→ database worker
→ koldstore.
__cl (latest-state mirror)
→ flush (policy or force)
→ Parquet segments + manifest
→ SELECT via KoldMergeScan (newest visible wins)
```
There is **no** trigger/strict capture path today. Foreground DML writes the
heap only; the serialized WAL applier allocates authoritative `seq` values.
## Hot tier
- Ordinary heap table with a primary key
- Application columns only (clean schema — no KoldStore columns on the user table)
- Native indexes cover **hot rows only** after flush
- Transactions, locking, MVCC, RLS, and permissions stay PostgreSQL-native
## Change-log mirror (`koldstore.__cl`)
One row per primary key (latest state), not an append-only event log:
| Column | Meaning |
|--------|---------|
| PK columns | Same as the heap |
| `seq` | Snowflake effect id — ordering / flush cutoffs / change cursor |
| `op` | `1` = INSERT, `2` = UPDATE, `3` = DELETE |
| `commit_lsn` | Diagnostics only — not the public resume cursor |
Tombstones (`op = 3`) remain until flush prunes them. `seq` is **not** a commit
LSN and is not a commit-order guarantee beyond “allocated by the serialized
applier.”
## WAL capture consistency
| Mode | Behavior |
|------|----------|
| Normal | Worker polls (~100 ms) and applies available WAL |
| Strong fence | `koldstore.wait_for_async_mirror()` drains to a boundary |
| Flush | Calls the same fence automatically |
Lag is expected. Call the fence before comparing heap state to mirror/`seq`
cursors when you need a strong boundary.
Other capture rules:
- Primary-key `UPDATE`s are rejected (BEFORE UPDATE OF pk guard)
- `TRUNCATE` on async managed tables is rejected
- Publication `koldstore_async_mirror` + one deterministic logical slot per database are auto-managed
## Cold tier
- Parquet segments under a registered storage backend
- Manifest metadata in PostgreSQL catalogs (`koldstore.manifest`, `koldstore.cold_segments`, …)
- Object storage is **not** WAL-protected — back up PostgreSQL **and** the cold prefix together
Default path templates:
- Shared: `{namespace}/{tableName}/`
- User-scoped (intended): `{namespace}/{tableName}/{scopeId}/`
Scoped cold folders and full per-`user_id` physical layout are still hardening;
session `koldstore.user_id` is required for user-typed tables today.
## Flush policy (intuition)
Example: `hot_row_limit = 10000`, `min_flush_rows = 1000`, `max_rows_per_file = 1000`
| Mirror rows | Result |
|-------------|--------|
| 10,505 | No flush (505 excess < min) |
| 11,000 | Flush 1,000 → 1 file |
| 11,250 | Flush 1,000, keep 10,250 hot |
Age policy uses `koldstore_move_after` (for example `'90 days'`) measured from
mirror `seq`. It is mutually exclusive with `hot_row_limit`.
`koldstore_move_when` is reserved and fails closed.
## KoldMergeScan
```text
KoldMergeScan
├── native hot child (Index / Bitmap / Seq Scan)
├── KoldParquetScan
└── MirrorOverlay (inserts / updates / tombstones)
```
Contracts that matter for operators:
- Cold-capable predicates → `KoldMergeScan` with hot child + planned cold access
- Hot-only / cold-proven-empty PK lookups may keep a **native** plan with **no** wrapper (intentional performance path)
- `koldstore.enable_merge_scan = off` → **ERROR** at execution (never silent hot-only)
- Unavailable cold storage → query **fails** (no partial hot-only results)
## Catalog touchpoints
Useful relations/functions while debugging:
- `koldstore.schemas` — managed table options
- `koldstore.jobs` — migrate / flush progress
- `koldstore.describe_table(...)` — hot/mirror/cold counts, sizes, pending jobs
- `koldstore.async_mirror_status()` — slot lag, retained bytes, health
## Next
- [Manage & Flush](https://kalamdb.org/docs/pg-koldstore/manage-and-flush) — how to drive policy and jobs
- [Change Feed](https://kalamdb.org/docs/pg-koldstore/change-feed) — exclusive `seq` catch-up
- [SQL API Reference](https://kalamdb.org/docs/pg-koldstore/sql-api) — full function list
================================================================================
# Getting Started
URL: https://kalamdb.org/docs/pg-koldstore/getting-started
Source: content/pg-koldstore/getting-started.mdx
> Register storage, manage a heap table, fence the async mirror, flush to Parquet, query one table, and page the change feed.
# Getting Started
This walkthrough creates a managed table on local filesystem storage, flushes
excess rows to Parquet, and shows both merge-scan reads and the change feed.
Prerequisites: [Installation](https://kalamdb.org/docs/pg-koldstore/installation) completed —
`shared_preload_libraries` includes `koldstore`, `wal_level=logical`, and
`CREATE EXTENSION koldstore` succeeded.
## 1. Register storage
```sql
SELECT koldstore.register_storage(
name => 'local-dev',
storage_type => 'filesystem',
base_path => '/tmp/koldstore-demo',
credentials => '{}'::jsonb,
config => '{}'::jsonb
);
```
`storage_type` may also be `s3`, `gcs`, or `azure`. For MinIO-style S3:
```sql
SELECT koldstore.register_storage(
name => 'minio',
storage_type => 's3',
base_path => 'koldstore-bucket',
credentials => '{"access_key_id":"minioadmin","secret_access_key":"minioadmin"}'::jsonb,
config => '{"endpoint":"http://127.0.0.1:9000","region":"us-east-1","path_style":true}'::jsonb
);
```
## 2. Create a normal heap table
Managed tables need a **primary key**. Supported types today include `boolean`,
integers, `real`, `double precision`, `text`, `varchar`, `uuid`, `jsonb`, and
`timestamptz`.
```sql
CREATE SCHEMA IF NOT EXISTS app;
CREATE TABLE app.messages (
id bigint PRIMARY KEY,
body text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO app.messages (id, body)
SELECT gs, 'row ' || gs
FROM generate_series(1, 1012) AS gs;
```
## 3. Manage the table
Two equivalent styles:
**A. `manage_table` (structured options)**
```sql
SELECT koldstore.manage_table(
table_name => 'app.messages',
storage => 'local-dev',
hot_row_limit => 1000,
min_flush_rows => 1,
max_rows_per_file => 500,
migration_order_by => 'id'
);
```
**B. `ALTER TABLE` options**
```sql
ALTER TABLE app.messages SET (
koldstore_enabled = true,
koldstore_storage = 'local-dev',
koldstore_hot_row_limit = 1000,
koldstore_min_flush_rows = 1,
koldstore_max_rows_per_file = 500
);
```
Do **not** turn management off with `koldstore_enabled = false` — use
`koldstore.unmanage_table(...)` instead. Storage cannot be changed after manage.
With `hot_row_limit` set, `manage_table` rejects non-PK `UNIQUE` constraints and
foreign keys (those stay hot-only after flush). See
[Status & Limits](https://kalamdb.org/docs/pg-koldstore/limitations).
## 4. Fence the async mirror
Foreground DML writes the heap only. A database worker applies committed WAL
into the latest-state mirror (`koldstore.messages__cl`) with short lag. Before
work that must observe every commit visible at the start of the call:
```sql
SELECT koldstore.wait_for_async_mirror();
```
`flush_table` runs this fence automatically.
## 5. Page the change feed
```sql
SELECT seq, op, pk, deleted, source
FROM koldstore.changes_since(
table_name => 'app.messages'::regclass,
since_seq => 0,
limit_rows => 100
);
```
Resume with an exclusive cursor (`seq > since_seq`). Or rewind to the newest N
rows (delivered oldest → newest) when `since_seq = 0`:
```sql
SELECT seq, op, pk, deleted, source
FROM koldstore.changes_since(
table_name => 'app.messages'::regclass,
since_seq => 0,
limit_rows => 1000,
last_rows => 50
);
```
Full semantics: [Change Feed](https://kalamdb.org/docs/pg-koldstore/change-feed).
## 6. Flush excess rows to cold storage
```sql
SELECT koldstore.flush_table(table_name => 'app.messages'::regclass);
```
With `hot_row_limit = 1000` and more than 1000 mirror keys, the oldest excess
rows (by mirror `seq`) move to Parquet and leave the hot heap. Inspect status:
```sql
SELECT jsonb_pretty(
koldstore.describe_table(table_name => 'app.messages'::regclass)
);
```
## 7. Query one table
```sql
SELECT count(*) FROM app.messages; -- hot + cold via KoldMergeScan
SELECT * FROM app.messages WHERE id = 1;
```
After flush, cold-capable plans show `KoldMergeScan`. Hot-only PK lookups that
cannot hit cold may keep a native Index/Seq/Bitmap Scan with no wrapper — that
path is intentional and performance-critical.
## What just happened
1. PostgreSQL still owns transactions, MVCC, permissions, and the hot heap
2. WAL capture wrote latest-state rows into `koldstore.messages__cl`
3. Flush published Parquet segments and pruned flushed hot/mirror rows
4. `SELECT` merges hot + cold; `changes_since` pages the same latest-state stream
Next: [Under the Hood](https://kalamdb.org/docs/pg-koldstore/concepts) for the architecture, or
[Manage & Flush](https://kalamdb.org/docs/pg-koldstore/manage-and-flush) for policy details.
================================================================================
# pg Koldstore
URL: https://kalamdb.org/docs/pg-koldstore
Source: content/pg-koldstore/index.mdx
> Keep hot rows in PostgreSQL, flush history to open Parquet, and query one table with KoldStore — install, manage, flush, merge scan, and changes_since.
# pg Koldstore
**KoldStore** is an open-source PostgreSQL extension for forever-growing app
tables. It keeps the active working set in the PostgreSQL heap, moves older rows
to compressed Parquet you control, and still lets you query **one table** with
normal SQL.
This is different from [pg_kalam](https://kalamdb.org/docs/pg-kalam): pg_kalam is a foreign-data
wrapper that talks to a KalamDB server. KoldStore runs **inside** your PostgreSQL
instance and tiers storage for local heap tables.
## What you get
| Tier | Where it lives | Optimized for |
|------|----------------|---------------|
| Hot | PostgreSQL heap + native indexes | Active data, low-latency reads, transactional writes |
| Cold | Open Parquet (filesystem, S3/MinIO, GCS, Azure) | History, cost, retention |
Placement is **policy-driven** (`hot_row_limit` or age via `move_after`), not
access-frequency measurement. Reads go through `KoldMergeScan` so the newest
visible row wins across both tiers. Catch-up clients use
`koldstore.changes_since` over an exclusive `seq` cursor.
## Good fit today
- Messages and chat history
- AI memory / conversation transcripts
- Audit logs and append-heavy events
- Notifications, activity feeds, IoT telemetry
## Not a good fit yet
- Payment ledgers that need global uniqueness across hot and cold
- Workloads that need cold point lookups as fast as a B-tree
- Schema evolution, compaction, and backup/restore that are still hardening
## Read this section in order
1. [Installation](https://kalamdb.org/docs/pg-koldstore/installation) — preload, Docker, from source
2. [Getting Started](https://kalamdb.org/docs/pg-koldstore/getting-started) — first managed table
3. [Under the Hood](https://kalamdb.org/docs/pg-koldstore/concepts) — WAL capture, mirror, merge scan
4. [Manage & Flush](https://kalamdb.org/docs/pg-koldstore/manage-and-flush) — policies and jobs
5. [Change Feed](https://kalamdb.org/docs/pg-koldstore/change-feed) — `changes_since` / `last_rows`
6. [SQL API Reference](https://kalamdb.org/docs/pg-koldstore/sql-api) — every shipped function and GUC
7. [Operations](https://kalamdb.org/docs/pg-koldstore/operations) — auto-flush, mirror health
8. [Benchmarks](https://kalamdb.org/docs/pg-koldstore/benchmarks) — storage and VACUUM wins
9. [Status & Limits](https://kalamdb.org/docs/pg-koldstore/limitations) — honest beta boundaries
## Source and packaging
- Repository: [github.com/kalamdb/koldstore](https://github.com/kalamdb/koldstore)
- License: Apache 2.0
- PostgreSQL: **15–18** (published Docker image targets PG 16)
- Product page: [/koldstore](https://kalamdb.org/koldstore)
================================================================================
# Installation
URL: https://kalamdb.org/docs/pg-koldstore/installation
Source: content/pg-koldstore/installation.mdx
> Install KoldStore with Docker or from source, set shared_preload_libraries, enable wal_level=logical, and verify preload_status.
# Installation
KoldStore must load at **postmaster start**. Without
`shared_preload_libraries = 'koldstore'`, planner hooks never register and
managed `SELECT`s can silently miss cold rows after flush.
## Fastest path: Docker
Published images preload the extension and create it on first init:
```bash
docker pull jamals86/pg-koldstore:latest
docker run --rm \
-e POSTGRES_PASSWORD=postgres \
-p 5432:5432 \
jamals86/pg-koldstore:latest
```
Connect:
```bash
psql postgres://postgres:postgres@127.0.0.1:5432/koldstoredb
```
| Detail | Value |
|--------|-------|
| Default database | `koldstoredb` |
| PostgreSQL major | 16 |
| Tags | `latest`, `-pg16` (for example `0.1.5-beta.0-pg16`) |
| Architectures | amd64, arm64 |
Confirm:
```sql
SHOW shared_preload_libraries; -- must include koldstore
CREATE EXTENSION IF NOT EXISTS koldstore;
SELECT koldstore.preload_status(); -- loaded_via_shared_preload = true
```
`pg_cron` may be packaged in the image but is **not** preloaded. Enable it
yourself if you want cron-based flush scheduling.
## Shared preload (required on every install)
Install order:
1. Install extension package files
2. Set `shared_preload_libraries = 'koldstore'` (merge with any existing list)
3. **Restart** PostgreSQL (`reload` is not enough)
4. `CREATE EXTENSION koldstore`
Ubuntu / Debian example:
```bash
echo "shared_preload_libraries = 'koldstore'" | \
sudo tee /etc/postgresql/16/main/conf.d/koldstore.conf
sudo systemctl restart postgresql@16-main
```
Rules:
- `session_preload_libraries` is **not** sufficient
- Removing preload after `manage_table` is **unsupported**
- Check with `SELECT koldstore.preload_status();`
## Logical WAL (required for managed tables)
Committed-WAL capture needs:
```conf
wal_level = logical
```
Restart after changing `wal_level`. Do **not** create the publication or logical
slot yourself: `CREATE EXTENSION` ensures the empty publication
`koldstore_async_mirror`, and the first `manage_table` creates the database’s
deterministic slot. Provisioning is idempotent.
## Recommended production GUC baseline
Prefer `ALTER SYSTEM` / `ALTER DATABASE` for settings the background worker
reads (session `SET` does not always reach the worker):
| Setting | Baseline |
|---------|----------|
| `shared_preload_libraries` | include `koldstore` |
| `wal_level` | `logical` |
| `koldstore.async_mirror_max_retained_bytes` | `1073741824` (1 GiB health alert) |
| `koldstore.flush_check_interval_seconds` | `30` |
| `koldstore.async_apply_poll_interval_ms` | `100` |
## From source (developers)
Build and install with pgrx against a local PostgreSQL (typical loop for PG 16):
```bash
cargo pgrx install -p pg_koldstore \
--no-default-features \
--features "pg16 s3" \
--pg-config "$(cargo pgrx info pg-config 16)"
```
Then set preload, restart, and `CREATE EXTENSION`. See the repository
[quickstart](https://github.com/kalamdb/koldstore/blob/main/docs/quickstart.md)
and [Docker](https://github.com/kalamdb/koldstore/tree/main/docker) folder for
compose + MinIO layouts.
## Upgrades (beta)
There are **no** packaged `ALTER EXTENSION … UPDATE` edges yet while the product
is in development. Local installs reinstall or resync when the bootstrap SQL
changes. Treat in-place upgrade and `pg_upgrade` as future ops work — see
[Status & Limits](https://kalamdb.org/docs/pg-koldstore/limitations).
## Next step
[Getting Started](https://kalamdb.org/docs/pg-koldstore/getting-started) — register storage, manage
a table, flush, and read with `changes_since`.
================================================================================
# Status & Limits
URL: https://kalamdb.org/docs/pg-koldstore/limitations
Source: content/pg-koldstore/limitations.mdx
> Honest KoldStore beta boundaries: preload correctness, hot-only UNIQUE/FK, cold storage failure modes, and unfinished recovery work.
# Status & Limits
KoldStore is in **early development** and is **not production-ready**. Manage,
flush, auto-flush, hot/cold query, and `changes_since` work. Recovery,
backup/restore, compaction, schema evolution, PK changes, and export/import are
still hardening.
## Shared preload is mandatory
`shared_preload_libraries` must include `koldstore`. Without it, backends can
silently run ordinary heap scans and return **hot-only** rows after flush.
- Install → set preload → **restart** → `CREATE EXTENSION`
- `session_preload_libraries` is not enough
- Removing preload after `manage_table` is unsupported
Check: `SELECT koldstore.preload_status();`
## UNIQUE and foreign keys
PostgreSQL `UNIQUE` and FK constraints on managed tables are enforced on the
**hot heap only**. After flush, cold values live in Parquet without a global
hot+cold constraint layer.
| Constraint | Hot | Cold | Normal DML checks cold? |
|------------|-----|------|-------------------------|
| Primary key | Yes | Logical winner via merge | Cold presence via segment index + Parquet |
| Non-PK `UNIQUE` | Yes | No | No |
| Foreign keys | Yes | No | No |
When `hot_row_limit` is set, `manage_table` **rejects** tables that already have
non-PK `UNIQUE` constraints or foreign keys.
## Indexes and extensions
PostgreSQL indexes (including pgvector HNSW/IVFFlat and similar) cover **hot
rows only**. Flushed rows lose heap index entries. There is no automatic Parquet
index translation yet. The `vector` type is not in the v0.1 supported type
matrix.
## Cold storage availability
If cold storage is unavailable, managed queries that need cold segments
**fail**. KoldStore does not silently return hot-only results.
`koldstore.enable_merge_scan = off` also errors rather than allowing an
incorrect heap-only read.
## Capture limits
- WAL-only capture (no trigger mode)
- Primary-key updates rejected
- `TRUNCATE` on async managed tables rejected
- Bounded mirror lag unless you call `wait_for_async_mirror()`
- Scoped physical cold folders still incomplete vs registration templates
## Change feed limits
- Latest-state catch-up, not full event history
- Positive cursors older than retained cold floor → retention-gap **error**
- `last_rows` ignored when `since_seq > 0`
- `last_rows` must be `<= limit_rows`
## Unmanage, not disable
Use `koldstore.unmanage_table(...)`. Do not set `koldstore_enabled = false`.
Storage binding is immutable after manage.
## Not shipped
- Packaged extension upgrade edges (`ALTER EXTENSION … UPDATE`)
- Cold DML helpers and cold-only `UPDATE`/`DELETE` that mutate Parquet in place
- `backup_manifest` / `validate_cold_storage` / polished export-import
- Compaction and schema evolution tooling
## When to wait
Prefer waiting if you need global uniqueness across tiers, cold lookups as fast
as B-trees, or turnkey PITR that spans heap + object storage out of the box.
## When to try
Local or staging workloads that grow forever (messages, audit, AI memory) where
shrinking the hot heap and indexes is the main goal, and you can tolerate beta
ops boundaries.
================================================================================
# Manage & Flush
URL: https://kalamdb.org/docs/pg-koldstore/manage-and-flush
Source: content/pg-koldstore/manage-and-flush.mdx
> Enable KoldStore on a heap table, choose hot_row_limit or move_after, run flush_table, and inspect jobs with describe_table.
# Manage & Flush
Managing a table attaches KoldStore policy and WAL mirror capture. Flushing
moves eligible latest-state rows from the hot heap into Parquet and prunes them
from PostgreSQL.
## Enable management
Prefer named arguments. `hot_row_limit` is required in the `manage_table` call
— pass `NULL` for hot-only tables that should not flush.
```sql
SELECT koldstore.manage_table(
table_name => 'chat.messages',
storage => 's3_archive',
hot_row_limit => 10000,
min_flush_rows => 1000,
max_rows_per_file => 1000,
auto_flush => true,
migration_order_by => 'created_at'
);
```
| Parameter | Default | Meaning |
|-----------|---------|---------|
| `table_name` | required | Table to manage (`regclass`) |
| `storage` | required | Registered storage backend name |
| `hot_row_limit` | required (`NULL` allowed) | Max mirror rows to keep hot |
| `min_flush_rows` | `1000` | Minimum excess before a policy flush moves data |
| `max_rows_per_file` | `1000` | Max rows per Parquet segment (floor via `koldstore.min_max_rows_per_file`) |
| `table_type` | `'shared'` | `shared` or `user` (+ `scope_column`) |
| `compression` | `NULL` | Optional Parquet codec (`snappy`, `zstd`, `uncompressed`) |
| `target_file_size_mb` | `NULL` | Stored for future size-aware flush |
| `auto_flush` | `true` | Let the DB worker evaluate and run needed flushes |
### ALTER TABLE style
```sql
ALTER TABLE chat.messages SET (
koldstore_enabled = true,
koldstore_storage = 's3_archive',
koldstore_hot_row_limit = 10000,
koldstore_min_flush_rows = 1000,
koldstore_max_rows_per_file = 1000
);
```
Age-based eviction (mutually exclusive with `hot_row_limit`):
```sql
ALTER TABLE chat.messages SET (
koldstore_enabled = true,
koldstore_storage = 's3_archive',
koldstore_move_after = '90 days',
koldstore_min_flush_rows = 1000,
koldstore_max_rows_per_file = 10000
);
```
Age is measured from the latest captured mirror mutation encoded in `seq`.
## Flush
### Policy flush
```sql
SELECT koldstore.flush_table(table_name => 'chat.messages'::regclass);
```
Keeps the newest rows hot by mirror `seq` and flushes oldest eligible excess
first, honoring `hot_row_limit` / `min_flush_rows` / `max_rows_per_file`.
### Force flush
```sql
SELECT koldstore.flush_table(
table_name => 'chat.messages'::regclass,
force => true
);
```
Flushes **all** pending mirror rows. Use carefully on large tables.
### Enqueue without running
```sql
SELECT koldstore.enqueue_flush_job(
table_name => 'chat.messages'::regclass,
force => false
);
```
Returns `1` if a job was inserted, `0` if an active flush job already exists.
`auto_flush` does **not** gate manual flush / enqueue.
## Inspect progress
```sql
SELECT jsonb_pretty(
koldstore.describe_table(table_name => 'chat.messages'::regclass)
);
SELECT koldstore.list_jobs(
statuses => '["running","pending"]'::jsonb,
job_types => '["flush"]'::jsonb,
table_name => 'chat.messages'::regclass
);
```
Useful `describe_table` fields: `hot_rows`, `mirror_rows`, `cold_row_count`,
`cold_segment_count`, heap/index sizes, `manifest_state`, `pending_jobs`,
`last_error`.
## Cancel
```sql
SELECT koldstore.cancel_job(job_id => '…'::uuid);
SELECT koldstore.cancel_table_jobs(table_name => 'chat.messages'::regclass);
```
Pending jobs cancel immediately when unlocked. Running jobs stop cooperatively
at the next wave boundary. If cancel lands after cold publish already committed,
the job may finish `completed` with
`payload.cancel_requested_after_publish = true` (data is not unpublished).
## Auto-flush toggle
```sql
SELECT koldstore.set_table_auto_flush(
table_name => 'chat.messages'::regclass,
enabled => false
);
```
Manual `flush_table` still works. See [Operations](https://kalamdb.org/docs/pg-koldstore/operations)
for the built-in worker tick and pg_cron fallback.
## Unmanage
```sql
SELECT koldstore.unmanage_table(
table_name => 'chat.messages'::regclass,
rehydrate => true,
drop_cold => false
);
```
Use this instead of `koldstore_enabled = false`. Optional `rehydrate` /
`drop_cold` control whether cold rows return to the heap and whether cold
objects are deleted.
## Next
- [Change Feed](https://kalamdb.org/docs/pg-koldstore/change-feed)
- [SQL API Reference](https://kalamdb.org/docs/pg-koldstore/sql-api)
================================================================================
# Operations
URL: https://kalamdb.org/docs/pg-koldstore/operations
Source: content/pg-koldstore/operations.mdx
> Run KoldStore auto-flush, monitor async mirror health, schedule pg_cron fallbacks, and understand DROP TABLE cold cleanup.
# Operations
Day-2 concerns for a managed database: auto-flush, mirror health, optional
cron, and drop/cleanup behavior.
## Built-in auto-flush
The same database worker that applies WAL also evaluates `auto_flush` tables on
each `koldstore.flush_check_interval_seconds` tick (default `30`):
1. Apply available WAL
2. Evaluate tables with `auto_flush = true`
3. Run at most one needed `flush_table`
Failed auto-flush skips that table for about **60 seconds**. Opt out per table:
```sql
SELECT koldstore.set_table_auto_flush(
table_name => 'app.messages'::regclass,
enabled => false
);
```
Or pass `auto_flush => false` to `manage_table`. Manual flush still works.
Prefer `ALTER DATABASE` / `ALTER SYSTEM` for worker-facing GUCs — session `SET`
does not always reach the background worker.
## pg_cron fallback
Useful when you want wall-clock schedules or `auto_flush = false`:
```sql
SELECT cron.schedule(
'koldstore-flush-messages',
'*/5 * * * *',
$$SELECT koldstore.flush_table(table_name => 'app.messages'::regclass)$$
);
```
A policy flush with nothing eligible returns safely (`rows_flushed = 0`).
## Mirror health
```sql
SELECT koldstore.async_mirror_status();
SELECT koldstore.async_mirror_slot_name();
```
Alert on:
- `healthy` / retention flags in `async_mirror_status()`
- Retained WAL bytes vs `koldstore.async_mirror_max_retained_bytes` (default 1 GiB)
- Stale `updated_at` / apply rates
Crossing the retained-bytes threshold marks retention unhealthy but **never
stops** the applier from draining WAL. Separately configure PostgreSQL disk
guards and `max_slot_wal_keep_size` (an invalidated slot may require rebuild).
Strong application fence:
```sql
SELECT koldstore.wait_for_async_mirror();
```
## Disable capture infrastructure
Only after every managed table is unmanaged:
```sql
SELECT koldstore.disable_async_mirror();
```
Idempotent. A later `manage_table` recreates compatible infrastructure.
## DROP TABLE
Dropping a managed table cancels jobs, waits for locks, deactivates catalogs,
deletes cold objects under the table prefix (when deletion is enabled for that
path), drops `__cl`, and records a `drop_table_cleanup` job.
## Backup (honest beta)
- Back up PostgreSQL **and** cold object prefixes together
- Cold storage is not WAL-protected
- `recover_segments(..., dry_run => true|false)` exists for orphan discovery
- Packaged `backup_manifest` / `validate_cold_storage` are **not** shipped yet
- `pg_dump` / `COPY TO` see the logical merged view; physical Parquet is outside plain dumps
See [Status & Limits](https://kalamdb.org/docs/pg-koldstore/limitations).
## Suggested monitoring checklist
1. `SHOW shared_preload_libraries` still includes `koldstore`
2. `koldstore.preload_status()` → `loaded_via_shared_preload = true`
3. `async_mirror_status()` healthy; retained bytes under threshold
4. `describe_table` for hot/cold growth and `last_error`
5. Flush job backlog (`list_jobs` pending/running)
6. Disk for PostgreSQL data directory **and** cold backend
## Next
- [Benchmarks](https://kalamdb.org/docs/pg-koldstore/benchmarks)
- [Status & Limits](https://kalamdb.org/docs/pg-koldstore/limitations)
================================================================================
# SQL API Reference
URL: https://kalamdb.org/docs/pg-koldstore/sql-api
Source: content/pg-koldstore/sql-api.mdx
> Complete KoldStore SQL surface: session helpers, GUCs, storage, manage/flush, async mirror, changes_since, jobs, and describe_table.
# SQL API Reference
Signatures match the installed extension SQL today. Prefer **named arguments**
so optional parameters stay readable. Positional calls remain valid.
```sql
-- Preferred
SELECT koldstore.register_storage(
name => 'local-dev',
storage_type => 'filesystem',
base_path => '/tmp/koldstore-demo',
credentials => '{}'::jsonb,
config => '{}'::jsonb
);
```
## Session helpers
| Function | Returns | Meaning |
|----------|---------|---------|
| `snowflake_id()` | `bigint` | Monotonic Snowflake-like id |
| `koldstore_version()` | `text` | Extension version |
| `koldstore_user_id()` | `text` | Active `koldstore.user_id`, or `NULL` |
| `koldstore.preload_status()` | `jsonb` | Preload / merge-scan status |
## Public GUCs
| GUC | Default | Meaning |
|-----|---------|---------|
| `koldstore.user_id` | empty | Scope id for user-typed managed tables |
| `koldstore.cold_reads` | `auto` | `auto` / `on` / `off` (`off` errors when cold is required) |
| `koldstore.enable_merge_scan` | `on` | `off` → ERROR at exec (never silent hot-only) |
| `koldstore.max_open_parquet_readers` | `32` | Per-backend open reader cap (`1..=1024`) |
| `koldstore.max_merge_seen_keys` | `1000000` | Per-scan PK identity cap; `0` disables |
| `koldstore.log_level` | `info` | `error` / `warn` / `info` / `debug` / `trace` |
| `koldstore.min_max_rows_per_file` | `1000` | Floor for `max_rows_per_file` |
| `koldstore.flush_check_interval_seconds` | `30` | Auto-flush worker tick (`1..=86400`) |
| `koldstore.async_apply_poll_interval_ms` | `100` | Apply loop latch poll (`50..=5000`) |
| `koldstore.async_apply_max_rows_per_tick` | `0` | Max source changes per tick (`0` = unlimited) |
| `koldstore.async_apply_max_ms_per_tick` | `0` | Max ms per tick (`0` = unlimited) |
| `koldstore.flush_prelock_max_passes` | `3` | Flush phase-5.5 pre-lock apply passes |
| `koldstore.flush_prelock_max_ms` | `5000` | Flush phase-5.5 wall budget |
| `koldstore.async_mirror_max_retained_bytes` | `1 GiB` | Health threshold only — never blocks apply |
Internal GUCs (`internal_system_write`, `internal_flush_cleanup`) are reserved
for extension maintenance paths and cannot be set by application roles.
Configure durable settings with `ALTER SYSTEM` / `ALTER DATABASE` / `ALTER ROLE`
and follow PostgreSQL reload rules. Session `SET` does not always reach the
background worker.
## Function catalog
| Function | Returns |
|----------|---------|
| `koldstore.register_storage(...)` | `uuid` |
| `koldstore.alter_storage_credentials(...)` | `void` |
| `koldstore.alter_storage_location(...)` | `uuid` |
| `koldstore.manage_table(...)` | `uuid` (migration job id) |
| `koldstore.set_table_auto_flush(...)` | `boolean` |
| `koldstore.unmanage_table(...)` | `bigint` |
| `koldstore.wait_for_async_mirror()` | `bigint` |
| `koldstore.async_mirror_status()` | `jsonb` |
| `koldstore.async_mirror_slot_name()` | `text` |
| `koldstore.disable_async_mirror()` | `boolean` |
| `koldstore.enqueue_flush_job(...)` | `bigint` (`1` / `0`) |
| `koldstore.flush_table(...)` | `uuid` |
| `koldstore.list_jobs(...)` | `jsonb` array |
| `koldstore.cancel_job(...)` / `cancel_table_jobs(...)` | cancel status |
| `koldstore.describe_table(...)` | `jsonb` |
| `koldstore.recover_segments(...)` | `bigint` |
| `koldstore.changes_since(...)` | set of change rows |
## Storage
```sql
SELECT koldstore.register_storage(
name => 'local-dev',
storage_type => 'filesystem', -- filesystem | s3 | gcs | azure
base_path => '/tmp/koldstore-demo',
credentials => '{}'::jsonb,
config => '{}'::jsonb
-- optional: regular_path_tmpl, scoped_path_tmpl
);
SELECT koldstore.alter_storage_credentials(
name => 'local-dev',
credentials => '{"access_key_id":"...","secret_access_key":"..."}'::jsonb
);
SELECT koldstore.alter_storage_location(
name => 'local-dev',
base_path => '/var/lib/koldstore',
config => '{}'::jsonb
);
```
Default path templates: `{namespace}/{tableName}/` and
`{namespace}/{tableName}/{scopeId}/`.
## Manage / unmanage
See [Manage & Flush](https://kalamdb.org/docs/pg-koldstore/manage-and-flush) for parameters and
policy examples. Quick reference:
```sql
SELECT koldstore.manage_table(
table_name => 'chat.messages',
storage => 's3_archive',
hot_row_limit => 10000
);
SELECT koldstore.set_table_auto_flush('chat.messages'::regclass, false);
SELECT koldstore.unmanage_table(
table_name => 'chat.messages'::regclass,
rehydrate => true,
drop_cold => false
);
```
## Async mirror
```sql
SELECT koldstore.wait_for_async_mirror(); -- strong fence; 0 ≠ disabled
SELECT koldstore.async_mirror_status();
SELECT koldstore.async_mirror_slot_name();
-- After unmanaging every table:
SELECT koldstore.disable_async_mirror();
```
## Flush and jobs
```sql
SELECT koldstore.enqueue_flush_job(table_name => 'chat.messages'::regclass);
SELECT koldstore.flush_table(table_name => 'chat.messages'::regclass, force => false);
SELECT koldstore.list_jobs();
SELECT koldstore.describe_table(table_name => 'chat.messages'::regclass);
SELECT koldstore.recover_segments(table_name => 'chat.messages'::regclass, dry_run => true);
```
## Change feed
```sql
SELECT * FROM koldstore.changes_since(
table_name => 'chat.messages'::regclass,
since_seq => 0,
limit_rows => 1000,
last_rows => NULL
);
```
Full semantics: [Change Feed](https://kalamdb.org/docs/pg-koldstore/change-feed).
## Not shipped yet (do not call)
Documented as roadmap / ops intent only:
- Cold DML helpers (`hydrate_pk`, cold `update_row` / `delete_row`)
- `backup_manifest`, `validate_cold_storage`, packaged export/import
- Packaged `ALTER EXTENSION … UPDATE` edges
Standard SQL `UPDATE`/`DELETE` that only matches cold rows affects **zero**
hot rows in the current MVP — use unmanage/rehydrate paths when you need cold
rows back in the heap.
## Next
- [Operations](https://kalamdb.org/docs/pg-koldstore/operations)
- [Status & Limits](https://kalamdb.org/docs/pg-koldstore/limitations)
================================================================================
# Architecture
URL: https://kalamdb.org/docs/pg-kalam/architecture
Source: content/pg-kalam/architecture.mdx
> Understand how pg_kalam uses FDW callbacks, a ProcessUtility DDL hook, gRPC sessions, and Arrow batches to bridge PostgreSQL to KalamDB.
# PostgreSQL Extension Architecture
The current PostgreSQL extension is remote-only. PostgreSQL hosts the extension entrypoints, the FDW callbacks, and the local relation surface, while a running KalamDB server performs the actual execution over gRPC.
## Main Components
1. A PostgreSQL backend process: owns SQL parsing, planning, and the visible PostgreSQL-side relation surface.
2. The `pg_kalam` extension: registers `_PG_init`, the FDW callbacks, the DDL `ProcessUtility` hook, and the helper SQL functions.
3. A foreign server definition: stores remote connection settings such as `host`, `port`, timeout, TLS material, and optional auth metadata.
4. A per-backend remote state cache: holds a reusable tonic channel, Tokio runtime, and a logical remote session id.
5. KalamDB's shared RPC listener: exposes the `PgService` surface for scans, mutations, transactions, and direct SQL execution.
## Transport and Session Model
`pg_kalam` talks to KalamDB through the shared RPC listener, not through the HTTP SQL API.
Current server options:
- `host`
- `port`
- `timeout`
- `auth_header`
- `ca_cert`
- `client_cert`
- `client_key`
On first use in a PostgreSQL backend and for a specific remote-server config, the extension:
1. parses the foreign-server options,
2. creates a current-thread Tokio runtime,
3. opens a tonic channel,
4. computes a logical session id in the form `pg--`,
5. sends `OpenSession`, and
6. caches that state for reuse across later statements in the same backend process.
`on_proc_exit` then attempts to send `CloseSession` for every cached remote state owned by that PostgreSQL backend.
Two important consequences follow from the current code:
- The transport is reused per PostgreSQL backend and per remote config instead of reconnecting for every statement.
- PostgreSQL current schema or `search_path` is not currently pushed into the remote session automatically, so direct `kalam_exec(...)` statements should be schema-qualified.
## DDL Path
The extension registers a `ProcessUtility` hook from `_PG_init()`. That hook is what powers the current DDL surface.
The hook intercepts:
- `CREATE TABLE ... USING kalamdb`
- `CREATE FOREIGN TABLE`
- `ALTER FOREIGN TABLE`
- `DROP FOREIGN TABLE`
- top-level explicit transaction statements so the extension can coordinate remote transaction lifecycle
There are two DDL entry points:
1. `CREATE TABLE ... USING kalamdb`
This is the shorthand authoring path. It is currently bound to the default foreign server name `kalam_server`.
2. `CREATE FOREIGN TABLE ... SERVER `
This is the explicit path for non-default server names and manual mirroring.
In both paths, the current relation identity is what matters:
- the PostgreSQL schema becomes the remote KalamDB namespace,
- the PostgreSQL relation name becomes the remote KalamDB table name.
The runtime scan and modify paths do not treat `OPTIONS (namespace, table)` as the canonical mapping source.
### What the DDL hook does
For `CREATE TABLE ... USING kalamdb`, the extension:
1. validates that `_seq`, `_userid`, and `_deleted` were not declared explicitly,
2. strips the PostgreSQL `USING kalamdb` access-method marker before forwarding SQL to KalamDB,
3. normalizes `SERIAL`, `BIGSERIAL`, `SMALLSERIAL`, and identity columns into explicit integer columns with `DEFAULT SNOWFLAKE_ID()`,
4. forwards the user-column DDL and `WITH (...)` table options to KalamDB,
5. creates a local foreign table, and
6. injects `_seq BIGINT` on all tables plus `_userid TEXT` on user tables.
The local object becomes a foreign table after interception, which is why follow-up DDL should use `ALTER FOREIGN TABLE` and `DROP FOREIGN TABLE`.
## Read Path
For `SELECT` statements, the FDW scan flow is:
1. PostgreSQL plans the extension-managed relation scan.
2. `pg_kalam` resolves the relation identity from the local relation metadata and parses the foreign-server options.
3. The extension reads the current `kalam.user_id` value, if present.
4. Any pending buffered writes for that table are flushed first so reads see local writes.
5. The FDW builds a `ScanRequest` with physical-column projection and simple equality filters that are safe to push down.
6. A remote scan request is sent to KalamDB over `PgService`.
7. The returned Arrow IPC batches are converted into PostgreSQL tuples.
Current pushdown behavior is intentionally partial:
- projection pushdown is used for physical columns,
- simple `column = constant` or `column = $param` filters can be pushed,
- PostgreSQL still reapplies all quals locally,
- `ORDER BY` and `LIMIT` are not pushed down today.
`_userid` is synthesized locally from the current session user id. `_seq` comes back from the remote Arrow schema.
## Write Path
For `INSERT`, `UPDATE`, and `DELETE` statements:
1. PostgreSQL routes the operation through FDW modify callbacks.
2. `pg_kalam` resolves the target table and tenant context.
3. The extension converts PostgreSQL datums into DataFusion `ScalarValue` payloads.
4. Single-row inserts are buffered for better batching behavior inside explicit transactions.
5. Multi-row inserts go through the batch insert path directly.
6. Updates and deletes flush pending inserts first, then send remote mutation requests.
Single-row inserts are buffered so PostgreSQL transactions can batch work more efficiently. Pending writes are flushed before scans and before transaction completion so read-your-writes behavior stays predictable inside the PostgreSQL session.
Current user-id behavior:
- `_userid` is only treated as an explicit tenant override for `INSERT`.
- `UPDATE` and `DELETE` use the session `kalam.user_id` scope.
- In a multi-row insert, all rows must use the same `_userid` value.
Current row-identity behavior:
- `UPDATE` and `DELETE` use the first non-system column as the row-identity heuristic.
- In practice, keep the primary key as the first user column in the table definition.
## Explicit Transaction Flow
When a PostgreSQL session opens an explicit transaction with `BEGIN` or `START TRANSACTION`, the extension lazily opens a remote KalamDB transaction on the first FDW operation in that block.
The extension uses PostgreSQL xact callbacks to coordinate remote `BeginTransaction`, `CommitTransaction`, and `RollbackTransaction` calls.
That transaction state is observable through two server views:
- `system.sessions` for the live pg bridge session and its current remote transaction metadata.
- `system.transactions` for the active explicit transaction itself alongside other active transaction origins.
This makes PostgreSQL transaction behavior visible without materializing extra persisted bookkeeping on the server.
Current transaction limits come from the backend coordinator:
- DDL is rejected inside explicit transactions.
- Stream-table writes are rejected inside explicit transactions.
- The documented surface is top-level `BEGIN`, `COMMIT`, and `ROLLBACK` blocks.
## Direct Statement Passthrough
The extension also exposes a direct SQL bridge:
```sql
SELECT kalam_exec('SELECT * FROM system.tables');
```
Use `kalam_exec(...)` when you want to send a statement directly to KalamDB without wrapping it in PostgreSQL relation syntax first.
Current behavior:
- it uses the default foreign server `kalam_server`,
- it reuses the same per-backend remote session cache as the FDW path,
- it returns a JSON array string for query results,
- it returns a JSON string status message for DDL and DML,
- it is revoked from `PUBLIC`.
## Tenant Context
`kalam.user_id` is a PostgreSQL session setting registered by the extension.
```sql
SET kalam.user_id = 'user-alice';
SELECT kalam_user_id();
```
The session value is attached to remote requests and is the main way user-scoped reads, updates, and deletes are isolated.
`kalam.user_id` is registered as a PostgreSQL `SUSET` GUC, so your PostgreSQL role needs permission to set `SUSET` parameters.
## Related
- [Getting Started](https://kalamdb.org/docs/pg-kalam/getting-started)
- [SQL Syntax](https://kalamdb.org/docs/pg-kalam/sql-syntax)
- [Data Type Conversions](https://kalamdb.org/docs/pg-kalam/type-conversions)
- [Status & Limits](https://kalamdb.org/docs/pg-kalam/limitations)
================================================================================
# Getting Started
URL: https://kalamdb.org/docs/pg-kalam/getting-started
Source: content/pg-kalam/getting-started.mdx
> Set up pg_kalam by preloading the extension, exposing KalamDB's RPC port, creating the default foreign server, and using the current authoring paths correctly.
# PostgreSQL Extension Getting Started
This page covers both the release-download path and the current usage path after the `pg_kalam` binaries are installed.
If you still need to build or package the extension itself, use the repository guide: [pg/README.md](https://github.com/kalamdb/KalamDB/tree/main/pg).
If you want PostgreSQL with the extension already installed, use the published Docker image: [jamals86/pg-kalam](https://hub.docker.com/r/jamals86/pg-kalam).
## Download a Release Package
KalamDB publishes a prebuilt PostgreSQL extension package in each GitHub release:
- Releases page: [github.com/kalamdb/KalamDB/releases](https://github.com/kalamdb/KalamDB/releases)
- Current asset pattern:
- Check the matching `SHA256SUMS` asset before installing in production
The install example below automatically uses the latest published GitHub release tag.
Important constraints:
- The published release package currently targets Linux x86_64.
- The asset name includes the PostgreSQL feature it was built for, such as `pg16`.
- The binary must match the PostgreSQL major version of the server where you install it.
- If you need another platform or PostgreSQL major, build from source with `pgrx`.
Example install from a release asset:
## Run with Docker
For local or CI usage, you can run PostgreSQL with `pg_kalam` preinstalled directly from Docker Hub:
```bash
docker run --name pg-kalam \
-e POSTGRES_USER=kalamdb \
-e POSTGRES_PASSWORD=kalamdb123 \
-e POSTGRES_DB=kalamdb \
-p 5433:5432 \
-d jamals86/pg-kalam:latest-pg16
```
Then connect with `psql` and install the extension in the database if your init SQL has not already done it:
```sql
CREATE EXTENSION IF NOT EXISTS pg_kalam;
```
## Prerequisites
- PostgreSQL with `pg_kalam` installed into that exact PostgreSQL instance.
- A running KalamDB server reachable from PostgreSQL.
- A stable KalamDB RPC endpoint host and port. The default repository examples use `2910`.
- An auth header if your KalamDB server requires one.
## 1. Preload and Create the Extension
`pg_kalam` installs SQL objects with `CREATE EXTENSION`, but the DDL mirroring hook is registered from `_PG_init()`. For the shorthand `CREATE TABLE ... USING kalamdb` path, add the extension to PostgreSQL's preload list and restart PostgreSQL first:
```conf
shared_preload_libraries = 'pg_kalam'
```
Then connect as a superuser and install the extension into the target database:
```sql
CREATE EXTENSION IF NOT EXISTS pg_kalam;
```
Verify that PostgreSQL sees the extension functions:
```sql
SELECT kalam_version(), kalam_compiled_mode();
```
`kalam_compiled_mode()` should currently return `remote`.
## 2. Give KalamDB a Stable RPC Address
Point PostgreSQL at KalamDB's shared gRPC/RPC listener, not at the HTTP API port.
For local source runs, configure a fixed RPC address instead of relying on a random ephemeral port:
```toml
[cluster]
rpc_addr = "127.0.0.1:2910"
api_addr = "127.0.0.1:2900"
```
If PostgreSQL runs in Docker on macOS while KalamDB runs on the host machine, use `host.docker.internal` instead of `127.0.0.1` in the foreign-server definition.
## 3. Register the Default KalamDB Foreign Server
Create the default foreign server that the shorthand authoring path expects:
```sql
CREATE SERVER IF NOT EXISTS kalam_server
FOREIGN DATA WRAPPER pg_kalam
OPTIONS (
host '127.0.0.1',
port '2910',
auth_header 'Bearer '
);
```
Supported server options today:
- `host` and `port` are required.
- `auth_header`, `timeout`, `ca_cert`, `client_cert`, and `client_key` are optional.
- If you set `client_cert` or `client_key`, you must set both.
## 4. Choose the Right Authoring Path
There are two current SQL entry points:
| Goal | Use |
|---|---|
| Create a KalamDB table from PostgreSQL on the default server | `CREATE TABLE ... USING kalamdb` |
| Use a non-default foreign server or mirror a relation explicitly | `CREATE FOREIGN TABLE ... SERVER ` |
The shorthand `USING kalamdb` path is convenient, but it is hard-wired to the default server name `kalam_server`.
## 5. Create Your First Shared Table
The primary authoring syntax is `CREATE TABLE ... USING kalamdb`:
```sql
CREATE SCHEMA IF NOT EXISTS app;
CREATE TABLE app.shared_items (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
title TEXT NOT NULL,
value INTEGER,
created_at TIMESTAMP DEFAULT NOW()
) USING kalamdb WITH (
type = 'shared',
flush_policy = 'rows:1000,interval:60'
);
```
That statement is intercepted by the DDL hook, creates the remote namespace and table in KalamDB, then creates a local foreign table and injects the PostgreSQL-visible system columns that the extension manages locally.
Any KalamDB table shape that the server accepts can be authored this way from PostgreSQL. The same option names you would use in KalamDB are forwarded through the PostgreSQL `WITH (...)` clause.
Important rules for new tables:
- Do not declare `_seq`, `_userid`, or `_deleted` in the `CREATE TABLE ... USING kalamdb` column list. `pg_kalam` rejects that DDL because those columns are managed by the extension.
- `_seq BIGINT` is added to every PostgreSQL table surface that `pg_kalam` creates.
- `_userid TEXT` is added only to user tables.
- `_deleted` is reserved, but it is not auto-injected into the current PostgreSQL table surface.
- Keep the primary key as the first user column. The current `UPDATE` and `DELETE` FDW path uses the first non-system column as its row-identity heuristic.
- The local object is a foreign table after interception, so use `ALTER FOREIGN TABLE` and `DROP FOREIGN TABLE` for follow-up DDL.
## 6. Query and Mutate Through PostgreSQL
```sql
INSERT INTO app.shared_items (id, title, value)
VALUES (SNOWFLAKE_ID(), 'Alpha', 10), (SNOWFLAKE_ID(), 'Beta', 20);
SELECT id, title, value
FROM app.shared_items
ORDER BY id;
UPDATE app.shared_items
SET value = 25
WHERE id = 2;
DELETE FROM app.shared_items
WHERE id = 1;
```
Autocommit statements flush at statement end. Top-level explicit transactions are covered later in this section.
## 7. Use Session Identity for User Tables
User tables are tenant-scoped. Set `kalam.user_id` in each PostgreSQL session before querying or mutating them:
```sql
SET kalam.user_id = 'user-alice';
CREATE TABLE app.profiles (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
name TEXT,
age INTEGER
) USING kalamdb WITH (
type = 'user'
);
INSERT INTO app.profiles (id, name, age)
VALUES (SNOWFLAKE_ID(), 'Alice', 30);
INSERT INTO app.profiles (id, name, age, _userid)
VALUES (SNOWFLAKE_ID(), 'Bob', 41, 'user-bob');
SELECT id, name, age, _userid, _seq
FROM app.profiles
ORDER BY _seq;
```
The tenant scope comes from the PostgreSQL session value unless you explicitly provide `_userid` in an `INSERT`.
`kalam.user_id` is registered as a PostgreSQL `SUSET` parameter. In practice, the role running these statements must be allowed by PostgreSQL to change `SUSET` settings.
Current `_userid` rules:
- On user tables, omitting `_userid` uses the current `kalam.user_id` session value.
- On user tables, an explicit `_userid` in `INSERT` overrides the session value for that inserted row batch.
- In a multi-row `INSERT`, every row must use the same `_userid` value.
- `UPDATE` and `DELETE` still use the session's `kalam.user_id` scope. Changing row ownership with `UPDATE ... SET _userid = ...` is not part of the supported surface.
## 8. Create a Stream Table
Stream tables use the same authoring path, but they must supply `ttl_seconds`:
```sql
CREATE TABLE app.events (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
event_type TEXT NOT NULL,
payload JSONB,
created_at TIMESTAMP DEFAULT NOW()
) USING kalamdb WITH (
type = 'stream',
ttl_seconds = '3600'
);
```
Normal FDW access uses the stream table type, but explicit PostgreSQL transactions reject stream-table writes.
## 9. Reuse KalamDB Table Options with `WITH (...)`
The `WITH (...)` clause is the main place to express KalamDB table options from PostgreSQL:
```sql
CREATE TABLE app.orders (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
customer_id TEXT NOT NULL,
status TEXT DEFAULT 'pending',
created_at TIMESTAMP DEFAULT NOW()
) USING kalamdb WITH (
type = 'shared',
flush_policy = 'rows:1000,interval:60'
);
```
Use the same option names and values that KalamDB expects. The extension forwards them to the server when it constructs the real KalamDB `CREATE TABLE` statement.
## 10. Work with `FILE` Columns
`FILE` is the PostgreSQL extension's main KalamDB-specific type shortcut.
```sql
CREATE TABLE app.assets (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
attachment FILE
) USING kalamdb WITH (
type = 'shared'
);
SELECT attachment->>'name', attachment->>'mime'
FROM app.assets;
```
Current behavior:
- The remote KalamDB column stays `FILE`.
- The local PostgreSQL column is rewritten to `JSONB`.
- PostgreSQL sees a serialized `FileRef` payload with fields such as `id`, `sub`, `name`, `size`, `mime`, and `sha256`.
- Binary upload itself usually happens through KalamLink or another KalamDB file-aware client. PostgreSQL sees the resulting `FileRef` JSON representation.
## 11. Send Direct SQL to KalamDB
When you want to run a KalamDB statement directly, use `kalam_exec(...)`:
```sql
SELECT kalam_exec('ALTER TABLE app.orders ADD COLUMN priority INTEGER DEFAULT 0');
SELECT kalam_exec('DROP TABLE app.orders');
SELECT kalam_exec('SELECT * FROM system.tables');
```
`kalam_exec(...)` forwards the statement to the KalamDB server as-is.
- For `SELECT` statements, it returns a JSON array string.
- For DDL and DML statements, it returns a JSON string status message.
- It currently uses the default foreign server `kalam_server`.
- It is revoked from `PUBLIC`, so use a superuser or grant `EXECUTE` explicitly.
- It does not currently synchronize PostgreSQL `search_path` or current schema to the remote session automatically, so schema-qualify direct SQL.
## 12. Use a Non-Default Server Name
If you need a different foreign server name, create it and use the explicit `CREATE FOREIGN TABLE` path:
```sql
CREATE SERVER kalam_eu
FOREIGN DATA WRAPPER pg_kalam
OPTIONS (
host '10.0.0.25',
port '2910',
auth_header 'Bearer '
);
CREATE FOREIGN TABLE app.audit_log (
id BIGINT,
action TEXT,
payload JSONB
) SERVER kalam_eu
OPTIONS (
table_type 'shared'
);
```
Current relation-mapping rule: the local PostgreSQL schema and relation name are the remote namespace and table. `OPTIONS (namespace, table)` are not the runtime source of truth for scans and writes.
If you mirror a remote `FILE` column manually, declare the local PostgreSQL column as `JSONB`.
## 13. Maintain the Server Definition
Update server options with standard PostgreSQL syntax:
```sql
ALTER SERVER kalam_server OPTIONS (SET host 'kalamdb.internal', SET port '2910');
ALTER SERVER kalam_server OPTIONS (ADD auth_header 'Bearer ');
```
Remove the bridge when you are done:
```sql
DROP SERVER kalam_server CASCADE;
```
## Next Steps
- [Architecture](https://kalamdb.org/docs/pg-kalam/architecture)
- [SQL Syntax](https://kalamdb.org/docs/pg-kalam/sql-syntax)
- [Data Type Conversions](https://kalamdb.org/docs/pg-kalam/type-conversions)
- [Status & Limits](https://kalamdb.org/docs/pg-kalam/limitations)
================================================================================
# PostgreSQL Extension
URL: https://kalamdb.org/docs/pg-kalam
Source: content/pg-kalam/index.mdx
> Use the remote-only pg_kalam PostgreSQL extension to expose KalamDB tables in PostgreSQL and understand the current FDW, DDL, and type-conversion surface.
# PostgreSQL Extension
`pg_kalam` is a remote-only PostgreSQL foreign data wrapper extension. PostgreSQL hosts the FDW callbacks, DDL hook, and local relation surface, while KalamDB executes scans, writes, and mirrored DDL over gRPC on the shared RPC listener.
Prebuilt release assets are published on the [KalamDB GitHub Releases page](https://github.com/kalamdb/KalamDB/releases) for the current Linux x86_64 PostgreSQL extension target. Other platforms or PostgreSQL majors can still be built from source with `pgrx`.
KalamDB also publishes a ready-to-run Docker image with PostgreSQL and `pg_kalam` preinstalled at [jamals86/pg-kalam on Docker Hub](https://hub.docker.com/r/jamals86/pg-kalam).
This chapter documents the current supported remote-mode surface. The codebase no longer contains an embedded-mode PostgreSQL runtime.
Keep these rules in mind from the start:
- Point `CREATE SERVER ... OPTIONS (host, port)` at KalamDB's RPC listener, not the HTTP API port.
- Add `shared_preload_libraries = 'pg_kalam'` and restart PostgreSQL if you want `CREATE TABLE ... USING kalamdb` and the DDL mirroring hook to work.
- `CREATE TABLE ... USING kalamdb` and `kalam_exec(...)` currently assume a default foreign server named `kalam_server`.
- Use `CREATE FOREIGN TABLE ... SERVER ` when you need a non-default server or you want to mirror a relation explicitly.
## What You Can Do Today
- Install the extension into PostgreSQL 13 through 18, with `pg16` as the default build target.
- Create remote server definitions with host, port, optional auth header, timeout, and TLS material.
- Define KalamDB tables from PostgreSQL with `CREATE TABLE ... USING kalamdb`.
- Expose relations explicitly with `CREATE FOREIGN TABLE ... SERVER ...` when you need a non-default server name.
- Run `SELECT`, `INSERT`, `UPDATE`, and `DELETE` through FDW callbacks.
- Use top-level `BEGIN`, `COMMIT`, and `ROLLBACK` blocks for explicit transactions on shared and user tables.
- Observe bridge sessions and explicit transactions with `system.sessions` and `system.transactions`.
- Use `kalam_exec(...)` for direct remote SQL when you want to bypass the relation wrapper.
- Work with `FILE` columns as `JSONB` `FileRef` payloads on the PostgreSQL side.
## Current Capability Map
| Area | Status | Notes |
|---|---|---|
| Remote bridge | Available | `kalam_compiled_mode()` returns `remote` |
| `CREATE TABLE ... USING kalamdb` | Available with limits | Requires `shared_preload_libraries = 'pg_kalam'` and the default server `kalam_server` |
| `CREATE FOREIGN TABLE ... SERVER ...` | Available | Use this path for non-default server names and explicit mirroring |
| Shared and user table DML | Available | `SELECT`, `INSERT`, `UPDATE`, and `DELETE` run through FDW callbacks |
| Stream tables | Available with limits | Stream-table DDL works; explicit transactions reject stream-table writes |
| Explicit transactions | Available with limits | Top-level `BEGIN`, `COMMIT`, and `ROLLBACK`; no DDL inside explicit transactions |
| Pushdown | Partial | Projection plus simple equality filters; PostgreSQL still reapplies quals locally |
| Direct statement passthrough | Available with limits | `kalam_exec(...)` uses the default server and is revoked from `PUBLIC` |
| Helper SQL functions | Available | `kalam_version`, `kalam_compiled_mode`, `kalam_user_id`, `kalam_user_id_guc_name`, `snowflake_id`, `kalam_exec` |
| `IMPORT FOREIGN SCHEMA` | Not available | Remote mode currently returns an unsupported error |
| Data type conversions | Mixed | See [Data Type Conversions](https://kalamdb.org/docs/pg-kalam/type-conversions) for the exact current mapping and runtime caveats |
## Read This Section In Order
1. [Getting Started](https://kalamdb.org/docs/pg-kalam/getting-started)
2. [Architecture](https://kalamdb.org/docs/pg-kalam/architecture)
3. [SQL Syntax](https://kalamdb.org/docs/pg-kalam/sql-syntax)
4. [Data Type Conversions](https://kalamdb.org/docs/pg-kalam/type-conversions)
5. [Status & Limits](https://kalamdb.org/docs/pg-kalam/limitations)
## Recommended Workflow
Use the PostgreSQL extension when you want PostgreSQL tooling, SQL clients, or migrations to operate against KalamDB tables without switching your operators to the HTTP API or an SDK.
In most cases the flow is:
1. Download the matching `pg_kalam` release asset for your PostgreSQL major, or build it from source.
2. Install the extension into the exact PostgreSQL instance you will use and add it to `shared_preload_libraries`.
3. Give KalamDB a stable RPC address and point the default `kalam_server` foreign server at that endpoint.
4. Define tables with `CREATE TABLE ... USING kalamdb`, or use `CREATE FOREIGN TABLE ... SERVER ...` for non-default servers.
5. Set `kalam.user_id` in sessions that work with user-scoped tables.
6. Use top-level `BEGIN ... COMMIT` when you need explicit transaction batching on shared or user tables.
7. Use `kalam_exec(...)` only when you need direct remote SQL and can satisfy its privilege model.
## Related
- [SQL Reference](https://kalamdb.org/docs/server/sql-reference)
- [Data Types Reference](https://kalamdb.org/docs/server/architecture/datatypes)
- [Docker Deployment](https://kalamdb.org/docs/server/getting-started/docker)
- [API Reference](https://kalamdb.org/docs/server/api)
- [KalamDB PostgreSQL extension source guide](https://github.com/kalamdb/KalamDB/tree/main/pg)
================================================================================
# Status and Limits
URL: https://kalamdb.org/docs/pg-kalam/limitations
Source: content/pg-kalam/limitations.mdx
> Review the current remote-only scope, hard limits, and troubleshooting guidance for pg_kalam so the PostgreSQL extension is used within its actual code-backed surface.
# PostgreSQL Extension Status and Limits
The PostgreSQL extension is usable, but it is still an active development surface. Plan for incremental feature growth rather than assuming complete PostgreSQL FDW parity.
## Supported Today
- Remote-mode PostgreSQL bridge to a running KalamDB server
- Shared and user table reads and writes through FDW callbacks
- Stream-table authoring and normal FDW access outside explicit transactions
- Explicit `BEGIN`, `COMMIT`, and `ROLLBACK` transaction blocks for Kalam-backed tables
- `CREATE TABLE ... USING kalamdb` as the public PostgreSQL-side authoring syntax
- Auto-injected system columns: `_seq` on all tables and `_userid` on user tables
- Forwarding KalamDB table options from PostgreSQL `WITH (...)`
- Direct pass-through execution with `kalam_exec(...)`
- Session-scoped tenant routing with `kalam.user_id`
- Extension helper functions including `kalam_version()` and `SNOWFLAKE_ID()`
## Hard Limits and Current Constraints
| Area | Current behavior |
|---|---|
| Compile mode | `kalam_compiled_mode()` currently reports `remote`; this chapter documents that mode only |
| DDL hook | `CREATE TABLE ... USING kalamdb` requires `shared_preload_libraries = 'pg_kalam'` and a PostgreSQL restart |
| Default server binding | `CREATE TABLE ... USING kalamdb` and `kalam_exec(...)` currently assume the default foreign server name `kalam_server` |
| Schema import | `IMPORT FOREIGN SCHEMA` is not available in remote mode |
| Runtime mapping source | Relation schema and relation name are the runtime namespace and table; `OPTIONS (namespace, table)` are not the canonical scan/write mapping |
| Filter pushdown | Only simple equality filters are pushed down; PostgreSQL still reapplies quals locally |
| Order and limit pushdown | `ORDER BY` and `LIMIT` are not pushed down today |
| User scoping | User-table operations should set `kalam.user_id`; `_userid` is only an explicit `INSERT` override |
| `kalam.user_id` privileges | `kalam.user_id` is registered as a PostgreSQL `SUSET` parameter |
| Row identity for `UPDATE`/`DELETE` | The current FDW path uses the first non-system column as its row-identity heuristic |
| Explicit transactions | The documented surface is top-level `BEGIN`, `COMMIT`, and `ROLLBACK` blocks |
| DDL in transactions | DDL is rejected inside explicit transactions |
| Stream tables in transactions | Stream-table writes are rejected inside explicit transactions |
| Direct SQL helper | `kalam_exec(text)` is revoked from `PUBLIC` and does not automatically mirror PostgreSQL `search_path` or current schema |
| Type conversions | The logical mapping is broader than the extension's dedicated PostgreSQL runtime branches; see [Data Type Conversions](https://kalamdb.org/docs/pg-kalam/type-conversions) |
| Reserved columns | `_seq`, `_userid`, and `_deleted` are reserved names; only `_seq` and `_userid` are auto-injected into the current PostgreSQL relation surface |
Additional practical limits:
- `_seq` is read-only metadata projected from KalamDB. Values supplied in `INSERT` or `UPDATE` are not forwarded.
- Multi-row user-table `INSERT` statements must use one `_userid` value consistently.
- `UPDATE` and `DELETE` on user tables use the session `kalam.user_id` scope. Reassigning ownership through `_userid` updates is not supported.
- The server and table validators are permissive. Some bad option combinations fail on first use rather than at `CREATE SERVER` or `CREATE FOREIGN TABLE` time.
## Prototype Syntax vs Supported Syntax
This docs section focuses on the current remote-mode flow.
If you see older prototype scripts that mention experimental helpers such as schema opt-in functions, treat those as development artifacts unless they are covered in this chapter. The supported documentation surface today is:
- `CREATE EXTENSION`
- `CREATE SERVER` / `ALTER SERVER` / `DROP SERVER`
- `CREATE TABLE ... USING kalamdb`
- `BEGIN` / `COMMIT` / `ROLLBACK`
- `kalam_exec(...)`
- `SELECT`, `INSERT`, `UPDATE`, `DELETE`
- extension helper functions and `kalam.user_id`
## Troubleshooting
| Symptom | Likely cause | What to check |
|---|---|---|
| `extension "pg_kalam" is not available` | Extension files are not installed into the active PostgreSQL instance | Reinstall the extension into that PostgreSQL 16 installation |
| `could not access file "$libdir/pg_kalam"` | PostgreSQL cannot find the shared library | Verify the extension was installed into the correct PostgreSQL library directory |
| `CREATE TABLE ... USING kalamdb` does not mirror DDL | The preload hook is not active | Check `SHOW shared_preload_libraries` and confirm it includes `pg_kalam`, then restart PostgreSQL |
| Remote connection failure | The foreign-server host or port is wrong, the HTTP API port was used, or KalamDB is down | Verify the foreign server options and point them at the KalamDB RPC endpoint |
| Auth failures | `auth_header` is missing or stale | Update the foreign server definition with the right bearer token or shared secret |
| User-table reads or writes fail | `kalam.user_id` was not set, or the role cannot set it | Set `kalam.user_id` in the PostgreSQL session and verify the PostgreSQL role can change `SUSET` parameters |
| `permission denied for function kalam_exec` | `kalam_exec(text)` is revoked from `PUBLIC` | Use a superuser or `GRANT EXECUTE ON FUNCTION kalam_exec(text)` to the desired role |
| `UPDATE` or `DELETE` targets the wrong row | The table's primary key is not the first user column | Recreate the table with the primary key first or use a carefully controlled direct SQL path |
| `IMPORT FOREIGN SCHEMA` fails | Feature gap in current remote mode | Use `CREATE TABLE ... USING kalamdb` or `kalam_exec(...)` instead |
## Related
- [Getting Started](https://kalamdb.org/docs/pg-kalam/getting-started)
- [SQL Syntax](https://kalamdb.org/docs/pg-kalam/sql-syntax)
- [Data Type Conversions](https://kalamdb.org/docs/pg-kalam/type-conversions)
- [KalamDB PostgreSQL extension source guide](https://github.com/kalamdb/KalamDB/tree/main/pg)
================================================================================
# SQL Syntax
URL: https://kalamdb.org/docs/pg-kalam/sql-syntax
Source: content/pg-kalam/sql-syntax.mdx
> Reference the current pg_kalam SQL surface, including CREATE TABLE ... USING kalamdb, explicit foreign tables, system columns, direct SQL passthrough, and transaction constraints.
# PostgreSQL Extension SQL Syntax
This page documents the current SQL surface for the `pg_kalam` PostgreSQL extension.
The key implementation detail is that the extension exposes two different table-definition paths:
1. `CREATE TABLE ... USING kalamdb`
The shorthand authoring path backed by the DDL hook. This currently assumes the default server name `kalam_server`.
2. `CREATE FOREIGN TABLE ... SERVER `
The explicit PostgreSQL FDW path. Use this when you need a non-default server name or want to mirror a relation directly.
## Extension Metadata
Install and verify the extension:
```sql
CREATE EXTENSION IF NOT EXISTS pg_kalam;
SELECT kalam_version(), kalam_compiled_mode();
```
Current helper functions:
```sql
SELECT kalam_version();
SELECT kalam_compiled_mode();
SELECT kalam_user_id();
SELECT kalam_user_id_guc_name();
SELECT SNOWFLAKE_ID();
```
`kalam_compiled_mode()` should currently return `remote`.
## Foreign Server Syntax
Create the PostgreSQL foreign server that points at KalamDB:
```sql
CREATE SERVER IF NOT EXISTS kalam_server
FOREIGN DATA WRAPPER pg_kalam
OPTIONS (
host '127.0.0.1',
port '2910',
auth_header 'Bearer '
);
```
Update it with standard PostgreSQL FDW server syntax:
```sql
ALTER SERVER kalam_server OPTIONS (SET host 'kalamdb.internal', SET port '2910');
ALTER SERVER kalam_server OPTIONS (ADD auth_header 'Bearer ');
ALTER SERVER kalam_server OPTIONS (SET timeout '5000');
ALTER SERVER kalam_server OPTIONS (DROP auth_header);
```
Drop it when you no longer need the bridge:
```sql
DROP SERVER kalam_server CASCADE;
```
### Supported Server Options
| Option | Required | Purpose |
|---|---|---|
| `host` | Yes | KalamDB host name or IP |
| `port` | Yes | KalamDB gRPC port |
| `auth_header` | No | Authorization header sent to KalamDB |
| `timeout` | No | Request timeout in milliseconds |
| `ca_cert` | No | PEM CA bundle for TLS |
| `client_cert` | No | PEM client certificate for mTLS |
| `client_key` | No | PEM client private key for mTLS |
If you set either `client_cert` or `client_key`, you must set both.
Point `host` and `port` at KalamDB's shared RPC listener, not the HTTP API port.
## `CREATE TABLE ... USING kalamdb`
The public table-definition syntax is `CREATE TABLE ... USING kalamdb`:
```sql
CREATE TABLE app.orders (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
customer_id TEXT NOT NULL,
status TEXT DEFAULT 'pending',
created_at TIMESTAMP DEFAULT NOW()
) USING kalamdb WITH (
type = 'shared',
flush_policy = 'rows:1000,interval:60'
);
```
This does three things:
1. resolves the PostgreSQL schema and table name into the mirrored KalamDB namespace and table,
2. creates the target namespace and KalamDB table if needed,
3. creates the local foreign table surface and injects the PostgreSQL-visible system columns that `pg_kalam` manages locally.
### Shared table example
```sql
CREATE TABLE app.shared_items (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
title TEXT NOT NULL,
value INTEGER,
created_at TIMESTAMP DEFAULT NOW()
) USING kalamdb WITH (
type = 'shared',
flush_policy = 'rows:1000,interval:60'
);
```
### User table example
```sql
SET kalam.user_id = 'user-alice';
CREATE TABLE app.profiles (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
name TEXT,
age INTEGER
) USING kalamdb WITH (
type = 'user'
);
```
### Stream table example
```sql
CREATE TABLE app.events (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
event_type TEXT NOT NULL,
payload TEXT,
created_at TIMESTAMP DEFAULT NOW()
) USING kalamdb WITH (
type = 'stream',
ttl_seconds = '3600'
);
```
### Notes for `USING kalamdb`
- `USING kalamdb` selects the extension-managed PostgreSQL authoring path.
- `WITH (type = 'shared' | 'user' | 'stream')` selects the KalamDB table type.
- `PRIMARY KEY` and `DEFAULT SNOWFLAKE_ID()` are preserved in KalamDB.
- `SERIAL`, `BIGSERIAL`, `SMALLSERIAL`, and `GENERATED ... AS IDENTITY` are normalized into integer columns with `DEFAULT SNOWFLAKE_ID()` before the KalamDB DDL is sent.
- Additional `WITH (...)` options are forwarded to the mirrored KalamDB `CREATE TABLE` statement.
- This shorthand path currently depends on the DDL preload hook and the default server name `kalam_server`.
- The resulting PostgreSQL object is a foreign table, so follow-up DDL should use `ALTER FOREIGN TABLE` and `DROP FOREIGN TABLE`.
## `CREATE FOREIGN TABLE ... SERVER ...`
Use explicit foreign tables when you need a non-default server or want to mirror a remote relation directly:
```sql
CREATE SERVER kalam_eu
FOREIGN DATA WRAPPER pg_kalam
OPTIONS (
host '10.0.0.25',
port '2910',
auth_header 'Bearer '
);
CREATE FOREIGN TABLE app.audit_log (
id BIGINT,
action TEXT,
payload JSONB
) SERVER kalam_eu
OPTIONS (
table_type 'shared'
);
```
Current mapping rules:
- The local PostgreSQL schema is the remote namespace.
- The local PostgreSQL relation name is the remote table name.
- `OPTIONS (namespace, table)` are not the runtime source of truth for the current scan and modify paths.
- If a remote column is `FILE`, declare the local PostgreSQL column as `JSONB`.
## JSON and JSONB
`pg_kalam` maps PostgreSQL `JSON` and `JSONB` authoring types to KalamDB `JSON`.
On the `CREATE TABLE ... USING kalamdb` path, the local PostgreSQL column keeps the type you declared, so use `JSONB` when you want PostgreSQL's richer local JSON operator surface.
`FILE` is the special case: it is always mirrored locally as `JSONB`.
```sql
CREATE TABLE app.events (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
payload JSONB NOT NULL
) USING kalamdb WITH (
type = 'shared'
);
SELECT payload->>'kind' AS kind
FROM app.events
WHERE payload ? 'kind';
```
That syntax compatibility is the contract; PostgreSQL may still evaluate the JSONB expression locally on the mirrored value unless you explicitly route the statement through `kalam_exec(...)`.
When you do send SQL through `kalam_exec(...)`, KalamDB itself supports the same common PostgreSQL-style JSON operators:
```sql
SELECT kalam_exec($$
SELECT
payload->>'kind' AS kind,
payload->'metadata' AS metadata
FROM app.events
WHERE payload ? 'kind'
$$);
```
KalamDB-side JSON helper functions are also available, including `json_get`, `json_as_text`, `json_contains`, `json_length`, `json_object_keys`, and typed extractors such as `json_get_int`, `json_get_float`, and `json_get_bool`.
Current scope:
- KalamDB directly plans `->`, `->>`, and `?` today.
- Do not assume full PostgreSQL `jsonb` operator parity for operators such as `#>`, `#>>`, or `@>` yet.
## `FILE`
`FILE` is the main PostgreSQL extension-specific datatype shortcut.
```sql
CREATE TABLE app.assets (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
attachment FILE
) USING kalamdb WITH (
type = 'shared'
);
SELECT attachment->>'name', attachment->>'mime'
FROM app.assets;
```
Current `FILE` behavior:
- The remote KalamDB schema keeps the `FILE` logical type.
- The local PostgreSQL surface rewrites that column to `JSONB`.
- PostgreSQL reads and writes the serialized `FileRef` JSON representation.
- Binary upload and file lifecycle usually happen through KalamLink or another file-aware KalamDB client, not through a raw PostgreSQL `BYTEA` upload path.
## System Columns
`pg_kalam` manages system columns itself. Do not declare them explicitly in `CREATE TABLE ... USING kalamdb`.
| Column | Where it appears | Rules |
|---|---|---|
| `_seq` | All `pg_kalam` PostgreSQL tables | Auto-injected as `BIGINT`, populated from KalamDB read results, read-only from PostgreSQL, and not forwarded when supplied in `INSERT` or `UPDATE` |
| `_userid` | User tables only | Auto-injected as `TEXT`, exposed on reads, and usable as an explicit tenant override only for `INSERT` |
Current rules:
- `CREATE TABLE ... USING kalamdb (..., _seq BIGINT, ...)` is rejected. The same applies to `_userid` and `_deleted`.
- `_seq` is a read-only metadata column. Query it, sort by it, or return it to clients, but do not treat it as an application-managed field.
- For user tables, omitting `_userid` on `INSERT` uses the current `kalam.user_id` session value.
- For user tables, including `_userid` on `INSERT` overrides the session value for that inserted row batch.
- In a multi-row `INSERT`, all rows must use the same `_userid` value.
- `UPDATE` and `DELETE` continue to use the session's `kalam.user_id` scope. Reassigning ownership with `UPDATE ... SET _userid = ...` is not in the supported surface.
- `_deleted` is reserved but is not auto-injected into the current PostgreSQL relation surface.
Example:
```sql
SET kalam.user_id = 'user-alice';
INSERT INTO app.profiles (id, name, age)
VALUES (SNOWFLAKE_ID(), 'Alice', 30);
INSERT INTO app.profiles (id, name, age, _userid)
VALUES (SNOWFLAKE_ID(), 'Bob', 41, 'user-bob');
SELECT id, name, _userid, _seq
FROM app.profiles
ORDER BY _seq;
```
## Forwarded Table Options
Any supported KalamDB table option can be authored from PostgreSQL with the table `WITH (...)` clause.
That means PostgreSQL can act as the entry point for KalamDB-native table definitions as long as the resulting table options are valid for KalamDB once the extension forwards them.
### Common forwarded `WITH (...)` values
| Option | Purpose |
|---|---|
| `type` | Table type: `shared`, `user`, or `stream` |
| `flush_policy` | Hot-path flush behavior |
| `storage_id` | Target storage backend |
| `access_level` | Shared-table access policy |
| `ttl_seconds` | Stream retention TTL |
| `deleted_retention_hours` | Soft-delete retention window |
## Direct Statement Execution
To pass any statement directly to KalamDB, use `kalam_exec(...)`:
```sql
SELECT kalam_exec('ALTER TABLE app.orders ADD COLUMN priority INTEGER DEFAULT 0');
SELECT kalam_exec('DROP TABLE app.orders');
SELECT kalam_exec('SELECT * FROM system.tables');
```
`kalam_exec(...)` sends the SQL string to the KalamDB server as-is.
- For `SELECT` statements, it returns a JSON array string.
- For DDL and DML statements, it returns a JSON string message.
- It uses the default foreign server `kalam_server`.
- It is revoked from `PUBLIC`.
- It does not currently mirror PostgreSQL `search_path` or current schema automatically, so schema-qualify the statement.
If you were expecting a schema-qualified helper such as `kalam.exec(...)`, the currently exposed PostgreSQL function name is `kalam_exec(...)`.
## Query and Mutation Syntax
### Select
```sql
SELECT id, title, value
FROM app.shared_items
WHERE value >= 10
ORDER BY id;
```
### Insert
```sql
INSERT INTO app.shared_items (id, title, value)
VALUES (SNOWFLAKE_ID(), 'Alpha', 10), (SNOWFLAKE_ID(), 'Beta', 20);
```
`_seq` is visible for reads but is not a writable application column.
For PostgreSQL-style upsert and `RETURNING`, send the statement through `kalam_exec(...)` so
KalamDB executes it on the server:
```sql
SELECT kalam_exec($$
INSERT INTO app.shared_items (id, title, value)
VALUES (1, 'Beta', 20)
ON CONFLICT (id) DO UPDATE SET title = EXCLUDED.title, value = EXCLUDED.value
RETURNING id, title, value
$$);
```
See [/docs/server/sql-reference/dml](https://kalamdb.org/docs/server/sql-reference/dml) for upsert and `RETURNING`
rules and limits.
### Update
```sql
UPDATE app.shared_items
SET value = 25
WHERE id = 2;
```
### Delete
```sql
DELETE FROM app.shared_items
WHERE id = 1;
```
### User-scoped DML
```sql
SET kalam.user_id = 'user-alice';
INSERT INTO app.profiles (id, name, age)
VALUES ('p1', 'Alice', 30);
INSERT INTO app.profiles (id, name, age, _userid)
VALUES ('p2', 'Bob', 41, 'user-bob');
SELECT id, name, age, _userid, _seq
FROM app.profiles;
UPDATE app.profiles
SET age = 31
WHERE id = 'p1';
DELETE FROM app.profiles
WHERE id = 'p1';
RESET kalam.user_id;
```
In practice, `kalam.user_id` is a PostgreSQL `SUSET` setting, so the executing role must be allowed to change `SUSET` parameters.
## Explicit Transaction Syntax
The PostgreSQL extension supports explicit transaction control for Kalam-backed tables:
```sql
BEGIN;
START TRANSACTION;
COMMIT;
COMMIT WORK;
ROLLBACK;
ROLLBACK WORK;
```
### Commit example
```sql
BEGIN;
INSERT INTO app.shared_items (id, title, value)
VALUES (SNOWFLAKE_ID(), 'Alpha', 10);
UPDATE app.shared_items
SET value = 25
WHERE title = 'Alpha';
COMMIT;
```
### Read-your-writes and rollback example
```sql
BEGIN;
INSERT INTO app.shared_items (id, title, value)
VALUES (SNOWFLAKE_ID(), 'Draft', 99);
SELECT id, title, value
FROM app.shared_items
WHERE title = 'Draft';
ROLLBACK;
```
Within the same PostgreSQL transaction, reads see the staged writes from that transaction. On `COMMIT`, the staged writes are made durable in KalamDB. On `ROLLBACK`, they are discarded.
### Transaction observability
Use direct KalamDB SQL or `kalam_exec(...)` when you want to inspect the server-side session and transaction views from PostgreSQL.
Example via `kalam_exec(...)` for active pg bridge sessions:
```sql
SELECT kalam_exec(
'SELECT session_id, transaction_id, transaction_state, transaction_has_writes
FROM system.sessions
WHERE transaction_id IS NOT NULL
ORDER BY last_seen_at DESC, session_id'
);
```
Example via `kalam_exec(...)` for active explicit transactions across pg and native KalamDB SQL:
```sql
SELECT kalam_exec(
'SELECT transaction_id, owner_id, origin, state, write_count
FROM system.transactions
ORDER BY origin, transaction_id'
);
```
### Current limits inside explicit transactions
- Shared and user table DML are supported.
- Stream-table writes are rejected inside explicit transactions.
- DDL inside explicit transactions is not supported.
- Savepoints and PostgreSQL subtransactions are not yet supported.
## Row Identity for `UPDATE` and `DELETE`
The current FDW modify path uses the first non-system column as its row-identity heuristic.
Practical guidance:
- Keep the primary key as the first user column in the table definition.
- If you are mirroring an existing remote table and expect heavy `UPDATE` or `DELETE` use, validate the behavior against your exact schema before depending on it in production.
## Not Yet Supported
`IMPORT FOREIGN SCHEMA` is not yet supported in the current remote mode:
```sql
IMPORT FOREIGN SCHEMA app FROM SERVER kalam_server INTO app;
```
For now, use `CREATE TABLE ... USING kalamdb` for PostgreSQL-side authoring or `kalam_exec(...)` for direct server execution.
## Related
- [Getting Started](https://kalamdb.org/docs/pg-kalam/getting-started)
- [Architecture](https://kalamdb.org/docs/pg-kalam/architecture)
- [Data Type Conversions](https://kalamdb.org/docs/pg-kalam/type-conversions)
- [Status & Limits](https://kalamdb.org/docs/pg-kalam/limitations)
================================================================================
# Data Type Conversions
URL: https://kalamdb.org/docs/pg-kalam/type-conversions
Source: content/pg-kalam/type-conversions.mdx
> See how pg_kalam maps PostgreSQL types to KalamDB logical types, Arrow payloads, and back into PostgreSQL, including FILE and current runtime caveats.
# PostgreSQL Extension Data Type Conversions
This page documents the current conversion behavior in `pg_kalam`.
There are three distinct layers to keep straight:
1. PostgreSQL-side DDL authoring and logical schema mapping.
2. PostgreSQL datum conversion on writes.
3. Arrow batch materialization on reads.
The important constraint is that the canonical KalamDB logical type system is broader than the extension's dedicated PostgreSQL runtime branches. The logical mapping exists for more types than the extension can currently materialize natively on the PostgreSQL side.
If this page and the code diverge, the code wins.
## 1. PostgreSQL Authoring to KalamDB Logical Types
These are the common PostgreSQL-facing type names that map into KalamDB's logical schema model today.
| PostgreSQL-side authoring type | KalamDB logical type | Arrow or storage representation | Notes |
|---|---|---|---|
| `BOOLEAN`, `BOOL` | `Boolean` | `Boolean` | Dedicated native read and write branches exist |
| `SMALLINT`, `INT2` | `SmallInt` | `Int16` | Dedicated native read and write branches exist |
| `INTEGER`, `INT`, `INT4`, `MEDIUMINT` | `Int` | `Int32` | Dedicated native read and write branches exist |
| `BIGINT`, `INT8` | `BigInt` | `Int64` | Dedicated native read and write branches exist |
| `REAL`, `FLOAT`, `FLOAT4` | `Float` | `Float32` | Dedicated native read and write branches exist |
| `DOUBLE PRECISION`, `DOUBLE`, `FLOAT8`, `FLOAT64` | `Double` | `Float64` | Dedicated native read and write branches exist |
| `TEXT`, `VARCHAR`, `CHAR`, `STRING`, `NVARCHAR` | `Text` | `Utf8` | `TEXT` and `VARCHAR` have dedicated write fast paths; `CHAR` currently uses the generic text-output fallback on writes |
| `BYTEA`, `BYTES`, `BINARY`, `VARBINARY`, `BLOB` | `Bytes` | `Binary` | Read path has a dedicated `BYTEA` materializer; write path currently falls back to PostgreSQL text output |
| `JSON`, `JSONB` | `Json` | `Utf8` containing JSON text | JSON and JSONB are first-class in the extension on both read and write |
| `DATE` | `Date` | `Date32` | Read path has a dedicated `DATE` materializer; write path currently falls back to PostgreSQL text output |
| `TIME` | `Time` | `Time64(Microsecond)` | Logical mapping exists, but read and write still rely on generic conversion fallback |
| `TIMESTAMP` | `Timestamp` | `Timestamp(Microsecond, None)` | Read path has a dedicated timestamp materializer; write path currently falls back to PostgreSQL text output |
| `NUMERIC(p,s)`, `DECIMAL(p,s)` | `Decimal { precision, scale }` | `Decimal128(p, s)` | Logical mapping exists; PostgreSQL numeric materialization is not yet a dedicated branch |
| `UUID` | `Uuid` | `FixedSizeBinary(16)` | Logical mapping exists; PostgreSQL UUID materialization is not yet a dedicated branch |
| `FILE` | `File` | `Utf8` containing serialized `FileRef` JSON | The remote KalamDB column is `FILE`; the local PostgreSQL column is rewritten to `JSONB` on the `USING kalamdb` path |
Two special cases need separate callouts:
- `KalamDataType::DateTime` is the timezone-aware logical type and stores as `Timestamp(Microsecond, Some("UTC"))`. Its reverse PostgreSQL type name is `TIMESTAMPTZ`, but PostgreSQL-side timezone-aware authoring spellings should be validated against your exact build.
- `KalamDataType::Embedding(n)` is the logical embedding type. Its reverse PostgreSQL type name is `VECTOR(n)`, but the KalamDB logical DDL spelling is still `EMBEDDING(n)`, so PostgreSQL-side authoring should be validated carefully instead of assuming full shorthand parity.
## 2. KalamDB Logical Types Back to PostgreSQL
When the extension needs to generate or mirror PostgreSQL column type names from a KalamDB logical schema, it uses the following canonical mapping.
| KalamDB logical type | Generated or mirrored PostgreSQL type name |
|---|---|
| `Boolean` | `BOOLEAN` |
| `SmallInt` | `SMALLINT` |
| `Int` | `INTEGER` |
| `BigInt` | `BIGINT` |
| `Float` | `REAL` |
| `Double` | `DOUBLE PRECISION` |
| `Text` | `TEXT` |
| `Bytes` | `BYTEA` |
| `Date` | `DATE` |
| `Time` | `TIME` |
| `Timestamp` | `TIMESTAMP` |
| `DateTime` | `TIMESTAMPTZ` |
| `Uuid` | `UUID` |
| `Json` | `JSONB` |
| `File` | `JSONB` |
| `Decimal { precision, scale }` | `NUMERIC(p, s)` |
| `Embedding(n)` | `VECTOR(n)` |
Current local-surface rules modify that canonical reverse mapping in a few places:
- On `CREATE TABLE ... USING kalamdb`, most local PostgreSQL columns keep the type you declared.
- `JSON` stays `JSON` locally if you declared `JSON`.
- `JSONB` stays `JSONB` locally if you declared `JSONB`.
- `FILE` is the special case: it is always rewritten locally to `JSONB`.
If you mirror a remote `FILE` column manually with `CREATE FOREIGN TABLE`, declare the local PostgreSQL column as `JSONB`.
## 3. DDL Normalizations Before Remote Create
The DDL hook performs a small set of PostgreSQL-specific normalizations before sending the user-column definition to KalamDB.
| PostgreSQL authoring form | Remote KalamDB DDL result |
|---|---|
| `SERIAL` | Integer column plus `DEFAULT SNOWFLAKE_ID()` |
| `BIGSERIAL` | Bigint column plus `DEFAULT SNOWFLAKE_ID()` |
| `SMALLSERIAL` | Smallint column plus `DEFAULT SNOWFLAKE_ID()` |
| `GENERATED ... AS IDENTITY` | Explicit integer type plus `DEFAULT SNOWFLAKE_ID()` |
This normalization happens before the remote `CREATE TABLE` is sent.
## 4. Runtime Read and Write Behavior Today
The table above describes the logical schema contract. The next table describes the actual extension implementation status at runtime.
| Type family | PostgreSQL -> KalamDB write path | KalamDB -> PostgreSQL read path | Current status |
|---|---|---|---|
| `BOOLEAN`, `SMALLINT`, `INTEGER`, `BIGINT`, `REAL`, `DOUBLE PRECISION` | Dedicated native `Datum -> ScalarValue` branches | Dedicated native Arrow array branches | Best-covered native round-trip surface |
| `TEXT`, `VARCHAR` | Dedicated text fast path | Dedicated `Utf8` and `LargeUtf8` branches | Best-covered string surface |
| `CHAR`, `CHARACTER` | Generic PostgreSQL type-output fallback to `ScalarValue::Utf8` | Reads through the normal `Utf8` text path once the backend returns text | Logical mapping exists, but writes are not a dedicated branch |
| `JSON`, `JSONB` | Dedicated JSON and JSONB normalization to JSON text | Dedicated PostgreSQL type-input path for JSON and JSONB | Best-covered document surface |
| `FILE` | PostgreSQL sees the `FileRef` JSON representation, usually as `JSONB`; binary upload itself typically happens through KalamLink or another file-aware client | Dedicated JSONB materialization from UTF-8 `FileRef` JSON | Stable local representation, special-case local `JSONB` surface |
| `BYTEA` | Generic PostgreSQL type-output fallback to `ScalarValue::Utf8` | Dedicated Arrow `Binary -> bytea` branch | Read path is native; write path relies on generic coercion |
| `DATE` | Generic PostgreSQL type-output fallback to `ScalarValue::Utf8` | Dedicated Arrow `Date32 -> DATE` branch | Read path is native; write path relies on generic coercion |
| `TIMESTAMP` and generated `TIMESTAMPTZ` surface | Generic PostgreSQL type-output fallback to `ScalarValue::Utf8` | Dedicated Arrow timestamp branches reuse PostgreSQL's internal timestamp representation | Read path is native; write path relies on generic coercion |
| `TIME` | Generic PostgreSQL type-output fallback to `ScalarValue::Utf8` | Generic fallback string rendering | Not yet a dedicated PostgreSQL round-trip branch |
| `UUID` | Generic PostgreSQL type-output fallback to `ScalarValue::Utf8` | No dedicated UUID materialization branch in `arrow_to_pg` | Logical mapping exists; validate against your build before treating UUID as a stable PG round-trip surface |
| `NUMERIC(p,s)` | Generic PostgreSQL type-output fallback to `ScalarValue::Utf8` | No dedicated numeric materialization branch in `arrow_to_pg` | Logical mapping exists; native PostgreSQL numeric round-trip is not implemented yet |
| `VECTOR(n)` or `Embedding(n)` | PostgreSQL-side authoring depends on local type availability and current DDL spelling | No dedicated vector materialization branch in `arrow_to_pg` | Reverse mapping exists, but PostgreSQL vector round-trip is not yet a native branch |
## 5. Under the Hood
### Write path
For `INSERT`, `UPDATE`, and `DELETE`, the extension does the following:
1. PostgreSQL exposes a `TupleTableSlot` to the FDW modify callbacks.
2. `slot_to_row()` walks the local columns, skipping `_seq` and `_deleted`.
3. `_userid` is captured separately and only used as an explicit tenant override for `INSERT`.
4. `pg_to_kalam::datum_to_scalar()` converts each remaining datum into a DataFusion `ScalarValue`.
5. Supported primitive OIDs use dedicated branches.
6. Everything else currently falls back to PostgreSQL's type output function and becomes `ScalarValue::Utf8`.
7. The client serializes mutations as JSON row payloads in the gRPC request.
That is why the write path is broader than the dedicated fast-path branches but still depends on server-side coercion for several PostgreSQL type families.
### Read path
For `SELECT`, the extension does the following:
1. Builds a `ScanRequest` with relation identity, table type, tenant context, and simple equality filters.
2. Sends the request over gRPC.
3. Receives Arrow IPC batches from KalamDB.
4. `arrow_to_pg::arrow_value_to_datum()` materializes supported Arrow types directly into PostgreSQL datums.
5. JSON and JSONB use PostgreSQL type-input functions so PostgreSQL sees real document datums, not plain text.
6. Unsupported Arrow types fall back to `ScalarValue::to_string()`.
That final fallback is why logical schema support is currently broader than the stable PostgreSQL materialization surface.
## 6. What Is Safest Today
If you need the most predictable PostgreSQL behavior today, prefer these surfaces first:
- `BOOLEAN`, `SMALLINT`, `INTEGER`, `BIGINT`, `REAL`, `DOUBLE PRECISION`
- `TEXT` and `VARCHAR`
- `JSON`, `JSONB`, and `FILE` as local `JSONB`
- `DATE`, `TIMESTAMP`, and the timestamp-like generated `TIMESTAMPTZ` read path
Use extra care and validate against your target build for:
- `TIME`
- `UUID`
- `NUMERIC(p,s)`
- `VECTOR(n)` or other embedding-heavy PostgreSQL surfaces
The strongest current automated coverage in the extension repo is around JSON, JSONB, and `FILE` behavior. The rest of the mapping surface is primarily code-backed rather than broad end-to-end type-matrix coverage.
## Related
- [SQL Syntax](https://kalamdb.org/docs/pg-kalam/sql-syntax)
- [Status & Limits](https://kalamdb.org/docs/pg-kalam/limitations)
- [KalamDB Data Types Reference](https://kalamdb.org/docs/server/architecture/datatypes)
================================================================================
# Create a Table in Admin UI
URL: https://kalamdb.org/docs/server/admin-ui/create-table
Source: content/server/admin-ui/create-table.mdx
> Create a KalamDB table from the Admin UI table editor, define fields, choose storage options, and verify the schema in SQL Studio.
# Create a Table in the KalamDB Admin UI
Create a table in the KalamDB Admin UI with the visual table editor, where you define the namespace, table type, table name, storage options, flush policy, compression, and fields before reviewing the generated schema change.
## Open The Table Editor
Open SQL Studio, choose the target namespace, then select `New table` from the explorer toolbar.
The editor starts with:
- namespace context
- table type selector
- table name
- storage ID and optional user-storage toggle
- flush policy and compression controls
- column rows with primary key, type, nullable, unique, and default controls
- `Discard` and `Review & Create` actions
## Define Fields
For a starter `agent_events` table:
1. Set `Table name` to `agent_events`.
2. Keep the default `id` primary key unless your schema needs a different key.
3. Use `Add column` for each additional field.
4. Set field names such as `agent_id` and `event_type`.
5. Choose each field type from the type selector.
6. Adjust nullable, unique, and default values before review.
Use `Review & Create` to inspect the schema change, then confirm creation. The action is disabled until required fields are valid.
## Verify The Table
After creation, the table appears in the namespace explorer. Open SQL Studio and run:
```sql
SELECT id, agent_id, event_type
FROM default.agent_events
LIMIT 20;
```
If you create the table in another namespace, replace `default` with that namespace.
## Related SQL Reference
- [Tables](https://kalamdb.org/docs/server/sql-reference/tables)
- [Data Manipulation](https://kalamdb.org/docs/server/sql-reference/dml)
- [Query Data](https://kalamdb.org/docs/server/sql-reference/query-data)
================================================================================
# Admin UI Dashboard
URL: https://kalamdb.org/docs/server/admin-ui/dashboard
Source: content/server/admin-ui/dashboard.mdx
> Read the KalamDB Admin UI dashboard to inspect server health, activity summaries, active connections, and operational pressure.
# KalamDB Admin UI Dashboard
The KalamDB Admin UI dashboard is the first operational page for checking server health, recent activity, workload pressure, storage usage, and cluster status.
## What The Dashboard Shows
Use the dashboard before deeper inspection. It includes:
- session greeting and quick access to the statistics SQL view
- time-range selector and manual refresh
- summary cards for uptime, tables, namespaces, connections, subscriptions, pub/sub, total queries, query rate, and average latency
- charts for SQL query activity, connections and subscriptions, pub/sub consumers, resource usage, and manifest/parquet rates
- slow query table with timestamp, SQL statement, duration, priority, and pagination
- storage usage card with backend selector, used/free capacity, path, usage percentage, and health
- cluster node health with node count, leader status, active/joining state, memory, CPU, uptime, and replication lag
## When To Use It
Start here when you need to decide where to drill in next: SQL Studio for query behavior, Live Queries for subscription pressure, Streaming for topics and consumers, Logs & Analytics for slow queries and jobs, or Settings for runtime configuration.
================================================================================
# Admin UI
URL: https://kalamdb.org/docs/server/admin-ui
Source: content/server/admin-ui/index.mdx
> Use the KalamDB Admin UI to inspect server state, run SQL, manage users, monitor live queries, and operate realtime workloads.
# KalamDB Admin UI
The KalamDB Admin UI is the browser workspace for authentication-aware server operations, SQL Studio work, schema editing, user management, realtime monitoring, logging, and runtime configuration review.
## Admin UI Navigation
Open the Admin UI at:
```text
http://localhost:2900/ui
```
The sidebar groups the primary operator pages:
- [Login](https://kalamdb.org/docs/server/admin-ui/login) for local and OIDC browser authentication
- [Dashboard](https://kalamdb.org/docs/server/admin-ui/dashboard) for health cards, metric charts, slow queries, storage usage, and cluster node status
- [SQL Studio](https://kalamdb.org/docs/server/admin-ui/sql-studio) for namespace browsing, table inspection, saved query tabs, SQL execution, and visual schema editing
- [Create a Table](https://kalamdb.org/docs/server/admin-ui/create-table) for the visual table editor workflow
- [Streaming](https://kalamdb.org/docs/server/admin-ui/streaming) for topic inventory, topic inspection, consumer offsets, and topic-to-SQL navigation
- [Users](https://kalamdb.org/docs/server/admin-ui/users) for users, roles, pending invites, pagination, edits, and account lifecycle actions
- [Live Queries](https://kalamdb.org/docs/server/admin-ui/live-queries) for active WebSocket subscriptions, filters, change counts, and kill actions
- [Logging](https://kalamdb.org/docs/server/admin-ui/logging) for audit events, server logs, background jobs, and slow query analytics
- [Settings](https://kalamdb.org/docs/server/admin-ui/settings) for current-user context and grouped server, storage, cluster, backup, and security settings
## Shared Layout
Every authenticated page keeps the same shell:
- top bar with connection action, global search, WebSocket status, notifications, and current-user menu
- icon sidebar for Dashboard, SQL Studio, Streaming, Users, Live Queries, Logs, and Settings
- authenticated session context from the same login and token flow used by the HTTP and WebSocket APIs
## Authentication Context
The Admin UI discovers login modes from `/v1/api/auth/login-options` and uses the same local and OIDC authentication surface described in [Authentication & Bootstrap](https://kalamdb.org/docs/server/getting-started/authentication).
For OIDC deployments, register the browser redirect URI documented in [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc).
================================================================================
# Admin UI Jobs
URL: https://kalamdb.org/docs/server/admin-ui/jobs
Source: content/server/admin-ui/jobs.mdx
> Inspect KalamDB background jobs in Logs & Analytics, including status, job type, namespace, duration, node, and job details.
# KalamDB Admin UI Jobs
Jobs are shown inside the Admin UI [Logs & Analytics](https://kalamdb.org/docs/server/admin-ui/logging) workspace. The Jobs section lists background work and maintenance activity that can explain server load, storage behavior, and delayed operations.
## What Jobs Shows
The Jobs section includes:
- filters panel
- page-size selector and pagination
- refresh action
- status column
- job type column such as cleanup or stream eviction
- namespace/table context when the job applies to a data object
- created timestamp, duration, and node
- detail action for opening a specific job record
Pair this section with Server logs when a job needs event-level detail.
================================================================================
# Admin UI Live Queries
URL: https://kalamdb.org/docs/server/admin-ui/live-queries
Source: content/server/admin-ui/live-queries.mdx
> Inspect active KalamDB live queries in the Admin UI, including subscriptions, realtime workload behavior, and query activity.
# KalamDB Admin UI Live Queries
The Live Queries page shows active realtime subscriptions and WebSocket query activity that affect server load and user-facing updates.
## What Live Queries Shows
The page includes:
- active query count and total query count
- auto-refresh state
- filters for user ID, namespace, table, and status
- table columns for subscription ID, user, namespace, table, status, duration, change count, SQL query, serving node, and actions
- `Kill` action for terminating a problematic subscription
## When To Use It
Use this page to identify unexpectedly broad realtime queries, confirm client cleanup during disconnects, compare live query behavior with stream activity and logs, or stop a runaway subscription.
For the architecture model, see [Live Query Architecture](https://kalamdb.org/docs/server/architecture/live-query).
================================================================================
# Admin UI Logging
URL: https://kalamdb.org/docs/server/admin-ui/logging
Source: content/server/admin-ui/logging.mdx
> Use the KalamDB Admin UI Logging page to inspect server, job, authentication, and operational events during troubleshooting.
# KalamDB Admin UI Logging
The Logging page is the Logs & Analytics workspace for event-level troubleshooting across audit events, server logs, background jobs, and slow SQL.
## What Logs & Analytics Shows
The page is split into four sections:
- `Audit`: security and administrative activity
- `Server`: query-backed view of `system.server_logs` with log frequency graph, pause/refresh controls, text filter, case-sensitivity toggle, level filter, row limit selector, timestamp, level, target, and message
- `Jobs`: background job table with status, job type, namespace/table, created time, duration, node, filters, pagination, refresh, and detail action
- `Slow Queries`: slow query analytics for statements that exceed the configured threshold
## When To Use It
Use logs to confirm login/setup behavior, trace failed SQL or maintenance operations, correlate jobs with streaming and live-query events, and collect evidence before changing production configuration.
For log configuration, see [Logging Configuration](https://kalamdb.org/docs/server/configurations/logging).
================================================================================
# Admin UI Login
URL: https://kalamdb.org/docs/server/admin-ui/login
Source: content/server/admin-ui/login.mdx
> Sign in to the KalamDB Admin UI with local credentials or OpenID Connect, and verify the login methods exposed by the server.
# KalamDB Admin UI Login
The KalamDB Admin UI login page uses the server authentication configuration to show local password login, OpenID Connect login, setup routing, or a combination of those options.
## What Login Shows
The browser reads available methods from:
```bash
curl http://127.0.0.1:2900/v1/api/auth/login-options
```
Depending on server configuration, the page can show:
- local username/password fields
- provider button such as `Continue with Dex`
- setup link for an unconfigured node
- explanatory note for local cluster scripts and root credentials
- invalid-credentials feedback after failed local login
If local auth is enabled, sign in with the DBA account created during bootstrap. If OIDC is enabled, use the configured provider button and complete the provider flow.
## Related Authentication Pages
- [Authentication & Bootstrap](https://kalamdb.org/docs/server/getting-started/authentication)
- [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc)
- [Token Auth](https://kalamdb.org/docs/server/auth/token-auth)
================================================================================
# Admin UI Settings
URL: https://kalamdb.org/docs/server/admin-ui/settings
Source: content/server/admin-ui/settings.mdx
> Review KalamDB Admin UI settings to inspect exposed runtime configuration, authentication state, and operational defaults.
# KalamDB Admin UI Settings
The Settings page shows current-user context, runtime configuration, and operational defaults exposed by the server to authenticated administrators.
## What Settings Shows
The Settings page includes a left-side settings section menu:
- `All Settings`
- `General`
- `Cluster`
- `Storages`
- `Backup`
- `Security`
The main view starts with current admin session details: username, role, email, and user ID. It then groups configuration values such as server host/port/workers, public origin, storage paths and RocksDB profiles, cluster ID, DataFusion limits, query limits, logging format/path, flush defaults, manifest cache, stream TTLs, topic retention, WebSocket timeouts, and rate limits.
## When To Use It
Use this page to confirm the server origin and Admin UI paths match deployment expectations, verify authentication and security posture, check storage and backup configuration, and review exposed runtime defaults before production rollout.
For the full config matrix, see [Advanced Configuration](https://kalamdb.org/docs/server/configurations/advanced).
================================================================================
# Admin UI SQL Studio
URL: https://kalamdb.org/docs/server/admin-ui/sql-studio
Source: content/server/admin-ui/sql-studio.mdx
> Use KalamDB SQL Studio to browse namespaces and tables, run SQL statements, inspect results, and validate schema changes.
# KalamDB Admin UI SQL Studio
SQL Studio is the Admin UI workspace for exploring namespaces, writing SQL, checking table metadata, editing schemas, and validating query results.
## What SQL Studio Contains
SQL Studio has two main work areas:
- `Explorer`: namespace picker, namespace create/drop actions, table filter, favorites, table list, table expansion, and table drop actions
- `Editor`: table editor and query editor modes, saved/draft query tabs, Monaco SQL editor, results/log tabs, live toggle, save/run controls, execute options, and query actions
When a table is selected, the right pane can show metadata and editable schema fields. For new tables, the same area becomes the visual table editor described in [Create a Table](https://kalamdb.org/docs/server/admin-ui/create-table).
## Run SQL
Start with a system query:
```sql
SELECT * FROM system.namespaces LIMIT 100;
```
Then inspect the result grid and execution details.
## Table And Query Workflows
Use this page to:
- inspect namespaces and table columns without leaving the browser
- save and reopen common query tabs
- run SQL and review results or execution logs
- turn live mode on for realtime result updates where supported
- switch from table inspection into the visual table editor
Use [Create a Table](https://kalamdb.org/docs/server/admin-ui/create-table) for the first schema workflow, then return to SQL Studio to query and verify inserted rows.
================================================================================
# Admin UI Streaming
URL: https://kalamdb.org/docs/server/admin-ui/streaming
Source: content/server/admin-ui/streaming.mdx
> Monitor KalamDB streaming topics from the Admin UI, inspect topic activity, and connect realtime stream behavior to SQL topics.
# KalamDB Admin UI Streaming
The Streaming page shows topic-oriented realtime activity so operators can inspect topic inventory, consumer state, and stream behavior from the Admin UI.
## What Streaming Shows
The Streaming area has separate views for topics and consumers:
- `Topics`: searchable topic inventory with topic name, partitions, routes, retention window, last update time, and actions
- `Consumers`: consumer and offset monitoring for active consumer groups and claims
- topic actions to open the relevant SQL view or inspect a topic in place
- refresh controls for pulling current topic and consumer state
## When To Use It
Use this page to confirm expected topics are visible, check retention and routing metadata, inspect producer/consumer behavior, and correlate stream activity with Jobs and Logs.
For SQL topic setup, see [SQL Topics](https://kalamdb.org/docs/server/topic-pubsub/sql-topics).
================================================================================
# Admin UI Users
URL: https://kalamdb.org/docs/server/admin-ui/users
Source: content/server/admin-ui/users.mdx
> Use the KalamDB Admin UI Users page to inspect local users, roles, OIDC identities, and account state during auth operations.
# KalamDB Admin UI Users
The Users page is the Admin UI surface for reviewing KalamDB identities, roles, pending invites, and authentication state.
## What Users Shows
The Users page includes:
- search box for filtering users
- page-size selector and previous/next pagination controls
- refresh action
- `Create User` action
- `Pending Invites` table with invite ID, email, role, expiration, creation time, reinvite action, and delete action
- `Users` table with name, user ID, role, email, creation time, edit action, and delete action
- protected handling for system/root accounts where destructive actions may be disabled
## When To Use It
Use this page after bootstrap, local-user changes, or OIDC rollout to confirm the DBA account exists, roles match expectations, invites are still valid, and service/system accounts are not accidentally removed.
For SQL user commands, see [Users SQL Reference](https://kalamdb.org/docs/server/sql-reference/users).
================================================================================
# HTTP API Reference
URL: https://kalamdb.org/docs/server/api/http-reference
Source: content/server/api/http-reference.mdx
> Complete HTTP API reference for KalamDB. Explore route maps, auth rules, request/response payloads, error codes, and endpoint contracts.
# HTTP API Reference
Base URL: `http://:2900`
Version prefix: `/v1`
## Route Map
### Health and status
- `GET /health` (localhost-only)
- `GET /v1/api/healthcheck` (localhost-only)
- `GET /v1/api/cluster/health` (localhost-only)
### SQL and files
- `POST /v1/api/sql`
- `GET /v1/files/{namespace}/{table_name}/{subfolder}/{file_id}`
### WebSocket
- `GET /v1/ws`
### Auth
- `POST /v1/api/auth/login`
- `POST /v1/api/auth/refresh`
- `POST /v1/api/auth/logout`
- `GET /v1/api/auth/me`
- `POST /v1/api/auth/setup`
- `GET /v1/api/auth/status`
### Topic HTTP API
- `POST /v1/api/topics/consume`
- `POST /v1/api/topics/ack`
## Authentication Rules
### Bearer token required
```http
Authorization: Bearer
```
- `POST /v1/api/sql`
- `GET /v1/files/...`
- `POST /v1/api/topics/consume`
- `POST /v1/api/topics/ack`
Basic auth is rejected on these endpoints.
### Cookie or bearer accepted
- `POST /v1/api/auth/refresh`
- `GET /v1/api/auth/me`
### Public endpoints
- `POST /v1/api/auth/login`
- `POST /v1/api/auth/logout`
- `POST /v1/api/auth/setup` (localhost-only unless `auth.allow_remote_setup = true`)
- `GET /v1/api/auth/status` (localhost-only unless remote setup enabled)
- `GET /health` and `GET /v1/api/healthcheck` (localhost-only)
- `GET /v1/api/cluster/health` (localhost-only)
## SQL Endpoint
### `POST /v1/api/sql`
Headers:
- `Authorization: Bearer `
- `Content-Type: application/json` or `multipart/form-data`
### JSON body
```json
{
"sql": "SELECT * FROM app.messages WHERE id = $1",
"params": [123],
"namespace_id": "app"
}
```
`namespace_id` applies only to that request. Interactive clients such as the
CLI can store the namespace locally after a successful `USE namespace` and send
it again on later requests.
### Multipart body for `FILE(...)`
Parts:
- `sql`
- `params` (optional JSON array string)
- `namespace_id` (optional request-scoped default namespace)
- file parts named `file:`
SQL:
```sql
INSERT INTO app.docs (id, attachment) VALUES ('d1', FILE("contract"));
```
Multipart file key must be `file:contract`.
### Success shape
`SELECT` statements and `INSERT ... ON CONFLICT ... DO UPDATE ... RETURNING` statements
return query rows in `results[]`:
```json
{
"status": "success",
"results": [
{
"schema": [{"name":"id","data_type":"BigInt","index":0}],
"rows": [[1]],
"row_count": 1,
"as_user": "alice"
}
],
"took": 12.3
}
```
Plain `INSERT`, `UPDATE`, and `DELETE` statements without `RETURNING` return an affected-row
count instead of data rows. Upsert without `RETURNING` follows the insert result shape.
Example upsert that returns rows:
```sql
INSERT INTO app.items (id, name)
VALUES (1, 'beta')
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name
RETURNING id, name;
```
### Error shape
```json
{
"status": "error",
"results": [],
"took": 1.2,
"error": {
"code": "INVALID_SQL",
"message": "...",
"details": null
}
}
```
### Common SQL error codes
- `INVALID_SQL`
- `PERMISSION_DENIED`
- `TABLE_NOT_FOUND`
- `RATE_LIMIT_EXCEEDED`
- `NOT_LEADER`
- `CLUSTER_UNAVAILABLE`
- `FILE_TOO_LARGE`
- `TOO_MANY_FILES`
- `INVALID_MIME_TYPE`
## File Download
### `GET /v1/files/{namespace}/{table_name}/{subfolder}/{file_id}`
- bearer token required
- optional `user_id` query parameter for user-table scope resolution
- `SYSTEM` and `STREAM` tables are rejected for file paths
Response:
- `200` with binary content
- `400/403/404` for validation, permission, or not-found failures
## Auth Endpoints
### `POST /v1/api/auth/login`
```json
{
"user": "alice",
"password": "Secret123!"
}
```
Returns user profile + access/refresh tokens and sets auth cookie.
### `POST /v1/api/auth/refresh`
Accepts bearer token or cookie. Returns rotated token pair.
### `POST /v1/api/auth/logout`
Clears auth cookie.
### `GET /v1/api/auth/me`
Returns current authenticated user info.
### `POST /v1/api/auth/setup`
Initial bootstrap only, when root has no password yet.
```json
{
"user": "admin",
"password": "AdminPass123!",
"root_password": "RootPass123!",
"email": "admin@example.com"
}
```
Sets root password and creates the DBA user. Does not auto-login.
### `GET /v1/api/auth/status`
Returns setup status:
```json
{
"needs_setup": true,
"message": "Server requires initial setup..."
}
```
## Topic Endpoints
Both require bearer auth and role in `{service, dba, system}`.
### `POST /v1/api/topics/consume`
```json
{
"topic_id": "orders_topic",
"group_id": "worker_group",
"start": "Latest",
"limit": 100,
"partition_id": 0,
"timeout_seconds": 10
}
```
Accepted `start` formats:
- `"Latest"`
- `"Earliest"`
- `{ "Offset": 123 }`
### `POST /v1/api/topics/ack`
```json
{
"topic_id": "orders_topic",
"group_id": "worker_group",
"partition_id": 0,
"upto_offset": 10
}
```
## Health Endpoints
`GET /health` and `GET /v1/api/healthcheck` return the same payload and are localhost-only:
```json
{
"status": "healthy",
"version": "...",
"api_version": "v1",
"build_date": "..."
}
```
For live protocol details, continue to [WebSocket Protocol](https://kalamdb.org/docs/server/api/websocket-protocol).
================================================================================
# API Reference
URL: https://kalamdb.org/docs/server/api
Source: content/server/api/index.mdx
> Complete HTTP and WebSocket API reference for KalamDB. Learn how to integrate backend services, build custom clients, and use realtime streaming endpoints.
# API Reference
This root chapter contains transport-level contracts for backend integrations, services, and custom clients.
- [HTTP API Reference](https://kalamdb.org/docs/server/api/http-reference): Routes, auth rules, request/response payloads, and error codes.
- [WebSocket Protocol](https://kalamdb.org/docs/server/api/websocket-protocol): Auth handshake, subscription lifecycle, message schema, limits, and protocol errors.
For SQL command syntax, use [SQL Reference](https://kalamdb.org/docs/server/sql-reference).
For first-time server setup/login endpoint usage, see [Authentication & Bootstrap](https://kalamdb.org/docs/server/getting-started/authentication).
================================================================================
# WebSocket Protocol
URL: https://kalamdb.org/docs/server/api/websocket-protocol
Source: content/server/api/websocket-protocol.mdx
> KalamDB WebSocket protocol reference. Learn about connection handshakes, auth flows, message contracts, rate limits, and realtime stream behavior.
# WebSocket Protocol
Endpoint: `ws://:2900/v1/ws`
Transport: RFC 6455
Message format: JSON text frames (server may send gzip-compressed binary frames for larger payloads)
## Connection And Authentication
### Upgrade
Client opens `GET /v1/ws`.
Server can reject pre-upgrade for:
- shutdown mode
- origin policy failure
- strict origin check with missing `Origin` header
### Origin policy
Origin allow-list resolution:
1. `security.allowed_ws_origins` if non-empty
2. otherwise `security.cors.allowed_origins`
### Post-connect auth (required)
```json
{
"type": "authenticate",
"method": "jwt",
"token": ""
}
```
Success:
```json
{
"type": "auth_success",
"user_id": "u_...",
"role": "Dba"
}
```
Failure:
```json
{
"type": "auth_error",
"message": "Invalid username or password"
}
```
## Client Messages
- `authenticate`
- `subscribe`
- `next_batch`
- `unsubscribe`
### `subscribe`
```json
{
"type": "subscribe",
"subscription": {
"id": "orders_live",
"sql": "SELECT * FROM app.orders WHERE status = 'open'",
"options": {
"batch_size": 200,
"last_rows": 100,
"from": 12345
}
}
}
```
### `next_batch`
```json
{
"type": "next_batch",
"subscription_id": "orders_live",
"last_seq_id": 12345
}
```
### `unsubscribe`
```json
{
"type": "unsubscribe",
"subscription_id": "orders_live"
}
```
## Server Messages
- `auth_success` / `auth_error`
- `subscription_ack`
- `initial_data_batch`
- `change`
- `error`
### `subscription_ack`
```json
{
"type": "subscription_ack",
"subscription_id": "orders_live",
"total_rows": 0,
"batch_control": {
"batch_num": 0,
"has_more": true,
"status": "loading",
"last_seq_id": 12345,
"snapshot_end_seq": 13000
},
"schema": [
{"name":"id","data_type":"BigInt","index":0}
]
}
```
### `initial_data_batch`
```json
{
"type": "initial_data_batch",
"subscription_id": "orders_live",
"rows": [{"id":1,"status":"open","_seq":12346}],
"batch_control": {
"batch_num": 0,
"has_more": true,
"status": "loading",
"last_seq_id": 12346,
"snapshot_end_seq": 13000
}
}
```
### `change`
```json
{
"type": "change",
"subscription_id": "orders_live",
"change_type": "insert",
"rows": [{"id":2,"status":"open","_seq":13001}]
}
```
`change_type` values:
- `insert`
- `update`
- `delete`
### `error`
```json
{
"type": "error",
"subscription_id": "orders_live",
"code": "INVALID_SQL",
"message": "..."
}
```
## Limits
- `rate_limit.max_subscriptions_per_user` (default `10`)
- hard cap per connection: `100`
- `security.max_ws_message_size` (default `1MB`)
- `rate_limit.max_messages_per_sec` (default `50`)
- auth timeout: `websocket.auth_timeout_secs` (default `3`)
## Error Codes
Common WebSocket protocol/error codes include:
- `AUTH_REQUIRED`
- `INVALID_SUBSCRIPTION_ID`
- `SUBSCRIPTION_LIMIT_EXCEEDED`
- `INVALID_SQL`
- `RATE_LIMIT_EXCEEDED`
- `MESSAGE_TOO_LARGE`
- `UNSUPPORTED_DATA`
- `PROTOCOL`
For endpoint auth matrix and SQL payload contracts, see [HTTP API Reference](https://kalamdb.org/docs/server/api/http-reference).
================================================================================
# Clustering & HA
URL: https://kalamdb.org/docs/server/architecture/clustering
Source: content/server/architecture/clustering.mdx
> Learn how KalamDB clustering works with Raft consensus for high availability, leader election, log replication, and automatic failover.
# Clustering & High Availability
KalamDB uses a **Multi-Raft** architecture to scale reads, writes, and subscriptions across nodes while keeping data strongly replicated per shard.
Cluster routing depends on table type: `USER` and `STREAM` data route by effective `user_id`, while
`SHARED` data currently routes through the shared data group. For table-type semantics, see
[/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types).
## How the Cluster Is Structured
KalamDB does not run a single Raft group for everything. It runs multiple groups:
- `Meta`: cluster/system metadata
- `DataUserShard(N)`: user-data shards
- `DataSharedShard(N)`: shared-data shards
Each group has its own leader and followers. This means leadership is distributed across the cluster, not centralized to a single node for all workloads.
Sharding is handled by a router that hashes user/table identifiers to shard groups, so users are naturally distributed across shards. This is how KalamDB scales tenant load and isolates shard-level hotspots.
A single node participates in multiple Raft groups simultaneously. One node is not limited to one group; instead, each node can be a leader for some groups and a follower for others at the same time.
## Multi-Raft Cluster Diagram
```mermaid
flowchart TB
subgraph Cluster["KalamDB Cluster"]
%% Nodes
N1["Node 1"]
N2["Node 2"]
N3["Node 3"]
%% Meta Group
subgraph Meta["Meta Raft Group"]
M1["Leader (Node 1)"]
M2["Follower (Node 2)"]
M3["Follower (Node 3)"]
end
%% User Shard Example
subgraph UserShard7["DataUserShard(7)"]
U1["Leader (Node 2)"]
U2["Follower (Node 1)"]
U3["Follower (Node 3)"]
end
%% Another User Shard
subgraph UserShard12["DataUserShard(12)"]
U4["Leader (Node 3)"]
U5["Follower (Node 1)"]
U6["Follower (Node 2)"]
end
%% Shared Shard
subgraph SharedShard["DataSharedShard(0)"]
S1["Leader (Node 1)"]
S2["Follower (Node 2)"]
S3["Follower (Node 3)"]
end
end
%% Routing
Client["Client Request"]
Router["Shard Router (hash user_id / table)"]
Client --> Router
Router --> UserShard7
Router --> UserShard12
Router --> SharedShard
%% Raft replication arrows
U1 --> U2
U1 --> U3
U4 --> U5
U4 --> U6
S1 --> S2
S1 --> S3
M1 --> M2
M1 --> M3
```
### How to read this diagram
- The cluster has multiple Raft groups, not one.
- Each group such as `Meta`, `DataUserShard(N)`, or `DataSharedShard(N)` runs its own Raft consensus.
- Each group has one leader that handles writes and followers that replicate data and can serve follower-side access patterns.
- A single node participates in many groups at once, which is why Node 1, Node 2, and Node 3 appear repeatedly across the diagram.
Example:
- `DataUserShard(7)` has its leader on Node 2.
- `DataUserShard(12)` has its leader on Node 3.
So:
- load is spread across nodes
- there is no single leader bottleneck
- multiple writes can happen in parallel across different groups
### Why this matters
Instead of one cluster having one leader and turning that leader into a bottleneck, one cluster has many leaders, one per active Raft group.
That is the core scaling property of Multi-Raft in KalamDB.
### Tiny mental model
```text
user-1 -> shard 7 -> leader on Node 2
user-2 -> shard 12 -> leader on Node 3
user-3 -> shard 7 -> leader on Node 2
```
So:
- the same user stays on the same shard for consistent writes
- different users can land on different leaders for parallel scaling
## High Concurrent Connection Model
KalamDB accepts WebSocket clients on any node and manages them in a lock-free connection registry:
- `ConnectionsManager` uses `DashMap` indexes for fast concurrent connection/subscription access.
- Connection lifecycle, authentication timeout, heartbeat timeout, and subscription routing are all centralized there.
- The default concurrent connection guard is high (`10,000`), with configurable limits.
This allows high fan-in of realtime clients while keeping routing overhead low.
## Leader/Follower Access Pattern
Clients can connect to either leader or follower nodes.
- **Reads/subscription bootstrap** can be served on the node the client is connected to.
- **Writes** are always leader-authoritative per Raft group.
- If a request lands on a follower for a write path, KalamDB forwards it to the current leader.
- If leadership changed mid-request, typed `NotLeader` handling retries through the known leader.
This gives you flexible client routing without sacrificing consistency for writes.
## Notification Forwarding Across Nodes
Live subscribers do not need to be connected to the leader node for their shard.
When data changes:
1. The shard leader processes the change notification.
2. The leader broadcasts forwarded notifications to follower nodes over cluster RPC.
3. Each follower dispatches to subscriptions connected locally on that follower.
This is the key reason you can attach users to any node and still receive correct realtime updates for their shard data.
```mermaid
flowchart LR
A["Client connected to Follower"] --> B["Follower stores local WS subscription"]
C["Write hits shard leader"] --> D["Leader commits via Raft"]
D --> E["Leader emits change notification"]
E --> F["Leader broadcasts notify_followers RPC"]
F --> G["Follower receives forwarded event"]
G --> H["Follower pushes update to local client"]
```
## Sharding + HA in Practice
KalamDB shards users across multiple Raft data groups. Each shard group has its own:
- leader election
- replication log
- follower set
- failover boundary
Operationally, this gives you:
- better parallelism (many active leaders across groups)
- shard-level fault isolation
- horizontal scaling by increasing cluster nodes and shard count
- high availability from follower replicas per shard
When a node fails, Raft elects new leaders for affected groups and clients can reconnect to any healthy node.
## Supported Cluster Commands
Cluster operations are surfaced through CLI meta-commands. See
[Interactive Commands — cluster](https://kalamdb.org/docs/cli/interactive-commands)
for the operator command list.
For raw inspection without CLI formatting, query `system.cluster` and `system.cluster_groups` directly. Old `CLUSTER LIST` / `STATUS` / `LS` SQL forms are replaced by the CLI and system views.
`CLUSTER LEAVE` remains unsupported. `CLUSTER TRANSFER LEADER` is still routed by the backend, but some builds can report it as unsupported at runtime depending on the OpenRaft version in use.
## How user data is divided across groups
KalamDB uses Multi-Raft rather than one Raft group for the whole cluster.
- One group handles metadata.
- Multiple user data groups handle user and stream data.
- The target user group is chosen by hashing `user_id` into one of `cluster.user_shards` groups.
That means all writes for a specific user are coordinated by the same user-data group leader at a given time. The data is still replicated to that group's followers, but the active write path for one user's workload is not scattered across many leaders. This improves locality and reduces cross-group coordination, which is one of the reasons the cluster can stay fast as concurrency grows.
## Follower insert example
Suppose `user-42` is mapped to `DataUserShard(7)`, and the client sends an insert to node 2:
```bash
kalam --url http://node-2:2900 --command "INSERT INTO app.messages (id, body) VALUES (101, 'hello')"
```
If node 2 is a follower for `DataUserShard(7)`, KalamDB:
1. prepares and classifies the SQL once,
2. resolves the target group from the table type and effective `user_id`,
3. forwards the original SQL, params, auth header, and request id to the leader of `DataUserShard(7)`,
4. lets that leader append and replicate the write through Raft,
5. returns the committed result back through the follower to the client.
So clients can write through followers, but the actual commit still happens on the correct group leader.
## Shared tables today
Shared tables are currently not truly sharded. Today they route to a single shared data group.
More flexible shared-table sharding is a work in progress. The planned direction is partition-by-key so each shared table can define how rows are partitioned and which group should own them.
## Configuration Knobs
In cluster mode, tune shard topology from cluster config:
- `cluster.user_shards`
- `cluster.shared_shards`
These values determine how many independent Raft data groups exist for user and shared workloads.
To view all the configurations for the Cluster setup:
- [/docs/server/configurations/advanced#cluster](https://kalamdb.org/docs/server/configurations/advanced#cluster)
## Deploying a 3-Node Cluster
The quickest way to start a cluster is with Docker:
```bash
KALAMDB_JWT_SECRET="$(openssl rand -base64 32)" \
curl -sSL https://raw.githubusercontent.com/kalamdb/KalamDB/main/docker/run/cluster/docker-compose.yml | docker-compose -f - up -d
```
## Backup & Restore
```sql
-- Backup a namespace
BACKUP DATABASE myapp TO '/backups/myapp-2026-02-18';
-- Restore from backup
RESTORE DATABASE myapp FROM '/backups/myapp-2026-02-18';
-- View backup history
SHOW BACKUPS FOR DATABASE myapp;
```
## Related Docs
- [/docs/server/sql-reference/cluster](https://kalamdb.org/docs/server/sql-reference/cluster)
- [Interactive Commands — cluster](https://kalamdb.org/docs/cli/interactive-commands)
- [/docs/server/configurations/advanced#cluster](https://kalamdb.org/docs/server/configurations/advanced#cluster)
- [/docs/server/architecture/live-query](https://kalamdb.org/docs/server/architecture/live-query)
- [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types)
================================================================================
# Data Types
URL: https://kalamdb.org/docs/server/architecture/datatypes
Source: content/server/architecture/datatypes.mdx
> Practical guide to choosing and using KalamDB data types in SQL. Covers BIGINT, TEXT, BOOLEAN, TIMESTAMP, FILE, EMBEDDING, and more with examples.
# Data Types
This page is a practical guide for **using data types in SQL** when building with KalamDB.
Data types are independent from table types, but some types have table-type-specific behavior. `FILE`
columns are supported on `USER` and `SHARED` tables, and `EMBEDDING(n)` columns become most useful
with vector search. See [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types)
for the table boundary.
## Type Selection Flow
```mermaid
flowchart TD
A["Data you need to store"] --> B{"Structured document?"}
B -- "Yes" --> C["JSON"]
B -- "No" --> D{"Time-based value?"}
D -- "Timestamped event" --> E["TIMESTAMP"]
D -- "Calendar date only" --> F["DATE"]
D -- "Not time" --> G{"Exact arithmetic needed?"}
G -- "Yes (money/rates)" --> H["DECIMAL(p,s)"]
G -- "No" --> I{"Whole number?"}
I -- "Small/medium" --> J["INT"]
I -- "Large / IDs" --> K["BIGINT"]
I -- "No" --> L{"Boolean?"}
L -- "Yes" --> M["BOOLEAN"]
L -- "No" --> N["TEXT"]
```
## Quick Type Selection
Use this table when deciding a column type:
| You store... | Use | Why |
|---|---|---|
| IDs from `SNOWFLAKE_ID()` | `BIGINT` | 64-bit integer ID generation |
| Small whole numbers (status, counters) | `INT` | Compact and fast |
| Large counters | `BIGINT` | Higher range |
| User-facing text | `TEXT` | Flexible string storage |
| True/false values | `BOOLEAN` | Clear semantics |
| Decimal money-like values | `DECIMAL(p,s)` | Exact precision |
| Timestamps | `TIMESTAMP` | Time-based queries and ordering |
| Dates only | `DATE` | Calendar values without time |
| Structured document payload | `JSON` | Nested object/array data |
| Vector embeddings | `EMBEDDING(n)` | Semantic retrieval and similarity search |
## Common Types in Practice
### Integer Types
```sql
CREATE TABLE app.orders (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
user_id BIGINT NOT NULL,
item_count INT NOT NULL DEFAULT 1
) WITH (TYPE = 'USER');
```
- Prefer `INT` for bounded counts.
- Prefer `BIGINT` for IDs and values that can grow large.
### Text and Boolean
```sql
CREATE TABLE app.users (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
email TEXT NOT NULL,
display_name TEXT,
is_active BOOLEAN NOT NULL DEFAULT TRUE
) WITH (TYPE = 'USER');
```
- Use `TEXT` for variable-length strings.
- Use `BOOLEAN` instead of `0/1` integers for feature flags and state.
### Decimal Values
```sql
CREATE TABLE billing.invoice_items (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
sku TEXT NOT NULL,
amount DECIMAL(12,2) NOT NULL,
tax_rate DECIMAL(5,4) NOT NULL DEFAULT 0.0000
) WITH (TYPE = 'SHARED');
```
- Use `DECIMAL` for exact arithmetic (pricing, balances, rates).
- Pick precision/scale deliberately, e.g. `DECIMAL(12,2)`.
### Time Types
```sql
CREATE TABLE chat.messages (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
user_id BIGINT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
) WITH (TYPE = 'USER');
SELECT *
FROM chat.messages
WHERE created_at >= NOW() - INTERVAL '7 days'
ORDER BY created_at DESC;
```
- Use `TIMESTAMP` for event records and ordering.
- Use `DATE` when time-of-day is not needed.
### JSON Payloads
```sql
CREATE TABLE app.events (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
event_name TEXT NOT NULL,
payload JSON,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
) WITH (
TYPE = 'STREAM',
TTL_SECONDS = 300
);
```
- Use `JSON` for dynamic payloads and schema-flexible metadata.
- Keep frequently filtered fields as typed top-level columns.
### Embeddings for Vector Search
```sql
CREATE TABLE rag.documents_vectors (
id BIGINT PRIMARY KEY,
doc_embedding EMBEDDING(384)
) WITH (TYPE = 'USER');
ALTER TABLE rag.documents_vectors
CREATE INDEX doc_embedding USING COSINE;
SELECT id
FROM rag.documents_vectors
ORDER BY COSINE_DISTANCE(doc_embedding, '[0.12,0.03,0.98]')
LIMIT 5;
```
- Use `EMBEDDING(n)` for fixed-size vector columns.
- Pick `n` to match the embedding model you generate in your app or worker.
- Index the embedding column before using it for nearest-neighbor retrieval.
## Type Conversion
Use explicit casts when moving between compatible types.
```sql
SELECT CAST('42' AS INT) AS count_value;
SELECT CAST(amount AS DECIMAL(12,2)) AS normalized_amount
FROM billing.invoice_items;
```
Prefer explicit conversion over relying on implicit casting in critical queries.
## Nullability and Defaults
Use `NOT NULL` for required fields and `DEFAULT` for stable inserts.
```sql
CREATE TABLE app.sessions (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
user_id BIGINT NOT NULL,
started_at TIMESTAMP NOT NULL DEFAULT NOW(),
ended_at TIMESTAMP,
is_valid BOOLEAN NOT NULL DEFAULT TRUE
) WITH (TYPE = 'USER');
```
## Recommended Patterns
- IDs: `BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID()`
- Created timestamps: `TIMESTAMP NOT NULL DEFAULT NOW()`
- Money/rates: `DECIMAL` (not floating point)
- Optional user text: nullable `TEXT`
- Real-time transient events: `STREAM` + `TTL_SECONDS`
## Related Docs
- [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types)
- [/docs/server/architecture/vector-search](https://kalamdb.org/docs/server/architecture/vector-search)
- [/docs/server/architecture/file-upload-datatype](https://kalamdb.org/docs/server/architecture/file-upload-datatype)
- [/docs/server/sql-reference/tables](https://kalamdb.org/docs/server/sql-reference/tables)
- [/docs/server/sql-reference/file-datatype](https://kalamdb.org/docs/server/sql-reference/file-datatype)
================================================================================
# File - Upload & DataType
URL: https://kalamdb.org/docs/server/architecture/file-upload-datatype
Source: content/server/architecture/file-upload-datatype.mdx
> Understand how FILE upload and read flows map to USER and SHARED tenant storage paths in KalamDB. Covers multipart uploads, FileRef metadata, physical object-store layout, and file retrieval.
# File - Upload & DataType
KalamDB separates file metadata from file bytes:
- SQL rows store a **`FileRef` JSON payload** in `FILE` columns (id, size, sha256, mime, subfolder, original name).
- **Binary content is written as a real object** in the configured storage backend — a file on the local filesystem or an object in S3, GCS, or Azure Blob Storage.
This is **not** a BLOB-in-the-row model like PostgreSQL `BYTEA`, MySQL `BLOB`, or SQLite `BLOB`. KalamDB never stores file bytes inside RocksDB row values or inside Parquet column pages. Queries return metadata; downloads stream bytes from the storage path the `FileRef` points at.
This allows database queries and file transfer to scale independently while preserving table-level security.
File storage follows table-type scope. For the full `USER`/`SHARED`/`STREAM` capability matrix, see
[/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types).
## Upload and Read Flow
1. Client sends SQL using `FILE("placeholder")`.
2. Client attaches multipart file part as `file:`.
3. KalamDB validates auth and table type (`USER` / `SHARED` only).
4. Binary is staged briefly, then **finalized as a standalone object** under the table's storage path (see [Physical storage layout](#physical-storage-layout)).
5. The hot-tier row stores a `FileRef` JSON payload used for future reads/downloads.
For endpoint details, see [/docs/server/api/http-reference](https://kalamdb.org/docs/server/api/http-reference).
## Physical storage layout
FILE uploads land in the **`base_directory`** of the table's assigned storage (from `system.storages`, default **`local`**). For a typical local dev server that directory is under `storage.data_path`:
```text
{data_path}/storage/ # default base_directory for the local storage
├── {namespace}/{tableName}/ # SHARED table root
│ └── f0001/1234567890123456789-contract.pdf
└── {namespace}/{tableName}/{userId}/ # USER table root (per-user scope)
└── f0001/1234567890123456789-profile.md
```
With the default server templates (`shared_tables_template`, `user_tables_template`) and `data_path = "./data"`:
| Table type | Template | Example relative path under `storage/` |
|------------|----------|--------------------------------------|
| `SHARED` | `{namespace}/{tableName}` | `app/documents/f0001/1234567890123456789-contract.pdf` |
| `USER` | `{namespace}/{tableName}/{userId}` | `okf_sync/context_files/9876543210987654321/f0001/1234567890123456789-profile.md` |
Each upload gets:
1. A **snowflake file id** (used in the on-disk filename).
2. A **subfolder** such as `f0001` (rotates as folders fill; configurable via `max_files_per_folder`).
3. A **stored filename** `{id}-{sanitized-original-name}.{ext}` built from the `FileRef`.
The full object key / filesystem path is:
```text
{base_directory}/{resolved_template}/{subfolder}/{stored_filename}
```
Remote storages use the same relative layout inside the bucket or container prefix configured in `base_directory` (for example `s3://my-bucket/kalam/`).
### What lives in SQL vs what lives on disk
| Layer | Holds | Example |
|-------|-------|---------|
| RocksDB hot row | `FileRef` JSON in the `FILE` column | `{"id":"123…","sub":"f0001","name":"profile.md","size":142,"mime":"text/markdown","sha256":"a1b2…"}` |
| Parquet cold segment (after flush) | Same `FileRef` JSON in the column — **not** the bytes | Metadata only |
| Storage object store | **Actual file bytes** | `…/okf_sync/context_files/{userId}/f0001/123…-profile.md` |
Clients download through `GET /v1/files/{namespace}/{table}/{sub}/{stored_filename}` (or SDK helpers built on that path). The handler reads the object from storage; it does not decode bytes from a SQL BLOB column.
### End-to-end finalize path (backend)
From `kalamdb-filestore` + SQL file handlers:
1. Multipart upload bytes are written to a **temporary staging directory** per request.
2. `finalize_file` generates a `FileRef`, computes sha256/mime, allocates a subfolder, and **`put`s the bytes** to the table's storage backend via the object-store API.
3. The staging directory is removed; only the permanent object and the row's `FileRef` remain.
4. On row delete or FILE replacement, the old object can be garbage-collected separately from MVCC row versions.
See the [Live OKF Context Sync](https://kalamdb.org/docs/use-cases/live-okf-context-sync#file-bytes-on-disk-not-in-sql) use case for a concrete walkthrough with `kalam dev` paths you can inspect on disk.
## USER vs SHARED Pathing Model
KalamDB routes file bytes according to table type:
- **USER tables**: file objects are stored under the authenticated user's tenant scope.
- **SHARED tables**: file objects are stored under shared table scope.
This matches KalamDB's tenant-aware architecture:
- user-private data stays isolated in user-tenant paths
- shared assets are accessible through shared-table policy
`SYSTEM` and `STREAM` table paths are not valid for file APIs.
### Backend-Verified Path Resolution
From backend implementation (`kalamdb-filestore` + API handlers):
- For `USER` tables, file operations are resolved with `user_id` context.
- For `SHARED` tables, file operations are resolved without `user_id` context.
- Download handler enforces this behavior and rejects `STREAM`/`SYSTEM` file access.
Default storage templates are:
- `shared_tables_template = "{namespace}/{tableName}"`
- `user_tables_template = "{namespace}/{tableName}/{userId}"`
So for a table `chat.attachments`:
- shared object example: `chat/attachments/f0001/1234567890123456789-photo.png`
- user object example: `chat/attachments//f0001/1234567890123456789-photo.png`
Templates are configurable per storage with `CREATE STORAGE` / `ALTER STORAGE`, but the user-scoped resolution model remains the same. Changing templates affects **new** path resolution; existing objects keep their stored paths via the `FileRef` recorded in each row.
## Architecture Diagram
```mermaid
flowchart LR
Client[Client] -->|upload request| Api[SQL and File API]
Api -->|validate auth and table type| Router{Table Type Router}
Router -->|USER table with user_id| UserPath[User Tenant Folder]
Router -->|SHARED table without user_id| SharedPath[Shared Table Folder]
UserPath --> Ref[Store file reference in row]
SharedPath --> Ref
UserPath --> ObjStore[(Object store / storage folder)]
SharedPath --> ObjStore
Ref --> Download[Read via file endpoint]
ObjStore --> Download
Download --> Client
```
## Why This Design Helps
- Keeps tenant boundaries explicit for uploads and reads.
- Avoids mixing user-private files with shared table assets.
- Lets applications query metadata with SQL while streaming bytes through file endpoints.
- **Object-store friendly:** bytes behave like normal files or S3 keys, so backup, CDN offload, and lifecycle policies apply to file content without rewriting SQL dumps.
- **Smaller hot/cold SQL tiers:** Parquet segments and RocksDB hold references and queryable fields, not multi-megabyte blobs per row.
## Related Docs
- [/docs/server/sql-reference/file-datatype](https://kalamdb.org/docs/server/sql-reference/file-datatype)
- [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types)
- [/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers)
- [/docs/server/sql-reference/storage-id-usage](https://kalamdb.org/docs/server/sql-reference/storage-id-usage)
- [/docs/use-cases/live-okf-context-sync](https://kalamdb.org/docs/use-cases/live-okf-context-sync) — sync worker that uploads Markdown through `FILE` columns; inspect `kalam/server/data/storage/` after `kalam dev`
================================================================================
# Architecture Overview
URL: https://kalamdb.org/docs/server/architecture
Source: content/server/architecture/index.mdx
> Explore KalamDB's architecture: query execution, multi-tier storage (RocksDB/Parquet), realtime delivery, and cluster consensus.
# Architecture Overview
KalamDB combines SQL execution, realtime delivery, and multi-tier storage in one runtime.
It also supports vector search with `EMBEDDING(n)` columns, cosine indexes, and similarity ranking in SQL for semantic retrieval workflows.
Use this page as the map. The deeper architecture pages explain one boundary at a time:
[/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types),
[/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers),
[/docs/server/architecture/stream-storage](https://kalamdb.org/docs/server/architecture/stream-storage),
[/docs/server/architecture/manifests](https://kalamdb.org/docs/server/architecture/manifests),
[/docs/server/architecture/live-query](https://kalamdb.org/docs/server/architecture/live-query),
[/docs/server/architecture/clustering](https://kalamdb.org/docs/server/architecture/clustering),
[/docs/server/architecture/datatypes](https://kalamdb.org/docs/server/architecture/datatypes),
[/docs/server/architecture/vector-search](https://kalamdb.org/docs/server/architecture/vector-search), and
[/docs/server/architecture/file-upload-datatype](https://kalamdb.org/docs/server/architecture/file-upload-datatype).
## Major Runtime Layers
- `kalamdb-api`: HTTP + WebSocket surface (`/v1/api/*`, `/v1/ws`)
- `kalamdb-core`: orchestration, SQL execution flow, authorization checks, job dispatch
- `kalamdb-sql`: SQL parser/classifier/extensions (`SUBSCRIBE`, `TOPIC`, `STORAGE`, `EXECUTE AS ''`, etc.)
- `kalamdb-store` + RocksDB: hot write/read path
- `kalamdb-filestore` + Parquet: cold segments and manifests
- `kalamdb-raft`: cluster consensus and replication
## Runtime Flow
```mermaid
flowchart TD
A["Client (HTTP / WebSocket)"] --> B["kalamdb-api"]
B --> C["kalamdb-core orchestration"]
C --> D["kalamdb-sql parser/classifier"]
D --> E["DataFusion execution"]
E --> F["Hot Tier (RocksDB)"]
F --> G["Flush Jobs"]
G --> H["Cold Tier (Parquet + manifest)"]
C --> I["Realtime notifications"]
C --> J["kalamdb-raft (cluster mode)"]
```
## Architecture Reading Map
```mermaid
flowchart TD
A["Start: Architecture Overview"] --> B["Table Types"]
B --> C["USER / SHARED / STREAM scope"]
C --> D["Storage Tiers"]
D --> E["Manifests"]
C --> F["Stream Storage"]
C --> G["Live Query"]
G --> H["Clustering & HA"]
C --> I["FILE Upload & DataType"]
C --> J["Data Types"]
J --> K["Vector Search"]
```
Read [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types) first when you
are deciding data ownership or security. Read
[/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers) and
[/docs/server/architecture/manifests](https://kalamdb.org/docs/server/architecture/manifests) when you are tuning
flush, compaction, or storage templates. Read
[/docs/server/architecture/stream-storage](https://kalamdb.org/docs/server/architecture/stream-storage) when you are
tuning `STREAM` table retention, transient UI state, or TTL cleanup behavior. Read
[/docs/server/architecture/live-query](https://kalamdb.org/docs/server/architecture/live-query) and
[/docs/server/architecture/clustering](https://kalamdb.org/docs/server/architecture/clustering) when you are scaling
realtime connections or multi-node deployments.
## Data Directory Layout
Default paths are rooted at `storage.data_path` (usually `./data`):
```text
data/
├── rocksdb/ # hot tier
├── storage/ # cold parquet tier
├── snapshots/ # raft snapshots
└── streams/ # stream table logs
```
Table-specific cold paths are generated from templates:
- shared tables: `storage.shared_tables_template`
- user tables: `storage.user_tables_template`
## Query Path (Simplified)
1. API receives SQL (`POST /v1/api/sql`) with bearer auth.
2. SQL is parsed/classified (standard + KalamDB extensions).
3. Authorization and table-type rules are applied.
4. Query executes against hot tier, cold tier, or both.
5. Response returns normalized JSON schema/rows.
6. Optional change notifications flow to subscriptions/topics.
For detailed flush behavior and cold-tier movement, see
[/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers).
For the file-backed `STREAM` table path, see
[/docs/server/architecture/stream-storage](https://kalamdb.org/docs/server/architecture/stream-storage).
## Technology Stack
| Component | Technology | Notes |
|-----------|-----------|-------|
| Language | Rust 1.92+ | concurrency + memory safety |
| Query engine | Apache DataFusion 54.0 | SQL planning/execution (DuckDB dialect for lambdas and JSON operators) |
| Columnar format | Apache Arrow 58.x | in-memory batches |
| Cold storage | Apache Parquet 58.x | compressed columnar files |
| Hot storage | RocksDB 0.24 | write-heavy low-latency path |
| API runtime | Actix Web 4.12 | HTTP + WebSocket |
| Auth | bcrypt + JWT | password + token flows |
## Continue Reading
- [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types)
- [/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers)
- [/docs/server/architecture/stream-storage](https://kalamdb.org/docs/server/architecture/stream-storage)
- [/docs/server/architecture/manifests](https://kalamdb.org/docs/server/architecture/manifests)
- [/docs/server/architecture/live-query](https://kalamdb.org/docs/server/architecture/live-query)
- [/docs/server/architecture/clustering](https://kalamdb.org/docs/server/architecture/clustering)
- [/docs/server/architecture/datatypes](https://kalamdb.org/docs/server/architecture/datatypes)
- [/docs/server/architecture/vector-search](https://kalamdb.org/docs/server/architecture/vector-search)
- [/docs/server/architecture/file-upload-datatype](https://kalamdb.org/docs/server/architecture/file-upload-datatype)
================================================================================
# Live Query
URL: https://kalamdb.org/docs/server/architecture/live-query
Source: content/server/architecture/live-query.mdx
> How KalamDB achieves high-performance real-time subscriptions and offline-first sync using Sequence IDs and tenant-aware routing.
# Live Query Architecture
KalamDB is designed from the ground up to support real-time, reactive applications. Unlike traditional databases that require complex polling mechanisms or external message brokers (like Kafka or Redis) to stream changes, KalamDB natively supports **Live Queries** via WebSockets.
This architecture allows clients to subscribe to changes on `USER`, `SHARED`, and `STREAM` tables and receive instant push notifications whenever data is inserted, updated, or deleted.
Live queries inherit the table-type access and scope rules from
[/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types). `USER` and `STREAM`
subscriptions are user-scoped; `SHARED` subscriptions use the shared table's `ACCESS_LEVEL` policy.
## How It Works
When a client subscribes to a query (e.g., `SELECT * FROM chat.messages WHERE conversation_id = 123`), KalamDB does not just execute the query once. It registers a **Live Query Subscription** in the cluster.
You can subscribe in two common ways:
- **Whole-row subscription**: `SELECT * FROM chat.changes WHERE ...`
- **Projection subscription**: `SELECT username, status FROM chat.changes WHERE ...`
Projection subscriptions are useful when the client needs only a few columns, which reduces payload size and improves UI update performance.
### Tenant-Aware Routing for Performance
One of the main reasons KalamDB achieves better performance than other databases for real-time workloads is its **tenant-aware storage architecture**.
In traditional databases, a live query often requires the system to scan or index the entire table or maintain complex global replication logs to find relevant changes. In KalamDB, `USER` and `STREAM` tables are physically sharded by `user_id`.
When a live query is registered for a specific user, the system doesn't need to scan the whole table. It directly monitors the isolated storage partition for that specific user tenant. This drastically reduces I/O overhead and allows the system to scale horizontally to millions of concurrent connections.
In clustered mode, the client may be connected to a follower node while writes are accepted by the leader. KalamDB forwards those changes through Raft replication so the follower applies them locally, evaluates matching subscriptions, and pushes updates to the connected client.
```mermaid
flowchart LR
Client[Client App] -->|subscribe| Follower[Follower Node]
Follower -->|register subscription| Leader[Leader Node]
subgraph Cluster
Leader -->|replicate subscription metadata| Follower
Write[Write Operation] --> Leader
Leader --> Router{Tenant Router}
Router --> UserStore[User Tenant Storage]
Router --> SharedStore[Shared Storage]
UserStore --> Matcher[Query Matcher]
SharedStore --> Matcher
Matcher --> Leader
Leader -->|replicate data change| Follower
end
Follower -->|local notify + push update| Client
```
## Offline-First and Sequence IDs
KalamDB's live queries are built to support **offline-first** applications seamlessly.
When subscribing to a table, you don't just get future events. You can also request historical data to synchronize your local state before the live stream begins. This is powered by **Sequence IDs** (`SeqId`), which provide a strict, monotonically increasing order of operations.
When initiating a subscription, you can specify:
- **`since_seq`**: Pull all changes that happened after a specific Sequence ID.
- **`fetch_last`**: Load only the latest *N* number of rows.
### Example: Chat Application
Imagine a user opening an old conversation in a chat app. Loading the entire history of the conversation would be slow and consume unnecessary bandwidth.
Instead, the client can subscribe to the conversation and request only the **latest 10 messages**:
1. The client sends a subscription request with `fetch_last: 10`.
2. KalamDB immediately returns an `initial_data_batch` containing those 10 messages.
3. The subscription remains open.
4. As new messages are sent in that conversation, KalamDB pushes them to the client in real-time.
If the user goes offline and reconnects later, the client can simply resubscribe using the `since_seq` of the last message it received. KalamDB will instantly push any messages missed during the offline period, followed by a seamless transition back to the live stream.
## Cluster-Wide Visibility
Active live query subscriptions are node-local runtime state. Each node exposes
its current in-memory subscriptions through `system.live`; that view is not
persisted to RocksDB and it is not replicated through Raft.
Clients can stay connected to follower nodes while writes are processed by the leader. The change stream is still delivered to followers, so followers can fan out updates to their connected clients. This helps increase total concurrent connection capacity because client sessions are distributed across nodes.
## Shared Table Subscriptions at High Scale
KalamDB has a dedicated shared-table streaming notification path optimized for high subscriber fan-out.
For `SHARED` tables, the runtime keeps a dedicated table-level subscription index so it can resolve shared subscribers quickly without user-partition lookup.
When a shared-table change arrives, KalamDB uses a streaming fan-out pipeline:
1. **Pre-compute row JSON once** and share it across workers.
2. **Snapshot subscriber handles** for the target shared table.
3. **Process subscribers in parallel chunks** (chunked dispatch) while still applying per-subscriber filter, projection, flow control, and channel delivery semantics.
Why this enables high subscriber counts:
- avoids repeating full row conversion for every subscriber
- uses shared payload memory for lower CPU and allocation overhead
- parallelizes fan-out while preserving subscription-specific behavior
- keeps backpressure behavior intact with flow control and non-blocking send paths
This is a core part of KalamDB's realtime power for `SHARED` tables, where a single change may need to reach very large subscriber groups with low latency.
## Table-Type Delivery Summary
| Table type | Subscription scope | Access policy used | Delivery shape |
|---|---|---|---|
| `USER` | Effective `user_id` partition | User-table read gate | Tenant-local matching and fan-out |
| `SHARED` | Whole shared table scope | Shared `ACCESS_LEVEL` read gate | Shared-table index plus chunked fan-out |
| `STREAM` | Effective `user_id` stream partition | User/stream read gate | TTL-oriented transient updates |
| `SYSTEM` | Not supported for app live query | DBA/System-only metadata access | No normal live-query stream |
```mermaid
sequenceDiagram
participant C as Client
participant F as Follower Node
participant L as Leader Node
participant S as Tenant Storage
C->>F: WebSocket live subscribe
F->>L: Register subscription metadata
L-->>F: Subscription replicated
L->>S: Apply write (insert/update/delete)
S-->>L: Change with SeqId
L-->>F: Replicate change event
F-->>C: Push live update
```
## Related Docs
- [/docs/server/sql-reference/subscriptions](https://kalamdb.org/docs/server/sql-reference/subscriptions)
- [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types)
- [/docs/server/architecture/clustering](https://kalamdb.org/docs/server/architecture/clustering)
- [/docs/ts-sdk/subscriptions](https://kalamdb.org/docs/ts-sdk/subscriptions)
================================================================================
# Manifests
URL: https://kalamdb.org/docs/server/architecture/manifests
Source: content/server/architecture/manifests.mdx
> How KalamDB tracks cold Parquet segments across memory, RocksDB, and storage manifest.json files, including tail compaction semantics.
# Manifests
KalamDB uses a manifest as the authoritative index for cold Parquet segments. Every manifest is
owned by the manifest service, which decides where the current copy lives, when it is dirty, and
when storage `manifest.json` is rewritten.
Manifests exist behind the hot/cold flow described in
[/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers). They apply to
`USER` and `SHARED` cold-storage scopes; see
[/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types) for the table-type
boundary.
## Manifest Tiers
| Table type | Hot manifest path | Cold manifest path |
|------------|-------------------|--------------------|
| `SHARED` | process memory -> `system.manifest` in RocksDB | `manifest.json` in table storage |
| `USER` | `system.manifest` in RocksDB | `manifest.json` in the user storage path |
This split is intentional:
- `SHARED` manifests stay in process memory because they are low-cardinality and widely reused.
- `USER` manifests stay out of process memory so millions of user-scoped manifests do not expand server RSS.
- Both table types still persist their durable hot copy in RocksDB before storage writes.
The canonical read path is `ManifestService::get_or_load()`:
1. check the fastest hot tier for the scope
2. fall back to RocksDB manifest copy
3. fall back to storage `manifest.json`
4. hydrate faster tiers above the answering layer
## Control Flow
```mermaid
flowchart TD
A["SQL / flush / metadata change"] --> B["ManifestService"]
subgraph Shared["SHARED table"]
B --> S1["In-memory manifest cache"]
S1 <--> S2["RocksDB system.manifest row"]
S2 -->|flush or explicit persist| S3["storage manifest.json"]
S3 -->|cache miss reload| S2
S2 -->|shared hot reload| S1
end
subgraph User["USER table"]
B --> U1["RocksDB system.manifest row"]
U1 -->|flush or explicit persist| U2["user storage manifest.json"]
U2 -->|cache miss reload| U1
end
```
## What Happens On Normal Writes
For normal DML and flush-preparation updates, KalamDB does not rewrite `manifest.json` on every change.
1. The write path updates hot data in RocksDB.
2. The manifest service updates manifest metadata in the hot tier.
3. The manifest is marked `pending_write`.
4. Cold storage is updated later by the flush path.
That means manifest bookkeeping stays cheap on the write path while still keeping the latest authoritative manifest state available for reads, flush planning, and primary-key checks.
The hot manifest entry also tracks a sync state:
- `in_sync`
- `pending_write`
- `syncing`
- `stale`
- `error`
During a flush, the scope becomes `syncing` while the Parquet temp file is being written.
## When `manifest.json` Is Actually Written
`manifest.json` is persisted when KalamDB commits cold-storage state, not on every mutation.
- Automatic or manual flushes write Parquet, update manifest segment metadata, and then persist `manifest.json`.
- Reloads after cache misses can hydrate the hot tier from the stored manifest.
- Vector-index DDL metadata is a deliberate exception: it is persisted immediately as a control-plane metadata write so vector state is not lost when there is no row flush to trigger storage persistence.
Flush commits a new segment through one canonical path: append the segment metadata, write
`manifest.json`, then refresh RocksDB and shared-scope memory cache.
## Flush-Time Segment Append
For a normal cold append, KalamDB writes `batch-N.parquet.tmp`, renames it to `batch-N.parquet`,
then appends a new segment entry that includes:
- `min_seq` and `max_seq`
- `row_count`
- `size_bytes`
- `schema_version`
- `status`
- `column_stats` for indexed columns
`last_sequence_number` tracks the latest `batch-N` slot used for future flush naming.
## How Manifest Compaction Works
Post-flush small-segment compaction does not rebuild the whole manifest. It compactly rewrites only
the trailing small-file suffix.
The compactor:
1. selects the trailing run of small, committed, same-schema segments
2. rewrites the newest MVCC winners into `compact-.parquet`
3. reacquires the manifest scope lock
4. verifies the selected inputs are still the current trailing suffix
5. truncates that suffix and appends the compacted replacement segment, or appends nothing if the
suffix was fully pruned
6. persists the updated `manifest.json`
If another flush changed the manifest tail while compaction was running, the swap is skipped and
the compacted file is deleted.
Compaction filenames do not use the `batch-N` numbering scheme, so they do not consume a new batch
sequence slot.
## What A Real `manifest.json` Looks Like
The example below is taken from a real user-table manifest written by the KalamDB backend. The values vary by table and flush, but the field names and nesting match the on-disk JSON shape.
```json
{
"table_id": "flush_manifest_ns_mpczl3q7_s3k_0.user_flush_test_mpczl3q7_s3k_0",
"user_id": "admin",
"version": 2,
"created_at": 1779216537052,
"updated_at": 1779216537063,
"segments": [
{
"min_seq": 315199164996489218,
"max_seq": 315199165030043648,
"row_count": 20,
"size_bytes": 2711,
"created_at": 1779216537063,
"id": "batch-0.parquet",
"path": "batch-0.parquet",
"column_stats": {
"1": {
"min": { "Int64": "315199164994551810" },
"max": { "Int64": "315199165028106240" },
"null_count": 0
}
},
"schema_version": 1,
"status": "committed"
}
],
"last_sequence_number": 0,
"files": null,
"vector_indexes": {}
}
```
What these fields mean in practice:
- `segments` is the durable list of cold Parquet batches for that exact table scope.
- `column_stats` is keyed by stable column ID, not by column name, so renames do not invalidate old segment metadata.
- `last_sequence_number` tracks the last `batch-N.parquet` slot used for segment file naming.
- `files` stays `null` until the table enables FILE-column subfolder tracking.
- `vector_indexes` stays empty until the table has vector index metadata to persist.
## Why Primary-Key Checks Use The Manifest Service
Primary-key existence and cold-segment pruning go through the manifest service instead of reopening `manifest.json` on every check.
That gives KalamDB one source of truth for:
- the freshest shared-table manifest already in memory
- the freshest user-table manifest already in RocksDB
- fallback reload from storage only when the hot tier does not have the manifest yet
This avoids repeated storage reads and keeps pruning decisions aligned with the same manifest state the flush path updates.
It also means manifest-aware reads see the same suffix-replacement result after compaction that the
flush and compaction jobs committed.
## Shared vs User Storage Layout
A `SHARED` table writes one manifest per table path:
```text
storage///manifest.json
```
A `USER` table writes one manifest per user-scoped table path:
```text
storage////manifest.json
```
Each manifest tracks the Parquet segments for that exact scope only.
## Related Docs
- [/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers)
- [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types)
- [/docs/server/sql-reference/storage](https://kalamdb.org/docs/server/sql-reference/storage)
- [/docs/server/sql-reference/storage-id-usage](https://kalamdb.org/docs/server/sql-reference/storage-id-usage)
================================================================================
# Storage Tiers
URL: https://kalamdb.org/docs/server/architecture/storage-tiers
Source: content/server/architecture/storage-tiers.mdx
> Understand KalamDB's hot RocksDB tier, cold Parquet tier, manifest state, and the current post-flush tail-compaction path.
# Storage Tiers
KalamDB uses a dual-tier storage architecture that keeps the write path hot in RocksDB while moving
queryable historical state into immutable Parquet segments.
This page describes how `USER` and `SHARED` tables move from hot to cold storage. For the table-type
rules that decide whether a table can flush at all, see
[/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types).
## Hot Tier (RocksDB)
The hot tier handles incoming writes in RocksDB-backed table stores.
**Characteristics:**
- ⚡ Sub-millisecond write latency
- Optimized for point lookups, recent writes, and write-heavy workloads
- Holds the newest live rows and the newest delete tombstones
- Feeds the flush scheduler through manifest scopes marked `pending_write`
## Cold Tier (Parquet)
Flushed data is written to Apache Parquet files for efficient scans, pruning, and long-term
storage.
**Characteristics:**
- 📊 Columnar format for efficient analytics
- High compression ratios
- Each segment tracked in `manifest.json` and a hot manifest copy
- Supports multiple storage backends (local, S3, Azure, GCS)
### Parquet Compression
Table `COMPRESSION` is a cold-tier Parquet setting for `USER` and `SHARED` tables. KalamDB
currently supports three values:
| Value | Meaning | Typical use |
|-------|---------|-------------|
| `none` | Writes uncompressed Parquet pages. | Debugging, CPU-constrained flushes, or data already compressed at the value level |
| `snappy` | Fast Parquet compression and decompression with low CPU overhead. This is the default. | General workloads and latency-sensitive flushes |
| `zstd` | Zstandard Parquet compression at level 1 for better storage density with modest extra CPU. | Larger historical datasets or storage-cost-sensitive tables |
The selected codec is applied when `USER` and `SHARED` table scopes write `batch-N.parquet.tmp` and
when tail compaction writes `compact-.parquet.tmp`. After the atomic rename, readers discover
the codec from the Parquet footer, so no manifest-side decompression setting is needed. This setting
does not affect RocksDB hot storage, WebSocket gzip, backups, or stream table log files. `STREAM`
tables do not accept table Parquet compression because current stream storage does not flush to
Parquet cold segments.
## End-to-End Tier Flow
```mermaid
flowchart LR
A["INSERT / UPDATE / DELETE"] --> B["Hot write in RocksDB"]
B --> C["Manifest marked as pending_write"]
C --> D["STORAGE FLUSH job"]
D --> E["Resolve latest hot versions per scope"]
E --> F["Write batch-N.parquet.tmp"]
F --> G["Atomic rename to batch-N.parquet"]
G --> H["Persist manifest.json + hot manifest copy"]
H --> I["Delete flushed hot rows"]
I --> J["Optional post-flush small-segment compaction"]
J --> K["Reads use hot + cold paths"]
```
## Flush Policy
Tables are configured with a flush policy that determines when data moves from hot to cold tier:
```sql
CREATE TABLE app.messages (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
) WITH (
TYPE = 'USER',
FLUSH_POLICY = 'rows:1000,interval:60'
);
```
| Policy | Description |
|--------|-------------|
| `rows:N` | Flush after N rows accumulated |
| `interval:N` | Flush every N seconds |
| `rows:N,interval:N` | Flush on whichever threshold is hit first |
## How Flushing Works (Current Engine Path)
The flush flow is job-driven and crash-safe:
1. DML writes land in RocksDB first.
2. The manifest scope is marked `pending_write`.
3. `STORAGE FLUSH TABLE` or `STORAGE FLUSH ALL` creates background flush jobs in `system.jobs`.
4. The flush executor resolves the target scope and scans hot rows in bounded batches.
5. The latest `_seq` wins per primary key. Latest tombstones stay hot instead of being written to
Parquet.
6. `FlushScopeWriter` writes `batch-N.parquet.tmp`.
7. The temp file is atomically renamed to `batch-N.parquet`.
8. Manifest metadata for the new segment is persisted through the manifest service.
9. Flushed hot rows are deleted from RocksDB in bounded cleanup batches.
Notes:
- `STREAM` and `SYSTEM` tables are not part of this flush-to-Parquet path.
- `STORAGE FLUSH` is asynchronous; monitor progress via `system.jobs`.
- Shared scopes flush once per table scope. User scopes flush once per `(table, user)` scope.
## Flush State Machine
```mermaid
stateDiagram-v2
[*] --> InSync
InSync --> PendingWrite: DML write
PendingWrite --> Syncing: flush starts
Syncing --> InSync: parquet + manifest committed
Syncing --> Error: flush failure
Error --> PendingWrite: retry / next flush
```
## Post-Flush Small-Segment Compaction
KalamDB now has an optional post-flush tail-compaction path under `[flush.compaction]`:
```toml
[flush.compaction]
enabled = false
min_eligible_segments = 5
max_segments_per_run = 8
user_max_segment_rows = 10000
shared_max_segment_rows = 25000
```
This path is separate from the flush critical section. After a successful flush, the leader may
enqueue a `segment_compact` job for the scopes that actually wrote Parquet files.
The compactor:
1. walks the manifest from newest to oldest
2. selects only the trailing run of small, committed, same-schema segments
3. stops when it hits an already-large segment, a non-readable segment, or a schema-version
boundary
4. rewrites only the latest MVCC winners into `compact-.parquet`
5. swaps the manifest tail only if the selected suffix is still unchanged
If a newer flush changed the manifest tail while compaction was running, the compacted output is
discarded and the existing manifest remains authoritative.
Only `USER` and `SHARED` tables participate in this mechanism.
## Manual Flush & Compaction
```sql
-- Flush a specific table
STORAGE FLUSH TABLE myapp.messages;
-- Flush all tables in a namespace
STORAGE FLUSH ALL IN myapp;
-- Compact cold storage
STORAGE COMPACT TABLE myapp.messages;
-- Check storage health
STORAGE CHECK local EXTENDED;
```
## S3 User-Data Example
Create an S3 storage and bind a `USER` table to it:
For MinIO-compatible local S3 setup, see
[/docs/server/integrations/minio](https://kalamdb.org/docs/server/integrations/minio).
```sql
CREATE STORAGE s3_prod
TYPE 's3'
BUCKET 'my-kalamdb-prod'
REGION 'us-east-1'
USER_TABLES_TEMPLATE 'users/{namespace}/{tableName}/{userId}'
SHARED_TABLES_TEMPLATE 'shared/{namespace}/{tableName}';
CREATE TABLE chat.messages (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
conversation_id BIGINT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
) WITH (
TYPE = 'USER',
STORAGE_ID = 's3_prod',
FLUSH_POLICY = 'rows:1000,interval:60'
);
```
For user `u_42`, cold-tier objects are stored under the user template:
```text
s3://my-kalamdb-prod/users/chat/messages/u_42/manifest.json
s3://my-kalamdb-prod/users/chat/messages/u_42/batch-0.parquet
s3://my-kalamdb-prod/users/chat/messages/u_42/batch-1.parquet
```
Illustrative manifest excerpt:
```json
{
"segments": [
{
"id": "batch-1.parquet",
"path": "batch-1.parquet",
"row_count": 1000,
"size_bytes": 184320,
"schema_version": 1,
"status": "committed"
}
],
"last_sequence_number": 1
}
```
## Per-User Storage Isolation
```text
data/storage/
├── //manifest.json # shared table path template
├── //batch-.parquet
└── ///manifest.json # user table path template
```
Path layout is controlled by:
- `storage.shared_tables_template` (default: `{namespace}/{tableName}`)
- `storage.user_tables_template` (default: `{namespace}/{tableName}/{userId}`)
Each user's cold-tier data can still live in a separate directory. This enables:
- **Trivial data export** — just copy the user's directory
- **Instant deletion** — remove the directory for GDPR compliance
- **Independent scaling** — no cross-user interference
For the manifest-layer details behind this flow, see
[/docs/server/architecture/manifests](https://kalamdb.org/docs/server/architecture/manifests).
## Related Docs
- [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types)
- [/docs/server/architecture/manifests](https://kalamdb.org/docs/server/architecture/manifests)
- [/docs/server/sql-reference/storage](https://kalamdb.org/docs/server/sql-reference/storage)
- [/docs/server/sql-reference/storage-id-usage](https://kalamdb.org/docs/server/sql-reference/storage-id-usage)
- [/docs/server/integrations/minio](https://kalamdb.org/docs/server/integrations/minio)
================================================================================
# Stream Storage
URL: https://kalamdb.org/docs/server/architecture/stream-storage
Source: content/server/architecture/stream-storage.mdx
> Learn how KalamDB stores STREAM table rows in TTL-sized window files, how eviction deletes expired directories, and why stream reads stay user-scoped.
# Stream Storage Architecture
KalamDB stream storage is the runtime path behind `STREAM` tables. It is optimized for short-lived,
user-scoped rows such as presence, typing indicators, cursor positions, and transient events.
Unlike `USER` and `SHARED` tables, stream rows do not flush into Parquet or participate in the
manifest-driven cold tier. The engine keeps stream data in append-only log files sized for fast TTL
eviction.
For the SQL creation model and table-type rules, see
[/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types).
## Stream Storage File Layout
With the default data directory, each stream table gets a table-scoped base path under:
```text
data/streams//
```
Inside that base path, rows are written into TTL-sized window directories:
```text
data/streams///w-//.log
```
Example:
```text
data/streams/chat/typing_events/
├── w1748343000000-60000/
│ ├── shard-0/alice.log
│ └── shard-2/bob.log
└── w1748343060000-60000/
└── shard-0/alice.log
```
The layout is intentionally simple:
- one immutable time window directory per retention bucket
- one shard subdirectory to distribute active writers
- one `.log` file per user inside that shard/window
KalamDB uses `.log` directly instead of `/records.log` because the validated
`user_id` alphabet is already filename-safe, so the extra directory level would add metadata churn
without adding useful structure.
## Why the Window Name Encodes Time
Each window directory name encodes two values:
- `window_start_ms`
- `duration_ms`
That lets cleanup decide whether a whole window is expired without opening any log file.
If `window_end <= ttl_cutoff`, the entire directory can be removed in one operation. This avoids a
row scan during normal retention and keeps cleanup proportional to the number of expired windows,
not the number of rows.
## Bucket Sizing From `TTL_SECONDS`
The bucket size is derived from the table's `TTL_SECONDS` value:
| `TTL_SECONDS` | Window size |
|---|---|
| `<= 15 minutes` | minute windows |
| `<= 1 day` | hour windows |
| `<= 1 week` | day windows |
| `<= 30 days` | week windows |
| `> 30 days` | month windows |
Short-lived streams use minute windows so eviction stays close to the actual retention period.
Longer-lived streams use coarser windows to avoid creating too many tiny files.
## Write Path
When a row is inserted or deleted in a `STREAM` table:
1. the effective `user_id` selects the per-user stream scope
2. the row timestamp selects the current time window
3. the user is routed into a shard directory
4. the record is appended to `.log` as a length-prefixed flexbuffers frame
The file-backed stream store keeps a bounded writer cache:
- 64 KiB buffered writes per open segment
- up to 256 cached segment writers per store
- cold writers flushed and closed when the cache exceeds the cap
The stream log does not call `fsync` on every append. Stream tables are intended for ephemeral
state, so the design favors low write latency and relies on the OS page cache for writeback.
## Stream Storage Cleanup
TTL eviction is window-based, not row-by-row.
The stream eviction job computes a cutoff timestamp and then:
1. scans window directory names under the table's stream base path
2. identifies windows whose computed `window_end` is older than the cutoff
3. closes any cached writers under those directories
4. removes the expired window directories
```mermaid
flowchart LR
A["STREAM write"] --> B["Pick TTL-sized window"]
B --> C["Route by shard + user_id"]
C --> D["Append to .log"]
E["Eviction job"] --> F["Read window directory names"]
F --> G{"window_end <= cutoff?"}
G -- "yes" --> H["Close cached writers"]
H --> I["remove_dir_all(window)"]
G -- "no" --> J["Keep active window"]
```
This is why the time-window layout matters: cleanup is mostly directory unlink work instead of file
parsing or per-row filtering.
The pre-validation path also benefits from this layout. `has_logs_before()` can stop as soon as it
finds one expired window directory, and the job executor runs that filesystem work on Tokio's
blocking pool so async workers are not pinned by directory traversal or removal.
## Read Path
Reads remain user-scoped.
To serve a range or latest query, the stream store:
1. computes the shard for the target `user_id`
2. lists only that user's candidate window files
3. filters windows by overlap with the requested time range
4. reads only those candidate log files
5. applies delete records so latest state wins for each stream row ID
This keeps reads narrow even when many users share the same stream table.
## What Stream Storage Is Not
Stream storage is deliberately separate from the Parquet cold tier used by `USER` and `SHARED`
tables.
- no Parquet flush path
- no manifest-driven segment lifecycle
- no long-term analytic storage guarantees
- no file-column support
If you need durable historical records or analytics, use a `USER` or `SHARED` table and the cold
storage flow documented in [/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers).
If you need short-lived realtime state that should expire automatically, `STREAM` storage is the
intended path.
## Related Docs
- [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types)
- [/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers)
- [/docs/server/architecture/live-query](https://kalamdb.org/docs/server/architecture/live-query)
- [/docs/server/topic-pubsub](https://kalamdb.org/docs/server/topic-pubsub)
- [/docs/server/sql-reference/tables](https://kalamdb.org/docs/server/sql-reference/tables)
================================================================================
# Table Types
URL: https://kalamdb.org/docs/server/architecture/table-types
Source: content/server/architecture/table-types.mdx
> When to use USER, SHARED, STREAM, and SYSTEM table types in KalamDB. Covers isolation behavior, access rules, and backend execution semantics.
# Table Types
KalamDB uses four runtime table types. The table type decides data scope, write path, storage
layout, live-query routing, and which role checks run before the server exposes rows.
- `USER`
- `SHARED`
- `STREAM`
- `SYSTEM` (internal)
## Table-Type Routing Model
```mermaid
flowchart LR
A["CREATE TABLE / runtime lookup"] --> B{"Table type"}
B -- "TYPE = 'USER'" --> C["USER scope"]
B -- "TYPE = 'SHARED'" --> D["Shared table scope"]
B -- "TYPE = 'STREAM'" --> E["Per-user stream scope"]
B -- "Engine-created" --> F["SYSTEM scope"]
C --> C1["Effective user_id selects tenant partition"]
C1 --> C2["Hot RocksDB + optional Parquet flush"]
D --> D1["ACCESS_LEVEL policy controls read/write roles"]
D1 --> D2["One shared table scope + optional Parquet flush"]
E --> E1["Effective user_id selects stream partition"]
E1 --> E2["TTL eviction, no Parquet flush path"]
F --> F1["Metadata and operational views"]
F1 --> F2["DBA/System-only access"]
```
## Quick Selection
| Choose | When the data is | Typical examples |
|---|---|---|
| `USER` | Private to the authenticated or effective user | chat messages, agent memory, preferences, tenant records |
| `SHARED` | Global within the namespace and governed by table policy | feature flags, catalogs, plans, public reference data |
| `STREAM` | Short-lived and user-scoped | typing indicators, presence, cursor positions, transient events |
| `SYSTEM` | Engine metadata or runtime state | users, schemas, jobs, cluster views, transactions |
Application SQL can create `USER`, `SHARED`, and `STREAM` tables. `SYSTEM` tables are created and
evolved by the server.
For SQL syntax, see [/docs/server/sql-reference/tables](https://kalamdb.org/docs/server/sql-reference/tables).
## SQL Creation Patterns
Recommended unified syntax:
```sql
CREATE TABLE app.messages (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
) WITH (TYPE = 'USER');
```
Also supported for compatibility:
- `CREATE USER TABLE ...`
- `CREATE SHARED TABLE ...`
- `CREATE STREAM TABLE ...`
If neither a typed prefix nor `WITH (TYPE = ...)` is supplied, the parser currently defaults to a
`SHARED` table. Prefer setting `TYPE` explicitly in new application SQL so the data boundary is
obvious in reviews.
## USER Tables
Per-user logical isolation with user-aware routing and permissions.
```sql
CREATE TABLE chat.messages (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
conversation_id BIGINT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
) WITH (
TYPE = 'USER',
FLUSH_POLICY = 'rows:1000,interval:60',
STORAGE_ID = 'local'
);
```
Use for: conversations, preferences, private records, tenant data.
### USER Table Behavior
- Rows are scoped by the authenticated or effective `user_id` from the session.
- Reads do not implicitly cross user boundaries, even for `system`, `dba`, or `service` roles.
- Cross-user work must use an authorized impersonation flow such as `EXECUTE AS USER`.
- `USER` tables require a primary key.
- `STORAGE_ID`, `USE_USER_STORAGE`, and `FLUSH_POLICY` apply to this table type.
- Hot rows can flush into cold Parquet segments. The flush scope is `(table, user)`.
- `FILE` columns and file APIs are supported.
- Live queries are evaluated in the user-scoped partition.
### USER Role Access
| Operation | Anonymous | User | Service | DBA | System |
|---|---:|---:|---:|---:|---:|
| Read own/effective `USER` scope | No | Yes | Yes | Yes | Yes |
| Insert/update/delete own/effective `USER` scope | No | Yes | Yes | Yes | Yes |
| Create `USER` table | No | No | Yes | Yes | Yes |
| Alter `USER` table | No | No | Yes | Yes | Yes |
| Drop `USER` table | No | No | No | Yes | Yes |
## SHARED Tables
Global shared scope for all users.
```sql
CREATE TABLE app.config (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TIMESTAMP DEFAULT NOW()
) WITH (
TYPE = 'SHARED',
ACCESS_LEVEL = 'PRIVATE'
);
```
Use for: reference data, global settings, shared catalogs.
### SHARED Table Behavior
- Rows are not partitioned by `user_id`.
- Access is controlled by the table's `ACCESS_LEVEL` option.
- If `ACCESS_LEVEL` is omitted, the server stores `PRIVATE`.
- `SHARED` tables require a primary key.
- `STORAGE_ID`, `FLUSH_POLICY`, and `ACCESS_LEVEL` apply to this table type.
- `USE_USER_STORAGE` is rejected because shared data has no user-specific storage scope.
- Hot rows can flush into cold Parquet segments. The flush scope is the shared table scope.
- `FILE` columns and file APIs are supported.
- Live queries use a shared-table subscription path optimized for large fan-out.
### ACCESS_LEVEL Semantics
The SQL parser accepts `PUBLIC`, `PRIVATE`, `RESTRICTED`, and `DBA` for `ACCESS_LEVEL` on shared
tables. Values are case-insensitive. `PRIVATE` and `RESTRICTED` currently enforce the same runtime
role matrix; keep `RESTRICTED` for data you want to label as elevated service/admin-only access.
```mermaid
flowchart TD
A["Request touches SHARED table"] --> B["Load table definition"]
B --> C["Read ACCESS_LEVEL from table options"]
C --> D{"Operation"}
D -- "SELECT / live initial read" --> E["can_access_shared_table"]
D -- "INSERT / UPDATE / DELETE" --> F["can_write_shared_table"]
E --> G{"Role allowed?"}
F --> G
G -- "yes" --> H["Continue execution"]
G -- "no" --> I["Access denied"]
```
| `ACCESS_LEVEL` | Read: Anonymous | Read: User | Read: Service | Read: DBA | Read: System | Write: User | Write: Service | Write: DBA | Write: System |
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|
| `PUBLIC` | Yes | Yes | Yes | Yes | Yes | No | Yes | Yes | Yes |
| `PRIVATE` | No | No | Yes | Yes | Yes | No | Yes | Yes | Yes |
| `RESTRICTED` | No | No | Yes | Yes | Yes | No | Yes | Yes | Yes |
| `DBA` | No | No | No | Yes | Yes | No | No | Yes | Yes |
Notes:
- `PUBLIC` means readable by every session role, including anonymous session context, but regular
`user` role sessions still cannot write.
- `PRIVATE` is the default and is service/admin read-write.
- `DBA` is the strictest shared-table option and excludes `service` roles.
- DDL still has its own gate: `service`, `dba`, and `system` can create or alter application
tables; regular `user` sessions cannot create, alter, or drop tables.
### Changing Shared Access
Two ALTER forms are supported:
```sql
ALTER TABLE app.config SET TBLPROPERTIES (ACCESS_LEVEL = 'PUBLIC');
ALTER TABLE app.config SET ACCESS LEVEL public;
```
The `SET TBLPROPERTIES` form accepts `PUBLIC`, `PRIVATE`, `RESTRICTED`, and `DBA`. The shorthand
`SET ACCESS LEVEL ...` form currently accepts `PUBLIC`, `PRIVATE`, and `RESTRICTED`.
## STREAM Tables
Ephemeral rows with TTL-based eviction. Like `USER` tables, `STREAM` tables are scoped per user tenant and are **not** shared across all users.
```sql
CREATE TABLE chat.typing_events (
event_id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
user_id TEXT NOT NULL,
event_type TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
) WITH (
TYPE = 'STREAM',
TTL_SECONDS = 30
);
```
Use for: typing indicators, presence, cursor events, transient signals.
### STREAM Table Behavior
- Rows are scoped by the authenticated or effective `user_id`.
- `TTL_SECONDS` is required in SQL creation.
- Stream rows are evicted by the stream lifecycle rather than flushed into Parquet cold storage.
- `STREAM` tables use the same read/write permission gate as `USER` tables.
- `FILE` columns and file APIs are not supported.
- Live queries are supported and are commonly used for transient UI state.
## Realtime Subscriptions
Today, KalamDB supports realtime subscriptions to all three primary application table types: `USER`, `SHARED`, and `STREAM`. This allows applications to listen for live changes (inserts, updates, deletes) as they happen, enabling reactive UIs and realtime workflows.
Subscriptions can be full-row (`SELECT *`) or projected (`SELECT username, status FROM chat.changes ...`) depending on the payload shape your client needs.
For the delivery path, shared-table fan-out, and reconnect semantics, see
[/docs/server/architecture/live-query](https://kalamdb.org/docs/server/architecture/live-query).
## SYSTEM Tables
Internal control-plane and metadata tables (`system.*`).
- not intended for normal app DDL lifecycle
- role-restricted access for sensitive operations
- file upload/download flows reject `SYSTEM` tables
- direct DML against `system.*` tables is blocked for normal application workflows
- computed `system.*` views expose runtime metadata such as sessions, transactions, cluster state,
and jobs
`SYSTEM` table access is restricted to `dba` and `system` roles. `service`, `user`, and anonymous
roles cannot read system tables through the normal table-access gate.
For inspectable system views, see [/docs/server/sql-reference/system-views](https://kalamdb.org/docs/server/sql-reference/system-views).
## Table Options By Type
| Option | `USER` | `SHARED` | `STREAM` | `SYSTEM` | Server behavior |
|---|---:|---:|---:|---:|---|
| `TYPE` | Yes | Yes | Yes | No app DDL | Selects table type when not using `CREATE USER/SHARED/STREAM TABLE`. |
| `STORAGE_ID` | Yes | Yes | Parsed but not used | Internal | Defaults to `local`; must reference an existing storage backend. |
| `USE_USER_STORAGE` | Yes | No | No | No | Valid only on `USER`; rejected on `SHARED`/`STREAM`. |
| `FLUSH_POLICY` | Yes | Yes | Parsed but no flush path | Internal | Triggers hot-to-cold movement for `USER` and `SHARED`. |
| `DELETED_RETENTION_HOURS` | Parsed | Parsed | Parsed | Internal | Parsed into the DDL statement; runtime deletion retention is handled by server lifecycle code. |
| `TTL_SECONDS` | No | No | Required | No | Valid only on `STREAM`; omitted TTL is rejected. |
| `ACCESS_LEVEL` | No | Yes | No | No | Valid only on `SHARED`; omitted value defaults to `PRIVATE`. |
For storage templates and `STORAGE_ID` examples, see
[/docs/server/sql-reference/storage-id-usage](https://kalamdb.org/docs/server/sql-reference/storage-id-usage).
## DDL Role Matrix
| DDL operation | Anonymous | User | Service | DBA | System |
|---|---:|---:|---:|---:|---:|
| Create `USER` / `SHARED` / `STREAM` table | No | No | Yes | Yes | Yes |
| Create `SYSTEM` table | No | No | No | No | No via app SQL |
| Alter application table | No | No | Yes | Yes | Yes |
| Drop application table | No | No | No | Yes | Yes |
| Create view | No | No | No | Yes | Yes |
## Capability Matrix
| Capability | USER | SHARED | STREAM | SYSTEM |
|---|---|---|---|---|
| App DDL creation | Yes | Yes | Yes | Internal only |
| Per-user isolation | Yes | No | Yes | Internal |
| `ACCESS_LEVEL` option | No | Yes | No | No |
| `TTL_SECONDS` option | No | No | Required | No |
| `USE_USER_STORAGE` option | Yes | No | No | No |
| Cold-tier Parquet lifecycle | Yes | Yes | No | Internal-specific |
| File APIs supported | Yes | Yes | No | No |
| Live Query | Yes | Yes | Yes | No |
| Cluster multi-group Raft support | Yes | Yes | Yes | Internal |
### Capability Definitions
- **App DDL creation**: Whether application SQL can create/manage this table type directly.
- **Per-user isolation**: Whether data is automatically isolated by authenticated user/tenant.
- **`ACCESS_LEVEL` option**: Whether table-level access policy can be configured through `ACCESS_LEVEL`.
- **`TTL_SECONDS` option**: Whether the table supports TTL-based automatic row eviction.
- **`USE_USER_STORAGE` option**: Whether table storage can be explicitly pinned to user-scoped storage paths.
- **Cold-tier Parquet lifecycle**: Whether hot data can flush/compact into cold-tier Parquet segments.
- **File APIs supported**: Whether `/v1/files/...` upload/download flows are supported for rows in this table type.
- **Live Query**: Whether realtime subscriptions can stream change events from this table type.
- **Cluster multi-group Raft support**: Whether metadata/data operations for this table type participate in clustered Raft replication groups.
## Related Docs
- [/docs/server/sql-reference/tables](https://kalamdb.org/docs/server/sql-reference/tables)
- [/docs/server/sql-reference/dml](https://kalamdb.org/docs/server/sql-reference/dml)
- [/docs/server/sql-reference/impersonation](https://kalamdb.org/docs/server/sql-reference/impersonation)
- [/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers)
- [/docs/server/architecture/live-query](https://kalamdb.org/docs/server/architecture/live-query)
- [/docs/server/architecture/file-upload-datatype](https://kalamdb.org/docs/server/architecture/file-upload-datatype)
- [/docs/server/sql-reference/storage-id-usage](https://kalamdb.org/docs/server/sql-reference/storage-id-usage)
================================================================================
# Vector Search
URL: https://kalamdb.org/docs/server/architecture/vector-search
Source: content/server/architecture/vector-search.mdx
> Learn how to store embeddings, build cosine indexes, and run vector search in KalamDB with EMBEDDING columns and COSINE_DISTANCE queries.
# Vector Search
KalamDB supports vector search with native `EMBEDDING(n)` columns, cosine indexes, and SQL ranking via `COSINE_DISTANCE(...)`.
Use this when you need semantic retrieval for agent memory, RAG document recall, or similarity search over user-scoped data.
Vector search is table-type aware because embeddings live in normal KalamDB tables. Use
[/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types) to choose `USER` for
private semantic memory or `SHARED` for global catalogs.
## What Vector Search Looks Like
The current, tested flow in KalamDB is:
1. Create one or more `EMBEDDING(n)` columns.
2. Build a vector index on each embedding column with `ALTER TABLE ... CREATE INDEX ... USING COSINE`.
3. Query nearest rows with `ORDER BY COSINE_DISTANCE(...) LIMIT k`.
## Create a Vector Table
```sql
CREATE NAMESPACE IF NOT EXISTS rag;
CREATE TABLE rag.documents_vectors (
id BIGINT PRIMARY KEY,
doc_embedding EMBEDDING(3),
attachment_a_embedding EMBEDDING(3),
attachment_b_embedding EMBEDDING(3)
) WITH (TYPE = 'USER');
```
Why this shape works well:
- Keep document metadata and files in your main table.
- Keep embeddings in a parallel table keyed by the same `id`.
- Use `TYPE = 'USER'` when each signed-in user should search only their own vectors.
## Build Cosine Indexes
```sql
ALTER TABLE rag.documents_vectors
CREATE INDEX doc_embedding USING COSINE;
ALTER TABLE rag.documents_vectors
CREATE INDEX attachment_a_embedding USING COSINE;
ALTER TABLE rag.documents_vectors
CREATE INDEX attachment_b_embedding USING COSINE;
```
KalamDB currently documents and tests cosine similarity indexes. If you create multiple embedding columns, index each column you plan to search independently.
## Insert Embeddings
Embeddings are inserted as JSON-like numeric arrays in SQL strings:
```sql
INSERT INTO rag.documents_vectors (
id,
doc_embedding,
attachment_a_embedding,
attachment_b_embedding
) VALUES (
1,
'[1.0,0.0,0.0]',
'[0.95,0.05,0.0]',
'[0.90,0.10,0.0]'
);
```
Make sure the vector length matches the `EMBEDDING(n)` dimension declared in the schema.
## Run a Similarity Query
```sql
SELECT id
FROM rag.documents_vectors
ORDER BY COSINE_DISTANCE(doc_embedding, '[1.0,0.0,0.0]')
LIMIT 3;
```
Smaller `COSINE_DISTANCE(...)` values are more similar, so the nearest matches appear first.
Pass embeddings as a JSON array literal in the second argument (for example `'[1.0,0.0,0.0]'`).
That is the supported KalamDB pattern for `EMBEDDING(n)` columns. When both arguments are
array/list values, KalamDB can also use DataFusion 54's native `cosine_distance` implementation.
You can search any indexed embedding column:
```sql
SELECT id
FROM rag.documents_vectors
ORDER BY COSINE_DISTANCE(attachment_b_embedding, '[0.0,1.0,0.0]')
LIMIT 2;
```
## Join Search Results Back to Documents
In most apps, vector search is only the retrieval step. After ranking the embedding rows, join back to your main document table:
```sql
SELECT d.id, d.title, d.body
FROM rag.documents AS d
JOIN rag.documents_vectors AS v ON v.id = d.id
ORDER BY COSINE_DISTANCE(v.doc_embedding, '[1.0,0.0,0.0]')
LIMIT 5;
```
That pattern lets you keep rich rows, FILE attachments, and embeddings separate while still querying them together.
## RAG Pattern With Files
One tested KalamDB scenario stores:
- a USER-scoped documents table with two `FILE` columns
- a USER-scoped vectors table keyed by the same `id`
- vector indexes persisted across flushes
- similarity queries that continue working across hot and cold storage
This is a good fit for:
- private agent memory per user
- semantic search over uploaded files
- document recall before LLM summarization or answer generation
## Practical Notes
- Use `TYPE = 'USER'` for tenant-safe semantic search.
- Keep embedding generation in your application or worker pipeline; KalamDB stores and indexes the vectors.
- If you update or delete rows, the vector search results follow the table state.
- Vector search continues to work when some rows are still hot and others have already flushed to cold storage.
## Related Docs
- [/docs/server/architecture/datatypes](https://kalamdb.org/docs/server/architecture/datatypes)
- [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types)
- [/docs/server/sql-reference/tables](https://kalamdb.org/docs/server/sql-reference/tables)
- [/docs/server/sql-reference/functions](https://kalamdb.org/docs/server/sql-reference/functions)
- [/docs/ts-sdk/examples](https://kalamdb.org/docs/ts-sdk/examples)
================================================================================
# Auto-Provisioning Users
URL: https://kalamdb.org/docs/server/auth/auto-provisioning
Source: content/server/auth/auto-provisioning.mdx
> Understand when regular OIDC users are rowless, when explicit persisted users are required, and how OIDC subjects become KalamDB user IDs.
# Auto-Provisioning Users
KalamDB's current OIDC model has two distinct paths:
- regular external users can authenticate directly from validated token claims
- elevated or explicit external users are stored locally with `issuer` and `subject`
That means auto-provisioning is not just "create a row on first login" anymore. For the common `user` role path, KalamDB can stay stateless after checking whether a local row already exists for the OIDC subject.
## Enable Auto-Provisioning
```toml
[auth]
jwt_trusted_issuers = "kalamdb,https://idp.example.com/realms/kalamdb"
[auth.oidc]
enabled = true
issuer = "https://idp.example.com/realms/kalamdb"
client_id = "kalamdb"
scopes = ["openid", "email", "profile"]
auto_provision = true
default_role = "user"
```
Environment variable form:
```bash
```
If `auth.oidc.auto_provision = false`, authentication succeeds only when a matching local OIDC user already exists.
## What Happens For Regular Users
When these conditions are both true:
- `auth.oidc.auto_provision = true`
- `auth.oidc.default_role = "user"`
KalamDB uses the OIDC `sub` claim directly as the KalamDB `user_id`.
Regular users resolve from validated token claims when no persisted `system.users` row exists for that subject. KalamDB checks the row first so explicit persisted OIDC users, deleted users, and same-ID local password users keep their local policy.
## What Still Gets Persisted
Persisted local state is still important for:
- elevated users such as `service`, `dba`, or `system`
- soft-deleted external users that must remain blocked
- deployments with `auto_provision = false`
- deployments that intentionally use a default role other than `user`
Persisted OIDC users store only this identity binding:
```json
{
"issuer": "https://idp.example.com/realms/kalamdb",
"subject": "provider-subject"
}
```
For persisted OIDC users, `subject` must match `system.users.user_id`.
## Pre-Create Or Elevate An OIDC User
```sql
CREATE USER 'provider-subject'
WITH OIDC '{"issuer":"https://idp.example.com/realms/kalamdb","subject":"provider-subject"}'
ROLE dba
EMAIL 'alice@example.com';
```
Use explicit provisioning when you need:
- pre-approved access only
- an elevated role before first login
- no end-user auto-provisioning in production
## What Happens When The User Does Not Exist
If `auth.oidc.auto_provision = false`, the first request for an unknown external identity fails even when the provider token itself is valid.
That is expected. Token validation and user mapping are separate steps.
## Identity Stability Rules
KalamDB uses the token subject as the user ID, not the email address and not a provider code.
That means:
- changing email at the provider does not change the KalamDB user ID
- a different issuer or provider may emit a different subject for the same person
- deleting and recreating a provider-side account can create a new subject and therefore a new KalamDB user ID
## Recommended Rollout Pattern
1. Enable trusted issuers.
2. Turn on `auth.oidc.auto_provision = true` for normal end users.
3. Keep elevated roles explicit.
4. Check the created identity with `GET /v1/api/auth/me` or `SELECT CURRENT_USER();`.
5. Promote only the accounts that need broader access.
## Related
- [Authentication Overview](https://kalamdb.org/docs/server/auth)
- [Token Auth](https://kalamdb.org/docs/server/auth/token-auth)
- [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc)
- [Single OIDC Provider Model](https://kalamdb.org/docs/server/auth/providers)
- [User Management](https://kalamdb.org/docs/server/sql-reference/users)
================================================================================
# Authentication Providers
URL: https://kalamdb.org/docs/server/auth
Source: content/server/auth/index.mdx
> Authentication model overview for KalamDB. Learn how local auth and the single external OIDC provider fit together, how tokens are routed, and how OIDC subjects become user IDs.
# Authentication Overview
KalamDB has one authentication surface with two modes:
- local username/password login through `[auth.local]`
- one external OpenID Connect provider through `[auth.oidc]`
The same API and SDK surfaces work for both:
- `POST /v1/api/auth/login`
- `POST /v1/api/auth/refresh`
- `GET /v1/api/auth/me`
- `GET /v1/api/auth/login-options`
- bearer auth for `/v1/api/sql`, files, topics, and WebSocket traffic
## Authentication Methods
| Method | Header | Description |
|---|---|---|
| Basic Auth | `Authorization: Basic ` | Username + password login, usually exchanged for JWT |
| Bearer (Internal JWT) | `Authorization: Bearer ` | KalamDB-issued `HS256` token |
| Bearer (External OIDC) | `Authorization: Bearer ` | Token from the configured trusted OIDC issuer |
All bearer tokens go through the same validation pipeline. KalamDB routes internal and external tokens automatically from the JWT algorithm and issuer.
## Bearer Token Routing
```
Client Identity Provider KalamDB
│ │ │
│── sign in ────────────────►│ │
│◄── ID token (RS256) ────────│ │
│ │ │
│── Authorization: Bearer ─────────────────►│
│ │ 1. Check iss is trusted │
│ │ 2. OIDC discovery │
│ │ 3. Fetch + cache JWKS │
│ │ 4. Verify RS256 sig │
│ │ 5. Resolve/provision user│
│◄── SQL response ────────────────────────────────────── │
```
KalamDB inspects the token before verification:
1. **Algorithm** — `HS256` → internal shared-secret validation; `RS256/ES256/…` → external OIDC flow.
2. **Issuer (`iss`)** — must appear in `jwt_trusted_issuers`; untrusted issuers are rejected before any network I/O.
3. **JWKS** — keys are fetched on first use and cached per-issuer; key rotation is handled automatically.
4. **Audience (`aud`)** — validated against `auth.oidc.audience` or `auth.oidc.client_id` when configured.
## Single External OIDC Provider
KalamDB no longer uses a built-in provider enum or a provider matrix. The active external identity model is issuer-based:
```toml
[auth]
jwt_trusted_issuers = "kalamdb,https://idp.example.com/realms/kalamdb"
[auth.oidc]
enabled = true
display_name = "Company SSO"
issuer = "https://idp.example.com/realms/kalamdb"
client_id = "kalamdb"
scopes = ["openid", "email", "profile"]
auto_provision = true
default_role = "user"
```
Examples of issuers that fit this model:
- Dex
- Keycloak
- Okta
- Auth0
- Entra ID
- Google
- Firebase
The important point is that one KalamDB server process supports one configured external OIDC issuer.
## Identity Mapping
External users use the OIDC `sub` claim directly as the KalamDB `user_id`. There is no provider code, username prefix, or generated hash ID in the active model.
The same subject value appears in SQL identity, `system.users.user_id`, and PG extension `EXECUTE AS USER ''` workflows. The subject must be a valid KalamDB user ID: ASCII letters, digits, `_`, or `-`, up to 128 characters.
When KalamDB persists a local override or explicit OIDC user, it stores only this provider identity data:
```json
{
"issuer": "https://idp.example.com/realms/kalamdb",
"subject": "provider-subject"
}
```
## Auto-Provision vs Explicit Users
When `auth.oidc.auto_provision = true` and `auth.oidc.default_role = "user"`, regular external users authenticate as role `user` without a per-user row on the hot path.
KalamDB checks for an existing `system.users` row with `user_id = sub` first. That keeps explicit persisted OIDC users, deleted users, and same-ID local password users from being bypassed.
Elevated users still need explicit persisted overrides. Use this when a user must be `service`, `dba`, or `system`:
```sql
CREATE USER 'provider-subject'
WITH OIDC '{"issuer":"https://idp.example.com/realms/kalamdb","subject":"provider-subject"}'
ROLE dba
EMAIL 'alice@example.com';
```
## Browser And Device Login Surfaces
The Admin UI and CLI both discover auth capabilities from `GET /v1/api/auth/login-options`.
Current user-facing OIDC flows:
- Admin UI browser login through `/v1/api/auth/oidc/exchange-code`
- CLI browser login with `kalam login --oidc`
- CLI direct device login with `kalam login --oidc --no-browser`
- CLI brokered device login with `kalam login --oidc --no-browser --brokered`
## Role Management
Roles remain the same regardless of auth source:
- `user`: normal app or tenant user
- `service`: workers, automation, agents
- `dba`: database administration
- `system`: highest-privilege internal role
## Supported Algorithms
| Algorithm | Supported |
|---|---|
| HS256 | ✅ Internal tokens only |
| RS256 | ✅ |
| RS384 | ✅ |
| RS512 | ✅ |
| PS256 | ✅ |
| PS384 | ✅ |
| PS512 | ✅ |
| ES256 | ✅ |
| ES384 | ✅ |
| ES512 | ❌ Not supported |
## Related
- [Single OIDC Provider Model](https://kalamdb.org/docs/server/auth/providers)
- [Token Auth](https://kalamdb.org/docs/server/auth/token-auth)
- [Auto-Provisioning Users](https://kalamdb.org/docs/server/auth/auto-provisioning)
- [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc)
- [Dex](https://kalamdb.org/docs/server/integrations/dex)
================================================================================
# Single OIDC Provider Model
URL: https://kalamdb.org/docs/server/auth/providers
Source: content/server/auth/providers.mdx
> Understand KalamDB's current OIDC identity model: one configured issuer, direct subject user IDs, and no provider-family enum.
# Single OIDC Provider Model
KalamDB's active external auth model is issuer-based, not provider-family-based.
That means:
- one KalamDB server process supports one configured external OIDC provider
- the provider is identified by `auth.oidc.issuer` and `auth.oidc.client_id`
- there is no built-in provider enum, provider prefix table, or provider-specific username format in the active model
Dex, Keycloak, Okta, Auth0, Entra ID, Google, Firebase, and similar services all fit this same shape as long as they expose standards-compliant OIDC discovery.
## The Current `server.toml` Shape
```toml
[auth]
jwt_trusted_issuers = "kalamdb,https://idp.example.com/realms/kalamdb"
[auth.oidc]
enabled = true
display_name = "Company SSO"
issuer = "https://idp.example.com/realms/kalamdb"
client_id = "kalamdb"
scopes = ["openid", "email", "profile"]
auto_provision = true
default_role = "user"
```
## No Provider Matrix
The current docs and backend surface should not talk about:
- `[oauth]`
- `[oauth.providers.*]`
- provider name lists such as `keycloak`, `firebase`, `auth0`, and so on
- provider-code username formats such as `oidc:kcl:`
Those were part of an older design. The active model is a single configured OIDC issuer.
## How External Users Are Identified
External users use the provider `sub` claim directly as the KalamDB `user_id`. The same value is used by SQL identity, `system.users.user_id`, and PG extension `EXECUTE AS USER ''`.
Subjects must be valid KalamDB user IDs: ASCII letters, digits, `_`, or `-`, up to 128 characters. If the provider emits unsafe subject characters, normalize that at the IdP layer before using it with KalamDB.
Persisted OIDC users store the issuer and subject, and the `subject` must match the row's `user_id`:
```json
{
"issuer": "https://idp.example.com/realms/kalamdb",
"subject": "provider-subject"
}
```
## Auto-Provision And Explicit Users
Regular users can authenticate without a per-user row when:
- `auth.oidc.auto_provision = true`
- `auth.oidc.default_role = "user"`
Elevated roles should be explicit. Example:
```sql
CREATE USER 'provider-subject'
WITH OIDC '{"issuer":"https://idp.example.com/realms/kalamdb","subject":"provider-subject"}'
ROLE dba
EMAIL 'alice@example.com';
```
## Related
- [Authentication Overview](https://kalamdb.org/docs/server/auth)
- [Token Auth](https://kalamdb.org/docs/server/auth/token-auth)
- [Auto-Provisioning Users](https://kalamdb.org/docs/server/auth/auto-provisioning)
- [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc)
- [Dex](https://kalamdb.org/docs/server/integrations/dex)
================================================================================
# Token Auth
URL: https://kalamdb.org/docs/server/auth/token-auth
Source: content/server/auth/token-auth.mdx
> Set up token authentication in KalamDB. Covers local login, OIDC exchange flows, direct external bearer tokens, refresh, and SDK token-provider patterns.
# Token Auth
Use token auth when your app should send `Authorization: Bearer ` on every request instead of raw user/password credentials.
KalamDB supports two token sources:
- KalamDB-issued access and refresh tokens returned by local login or OIDC exchange endpoints
- direct external OIDC ID tokens from the configured trusted issuer
## Internal Token Flow
The built-in login flow is:
1. Send user and password to `POST /v1/api/auth/login`.
2. Receive `access_token` and `refresh_token`.
3. Send the access token on API and WebSocket requests.
4. Refresh it through `POST /v1/api/auth/refresh` when needed.
### Login
```bash
curl -X POST http://127.0.0.1:2900/v1/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"user":"admin","password":"AdminPass123!"}'
```
Typical response fields:
- `access_token`
- `refresh_token`
- `expires_at` or token-expiry metadata
- authenticated user profile fields
### Use the access token
```bash
TOKEN=""
curl -X POST http://127.0.0.1:2900/v1/api/sql \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"sql":"SELECT CURRENT_USER();"}'
```
## Refresh Tokens
```bash
REFRESH=""
curl -X POST http://127.0.0.1:2900/v1/api/auth/refresh \
-H "Authorization: Bearer $REFRESH"
```
Use the returned access token for subsequent requests.
## Checking Auth State
There are two practical checks:
### Check the current authenticated user
```bash
curl http://127.0.0.1:2900/v1/api/auth/me \
-H "Authorization: Bearer $TOKEN"
```
This confirms whether the token is accepted and shows the current user record.
### Check from SQL
```bash
curl -X POST http://127.0.0.1:2900/v1/api/sql \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"sql":"SELECT CURRENT_USER();"}'
```
This is the fastest way to confirm the runtime identity that SQL sees.
## OIDC Browser And Device Exchange Flows
The Admin UI and CLI normally exchange provider login results for KalamDB-issued tokens.
Endpoints:
- `POST /v1/api/auth/oidc/exchange-code` for browser Authorization Code + PKCE
- `POST /v1/api/auth/oidc/exchange-token` for direct device login when the CLI obtains a provider ID token
- `POST /v1/api/auth/oidc/device/start` and `POST /v1/api/auth/oidc/device/poll` for brokered device flow
CLI entry points:
```bash
# Browser PKCE login
kalam login --instance local --url http://127.0.0.1:2900 --oidc
# Direct device login
kalam login --instance local --url http://127.0.0.1:2900 --oidc --no-browser
# Brokered device login
kalam login --instance local --url http://127.0.0.1:2900 --oidc --no-browser --brokered
```
All three flows return or save KalamDB access and refresh tokens. They do not persist provider credentials.
## Direct External Bearer Tokens
If you already have a valid token from the configured trusted OIDC issuer, you can also skip the exchange endpoints and send that provider token directly as a bearer token.
KalamDB will:
1. read `alg` and `iss`
2. reject untrusted issuers
3. perform OIDC discovery for the issuer
4. fetch and cache the issuer JWKS
5. validate the token signature and claims
6. resolve or create the matching local user
OIDC setup details live in:
- [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc)
- [Single OIDC Provider Model](https://kalamdb.org/docs/server/auth/providers)
- [Dex](https://kalamdb.org/docs/server/integrations/dex)
## SDK Pattern
For long-lived apps, return a fresh token from the SDK auth provider callback instead of hard-coding a token once.
### TypeScript
```ts
const authProvider: AuthProvider = async () => {
const token = await myApp.getOrRefreshJwt();
return Auth.jwt(token);
};
const client = createClient({
url: 'https://db.example.com',
authProvider,
});
```
### Dart
```dart
final client = await KalamClient.connect(
url: 'https://db.example.com',
authProvider: () async {
final token = await myApp.getOrRefreshJwt();
return Auth.jwt(token);
},
);
```
## Configuration Notes
Internal token auth still depends on a strong shared secret:
```toml
[auth]
jwt_secret = "replace-with-strong-random-secret-32-plus-chars"
cookie_secure = true
```
OIDC also depends on the configured single provider block:
```toml
[auth.oidc]
enabled = true
issuer = "https://idp.example.com/realms/kalamdb"
client_id = "kalamdb"
scopes = ["openid", "email", "profile"]
```
Use `KALAMDB_JWT_SECRET` and the `KALAMDB_AUTH_OIDC_*` env vars in production instead of committing secrets to `server.toml`.
## Related
- [Authentication Overview](https://kalamdb.org/docs/server/auth)
- [Auto-Provisioning Users](https://kalamdb.org/docs/server/auth/auto-provisioning)
- [HTTP API Reference](https://kalamdb.org/docs/server/api/http-reference)
- [Authentication & Bootstrap](https://kalamdb.org/docs/server/getting-started/authentication)
================================================================================
# Benchmarking
URL: https://kalamdb.org/docs/server/benchmarking
Source: content/server/benchmarking/index.mdx
> Static KalamDB benchmark report links synced from benchv2/results, with one HTML benchmark report listed for each tracked version.
# Benchmarking
This page publishes the newest benchmark HTML report for each KalamDB version currently synced from the `benchv2/results` folder in the main repository.
The June 17 [`v0.5.3-rc.1` pre-release](https://kalamdb.org/docs/server/release-v0-5-3-rc-1) is the newest synced run: **38/38 benchmarks passed** with **213,667.71 ms measured benchmark time**, an **8.0% improvement** over `0.5.2-rc.1`. Open the [exact HTML benchmark artifact](https://kalamdb.org/benchmark/results/bench-2026-06-17-131609-0-5-3-rc-1.html) or read the [release blog post](https://kalamdb.org/blog/kalamdb-v0-5-3-rc-1-pre-release).
## What The Table Shows
- `Version` is read from the synced benchmark metadata when available.
- `Run at` is the benchmark timestamp associated with the synced report.
- `Passed/Total` comes from the synced benchmark summary.
- `Duration` is the displayed benchmark runtime for that report, preferring measured time when newer reports provide it.
- `Delta` compares each row's displayed duration against the next older listed run, with an arrow and color showing whether it got faster or slower.
- `HTML report` opens the static benchmark report directly.
================================================================================
# Advanced Configuration
URL: https://kalamdb.org/docs/server/configurations/advanced
Source: content/server/configurations/advanced.mdx
> User-friendly guide to every KalamDB configuration option, with plain-English explanations, practical tuning advice, and production-ready examples.
# Advanced Configuration
This page is the canonical public reference for every supported `server.toml` key and every documented `KALAMDB_*` runtime override.
Use it when you need exact key names, actual defaults, or a clear answer to whether a setting is configured through TOML, an environment variable, or both.
## Before You Start
- If you are new to setup, start with
[/docs/server/getting-started/configuration](https://kalamdb.org/docs/server/getting-started/configuration).
- If you want the fully commented sample file, download [server.example.toml (full commented sample)](https://raw.githubusercontent.com/kalamdb/KalamDB/refs/heads/main/backend/server.example.toml).
- The tables below show the real defaults from the config crate. The checked-in sample config may intentionally use different values for local development, CI, or demo environments.
Focused guides:
- [/docs/server/configurations/logging](https://kalamdb.org/docs/server/configurations/logging)
- [/docs/server/configurations/otel](https://kalamdb.org/docs/server/configurations/otel)
- [/docs/server/configurations/oidc](https://kalamdb.org/docs/server/configurations/oidc)
- [/docs/server/integrations/minio](https://kalamdb.org/docs/server/integrations/minio)
- [/docs/server/integrations/keycloak](https://kalamdb.org/docs/server/integrations/keycloak)
- [/docs/server/integrations/jaeger](https://kalamdb.org/docs/server/integrations/jaeger)
## Loading Order
1. Load `server.toml`.
2. Apply config-loader environment overrides from `KALAMDB_*` variables.
3. Apply runtime-only environment behavior that lives outside the config crate.
4. Normalize paths and validate the final configuration.
Runtime-only env vars are currently:
- `KALAMDB_ROOT_PASSWORD` for root bootstrap password selection during startup.
- `KALAMDB_TOKIO_WORKER_THREADS` for overriding Tokio runtime worker threads.
## Environment Override Reference
Only the variables below are supported today. If a setting is not listed here, configure it in `server.toml`.
Boolean parsing notes:
- Most positive boolean env vars treat `true`, `1`, and `yes` as enabled.
- `KALAMDB_COOKIE_SECURE` is inverted for convenience: `false`, `0`, and `no` disable it; any other non-empty value enables it.
List parsing notes:
- `KALAMDB_SECURITY_CORS_ALLOWED_ORIGINS` and `KALAMDB_SECURITY_TRUSTED_PROXY_RANGES` accept comma-separated values.
- `KALAMDB_CLUSTER_PEERS` accepts `node_id@rpc_addr@api_addr[@rpc_server_name]` entries separated by semicolons.
### Server, Logging, and Storage
| Environment variable | Maps to | Notes |
|---|---|---|
| `KALAMDB_SERVER_HOST` | `server.host` | Bind host/interface. |
| `KALAMDB_SERVER_PORT` | `server.port` | HTTP listen port. |
| `KALAMDB_SERVER_PUBLIC_ORIGIN` | `server.public_origin` | Empty string preserves browser-origin fallback behavior. |
| `KALAMDB_SERVER_WORKERS` | `server.workers` | Actix worker threads. |
| `KALAMDB_LOG_LEVEL` | `logging.level` | `error`, `warn`, `info`, `debug`, `trace`. |
| `KALAMDB_LOG_FORMAT` | `logging.format` | `compact`, `pretty`, or `json`. |
| `KALAMDB_LOGS_DIR` | `logging.logs_path` | Directory for `server.log`, `server.jsonl`, and `slow.log`. |
| `KALAMDB_LOG_TO_CONSOLE` | `logging.log_to_console` | Also emit logs to stdout/stderr. |
| `KALAMDB_SLOW_QUERY_THRESHOLD_MS` | `logging.slow_query_threshold_ms` | Slow query threshold in milliseconds. |
| `KALAMDB_OTLP_ENABLED` | `logging.otlp.enabled` | Enable OTLP trace export. |
| `KALAMDB_OTLP_ENDPOINT` | `logging.otlp.endpoint` | Collector endpoint. |
| `KALAMDB_OTLP_PROTOCOL` | `logging.otlp.protocol` | `grpc` or `http`. |
| `KALAMDB_OTLP_SERVICE_NAME` | `logging.otlp.service_name` | Service name shown by the collector/backend. |
| `KALAMDB_OTLP_TIMEOUT_MS` | `logging.otlp.timeout_ms` | OTLP export timeout. |
| `KALAMDB_DATA_DIR` | `storage.data_path` | Base data directory. |
### Topics, Auth, Security, and WebSockets
| Environment variable | Maps to | Notes |
|---|---|---|
| `KALAMDB_TOPIC_VISIBILITY_TIMEOUT_SECS` | `topics.visibility_timeout_secs` | Canonical topic visibility timeout override. |
| `KALAMDB_VISIBILITY_TIMEOUT_SECS` | `topics.visibility_timeout_secs` | Legacy compatibility alias. |
| `KALAMDB_TOPIC_DEFAULT_RETENTION_SECONDS` | `topics.default_retention_seconds` | Default topic time retention. |
| `KALAMDB_TOPIC_DEFAULT_RETENTION_MAX_BYTES` | `topics.default_retention_max_bytes` | Default topic byte retention. |
| `KALAMDB_TOPIC_RETENTION_CHECK_INTERVAL_SECONDS` | `topics.retention_check_interval_seconds` | Scheduler interval. |
| `KALAMDB_TOPIC_RETENTION_BATCH_SIZE` | `topics.retention_batch_size` | Delete batch size. |
| `KALAMDB_JWT_SECRET` | `auth.jwt_secret` | Internal JWT signing/verification secret. |
| `KALAMDB_JWT_TRUSTED_ISSUERS` | `auth.jwt_trusted_issuers` | Comma-separated issuer list. |
| `KALAMDB_JWT_EXPIRY_HOURS` | `auth.jwt_expiry_hours` | Internal token TTL. |
| `KALAMDB_COOKIE_SECURE` | `auth.cookie_secure` | HTTPS-only auth cookies. |
| `KALAMDB_ALLOW_REMOTE_SETUP` | `auth.allow_remote_setup` | Enables first-time setup from non-localhost clients. |
| `KALAMDB_PG_AUTH_TOKEN` | `auth.pg_auth_token` | Pre-shared token for `pg_kalam` gRPC auth. |
| `KALAMDB_AUTH_OIDC_AUTO_PROVISION` | `auth.oidc.auto_provision` | OIDC auto-provisioning override. |
| `KALAMDB_SECURITY_CORS_ALLOWED_ORIGINS` | `security.cors.allowed_origins` | Comma-separated list or `*`. |
| `KALAMDB_SECURITY_TRUSTED_PROXY_RANGES` | `security.trusted_proxy_ranges` | Canonical proxy trust override. |
| `KALAMDB_TRUSTED_PROXY_RANGES` | `security.trusted_proxy_ranges` | Legacy env alias kept for compatibility. |
| `KALAMDB_RATE_LIMIT_AUTH_REQUESTS_PER_IP_PER_SEC` | `rate_limit.max_auth_requests_per_ip_per_sec` | Brute-force protection rate limit. |
| `KALAMDB_WEBSOCKET_CLIENT_TIMEOUT_SECS` | `websocket.client_timeout_secs` | Client heartbeat timeout. |
| `KALAMDB_WEBSOCKET_AUTH_TIMEOUT_SECS` | `websocket.auth_timeout_secs` | Time allowed before auth message arrives. |
| `KALAMDB_WEBSOCKET_HEARTBEAT_INTERVAL_SECS` | `websocket.heartbeat_interval_secs` | Heartbeat scan interval. |
### Cluster and RPC TLS
| Environment variable | Maps to | Notes |
|---|---|---|
| `KALAMDB_CLUSTER_ID` | `cluster.cluster_id` | Creates the cluster section if needed. |
| `KALAMDB_NODE_ID` | `cluster.node_id` | Canonical node ID override. |
| `KALAMDB_CLUSTER_NODE_ID` | `cluster.node_id` | Legacy alias for `KALAMDB_NODE_ID`. |
| `KALAMDB_CLUSTER_RPC_ADDR` | `cluster.rpc_addr` | Advertised Raft RPC address. |
| `KALAMDB_CLUSTER_API_ADDR` | `cluster.api_addr` | Advertised API address. |
| `KALAMDB_CLUSTER_PEERS` | `cluster.peers` | `node_id@rpc_addr@api_addr[@rpc_server_name];...` |
| `KALAMDB_RPC_TLS_ENABLED` | `rpc_tls.enabled` | Enable TLS/mTLS on the gRPC listener. |
| `KALAMDB_RPC_TLS_CA_CERT` | `rpc_tls.ca_cert` | File path or inline PEM. |
| `KALAMDB_RPC_TLS_SERVER_CERT` | `rpc_tls.server_cert` | File path or inline PEM. |
| `KALAMDB_RPC_TLS_SERVER_KEY` | `rpc_tls.server_key` | File path or inline PEM. |
| `KALAMDB_RPC_TLS_REQUIRE_CLIENT_CERT` | `rpc_tls.require_client_cert` | Require client certs signed by `ca_cert`. |
### Runtime-Only Environment Variables
| Environment variable | Effect | Notes |
|---|---|---|
| `KALAMDB_ROOT_PASSWORD` | Sets the initial root password during startup bootstrap. | This does not come from `apply_env_overrides`; it is read in server lifecycle startup. It takes precedence over `auth.root_password`. |
| `KALAMDB_TOKIO_WORKER_THREADS` | Overrides Tokio runtime worker thread count. | This is read in the server binary and takes precedence over `performance.tokio_worker_threads`. |
## Full `server.toml` Reference
### Top-Level Keys
| Key | Default | Description |
|---|---|---|
| `transaction_timeout_secs` | `300` | Maximum lifetime of an open transaction before the server aborts it. |
| `max_transaction_buffer_bytes` | `104857600` (100 MB) | Maximum in-memory transaction overlay size before new writes are rejected. |
### `[server]`
| Key | Default | Description |
|---|---|---|
| `host` | `"127.0.0.1"` | Network interface to bind. Use `0.0.0.0` only with explicit CORS/origin policy. |
| `port` | `2900` | HTTP port for REST, SQL, auth, and WebSocket entrypoints. |
| `public_origin` | unset | Public browser-facing origin used by the Admin UI for `/v1/api` and `/v1/ws`. |
| `workers` | `0` | Actix worker threads. `0` means automatic sizing. |
| `api_version` | `"v1"` | Version prefix for API routes. |
| `enable_http2` | `true` | Enables HTTP/1.1 + cleartext HTTP/2 negotiation. |
| `ui_path` | unset | Path to a built Admin UI bundle to serve from the server. |
### `[storage]`
| Key | Default | Description |
|---|---|---|
| `data_path` | `"./data"` | Base path for RocksDB, Parquet storage, snapshots, streams, exports, and temp files. |
| `shared_tables_template` | `"{namespace}/{tableName}"` | Folder template for shared tables. |
| `user_tables_template` | `"{namespace}/{tableName}/{userId}"` | Folder template for user-scoped tables. |
#### `[storage.remote_timeouts]`
| Key | Default | Description |
|---|---|---|
| `request_timeout_secs` | `60` | Request timeout for remote storage backends. |
| `connect_timeout_secs` | `10` | Connection timeout for remote storage backends. |
#### `[storage.rocksdb]`
| Key | Default | Description |
|---|---|---|
| `block_cache_size` | `2097152` (2 MB) | Shared RocksDB read block cache across all column families. |
| `max_background_jobs` | `4` | Background compaction and flush worker count. |
| `max_open_files` | `512` | Maximum open files kept by RocksDB. `-1` means unlimited. |
| `sync_writes` | `false` | Fsync WAL on each write for stronger durability and lower throughput. |
| `disable_wal` | `false` | Disables WAL for speed at the cost of crash recovery guarantees. |
| `compact_on_startup` | `false` | Compact column families during startup to reduce SST spread. Disabled by default to keep startup fast. |
#### `[storage.rocksdb.cf_profiles.system_meta]`
| Key | Default | Description |
|---|---|---|
| `write_buffer_size` | `32768` | Write buffer size for system metadata CFs. |
| `max_write_buffers` | `2` | Max RocksDB memtables for system metadata CFs. |
#### `[storage.rocksdb.cf_profiles.system_index]`
| Key | Default | Description |
|---|---|---|
| `write_buffer_size` | `32768` | Write buffer size for system index CFs. |
| `max_write_buffers` | `2` | Max RocksDB memtables for system index CFs. |
#### `[storage.rocksdb.cf_profiles.hot_data]`
| Key | Default | Description |
|---|---|---|
| `write_buffer_size` | `131072` | Write buffer size for hot data CFs. |
| `max_write_buffers` | `2` | Max RocksDB memtables for hot data CFs. |
#### `[storage.rocksdb.cf_profiles.hot_index]`
| Key | Default | Description |
|---|---|---|
| `write_buffer_size` | `65536` | Write buffer size for hot index CFs. |
| `max_write_buffers` | `2` | Max RocksDB memtables for hot index CFs. |
#### `[storage.rocksdb.cf_profiles.raft]`
| Key | Default | Description |
|---|---|---|
| `write_buffer_size` | `262144` | Write buffer size for the Raft CF. |
| `max_write_buffers` | `2` | Max RocksDB memtables for the Raft CF. |
### `[datafusion]`
| Key | Default | Description |
|---|---|---|
| `memory_limit` | `33554432` (32 MB) | Query execution memory budget. |
| `query_parallelism` | `2` | Query execution worker concurrency. |
| `max_partitions` | `4` | Maximum partitions used in planning and scans. |
| `batch_size` | `1024` | Record batch size for execution operators. |
### `[flush]`
| Key | Default | Description |
|---|---|---|
| `default_row_limit` | `10000` | Row threshold that triggers flush when no per-table override exists. |
| `default_time_interval` | `300` | Time threshold in seconds for automatic flush. |
| `flush_batch_size` | `10000` | Rows loaded into memory per flush batch. |
| `check_interval_seconds` | `60` | Scheduler scan interval for pending writes. `0` disables the scheduler. |
#### `[flush.compaction]`
Optional post-flush Parquet compaction for trailing small segments. This is configured in
`server.toml`; there are no dedicated `KALAMDB_*` overrides for these keys today.
For the architecture behind these settings, see
[/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers) and
[/docs/server/architecture/manifests](https://kalamdb.org/docs/server/architecture/manifests).
| Key | Default | Description |
|---|---|---|
| `enabled` | `false` | Enable leader-only post-flush compaction for eligible `USER` and `SHARED` table scopes. |
| `min_eligible_segments` | `5` | Minimum number of newest trailing small segments required before a compaction job is considered. |
| `max_segments_per_run` | `8` | Maximum number of newest trailing segments rewritten by one compaction job. |
| `user_max_segment_rows` | `10000` | User-table segments below this row count are considered small. |
| `shared_max_segment_rows` | `25000` | Shared-table segments below this row count are considered small. |
Compaction rewrites only the manifest tail: newest committed segments on the same schema version
and below the configured row target. If a newer flush changes that tail while compaction is writing,
the swap is skipped and the compacted output is discarded.
### `[retention]`
| Key | Default | Description |
|---|---|---|
| `enable_dba_stats` | `true` | Enables background `dba.stats` collection. |
| `dba_stats_retention_days` | `7` | Number of days to retain `dba.stats` samples. `0` disables automatic cleanup. |
### `[stream]`
| Key | Default | Description |
|---|---|---|
| `default_ttl_seconds` | `10` | Default lifetime of stream rows/events. |
| `default_max_buffer` | `10000` | Default in-memory stream buffer limit. |
| `eviction_interval_seconds` | `60` | Background eviction interval for expired stream data. |
### `[manifest_cache]`
Manifest cache behavior is explained in
[/docs/server/architecture/manifests](https://kalamdb.org/docs/server/architecture/manifests).
| Key | Default | Description |
|---|---|---|
| `eviction_interval_seconds` | `300` | Interval between manifest cache eviction passes. |
| `max_entries` | `500` | Maximum hot manifest entries kept in memory. |
| `eviction_ttl_days` | `7` | Age after which unused manifest entries are evicted. |
### `[limits]`
| Key | Default | Description |
|---|---|---|
| `max_message_size` | `1048576` (1 MB) | Maximum accepted request payload/message size. |
| `max_query_limit` | `1000` | Hard maximum `LIMIT` the server will accept. |
| `default_query_limit` | `50` | Implicit `LIMIT` when the query omits one. |
### `[logging]`
For end-to-end examples and deployment guidance, use
[/docs/server/configurations/logging](https://kalamdb.org/docs/server/configurations/logging).
| Key | Default | Description |
|---|---|---|
| `level` | `"info"` | Global log verbosity. |
| `logs_path` | `"./logs"` | Directory for server log files. |
| `log_to_console` | `true` | Also emit logs to stdout/stderr. |
| `format` | `"compact"` | `compact`, `pretty`, or `json`. |
| `slow_query_threshold_ms` | `1200` | Slow query threshold in milliseconds. |
`[logging.targets]` is an optional dynamic table of per-target log levels, for example:
```toml
[logging.targets]
datafusion = "warn"
arrow = "warn"
parquet = "warn"
```
#### `[logging.otlp]`
| Key | Default | Description |
|---|---|---|
| `enabled` | `false` | Enables OpenTelemetry trace export. |
| `endpoint` | `"http://127.0.0.1:4317"` | Collector endpoint. |
| `protocol` | `"grpc"` | OTLP transport: `grpc` or `http`. |
| `service_name` | `"kalamdb-server"` | Service name attached to exported spans. |
| `timeout_ms` | `3000` | OTLP export timeout in milliseconds. |
### `[performance]`
| Key | Default | Description |
|---|---|---|
| `request_timeout` | `30` | Maximum full request execution time in seconds. |
| `keepalive_timeout` | `75` | Idle HTTP keep-alive timeout in seconds. |
| `max_connections` | `25000` | Maximum simultaneous client connections. |
| `backlog` | `4096` | Kernel listen backlog for pending TCP connections. |
| `tokio_worker_threads` | `0` | Tokio runtime worker threads. `0` means auto-size, capped in the binary. |
| `worker_max_blocking_threads` | `32` | Per-worker cap for blocking tasks such as RocksDB I/O. |
| `client_request_timeout` | `5` | Time allowed for a client to finish sending request headers. |
| `client_disconnect_timeout` | `2` | Graceful client disconnect timeout. |
| `max_header_size` | `16384` (16 KB) | Maximum HTTP header size. |
### `[rate_limit]`
| Key | Default | Description |
|---|---|---|
| `max_queries_per_sec` | `100` | Per-user SQL query rate limit. |
| `max_messages_per_sec` | `50` | Per-WebSocket incoming message rate limit. |
| `max_subscriptions_per_user` | `10` | Maximum live subscriptions per user. |
| `max_auth_requests_per_ip_per_sec` | `20` | Rate limit for auth/setup endpoints per IP. |
| `max_connections_per_ip` | `100` | Maximum concurrent connections per IP. |
| `max_requests_per_ip_per_sec` | `200` | Pre-auth request flood protection per IP. |
| `request_body_limit_bytes` | `10485760` (10 MB) | Request body cap used by connection protection. |
| `ban_duration_seconds` | `300` | Temporary IP ban duration after repeated abuse. |
| `enable_connection_protection` | `true` | Master switch for connection abuse protection. |
| `cache_max_entries` | `1000` | Maximum cached rate-limit entries. |
| `cache_ttl_seconds` | `600` | Idle TTL for cached rate-limit entries. |
### `[security]`
| Key | Default | Description |
|---|---|---|
| `trusted_proxy_ranges` | `[]` | Proxy IPs or CIDR ranges allowed to supply forwarded client IP headers. |
| `max_ws_message_size` | `1048576` (1 MB) | Maximum accepted WebSocket message size. |
| `strict_ws_origin_check` | `false` | Reject WebSocket connections that lack a valid `Origin` header. |
| `max_request_body_size` | `10485760` (10 MB) | Global HTTP request body cap. |
#### `[security.cors]`
| Key | Default | Description |
|---|---|---|
| `allowed_origins` | `[]` | Allowed browser origins. Empty behaves like wildcard at runtime, but non-localhost startup validation requires explicit origins. |
| `allowed_methods` | `["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]` | Allowed CORS methods. |
| `allowed_headers` | `["Authorization", "Content-Type", "Accept", "Origin", "X-Requested-With"]` | Allowed browser request headers. |
| `expose_headers` | `[]` | Response headers exposed to browser JavaScript. |
| `allow_credentials` | `false` | Allow cookies or authorization headers on CORS requests. |
| `max_age` | `3600` | Preflight cache duration in seconds. |
| `allow_private_network` | `false` | Enables private network preflight support where browsers implement it. |
### `[websocket]`
| Key | Default | Description |
|---|---|---|
| `client_timeout_secs` | `10` | Maximum time without client heartbeat activity before disconnect. |
| `auth_timeout_secs` | `3` | Time allowed after connect before auth must arrive. |
| `heartbeat_interval_secs` | `5` | Interval between heartbeat scans/pings. |
### `[auth]` or `[authentication]`
Both section names are accepted. The config crate stores them as `auth` internally.
| Key | Default | Description |
|---|---|---|
| `root_password` | unset | Config-file root bootstrap password. Runtime env `KALAMDB_ROOT_PASSWORD` takes precedence. |
| `jwt_secret` | `"CHANGE_ME_IN_PRODUCTION"` | Secret for signing and validating internal HS256 tokens. |
| `jwt_trusted_issuers` | `""` | Comma-separated trusted external issuer list. |
| `jwt_expiry_hours` | `24` | Lifetime of internal JWTs in hours. |
| `cookie_secure` | `true` | Restricts auth cookies to HTTPS. |
| `allow_remote_setup` | `false` | Allow first-time setup from non-localhost clients. |
| `pg_auth_token` | unset | Pre-shared token for `pg_kalam` gRPC authentication. |
Compatibility note: older configs may still contain a pre-rename password-policy key; the current supported key is `auth.local.enforce_password_complexity`.
#### `[auth.local]`
| Key | Default | Description |
|---|---|---|
| `enabled` | `true` | Enables local username/password login and password setup. |
| `bcrypt_cost` | `12` | Password hash work factor. Valid range is determined by bcrypt. |
| `min_password_length` | `8` | Minimum accepted password length. |
| `max_password_length` | `1024` | Maximum locally accepted password length. |
| `enforce_password_complexity` | `false` | Require uppercase, lowercase, digit, and special characters in passwords. |
#### `[auth.oidc]`
KalamDB supports one external OpenID Connect provider per server process.
| Key | Default | Description |
|---|---|---|
| `enabled` | `false` | Enables the configured OIDC provider. |
| `display_name` | unset | Human-facing provider label for UI and CLI login. |
| `issuer` | unset | OIDC issuer URL. Must be included in `auth.jwt_trusted_issuers`. |
| `client_id` | unset | Client ID used for browser/device flows and default audience validation. |
| `client_secret` | unset | Client secret for confidential clients or brokered provider exchanges. |
| `scopes` | `["openid", "email", "profile"]` | OIDC scopes. Must include `openid`. |
| `device_authorization_endpoint` | unset | Optional device-flow endpoint override when discovery omits it. |
| `broker_device_flow_enabled` | `false` | Enables KalamDB-brokered OIDC device flow. |
| `auto_provision` | `false` | Allows absent regular OIDC subjects when default role is `user`; creates rows for elevated default roles. |
| `default_role` | `"user"` | Role used for auto-provisioned OIDC users. |
| `audience` | unset | Explicit audience override; defaults to `client_id`. |
### `[user_management]`
| Key | Default | Description |
|---|---|---|
| `deletion_grace_period_days` | `30` | Grace period before soft-deleted users are purged. |
| `cleanup_job_schedule` | `"0 2 * * *"` | Cron schedule for user cleanup. |
### `[files]`
| Key | Default | Description |
|---|---|---|
| `max_size_bytes` | `26214400` (25 MB) | Maximum size of a single uploaded file. |
| `max_files_per_request` | `20` | Maximum uploaded files accepted in one request. |
| `max_files_per_folder` | `5000` | Folder rotation threshold for durable file storage. |
| `staging_path` | `"./data/tmp"` | Temporary upload staging directory. |
| `allowed_mime_types` | `[]` | Allowed MIME types. Empty means allow all. |
### `[shutdown.flush]`
| Key | Default | Description |
|---|---|---|
| `timeout` | `300` | Maximum wait time in seconds for flush jobs during graceful shutdown. |
### `[jobs]`
| Key | Default | Description |
|---|---|---|
| `max_concurrent` | `10` | Maximum concurrently running background jobs. |
| `max_retries` | `3` | Retry attempts before a job is marked failed. |
| `retry_backoff_ms` | `100` | Initial retry backoff in milliseconds. |
| `wal_cleanup_interval_seconds` | `300` | Interval for memtable flushes that reclaim stale WAL files. `0` disables it. |
### `[execution]`
| Key | Default | Description |
|---|---|---|
| `handler_timeout_seconds` | `30` | Maximum execution handler duration in seconds. |
| `max_parameters` | `50` | Maximum parameter count per statement. |
| `max_parameter_size_bytes` | `524288` (512 KB) | Maximum size of a single parameter value. |
| `sql_plan_cache_max_entries` | `200` | Maximum cached SQL logical plans. |
| `sql_plan_cache_ttl_seconds` | `900` | Idle TTL for cached SQL logical plans. |
### `[topics]`
| Key | Default | Description |
|---|---|---|
| `visibility_timeout_secs` | `60` | Hide unacknowledged consumer claims for this many seconds before redelivery. |
| `default_retention_seconds` | `604800` (7 days) | Default topic time retention. |
| `default_retention_max_bytes` | `1073741824` (1 GiB) | Default topic byte retention per partition. |
| `retention_check_interval_seconds` | `3600` | Topic retention scheduler interval in seconds. `0` disables scheduling. |
| `retention_batch_size` | `10000` | Maximum deletes per partition in a single retention pass. |
### `[rpc_tls]`
This secures the shared gRPC listener used by Raft replication, cluster RPC, and the PostgreSQL extension.
| Key | Default | Description |
|---|---|---|
| `enabled` | `false` | Enable TLS or mTLS on the gRPC listener. |
| `ca_cert` | unset | CA certificate used to validate incoming client certificates. File path or inline PEM. |
| `server_cert` | unset | Server certificate. File path or inline PEM. |
| `server_key` | unset | Private key for `server_cert`. File path or inline PEM. |
| `require_client_cert` | `true` | Require clients to present a CA-signed certificate. |
### `[cluster]`
Omit this section entirely for standalone mode.
For the Multi-Raft model behind these options, see
[/docs/server/architecture/clustering](https://kalamdb.org/docs/server/architecture/clustering).
| Key | Default | Description |
|---|---|---|
| `cluster_id` | `"cluster"` | Logical cluster identifier shared by all nodes. |
| `node_id` | required | Unique node ID for this server. Must be greater than `0`. |
| `rpc_addr` | `"127.0.0.1:2910"` | Advertised Raft RPC address. Use a reachable address, not a wildcard, in real clusters. |
| `api_addr` | `"0.0.0.0:2900"` | Advertised API address for this node. |
| `user_shards` | `8` | Number of user-data shards. |
| `shared_shards` | `1` | Number of shared-data shards. |
| `heartbeat_interval_ms` | `250` | Raft leader heartbeat interval. |
| `election_timeout_ms` | `(500, 1000)` | Min/max election timeout range. Min must exceed heartbeat. |
| `snapshot_policy` | `"LogsSinceLast(1000)"` | Snapshot policy. Use `LogsSinceLast(N)` or `Never`. |
| `max_snapshots_to_keep` | `3` | Snapshot retention count. `0` keeps all snapshots. |
| `replication_timeout_ms` | `5000` | Timeout for learner catch-up and replication progress. |
| `reconnect_interval_ms` | `3000` | Delay between reconnect attempts to unreachable peers. |
| `peer_wait_max_retries` | unset | Optional cap on peer-startup readiness retries. |
| `peer_wait_initial_delay_ms` | unset | Optional initial delay between peer readiness checks. |
| `peer_wait_max_delay_ms` | unset | Optional backoff cap for peer readiness checks. |
#### `[[cluster.peers]]`
| Key | Default | Description |
|---|---|---|
| `node_id` | required | Peer node ID. |
| `rpc_addr` | required | Peer RPC address. |
| `api_addr` | required | Peer API address. |
| `rpc_server_name` | unset | Optional TLS server-name override for SNI/hostname verification. |
**Total Raft groups** = 3 fixed groups (system metadata, user metadata, jobs) + `user_shards` + `shared_shards`.
Validation highlights:
- `node_id` must be greater than `0`
- `election_timeout_ms.0` must be greater than `heartbeat_interval_ms`
- `election_timeout_ms.1` must be greater than `election_timeout_ms.0`
- `user_shards` and `shared_shards` must be greater than `0`
- non-localhost multi-node clusters require `rpc_tls.enabled = true`
================================================================================
# Configurations
URL: https://kalamdb.org/docs/server/configurations
Source: content/server/configurations/index.mdx
> Complete configuration guide for KalamDB deployments. Learn how to tune your server, configure logging, configure OIDC, and set up OpenTelemetry.
# Configurations
This section centralizes runtime configuration for KalamDB.
If you are just getting started, begin with [Getting Started: Configuration](https://kalamdb.org/docs/server/getting-started/configuration).
For full production-grade tuning and every configuration section exposed by the config crate, continue to [Advanced Configuration](https://kalamdb.org/docs/server/configurations/advanced).
For focused guides, continue to:
- [Logging & Traceability](https://kalamdb.org/docs/server/configurations/logging)
- [OpenTelemetry (OTEL)](https://kalamdb.org/docs/server/configurations/otel)
- [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc)
Integration-specific guides are in [Integrations](https://kalamdb.org/docs/server/integrations):
- [Dex](https://kalamdb.org/docs/server/integrations/dex)
- [MinIO (S3-Compatible)](https://kalamdb.org/docs/server/integrations/minio)
- [Jaeger](https://kalamdb.org/docs/server/integrations/jaeger)
## Download Full Sample Config
If you want a complete config file with inline comments for every section, download:
- [server.example.toml (full commented sample)](https://raw.githubusercontent.com/kalamdb/KalamDB/refs/heads/main/backend/server.example.toml)
Quick download command:
```bash
curl -L https://raw.githubusercontent.com/kalamdb/KalamDB/refs/heads/main/backend/server.example.toml -o server.sample.toml
```
================================================================================
# Logging & Traceability
URL: https://kalamdb.org/docs/server/configurations/logging
Source: content/server/configurations/logging.mdx
> Configure KalamDB logging, JSON logs, OTLP tracing, Docker log output, and Dokploy-compatible runtime logging from server.toml or environment variables.
# Logging & Traceability
This page is the canonical guide for KalamDB runtime logging, JSON log output, Docker log collection, and distributed tracing.
Use it when you want to:
- switch the backend to JSON logs
- configure logging from `server.toml`
- run with logging overrides from environment variables
- ship logs through Docker or Dokploy
- enable OTLP tracing for Jaeger or another collector
## What KalamDB Supports Today
KalamDB currently supports two practical runtime log output modes:
- `compact`: human-readable text logs written to `server.log`
- `json`: JSON Lines logs written to `server.jsonl`
When `log_to_console = true`, the same events are also emitted to container stdout or your local terminal.
For traceability beyond logs, KalamDB also supports OTLP trace export under `[logging.otlp]`.
## JSON Logging Syntax
KalamDB JSON logs are emitted as JSON Lines.
- Each log event is one JSON object
- Each object is written on its own line
- The log file is `server.jsonl`
- This format is the one read by `system.server_logs`
Example real log line:
```json
{"timestamp":"2026-05-13T04:39:33.428337Z","level":"INFO","message":"KalamDB Server v0.5.0-beta.1 | Build: 2026-05-13 03:42:16 UTC","log.target":"kalamdb_server","log.module_path":"kalamdb_server","log.file":"backend/src/main.rs","log.line":340,"target":"kalamdb_server","threadName":"main"}
```
Common fields you should expect:
| Field | Meaning |
|---|---|
| `timestamp` | UTC event timestamp in RFC 3339 format |
| `level` | Log severity such as `INFO`, `WARN`, `ERROR`, `DEBUG`, `TRACE` |
| `message` | Event message text |
| `target` | Rust tracing target for the event |
| `threadName` | Thread name that emitted the event |
| `log.file` | Source file when available |
| `log.line` | Source line when available |
| `log.module_path` | Rust module path when available |
Depending on the event and span context, extra tracing metadata can also appear.
## Important Limitation: No Custom JSON Schema Yet
KalamDB does **not** currently support custom JSON log templates.
That means:
- you can choose `compact` or `json`
- you can choose the log level and destination
- you can export OTLP traces
- you **cannot** rename JSON keys or define a custom JSON logging pattern yet
If you need a different schema for downstream systems, transform the JSON lines in your log pipeline after KalamDB emits them.
## Configure Logging In `server.toml`
Use this when you want structured logs on disk and on stdout:
```toml
[logging]
level = "info"
format = "json"
logs_path = "./logs"
log_to_console = true
slow_query_threshold_ms = 1200
[logging.targets]
datafusion = "warn"
arrow = "warn"
parquet = "warn"
```
With that config:
- file logs go to `./logs/server.jsonl`
- console logs also use the same event stream
- `system.server_logs` can read the JSON log file
If you use `format = "compact"`, the server writes `server.log` instead.
## Configure Logging With Environment Variables
Environment variables override `server.toml` at startup.
### JSON Logging From Env
```bash
KALAMDB_LOG_LEVEL=info \
KALAMDB_LOG_FORMAT=json \
KALAMDB_LOGS_DIR=./logs \
KALAMDB_LOG_TO_CONSOLE=true \
cargo run --manifest-path backend/Cargo.toml --bin kalamdb-server
```
### Logging Environment Variables
| Environment variable | Maps to | Notes |
|---|---|---|
| `KALAMDB_LOG_LEVEL` | `logging.level` | `error`, `warn`, `info`, `debug`, `trace` |
| `KALAMDB_LOG_FORMAT` | `logging.format` | Use `json` for JSON Lines, `compact` for text |
| `KALAMDB_LOGS_DIR` | `logging.logs_path` | Directory for `server.log` or `server.jsonl` |
| `KALAMDB_LOG_TO_CONSOLE` | `logging.log_to_console` | `true`, `1`, `yes` enable console emission |
| `KALAMDB_SLOW_QUERY_THRESHOLD_MS` | `logging.slow_query_threshold_ms` | Slow query threshold in milliseconds |
| `KALAMDB_OTLP_ENABLED` | `logging.otlp.enabled` | Enables trace export |
| `KALAMDB_OTLP_ENDPOINT` | `logging.otlp.endpoint` | OTLP collector endpoint |
| `KALAMDB_OTLP_PROTOCOL` | `logging.otlp.protocol` | `grpc` or `http` |
| `KALAMDB_OTLP_SERVICE_NAME` | `logging.otlp.service_name` | Service name shown in tracing backends |
| `KALAMDB_OTLP_TIMEOUT_MS` | `logging.otlp.timeout_ms` | OTLP export timeout |
Current limitation:
- per-target overrides under `[logging.targets]` are currently configured in `server.toml`, not through dedicated environment variables
## Docker Logging
For Docker deployments, the simplest pattern is:
1. emit JSON logs to stdout
2. keep `log_to_console = true`
3. optionally persist the same logs under `/data/logs`
The single-node Docker config shipped with KalamDB already does this:
```toml
[logging]
level = "info"
logs_path = "/data/logs"
log_to_console = true
format = "json"
```
### `docker run` Example
```bash
docker run --rm \
-p 2900:2900 \
-v "$PWD/data:/data" \
-e KALAMDB_LOG_LEVEL=info \
-e KALAMDB_LOG_FORMAT=json \
-e KALAMDB_LOG_TO_CONSOLE=true \
-e KALAMDB_LOGS_DIR=/data/logs \
jamals86/kalamdb:latest
```
### Docker Compose Example
```yaml
services:
kalamdb:
image: jamals86/kalamdb:latest
ports:
- "2900:2900"
volumes:
- ./data:/data
environment:
KALAMDB_LOG_LEVEL: info
KALAMDB_LOG_FORMAT: json
KALAMDB_LOG_TO_CONSOLE: "true"
KALAMDB_LOGS_DIR: /data/logs
```
## Dokploy Compatibility
Dokploy runtime logs are compatible with KalamDB JSON logs **as raw log lines**.
What this means in practice:
- if KalamDB writes JSON logs to stdout, Dokploy can display them
- each KalamDB log event appears as one JSON line
- this works best when `KALAMDB_LOG_FORMAT=json` and `KALAMDB_LOG_TO_CONSOLE=true`
What you should **not** assume today:
- Dokploy does not appear to have a documented KalamDB-specific schema for these fields
- I did not find evidence that Dokploy automatically maps fields like `threadName`, `target`, or `log.line` into structured columns for KalamDB logs
The safe assumption is:
- Dokploy will show the JSON lines correctly
- any deeper structured parsing depends on Dokploy-side features or downstream tooling, not on a KalamDB-specific format contract
Dokploy’s log UI is built around Docker log streaming, so stdout compatibility matters more than matching a custom syntax.
## OTLP Tracing
For distributed tracing, configure `[logging.otlp]` in addition to normal logs.
```toml
[logging.otlp]
enabled = true
endpoint = "http://127.0.0.1:4317"
protocol = "grpc"
service_name = "kalamdb-server"
timeout_ms = 3000
```
Environment equivalent:
```bash
```
Use OTLP when you want:
- trace spans in Jaeger, Tempo, or an OpenTelemetry Collector
- request correlation across services
- timing and span visibility beyond plain log lines
Use JSON logging when you want:
- structured application logs
- Docker or Dokploy log shipping
- queryable `system.server_logs` entries
Most production setups use both.
## Query Logs From SQL
`system.server_logs` reads the JSON log file generated by the backend.
Example:
```sql
SELECT timestamp, level, target, message
FROM system.server_logs
ORDER BY timestamp DESC
LIMIT 50;
```
This view only works reliably when the backend log format is `json`.
## Recommended Production Setup
For most production deployments:
1. set `format = "json"`
2. keep `log_to_console = true`
3. write logs under a persistent directory such as `/data/logs`
4. enable OTLP if you need request tracing in Jaeger or Tempo
5. use Dokploy or Docker for raw log streaming, and a collector for deeper analytics if needed
## Related
- [OpenTelemetry (OTEL)](https://kalamdb.org/docs/server/configurations/otel)
- [Advanced Configuration](https://kalamdb.org/docs/server/configurations/advanced#logging)
- [Jaeger](https://kalamdb.org/docs/server/integrations/jaeger)
================================================================================
# OIDC & Issuer Trust
URL: https://kalamdb.org/docs/server/configurations/oidc
Source: content/server/configurations/oidc.mdx
> Configure KalamDB's single external OpenID Connect provider, issuer trust, device flow options, and the exact `server.toml` / env vars used by the Admin UI and CLI.
# OIDC & Issuer Trust
KalamDB supports one external OpenID Connect provider per server process, configured under `[auth.oidc]`.
Use this page when you want the Admin UI, the CLI, or direct bearer-token clients to authenticate through a standards-compliant OIDC issuer such as Dex, Keycloak, Okta, Auth0, Entra ID, Google, or Firebase.
There is no active `[oauth]` or `[oauth.providers.*]` matrix anymore. The current model is:
- `[auth.local]` for local username/password login
- `[auth.oidc]` for one external OIDC provider
- `auth.jwt_trusted_issuers` for the issuer allow-list used by bearer-token validation
## Token Routing Model
KalamDB inspects every bearer token **before** signature verification to decide how to validate it:
| Token `alg` | Token `iss` | Validation Path |
|---|---|---|
| HS256 | `kalamdb` | Internal: verified using `auth.jwt_secret` |
| RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384 | Any trusted issuer | External: verified via OIDC discovery + provider JWKS |
> **ES512 is not supported.**
This means external tokens from supported OIDC providers are validated natively through the bearer-token pipeline.
## `server.toml` Settings
```toml
[auth]
jwt_secret = "replace-with-strong-random-secret-32-plus-chars"
jwt_trusted_issuers = "kalamdb,https://issuer.example.com"
jwt_expiry_hours = 24
allow_remote_setup = false
cookie_secure = true
[auth.local]
enabled = true
[auth.oidc]
enabled = true
display_name = "Company SSO"
issuer = "https://issuer.example.com"
client_id = "kalamdb"
client_secret = "optional-confidential-client-secret"
scopes = ["openid", "email", "profile"]
auto_provision = true
default_role = "user"
broker_device_flow_enabled = true
# Optional override when discovery metadata omits device flow.
device_authorization_endpoint = "https://issuer.example.com/oauth2/device_authorization"
# Optional explicit audience override. Defaults to client_id when unset.
# audience = "kalamdb-api"
```
Notes:
- `[auth]` is the canonical section name. `[authentication]` still deserializes as a compatibility alias for local files, but new docs and examples use `[auth]`.
- `auth.oidc.issuer` and `auth.oidc.client_id` are required when `auth.oidc.enabled = true`.
- `auth.oidc.scopes` must include `openid`.
- `auth.oidc.audience` is optional and defaults to `client_id` when omitted.
- `jwt_trusted_issuers` is a comma-separated string, not an array.
- Keep `kalamdb` in `jwt_trusted_issuers` so local KalamDB-issued tokens continue to verify.
- Legacy `[oauth]` and `[oauth.providers.*]` sections are no longer supported.
## Environment Variable Overrides
```bash
```
Boolean env vars accept `"true"`, `"1"`, or `"yes"` (case-insensitive).
Environment variables override `server.toml` values.
## OIDC Configuration Matrix
| `server.toml` key | Environment variable | Required when OIDC enabled | Notes |
|---|---|---|---|
| `auth.jwt_secret` | `KALAMDB_JWT_SECRET` | Yes | Used for KalamDB-issued HS256 access and refresh tokens |
| `auth.jwt_trusted_issuers` | `KALAMDB_JWT_TRUSTED_ISSUERS` | Yes | Include `kalamdb` plus the configured OIDC issuer |
| `auth.local.enabled` | `KALAMDB_AUTH_LOCAL_ENABLED` | No | Disable local username/password auth after OIDC migration if desired |
| `auth.local.bcrypt_cost` | — | No | Password hash work factor for local users |
| `auth.local.min_password_length` | — | No | Minimum accepted local password length |
| `auth.local.max_password_length` | — | No | Maximum accepted local password length |
| `auth.local.enforce_password_complexity` | — | No | Require uppercase, lowercase, digit, and special characters |
| `auth.oidc.enabled` | `KALAMDB_AUTH_OIDC_ENABLED` | Yes | Enables the single external OIDC provider |
| `auth.oidc.display_name` | `KALAMDB_AUTH_OIDC_DISPLAY_NAME` | No | Human-facing label in UI/CLI, for example `Dex` or `Company SSO` |
| `auth.oidc.issuer` | `KALAMDB_AUTH_OIDC_ISSUER` | Yes | Must start with `http://` or `https://` |
| `auth.oidc.client_id` | `KALAMDB_AUTH_OIDC_CLIENT_ID` | Yes | Used for browser and device flows and default audience validation |
| `auth.oidc.client_secret` | `KALAMDB_AUTH_OIDC_CLIENT_SECRET` | No | Needed for confidential clients or some brokered device flows |
| `auth.oidc.scopes` | `KALAMDB_AUTH_OIDC_SCOPES` | No | CSV env value; must include `openid` |
| `auth.oidc.device_authorization_endpoint` | `KALAMDB_AUTH_OIDC_DEVICE_AUTHORIZATION_ENDPOINT` | No | Optional override when discovery omits device flow |
| `auth.oidc.broker_device_flow_enabled` | `KALAMDB_AUTH_OIDC_BROKER_DEVICE_FLOW_ENABLED` | No | Enables KalamDB-brokered device flow endpoints |
| `auth.oidc.auto_provision` | `KALAMDB_AUTH_OIDC_AUTO_PROVISION` | No | Allows absent regular OIDC subjects when default role is `user`; creates rows for elevated default roles |
| `auth.oidc.default_role` | `KALAMDB_AUTH_OIDC_DEFAULT_ROLE` | No | Default role for auto-provisioned users |
| `auth.oidc.audience` | `KALAMDB_AUTH_OIDC_AUDIENCE` | No | Explicit audience override; defaults to `client_id` |
## Browser And Device Endpoints
Clients discover the current auth surface from:
```text
GET /v1/api/auth/login-options
```
KalamDB exposes these OIDC-facing endpoints:
| Endpoint | Used by | Purpose |
|---|---|---|
| `POST /v1/api/auth/oidc/exchange-code` | Admin UI and CLI browser login | Exchange authorization code and PKCE verifier for KalamDB tokens |
| `POST /v1/api/auth/oidc/exchange-token` | CLI direct device login | Exchange provider ID token for KalamDB tokens |
| `POST /v1/api/auth/oidc/device/start` | CLI brokered device login | Start KalamDB-brokered device flow |
| `POST /v1/api/auth/oidc/device/poll` | CLI brokered device login | Poll for brokered device completion |
Redirect URIs to register at the provider:
- Admin UI: `https://YOUR_KALAMDB_ORIGIN/ui/oauth/callback`
- CLI browser login: `http://127.0.0.1:8787/callback`
## OIDC Discovery Flow
When KalamDB encounters an external token (non-HS256):
1. Extracts `alg` and `kid` from the JWT header (unverified).
2. Extracts `iss` from the JWT payload (unverified).
3. Confirms `iss` is in `jwt_trusted_issuers`.
4. Fetches `{issuer_url}/.well-known/openid-configuration`.
5. Extracts `jwks_uri` from the discovery response.
6. Fetches the JWKS endpoint and caches keys by `kid`.
7. Matches the token `kid` to a cached signing key.
8. Validates signature, issuer, expiry, and audience (if configured).
## JWKS Caching Behavior
- Keys are cached per-issuer in memory, indexed by `kid`.
- **Cache-miss refresh**: if a token's `kid` is not found, KalamDB automatically re-fetches the JWKS endpoint.
- **No TTL-based eviction**: cache refreshes only on miss — keys remain cached until an unknown `kid` triggers a refresh.
- Keys without a `kid` field are silently discarded from the cache.
- Key rotation at the provider is handled automatically (new `kid` → cache miss → refresh).
## JWT Claims Structure
KalamDB reads these claims from external tokens:
| Claim | Type | Required | Notes |
|---|---|---|---|
| `sub` | string | Yes | KalamDB `user_id`; must be a valid `UserId` |
| `iss` | string | Yes | Must match a trusted issuer |
| `exp` | number | Yes | Expiry (Unix timestamp, seconds) |
| `iat` | number | Yes | Issued-at (Unix timestamp, seconds) |
| `username` / `preferred_username` | string | No | Informational only; not used as the KalamDB `user_id` |
| `email` | string | No | User email |
| `role` | string | No | KalamDB role: `user`, `service`, `dba`, `system` |
| `token_type` | string | No | `"access"` or `"refresh"` |
## Deployment Checklist
1. Confirm the exact issuer URL. Trailing slashes matter.
2. Register the Admin UI and CLI redirect URIs at the provider.
3. Set `auth.oidc.issuer`, `auth.oidc.client_id`, and `auth.jwt_trusted_issuers`.
4. Keep `auth.local.enabled = true` during bootstrap and migration.
5. Test `GET /v1/api/auth/login-options`.
6. Test Admin UI browser login and at least one CLI OIDC mode.
7. Disable local auth later only after OIDC access is verified.
## Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| `401 Unauthorized` on external token | Issuer not in `jwt_trusted_issuers` | Add exact issuer URL |
| `auth.oidc.issuer is required` on startup | OIDC enabled without issuer | Set `auth.oidc.issuer` |
| `auth.oidc.client_id is required` on startup | OIDC enabled without client ID | Set `auth.oidc.client_id` |
| `auth.oidc.scopes must include the 'openid' scope` | Missing required scope | Add `openid` to `auth.oidc.scopes` |
| `MissingKid` error | Token missing `kid` header | Provider must include `kid` in JWT header |
| `KeyNotFound` after JWKS refresh | Key rotated and `kid` no longer present | Check provider JWKS, ensure key is published |
| `DiscoveryFailed` | Cannot reach `/.well-known/openid-configuration` | Check network, DNS, and issuer URL format |
| `JwtValidationFailed: ExpiredSignature` | Token expired | Get fresh token from provider |
| User not found after auth | `auth.oidc.auto_provision` is `false` or elevated roles are explicit | Enable auto-provision or pre-create the user with `CREATE USER '' WITH OIDC ...` |
## Related
- [Authentication & Bootstrap](https://kalamdb.org/docs/server/getting-started/authentication)
- [Single OIDC Provider Model](https://kalamdb.org/docs/server/auth/providers)
- [Dex](https://kalamdb.org/docs/server/integrations/dex)
- [OpenTelemetry (OTEL)](https://kalamdb.org/docs/server/configurations/otel)
================================================================================
# OpenTelemetry (OTEL)
URL: https://kalamdb.org/docs/server/configurations/otel
Source: content/server/configurations/otel.mdx
> Configure KalamDB distributed tracing with OpenTelemetry. Set up OTLP exporters, Jaeger integration, environment overrides, and server.toml settings.
# OpenTelemetry (OTEL)
This page shows how to enable distributed tracing for KalamDB with OTLP-compatible backends such as Jaeger.
## Quick Start (Jaeger)
Download a ready-to-run compose file:
- [Download docker-compose.yml (Jaeger)](https://kalamdb.org/downloads/otel/docker-compose.yml)
Run it from the folder where you downloaded it:
```bash
cd /path/to/downloaded/file
docker compose up -d
```
Jaeger UI: `http://127.0.0.1:16686`
This example mirrors the Jaeger section in `KalamDB/docker/utils/docker-compose.yml`.
## Configure `server.toml`
KalamDB OTEL settings live under `[logging.otlp]`:
```toml
[logging.otlp]
enabled = true
endpoint = "http://127.0.0.1:4317"
protocol = "grpc"
service_name = "kalamdb-server"
timeout_ms = 3000
```
## Environment Variable Overrides
Environment variables override `server.toml` values at startup.
```bash
```
`KALAMDB_OTLP_ENABLED` accepts `"true"`, `"1"`, or `"yes"` (case-insensitive).
## Full OTEL Configuration Matrix
| `server.toml` key | Environment variable | Default | Notes |
|---|---|---|---|
| `logging.otlp.enabled` | `KALAMDB_OTLP_ENABLED` | `false` | Enables OTLP trace exporting. |
| `logging.otlp.endpoint` | `KALAMDB_OTLP_ENDPOINT` | `http://127.0.0.1:4317` | Collector endpoint. |
| `logging.otlp.protocol` | `KALAMDB_OTLP_PROTOCOL` | `grpc` | Supported values: `grpc`, `http`. |
| `logging.otlp.service_name` | `KALAMDB_OTLP_SERVICE_NAME` | `kalamdb-server` | Service name shown in tracing backend. |
| `logging.otlp.timeout_ms` | `KALAMDB_OTLP_TIMEOUT_MS` | `3000` | Export timeout in milliseconds. |
Protocol behavior:
- `grpc` expects endpoint like `http://127.0.0.1:4317` (Jaeger port 4317)
- `http` expects collector HTTP endpoint like `http://127.0.0.1:4318`; the exporter appends `/v1/traces` automatically
## Validation Checklist
1. Start Jaeger/collector
2. Start KalamDB with OTEL enabled
3. Execute API requests or SQL calls
4. Open Jaeger UI and confirm service name appears
## Configuration Precedence
KalamDB applies settings in this order:
1. `server.toml`
2. `KALAMDB_*` environment overrides
If both are set, environment variables win.
## Supported Backends
Any OTLP-compatible collector works with KalamDB, including:
- [Jaeger](https://kalamdb.org/docs/server/integrations/jaeger) (built-in compose example)
- Grafana Tempo
- Honeycomb
- Datadog (OTLP ingest)
- OpenTelemetry Collector (forwarding to any backend)
## Related
- [Logging & Traceability](https://kalamdb.org/docs/server/configurations/logging)
- [Jaeger Integration](https://kalamdb.org/docs/server/integrations/jaeger)
- [Advanced Configuration](https://kalamdb.org/docs/server/configurations/advanced#loggingotlp)
================================================================================
# Admin UI Guide
URL: https://kalamdb.org/docs/server/getting-started/admin-ui
Source: content/server/getting-started/admin-ui.mdx
> Operate KalamDB from the built-in Admin UI. Learn how to access setup, login, use SQL Studio, monitor operations, and manage your database visually.
# KalamDB Admin UI Guide
The Admin UI is served by the KalamDB server at `/ui` and is the fastest way to run SQL, inspect metadata, and monitor operations.
## Access The UI
Open:
```text
http://localhost:2900/ui/sql
```
If server setup is not complete, finish bootstrap first from [Authentication & Bootstrap](https://kalamdb.org/docs/server/getting-started/authentication).
## Sign In
Use a real DBA/system account you created during setup. There is no universal hardcoded production credential.
After login, the UI stores auth state using secure cookie/token flow from `/v1/api/auth/*` endpoints.
## SQL Studio
SQL Studio is the primary workspace:
- namespace/table explorer
- multi-tab SQL editor
- results and execution details
- schema context and recent query history
Starter query:
```sql
SELECT * FROM system.namespaces LIMIT 100;
```
## Main Navigation Areas
- [Dashboard](https://kalamdb.org/docs/server/admin-ui/dashboard): health and high-level runtime indicators
- [SQL Studio](https://kalamdb.org/docs/server/admin-ui/sql-studio): query execution and schema exploration
- [Create a Table](https://kalamdb.org/docs/server/admin-ui/create-table): first schema workflow from SQL Studio
- [Streaming](https://kalamdb.org/docs/server/admin-ui/streaming): topic monitoring and stream operations
- [Users](https://kalamdb.org/docs/server/admin-ui/users): user/role administration
- [Jobs](https://kalamdb.org/docs/server/admin-ui/jobs): background job tracking (flush/retention/cleanup)
- [Live Queries](https://kalamdb.org/docs/server/admin-ui/live-queries): active subscription inspection
- [Logging](https://kalamdb.org/docs/server/admin-ui/logging): server/job/audit log views
- [Settings](https://kalamdb.org/docs/server/admin-ui/settings): runtime configuration values exposed by the backend
For page-by-page screenshots and operational workflows, continue to [Admin UI](https://kalamdb.org/docs/server/admin-ui).
## Practical Operator Workflow
1. Check node health and queue pressure in `Dashboard`.
2. Validate schema and data paths in `SQL Studio`.
3. Inspect `Jobs` for flush and maintenance behavior.
4. Review `Live Queries` and `Streaming` for realtime workload behavior.
5. Verify production guardrails in `Settings` before rollout.
================================================================================
# AI Agent Coding Guidelines
URL: https://kalamdb.org/docs/server/getting-started/ai-agent-guidelines
Source: content/server/getting-started/ai-agent-guidelines.mdx
> Best practices for AI-generated KalamDB code: use subscriptions for realtime updates, avoid interval polling, and follow consumer ACK patterns.
# AI Agent Coding Guidelines
When AI agents generate code for KalamDB, use these rules as defaults.
Install the official KalamDB skill so supported coding agents can load KalamDB commands, SQL syntax, SDK examples, and operations guidance:
```bash
npx skills add kalamdb/kalamdb-skills
```
## Prefer subscriptions for realtime state
For live UI or state sync, use WebSocket subscriptions:
- `client.live(sql, callback, options)` for materialized row state
- `client.liveTable(table, callback, options)` when table-name sugar is enough
- `client.liveEvents(sql, callback, options)` only when you need raw protocol events
Use subscriptions as the primary realtime pattern instead of interval polling.
Keep live SQL in the strict supported shape: `SELECT ... FROM ... WHERE ...`.
Do not put `ORDER BY` or `LIMIT` inside live subscription SQL; sort or cap rows after they arrive.
## Do not use interval polling for realtime updates
Avoid timer-based loops such as:
- `setInterval(() => fetch(...), 1000)`
- periodic `SELECT` queries to detect row changes
- fixed sleep loops to simulate streaming
These patterns add unnecessary load and increase staleness compared to server-push subscriptions.
## Use topics and consumers for background workers
For worker pipelines and asynchronous processing, use topic consumers with ACK semantics:
- `createConsumerClient({...}).consumer({...}).run(handler)`
- `runConsumer({...})`
This is preferred over polling tables on a schedule.
## Reliability defaults for AI-generated code
- Keep writes idempotent.
- Track and restore progress with sequence IDs / offsets when reconnecting.
- Handle subscription `error` events explicitly.
- Use bounded retries and `onFailed` persistence in agent/consumer flows.
## Recommended references
- [Realtime Subscriptions](https://kalamdb.org/docs/ts-sdk/subscriptions)
- [Topic Consumers & ACK](https://kalamdb.org/docs/ts-sdk/consumers)
- [Consumer Runtime](https://kalamdb.org/docs/ts-sdk/agents)
- [SQL Subscriptions](https://kalamdb.org/docs/server/sql-reference/subscriptions)
- [SQL Topics & Consumers](https://kalamdb.org/docs/server/topic-pubsub/sql-topics)
================================================================================
# Authentication & Bootstrap
URL: https://kalamdb.org/docs/server/getting-started/authentication
Source: content/server/getting-started/authentication.mdx
> Bootstrap KalamDB, keep or disable local password login, and configure the single OpenID Connect provider used by the Admin UI and CLI.
# Authentication & Bootstrap
KalamDB now has one authentication surface with two modes:
- local username/password login controlled by `[auth.local]`
- one external OpenID Connect provider controlled by `[auth.oidc]`
There is no active provider matrix anymore. Dex, Keycloak, Okta, Auth0, Entra ID, Google, Firebase, and similar services all fit the same single-provider OIDC slot as long as they expose standards-compliant discovery metadata.
Use this page for the practical rollout:
1. bootstrap the server locally
2. create the initial DBA and root credentials
3. configure `[auth.oidc]`
4. test Admin UI and CLI login
5. optionally disable local password login after migration
For the full config matrix, see [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc). For a local test IdP, use [Dex](https://kalamdb.org/docs/server/integrations/dex).
## 1. Bootstrap The Server
Check whether the server still needs first-time setup:
```bash
curl http://127.0.0.1:2900/v1/api/auth/status
```
When the response contains `"needs_setup": true`, create the initial DBA and root password:
```bash
curl -X POST http://127.0.0.1:2900/v1/api/auth/setup \
-H 'Content-Type: application/json' \
-d @- <<'JSON'
{
"user": "admin",
"password": "AdminPass123!",
"root_password": "RootPass123!",
"email": "admin@example.com"
}
JSON
```
Important behavior:
- `POST /v1/api/auth/setup` is localhost-only unless `auth.allow_remote_setup = true`
- setup creates the initial local admin credentials; it does not return tokens
- if you plan to disable local auth later, do the bootstrap first while `[auth.local].enabled = true`
## 2. Configure Local + OIDC Auth In `server.toml`
Start with a combined rollout so you keep a local admin escape hatch while validating OIDC:
```toml
[auth]
jwt_secret = "replace-with-a-strong-random-secret-at-least-32-chars"
jwt_trusted_issuers = "kalamdb,https://idp.example.com/realms/kalamdb"
jwt_expiry_hours = 24
allow_remote_setup = false
cookie_secure = true
[auth.local]
enabled = true
[auth.oidc]
enabled = true
display_name = "Company SSO"
issuer = "https://idp.example.com/realms/kalamdb"
client_id = "kalamdb"
client_secret = "optional-confidential-client-secret"
scopes = ["openid", "email", "profile"]
auto_provision = true
default_role = "user"
broker_device_flow_enabled = true
# Optional when discovery does not advertise device flow.
device_authorization_endpoint = "https://idp.example.com/realms/kalamdb/protocol/openid-connect/auth/device"
```
Rules that matter:
- KalamDB supports one external OIDC provider per server process
- `auth.oidc.issuer` and `auth.oidc.client_id` are required when `auth.oidc.enabled = true`
- `auth.oidc.scopes` must include `openid`
- `auth.jwt_trusted_issuers` should include both `kalamdb` and the external issuer
Equivalent environment overrides:
```bash
```
Boolean env vars accept `true`, `1`, or `yes` (case-insensitive).
## 3. Discover Available Login Methods
The Admin UI and CLI discover auth capabilities from the same endpoint:
```bash
curl http://127.0.0.1:2900/v1/api/auth/login-options
```
Typical response:
```json
{
"local": { "enabled": true },
"oidc": {
"enabled": true,
"display_name": "Company SSO",
"issuer": "https://idp.example.com/realms/kalamdb",
"client_id": "kalamdb",
"authorization_endpoint": "https://idp.example.com/.../auth",
"token_endpoint": "https://idp.example.com/.../token",
"device_authorization_endpoint": "https://idp.example.com/.../device",
"scopes": ["openid", "email", "profile"],
"device_flow": {
"direct_supported": true,
"broker_supported": true,
"broker_start_endpoint": "/v1/api/auth/oidc/device/start",
"broker_poll_endpoint": "/v1/api/auth/oidc/device/poll"
}
}
}
```
## 4. Admin UI Browser Login
For browser login, configure the IdP redirect URI to:
```text
https://YOUR_KALAMDB_ORIGIN/ui/oauth/callback
```
The Admin UI uses Authorization Code + PKCE:
1. browser fetches `/v1/api/auth/login-options`
2. browser redirects to the configured OIDC authorization endpoint
3. the callback page posts the code and PKCE verifier to `/v1/api/auth/oidc/exchange-code`
4. KalamDB verifies the provider response and returns KalamDB access and refresh tokens
## 5. CLI Login
### Local username/password login
```bash
kalam login --instance local --url http://127.0.0.1:2900 --user admin --password
```
### Browser OIDC login
```bash
kalam login --instance local --url http://127.0.0.1:2900 --oidc
```
For browser OIDC, register this redirect URI at the provider:
```text
http://127.0.0.1:8787/callback
```
### Device login without opening a browser callback
```bash
kalam login --instance local --url http://127.0.0.1:2900 --oidc --no-browser
```
This uses direct provider device flow when the provider exposes a device authorization endpoint.
### Brokered device login
```bash
kalam login --instance local --url http://127.0.0.1:2900 --oidc --no-browser --brokered
```
Use brokered mode when the CLI host cannot reach the provider directly but can reach KalamDB.
Operational notes:
- successful interactive `kalam login` continues directly into the SQL shell
- non-interactive `kalam login` saves credentials and exits
- `--no-save` skips writing credentials to disk
## 6. Use KalamDB Tokens For API Calls
After local login or OIDC exchange, use the returned KalamDB access token on API calls:
```bash
curl -X POST http://127.0.0.1:2900/v1/api/sql \
-H "Authorization: Bearer " \
-H 'Content-Type: application/json' \
-d '{"sql":"SELECT CURRENT_USER();"}'
```
Use `/v1/api/auth/me` to confirm the resolved identity:
```bash
curl http://127.0.0.1:2900/v1/api/auth/me \
-H "Authorization: Bearer "
```
## 7. Auto-Provisioning And Explicit OIDC Users
With `auth.oidc.auto_provision = true` and `auth.oidc.default_role = "user"`, regular OIDC users authenticate as their OIDC `sub` without a persisted per-user row when no local row exists.
When you need an elevated role before first login, or when `auto_provision = false`, pre-create the OIDC user explicitly with issuer and subject:
```sql
CREATE USER 'provider-subject'
WITH OIDC '{"issuer":"https://idp.example.com/realms/kalamdb","subject":"provider-subject"}'
ROLE service
EMAIL 'alice@example.com';
```
For persisted OIDC users, the `CREATE USER` id must match the OIDC `subject`.
## 8. Disable Local Password Login After Migration
Once OIDC is proven and you still have a safe DBA path, you can disable local password login:
```toml
[auth.local]
enabled = false
```
This disables local username/password login server-side. The Admin UI and CLI also learn that state through `/v1/api/auth/login-options` and should hide local login controls.
## Related
- [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc)
- [Kalam CLI](https://kalamdb.org/docs/cli)
- [Dex](https://kalamdb.org/docs/server/integrations/dex)
- [Single OIDC Provider Model](https://kalamdb.org/docs/server/auth/providers)
The bridge issuer (e.g. `kalamdb-keycloak-bridge`) must appear in `jwt_trusted_issuers`.
## 8. Production Hardening
Before exposing KalamDB publicly:
1. Set strong `auth.jwt_secret` (or `KALAMDB_JWT_SECRET`).
2. Keep `auth.allow_remote_setup = false`.
3. Set `auth.cookie_secure = true` behind HTTPS.
4. Configure `auth.jwt_trusted_issuers` when using external IdPs.
5. Tune `rate_limit.max_auth_requests_per_ip_per_sec`.
For full endpoint auth behavior, see [HTTP API Reference](https://kalamdb.org/docs/server/api/http-reference).
================================================================================
# GitHub Binaries
URL: https://kalamdb.org/docs/server/getting-started/binaries
Source: content/server/getting-started/binaries.mdx
> Download prebuilt kalamdb-server and kalam CLI binaries from GitHub Releases. Supports Linux, macOS Apple Silicon, and Windows platforms.
# GitHub Binaries
If you do not want Docker or a local Rust toolchain, use release artifacts from GitHub:
- [KalamDB Releases](https://github.com/kalamdb/KalamDB/releases)
Each release publishes `kalamdb-server` and `kalamcli` archives, a `SHA256SUMS` file, and `versions.json`. Always verify checksums before running downloaded binaries.
## What to download
From the selected release, download **both** the server and CLI archive for your platform, plus `SHA256SUMS`:
| Platform | Server archive | CLI archive |
|----------|----------------|-------------|
| Linux x86_64 | `kalamdb-server--linux-x86_64.tar.gz` | `kalamcli--linux-x86_64.tar.gz` |
| Linux ARM64 | `kalamdb-server--linux-aarch64.tar.gz` | `kalamcli--linux-aarch64.tar.gz` |
| macOS Apple Silicon | `kalamdb-server--macos-aarch64.tar.gz` | `kalamcli--macos-aarch64.tar.gz` |
| Windows x86_64 | `kalamdb-server--windows-x86_64.zip` | `kalamcli--windows-x86_64.zip` |
The file names below automatically use the latest GitHub release tag. macOS Intel (`x86_64`) builds are not published — use Docker, build from source, or run under Rosetta with a Linux binary.
## Important: binary names and config
Release archives extract to **versioned filenames** such as `kalamdb-server-0.5.3-rc.1-linux-x86_64`, not plain `kalamdb-server`. The server also requires a `server.toml` config path on startup — it will not run with zero arguments.
## Start the server
server.toml <<'EOF'
[server]
host = "127.0.0.1"
port = 2900
[storage]
data_path = "./data"
[limits]
[logging]
logs_path = "./logs"
log_to_console = true
[performance]
[auth]
jwt_secret = "local-binary-test-secret-32chars-min"
root_password = "kalamdb123"
EOF
./kalamdb-server-{{RELEASE_VERSION}}-linux-x86_64 server.toml`}
/>
Confirm it is up:
```bash
curl http://127.0.0.1:2900/v1/api/auth/status
```
Point the CLI at the same URL:
```bash
./kalamcli--linux-x86_64 --url http://127.0.0.1:2900 --user root --password
```
Optional: set `KALAMDB_SERVER_BIN` when using `kalam dev` in a project:
```bash
kalam dev
```
## Linux example (x86_64)
server.toml <<'EOF'
[server]
host = "127.0.0.1"
port = 2900
[storage]
data_path = "./data"
[limits]
[logging]
logs_path = "./logs"
log_to_console = true
[performance]
[auth]
jwt_secret = "local-binary-test-secret-32chars-min"
root_password = "kalamdb123"
EOF
./kalamdb-server-{{RELEASE_VERSION}}-linux-x86_64 server.toml`}
/>
In another terminal:
```bash
curl http://127.0.0.1:2900/v1/api/auth/status
./kalam --help
```
Install onto your PATH (optional):
```bash
install -m 755 kalamdb-server kalam ~/.local/bin/
```
## macOS example (Apple Silicon)
server.toml <<'EOF'
[server]
host = "127.0.0.1"
port = 2900
[storage]
data_path = "./data"
[limits]
[logging]
logs_path = "./logs"
log_to_console = true
[performance]
[auth]
jwt_secret = "local-binary-test-secret-32chars-min"
root_password = "kalamdb123"
EOF
./kalamdb-server-{{RELEASE_VERSION}}-macos-aarch64 server.toml
./kalam --help`}
/>
## Windows example (x86_64)
Verify and use the CLI:
```powershell
curl http://127.0.0.1:2900/v1/api/auth/status
.\kalamcli--windows-x86_64.exe --help
```
## Next steps
- [Quick Start](https://kalamdb.org/docs/server/getting-started)
- [Kalam CLI](https://kalamdb.org/docs/cli)
- [Configuration](https://kalamdb.org/docs/server/getting-started/configuration)
- [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc)
- [Firebase](https://kalamdb.org/docs/server/integrations/firebase)
- [Keycloak](https://kalamdb.org/docs/server/integrations/keycloak)
- [MinIO (S3-Compatible)](https://kalamdb.org/docs/server/integrations/minio)
- [Jaeger](https://kalamdb.org/docs/server/integrations/jaeger)
- [OpenTelemetry (OTEL)](https://kalamdb.org/docs/server/configurations/otel)
================================================================================
# Configuration
URL: https://kalamdb.org/docs/server/getting-started/configuration
Source: content/server/getting-started/configuration.mdx
> Minimal configuration for running KalamDB safely in development and production. Covers server.toml settings and environment variable overrides.
# Configuration
KalamDB loads runtime settings from `server.toml` and then applies `KALAMDB_*` environment overrides.
For complete section-by-section tuning, use [Advanced Configuration](https://kalamdb.org/docs/server/configurations/advanced).
Focused guides:
- [OpenTelemetry (OTEL)](https://kalamdb.org/docs/server/configurations/otel)
- [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc)
- [Dex](https://kalamdb.org/docs/server/integrations/dex)
- [MinIO (S3-Compatible)](https://kalamdb.org/docs/server/integrations/minio)
- [Jaeger](https://kalamdb.org/docs/server/integrations/jaeger)
## Download Full Sample Config
- [server.example.toml (full commented sample)](https://raw.githubusercontent.com/kalamdb/KalamDB/refs/heads/main/backend/server.example.toml)
```bash
curl -L https://raw.githubusercontent.com/kalamdb/KalamDB/refs/heads/main/backend/server.example.toml -o server.example.toml
```
## Run With Config File
```bash
kalamdb-server --config /path/to/server.toml
```
## Minimal Production-Oriented Example
```toml
[server]
host = "0.0.0.0"
port = 2900
api_version = "v1"
enable_http2 = true
[storage]
data_path = "./data"
[auth]
jwt_secret = "replace-with-strong-random-secret-32-plus-chars"
cookie_secure = true
allow_remote_setup = false
[auth.local]
enabled = true
[rate_limit]
enable_connection_protection = true
max_requests_per_ip_per_sec = 200
max_auth_requests_per_ip_per_sec = 20
[topics]
visibility_timeout_secs = 60
[security]
max_request_body_size = 10485760
max_ws_message_size = 1048576
[security.cors]
allowed_origins = ["https://app.example.com"]
allow_credentials = true
```
Notes:
- `data_path` is the canonical storage root key (not `data_dir`).
- `auth` can also be written as `[authentication]` (alias supported by config loader).
- table flush behavior is set per table via SQL `WITH (FLUSH_POLICY = '...')`, not a top-level `storage.flush_policy` key.
- `topics.visibility_timeout_secs` controls when unacknowledged topic consumer claims become available for redelivery.
## Add OIDC To `server.toml`
KalamDB supports one external OIDC provider at a time.
```toml
[auth]
jwt_trusted_issuers = "kalamdb,https://idp.example.com/realms/kalamdb"
[auth.local]
enabled = true
[auth.oidc]
enabled = true
display_name = "Company SSO"
issuer = "https://idp.example.com/realms/kalamdb"
client_id = "kalamdb"
scopes = ["openid", "email", "profile"]
auto_provision = true
default_role = "user"
broker_device_flow_enabled = true
```
Keep local auth enabled during bootstrap and rollout, then disable it later with `[auth.local].enabled = false` if you want an OIDC-only deployment.
## High-Value Environment Overrides
These are commonly used in Docker and CI:
- `KALAMDB_SERVER_HOST`, `KALAMDB_SERVER_PORT`, `KALAMDB_SERVER_PUBLIC_ORIGIN`, `KALAMDB_SERVER_WORKERS`
- `KALAMDB_DATA_DIR`
- `KALAMDB_LOG_LEVEL`, `KALAMDB_LOG_FORMAT`, `KALAMDB_LOGS_DIR`, `KALAMDB_LOG_TO_CONSOLE`
- `KALAMDB_JWT_SECRET`, `KALAMDB_JWT_TRUSTED_ISSUERS`, `KALAMDB_JWT_EXPIRY_HOURS`, `KALAMDB_COOKIE_SECURE`, `KALAMDB_ALLOW_REMOTE_SETUP`
- `KALAMDB_AUTH_LOCAL_ENABLED`
- `KALAMDB_AUTH_OIDC_ENABLED`, `KALAMDB_AUTH_OIDC_DISPLAY_NAME`, `KALAMDB_AUTH_OIDC_ISSUER`, `KALAMDB_AUTH_OIDC_CLIENT_ID`, `KALAMDB_AUTH_OIDC_CLIENT_SECRET`
- `KALAMDB_AUTH_OIDC_SCOPES`, `KALAMDB_AUTH_OIDC_DEVICE_AUTHORIZATION_ENDPOINT`, `KALAMDB_AUTH_OIDC_BROKER_DEVICE_FLOW_ENABLED`
- `KALAMDB_AUTH_OIDC_AUTO_PROVISION`, `KALAMDB_AUTH_OIDC_DEFAULT_ROLE`, `KALAMDB_AUTH_OIDC_AUDIENCE`
- `KALAMDB_SECURITY_CORS_ALLOWED_ORIGINS`, `KALAMDB_SECURITY_TRUSTED_PROXY_RANGES`
- `KALAMDB_RATE_LIMIT_AUTH_REQUESTS_PER_IP_PER_SEC`
- `KALAMDB_TOPIC_VISIBILITY_TIMEOUT_SECS`, `KALAMDB_TOPIC_DEFAULT_RETENTION_SECONDS`, `KALAMDB_TOPIC_DEFAULT_RETENTION_MAX_BYTES`
- `KALAMDB_CLUSTER_ID`, `KALAMDB_NODE_ID`, `KALAMDB_CLUSTER_RPC_ADDR`, `KALAMDB_CLUSTER_API_ADDR`, `KALAMDB_CLUSTER_PEERS`
- `KALAMDB_RPC_TLS_ENABLED`, `KALAMDB_RPC_TLS_CA_CERT`, `KALAMDB_RPC_TLS_SERVER_CERT`, `KALAMDB_RPC_TLS_SERVER_KEY`
- `KALAMDB_ROOT_PASSWORD` for root bootstrap and `KALAMDB_TOKIO_WORKER_THREADS` for Tokio runtime sizing
Compatibility aliases still accepted today:
- `KALAMDB_CLUSTER_NODE_ID` for `KALAMDB_NODE_ID`
- `KALAMDB_VISIBILITY_TIMEOUT_SECS` for `KALAMDB_TOPIC_VISIBILITY_TIMEOUT_SECS`
- `KALAMDB_TRUSTED_PROXY_RANGES` for `KALAMDB_SECURITY_TRUSTED_PROXY_RANGES`
For OTEL-specific override behavior, see [OpenTelemetry (OTEL)](https://kalamdb.org/docs/server/configurations/otel).
For IdP issuer setup, see [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc).
For the full, complete override table, use [Advanced Configuration](https://kalamdb.org/docs/server/configurations/advanced).
## Cluster Override Format
`KALAMDB_CLUSTER_PEERS` uses this format:
```text
node_id@rpc_addr@api_addr[@rpc_server_name];node_id@rpc_addr@api_addr[@rpc_server_name]
```
Example:
```text
2@kalamdb-node2:2910@http://kalamdb-node2:2900;3@kalamdb-node3:2910@http://kalamdb-node3:2900
```
================================================================================
# Docker Deployment
URL: https://kalamdb.org/docs/server/getting-started/docker
Source: content/server/getting-started/docker.mdx
> Run KalamDB with Docker or Docker Compose in single-node or 3-node cluster mode. Includes copy-paste commands, health checks, and volume configuration.
# Docker Deployment
KalamDB publishes [`jamals86/kalamdb:latest`](https://hub.docker.com/r/jamals86/kalamdb) on Docker Hub. The image includes `kalamdb-server`, `kalam-cli`, and a `kalam` symlink. The server listens on port **2900** inside the container.
> **Note:** Some older Docker Hub overview text references port `8080` or the `kalamstack` GitHub org. The commands below match the current image. Canonical Hub readme source: [`docker/REPO-README.md`](https://github.com/kalamdb/KalamDB/blob/main/docker/REPO-README.md) in the KalamDB repo.
## Quick start — copy-paste
Run a single-node server on host port **2900**, bootstrap auth, and log in:
```bash
docker run -d --name kalamdb -p 2900:2900 \
-e KALAMDB_SERVER_HOST=0.0.0.0 \
-e KALAMDB_JWT_SECRET="$(openssl rand -base64 32)" \
-e KALAMDB_ALLOW_REMOTE_SETUP=true \
-v kalamdb_data:/data \
jamals86/kalamdb:latest
sleep 5
curl -sS http://127.0.0.1:2900/v1/api/auth/status
curl -sS -X POST http://127.0.0.1:2900/v1/api/auth/setup \
-H 'Content-Type: application/json' \
-d '{"user":"admin","password":"AdminPass123!","root_password":"RootPass123!"}'
curl -sS -X POST http://127.0.0.1:2900/v1/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"user":"admin","password":"AdminPass123!"}'
```
Save the `access_token` from the login response.
Verify from the host with `auth/status` (recommended). The `/v1/api/healthcheck` endpoint is localhost-only when reached through Docker port publishing — use this from inside the container instead:
```bash
docker exec kalamdb wget -qO- http://127.0.0.1:2900/v1/api/healthcheck
```
Admin UI: `http://127.0.0.1:2900/ui` — log in as `admin` / `AdminPass123!`.
Useful environment variables for local Docker runs:
| Variable | Purpose |
|----------|---------|
| `KALAMDB_SERVER_HOST` | Bind address inside the container — use `0.0.0.0` when publishing ports |
| `KALAMDB_JWT_SECRET` | JWT signing secret — use at least 32 characters in shared environments |
| `KALAMDB_ALLOW_REMOTE_SETUP` | Allow first-time auth setup from the host when running in Docker |
| `KALAMDB_SECURITY_CORS_ALLOWED_ORIGINS` | CORS origins for the Admin UI (compose files default to `*` for local demos) |
Avoid `KALAMDB_ROOT_PASSWORD` for first-time Docker setup on the host: it can mark the server as configured while host login still fails. Use `POST /v1/api/auth/setup` instead (as in the quick start above).
## Single node with Docker Compose
Download the compose file, create a compatible `server.toml` (the checked-in `docker/run/single/server.toml` in the KalamDB repo is missing required sections for current server builds), then start:
```bash
mkdir kalamdb-docker && cd kalamdb-docker
curl -fsSL https://raw.githubusercontent.com/kalamdb/KalamDB/main/docker/run/single/docker-compose.yml -o docker-compose.yml
cat > server.toml <<'EOF'
[server]
host = "0.0.0.0"
port = 2900
[storage]
data_path = "/data"
[limits]
[logging]
level = "info"
logs_path = "/data/logs"
log_to_console = true
format = "json"
[performance]
[auth]
jwt_secret = "docker-compose-placeholder-secret-32c"
[security.cors]
allowed_origins = ["*"]
[rate_limit]
max_queries_per_sec = 1000000
max_messages_per_sec = 10000000
max_subscriptions_per_user = 100000
max_auth_requests_per_ip_per_sec = 20000000
max_connections_per_ip = 100000
max_requests_per_ip_per_sec = 20000000
request_body_limit_bytes = 104857600
ban_duration_seconds = 300
enable_connection_protection = true
EOF
KALAMDB_JWT_SECRET="$(openssl rand -base64 32)" KALAMDB_PORT=2900 docker compose up -d
```
By default the compose file maps `${KALAMDB_PORT:-8088}:2900`. Setting `KALAMDB_PORT=2900` publishes the API at `http://127.0.0.1:2900`.
Bootstrap and verify:
```bash
curl -sS http://127.0.0.1:2900/v1/api/auth/status
curl -sS -X POST http://127.0.0.1:2900/v1/api/auth/setup \
-H 'Content-Type: application/json' \
-d '{"user":"admin","password":"AdminPass123!","root_password":"RootPass123!"}'
```
Friendly alias for the compose file only: [kalamdb.org/docker-compose.yml](https://kalamdb.org/docker-compose.yml) redirects to the GitHub source. You still need a compatible `server.toml` beside it.
## 3-node cluster
```bash
mkdir kalamdb-cluster && cd kalamdb-cluster
curl -fsSL https://raw.githubusercontent.com/kalamdb/KalamDB/main/docker/run/cluster/docker-compose.yml -o docker-compose.yml
KALAMDB_JWT_SECRET="$(openssl rand -base64 32)" docker compose up -d
```
Default host ports:
| Node | HTTP API | Raft gRPC |
|------|----------|-----------|
| node 1 | `8081` | `9091` |
| node 2 | `8082` | `9092` |
| node 3 | `8083` | `9093` |
Verify node 1 from the host:
```bash
curl http://127.0.0.1:8081/v1/api/auth/status
```
## Windows PowerShell
`docker run` with bootstrap:
```powershell
docker run -d --name kalamdb -p 2900:2900 `
-e KALAMDB_SERVER_HOST=0.0.0.0 `
-e KALAMDB_JWT_SECRET="$([Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Maximum 256 })))" `
-e KALAMDB_ALLOW_REMOTE_SETUP=true `
-v kalamdb_data:/data `
jamals86/kalamdb:latest
Start-Sleep -Seconds 5
Invoke-RestMethod http://127.0.0.1:2900/v1/api/auth/status
Invoke-RestMethod -Method Post http://127.0.0.1:2900/v1/api/auth/setup `
-ContentType "application/json" `
-Body '{"user":"admin","password":"AdminPass123!","root_password":"RootPass123!"}'
```
Compose (after creating `docker-compose.yml` and the `server.toml` from the single-node section above):
```powershell
$env:KALAMDB_JWT_SECRET = [Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Maximum 256 }))
$env:KALAMDB_PORT = "2900"
Invoke-WebRequest https://raw.githubusercontent.com/kalamdb/KalamDB/main/docker/run/single/docker-compose.yml -UseBasicParsing -OutFile docker-compose.yml
docker compose up -d
```
## CLI inside the container
The image ships with `kalam` and `kalam-cli`:
```bash
docker exec kalamdb kalam --version
docker exec -it kalamdb kalam --url http://127.0.0.1:2900 --user admin --password
```
CLI state defaults to `/data/.kalam` inside the container so credentials persist with the `/data` volume.
## Persistence and cleanup
Data is stored under `/data` in the container. The quick-start `docker run` command uses the named volume `kalamdb_data`.
```bash
docker logs -f kalamdb # view logs
docker stop kalamdb # stop without deleting data
docker rm kalamdb # remove container; volume keeps data
docker rm -f kalamdb && docker volume rm kalamdb_data # full reset
```
## Notes
- Keep `KALAMDB_JWT_SECRET` non-default in shared environments.
- For first login/setup in Docker, review [Authentication & Bootstrap](https://kalamdb.org/docs/server/getting-started/authentication).
- For OIDC issuer trust, review [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc).
- Official image: [jamals86/kalamdb on Docker Hub](https://hub.docker.com/r/jamals86/kalamdb)
================================================================================
# Quick Start Guide
URL: https://kalamdb.org/docs/server/getting-started
Source: content/server/getting-started/index.mdx
> Run KalamDB, bootstrap authentication, create your first table, and continue into the TypeScript SDK, HTTP API, or PostgreSQL extension.
# Quick Start
This guide takes you from zero to a working KalamDB flow:
1. Run the server
2. Bootstrap authentication
3. Create a namespace and table
4. Execute SQL
5. Connect from the TypeScript SDK or CLI
Use this path if you want the fastest route into a working local KalamDB setup before moving into the TypeScript SDK, HTTP API, PostgreSQL extension, or production guides.
## Prerequisites
Pick the path that matches how you want to run the server:
| Option | You need |
|--------|----------|
| **1. Docker** | Docker Engine or Docker Desktop |
| **2. Kalam CLI** | [Kalam CLI](https://kalamdb.org/docs/cli/setup) installed (`install.sh` or npm) and, for TypeScript starters, Node.js plus a package manager (npm, pnpm, yarn, or bun) |
| **3. GitHub binaries** | `curl` or a browser — no Rust toolchain required |
| **4. Build from source** | Git and Rust `1.92+` |
## Option 1: Docker
The fastest way to run KalamDB locally is the published image [`jamals86/kalamdb:latest`](https://hub.docker.com/r/jamals86/kalamdb). Copy-paste the commands below — they start the server, bootstrap auth, and log you in as the initial DBA user.
```bash
docker run -d --name kalamdb -p 2900:2900 \
-e KALAMDB_SERVER_HOST=0.0.0.0 \
-e KALAMDB_JWT_SECRET="$(openssl rand -base64 32)" \
-e KALAMDB_ALLOW_REMOTE_SETUP=true \
-v kalamdb_data:/data \
jamals86/kalamdb:latest
sleep 5
curl -sS http://127.0.0.1:2900/v1/api/auth/status
curl -sS -X POST http://127.0.0.1:2900/v1/api/auth/setup \
-H 'Content-Type: application/json' \
-d '{"user":"admin","password":"AdminPass123!","root_password":"RootPass123!"}'
curl -sS -X POST http://127.0.0.1:2900/v1/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"user":"admin","password":"AdminPass123!"}'
```
What this does:
- Starts `kalamdb-server` with data persisted in the named volume `kalamdb_data`
- Generates a JWT signing secret (required when binding to `0.0.0.0`)
- Allows first-time setup from your host machine (`KALAMDB_ALLOW_REMOTE_SETUP=true`)
- Creates DBA user `admin` and returns a JWT when login succeeds
Save the `access_token` from the login response for the SQL steps below.
> **Docker note:** `/v1/api/healthcheck` is localhost-only inside the container. From your host, use `/v1/api/auth/status` (as above) or run `docker exec kalamdb wget -qO- http://127.0.0.1:2900/v1/api/healthcheck`.
Admin UI: `http://127.0.0.1:2900/ui` — log in as `admin` with `AdminPass123!`.
For Docker Compose (single node or 3-node cluster), volume overrides, and Windows PowerShell examples, see [Docker Deployment](https://kalamdb.org/docs/server/getting-started/docker).
## Option 2: Kalam CLI (`kalam init` + `kalam dev`)
Use this path when you want a project scaffold (`kalam.toml`, `schema.sql`, migrations) and a single command that starts the local server, applies schema, and supervises your app.
### Step 1 — Install the CLI
```bash
curl -fsSL https://kalamdb.org/install.sh | sh
```
Or with npm:
```bash
npm install -g @kalamdb/cli
```
Verify:
```bash
kalam --version
kalam doctor
```
### Step 2 — Scaffold a project with `kalam init`
Create a directory and run the interactive wizard:
```bash
mkdir my-app && cd my-app
kalam init
```
The wizard asks for:
1. **Project name** — written to `[project].name` in `kalam.toml`
2. **Schema mode** — choose **SQL file** (`schema.sql`)
3. **Language targets** — TypeScript and/or Dart for generated SDK types
4. **Template** — built-in TypeScript starter (for example `simple-live`)
5. **Package manager** — npm, pnpm, yarn, or bun (TypeScript only)
6. **Server mode** — choose **local** so `kalam dev` can start `kalamdb-server` for you
7. **Server URL** — defaults to `http://localhost:2900` for local mode
Non-interactive equivalent (CI or scripted setup):
```bash
kalam init --yes \
--name my-app \
--languages typescript \
--template simple-live \
--package-manager pnpm \
--server-mode local
```
`kalam init` creates at minimum:
| File / directory | Purpose |
|------------------|---------|
| `kalam.toml` | Project config: environments, schema paths, dev orchestration |
| `schema.sql` | Your schema source |
| `kalam/migrations/` | Migration history |
| `kalam/server/server.toml` | Local server config (includes `auth.root_password`) |
| `src/generated/kalam.ts` | Generated TypeScript types (after first dev run) |
### Step 3 — Start everything with `kalam dev`
From the project root:
```bash
kalam dev
```
On startup, `kalam dev`:
1. Resolves the dev environment from `kalam.toml` (`http://localhost:2900` by default)
2. **Starts or reuses** a local `kalamdb-server` — if no binary is installed, the CLI can download the matching release into `~/.kalam/bin` (interactive terminals only)
3. **Bootstraps auth** — logs in as local root using `kalam/server/server.toml` and saves credentials to `~/.kalam/`
4. **Applies schema** — runs the migration pipeline against `schema.sql`
5. **Regenerates types** — refreshes SDK artifacts when configured
6. **Supervises app processes** — starts commands from `[dev.processes]` (for example `pnpm dev` for TypeScript starters)
To wipe local database files and start over, stop the dev session and run `kalam db reset` (use `--yes` when reusing a non-project server or in non-interactive shells), then `kalam dev` again.
Press `Ctrl+C` to shut down managed processes cleanly.
Useful flags:
```bash
kalam dev --env dev --namespace app --project-dir .
kalam dev --force # skip draft confirmation prompts on startup
```
Check project state in another terminal:
```bash
kalam status
curl http://127.0.0.1:2900/v1/api/auth/status
```
While `kalam dev` is running, sign in to the local server with **`root` / `kalamdb123`**:
- **Admin UI:** [http://localhost:2900/ui](http://localhost:2900/ui)
- **CLI:** `kalam --url http://127.0.0.1:2900 --user root --password kalamdb123`
If port **2900** is already in use, `kalam dev` reuses that server instead of starting a new one. Use a free port when scaffolding:
```bash
kalam init --yes --name my-app --languages typescript --server-mode local --server-url http://localhost:2933
```
Full references: [Project Init](https://kalamdb.org/docs/cli/init) and [Local Development](https://kalamdb.org/docs/cli/dev).
## Option 3: GitHub release binaries
Download prebuilt `kalamdb-server` and `kalam` archives from [KalamDB Releases](https://github.com/kalamdb/KalamDB/releases). Each release publishes platform archives plus a `SHA256SUMS` file — verify checksums before extracting.
### Choose your platform
The latest release ships these server and CLI archives (version shown dynamically):
| Platform | Server archive | CLI archive |
|----------|----------------|-------------|
| Linux x86_64 | `kalamdb-server--linux-x86_64.tar.gz` | `kalamcli--linux-x86_64.tar.gz` |
| Linux ARM64 | `kalamdb-server--linux-aarch64.tar.gz` | `kalamcli--linux-aarch64.tar.gz` |
| macOS Apple Silicon | `kalamdb-server--macos-aarch64.tar.gz` | `kalamcli--macos-aarch64.tar.gz` |
| Windows x86_64 | `kalamdb-server--windows-x86_64.zip` | `kalamcli--windows-x86_64.zip` |
Replace `` with the bare release tag from GitHub (for example `0.5.3-rc.1`). macOS Intel builds are not published — use Docker, Rosetta with a Linux binary, or build from source on Intel Macs.
Release archives contain **versioned binary names** (not `kalamdb-server` / `kalam`). You also need a `server.toml` config file — the binary does not start without one.
### Linux (x86_64) example
server.toml <<'EOF'
[server]
host = "127.0.0.1"
port = 2900
[storage]
data_path = "./data"
[limits]
[logging]
logs_path = "./logs"
log_to_console = true
[performance]
[auth]
jwt_secret = "local-binary-test-secret-32chars-min"
root_password = "kalamdb123"
EOF
# 5. Start the server
./kalamdb-server-{{RELEASE_VERSION}}-linux-x86_64 server.toml`}
/>
In another terminal, confirm the server is up and run the CLI:
```bash
curl http://127.0.0.1:2900/v1/api/auth/status
./kalamcli--linux-x86_64 --help # or ./kalam --help if you created the symlink
```
### macOS (Apple Silicon) example
server.toml <<'EOF'
[server]
host = "127.0.0.1"
port = 2900
[storage]
data_path = "./data"
[limits]
[logging]
logs_path = "./logs"
log_to_console = true
[performance]
[auth]
jwt_secret = "local-binary-test-secret-32chars-min"
root_password = "kalamdb123"
EOF
./kalamdb-server-{{RELEASE_VERSION}}-macos-aarch64 server.toml`}
/>
### Windows (x86_64) example
Then verify from PowerShell or Command Prompt:
```powershell
curl http://127.0.0.1:2900/v1/api/auth/status
.\kalamcli--windows-x86_64.exe --help
```
More platform notes: [GitHub Binaries](https://kalamdb.org/docs/server/getting-started/binaries).
## Option 4: Build from source
Clone the repository and run the server with Cargo (requires Rust `1.92+`):
```bash
git clone https://github.com/kalamdb/KalamDB.git
cd KalamDB/backend
cargo run --release --bin kalamdb-server -- server.example.toml
```
The example config binds to `http://127.0.0.1:2900` by default. Copy and edit `server.example.toml` if you need different paths or secrets.
To build the CLI as well:
```bash
cd ../cli
cargo build --release
./target/release/kalam --help
```
## Verify Health
After starting the server with any option above:
```bash
curl http://127.0.0.1:2900/v1/api/auth/status
```
For a native or CLI-managed local server you can also use:
```bash
curl http://127.0.0.1:2900/v1/api/healthcheck
```
Docker users: the health endpoint is localhost-only from inside the container. Use `auth/status` from the host, or `docker exec kalamdb wget -qO- http://127.0.0.1:2900/v1/api/healthcheck`.
## Bootstrap Authentication
Check whether the server still needs first-time setup:
```bash
curl http://127.0.0.1:2900/v1/api/auth/status
```
If it returns `"needs_setup": true`, initialize root + DBA user:
```bash
curl -X POST http://127.0.0.1:2900/v1/api/auth/setup \
-H 'Content-Type: application/json' \
-d '{"user":"admin","password":"AdminPass123!","root_password":"RootPass123!"}'
```
If the server is already configured, this endpoint returns conflict; in that case, go directly to login.
Then log in:
```bash
curl -X POST http://127.0.0.1:2900/v1/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"user":"admin","password":"AdminPass123!"}'
```
Keep the returned `access_token` for API calls.
## Create Namespace And Table
```bash
TOKEN=""
curl -X POST http://127.0.0.1:2900/v1/api/sql \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"sql":"CREATE NAMESPACE IF NOT EXISTS app;"}'
curl -X POST http://127.0.0.1:2900/v1/api/sql \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d @- <<'JSON'
{"sql":"CREATE TABLE app.messages (id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(), sender TEXT NOT NULL, content TEXT NOT NULL, created_at TIMESTAMP DEFAULT NOW()) WITH (TYPE='USER', FLUSH_POLICY='rows:1000,interval:60');"}
JSON
```
## Insert And Query
```bash
curl -X POST http://127.0.0.1:2900/v1/api/sql \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d @- <<'JSON'
{"sql":"INSERT INTO app.messages (sender, content) VALUES ('alice', 'Hello KalamDB');"}
JSON
curl -X POST http://127.0.0.1:2900/v1/api/sql \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"sql":"SELECT * FROM app.messages ORDER BY created_at DESC LIMIT 10;"}'
```
## Connect With TypeScript SDK (`@kalamdb/client`)
Install the package first:
```bash
npm install @kalamdb/client
```
Package reference:
```ts
const client = createClient({
url: 'http://127.0.0.1:2900',
authProvider: async () => Auth.jwt(''),
});
const res = await client.query('SELECT * FROM app.messages LIMIT 5');
console.log(res.results[0]);
```
## Connect With Kalam CLI
Install the CLI with npm or the curl installer:
```bash
npm install -g @kalamdb/cli
# or
curl -fsSL https://kalamdb.org/install.sh | sh
```
Log in and run a query:
```bash
kalam login --instance local --url http://127.0.0.1:2900 --user admin --password
kalam -c "SELECT * FROM app.messages ORDER BY created_at DESC LIMIT 5;"
kalam doctor
```
If you want to test the OIDC flow instead of local password login:
```bash
kalam login --instance local --url http://127.0.0.1:2900 --oidc
kalam login --instance local --url http://127.0.0.1:2900 --oidc --no-browser
```
Full CLI reference: [Kalam CLI](https://kalamdb.org/docs/cli) — start with [Project Init](https://kalamdb.org/docs/cli/init) and [Local Development](https://kalamdb.org/docs/cli/dev)
## Use KalamDB Skills With Coding Agents
Install the official KalamDB skill for Codex, Claude Code, OpenCode, and Agent Skills-compatible tools:
```bash
npx skills add kalamdb/kalamdb-skills
```
## Choose Your Next Track
### Building an app or AI-agent backend in TypeScript
Continue with [TypeScript Setup](https://kalamdb.org/docs/ts-sdk/setup), [Authentication](https://kalamdb.org/docs/ts-sdk/auth), [Querying & DML](https://kalamdb.org/docs/ts-sdk/querying), and [Realtime Subscriptions](https://kalamdb.org/docs/ts-sdk/subscriptions).
### Building background workers and agent automation
Continue with [Topic Consumers & ACK](https://kalamdb.org/docs/ts-sdk/consumers), [Consumer Runtime](https://kalamdb.org/docs/ts-sdk/agents), and [AI Agent Coding Guidelines](https://kalamdb.org/docs/server/getting-started/ai-agent-guidelines).
### Integrating through PostgreSQL
Continue with [PostgreSQL Extension Getting Started](https://kalamdb.org/docs/pg-kalam/getting-started), [SQL Syntax](https://kalamdb.org/docs/pg-kalam/sql-syntax), and [Data Type Conversions](https://kalamdb.org/docs/pg-kalam/type-conversions).
### Hardening for production
Continue with [Authentication & Bootstrap](https://kalamdb.org/docs/server/getting-started/authentication), [Configuration](https://kalamdb.org/docs/server/getting-started/configuration), [Security](https://kalamdb.org/docs/server/security), and [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc).
### Advanced features and integrations
Continue with [SQL Reference](https://kalamdb.org/docs/server/sql-reference), [Vector Search](https://kalamdb.org/docs/server/architecture/vector-search), [Dex](https://kalamdb.org/docs/server/integrations/dex), [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc), [MinIO (S3-Compatible)](https://kalamdb.org/docs/server/integrations/minio), [Jaeger](https://kalamdb.org/docs/server/integrations/jaeger), and [OpenTelemetry (OTEL)](https://kalamdb.org/docs/server/configurations/otel).
================================================================================
# Server Docs
URL: https://kalamdb.org/docs/server
Source: content/server/index.mdx
> Run KalamDB, configure authentication, use SQL, operate storage, and deploy production-ready realtime database services.
# KalamDB Server Docs
Use these server docs to run KalamDB, build a SQL-first backend for AI agents and realtime apps, configure authentication, operate storage, and deploy production services.
If you are comparing backend-as-a-service options, KalamDB gives you a self-hosted realtime data layer with SQL, authentication, tenant isolation, live subscriptions, topics, and vector search in one runtime.
## Start Here
1. [/docs/server/getting-started](https://kalamdb.org/docs/server/getting-started)
2. [/docs/server/sql-reference](https://kalamdb.org/docs/server/sql-reference)
3. [/docs/server/getting-started/authentication](https://kalamdb.org/docs/server/getting-started/authentication)
4. [/docs/server/configurations](https://kalamdb.org/docs/server/configurations)
5. [/docs/server/security](https://kalamdb.org/docs/server/security)
## Choose Your Path
### I am building AI agents or a chat application
Start with [/docs/server/getting-started](https://kalamdb.org/docs/server/getting-started), then continue to
[/docs/ts-sdk/setup](https://kalamdb.org/docs/ts-sdk/setup),
[/docs/ts-sdk/subscriptions](https://kalamdb.org/docs/ts-sdk/subscriptions), and
[/docs/ts-sdk/consumers](https://kalamdb.org/docs/ts-sdk/consumers).
### I want a backend-as-a-service alternative I can control
Start with [/docs/server/getting-started](https://kalamdb.org/docs/server/getting-started), then read
[/docs/server/getting-started/authentication](https://kalamdb.org/docs/server/getting-started/authentication),
[/docs/server/sql-reference](https://kalamdb.org/docs/server/sql-reference),
[/docs/server/security](https://kalamdb.org/docs/server/security), and
[/docs/server/configurations/oidc](https://kalamdb.org/docs/server/configurations/oidc).
### I need PostgreSQL compatibility
Start with [/docs/pg-kalam/getting-started](https://kalamdb.org/docs/pg-kalam/getting-started), then read
[/docs/pg-kalam/architecture](https://kalamdb.org/docs/pg-kalam/architecture),
[/docs/pg-kalam/sql-syntax](https://kalamdb.org/docs/pg-kalam/sql-syntax), and
[/docs/pg-kalam/type-conversions](https://kalamdb.org/docs/pg-kalam/type-conversions).
### I need advanced data and operations guidance
Start with [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types),
[/docs/server/architecture/vector-search](https://kalamdb.org/docs/server/architecture/vector-search),
[/docs/server/topic-pubsub](https://kalamdb.org/docs/server/topic-pubsub),
[/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers),
[/docs/server/architecture/clustering](https://kalamdb.org/docs/server/architecture/clustering), and
[/docs/server/configurations/otel](https://kalamdb.org/docs/server/configurations/otel).
## Questions New Users Ask
### What should I read first?
If you are new, start at [/docs/server/getting-started](https://kalamdb.org/docs/server/getting-started). It takes you
from running KalamDB locally to creating your first table and sending your first SQL query.
### Can frontend clients execute SQL directly against KalamDB?
Yes. Use [/docs/ts-sdk](https://kalamdb.org/docs/ts-sdk) for browser and app-facing clients. USER tables keep the same
SQL scoped to the signed-in user, and live subscriptions push updates over WebSockets.
### Where do I start for workers, automation, and advanced agent flows?
Go from [/docs/ts-sdk/setup](https://kalamdb.org/docs/ts-sdk/setup) to
[/docs/ts-sdk/consumers](https://kalamdb.org/docs/ts-sdk/consumers),
[/docs/ts-sdk/agents](https://kalamdb.org/docs/ts-sdk/agents), and
[/docs/server/getting-started/ai-agent-guidelines](https://kalamdb.org/docs/server/getting-started/ai-agent-guidelines).
### Where do I learn production deployment and observability?
Read [/docs/server/security](https://kalamdb.org/docs/server/security),
[/docs/server/configurations/logging](https://kalamdb.org/docs/server/configurations/logging),
[/docs/server/configurations/advanced](https://kalamdb.org/docs/server/configurations/advanced),
[/docs/server/configurations/oidc](https://kalamdb.org/docs/server/configurations/oidc),
[/docs/server/integrations/dex](https://kalamdb.org/docs/server/integrations/dex),
[/docs/server/integrations/jaeger](https://kalamdb.org/docs/server/integrations/jaeger), and
[/docs/server/configurations/otel](https://kalamdb.org/docs/server/configurations/otel).
## Core Capabilities
KalamDB combines:
- SQL query and DDL workflows
- user-isolated and shared table models
- live query subscriptions over WebSocket
- topic consume/ack worker patterns
- vector search with `EMBEDDING(n)` columns and cosine ranking
- hot + cold storage lifecycle in one runtime
| Capability | What it gives you |
|------------|-------------------|
| `USER` tables | per-user/tenant isolation |
| `SHARED` tables | global app data scope |
| `STREAM` tables | ephemeral realtime state with TTL |
| live subscriptions | push-based UI/state updates |
| topics + consumers | queue-like worker processing |
| vector search | semantic retrieval with cosine distance |
| storage commands | explicit flush/compact/manifest tooling |
## Release Information
For the latest published versions and artifacts, use:
- [KalamDB Releases](https://github.com/kalamdb/KalamDB/releases)
================================================================================
# Dex
URL: https://kalamdb.org/docs/server/integrations/dex
Source: content/server/integrations/dex.mdx
> Use Dex as a local or test OpenID Connect provider for KalamDB, including server.toml config, Admin UI login, and CLI browser/device flows.
# Dex
Dex is the simplest supported way to test KalamDB's current OIDC flow locally.
Use Dex when you want:
- a small standards-compliant OIDC issuer for local development
- repeatable browser and device-flow testing for the Admin UI and CLI
- a test IdP that matches KalamDB's single-provider OIDC model
## Local Dex Setup
The KalamDB repo already includes a Dex container and config:
```bash
cd KalamDB/docker/utils
docker compose up -d dex
```
Default local Dex values from the shared development config:
- issuer: `http://127.0.0.1:5556`
- public client ID: `client`
- Admin UI callback: `http://127.0.0.1:2900/ui/oauth/callback`
- CLI browser callback: `http://127.0.0.1:8787/callback`
- test user: `alice@example.org`
- test password: `kalamdb123`
## KalamDB `server.toml` Example
```toml
[auth]
jwt_secret = "replace-with-a-strong-random-secret-at-least-32-chars"
jwt_trusted_issuers = "kalamdb,http://127.0.0.1:5556"
allow_remote_setup = false
cookie_secure = false
[auth.local]
enabled = true
[auth.oidc]
enabled = true
display_name = "Dex"
issuer = "http://127.0.0.1:5556"
client_id = "client"
scopes = ["openid", "email", "profile"]
auto_provision = true
default_role = "user"
broker_device_flow_enabled = false
device_authorization_endpoint = "http://127.0.0.1:5556/device/code"
```
For the public Dex client used in local development, leave `client_secret` unset.
## Admin UI Login
Make sure the Dex client allows this redirect URI:
```text
http://127.0.0.1:2900/ui/oauth/callback
```
Then open the Admin UI, choose the OIDC login button, and sign in as `alice@example.org` with password `kalamdb123`.
## CLI Login
### Browser login
```bash
kalam login --instance local --url http://127.0.0.1:2900 --oidc
```
This uses the local browser callback at `http://127.0.0.1:8787/callback`.
### Direct device login
```bash
kalam login --instance local --url http://127.0.0.1:2900 --oidc --no-browser
```
Use this when you want to stay headless and let the CLI complete provider device flow directly.
### Brokered device login
```bash
kalam login --instance local --url http://127.0.0.1:2900 --oidc --no-browser --brokered
```
Enable brokered mode only when the CLI host cannot reach Dex directly but can reach KalamDB.
## Notes On Production Use
Dex can be used beyond local development, but production use is an operational choice, not a KalamDB requirement.
Dex is a good fit when:
- you control the deployment environment
- you manage TLS, connector configuration, storage, and backups yourself
- you want a lightweight self-hosted OIDC layer
Many teams still prefer an existing enterprise IdP for production. From KalamDB's perspective, that is fine because the runtime only cares about one configured OIDC issuer.
## Related
- [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc)
- [Authentication & Bootstrap](https://kalamdb.org/docs/server/getting-started/authentication)
- [Kalam CLI](https://kalamdb.org/docs/cli)
- [OIDC Model](https://kalamdb.org/docs/server/auth/providers)
================================================================================
# Firebase
URL: https://kalamdb.org/docs/server/integrations/firebase
Source: content/server/integrations/firebase.mdx
> Use Firebase as the single configured OIDC issuer for KalamDB. This page shows the current generic OIDC model, not the removed provider-specific config surface.
# Firebase
KalamDB no longer has Firebase-specific auth configuration. Firebase now fits the same single-provider OIDC model as Dex, Keycloak, Okta, Auth0, Entra ID, Google, and other standards-compliant issuers.
Use the generic OIDC docs and set the issuer to your Firebase project issuer.
## Example `server.toml`
```toml
[auth]
jwt_trusted_issuers = "kalamdb,https://securetoken.google.com/YOUR_PROJECT_ID"
[auth.oidc]
enabled = true
display_name = "Firebase"
issuer = "https://securetoken.google.com/YOUR_PROJECT_ID"
client_id = "YOUR_PROJECT_ID"
scopes = ["openid", "email", "profile"]
auto_provision = true
default_role = "user"
```
If you need explicit audience validation, keep `client_id` set to the Firebase project ID.
## What Changed
Do not use or document these removed shapes anymore:
- `[oauth]`
- `[oauth.providers.firebase]`
- provider-coded identities such as `oidc:fbs:`
Current external identities use the Firebase/OIDC `sub` claim directly as the KalamDB `user_id`.
If you need explicit persisted users or elevated roles, create them with `issuer` and `subject`:
```sql
CREATE USER 'FIREBASE_UID'
WITH OIDC '{"issuer":"https://securetoken.google.com/YOUR_PROJECT_ID","subject":"FIREBASE_UID"}'
ROLE dba
EMAIL 'user@example.com';
```
## Related
- [Authentication Overview](https://kalamdb.org/docs/server/auth)
- [Token Auth](https://kalamdb.org/docs/server/auth/token-auth)
- [Auto-Provisioning Users](https://kalamdb.org/docs/server/auth/auto-provisioning)
- [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc)
- [OIDC Model](https://kalamdb.org/docs/server/auth/providers)
================================================================================
# Integrations
URL: https://kalamdb.org/docs/server/integrations
Source: content/server/integrations/index.mdx
> Integration guides for external services used with KalamDB. Learn how to use Dex for local OIDC testing and how to connect MinIO and Jaeger.
# Integrations
This section covers external tools and identity/storage integrations.
- [Dex](https://kalamdb.org/docs/server/integrations/dex)
- [MinIO (S3-Compatible)](https://kalamdb.org/docs/server/integrations/minio)
- [Jaeger](https://kalamdb.org/docs/server/integrations/jaeger)
KalamDB's current auth surface supports one external OIDC provider at a time. Dex is the recommended local and CI-friendly test issuer in the current docs set.
*** Add File: /Users/jamal/git/KalamSite/content/server/integrations/dex.mdx
---
title: "Dex"
description: "Use Dex as a local or test OpenID Connect provider for KalamDB, including server.toml config, Admin UI login, and CLI browser/device flows."
---
# Dex
Dex is the simplest supported way to test KalamDB's current OIDC flow locally.
Use Dex when you want:
- a small standards-compliant OIDC issuer for local development
- repeatable browser and device-flow testing for the Admin UI and CLI
- a test IdP that matches KalamDB's single-provider OIDC model
## Local Dex Setup
The KalamDB repo already includes a Dex container and config:
```bash
cd KalamDB/docker/utils
docker compose up -d dex
```
Default local Dex values from the shared development config:
- issuer: `http://127.0.0.1:5556`
- public client ID: `client`
- Admin UI callback: `http://127.0.0.1:2900/ui/oauth/callback`
- CLI browser callback: `http://127.0.0.1:8787/callback`
- test user: `alice@example.org`
- test password: `kalamdb123`
## KalamDB `server.toml` Example
```toml
[auth]
jwt_secret = "replace-with-a-strong-random-secret-at-least-32-chars"
jwt_trusted_issuers = "kalamdb,http://127.0.0.1:5556"
allow_remote_setup = false
cookie_secure = false
[auth.local]
enabled = true
[auth.oidc]
enabled = true
display_name = "Dex"
issuer = "http://127.0.0.1:5556"
client_id = "client"
scopes = ["openid", "email", "profile"]
auto_provision = true
default_role = "user"
broker_device_flow_enabled = false
device_authorization_endpoint = "http://127.0.0.1:5556/device/code"
```
For the public Dex client used in local development, leave `client_secret` unset.
## Admin UI Login
Make sure the Dex client allows this redirect URI:
```text
http://127.0.0.1:2900/ui/oauth/callback
```
Then open the Admin UI, choose the OIDC login button, and sign in as `alice@example.org` with password `kalamdb123`.
## CLI Login
### Browser login
```bash
kalam login --instance local --url http://127.0.0.1:2900 --oidc
```
This uses the local browser callback at `http://127.0.0.1:8787/callback`.
### Direct device login
```bash
kalam login --instance local --url http://127.0.0.1:2900 --oidc --no-browser
```
Use this when you want to stay headless and let the CLI complete provider device flow directly.
### Brokered device login
```bash
kalam login --instance local --url http://127.0.0.1:2900 --oidc --no-browser --brokered
```
Enable brokered mode only when the CLI host cannot reach Dex directly but can reach KalamDB.
## Notes On Production Use
Dex can be used beyond local development, but production use is an operational choice, not a KalamDB requirement.
Dex is a good fit when:
- you control the deployment environment
- you manage TLS, connector configuration, storage, and backups yourself
- you want a lightweight self-hosted OIDC layer
Many teams still prefer an existing enterprise IdP for production. From KalamDB's perspective, that is fine because the runtime only cares about one configured OIDC issuer.
## Related
- [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc)
- [Authentication & Bootstrap](https://kalamdb.org/docs/server/getting-started/authentication)
- [Kalam CLI](https://kalamdb.org/docs/cli)
- [OIDC Model](https://kalamdb.org/docs/server/auth/providers)
================================================================================
# Jaeger
URL: https://kalamdb.org/docs/server/integrations/jaeger
Source: content/server/integrations/jaeger.mdx
> Set up Jaeger for distributed tracing with KalamDB. Configure OTEL exports via gRPC or HTTP, visualize traces, and debug production workloads.
# Jaeger
Use Jaeger as an OTLP-compatible backend for KalamDB distributed tracing.
## Download Compose Example
- [Download docker-compose.yml (Jaeger)](https://kalamdb.org/downloads/otel/docker-compose.yml)
## Docker Compose Details
The compose file runs:
```yaml
jaeger:
image: jaegertracing/jaeger:latest
environment:
COLLECTOR_OTLP_ENABLED: "true"
ports:
- "16686:16686" # Jaeger UI
- "4317:4317" # OTLP gRPC receiver
- "4318:4318" # OTLP HTTP receiver
```
Start it:
```bash
docker compose up -d
```
Jaeger UI: `http://127.0.0.1:16686`
## Connect KalamDB → Jaeger
### Via gRPC (default, port 4317)
```toml
[logging.otlp]
enabled = true
endpoint = "http://127.0.0.1:4317"
protocol = "grpc"
service_name = "kalamdb-server"
timeout_ms = 3000
```
### Via HTTP (port 4318)
```toml
[logging.otlp]
enabled = true
endpoint = "http://127.0.0.1:4318"
protocol = "http"
service_name = "kalamdb-server"
timeout_ms = 3000
```
The OTLP HTTP exporter appends `/v1/traces` automatically.
### Via Environment Variables
```bash
```
## What to Expect in Jaeger UI
After starting KalamDB with OTEL enabled:
1. Open `http://127.0.0.1:16686`.
2. Select the service name from the dropdown (default: `kalamdb-server`).
3. Click **Find Traces**.
4. You should see traces for API requests, SQL execution, and storage operations.
Each trace shows the full request lifecycle — from API entry through SQL parsing, query execution, and storage I/O.
## Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| No service in Jaeger dropdown | OTEL not enabled or no traffic yet | Set `enabled = true`, execute some SQL, then refresh |
| Service appears but no traces | Endpoint mismatch | Verify `protocol` matches the port: `grpc` → 4317, `http` → 4318 |
| Connection refused | Jaeger not running or wrong port | Run `docker compose ps` to verify Jaeger is up |
| Traces delayed | Batch export timeout | Default timeout is 3000ms; increase `timeout_ms` if network is slow |
## Related
For all OTEL configuration options and environment variable overrides, see [OpenTelemetry (OTEL)](https://kalamdb.org/docs/server/configurations/otel).
================================================================================
# Keycloak
URL: https://kalamdb.org/docs/server/integrations/keycloak
Source: content/server/integrations/keycloak.mdx
> Use Keycloak as the single configured OIDC issuer for KalamDB. This page shows the current generic OIDC model, not the removed provider-specific config surface.
# Keycloak
KalamDB no longer has Keycloak-specific auth configuration. Keycloak now fits the same single-provider OIDC model as Dex, Okta, Auth0, Entra ID, Google, and other standards-compliant issuers.
Use the generic OIDC docs and set the issuer to your Keycloak realm URL.
## Example `server.toml`
```toml
[auth]
jwt_trusted_issuers = "kalamdb,https://keycloak.example.com/realms/myrealm"
[auth.oidc]
enabled = true
display_name = "Keycloak"
issuer = "https://keycloak.example.com/realms/myrealm"
client_id = "kalamdb"
scopes = ["openid", "email", "profile"]
auto_provision = true
default_role = "user"
```
The issuer URL must exactly match the `iss` claim in Keycloak tokens.
## What Changed
Current external identities use the OIDC `sub` claim directly as the KalamDB `user_id`.
If you need explicit persisted users or elevated roles, create them with `issuer` and `subject`:
```sql
CREATE USER 'provider-subject'
WITH OIDC '{"issuer":"https://keycloak.example.com/realms/myrealm","subject":"provider-subject"}'
ROLE dba
EMAIL 'alice@example.com';
```
## Related
- [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc)
- [Authentication & Bootstrap](https://kalamdb.org/docs/server/getting-started/authentication)
- [OIDC Model](https://kalamdb.org/docs/server/auth/providers)
================================================================================
# MinIO (S3-Compatible)
URL: https://kalamdb.org/docs/server/integrations/minio
Source: content/server/integrations/minio.mdx
> Use MinIO with KalamDB S3 storage. Covers CONFIG fields, storage factory behavior, Docker Compose setup, timeout tuning, and bucket configuration.
# MinIO (S3-Compatible)
KalamDB uses the S3 storage backend for MinIO. You configure MinIO through `CREATE STORAGE ... TYPE 's3'` and the storage `CONFIG` JSON.
## Quick Start with Docker Compose
The KalamDB repository includes a MinIO service in `docker/utils/docker-compose.yml`:
```yaml
minio:
image: minio/minio:latest
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
MINIO_CI: "on"
ports:
- "9120:9000" # S3 API
- "9121:9001" # Console UI
volumes:
- ./minio-data:/data
```
Start it:
```bash
docker compose up minio -d
```
MinIO Console: `http://127.0.0.1:9121` (login: `minioadmin` / `minioadmin`)
S3 API Endpoint: `http://127.0.0.1:9120`
The compose entrypoint auto-creates a `kalamdb-test` bucket and sets public anonymous access for development.
## S3 CONFIG Fields
For S3-compatible backends (including MinIO), KalamDB supports these `CONFIG` JSON fields:
| Field | Type | Default | Notes |
|---|---|---|---|
| `type` | string | — | Must be `"s3"` |
| `region` | string | `"us-east-1"` | Falls back to `us-east-1` in factory if omitted |
| `endpoint` | string | `null` | Custom endpoint for MinIO / S3-compatible |
| `allow_http` | boolean | `false` | Must be `true` for non-TLS endpoints |
| `access_key_id` | string | `null` | Static credentials |
| `secret_access_key` | string | `null` | Static credentials |
| `session_token` | string | `null` | Temporary session credentials |
## Example: Register MinIO Storage
```sql
CREATE STORAGE s3_minio
TYPE 's3'
NAME 'MinIO Local Storage'
DESCRIPTION 'MinIO bucket for local development'
BASE_DIRECTORY 's3://kalamdb-test/'
CONFIG '{
"type": "s3",
"region": "us-east-1",
"endpoint": "http://127.0.0.1:9120",
"allow_http": true,
"access_key_id": "minioadmin",
"secret_access_key": "minioadmin"
}'
SHARED_TABLES_TEMPLATE '{namespace}/{tableName}'
USER_TABLES_TEMPLATE '{namespace}/{tableName}/{userId}';
```
## S3 Factory Behavior
When a custom `endpoint` is set (as with MinIO), the factory applies special handling:
- **Path-style requests**: `virtual_hosted_style_request` is set to `false` automatically. This is required for MinIO and most S3-compatible services.
- **Timeout skip**: `ClientOptions` (request/connect timeouts from `[storage.remote_timeouts]`) are **not applied** to S3-compatible endpoints. This avoids timeout conflicts with non-AWS backends. Timeouts only apply to standard AWS S3.
- **Region fallback**: If `region` is omitted, it defaults to `"us-east-1"`.
## Notes on CONFIG
- Use an `s3://...` `BASE_DIRECTORY` for S3-compatible storage.
- `allow_http = true` is required for any non-TLS endpoint (local MinIO, test environments).
- The `type` field in the CONFIG JSON must match the `TYPE` in the SQL command.
## Remote Timeout Tuning (`server.toml`)
Remote object store timeouts are configured globally in `server.toml`:
```toml
[storage.remote_timeouts]
request_timeout_secs = 60
connect_timeout_secs = 10
```
> **Important**: These timeouts apply to **standard AWS S3 only**. They are skipped for S3-compatible endpoints (MinIO, etc.) to avoid conflicts.
## Credential Placement
Recommended options:
- **Development**: Put credentials in `CONFIG` JSON directly.
- **Production (AWS S3)**: Use IAM instance roles or environment-based AWS credential chains — omit `access_key_id`/`secret_access_key` from CONFIG.
- **Production (MinIO)**: Use scoped credentials; rotate regularly.
- Avoid committing production secrets to source control.
## Verify Storage Health
After creating the storage, verify connectivity:
```sql
STORAGE CHECK s3_minio;
STORAGE CHECK s3_minio EXTENDED;
```
## Other Cloud Storage Backends
KalamDB also supports GCS and Azure Blob Storage. See
[/docs/server/sql-reference/storage](https://kalamdb.org/docs/server/sql-reference/storage) for all backend types and
CONFIG formats.
## Related Resources
- [/docs/server/sql-reference/storage](https://kalamdb.org/docs/server/sql-reference/storage)
- [/docs/server/sql-reference/storage-id-usage](https://kalamdb.org/docs/server/sql-reference/storage-id-usage)
- [/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers)
- [/docs/server/architecture/manifests](https://kalamdb.org/docs/server/architecture/manifests)
================================================================================
# v0.5.3-rc.1 Release
URL: https://kalamdb.org/docs/server/release-v0-5-3-rc-1
Source: content/server/release-v0-5-3-rc-1.mdx
> KalamDB v0.5.3-rc.1 pre-release notes for the June 17 CLI workflow update, benchmark improvement, and docs destinations.
# KalamDB v0.5.3-rc.1 Release
KalamDB v0.5.3-rc.1 is the June 17 pre-release for the local CLI project workflow: `kalam init`, `kalam dev`, schema apply, generated SDK artifacts, and supervised app logs.
## v0.5.3-rc.1 Performance Benchmark
The synced benchmark artifact for `0.5.3-rc.1` reports **38/38 benchmarks passed** with **213,667.71 ms measured benchmark time**.
That is **8.0% faster measured benchmark time** than `0.5.2-rc.1`, which reported 232,155.85 ms.
- [Open the v0.5.3-rc.1 HTML benchmark report](https://kalamdb.org/benchmark/results/bench-2026-06-17-131609-0-5-3-rc-1.html)
- [View all synced benchmark reports](https://kalamdb.org/docs/server/benchmarking)
- [Open the GitHub release](https://github.com/kalamdb/KalamDB/releases/tag/v0.5.3-rc.1)
## CLI Project Workflow
The release makes the CLI project loop the recommended path for local development:
1. Run [Project Init](https://kalamdb.org/docs/cli/init) to create `kalam.toml`, `schema.sql`, migrations, optional starter files, and package-manager wiring.
2. Run [Local Development](https://kalamdb.org/docs/cli/dev) to start or reuse a KalamDB server, apply schema changes, regenerate SDK code, and stream supervised process logs.
3. Use [Migrations](https://kalamdb.org/docs/cli/migrations) when schema changes need a committed rollout path.
4. Run `kalam db reset` to clear local server data (`kalam/server/data`, `kalam/server/logs`) when you need a fresh database on the next `kalam dev`.
Local projects scaffolded with `--server-mode local` use `root` / `kalamdb123` in `kalam/server/server.toml` and `.env`.
```bash
mkdir my-agent-app && cd my-agent-app
kalam init --yes --name my-agent-app --languages typescript --package-manager pnpm
kalam dev
```
To start over:
```bash
kalam db reset
kalam dev
```
## Release Post
For the narrative announcement with the CLI workflow image, benchmark summary, and release links, read the [KalamDB v0.5.3-rc.1 pre-release blog post](https://kalamdb.org/blog/kalamdb-v0-5-3-rc-1-pre-release).
================================================================================
# v0.5.4-rc.1 Release
URL: https://kalamdb.org/docs/server/release-v0-5-4-rc-1
Source: content/server/release-v0-5-4-rc-1.mdx
> KalamDB v0.5.4-rc.1 pre-release notes for INSERT RETURNING, ON CONFLICT upsert, Live OKF Context Sync, and CLI and auth hardening.
# KalamDB v0.5.4-rc.1 Release
KalamDB v0.5.4-rc.1 is the June 25 pre-release for PostgreSQL-style `INSERT ... RETURNING` and `ON CONFLICT` upsert, the Live OKF Context Sync example, and CLI, auth, and test hardening.
## SQL: INSERT ... RETURNING
`INSERT` statements can now return affected rows directly in the SQL response.
**Supported syntax:**
```sql
INSERT INTO users (id, name, age) VALUES (1, 'Nader', 3) RETURNING *;
INSERT INTO users (id, name, age) VALUES (1, 'Nader', 3) RETURNING id, name;
```
**Behavior:**
- `RETURNING *` returns all table columns in schema order
- `RETURNING col1, col2, ...` returns a selected projection
- Works for single-row and multi-row `INSERT`
- API responses include the returned rows in the standard `results[].rows` shape
- Parameterized inserts support `RETURNING` over HTTP and through the SDKs
## SQL: INSERT ... ON CONFLICT (upsert)
PostgreSQL-style upsert is supported on the primary key conflict target.
```sql
INSERT INTO users (id, name, age) VALUES (1, 'Nader Updated', 5)
ON CONFLICT (id) DO UPDATE
SET name = EXCLUDED.name, age = EXCLUDED.age
RETURNING *;
INSERT INTO users (id, name, age) VALUES (1, 'Ignored', 99)
ON CONFLICT (id) DO NOTHING
RETURNING *;
```
| Feature | Behavior |
| --- | --- |
| `ON CONFLICT (pk) DO UPDATE SET ...` | Updates existing row; unassigned columns are preserved |
| `EXCLUDED.column` | References the proposed insert values |
| `ON CONFLICT (pk) DO NOTHING` | Skips conflicting rows |
| `DO UPDATE ... WHERE` | Applies update only when the predicate is true |
| `RETURNING` with `DO NOTHING` | Returns zero rows for skipped conflicts |
| Multi-row upsert | One returned row per input row (insert or update) |
| User tables | Conflicts are scoped per user (RLS isolation) |
| Shared tables | Conflicts are global on the primary key |
**Limitations (current release):**
- Conflict target must be the table's **primary key** column
- `ON CONFLICT ON CONSTRAINT` is not supported
- Non-primary-key unique targets are rejected with a clear error
## TypeScript ORM (`@kalamdb/orm`)
Drizzle-style upsert and returning now work end-to-end:
```typescript
const rows = await db
.insert(users)
.values({ id: 1, name: 'Nader', age: 3 })
.returning();
const updated = await db
.insert(users)
.values({ id: 1, name: 'Nader Updated', age: 5 })
.onConflictDoUpdate({
target: users.id,
set: { name: sql`excluded.name`, age: sql`excluded.age` },
})
.returning();
```
FILE uploads through Drizzle use `kalamFile(name, blob)` in `.values()` / `.set()`. `kalamDriver()` detects upload params and routes to multipart `queryWithFiles()` — no separate wrapper per insert:
```typescript
await db.insert(attachments).values({
id: 'att_1',
file_data: kalamFile('upload', selectedFile),
});
```
See [Live OKF Context Sync](https://kalamdb.org/docs/use-cases/live-okf-context-sync) for a folder-sync example that upserts FILE columns with `onConflictDoUpdate`.
## Live OKF Context Sync example
A new showcase app under `examples/live-okf-context-sync/` demonstrates syncing a **Google Open Knowledge Format (OKF)** folder into KalamDB for multi-client AI agent context.
- Per-user OKF Markdown folders synced into KalamDB via a Node sync worker
- File metadata in a user-scoped SQL table; bytes via KalamDB `FILE` upload/download
- Local folder watch (push) + `liveTable()` pull with a SQLite hash cache under `.index/`
- Schema-first workflow: `kalam/schema.sql` is the source of truth; TypeScript types are generated via `@kalamdb/orm`
- Live subscriptions so every sync worker sees metadata changes in realtime
- Delete local `data/` and restart — files restore from the server (KalamDB is the source of truth)
Docs: [Live OKF Context Sync](https://kalamdb.org/docs/use-cases/live-okf-context-sync)
## CLI and dev workflow
- **`kalam dev` improvements** — stronger prechecks for auth, paths, and server readiness before starting dev processes
- **Project init refactor** — cleaner scaffolding for TypeScript projects and connection URL handling
- **Self-update** — more reliable version comparison (version first, then build date for same-version rebuilds)
- **Update-check tests** — version-bump-proof; no longer require editing hardcoded release numbers on each release
- **`run-tests.sh`** — improved running-server credential validation and supplementary suite orchestration
## Security and auth
- **OIDC bearer auth fix** — closes a repository bypass where OIDC bearer tokens could skip expected authorization checks
- **Auth endpoint hardening** — login, setup, and OIDC exchange paths tightened for consistency
## Upgrade notes
1. **Rebuild or update** server and CLI to `0.5.4-rc.1`
2. **TypeScript projects** — bump `@kalamdb/*` packages together; run `npm install` to refresh lockfiles
3. **Upsert adoption** — replace read-then-write patterns with `ON CONFLICT ... DO UPDATE` where appropriate
4. **RETURNING** — use instead of a follow-up `SELECT` after inserts when you need the written row
5. **OKF example** — requires `kalam dev` (starts server, applies schema, generates ORM types)
- [Open the GitHub release](https://github.com/kalamdb/KalamDB/releases/tag/v0.5.4-rc.1)
- [Full changelog](https://github.com/kalamdb/KalamDB/compare/v0.5.3-rc.1...v0.5.4-rc.1)
## Release post
For the narrative announcement with SQL examples, the OKF context sync summary, and release links, read the [KalamDB v0.5.4-rc.1 pre-release blog post](https://kalamdb.org/blog/kalamdb-v0-5-4-rc-1-pre-release).
================================================================================
# SDK Version Compatibility
URL: https://kalamdb.org/docs/server/sdk/compatibility
Source: content/server/sdk/compatibility.mdx
> Compare KalamDB server, pg_kalam, TypeScript, Dart, Flutter, and Rust SDK versions to choose supported release pairings for apps.
# SDK Version Compatibility
Use this SDK version compatibility table to choose the KalamDB server, pg_kalam extension, and client SDK versions that belong together for a deployment.
## Compatibility Matrix
## Selection Guidance
- For new apps, start with the latest server docs and latest SDK docs unless you are pinned to a specific release candidate.
- Keep the server, pg_kalam extension, and SDKs on the same row when you need repeatable staging or production builds.
- When upgrading one component across rows, run a SQL request, an authenticated request, and a realtime subscription smoke test before rolling forward.
## Related Docs
- [TypeScript SDK setup](https://kalamdb.org/docs/ts-sdk/setup)
- [Dart / Flutter SDK setup](https://kalamdb.org/docs/dart-sdk/setup)
- [pg_kalam SQL syntax](https://kalamdb.org/docs/pg-kalam/sql-syntax)
- [HTTP API reference](https://kalamdb.org/docs/server/api/http-reference)
================================================================================
# SDKs & Client Libraries
URL: https://kalamdb.org/docs/server/sdk
Source: content/server/sdk/index.mdx
> Official KalamDB client libraries. Connect with TypeScript, Dart/Flutter, Rust, Python SDK docs, or the HTTP and WebSocket APIs.
# SDKs & Client Libraries
> **Status:** TypeScript, Dart/Flutter, and Rust SDKs are documented and available today. The Rust crate [`kalam-client`](https://crates.io/crates/kalam-client) is published on crates.io (beta). Python SDK docs exist for async SQL and live queries, but package publishing may still be in progress.
KalamDB client libraries:
- [TypeScript SDK](https://kalamdb.org/docs/ts-sdk) for browser/Node.js apps and worker services
- [Dart / Flutter SDK](https://kalamdb.org/docs/dart-sdk) for mobile and cross-platform app clients
- [Rust SDK](https://kalamdb.org/docs/rust-sdk) — [`kalam-client` on crates.io](https://crates.io/crates/kalam-client) for native async Rust services and topic workers
- [Python SDK](https://kalamdb.org/docs/python-sdk) for async SQL and live query examples; see [Python SDK status](https://kalamdb.org/docs/server/sdk/python) before relying on package installation
## Which One To Start With
- Building frontend + backend JS/TS apps: start at [TypeScript Setup](https://kalamdb.org/docs/ts-sdk/setup)
- Building Dart/Flutter apps: start at [Dart Setup](https://kalamdb.org/docs/dart-sdk/setup)
- Building Rust workers/services: start at [Rust SDK](https://kalamdb.org/docs/rust-sdk)
- Building Python services today: review [Python SDK](https://kalamdb.org/docs/python-sdk), then fall back to [HTTP API Reference](https://kalamdb.org/docs/server/api/http-reference) if you need a stable package install path
## Shared Concepts Across SDKs
- JWT bearer auth for SQL and realtime flows
- SQL execution over `/v1/api/sql`
- WebSocket realtime subscriptions over `/v1/ws`
- Topic consume/ack for worker processing
- FILE column uploads and downloads (`FileRef`, `BoundFileRef`, `TableId` in Rust; `BoundFileRef` in TypeScript)
- Vector search through regular SQL (`EMBEDDING`, `COSINE_DISTANCE`, and indexed queries)
For protocol-level integration details, see [API Reference](https://kalamdb.org/docs/server/api).
================================================================================
# Python SDK Status
URL: https://kalamdb.org/docs/server/sdk/python
Source: content/server/sdk/python/index.mdx
> Check the KalamDB Python SDK status, current docs, and fallback HTTP/WebSocket API paths before relying on package installation.
# Python SDK Status
KalamDB has Python SDK docs for async SQL helpers and live query streams:
- [Python SDK overview](https://kalamdb.org/docs/python-sdk)
- [Python Live Queries](https://kalamdb.org/docs/python-sdk/subscriptions)
Release metadata lists a `kalamdb` Python SDK package for the current `0.5.3-rc.1` line,
but the public package install path is not linked from the site config yet. Treat Python
package publishing as in-progress until the docs include a concrete install command.
## Current Recommendation For Python Services
For production integrations that need a stable install path, use the protocol docs
directly:
- [HTTP API Reference](https://kalamdb.org/docs/server/api/http-reference)
- [WebSocket Protocol](https://kalamdb.org/docs/server/api/websocket-protocol)
- [Authentication & Bootstrap](https://kalamdb.org/docs/server/getting-started/authentication)
For Python-facing examples, use the Python SDK docs above as the API shape reference,
then verify package availability before shipping.
## Expected Scope
- SQL execution over the HTTP API
- JWT authentication helpers
- realtime subscriptions
- topic consume/ack worker APIs
- FILE column upload helpers
================================================================================
# Security & Best Practices
URL: https://kalamdb.org/docs/server/security
Source: content/server/security/index.mdx
> Operational security baseline for KalamDB deployments. Learn about auth hardening, TLS, CORS controls, and abuse protection.
# Security
This chapter is for operators deploying KalamDB in staging/production.
## Security Baseline Checklist
1. Serve API behind HTTPS (TLS at edge proxy/load balancer).
2. Set strong `auth.jwt_secret` and keep it out of source control.
3. Keep `auth.allow_remote_setup = false` after initial bootstrap.
4. Restrict CORS and WebSocket origins to known domains.
5. Keep rate limiting and request-size limits enabled.
6. Restrict setup/health/admin-sensitive routes to trusted networks.
7. For clusters, enable `cluster.rpc_tls` (mTLS between nodes).
## Baseline Config Example
```toml
[auth]
jwt_secret = "replace-with-strong-random-secret-32-plus-chars"
cookie_secure = true
allow_remote_setup = false
[rate_limit]
enable_connection_protection = true
max_auth_requests_per_ip_per_sec = 20
max_requests_per_ip_per_sec = 200
max_connections_per_ip = 100
[security]
max_request_body_size = 10485760
max_ws_message_size = 1048576
strict_ws_origin_check = true
allowed_ws_origins = ["https://app.example.com"]
[security.cors]
allowed_origins = ["https://app.example.com", "https://admin.example.com"]
allow_credentials = true
```
## High-Risk Misconfigurations To Avoid
- Wildcard browser origins in production
- Disabled rate-limit middleware in public deployments
- Short/static JWT secrets shared across environments
- Leaving remote setup enabled permanently
- Exposing cluster RPC ports publicly
## Incident Response Priorities
1. Rotate compromised JWT secrets/certificates
2. Disable compromised users/service accounts
3. Tighten ingress and rate limits during active abuse
4. Preserve logs for forensic review
## Related Docs
- [Production Checklist](https://kalamdb.org/docs/server/security/production-checklist)
- [Authentication Providers](https://kalamdb.org/docs/server/auth)
- [Token Auth](https://kalamdb.org/docs/server/auth/token-auth)
- [Auto-Provisioning Users](https://kalamdb.org/docs/server/auth/auto-provisioning)
- [Firebase](https://kalamdb.org/docs/server/integrations/firebase)
- [Rate Limiting](https://kalamdb.org/docs/server/security/rate-limits)
- [Advanced Configuration](https://kalamdb.org/docs/server/configurations/advanced)
- [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc)
- [Keycloak](https://kalamdb.org/docs/server/integrations/keycloak)
- [OpenTelemetry (OTEL)](https://kalamdb.org/docs/server/configurations/otel)
================================================================================
# Production Security Checklist
URL: https://kalamdb.org/docs/server/security/production-checklist
Source: content/server/security/production-checklist.mdx
> Step-by-step checklist and hardened configuration reference for running KalamDB safely in production. Covers secrets, tokens, CORS, TLS, RBAC, rate limits, cluster mTLS, and incident response.
# Running KalamDB in Production
This guide walks you through every setting, secret, and network boundary you need to lock down before exposing a KalamDB server to the public internet or an untrusted network.
Work through the checklist top-to-bottom. Each section is independently actionable — you can turn a single item on and restart the server between steps.
> **Threat model assumption**: attackers can reach your HTTP and cluster RPC ports. Your defenses must not rely on network isolation alone.
---
## Quick Checklist
Copy this into your deploy runbook. Every box must be ticked before you open the port.
### Network & TLS
- [ ] `server.host = "127.0.0.1"` (or explicitly a private interface); never `0.0.0.0` unless you need remote access
- [ ] Terminate TLS at an edge proxy (nginx, Caddy, ALB, Cloudflare) — KalamDB itself serves plain HTTP
- [ ] HTTP server sits behind a firewall/security-group that only allows the edge proxy
- [ ] Cluster RPC port (`cluster.rpc_addr`) is **not** reachable from the public internet
- [ ] For multi-node clusters: `rpc_tls.enabled = true` with `require_client_cert = true`
### Secrets
- [ ] `auth.jwt_secret` is at least 32 bytes, random, unique per environment
- [ ] JWT secret is injected via env var or secret manager, **never** committed to source
- [ ] Root/admin password is changed from any default and stored in a secret manager
- [ ] `KALAMDB_ROOT_PASSWORD` env var is cleared from the shell history after seeding
- [ ] OAuth/OIDC client secrets are in env vars, not the config file
### Auth & RBAC
- [ ] `auth.allow_remote_setup = false` after first-run bootstrap
- [ ] Every human user has a named account, not the built-in `root`
- [ ] Service accounts use the `service` role, never `dba`/`system`
- [ ] Password policy tuned for local auth (`auth.local.min_password_length`, `auth.local.bcrypt_cost`)
- [ ] Access token expiry ≤ 1 hour; refresh token expiry ≤ 7 days
### Origins & Cookies
- [ ] `security.cors.allowed_origins` is an explicit allowlist — no `"*"`
- [ ] `security.cors.allow_credentials = true` **only** when origins are an explicit allowlist
- [ ] `security.strict_ws_origin_check = true`
- [ ] Auth cookies served over HTTPS: `auth.cookie_secure = true`, `SameSite=Strict`, `HttpOnly`
### Abuse Controls
- [ ] `rate_limit.enable_connection_protection = true`
- [ ] `rate_limit.max_auth_requests_per_ip_per_sec` tuned (start at 10–20)
- [ ] `rate_limit.max_connections_per_ip` tuned (start at 100)
- [ ] `security.max_request_body_size` tuned to your real upload size
- [ ] `security.max_ws_message_size` tuned to your real message size
### Observability
- [ ] Audit logs shipped to a separate write-only sink (e.g., SIEM, S3 with object lock)
- [ ] Metrics endpoint exposed only to the internal monitoring network
- [ ] Alerts configured on: failed logins/min, 401/403 rate, new user creation, role changes
---
## 1. Bind Addresses & TLS
KalamDB does not terminate TLS itself. Run it behind a reverse proxy that provides HTTPS and forwards to KalamDB on localhost.
### Recommended topology
```
[ client ] ──HTTPS──► [ nginx/Caddy/ALB ] ──HTTP(loopback)──► [ kalamdb :2900 ]
[ kalamdb :2910 cluster mTLS ]
```
### Config
```toml
[server]
host = "127.0.0.1" # loopback only; proxy handles remote clients
port = 2900
[cluster]
# Bind cluster RPC to the private cluster network interface only.
rpc_addr = "10.0.1.15:2910"
[rpc_tls]
enabled = true
require_client_cert = true
ca_cert = "/etc/kalamdb/tls/cluster-ca.pem"
server_cert = "/etc/kalamdb/tls/node.pem"
server_key = "/etc/kalamdb/tls/node.key"
```
If your cluster spans more than loopback nodes, KalamDB will refuse to start without `rpc_tls.enabled = true`. Do not work around this check.
---
## 2. JWT Secrets & Token Hygiene
### Secret requirements
- **Length**: minimum 32 bytes (64+ recommended)
- **Source**: cryptographic RNG (`openssl rand -base64 48`), not a passphrase
- **Rotation**: rotate on compromise, personnel change, or at least annually
- **Scope**: unique per environment (dev, staging, prod never share)
Generate and inject via environment:
```bash
```
```toml
[auth]
# Leave unset in the file when you set KALAMDB_AUTH_JWT_SECRET in the env.
# jwt_secret = "..."
access_token_expiry_hours = 1
refresh_token_expiry_hours = 168 # 7 days
```
KalamDB refuses to start on non-loopback binds if the secret is empty, a known-weak placeholder, or shorter than 32 bytes.
### Rotation procedure
1. Generate a new secret.
2. Deploy it to every node simultaneously.
3. Bounce the service.
4. All outstanding tokens will fail verification — clients must re-login.
> Do not re-use an old secret. There is no "grace period" key ring; rotation is abrupt by design.
### Where tokens are exposed
| Surface | Carries token? | Notes |
|---|---|---|
| `Authorization: Bearer …` header | Yes | Preferred for server-to-server |
| Auth cookies | Yes | Browser flows; always set `Secure`, `HttpOnly`, `SameSite=Strict` |
| WebSocket subprotocol | Yes | Sent once during upgrade; never in URL query |
| Logs | **No** | Log redaction is enabled by default; keep it enabled |
---
## 3. Admin Bootstrap & Setup
On first boot, KalamDB seeds a `root` user. What happens next depends on your configuration.
### Option A — Seeded password (recommended for remote deployments)
```bash
# run the server once so the password is hashed and persisted
# then unset the env var and store the password in your secret manager
unset KALAMDB_ROOT_PASSWORD
```
### Option B — Remote setup endpoint (one-time)
If you cannot set the env var, allow the setup endpoint for the first call only:
```toml
[auth]
allow_remote_setup = true # flip back to false immediately after setup
```
**Set `allow_remote_setup = false` and restart after you complete setup.** Leaving it enabled lets anyone who can reach the endpoint attempt to seed credentials.
### Option C — Localhost-only root
If you only administer via an SSH tunnel, leave the password empty. KalamDB will refuse remote logins for `root` and only accept localhost calls. Create named admin users via `CREATE USER` for remote access.
---
## 4. Password Policy
Minimum controls enforced by KalamDB:
- Minimum length (configurable via `auth.local.min_password_length`)
- Max length (configurable via `auth.local.max_password_length`; bcrypt still uses the first 72 bytes)
- Bcrypt hash with configurable cost (`auth.local.bcrypt_cost`, default `12`)
- Optional complexity rules via `auth.local.enforce_password_complexity`
- Rejected common-password list
- Constant-time verification
- Generic error messages (no user enumeration)
Recommended runtime:
```toml
[auth.local]
enabled = true
min_password_length = 12
bcrypt_cost = 12
enforce_password_complexity = false
```
Push stricter composition requirements (for example character classes) through `auth.local.enforce_password_complexity` or your external identity provider. KalamDB keeps complexity off by default to avoid false rejections of passphrases.
---
## 5. CORS & WebSocket Origins
### CORS for the browser admin UI
```toml
[security.cors]
allowed_origins = [
"https://admin.example.com",
"https://app.example.com",
]
allow_credentials = true
allowed_methods = ["GET", "POST", "OPTIONS"]
allowed_headers = ["Authorization", "Content-Type"]
max_age = 600
```
Hard rules:
- Never combine `allowed_origins = ["*"]` with `allow_credentials = true`. Browsers reject it, and KalamDB will refuse to start with that combination.
- Use scheme + host + port — origin matching is exact.
- Never include localhost origins in production config.
### WebSocket origins
```toml
[security]
strict_ws_origin_check = true
allowed_ws_origins = ["https://app.example.com"]
```
`strict_ws_origin_check = true` rejects connections that omit the `Origin` header (non-browser tooling must set it explicitly). Leave empty `allowed_ws_origins` only on loopback dev installs.
---
## 6. Rate Limiting & Connection Protection
Even with auth in place, rate limits protect against credential stuffing, SQL abuse, and connection exhaustion.
```toml
[rate_limit]
enable_connection_protection = true
# Auth endpoints (login, refresh, setup, WS auth)
max_auth_requests_per_ip_per_sec = 10
# Per-IP concurrent connection cap
max_connections_per_ip = 100
# Pre-auth request flood protection
max_requests_per_ip_per_sec = 200
# Per-user query rate (SQL endpoint)
max_queries_per_sec = 100
# WebSocket message flood protection
max_messages_per_sec = 50
# Temporary ban window for abusive IPs
ban_duration_seconds = 300
```
If you run behind a reverse proxy, configure `security.trusted_proxy_ranges` so that rate limits attribute requests to the real client IP via `X-Forwarded-For`. **Do not** set this to `0.0.0.0/0` — that lets any caller spoof any IP and bypass the limits.
```toml
[security]
trusted_proxy_ranges = ["10.0.0.0/8", "172.16.0.0/12"]
```
---
## 7. RBAC & Least Privilege
KalamDB has four roles. Use the lowest one that works.
| Role | Typical use | Can do |
|---|---|---|
| `user` | App end-users, SDK clients | Read/write their own namespaces, run DML |
| `service` | Backend services, pub/sub consumers | DML + topic consume/ack |
| `dba` | Database administrators | DDL, manage users except `system` |
| `system` | Reserved for the server itself | Everything (do not create new ones) |
Rules of thumb:
- Application tokens should be `user` or `service`, never `dba`.
- Create one `dba` per human administrator; audit their usage.
- `EXECUTE AS ''` follows the role hierarchy: system can target system/DBA/service/user, DBA can target DBA/service/user, service can target service/user, and regular users can only target themselves.
- System tables (`system.*`) are read/write restricted to `dba` and `system`. This is enforced inside the query planner, including through subqueries, CTEs, and views.
### On role demotion
When you demote or lock a user, rotate their tokens. KalamDB re-validates the DB role on each token refresh and invalidates tokens whose `token_generation` is older than the DB row — but already-issued access tokens remain valid until `access_token_expiry_hours`. Keep access-token TTLs short.
---
## 8. Request Size Guards & File Uploads
```toml
[security]
max_request_body_size = 10485760 # 10 MiB; tune to real payload size
max_ws_message_size = 1048576 # 1 MiB
```
For file-accepting endpoints (multipart `FILE("name")` placeholders, exports), KalamDB:
- Validates path components against an allowlist (alphanumeric, `-`, `_`, `.`)
- Canonicalises paths and rejects symlink escape
- Returns 403 to non-owners trying to download another user's files or exports
Operators should additionally:
- Put an object-storage lifecycle on the exports bucket (e.g., delete after N days)
- Scan uploaded files with your AV/virus pipeline before re-serving them to users
---
## 9. Cluster RPC Hardening
For anything beyond a single-node loopback cluster:
```toml
[rpc_tls]
enabled = true
require_client_cert = true
ca_cert = "/etc/kalamdb/tls/cluster-ca.pem"
server_cert = "/etc/kalamdb/tls/node-1.pem"
server_key = "/etc/kalamdb/tls/node-1.key"
[cluster]
rpc_addr = "10.0.1.15:2910" # private interface only
```
Notes:
- The cluster RPC port carries Raft consensus, `ForwardSql`, `Ping`, and node-info RPCs. Treat it as equally sensitive to the data port.
- `ForwardSql` additionally re-validates the caller's Bearer JWT on the receiving node — mTLS is defense-in-depth, not the only layer.
- Use a private CA unique to the cluster. Do not reuse your edge-TLS certificate chain for cluster mTLS.
- Rotate node certificates independently of JWT secrets.
---
## 10. Logging, Audit & Observability
- KalamDB emits structured JSON logs. SQL statements are redacted before logging; do not re-enable raw SQL in log formatters.
- Enable the audit stream to capture user logins, user creation/modification, role changes, impersonation events, and admin export downloads.
- Ship audit logs to an append-only sink (S3 with object lock, SIEM).
- Expose Prometheus metrics on the internal network only — never on the public API port.
### Suggested alerts
- `auth_failed_logins_per_minute > 20`
- `auth_403_forbidden_per_minute > 50`
- `user_role_change_events > 0` (human review on every change)
- `impersonation_events` grouped by actor
- Cluster RPC connection count deviation > 3σ
---
## 11. Incident Response Playbook
When credentials, a token secret, or a node are suspected compromised:
1. **Contain** — rotate `auth.jwt_secret`. All outstanding tokens become invalid immediately.
2. **Lock down** — set `auth.allow_remote_setup = false`, revoke compromised user accounts (`ALTER USER … LOCK`).
3. **Rotate** — regenerate cluster mTLS material, redeploy, force clients to re-authenticate.
4. **Narrow** — temporarily lower `max_auth_requests_per_ip_per_sec` and `max_connections_per_ip`.
5. **Preserve** — snapshot logs, audit stream, and RocksDB backup before further changes.
6. **Review** — audit `system.users` for unexpected role changes, `system.jobs` for unexpected admin jobs, exports directory for unexpected downloads.
7. **Post-mortem** — document the breach path, fix the configuration gap, add a regression check.
---
## 12. High-Risk Misconfigurations To Avoid
KalamDB will refuse to start on several of these. The rest are human errors we see most often.
- `server.host = "0.0.0.0"` with no edge proxy and default JWT secret
- `security.cors.allowed_origins = ["*"]` combined with `allow_credentials = true`
- `security.strict_ws_origin_check = false` on a public WebSocket endpoint
- `auth.allow_remote_setup = true` left on after bootstrap
- `auth.jwt_secret` shared across environments, or shorter than 32 bytes
- `security.trusted_proxy_ranges` containing `0.0.0.0/0` or `::/0`
- Running a multi-node cluster with `rpc_tls.enabled = false`
- Handing out `dba` or `system` role tokens to applications
- Storing secrets in `server.toml` committed to Git
---
## Baseline Production `server.toml`
Drop-in starting point. Fill in real values, inject secrets via env.
```toml
[server]
host = "127.0.0.1"
port = 2900
enable_http2 = true
[auth]
# jwt_secret intentionally omitted — supplied via KALAMDB_JWT_SECRET
allow_remote_setup = false
cookie_secure = true
jwt_expiry_hours = 24
[auth.local]
enabled = true
min_password_length = 12
bcrypt_cost = 12
enforce_password_complexity = false
[rate_limit]
enable_connection_protection = true
max_auth_requests_per_ip_per_sec = 10
max_connections_per_ip = 100
max_requests_per_ip_per_sec = 200
max_queries_per_sec = 100
max_messages_per_sec = 50
ban_duration_seconds = 300
[security]
max_request_body_size = 10485760
max_ws_message_size = 1048576
strict_ws_origin_check = true
allowed_ws_origins = ["https://app.example.com"]
trusted_proxy_ranges = ["10.0.0.0/8"]
[security.cors]
allowed_origins = ["https://admin.example.com", "https://app.example.com"]
allow_credentials = true
allowed_methods = ["GET", "POST", "OPTIONS"]
allowed_headers = ["Authorization", "Content-Type"]
max_age = 600
[rpc_tls]
enabled = true
require_client_cert = true
ca_cert = "/etc/kalamdb/tls/cluster-ca.pem"
server_cert = "/etc/kalamdb/tls/node.pem"
server_key = "/etc/kalamdb/tls/node.key"
[cluster]
rpc_addr = "10.0.1.15:2910"
```
---
## Related Docs
- [Security Overview](https://kalamdb.org/docs/server/security)
- [Rate Limiting](https://kalamdb.org/docs/server/security/rate-limits)
- [Authentication](https://kalamdb.org/docs/server/auth)
- [Token Auth](https://kalamdb.org/docs/server/auth/token-auth)
- [OIDC & Issuer Trust](https://kalamdb.org/docs/server/configurations/oidc)
- [Advanced Configuration](https://kalamdb.org/docs/server/configurations/advanced)
- [OpenTelemetry (OTEL)](https://kalamdb.org/docs/server/configurations/otel)
================================================================================
# Rate Limiting
URL: https://kalamdb.org/docs/server/security/rate-limits
Source: content/server/security/rate-limits.mdx
> How to tune KalamDB rate-limit settings for SQL queries, auth endpoints, WebSocket connections, and connection abuse controls in production.
# Rate Limiting
KalamDB applies rate limiting at SQL, auth, WebSocket, and per-IP connection layers.
## Key Settings
| Key | Scope |
|-----|-------|
| `rate_limit.max_queries_per_sec` | per-user SQL request rate |
| `rate_limit.max_messages_per_sec` | per-WebSocket incoming message rate |
| `rate_limit.max_subscriptions_per_user` | active live subscriptions per user |
| `rate_limit.max_auth_requests_per_ip_per_sec` | login/setup/refresh abuse protection |
| `rate_limit.max_connections_per_ip` | concurrent socket cap per IP |
| `rate_limit.max_requests_per_ip_per_sec` | pre-auth request flood protection |
| `rate_limit.request_body_limit_bytes` | request payload size guard |
| `rate_limit.ban_duration_seconds` | temporary ban window |
| `rate_limit.enable_connection_protection` | master switch for connection guard |
## Practical Starting Point
```toml
[rate_limit]
max_queries_per_sec = 100
max_messages_per_sec = 50
max_subscriptions_per_user = 10
max_auth_requests_per_ip_per_sec = 20
max_connections_per_ip = 100
max_requests_per_ip_per_sec = 200
request_body_limit_bytes = 10485760
ban_duration_seconds = 300
enable_connection_protection = true
cache_max_entries = 100000
cache_ttl_seconds = 600
```
## Environment Strategy
- development: permissive but enabled (so behavior stays realistic)
- staging: mirror production values
- production: strict thresholds + alerts on violations
## Tuning Workflow
1. collect baseline traffic per endpoint and user role
2. set limits just above normal peaks
3. alert on near-limit and over-limit events
4. adjust independently for auth, SQL, and WebSocket traffic
## Signals To Monitor
- spikes in `429` responses
- repeated bans for same source ranges
- auth endpoint bursts
- large WebSocket fan-out patterns with rising rejection counts
================================================================================
# Backup & Restore
URL: https://kalamdb.org/docs/server/sql-reference/backup
Source: content/server/sql-reference/backup.mdx
> Database backup and restore commands in KalamDB. Learn how to create namespace backups, schedule exports, and restore data from backup archives.
# Backup & Restore
KalamDB provides built-in SQL commands for backing up and restoring the **entire
database**. A backup captures everything in one compressed archive — RocksDB
data, Parquet storage files, Raft snapshots, and the server configuration — so
you always have a consistent, self-contained snapshot you can restore from.
> **Role requirement**: `BACKUP DATABASE` and `RESTORE DATABASE` require the
> **DBA** or **System** role.
---
## BACKUP DATABASE
Create a full database backup. The entire data directory and `server.toml` are
compressed into a single `.tar.gz` archive at the specified path.
```sql
BACKUP DATABASE TO '';
```
The command enqueues a background backup job and returns immediately with a
**Job ID** you can use to monitor progress.
### Parameters
| Parameter | Description |
|---|---|
| `backup_path` | Absolute path for the output archive. Must end in `.tar.gz`. Quotes (single or double) are required. |
### Archive contents
| Path inside archive | Description |
|---|---|
| `data/rocksdb/` | RocksDB write-path data |
| `data/storage/` | Flushed Parquet segment files |
| `data/snapshots/` | Raft snapshots |
| `data/streams/` | Stream commit log data |
| `server.toml` | Server configuration (if present) |
### Example
```sql
-- Back up the entire database to a timestamped archive
BACKUP DATABASE TO '/backups/kalamdb_20260224.tar.gz';
-- Store to a date-named path
BACKUP DATABASE TO '/var/backups/kalamdb/2026-02-24.tar.gz';
```
**Response**
```
Database backup started to '/backups/kalamdb_20260224.tar.gz'. Job ID: BK-0001
```
---
## RESTORE DATABASE
Restore the entire database from a previously created `.tar.gz` backup archive.
The command stages the restored data under `_restore_pending/` without
touching the live database. **A server restart is required to complete the
restore** — on startup, KalamDB detects the pending directory and swaps it in.
```sql
RESTORE DATABASE FROM '';
```
> **Warning**: Once the server restarts after staging, all data written since the
> backup was taken will be lost. Take a fresh backup of the current state before
> running a restore if you need a rollback option.
### Parameters
| Parameter | Description |
|---|---|
| `backup_path` | Absolute path to the `.tar.gz` backup archive to restore from. Quotes (single or double) are required. |
### Restore lifecycle
1. Issue `RESTORE DATABASE FROM '';` — returns a **Job ID**.
2. The job extracts the archive to `_restore_pending/`.
3. Job status transitions to `Completed` with the message *"Restore staged from '…'. RocksDB restore is pending server restart."*
4. **Restart the KalamDB server** — it detects the pending directory, swaps it in, and starts fresh.
### Example
```sql
-- Stage a restore from a specific timestamped backup
RESTORE DATABASE FROM '/backups/kalamdb_20260224.tar.gz';
```
**Response**
```
Database restore started from '/backups/kalamdb_20260224.tar.gz'. Job ID: RS-0001
```
After the job completes:
```sql
-- Confirm the restore job reached Completed before restarting
SELECT job_id, status, message FROM system.jobs WHERE job_id = 'RS-0001';
```
Then restart the server process to activate the restored data.
---
## Path security rules
KalamDB validates the backup path on both `BACKUP` and `RESTORE` to prevent
path traversal and sensitive file access:
- `..` sequences are **not allowed** (blocks path traversal)
- Null bytes (`\0`) are **not allowed**
- Writing to `/etc/`, `/root/`, `/var/log/`, or `C:\Windows\` is **blocked**
- The path must be quoted (bare paths are rejected)
```sql
-- These will be rejected:
BACKUP DATABASE TO '../../../tmp/evil.tar.gz'; -- path traversal
BACKUP DATABASE TO '/etc/shadow'; -- sensitive directory
BACKUP DATABASE TO /backups/app.tar.gz; -- unquoted path
```
---
## Async job execution
Backup and restore operations run as **background jobs** managed by the
`UnifiedJobManager`. The SQL command returns immediately with a job ID; the
work happens asynchronously so the server remains responsive.
You can monitor job status via the `system.jobs` table:
```sql
SELECT job_id, status, message
FROM system.jobs
WHERE job_id = 'BK-0001';
```
| `status` value | Meaning |
|---|---|
| `Queued` | Job is waiting to start |
| `Running` | Backup/restore is in progress |
| `Completed` | Finished successfully |
| `Failed` | An error occurred — see `message` for details |
---
## Backup strategy
For production deployments, consider:
1. **Scheduled backups** — Run `BACKUP DATABASE` on a cron or application-level
schedule (daily, hourly, etc.)
2. **Timestamped archives** — Include a date/time in the path so each backup is
uniquely named and old ones are not silently overwritten
3. **Remote storage** — Copy the `.tar.gz` archive to S3, GCS, or Azure Blob
Storage after creation for off-site durability
4. **Snapshot + backup** — Combine `CLUSTER SNAPSHOT` with `BACKUP DATABASE`
for a fully consistent cluster-wide state capture
5. **Pre-restore snapshot** — Take a backup of the current state before running
`RESTORE DATABASE` so you can roll back if needed
================================================================================
# Cluster Operations
URL: https://kalamdb.org/docs/server/sql-reference/cluster
Source: content/server/sql-reference/cluster.mdx
> CLI-first cluster inspection and administration in KalamDB, including list, snapshots, elections, purging, rebalance, join, and snapshot cleanup.
# Cluster Operations
Cluster inspection and administration are documented on the CLI page first. The CLI renders the structured SQL results returned by the server, which keeps operational output out of backend string-formatting paths.
See [Interactive Commands — cluster](https://kalamdb.org/docs/cli/interactive-commands)
for the supported operator commands and
[SQL Workflows — follower write forwarding](https://kalamdb.org/docs/cli/workflows)
for the end-to-end flow when a write lands on a follower node. For the Multi-Raft architecture,
see [/docs/server/architecture/clustering](https://kalamdb.org/docs/server/architecture/clustering).
## Inspect cluster state
Use the CLI for node and group views:
```bash
kalam --command "\\cluster list"
kalam --command "\\cluster list groups"
```
For raw tabular access, query the system views directly:
```sql
SELECT * FROM system.cluster ORDER BY is_leader DESC, node_id ASC;
SELECT * FROM system.cluster_groups ORDER BY group_id ASC;
```
`CLUSTER LIST`, `CLUSTER STATUS`, and `CLUSTER LS` are no longer documented SQL commands. Use `\cluster list` or the `system.cluster` views instead.
## Run cluster operations
Use the CLI cluster meta-commands documented in
[Interactive Commands](https://kalamdb.org/docs/cli/interactive-commands).
Those commands still use the SQL transport under the hood, but the CLI owns the operator-facing rendering.
## Automation note
The underlying SQL transport is still what the CLI calls, so automation can keep using the cluster SQL statements when you want the raw row payload instead of CLI rendering.
## Unsupported or removed commands
```sql
CLUSTER LEAVE; -- unsupported
```
`CLUSTER LEAVE` is still unsupported. For list-style inspection, use `\cluster list` or the system views instead of old `CLUSTER LIST`/`STATUS`/`LS` SQL forms.
`CLUSTER TRANSFER LEADER` is still routed by the backend, but some builds can report it as unsupported at runtime depending on the OpenRaft version in use.
## Related Architecture
- [/docs/server/architecture/clustering](https://kalamdb.org/docs/server/architecture/clustering)
- [/docs/server/architecture/live-query](https://kalamdb.org/docs/server/architecture/live-query)
- [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types)
================================================================================
# Schema & Query Diagnostics
URL: https://kalamdb.org/docs/server/sql-reference/diagnostics
Source: content/server/sql-reference/diagnostics.mdx
> Inspect table schemas with DESCRIBE and inspect query plans with EXPLAIN, ANALYZE, and VERBOSE. Includes KalamDB hot/cold storage scan metrics.
# Schema & Query Diagnostics
Use these commands to inspect table schemas and query execution plans. They are intended for
operators, DBAs, and developers tuning SQL against KalamDB tables.
For storage-tier background on plan metrics, see
[/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers).
## DESCRIBE — table schema
KalamDB supports two DESCRIBE styles. Both answer “what columns does this table have?”, but they
use different syntax, authorization, and result shapes.
### `DESCRIBE TABLE` (KalamDB native)
```sql
DESCRIBE TABLE [.];
DESC TABLE [.];
-- Schema version history
DESCRIBE TABLE [.] HISTORY;
```
**Access:** authenticated users with an elevated role (DBA or System). Regular `User` role
statements are rejected at execution time.
**Returns:** rich column metadata from the schema registry:
| Column | Description |
|--------|-------------|
| `column_name` | Column name |
| `ordinal_position` | 1-based position in the table |
| `column_id` | Parquet field id |
| `data_type` | Kalam SQL type name (for example `TEXT`, `BIGINT`, `TIMESTAMP`) |
| `is_nullable` | Whether the column allows `NULL` |
| `is_primary_key` | Whether the column is part of the primary key |
| `column_default` | Default expression, if any |
| `column_comment` | Column comment, if any |
| `schema_version` | Schema version when the column was added |
**Example:**
```sql
DESCRIBE TABLE dba.notifications;
```
### `DESCRIBE ` / `DESC ` (DataFusion shorthand)
```sql
DESCRIBE [.];
DESC [.];
```
**Access:** DBA or System only (admin meta command).
**Behavior:** KalamDB rewrites the statement to query `information_schema.columns` and returns
Kalam SQL type names (`kdb_data_type`) instead of Arrow physical types:
| Column | Description |
|--------|-------------|
| `column_name` | Column name |
| `data_type` | Kalam SQL type (`kdb_data_type`) |
| `is_nullable` | `YES` / `NO` |
Unqualified table names resolve against the session’s current namespace (`USE NAMESPACE`, CLI
`ns:` context, or API `namespace_id`).
**Example:**
```sql
USE NAMESPACE dba;
DESCRIBE notifications;
```
### CLI shortcut
In the interactive shell, `\d ` and `\describe ` run an
`information_schema.columns` query with Kalam type names — the same columns as the shorthand
above, plus `namespace`, `column_default`, and `position`:
```sql
\d dba.notifications
\describe notifications
```
See [CLI querying](https://kalamdb.org/docs/cli/querying) for output formats.
---
## EXPLAIN — query plans
`EXPLAIN` shows how DataFusion plans a query without executing it (unless `ANALYZE` is present).
**Access:** DBA or System only.
### Syntax (keyword order matters)
DataFusion expects this order:
```sql
EXPLAIN [ANALYZE] [VERBOSE] [FORMAT ] ;
```
| Keyword | Position | Effect |
|---------|----------|--------|
| `EXPLAIN` | required | Start the diagnostic command |
| `ANALYZE` | optional, **before** `VERBOSE` | Execute the query and attach runtime metrics |
| `VERBOSE` | optional, after `ANALYZE` | Add extra result rows (full metrics, row count, duration) when combined with `ANALYZE` |
| `FORMAT` | optional | Plan rendering style (`indent` default, `tree`, `pgjson`) |
**Common mistake:** `EXPLAIN VERBOSE ANALYZE` is **not** valid. Use
`EXPLAIN ANALYZE VERBOSE` instead. With the wrong order, the parser treats `ANALYZE` as a
separate statement and fails.
### `EXPLAIN` (plan only)
Returns two rows — logical plan and physical plan — without running the query:
```sql
EXPLAIN
SELECT id, user_id
FROM dba.notifications
WHERE user_id = 'test'
LIMIT 3;
```
Example output columns:
| plan_type | plan |
|-----------|------|
| `logical_plan` | Optimized logical plan tree |
| `physical_plan` | Physical operators (`FilterExec`, `GlobalLimitExec`, `DeferredBatchExec`, …) |
### `EXPLAIN ANALYZE` (plan + runtime metrics)
Executes the query, discards result rows to the client, and returns an annotated physical plan
with per-operator metrics:
```sql
EXPLAIN ANALYZE
SELECT id, user_id, _deleted
FROM dba.notifications
WHERE user_id = 'test'
LIMIT 3;
```
Returns one primary row:
| plan_type | plan |
|-----------|------|
| `Plan with Metrics` | Physical plan tree with timing and counter metrics |
On `USER` and `SHARED` tables, KalamDB enables **scan diagnostics** for `EXPLAIN ANALYZE`.
Look for `DeferredBatchExec` nodes and metrics such as:
| Metric | Meaning |
|--------|---------|
| `hot_rows_scanned` | Rows read from the hot tier (RocksDB) |
| `cold_rows_scanned` | Rows read from cold Parquet files |
| `cold_files_total` | Cold files considered by manifest pruning |
| `cold_files_skipped` | Cold files skipped by pruning |
| `cold_files_scanned` | Cold files actually read |
| `cold_file_visited{file=…}` | Individual Parquet files touched (up to 16 recorded) |
Plan text also includes static scan context on deferred MVCC sources, for example:
`storage_tiers=[hot=rocksdb,cold=parquet], mvcc=true`.
### `EXPLAIN ANALYZE VERBOSE` (full metrics + summary)
Adds extra rows after the annotated plan:
```sql
EXPLAIN ANALYZE VERBOSE
SELECT id, user_id, _deleted
FROM dba.notifications
WHERE user_id = 'test'
LIMIT 3;
```
| plan_type | plan |
|-----------|------|
| `Plan with Metrics` | Annotated plan with standard metrics |
| `Plan with Full Metrics` | Plan with all available metric detail |
| `Output Rows` | Rows produced by the query |
| `Duration` | Total execution time |
### Reading plans for KalamDB tables
Typical physical plans on application tables include:
- **`DeferredBatchExec`** — deferred MVCC scan (hot RocksDB + cold Parquet merge)
- **`FilterExec`** — predicate evaluation (pushdown where possible)
- **`ProjectionExec`** — column selection
- **`GlobalLimitExec`** / **`LocalLimitExec`** — `LIMIT` handling
- **`CooperativeExec`** — stream table scans
Use plain `EXPLAIN` to compare logical vs physical shape without execution cost. Use
`EXPLAIN ANALYZE` when you need to see whether filters hit the hot tier, how many cold files
were scanned, and operator timings.
---
## CLI output tips
In default **table** format, the CLI renders `plan_type` / `plan` result sets specially:
- Multi-line plan text is expanded across table rows (continuation lines leave `plan_type` blank)
- The `plan` column uses the full terminal width instead of the generic 32-character cap
For machine-readable output, use JSON:
```bash
kalam --json -c "EXPLAIN ANALYZE SELECT 1;"
```
Or in the interactive shell:
```sql
\format json
EXPLAIN ANALYZE VERBOSE SELECT id FROM dba.notifications LIMIT 3;
```
## Related
- [Table DDL — DESCRIBE TABLE](https://kalamdb.org/docs/server/sql-reference/tables#describe-table)
- [Query Data (SELECT & JOIN)](https://kalamdb.org/docs/server/sql-reference/query-data)
- [Storage tiers](https://kalamdb.org/docs/server/architecture/storage-tiers)
- [CLI querying](https://kalamdb.org/docs/cli/querying)
================================================================================
# Data Manipulation
URL: https://kalamdb.org/docs/server/sql-reference/dml
Source: content/server/sql-reference/dml.mdx
> KalamDB data manipulation language (DML) reference. Learn INSERT, UPDATE, DELETE, PostgreSQL-style upsert with ON CONFLICT DO UPDATE, and RETURNING syntax for USER, SHARED, and STREAM tables.
# Data Manipulation (DML)
Standard SQL data manipulation commands for reading and writing data.
DML permission and routing behavior depends on table type. For `USER`, `SHARED`, `STREAM`, and
`SYSTEM` access rules, see [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types).
## INSERT
Insert one or more rows into a table:
```sql
INSERT INTO [.] (, , ...)
VALUES (, , ...);
```
### Batch Insert
```sql
INSERT INTO [.] (, , ...)
VALUES
(, , ...),
(, , ...);
```
### Examples
```sql
-- Single insert with auto-generated ID
INSERT INTO chat.messages (conversation_id, sender, content)
VALUES (1, 'alice', 'Hello!');
-- Batch insert
INSERT INTO chat.messages (conversation_id, sender, role, content)
VALUES
(1, 'alice', 'user', 'What is KalamDB?'),
(1, 'assistant', 'assistant', 'KalamDB is a SQL-first realtime database.');
```
## Upsert with `ON CONFLICT`
KalamDB supports PostgreSQL-style upsert for literal `INSERT ... VALUES` statements on
`USER`, `SHARED`, and `STREAM` tables:
```sql
INSERT INTO [.] (, , ...)
VALUES (, , ...)
ON CONFLICT ()
DO UPDATE SET
= EXCLUDED.,
= ;
```
If the primary key already exists, KalamDB updates the row with the `DO UPDATE SET` assignments.
If the primary key is new, KalamDB inserts the row from `VALUES`.
### Examples
```sql
-- Update an existing shared row by primary key
INSERT INTO app.items (id, name)
VALUES (1, 'beta')
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name;
-- Insert when the primary key is missing, update when it already exists
INSERT INTO app.items (id, name)
VALUES (42, 'gamma')
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name;
```
`DO UPDATE SET` assignments support:
- `EXCLUDED.` to read the value from the attempted insert
- literal constants such as `'published'`, `42`, or `NULL`
### Upsert inside explicit transactions
Upsert works inside `BEGIN` / `COMMIT` blocks and sees rows staged earlier in the same
transaction, including on `SHARED` tables:
```sql
BEGIN;
INSERT INTO app.items (id, name) VALUES (2, 'alpha');
INSERT INTO app.items (id, name) VALUES (2, 'gamma')
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name;
COMMIT;
```
Outside an explicit transaction, KalamDB wraps each upsert in its own internal transaction
and commits it automatically.
### Upsert rules and limits
- Requires a single-column primary key. The conflict target must be that primary key column.
- Literal `VALUES` inserts only. `INSERT ... SELECT` upserts are not supported on this path.
- `ON CONFLICT DO NOTHING`, `ON CONFLICT ON CONSTRAINT`, and `ON CONFLICT DO UPDATE WHERE`
are not supported.
- Tuple assignments in `DO UPDATE SET` are not supported.
- `system.*` tables are rejected.
## `RETURNING` on upsert
Add `RETURNING` to an upsert to get back the inserted or updated row instead of only an
affected-row count. This follows PostgreSQL's `INSERT ... RETURNING` shape: the clause lists
the output columns, and aliases are supported.
```sql
INSERT INTO [.] ()
VALUES ()
ON CONFLICT ()
DO UPDATE SET
RETURNING [, AS ...];
```
### Examples
```sql
-- Return the final row after an update
INSERT INTO app.items (id, name)
VALUES (1, 'beta')
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name
RETURNING id, name;
-- Return a column alias
INSERT INTO app.items (id, name)
VALUES (1, 'beta')
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name
RETURNING id, name AS returned_name;
-- Return the inserted row when no conflict occurred
INSERT INTO app.items (id, name)
VALUES (42, 'alpha')
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name
RETURNING id, name;
```
### Response shape
- Without `RETURNING`, the SQL API reports an insert result with `rows_affected`.
- With `RETURNING`, the SQL API returns a query result with `schema`, `rows`, and `row_count`,
the same success shape as `SELECT`.
`RETURNING` is currently supported on the upsert path above. Plain `INSERT ... RETURNING`
without `ON CONFLICT`, and `UPDATE` / `DELETE ... RETURNING`, are not supported yet.
## UPDATE
```sql
UPDATE [.]
SET = , =
WHERE ;
```
### Example
```sql
UPDATE chat.messages
SET content = 'Updated message content'
WHERE id = 42;
```
## DELETE
```sql
DELETE FROM [.]
WHERE ;
```
### Example
```sql
DELETE FROM chat.messages
WHERE conversation_id = 1 AND sender = 'bot';
```
## Explicit Transactions
KalamDB stays in autocommit mode by default. Use explicit transaction blocks when you need PostgreSQL-style atomic multi-statement writes.
### Syntax
```sql
BEGIN;
START TRANSACTION;
COMMIT;
COMMIT WORK;
ROLLBACK;
ROLLBACK WORK;
```
### Commit example
```sql
BEGIN;
INSERT INTO chat.messages (conversation_id, sender, content)
VALUES (7, 'alice', 'draft');
UPDATE chat.messages
SET content = 'published'
WHERE conversation_id = 7 AND sender = 'alice';
COMMIT;
```
### Rollback example
```sql
BEGIN;
INSERT INTO chat.messages (conversation_id, sender, content)
VALUES (8, 'alice', 'temporary');
SELECT *
FROM chat.messages
WHERE conversation_id = 8;
ROLLBACK;
```
Within the open transaction, reads see the staged writes from that same transaction. After `ROLLBACK`, those staged changes are discarded.
### Rules and limits
- Explicit transactions currently support `USER` and `SHARED` tables.
- `STREAM` tables and `system.*` tables are rejected inside explicit transactions.
- DDL statements are not supported inside explicit transactions.
- Nested `BEGIN` and savepoints are not supported in this phase.
- A single `/v1/api/sql` request can contain multiple sequential `BEGIN ... COMMIT` or `BEGIN ... ROLLBACK` blocks.
- If a `/v1/api/sql` request ends with an open transaction, KalamDB rolls it back automatically.
To inspect live transaction state, query `system.transactions`. For pg-extension-specific session
state, use `system.sessions`. See
[/docs/server/sql-reference/system-views](https://kalamdb.org/docs/server/sql-reference/system-views).
For deeper read-query patterns (joins, CTEs, and DataFusion-compatible advanced SELECT usage), see
[/docs/server/sql-reference/query-data](https://kalamdb.org/docs/server/sql-reference/query-data).
## Related Architecture
- [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types)
- [/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers)
- [/docs/server/architecture/live-query](https://kalamdb.org/docs/server/architecture/live-query)
================================================================================
# FILE Datatype
URL: https://kalamdb.org/docs/server/sql-reference/file-datatype
Source: content/server/sql-reference/file-datatype.mdx
> How to define FILE columns in KalamDB tables, upload files with the FILE() function, and read or download stored file references using SQL and the HTTP API.
# FILE Datatype
KalamDB supports a `FILE` datatype for storing file references in table rows while the binary content is managed by the file service.
Use this page to understand SQL usage. For architecture and storage-path layout, see
[/docs/server/architecture/file-upload-datatype](https://kalamdb.org/docs/server/architecture/file-upload-datatype).
## Define FILE Columns
Create a table with a `FILE` column:
```sql
CREATE TABLE app.documents (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
title TEXT NOT NULL,
attachment FILE,
created_at TIMESTAMP DEFAULT NOW()
) WITH (TYPE = 'USER');
```
`FILE` columns are supported on `USER` and `SHARED` tables.
## Insert with FILE(...)
Use `FILE("placeholder")` in SQL and send multipart form parts named `file:`.
```sql
INSERT INTO app.documents (id, title, attachment)
VALUES (1001, 'Contract', FILE("contract"));
```
Multipart body parts:
- `sql`
- `params` (optional JSON array string)
- `namespace_id` (optional)
- `file:contract` (binary part for `FILE("contract")`)
Backend behavior (API + file utils):
- SQL multipart parser enforces file limits (`max_files_per_request`, `max_size_bytes`).
- Placeholder names in `FILE("name")` must match multipart keys `file:name`.
- On SQL failure after staging/finalization, cleanup routines attempt to remove uploaded files.
## Query FILE Columns
Querying returns file-reference metadata in the row.
```sql
SELECT id, title, attachment
FROM app.documents
WHERE id = 1001;
```
In SDKs, parse the returned file reference and bind table context before downloading:
- **TypeScript:** `queryRows(..., 'app.documents')` then `row.file('attachment')` for a `BoundFileRef` with no-arg `downloadUrl()`
- **Rust:** `TableId::from_strings("app", "documents")` then `cell.as_bound_file(&table_id)` and `download_bound_file()`
- **Dart:** `KalamFileRef.getDownloadUrl(...)` with explicit namespace and table (see [Dart SDK querying](https://kalamdb.org/docs/dart-sdk/querying#file-columns))
## Download Files
Use the file endpoint:
`GET /v1/files/{namespace}/{table_name}/{subfolder}/{file_id}`
Notes:
- Bearer token is required.
- `SYSTEM` and `STREAM` tables are rejected for file paths.
- `user_id` may be required for user-table scope resolution.
Path model summary:
- `USER` table downloads resolve using user scope (`user_id` effective context).
- `SHARED` table downloads resolve without user scope.
- Effective folder layout is controlled by storage templates (see architecture page).
## Best Practices
- Use `USER` tables for tenant-private files.
- Use `SHARED` tables for global/shared assets.
- Store descriptive metadata in additional columns (e.g., `mime`, `label`, `tags`).
- Fetch only required columns where possible (for example `SELECT id, attachment`).
## Related Docs
- [/docs/server/api/http-reference](https://kalamdb.org/docs/server/api/http-reference)
- [/docs/ts-sdk/file-columns](https://kalamdb.org/docs/ts-sdk/file-columns)
- [/docs/rust-sdk/file-columns](https://kalamdb.org/docs/rust-sdk/file-columns)
- [/docs/server/architecture/file-upload-datatype](https://kalamdb.org/docs/server/architecture/file-upload-datatype)
- [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types)
================================================================================
# Built-in Functions
URL: https://kalamdb.org/docs/server/sql-reference/functions
Source: content/server/sql-reference/functions.mdx
> KalamDB built-in SQL functions plus DataFusion-compatible scalar, aggregate, window, and PostgreSQL-style JSON query support. Includes SNOWFLAKE_ID, NOW, COSINE_DISTANCE, and more.
# Built-in Functions
KalamDB provides built-in functions for ID generation, user context, and timestamps.
KalamDB query execution also supports DataFusion SQL functions. For advanced query-time function usage, see:
- [DataFusion SQL Functions Overview](https://datafusion.apache.org/user-guide/sql/index.html)
- [DataFusion Scalar Functions](https://datafusion.apache.org/user-guide/sql/scalar_functions.html)
- [DataFusion Aggregate Functions](https://datafusion.apache.org/user-guide/sql/aggregate_functions.html)
- [DataFusion Window Functions](https://datafusion.apache.org/user-guide/sql/window_functions.html)
The functions on this page are KalamDB-specific functions you will use most often.
## ID Generation
### SNOWFLAKE_ID()
Generate a unique 64-bit snowflake ID (time-ordered, globally unique):
```sql
SELECT SNOWFLAKE_ID();
-- Returns: 7302451094528000001
```
Commonly used as the default for primary keys:
```sql
CREATE TABLE app.messages (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
content TEXT NOT NULL
);
```
### UUID_V7()
Generate a UUIDv7 (time-ordered UUID):
```sql
SELECT UUID_V7();
-- Returns: 0193a5e0-7a1b-7000-8000-000000000001
```
### ULID()
Generate a ULID (Universally Unique Lexicographically Sortable Identifier):
```sql
SELECT ULID();
-- Returns: 01HQJK5M5R3YJQWXHN4QWXYN01
```
## Context Functions
### CURRENT_USER()
Returns the username of the currently authenticated user:
```sql
SELECT CURRENT_USER();
-- Returns: 'alice'
```
Useful for audit columns:
```sql
CREATE TABLE app.audit_log (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
action TEXT NOT NULL,
performed_by TEXT DEFAULT CURRENT_USER(),
created_at TIMESTAMP DEFAULT NOW()
);
```
## Date & Time
### NOW()
Returns the current timestamp:
```sql
SELECT NOW();
-- Returns: 2026-02-18T10:30:00.000Z
```
Commonly used as a default for timestamp columns:
```sql
CREATE TABLE app.events (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
event_type TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
```
## Vector Search
### COSINE_DISTANCE()
Use `COSINE_DISTANCE(embedding_column, '[...]')` to rank rows by vector similarity.
This is the primary KalamDB pattern for `EMBEDDING(n)` columns: pass the stored column
as the first argument and a JSON array literal as the query vector.
```sql
SELECT id, title
FROM rag.documents_vectors
ORDER BY COSINE_DISTANCE(doc_embedding, '[1.0,0.0,0.0]')
LIMIT 5;
```
Typical flow:
1. Store embeddings in an `EMBEDDING(n)` column.
2. Create a cosine index with `ALTER TABLE ... CREATE INDEX ... USING COSINE`.
3. Use `ORDER BY COSINE_DISTANCE(...) LIMIT k` to fetch nearest matches.
Notes:
- The query vector must match the column dimension.
- Lower distance means a closer match.
- JSON query literals (for example `'[1.0,0.0,0.0]'`) use KalamDB's vector-search path and return `FLOAT`.
- When both arguments are array/list values, KalamDB delegates to DataFusion 54's native `cosine_distance` and returns `DOUBLE`.
- This is the main SQL entry point for vector search in KalamDB today.
## JSON Operators and Functions
KalamDB registers DataFusion's JSON function package in every SQL session.
That gives JSON columns the common PostgreSQL-style query syntax most users expect for extraction and existence checks.
```sql
SELECT
doc->'profile' AS profile_json,
doc->>'status' AS status_text,
doc->'items'->0 AS first_item,
doc ? 'customer_id' AS has_customer_id
FROM app.orders
WHERE doc->>'status' = 'paid';
```
Supported PostgreSQL-style operators in KalamDB SQL:
| Syntax | Equivalent helper | Result |
|---|---|---|
| `json_col -> 'key'` or `json_col -> 0` | `json_get(...)` | JSON value |
| `json_col ->> 'key'` or `json_col ->> 0` | `json_as_text(...)` | text |
| `json_col ? 'key'` | `json_contains(...)` | `BOOLEAN` |
Available JSON helpers from the registered DataFusion package:
| Function | Use |
|---|---|
| `json_get` | Return a JSON value at a path |
| `json_as_text` | Return a value at a path as text |
| `json_get_json` | Return nested JSON as raw JSON text |
| `json_get_str` | Return a string value |
| `json_get_int` | Return an integer value |
| `json_get_float` | Return a floating-point value |
| `json_get_bool` | Return a boolean value |
| `json_get_array` | Return an array value |
| `json_contains` | Test whether a key or index exists |
| `json_length` | Return the length of an array or object |
| `json_object_keys` | Return the keys of a JSON object as an array |
| `json_from_scalar` | Convert a scalar into a JSON value for composed expressions |
Scope note:
- KalamDB plans PostgreSQL-style `->`, `->>`, and `?` operators natively through DataFusion 54 and `datafusion-functions-json`. No SQL rewrite pass is applied for these operators.
- Do not assume full PostgreSQL `jsonb` operator parity for operators such as `#>`, `#>>`, or `@>` yet.
## DataFusion 54 Query Extensions
KalamDB runs on DataFusion 54 and enables additional query-time SQL beyond the KalamDB-specific functions above.
### Lambda array functions
Use SQL lambda syntax with higher-order array functions:
```sql
SELECT array_transform([1, 2, 3, 4, 5], x -> x * 10) AS scaled;
SELECT array_transform(
array_filter([1, 2, 3, 4, 5], x -> x > 2),
x -> x * 10
) AS filtered_scaled;
SELECT array_any_match([1, 2, 3], x -> x > 2) AS has_large;
```
These work in general `SELECT` queries. They are not supported in `SUBSCRIBE TO` / live-query SQL, which keeps a narrower surface.
### Lateral joins
DataFusion 54 adds basic lateral join support for correlated subqueries in the `FROM` clause:
```sql
SELECT u.id, expanded.value
FROM app.users AS u
CROSS JOIN LATERAL (
SELECT array_transform(u.tags, t -> upper(t)) AS value
) AS expanded;
```
### Comparison and subquery semantics
DataFusion 54 also changes a few comparison behaviors worth knowing when porting SQL:
- Numeric columns compared to string literals (for example `amount > '100'`) are coerced numerically, not lexicographically.
- Uncorrelated scalar subqueries that return more than one row fail at execution with a cardinality error.
For the full upstream function catalog, use the [DataFusion SQL docs](https://datafusion.apache.org/user-guide/sql/index.html).
## Function Summary
| Function | Return Type | Description |
|----------|-------------|-------------|
| `SNOWFLAKE_ID()` | `BIGINT` | Time-ordered 64-bit unique ID |
| `UUID_V7()` | `TEXT` | Time-ordered UUID v7 |
| `ULID()` | `TEXT` | Lexicographically sortable unique ID |
| `CURRENT_USER()` | `TEXT` | Authenticated username |
| `NOW()` | `TIMESTAMP` | Current server timestamp |
| `COSINE_DISTANCE(vector, query)` | `FLOAT` or `DOUBLE` | Vector similarity distance. JSON query literals return `FLOAT`; array/array inputs use native DataFusion 54 and return `DOUBLE`. |
## DataFusion Function Support
KalamDB supports DataFusion SQL function execution for SELECT/query workloads, including scalar, aggregate, and window functions documented in DataFusion.
If you need advanced expression behavior beyond KalamDB-specific built-ins, use the DataFusion function references linked above as the canonical guide.
For end-to-end schema and index examples, see
[/docs/server/architecture/vector-search](https://kalamdb.org/docs/server/architecture/vector-search).
================================================================================
# Execute As
URL: https://kalamdb.org/docs/server/sql-reference/impersonation
Source: content/server/sql-reference/impersonation.mdx
> Use KalamDB EXECUTE AS '' wrapper syntax with the role-based delegation matrix.
# Execute As
`EXECUTE AS` is wrapper syntax for a single SQL statement. It switches USER-table or STREAM-table execution to a resolved target user ID only when the authenticated actor role is allowed to target that user's role.
## EXECUTE AS
```sql
EXECUTE AS '' (
);
```
### Rules
1. The wrapper must contain **exactly one** SQL statement
2. The target user ID must be **single-quoted**
3. System users may target system, DBA, service, and user accounts
4. DBA users may target DBA, service, and user accounts
5. Service users may target service and user accounts
6. Regular users may only target themselves
7. The wrapper is valid for USER and STREAM tables; shared tables use their table policy directly
8. Legacy inline `... AS USER 'name'` syntax is **not supported**
### Examples
**Authorized delegated query:**
```sql
EXECUTE AS 'user_123' (
SELECT * FROM app.messages WHERE conversation_id = 42
);
```
**Authorized delegated insert:**
```sql
EXECUTE AS 'user_123' (
INSERT INTO app.messages (conversation_id, sender, role, content)
VALUES (42, 'user_123', 'assistant', 'Processing complete')
);
```
**Authorized delegated delete:**
```sql
EXECUTE AS 'user_123' (
DELETE FROM app.messages WHERE id = 123
);
```
## Use Cases
| Scenario | Description |
|----------|-------------|
| **Service workers** | Write rows into a user's USER-table or STREAM-table partition through an explicit delegation boundary |
| **Testing** | Assert allowed and denied role-matrix edges consistently |
| **Audit trails** | Record the actor and target user for delegated operations |
================================================================================
# SQL Reference
URL: https://kalamdb.org/docs/server/sql-reference
Source: content/server/sql-reference/index.mdx
> Complete SQL syntax reference for KalamDB. Learn how to manage namespaces, tables, DML, realtime subscriptions, users, and storage.
# SQL Reference
KalamDB is SQL-first with additional commands for realtime subscriptions, storage lifecycle, topics, and cluster operations.
This chapter is organized by command category so you can jump directly to implementation details.
For the architecture behind common SQL choices, start with
[/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types),
[/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers), and
[/docs/server/architecture/live-query](https://kalamdb.org/docs/server/architecture/live-query).
## SQL Categories
| Category | Description |
|----------|-------------|
| [/docs/server/sql-reference/namespaces](https://kalamdb.org/docs/server/sql-reference/namespaces) | create/drop/select namespace context |
| [/docs/server/sql-reference/tables](https://kalamdb.org/docs/server/sql-reference/tables) | create/alter/drop tables and views |
| [/docs/server/sql-reference/dml](https://kalamdb.org/docs/server/sql-reference/dml) | `INSERT`, `UPDATE`, `DELETE`, `SELECT`, upsert (`ON CONFLICT DO UPDATE`), `RETURNING` |
| [/docs/server/sql-reference/query-data](https://kalamdb.org/docs/server/sql-reference/query-data) | query patterns, joins, CTEs, and DataFusion-compatible SELECT usage |
| [/docs/server/sql-reference/diagnostics](https://kalamdb.org/docs/server/sql-reference/diagnostics) | `DESCRIBE` / `DESC` schema inspection and `EXPLAIN` / `ANALYZE` / `VERBOSE` query plans |
| [/docs/server/sql-reference/system-views](https://kalamdb.org/docs/server/sql-reference/system-views) | `system.*` observability views including `system.sessions` and `system.transactions` |
| [/docs/server/sql-reference/subscriptions](https://kalamdb.org/docs/server/sql-reference/subscriptions) | `SUBSCRIBE TO`, `KILL LIVE QUERY` |
| [/docs/server/sql-reference/topic-pubsub](https://kalamdb.org/docs/server/sql-reference/topic-pubsub) | topic pub/sub SQL syntax for `CREATE TOPIC`, `CONSUME`, `ACK`, and consumer groups |
| [/docs/server/sql-reference/users](https://kalamdb.org/docs/server/sql-reference/users) | create/alter/drop users and roles |
| [/docs/server/sql-reference/storage](https://kalamdb.org/docs/server/sql-reference/storage) | storage backends, flush, compact, manifests |
| [/docs/server/sql-reference/storage-id-usage](https://kalamdb.org/docs/server/sql-reference/storage-id-usage) | table-to-storage mapping patterns |
| [/docs/server/sql-reference/cluster](https://kalamdb.org/docs/server/sql-reference/cluster) | raft and node operations |
| [/docs/server/sql-reference/backup](https://kalamdb.org/docs/server/sql-reference/backup) | namespace backup/recovery commands |
| [/docs/server/sql-reference/impersonation](https://kalamdb.org/docs/server/sql-reference/impersonation) | `EXECUTE AS ''` wrapper syntax |
| [/docs/server/sql-reference/functions](https://kalamdb.org/docs/server/sql-reference/functions) | KalamDB built-ins plus DataFusion SQL function support |
## Statement Separator
```sql
SELECT 1;
SELECT 2;
```
## Quick Example
```sql
CREATE NAMESPACE IF NOT EXISTS chat;
CREATE TABLE chat.messages (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
sender TEXT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
) WITH (TYPE = 'USER', FLUSH_POLICY = 'rows:1000');
INSERT INTO chat.messages (sender, content)
VALUES ('alice', 'Hello world!');
SELECT * FROM chat.messages ORDER BY created_at DESC LIMIT 20;
```
================================================================================
# Namespaces
URL: https://kalamdb.org/docs/server/sql-reference/namespaces
Source: content/server/sql-reference/namespaces.mdx
> SQL commands for managing KalamDB namespaces. Learn how to create, drop, list, and select namespace context for organizing tables and data.
# Namespace Commands
Namespaces provide logical grouping for tables, similar to schemas in other databases.
## CREATE NAMESPACE
```sql
CREATE NAMESPACE ;
CREATE NAMESPACE IF NOT EXISTS ;
```
**Example:**
```sql
CREATE NAMESPACE IF NOT EXISTS chat;
CREATE NAMESPACE IF NOT EXISTS analytics;
```
## DROP NAMESPACE
```sql
DROP NAMESPACE ;
DROP NAMESPACE IF EXISTS ;
DROP NAMESPACE CASCADE;
DROP NAMESPACE IF EXISTS CASCADE;
```
The `CASCADE` option drops all tables within the namespace.
**Example:**
```sql
-- Drop only if empty
DROP NAMESPACE analytics;
-- Drop with all contained tables
DROP NAMESPACE IF EXISTS analytics CASCADE;
```
## ALTER NAMESPACE
```sql
ALTER NAMESPACE
SET DESCRIPTION '';
```
**Example:**
```sql
ALTER NAMESPACE chat
SET DESCRIPTION 'Chat application tables';
```
## USE / SET NAMESPACE
Set the default namespace for the current request or multi-statement batch:
```sql
USE ;
USE NAMESPACE ;
SET NAMESPACE ;
```
In the interactive CLI, a successful `USE` also updates the CLI's local
namespace so later requests automatically send `namespace_id`.
**Example:**
```sql
USE chat;
-- Now you can reference tables without the namespace prefix
SELECT * FROM messages;
```
## SHOW NAMESPACES
List all available namespaces:
```sql
SHOW NAMESPACES;
```
================================================================================
# Query Data (SELECT & JOIN)
URL: https://kalamdb.org/docs/server/sql-reference/query-data
Source: content/server/sql-reference/query-data.mdx
> How to read data in KalamDB with SELECT queries, WHERE filters, GROUP BY, JOINs, CTEs, PostgreSQL-style JSON queries, and DataFusion-compatible advanced query patterns.
# Query Data (SELECT & JOIN)
Use this page for query-only SQL patterns in KalamDB.
KalamDB supports the DataFusion SELECT query surface. For advanced query syntax and planner behavior, use the DataFusion SQL docs as the primary reference:
- [https://datafusion.apache.org/user-guide/sql/select.html](https://datafusion.apache.org/user-guide/sql/select.html)
- [https://datafusion.apache.org/user-guide/sql/index.html](https://datafusion.apache.org/user-guide/sql/index.html)
The visible rows and join targets still follow KalamDB table-type authorization. `USER` and `STREAM`
tables are scoped by effective user, `SHARED` tables use `ACCESS_LEVEL`, and `system.*` access is
restricted. See [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types).
## DataFusion Statement Scope
KalamDB uses DataFusion as the engine for query-time SQL, especially read/query workloads:
- `SELECT` and projection/filter/order/limit patterns
- joins (`INNER`, `LEFT`, `RIGHT`, `FULL`, `CROSS`, `USING`)
- subqueries and `WITH` (CTE)
- set operations such as `UNION` / `UNION ALL`
- scalar/aggregate/window function execution in query plans
KalamDB-specific operational SQL surfaces are implemented by KalamDB handlers instead of raw DataFusion SQL semantics, including:
- DDL (`CREATE/ALTER/DROP` namespaces, tables, users, storage, topics)
- topic/consumer commands (`CREATE TOPIC`, `ALTER TOPIC`, `CONSUME`, `ACK`, `RESET CONSUMER GROUP`)
- live-query controls (`SUBSCRIBE TO`, `KILL LIVE QUERY`)
## Basic SELECT
```sql
SELECT
FROM [.]
[WHERE ]
[ORDER BY [ASC|DESC]]
[LIMIT ];
```
Example:
```sql
SELECT id, sender, content, created_at
FROM chat.messages
WHERE conversation_id = 42
ORDER BY created_at DESC
LIMIT 50;
```
## Filtering, Grouping, and Aggregation
```sql
SELECT sender, COUNT(*) AS message_count
FROM chat.messages
WHERE conversation_id = 42
GROUP BY sender
HAVING COUNT(*) >= 10
ORDER BY message_count DESC;
```
## JOIN Support
KalamDB supports standard DataFusion join patterns, including:
- `INNER JOIN`
- `LEFT JOIN`
- `RIGHT JOIN`
- `FULL JOIN`
- `CROSS JOIN`
- `JOIN ... USING (...)`
Example (`INNER JOIN`):
```sql
SELECT m.id, m.content, u.email
FROM chat.messages m
INNER JOIN system.users u ON m.user_id = u.user_id
WHERE m.conversation_id = 42;
```
Example (`LEFT JOIN`):
```sql
SELECT c.id, c.title, m.content
FROM chat.conversations c
LEFT JOIN chat.messages m ON c.id = m.conversation_id
ORDER BY c.id;
```
## Subqueries and CTEs
Common DataFusion query constructs are supported, including subqueries and CTEs.
```sql
WITH ranked AS (
SELECT
sender,
COUNT(*) AS total_messages
FROM chat.messages
GROUP BY sender
)
SELECT *
FROM ranked
WHERE total_messages >= 100
ORDER BY total_messages DESC;
```
## Set Operations
You can use DataFusion set operations such as `UNION` and `UNION ALL`.
```sql
SELECT sender FROM chat.messages_2025
UNION ALL
SELECT sender FROM chat.messages_2026;
```
## PostgreSQL-style JSON Queries
`JSON` and `JSONB` columns can be queried with the same common extraction syntax PostgreSQL users already know.
```sql
SELECT
doc->>'status' AS status,
doc->'items'->0 AS first_item
FROM app.orders
WHERE doc->>'status' = 'paid'
AND doc ? 'customer_id';
```
Use `->` when you want a JSON value, `->>` when you want text, and `?` when you only need to test key existence.
KalamDB also exposes DataFusion JSON helpers such as `json_get`, `json_as_text`, `json_length`, and `json_object_keys` for function-style access.
For the full JSON function list and current operator coverage, see
[/docs/server/sql-reference/functions](https://kalamdb.org/docs/server/sql-reference/functions).
## DataFusion 54 Query Features
KalamDB 0.5.x runs on DataFusion 54. In addition to the core `SELECT` / `JOIN` / CTE surface above, general SQL queries can use:
- **Lambda array functions** such as `array_transform`, `array_filter`, and `array_any_match` with `x -> expr` syntax
- **Lateral joins** such as `CROSS JOIN LATERAL`, `INNER JOIN LATERAL`, and `LEFT JOIN LATERAL`
- **Native JSON operators** (`->`, `->>`, `?`) planned directly by DataFusion without a KalamDB rewrite pass
Subscription and live-query SQL intentionally keeps a narrower surface. Advanced DataFusion 54 features such as lambda arrays, lateral joins, `GROUP BY`, and joins are not available in `SUBSCRIBE TO` statements. See [/docs/server/sql-reference/subscriptions](https://kalamdb.org/docs/server/sql-reference/subscriptions).
When porting SQL from other engines, note that DataFusion 54 compares numeric columns to string literals numerically (for example `5 > '100'` is false) and rejects uncorrelated scalar subqueries that return multiple rows.
## Advanced Query Reference
For advanced SELECT features, rely on DataFusion SQL documentation:
- [https://datafusion.apache.org/user-guide/sql/select.html](https://datafusion.apache.org/user-guide/sql/select.html)
- [https://datafusion.apache.org/user-guide/sql/scalar_functions.html](https://datafusion.apache.org/user-guide/sql/scalar_functions.html)
- [https://datafusion.apache.org/user-guide/sql/index.html](https://datafusion.apache.org/user-guide/sql/index.html)
## Related Architecture
- [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types)
- [/docs/server/architecture/datatypes](https://kalamdb.org/docs/server/architecture/datatypes)
- [/docs/server/architecture/vector-search](https://kalamdb.org/docs/server/architecture/vector-search)
================================================================================
# Storage ID Usage
URL: https://kalamdb.org/docs/server/sql-reference/storage-id-usage
Source: content/server/sql-reference/storage-id-usage.mdx
> How to register storage backends and assign STORAGE_ID in CREATE TABLE for shared and user tables. Includes S3, local, and multi-backend configuration.
# Storage ID Usage
Use this guide when you want to control where table data is stored, pick storage per table, and understand user-table routing behavior.
For MinIO-specific S3-compatible setup, see
[/docs/server/integrations/minio](https://kalamdb.org/docs/server/integrations/minio). For the storage architecture,
see [/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers),
[/docs/server/architecture/manifests](https://kalamdb.org/docs/server/architecture/manifests), and
[/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types).
## Create storage backends first
Before using `STORAGE_ID` in `CREATE TABLE`, the storage must exist in `system.storages`.
### Filesystem storage
```sql
CREATE STORAGE local_archive
TYPE filesystem
NAME 'Local Archive'
DESCRIPTION 'Filesystem storage for archived tables'
PATH './data/storage/local-archive'
SHARED_TABLES_TEMPLATE 'shared/{namespace}/{tableName}'
USER_TABLES_TEMPLATE 'users/{namespace}/{tableName}/{userId}';
```
`PATH` is supported for filesystem storage. `BASE_DIRECTORY` is also accepted.
### S3 storage
```sql
CREATE STORAGE s3_prod
TYPE s3
NAME 'Production S3'
DESCRIPTION 'S3 bucket for production data'
BUCKET 'my-kalamdb-prod-bucket'
REGION 'us-east-1'
SHARED_TABLES_TEMPLATE 'shared/{namespace}/{tableName}'
USER_TABLES_TEMPLATE 'users/{namespace}/{tableName}/{userId}';
```
For S3, you can use either:
- `BUCKET 'bucket-name'` (optionally with `REGION`)
- `BASE_DIRECTORY 's3://bucket/prefix'`
Useful checks:
```sql
SHOW STORAGES;
SELECT storage_id, storage_type, storage_name FROM system.storages;
```
## Use STORAGE_ID in CREATE TABLE
KalamDB accepts `STORAGE_ID` in `WITH (...)` table options.
### Shared table with explicit storage
```sql
CREATE TABLE app.config (
key TEXT PRIMARY KEY,
value TEXT
) WITH (
TYPE = 'SHARED',
STORAGE_ID = 's3_prod'
);
```
### User table with explicit storage and flush policy
```sql
CREATE TABLE app.messages (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
content TEXT,
created_at TIMESTAMP DEFAULT NOW()
) WITH (
TYPE = 'USER',
STORAGE_ID = 'local_archive',
FLUSH_POLICY = 'rows:5000'
);
```
### If STORAGE_ID is omitted
If omitted, KalamDB resolves storage to `local` by default.
```sql
CREATE TABLE app.metrics (
id BIGINT PRIMARY KEY,
value DOUBLE
) WITH (TYPE = 'SHARED');
```
## Verify table storage metadata
Inspect table metadata in `system.schemas`:
```sql
SELECT namespace_id, table_name, table_type, storage_id, use_user_storage
FROM system.schemas
WHERE namespace_id = 'app' AND is_latest = true
ORDER BY table_name;
```
## Change table storage metadata
For `USER` and `SHARED` tables, admins can update `STORAGE_ID` with `ALTER TABLE ... SET TBLPROPERTIES`:
```sql
ALTER TABLE app.messages
SET TBLPROPERTIES (STORAGE_ID = 's3_prod');
```
The new storage ID must already exist in `system.storages`.
## Per-user storage routing (USE_USER_STORAGE)
For `USER` tables, you can enable per-user storage routing metadata:
```sql
CREATE TABLE app.geo_events (
event_id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
payload JSON,
created_at TIMESTAMP DEFAULT NOW()
) WITH (
TYPE = 'USER',
STORAGE_ID = 's3_prod',
USE_USER_STORAGE = true
);
```
Rules:
- `USE_USER_STORAGE` is valid only for `TYPE = 'USER'`
- `STORAGE_ID` remains fallback storage for the table
- User preference fields exist in `system.users` (`storage_mode`, `storage_id`)
You can also toggle it after creation:
```sql
ALTER TABLE app.geo_events
SET TBLPROPERTIES (USE_USER_STORAGE = false);
```
## Changing storage per user (current status)
Current SQL behavior:
- DML against `system.*` tables is blocked
- `ALTER USER` currently supports only `SET PASSWORD`, `SET ROLE`, and `SET EMAIL`
So there is no public SQL command yet to directly update a user's `storage_mode` or `storage_id`.
Current developer workflow:
- Set table-level routing with `STORAGE_ID` (optionally `USE_USER_STORAGE`)
- For user-level overrides, use internal/admin backend flows
## Related Architecture
- [/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers)
- [/docs/server/architecture/manifests](https://kalamdb.org/docs/server/architecture/manifests)
- [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types)
## Common failures
### Unknown storage ID
```sql
CREATE TABLE app.bad_example (
id INT PRIMARY KEY
) WITH (TYPE = 'SHARED', STORAGE_ID = 'does_not_exist');
```
Expected error pattern:
```text
Storage 'does_not_exist' does not exist
```
### Invalid USE_USER_STORAGE on non-user table
```sql
CREATE TABLE app.bad_shared (
id INT PRIMARY KEY
) WITH (TYPE = 'SHARED', USE_USER_STORAGE = true);
```
Expected error pattern:
```text
USE_USER_STORAGE is only supported for USER tables
```
================================================================================
# Storage Commands
URL: https://kalamdb.org/docs/server/sql-reference/storage
Source: content/server/sql-reference/storage.mdx
> Managing storage backends in KalamDB. Learn commands for registering storage, flushing hot data to cold tier, compacting Parquet files, and manifests.
# Storage Commands
KalamDB supports multiple storage backends for cold-tier data. These commands manage storage configuration, health checks, and maintenance.
For practical table-level storage selection (`STORAGE_ID`, `USE_USER_STORAGE`, and verification
queries), see [/docs/server/sql-reference/storage-id-usage](https://kalamdb.org/docs/server/sql-reference/storage-id-usage).
For S3-compatible local deployment setup, see
[/docs/server/integrations/minio](https://kalamdb.org/docs/server/integrations/minio).
For the underlying hot/cold architecture and manifest state machine, see
[/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers) and
[/docs/server/architecture/manifests](https://kalamdb.org/docs/server/architecture/manifests).
## CREATE STORAGE
```sql
CREATE STORAGE
TYPE ''
[NAME '']
[DESCRIPTION '']
[PATH '']
[BUCKET '']
[REGION '']
[BASE_DIRECTORY '']
[SHARED_TABLES_TEMPLATE '']
[USER_TABLES_TEMPLATE '']
[CREDENTIALS '']
[CONFIG ''];
```
### Examples
**Local filesystem:**
```sql
CREATE STORAGE local
TYPE 'filesystem'
PATH './data';
```
**Amazon S3:**
```sql
CREATE STORAGE s3_prod
TYPE 's3'
BUCKET 'my-bucket'
REGION 'us-west-2'
CREDENTIALS '{"access_key_id":"...","secret_access_key":"..."}';
```
**Google Cloud Storage:**
```sql
CREATE STORAGE gcs_prod
TYPE 'gcs'
BUCKET 'my-gcs-bucket'
CREDENTIALS '{"service_account_key":"..."}';
```
**Azure Blob Storage:**
```sql
CREATE STORAGE azure_prod
TYPE 'azure'
BUCKET 'my-container'
CREDENTIALS '{"account_name":"...","account_key":"..."}';
```
### Storage Type Aliases
| SQL Type | Alias | Base Directory Prefix |
|---|---|---|
| `filesystem` | — | Local path (e.g., `./data/storage`) |
| `s3` | — | `s3://bucket/prefix` |
| `gcs` | `gs` | `gs://bucket/prefix` |
| `azure` | `az` | `az://container/prefix` |
### CONFIG JSON Fields by Backend
#### S3 CONFIG
```json
{
"type": "s3",
"region": "us-east-1",
"endpoint": "http://127.0.0.1:9120",
"allow_http": true,
"access_key_id": "...",
"secret_access_key": "...",
"session_token": "..."
}
```
| Field | Type | Default | Notes |
|---|---|---|---|
| `region` | string | `"us-east-1"` | AWS region or custom region for S3-compatible |
| `endpoint` | string | `null` | Custom endpoint for MinIO / S3-compatible |
| `allow_http` | boolean | `false` | Must be `true` for non-TLS endpoints |
| `access_key_id` | string | `null` | Static credentials (omit for IAM roles) |
| `secret_access_key` | string | `null` | Static credentials |
| `session_token` | string | `null` | Temporary session credentials |
For S3-compatible local setup, see [/docs/server/integrations/minio](https://kalamdb.org/docs/server/integrations/minio).
#### GCS CONFIG
```json
{
"type": "gcs",
"service_account_json": "{...}"
}
```
| Field | Type | Default | Notes |
|---|---|---|---|
| `service_account_json` | string | `null` | Service account JSON key. Omit to use Application Default Credentials (ADC). |
#### Azure CONFIG
```json
{
"type": "azure",
"account_name": "mystorageaccount",
"access_key": "...",
"sas_token": "..."
}
```
| Field | Type | Default | Notes |
|---|---|---|---|
| `account_name` | string | `null` | Azure storage account name |
| `access_key` | string | `null` | Account access key |
| `sas_token` | string | `null` | Shared Access Signature token (alternative to access_key) |
#### Local/Filesystem CONFIG
```json
{
"type": "local",
"root": "./data/storage"
}
```
| Field | Type | Default | Notes |
|---|---|---|---|
| `root` | string | `null` | Informational; actual path comes from `BASE_DIRECTORY` or `PATH`. |
## ALTER STORAGE
```sql
ALTER STORAGE
[SET NAME '']
[SET DESCRIPTION '']
[SET SHARED_TABLES_TEMPLATE '']
[SET USER_TABLES_TEMPLATE '']
[SET CONFIG ''];
```
## DROP STORAGE
```sql
DROP STORAGE ;
DROP STORAGE IF EXISTS ;
```
## SHOW STORAGES
```sql
SHOW STORAGES;
```
## STORAGE CHECK
Verify storage backend health:
```sql
STORAGE CHECK ;
STORAGE CHECK EXTENDED;
```
## STORAGE FLUSH
Force flushing data from hot tier (RocksDB) to cold tier (Parquet):
```sql
-- Flush a specific table
STORAGE FLUSH TABLE .;
-- Flush all tables in a namespace
STORAGE FLUSH ALL IN ;
STORAGE FLUSH ALL IN NAMESPACE ;
-- Flush everything
STORAGE FLUSH ALL;
```
## STORAGE COMPACT
Merge and optimize cold-tier Parquet segments:
```sql
-- Compact a specific table
STORAGE COMPACT TABLE .;
-- Compact all tables in a namespace
STORAGE COMPACT ALL IN ;
STORAGE COMPACT ALL IN NAMESPACE ;
-- Compact everything
STORAGE COMPACT ALL;
```
## SHOW MANIFEST
Display the storage manifest (schema + segment index):
```sql
SHOW MANIFEST;
```
## Related Architecture
- [/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers)
- [/docs/server/architecture/manifests](https://kalamdb.org/docs/server/architecture/manifests)
- [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types)
================================================================================
# Subscriptions & Live Queries
URL: https://kalamdb.org/docs/server/sql-reference/subscriptions
Source: content/server/sql-reference/subscriptions.mdx
> Real-time SQL subscriptions over WebSocket in KalamDB. Learn SUBSCRIBE TO syntax, live query lifecycle, KILL LIVE QUERY, and event streaming patterns.
# Subscriptions & Live Queries
KalamDB's real-time engine pushes data changes to connected clients over WebSocket. This eliminates the need for polling and separate pub/sub infrastructure.
For the runtime architecture, table-type delivery behavior, and cluster fan-out path, see
[/docs/server/architecture/live-query](https://kalamdb.org/docs/server/architecture/live-query).
## SUBSCRIBE TO
Subscribe to a table for real-time change notifications:
```sql
SUBSCRIBE TO .
[WHERE ]
[OPTIONS (last_rows=, batch_size=, from=)];
```
### Options
| Option | Description |
|--------|-------------|
| `last_rows=` | Receive the last N rows as initial data |
| `batch_size=` | Maximum rows per notification batch |
| `from=` | Resume from a specific sequence ID |
### Examples
```sql
-- Subscribe to all messages in a conversation
SUBSCRIBE TO chat.messages
WHERE conversation_id = 1
OPTIONS (last_rows=50);
-- Subscribe to typing events with batching
SUBSCRIBE TO chat.typing_events
WHERE conversation_id = 1
OPTIONS (last_rows=10, batch_size=5);
-- Resume from a specific point
SUBSCRIBE TO chat.messages
WHERE conversation_id = 1
OPTIONS (from=1234);
```
## KILL LIVE QUERY
Cancel an active subscription:
```sql
KILL LIVE QUERY '';
```
## Using Subscriptions via SDK
The recommended way to use real-time subscriptions is via the TypeScript SDK:
```typescript
const client = createClient({
url: 'http://localhost:2900',
authProvider: async () => Auth.basic('admin', 'AdminPass123!')
});
// Open live events with a SQL WHERE clause
const unsub = await client.liveEvents(
'SELECT * FROM chat.messages WHERE conversation_id = 1',
(event) => {
if (event.type === 'change') {
console.log('New message:', event.rows);
}
},
{ batchSize: 50 }
);
// Simple table event stream
const unsubTyping = await client.liveEvents(
'SELECT * FROM chat.typing_events',
(event) => {
console.log('Typing:', event.change_type, event.rows);
}
);
// Check active subscription count
console.log(`Active: ${client.getSubscriptionCount()}`);
// Cleanup
await unsub();
await unsubTyping();
await client.disconnect();
```
## Using Subscriptions via CLI
In the interactive CLI, use `\live` or `\subscribe`:
```bash
kalam> \live SELECT * FROM chat.messages WHERE conversation_id = 1 OPTIONS (last_rows=20);
```
Both forms start the same live query flow. Press `Ctrl+C` to stop the subscription.
### Supported subscription SQL shape
Live subscriptions support a narrow SQL surface:
```sql
SELECT | *
FROM .
[WHERE ];
```
The following are **not** supported in `SUBSCRIBE TO` / live-query SQL today:
- `JOIN`, `GROUP BY`, `HAVING`, `ORDER BY`, `LIMIT`
- lateral joins and lambda array functions
- subqueries, CTEs, and set operations
Use regular `SELECT` queries for DataFusion 54 advanced features. Use subscriptions only for simple table watches with optional `WHERE` filters.
## How It Works
1. Client sends a `SUBSCRIBE TO` statement (via SQL, SDK, or WebSocket)
2. KalamDB registers the subscription with filter conditions
3. On every `INSERT`, `UPDATE`, or `DELETE` matching the filter, a change event is pushed
4. Events are delivered over the persistent WebSocket connection
5. Client can cancel with `KILL LIVE QUERY` or disconnect
### UPDATE notification payload
For UPDATE events, the notification includes:
- **`rows`**: All non-null columns from the updated row, plus the primary key and `_seq`. This provides a full snapshot of the current row state without requiring a re-query.
- **`old_values`**: Only the columns that actually changed (plus PK and `_seq`), with their previous values.
This design makes tables usable as **change triggers** — when any column is updated, subscribers receive every non-null value, not just the diff.
## Related Architecture
- [/docs/server/architecture/live-query](https://kalamdb.org/docs/server/architecture/live-query)
- [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types)
- [/docs/server/architecture/clustering](https://kalamdb.org/docs/server/architecture/clustering)
================================================================================
# System Views
URL: https://kalamdb.org/docs/server/sql-reference/system-views
Source: content/server/sql-reference/system-views.mdx
> Inspect KalamDB runtime state with system views, including system.sessions for pg bridge sessions and system.transactions for active explicit transactions.
# System Views
KalamDB exposes computed `system.*` views for metadata and runtime observability. This page focuses on the new session and transaction views added for explicit transaction support.
`system.*` access follows the `SYSTEM` table rules described in
[/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types#system-tables).
## `system.sessions`
`system.sessions` shows active PostgreSQL gRPC bridge sessions tracked by the server.
It is intentionally pg-session-focused:
- `pg_kalam` sessions appear here while they are active.
- Native `/v1/api/sql` requests do not create rows here.
- The CLI `\sessions` command runs this view directly.
### Common query
```sql
SELECT *
FROM system.sessions
ORDER BY last_seen_at DESC, session_id;
```
### Active-transaction query
```sql
SELECT session_id, transaction_id, transaction_state, transaction_has_writes
FROM system.sessions
WHERE transaction_id IS NOT NULL
ORDER BY last_seen_at DESC, session_id;
```
### Columns
| Column | Meaning |
|---|---|
| `session_id` | Server-side pg bridge session identifier. |
| `backend_pid` | Parsed PostgreSQL backend PID when the session id uses the `pg--...` form. |
| `current_schema` | Current schema reported by the pg extension session. |
| `state` | Session state, similar to PostgreSQL `pg_stat_activity` semantics. |
| `transaction_id` | Active remote transaction id, if one is open. |
| `transaction_state` | Current remote transaction lifecycle state. |
| `transaction_has_writes` | Whether the active transaction has staged writes. |
| `client_addr` | Observed client socket address for the gRPC session. |
| `transport` | Transport used by the session. |
| `opened_at` | When the server first observed the session. |
| `last_seen_at` | Most recent RPC activity timestamp. |
| `last_method` | Most recent gRPC method observed for the session. |
## `system.transactions`
`system.transactions` shows active explicit transactions across all server origins.
Use it to observe transaction state regardless of whether the transaction came from `pg_kalam` or native KalamDB SQL.
- Common `origin` values include `PgRpc` for the PostgreSQL extension and `SqlBatch` for native `/v1/api/sql` execution.
- Rows disappear immediately after `COMMIT`, `ROLLBACK`, timeout cleanup, or request-end cleanup.
### Common query
```sql
SELECT transaction_id, owner_id, origin, state, write_count
FROM system.transactions
ORDER BY origin, transaction_id;
```
### Capacity-oriented query
```sql
SELECT transaction_id, origin, state, age_ms, idle_ms, write_bytes, touched_tables_count
FROM system.transactions
ORDER BY age_ms DESC;
```
### Columns
| Column | Meaning |
|---|---|
| `transaction_id` | Canonical explicit transaction identifier. |
| `owner_id` | Human-readable owner identifier, such as a pg session id or request-scoped SQL owner id. |
| `origin` | Source of the transaction, such as `PgRpc` or `SqlBatch`. |
| `state` | Current lifecycle state. |
| `age_ms` | Transaction age in milliseconds. |
| `idle_ms` | Milliseconds since the transaction last performed work. |
| `write_count` | Number of staged mutations currently buffered. |
| `write_bytes` | Approximate in-memory size of the staged write set. |
| `touched_tables_count` | Number of tables referenced by the transaction. |
| `snapshot_commit_seq` | Committed snapshot boundary captured at `BEGIN`. |
## Key Distinction
- `system.sessions` shows live PostgreSQL bridge sessions only.
- `system.transactions` shows active explicit transactions across all supported origins.
- A native `/v1/api/sql` transaction appears in `system.transactions`, but not in `system.sessions`.
## Related
- [/docs/server/sql-reference/dml](https://kalamdb.org/docs/server/sql-reference/dml)
- [/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types)
- [Kalam CLI](https://kalamdb.org/docs/cli)
- [/docs/pg-kalam/sql-syntax](https://kalamdb.org/docs/pg-kalam/sql-syntax)
================================================================================
# Table DDL
URL: https://kalamdb.org/docs/server/sql-reference/tables
Source: content/server/sql-reference/tables.mdx
> KalamDB table DDL reference. Learn CREATE TABLE, ALTER TABLE, DROP TABLE, and VIEW commands with table type options, flush policies, and constraints.
# Table DDL
KalamDB supports three application table types: `USER`, `SHARED`, and `STREAM`. For the full
architecture, access matrices, storage behavior, and when to choose each type, see
[/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types).
## CREATE TABLE
### Syntax
```sql
CREATE [USER|SHARED|STREAM] TABLE [IF NOT EXISTS] [.] (
[NOT NULL|NULL] [DEFAULT ] [PRIMARY KEY],
...,
[CONSTRAINT PRIMARY KEY ()]
)
[WITH (
TYPE = '',
STORAGE_ID = '',
USE_USER_STORAGE = ,
FLUSH_POLICY = '',
TTL_SECONDS = ,
ACCESS_LEVEL = '',
EVICTION_STRATEGY = '',
MAX_STREAM_SIZE_BYTES = ,
COMPRESSION = ''
)];
```
Table options are type-specific:
- `USER`: `STORAGE_ID`, `USE_USER_STORAGE`, `FLUSH_POLICY`, `COMPRESSION`
- `SHARED`: `STORAGE_ID`, `ACCESS_LEVEL`, `FLUSH_POLICY`, `COMPRESSION`
- `STREAM`: `TTL_SECONDS`, `EVICTION_STRATEGY`, `MAX_STREAM_SIZE_BYTES`
`ACCESS_LEVEL` is enforced only on `SHARED` tables. `PUBLIC` allows regular users to read but not
write; `PRIVATE` and `RESTRICTED` allow service, dba, and system roles; `DBA` allows only dba and
system roles. The canonical shared-table access matrix is maintained at
[/docs/server/architecture/table-types](https://kalamdb.org/docs/server/architecture/table-types#access_level-semantics).
For storage-specific options such as `STORAGE_ID` and `USE_USER_STORAGE`, see
[/docs/server/sql-reference/storage-id-usage](https://kalamdb.org/docs/server/sql-reference/storage-id-usage). For
flush behavior, see [/docs/server/architecture/storage-tiers](https://kalamdb.org/docs/server/architecture/storage-tiers).
`COMPRESSION` accepts only `none`, `snappy`, and `zstd`, and is valid only for `USER` and `SHARED`
tables. It controls the Apache Parquet codec used when table data is flushed or compacted into
cold-storage segments. `none` writes uncompressed Parquet pages, `snappy` is the default and
optimizes for low CPU overhead, and `zstd` uses Zstandard level 1 for better compression density with
modest extra CPU. This option is not WebSocket gzip and does not change RocksDB hot-tier
compression. `STREAM` tables use hot stream log storage and do not accept table Parquet compression.
### Examples
**User table** (per-user isolated):
```sql
CREATE TABLE app.messages (
id BIGINT PRIMARY KEY DEFAULT SNOWFLAKE_ID(),
conversation_id BIGINT NOT NULL,
sender TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'user',
content TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
) WITH (
TYPE = 'USER',
STORAGE_ID = 'local',
USE_USER_STORAGE = false,
FLUSH_POLICY = 'rows:1000,interval:60',
COMPRESSION = 'snappy'
);
```
**Shared table** (global access):
```sql
CREATE SHARED TABLE app.config (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TIMESTAMP DEFAULT NOW()
) WITH (
ACCESS_LEVEL = 'PUBLIC',
COMPRESSION = 'zstd'
);
```
**Stream table** (ephemeral with TTL):
```sql
CREATE STREAM TABLE app.events (
event_id TEXT PRIMARY KEY,
payload TEXT,
created_at TIMESTAMP DEFAULT NOW()
) WITH (
TTL_SECONDS = 30,
EVICTION_STRATEGY = 'hybrid',
MAX_STREAM_SIZE_BYTES = 1048576
);
```
## ALTER TABLE
```sql
-- Add a column
ALTER TABLE [.]
ADD COLUMN [NOT NULL|NULL] [DEFAULT ];
-- Drop a column
ALTER TABLE [.]
DROP COLUMN ;
-- Modify a column type
ALTER TABLE [.]
MODIFY COLUMN [NOT NULL|NULL];
-- Change table properties
ALTER TABLE [.]
SET TBLPROPERTIES ( = , ...);
-- Equivalent shorthand for shared access changes
ALTER TABLE [.]
SET ACCESS LEVEL ;
-- Create a vector index on an embedding column
ALTER TABLE [.]
CREATE INDEX USING COSINE;
```
`SET TBLPROPERTIES` supports the same type-specific persisted options as `CREATE TABLE`.
Use `FLUSH_POLICY = NULL` to clear a user/shared flush policy.
Examples:
```sql
ALTER TABLE app.config
SET TBLPROPERTIES (ACCESS_LEVEL = 'PUBLIC', COMPRESSION = 'zstd');
ALTER TABLE app.messages
SET TBLPROPERTIES (FLUSH_POLICY = 'rows:5000', USE_USER_STORAGE = true);
ALTER TABLE app.events
SET TBLPROPERTIES (
TTL_SECONDS = 3600,
EVICTION_STRATEGY = 'size_based',
MAX_STREAM_SIZE_BYTES = 1048576
);
```
### Vector index example
```sql
CREATE TABLE rag.documents_vectors (
id BIGINT PRIMARY KEY,
embedding EMBEDDING(384)
) WITH (TYPE = 'USER');
ALTER TABLE rag.documents_vectors
CREATE INDEX embedding USING COSINE;
```
After the index exists, rank nearest rows with:
```sql
SELECT id
FROM rag.documents_vectors
ORDER BY COSINE_DISTANCE(embedding, '[0.12,0.03,0.98]')
LIMIT 10;
```
## DROP TABLE
```sql
DROP TABLE [IF EXISTS] [.]