SQL Reference
RelataDB extends ANSI SQL with bi-temporal reads, provenance, identity resolution, graph traversal, hybrid search, and agent memory verbs. The same SQL runs across every protocol door: psql, gRPC, HTTP /query, MCP query, Arrow Flight, and the SPARQL bridge.
What runs today: this page documents verified behaviour. Items marked not implemented or partial are not available. See Limits for the full status table.
Statement shape
[EXPLAIN POLICY]
[PURPOSE '<id>']
SELECT <projection>
FROM <Type | TVF>
[AS OF '<timestamp>']
[AS OF SYSTEM TIME '<timestamp>']
[WHERE <predicate>]
[ORDER BY <col> [ASC|DESC]]
[LIMIT n [AFTER '<cursor>']]
[WITH PROVENANCE]All clauses are order-sensitive. WITH PROVENANCE must be last.
PURPOSE
PURPOSE '<id>' is an optional prefix. When omitted the query runs; when declared it is recorded in the audit log. Per-tenant ACL policy can require it.
-- runs, no audit record
SELECT * FROM Person LIMIT 10
-- runs, audited under purpose "analytics"
PURPOSE 'analytics' SELECT * FROM Person LIMIT 10
-- returns the ACL decision tree without executing
EXPLAIN POLICY PURPOSE 'analytics' SELECT * FROM Person LIMIT 10Common values: analytics, audit, compliance_review, security_incident, research, operations.
AS OF — bi-temporal snapshots
AS OF returns rows whose valid_from <= ts < valid_to. AS OF SYSTEM TIME uses system-recorded time instead.
-- valid-time snapshot
SELECT * FROM Document AS OF '2026-01-01T00:00:00Z'
-- system-time snapshot
SELECT * FROM Document AS OF SYSTEM TIME '2026-01-01T00:00:00Z'Timestamp formats accepted: UTC ISO-8601 (YYYY-MM-DD, YYYY-MM-DDTHH:MM:SS, YYYY-MM-DDTHH:MM:SS.fffZ). Non-UTC offsets are rejected. Underlying storage is i64 nanoseconds UTC.
WITH PROVENANCE
Trailing modifier — must come after LIMIT:
SELECT * FROM Event
LIMIT 10
WITH PROVENANCEAdds a parallel provenance array to the response (one entry per row). Each entry exposes source, method, confidence, recorded_at, and derived_from (hex ProvenanceRef of the source row, or null for genesis).
Keyset pagination: LIMIT n AFTER
SELECT * FROM Person
LIMIT 100 AFTER '1798765432100000000'The cursor is the previous page's last system_from value (decimal nanoseconds). Cannot combine with ORDER BY.
EXPLAIN
EXPLAIN POLICY PURPOSE 'analytics' SELECT * FROM Person LIMIT 10
EXPLAIN_PATH('alice@example.com', 'bob@example.com')
EXPLAIN_REPLAY('<exhibit-id>', SEQ => 5)| Variant | Returns |
|---|---|
EXPLAIN POLICY | ACL decision tree + cell-mask plan for the requesting principal |
EXPLAIN_PATH | {strategy, graph_node_count, pll_warm} for a graph path probe |
EXPLAIN_REPLAY | Re-derives a logged exhibit link's seal byte-identically |
EXPLAIN POLICYis parsed as a prefix but the flag is not consulted on the execute path. Use theexplain_policyMCP tool for a live ACL decision.
WHERE expressions
| Form | Status |
|---|---|
col op literal (=, !=, <, <=, >, >=, LIKE) | Working |
| Arithmetic expressions | Working |
now() | Working — resolved at parse time |
now() - INTERVAL 'N days|hours|minutes|weeks' | Working |
MATCH(col, 'q'[, PHRASE|FUZZY|STEMMED|BOOLEAN]) | Working |
SOUNDEX(col) = 'name' / METAPHONE(col) / COLOGNE(col) | Working — phonetic name-variant matching |
MATCH modes: default resolves from the BM25 posting list; PHRASE uses the positional index; FUZZY and STEMMED fall back to substring scan; BOOLEAN evaluates uppercase AND/OR/NOT operators as posting-list set operations (a bare space means OR). Phonetic predicates (SOUNDEX/METAPHONE/COLOGNE) encode the right-hand literal to the phonetic key at parse time and support only = / !=.
Aggregation and JOIN
| Feature | Status |
|---|---|
JOIN (INNER, hash join) | Working |
GROUP BY + COUNT(*) | Working |
ORDER BY (single column, ASC/DESC) | Working |
Multi-column ORDER BY (tiebreakers) | Working |
UNION / UNION ALL / INTERSECT / EXCEPT | Working |
CTE (WITH [RECURSIVE] … AS (…)) | Working |
TUMBLE / HOP / SESSION streaming windows | Working |
Identity operators
-- Universal lookup by value (phone, email, IMEI, IBAN, IP, BTC address, …)
SELECT * FROM LOOKUP_IDENTITY('+919876543210')
-- Resolution modes: cluster (default), canonical, fuse
SELECT * FROM RESOLVE_IDENTITY('alice@example.com')
SELECT * FROM RESOLVE_IDENTITY('alice@example.com', MODE => 'canonical')
SELECT * FROM RESOLVE_IDENTITY('alice@example.com', MODE => 'fuse')RESOLVE_IDENTITY(..., MODE => 'fuse') dispatches the EnrichmentRule chain. Returns an error when no rules are registered.
The SQL keyword is the singular
RESOLVE_IDENTITY. The DataFusion TVF formSELECT * FROM resolve_identity(...)is also accepted and translated to the governed keyword form.
Entity merge / dedup
-- Ontological merge — link two identities as one person
FUSE_IDENTITIES('alice@example.com', 'bob@example.com')
-- Ontological unmerge — reverse a fuse
SPLIT_IDENTITIES('alice@example.com', 'bob@example.com')
-- SmartIngest auto-detect identities in free text
DETECT_IDENTITIES('call bob at +971501234567')
-- GDPR Art. 17 erasure — irreversible
ERASE SUBJECT 'alice@example.com' REASON 'gdpr-art17' CERTIFYGraph operators
-- Shortest paths (Pregel BFS)
SELECT * FROM PATHS_BETWEEN('alice', 'bob', 3)
SELECT * FROM PATHS_BETWEEN('person-123', 'org-456', MAX_HOPS => 4)
-- Network expansion
SELECT * FROM NETWORK_EXPAND('entity-X', MAX_HOPS => 3)
-- Per-node degree
SELECT id, DEGREE(id) AS degree FROM Person ORDER BY degree DESC LIMIT 10
NETWORK_EXPANDtakes the seed identifier as the first positional arg and the hop cap via theMAX_HOPS => nnamed arg (default2), with an optionalLINK_TYPES => 'a,b'filter.
Additional graph TVFs (all SQL-reachable):
| TVF | Description |
|---|---|
GRAPH_DIJKSTRA('<type>', FROM => 'a', TO => 'b') | Weighted shortest path |
GRAPH_SCC('<type>') | Strongly connected components |
GRAPH_CYCLES('<type>') | Cycle detection |
GRAPH_SSSP('<type>', FROM => 'a') | Single-source shortest paths |
GRAPH_SPANNING_TREE('<type>') | Minimum spanning tree |
GRAPH_APSP('<type>') | All-pairs shortest paths |
GRAPH_DIAMETER('<type>') | Graph diameter |
GRAPH_SIMILARITY('<type>', TOP_K => n) | Structural similarity |
GRAPH_NODE_METRIC('<type>', METRIC => 'kcore') | Betweenness, PageRank, clustering coefficient |
GRAPH_LINK_PREDICT('<type>', FROM => 'a', TO => 'b', METHOD => 'adamic_adar') | Link prediction score |
Search operators
-- Full-text search (BM25, custom engine — not Tantivy)
SELECT * FROM Person WHERE MATCH(name, 'Ahmed Khalil')
-- Hybrid BM25 + vector (reciprocal-rank fusion)
HYBRID_SEARCH FROM Document QUERY 'terror finance' LIMIT 25
-- Embedding-vector similarity against a seed row (multi-vector max-pool)
SIMILAR TO Document WHERE id = 'doc-42' LIMIT 10
-- pgvector-compatible KNN operators (pgwire door only)
SELECT id FROM docs ORDER BY embedding <=> '[0.9,0.1,0]' LIMIT 5| Operator | Metric | Note |
|---|---|---|
<=> | cosine distance | Preferred — ANN index is cosine-only |
<-> | L2 distance | Metric-correct via over-fetch + re-rank |
<#> | negative inner product | Metric-correct via over-fetch + re-rank |
Multimodal similarity
-- Similar-to a reference (multi-vector max-pool cosine over _emb_* slots)
SIMILAR TO Person WHERE id = 'person-42'
SIMILAR TO MultimodalAsset WHERE id = 'asset-7'
-- Near-duplicate / rough-visual-similarity image search (perceptual hash + Hamming index)
SIMILAR_IMAGE('media-42')
SIMILAR_IMAGE('media-42', THRESHOLD => 0.6, INDEX => 'ncmec')SIMILAR TO falls back to Jaccard token similarity when the reference has no embedding slots. Face and voice search are exposed as the FACE_SEARCH / VOICE_MATCH operators and the MCP face_match / voice_match tools. SIMILAR_IMAGE(media_ref, THRESHOLD => n, INDEX => corpus) matches by perceptual hash rather than embedding — THRESHOLD defaults to 0.9; INDEX optionally scopes the Hamming-index lookup to a named corpus (e.g. 'ncmec' for CSAM near-dup triage).
Agent memory verbs
REMEMBER 'Alice plans to renew in Q3.' SESSION 'sess-1' CONFIDENCE 0.9
RECALL 'Alice renewal' TOP_K 5 AS OF '2026-01-01'
REFLECT ON SESSION 'sess-1'
CONSOLIDATE MEMORY 'mem-uuid-old' WITH 'Alice confirmed renewal for Q3.'
FORGET MEMORY 'mem-uuid' RETAIN_DAYS 90
ASSOCIATE MEMORY 'mem-a' WITH 'mem-b' AS 'contradicts' CONFIDENCE 0.8
RESOLVE MEMORY 'mem-uuid' POLICY 'highest_confidence'The same verbs are available via MCP tools and HTTP /memory/* endpoints.
Domain TVFs
All reachable from SQL under the governed keyword translation:
| TVF | Domain | Description |
|---|---|---|
BENEFICIAL_OWNERSHIP_CHAIN(entity, max_depth) | FinINT | Beneficial ownership chain |
SANCTIONS_SCREEN(name[, THRESHOLD => 0.75]) | FinINT | Sanctions screening (default Jaccard threshold 0.75) |
CRYPTO_TRACE(wallet, max_hops, min_amount) | FinINT | BFS over TransactionGraph |
WIRE_RECONSTRUCTION(...) | FinINT | Wire transfer reconstruction |
HAWALA_TRACE(...) | FinINT | Hawala network trace |
GRAPH_COMMUNITY(...) | Graph | Community detection |
GEOFENCE(...) | Geo | Geofence lookup |
ANPR_TRACE(...) | Intel | ANPR plate trace |
DISPATCH_PRIORITY(...) | Ops | Dispatch priority |
CRIME_PATTERN_CLUSTER(...) | Intel | Crime pattern clustering |
Social-media analytics scorers
All 13 ScorerOp operators are SQL-reachable:
SENTIMENT_SCORE, STANCE_SCORE, BIAS_SCORE, AUTHENTICITY_SCORE,
POSTING_PATTERN, STYLE_FINGERPRINT, BOT_AMPLIFICATION_SCORE,
NARRATIVE_TRACE, PERSONA_CLUSTER_DETECT, INAUTHENTIC_BEHAVIOUR_SCAN,
COORDINATED_AMPLIFICATION, CROSS_PLATFORM_ACCOUNT, INFLUENCE_TIER.
Lookup tables
-- Register once (survives until restart)
REGISTER LOOKUP cmdb_assets FROM '/data/cmdb.csv'
KEY (ip) FIELDS (owner, criticality, environment)
REFRESH EVERY 5 MINUTES;
-- Enrich query results at run time
SELECT
src_ip,
LOOKUP cmdb_assets(src_ip) -> owner AS src_owner,
LOOKUP cmdb_assets(src_ip) -> criticality AS src_crit,
bytes
FROM NetworkFlow
WHERE ts > now() - INTERVAL '1 hour'
LIMIT 100;Materialized views
CREATE MATERIALIZED VIEW active_persons AS
SELECT * FROM Person WHERE status = 'active'
REFRESH INCREMENTAL EVERY 60;Background refresh runs every 60 seconds. RELATA_MV_MAX_ROWS (default 1,000,000) caps cached rows before eviction to base-table fallback.
WATCH
WATCH PURPOSE 'security_incident' SELECT * FROM Event WHERE severity = 'high'Registers a subscription — pushes matching rows to the caller as they arrive. Available over the SSE /watch/stream endpoint.
ERASE SUBJECT (GDPR Art. 17)
ERASE SUBJECT 'person-42' REASON 'gdpr-art17' CERTIFY;REASON '<reason>' is required; the trailing CERTIFY keyword acknowledges the operation is destructive and irreversible. Shreds rows, orphaned blobs, and the per-subject DEK (KMS-wired, fail-closed). Returns a signed Art. 17 receipt.
DDL
RelataDB is ontology-governed. Types are declared via the ontology, not CREATE TABLE. DDL is supported only via the pgwire door for pgvector compatibility.
| Statement | Status |
|---|---|
CREATE EXTENSION vector / DROP EXTENSION vector | Working (pgwire, no-op OK tag) |
CREATE TABLE | Working (pgwire) — registers the type; column list is ignored |
INSERT / UPDATE / DELETE | Working (pgwire) — the only native DML path |
CREATE MATERIALIZED VIEW … REFRESH INCREMENTAL EVERY <s> | Working |
ALTER TABLE … ADD/DROP COLUMN | Parses, then 501s — use the ontology API |
CREATE INDEX / CREATE TYPE / CREATE SCHEMA | Not implemented |
Clause grammar & capability matrix
Code-verified per-clause status. "Working" produces correct results under cargo test; "Partial" parses but output is incomplete; "Not implemented" returns a 400 or empty result. See Limits for deeper caveats.
SELECT projection
| Form | Status | Example |
|---|---|---|
SELECT * / SELECT col, col | Working | SELECT name, age FROM Person |
COUNT(*), SUM/AVG/MIN/MAX(col) | Working | SELECT SUM(amount) FROM Transaction |
Nested aggregates COUNT(SUM(...)) | Not implemented | Use separate queries |
COALESCE(col, default) | Not implemented | Handle nulls in the application layer |
CASE WHEN col = v THEN ... [ELSE ...] END | Working | Equality-form CASE WHEN over a column |
col AS alias / DISTINCT | Working | SELECT DISTINCT col FROM ... |
FROM, JOIN, subquery
| Feature | Status | Note |
|---|---|---|
| Single type | Working | FROM Person |
INNER JOIN | Working | Hash join, O(n+m) |
LEFT/RIGHT/FULL [OUTER] JOIN | Working | Outer-join variants parse and execute |
| Subquery in FROM | Working | Bounded by MAX_PARSE_DEPTH = 40 |
CTE WITH [RECURSIVE] ... AS (...) | Working | Recursive CTEs split on set_ops |
WHERE predicates
| Predicate | Status |
|---|---|
col op literal (=,!=,<,<=,>,>=,LIKE) | Working |
IS NULL / IS NOT NULL | Working |
IN (a, b, c) and IN (SELECT ...) | Working (uncorrelated; correlated resolved per outer row) |
[NOT] BETWEEN a AND b | Working (inclusive; numeric, temporal, string) |
AND / OR / NOT; col op col; arithmetic col op (expr op literal) | Working |
now() and now() - INTERVAL 'N days|hours|minutes|weeks|months|years' | Working (months ≈ 30d, years ≈ 365d) |
MATCH(col, 'q'[, PHRASE|FUZZY|STEMMED|BOOLEAN]) | Working |
SOUNDEX(col) = 'name' / METAPHONE(col) / COLOGNE(col) | Working — phonetic name-variant matching |
SQL:2011 FOR SYSTEM_TIME FROM/TO/BETWEEN; OVERLAPS/PRECEDES/SUCCEEDS | Not implemented — use AS OF or explicit valid_from/system_from comparisons |
ORDER BY, LIMIT, pagination
| Feature | Status | Note |
|---|---|---|
ORDER BY col [ASC|DESC]; multi-column | Working | Secondary keys via tiebreakers |
LIMIT n | Working | |
LIMIT n AFTER 'cursor' | Working | Cursor = system_from ns (decimal); cannot combine with ORDER BY |
OFFSET n | Not implemented | Use cursor-based pagination |
GROUP BY, windows, HAVING
| Feature | Status |
|---|---|
GROUP BY col | Working |
GROUP BY TUMBLE/HOP/SESSION(ts, ...) (streaming windows) | Working |
HAVING (post-aggregation predicate) | Working |
Window functions ROW_NUMBER(), RANK() | Not implemented |
FILTER within aggregate | Not implemented |
EXPLAIN variants
| Variant | Status | Returns |
|---|---|---|
EXPLAIN <query> | Working | Physical plan (scan access, join order, spill decision); does not execute |
EXPLAIN ANALYZE <query> | Working | Runs the query, appends Analyze stage + actual_rows + processing_time_ms |
EXPLAIN_PATH('a','b') | Working | {strategy, graph_node_count, pll_warm} |
EXPLAIN_REPLAY('<id>', SEQ => n) | Working | Re-derives an exhibit link's seal byte-identically |
EXPLAIN POLICY | Partial | Parses; flag not consulted on execute path — use the explain_policy MCP tool |
Cluster fan-out aggregates
On cluster profile with peers, fan-out merge supports SUM(col)/SUM(*) (merged across shards), AVG(col)/AVG(*) (weighted merge), and COUNT(*) (summed across shards).
DDL (pgwire path only)
These statements only work over the pgwire listener (port 5433 by default), not /query:
| Statement | Status |
|---|---|
CREATE EXTENSION vector / CREATE TABLE / INSERT/UPDATE/DELETE | Working (pgwire) |
CREATE MATERIALIZED VIEW ... REFRESH [INCREMENTAL] EVERY <s> | Working |
ALTER TABLE | Parses, then 501s — use POST /ontology/migrate or POST /types/:name/schema |
CREATE INDEX / CREATE TYPE / CREATE SCHEMA | Not implemented — use POST /ontology/migrate (SHACL) or POST /types (runtime) |
Ingest body formats
POST /ingest?object_type=<Type>&purpose=<P> auto-detects the body format (no Content-Type change required — the server inspects the first byte):
| Format | Detection | Example |
|---|---|---|
| CSV | Default (no leading { or [) | name,age\nAlice,30 |
| NDJSON | Leading { (one JSON object per line) | {"name":"Alice","age":30}\n{...} |
| JSON Array | Leading [ | [{"name":"Alice","age":30}] |
The
purposeparameter is validated against^[a-zA-Z_][a-zA-Z0-9_]{0,63}$— use underscores, not hyphens (e.g.threat_intel, notthreat-intel); max 64 chars. A rejected purpose returns 400 with a hint showing the underscore-normalised form.
Custom types
| Operation | Status | Note |
|---|---|---|
POST /types | Working | Register a custom type at runtime; persisted to custom_types.jsonl |
DELETE /types/:name | Working | Admin-only de-registration |
RELATA_ACL_GRANT=MyType:read+write | Working | Grants api-user + mcp-client read/write on named types |
| Unknown type on ingest/read | 400 | Fail-closed with guidance to register via POST /types |
Detection-rule SQL guard
Detection-rule conditions are validated at create + eval time: rejects ;, --, /* */ markers; rejects UNION/INTERSECT/EXCEPT adjacent to punctuation; 2048-byte length cap on condition text.
Not yet implemented
| Feature | Notes |
|---|---|
OFFSET n pagination | Use cursor-based LIMIT n AFTER '<cursor>' |
Multi-column DISTINCT | — |
| SQL:2011 period predicates | Use explicit valid_from/system_from predicates; AS OF covers point-in-time |
EXPLAIN POLICY live evaluation | Use the explain_policy MCP tool |
| User-defined functions | Use the MCP tool surface |