DDL (Data Definition Language)

Database Management

-- Create database with optional quota settings
CREATE DATABASE emp_prod WITH (
    max_memory_bytes = 1073741824,
    max_storage_bytes = 10737418240,
    max_qps = 1000,
    max_connections = 100,
    cache_weight = 1,
    priority_class = 'standard',
    maintenance_cpu_pct = 25
);

-- Drop database
DROP DATABASE emp_prod;          -- errors if non-empty
DROP DATABASE emp_prod CASCADE;  -- drops all collections
DROP DATABASE emp_prod FORCE;    -- forces materialization of dependent clones, then drops

-- Rename database
ALTER DATABASE emp_prod RENAME TO emp_prod_old;

-- Set or update quota
ALTER DATABASE emp_prod SET QUOTA (
    max_memory_bytes = 2147483648,
    max_qps = 2000,
    cache_weight = 2
);

-- Enable DML audit
ALTER DATABASE emp_prod SET AUDIT_DML = 'writes';  -- 'none' | 'writes' | 'all'

-- Set idle session timeout
ALTER DATABASE emp_prod SET IDLE_TIMEOUT 1800;  -- seconds; 0 = disabled

-- Clone database
CLONE DATABASE staging FROM emp_prod AS OF SYSTEM TIME 1730000000000;
CLONE DATABASE preview FROM emp_prod AS OF LATEST;

-- Force materialization of a clone (blocks until complete)
ALTER DATABASE staging MATERIALIZE;

-- Mirror database
MIRROR DATABASE emp_eu FROM emp_prod MODE = async;
MIRROR DATABASE emp_eu FROM emp_prod MODE = sync;

-- Promote mirror to writable
ALTER DATABASE emp_eu PROMOTE;

-- Move tenant between databases (offline, requires all sessions drained)
MOVE TENANT acme FROM emp_us TO emp_eu;

-- Set per-tenant quota within a database
ALTER TENANT acme IN DATABASE emp_prod SET QUOTA (
    max_memory_bytes = 536870912,
    max_qps = 500,
    max_connections = 50,
    max_vector_dim = 1536,
    max_graph_depth = 5
);

-- Session management
USE DATABASE emp_prod;  -- or pgwire \c emp_prod
SHOW DATABASES;
SHOW DATABASE LINEAGE FOR staging;
SHOW DATABASE MIRROR STATUS FOR emp_eu;
SHOW DATABASE QUOTA FOR emp_prod;
SHOW DATABASE USAGE FOR emp_prod;
SHOW TENANT QUOTA FOR acme IN DATABASE emp_prod;
SHOW TENANT USAGE FOR acme IN DATABASE emp_prod;
SHOW SESSIONS;
SHOW SESSIONS IN DATABASE emp_prod;
KILL SESSION '550e8400-e29b-41d4-a716-446655440000';
SHOW AUDIT IN DATABASE emp_prod WHERE event_type = 'database_cloned';

-- Role and access
GRANT DATABASE_OWNER ON DATABASE emp_prod TO alice;
GRANT DATABASE_EDITOR ON DATABASE emp_prod TO bob;
GRANT DATABASE_READER ON DATABASE emp_prod TO charlie;
GRANT CREATE COLLECTION ON DATABASE emp_prod TO developers_role;
GRANT SELECT ON DATABASE emp_prod TO analysts_role;
ALTER USER alice SET DEFAULT DATABASE emp_prod;

-- API keys with database scope
CREATE API KEY prod_key FOR alice WITH DATABASES (emp_prod, emp_staging);
ALTER API KEY prod_key SET DATABASES (emp_prod);
SHOW API KEYS;

-- Service accounts with database scope
CREATE SERVICE ACCOUNT sa_prod FOR DATABASE emp_prod;
ALTER SERVICE ACCOUNT sa_prod SET DATABASES (emp_prod, emp_staging);
DROP SERVICE ACCOUNT sa_prod;

-- OIDC / SSO providers
CREATE OIDC PROVIDER corp_sso WITH (
    issuer = 'https://idp.corp.com',
    jwks_uri = 'https://idp.corp.com/.well-known/jwks.json',
    audience = 'nodedb-prod'
);
ALTER OIDC PROVIDER corp_sso SET CLAIM MAPPING (
    { claim: 'groups', value: 'eng', effect: 'add_databases = (emp_prod)' }
);
DROP OIDC PROVIDER corp_sso;
SHOW OIDC PROVIDERS;

See the administration guides for detailed walkthroughs: Database Management, Database Clone, Database Mirror, Move Tenant, Quotas, Session Management, OIDC / SSO.

Collections

-- Schemaless document (default)
CREATE COLLECTION users;

-- Strict document
CREATE COLLECTION orders (
    id TEXT PRIMARY KEY, customer_id TEXT, total FLOAT, status TEXT, created_at TIMESTAMP
) WITH (engine='document_strict');

-- Key-Value
CREATE COLLECTION sessions (key TEXT PRIMARY KEY) WITH (engine='kv');

-- Columnar (plain analytics)
CREATE COLLECTION logs (ts TIMESTAMP TIME_KEY, host VARCHAR, level VARCHAR, message VARCHAR)
    WITH (engine='columnar');

-- Timeseries (peer engine, append-only with retention + continuous aggregates)
CREATE COLLECTION metrics (ts TIMESTAMP TIME_KEY, host VARCHAR, cpu FLOAT)
    WITH (engine='timeseries', partition_by='1h', retention='90d');

-- Spatial (peer engine, R-tree + geohash + OGC predicates)
CREATE COLLECTION locations (geom GEOMETRY SPATIAL_INDEX, name VARCHAR)
    WITH (engine='spatial');

-- Convenience alias for timeseries
CREATE TIMESERIES metrics;

DROP COLLECTION users;          -- soft-delete: tombstoned, still restorable
UNDROP COLLECTION users;        -- restore while within the retention window
DROP COLLECTION users PURGE;    -- admin-only: immediate hard-delete, not restorable
SHOW COLLECTIONS;
DESCRIBE users;

DROP COLLECTION is two-phase. A plain drop marks the collection as tombstoned and enqueues background L2 cleanup after a configurable retention window; within that window, UNDROP COLLECTION restores the collection and its data. Admins can bypass the window with DROP ... PURGE.

-- Retention window knobs
ALTER SYSTEM SET collection_gc_retention = '7d';
ALTER TENANT acme SET collection_gc_retention = '24h';

-- Inspect the lifecycle
SELECT name, dropped_at, size_bytes_estimate FROM _system.dropped_collections;
SELECT * FROM _system.l2_cleanup_queue;

Dropping a collection also cascades downstream catalog state: dependent materialized views are unregistered, change-stream consumer groups are torn down, and the L2 cleanup worker reclaims segment files.

Constraint Policy

NodeDB accepts a deliberately small subset of SQL constraints in CREATE COLLECTION / CREATE TABLE. Anything outside that subset is rejected with SQLSTATE 0A000 (feature_not_supported) — the parser tells you the canonical replacement instead of silently stripping the constraint.

Accepted: inline <col> <TYPE> PRIMARY KEY on a single column. The named column becomes the row identity; without one, NodeDB auto-injects _rowid INT64 PRIMARY KEY.

Rejected at DDL time:

FormReplacement
Table-level PRIMARY KEY (col)Inline form: <col> <TYPE> PRIMARY KEY
Named CONSTRAINT pk_x PRIMARY KEY (col)Inline form
Inline or table-level UNIQUECREATE UNIQUE INDEX ON tbl(col)
Inline or table-level CHECK (...)Application-side validation (typeguards on schemaless docs)
FOREIGN KEY (...) REFERENCES ...Application-side enforcement
Column-level REFERENCES other(col)Application-side enforcement

These are launch-frozen choices. UNIQUE/CHECK/FK/REFERENCES would each pull in a fixed semantic that's expensive to walk back, and the canonical replacements (unique indexes, app-side validation) cover the same use cases without locking the engine into a particular cross-row enforcement strategy.

Schema Evolution

ALTER TABLE orders ADD COLUMN priority INT;
ALTER COLLECTION orders ADD COLUMN region STRING DEFAULT 'us-east';
ALTER COLLECTION orders DROP COLUMN region;

Storage Conversion

CONVERT COLLECTION cache TO kv;
CONVERT COLLECTION users TO document_strict;
CONVERT COLLECTION logs TO document_schemaless;

Indexes

CREATE INDEX idx_email ON users(email);
CREATE UNIQUE INDEX ON users(username);
CREATE VECTOR INDEX ON articles METRIC cosine DIM 384 M 16 EF_CONSTRUCTION 200;
CREATE SEARCH INDEX ON articles FIELDS title, body ANALYZER 'english' FUZZY true;
CREATE SPATIAL INDEX ON locations(geom);
CREATE SORTED INDEX lb ON scores (score DESC) KEY player_id;
DROP INDEX idx_email;

-- Rebuild all indexes on a collection (non-blocking concurrent path recommended)
REINDEX CONCURRENTLY articles;

-- Rebuild a specific named index only
REINDEX INDEX idx_email CONCURRENTLY users;

-- Blocking rebuild (holds write lock for the duration — use only in maintenance windows)
REINDEX articles;

REINDEX CONCURRENTLY builds the new index in a shadow location while reads and writes continue against the live index. Cutover is a single atomic swap. Supported for HNSW vector indexes, FTS LSM indexes, and Graph CSR indexes. Secondary B-tree indexes use the blocking path regardless of CONCURRENTLY (rebuild is fast for B-tree).

Triggers

-- ASYNC (default): Event Plane, zero write-latency impact
CREATE TRIGGER notify AFTER INSERT ON orders FOR EACH ROW $$
BEGIN INSERT INTO notifications (user_id, message) VALUES (NEW.customer_id, 'Order placed'); END; $$;

-- SYNC: same transaction, ACID
CREATE TRIGGER enforce AFTER UPDATE ON accounts FOR EACH ROW WITH (EXECUTION = SYNC) $$
BEGIN IF NEW.balance < 0 THEN RAISE EXCEPTION 'Negative balance'; END IF; END; $$;

DROP TRIGGER notify ON orders;
SHOW TRIGGERS;

The op tag (INSERT vs UPDATE) is derived from storage prior-bytes, not the surface SQL verb. An UPSERT or INSERT ... ON CONFLICT (pk) DO UPDATE that overwrites an existing row fires AFTER UPDATE; the same statement against a non-existent key fires AFTER INSERT. ON CONFLICT DO NOTHING emits no event on conflict.

Functions & Procedures

-- SQL expression (inlined, zero overhead)
CREATE FUNCTION full_name(first VARCHAR, last VARCHAR) RETURNS VARCHAR
LANGUAGE SQL IMMUTABLE AS $$ first || ' ' || last $$;

-- Procedural
CREATE PROCEDURE transfer_funds(from_id UUID, to_id UUID, amount DECIMAL) BEGIN
    UPDATE accounts SET balance = balance - amount WHERE id = from_id;
    UPDATE accounts SET balance = balance + amount WHERE id = to_id;
END;

CALL transfer_funds('acc_a', 'acc_b', 50.00);

Change Streams & Topics

CREATE CHANGE STREAM order_events ON orders WITH (URL = 'https://hooks.example.com/orders');
CREATE CONSUMER GROUP processors ON order_events;
CREATE TOPIC alerts WITH (RETENTION = '1 hour');
PUBLISH TO alerts 'message';

Materialized Views

CREATE MATERIALIZED VIEW order_stats AS SELECT status, COUNT(*), SUM(total) FROM orders GROUP BY status;
REFRESH MATERIALIZED VIEW order_stats;

CREATE CONTINUOUS AGGREGATE cpu_hourly ON cpu_metrics AS
    SELECT time_bucket('1 hour', ts) AS hour, host, AVG(cpu) FROM cpu_metrics GROUP BY hour, host
WITH (refresh_interval = '1m');

Cron Scheduler

CREATE SCHEDULE nightly_cleanup CRON '0 2 * * *' AS BEGIN
    DELETE FROM sessions WHERE expires_at < now();
END;

Backup & Restore

BACKUP TENANT acme TO '/backups/acme.bak';
RESTORE TENANT acme FROM '/backups/acme.bak';
RESTORE TENANT acme FROM '/backups/acme.bak' DRY RUN;
PURGE TENANT acme CONFIRM;
View page sourceLast updated on May 12, 2026 by Farhan Syah