TessariDBDocs
  • 0.34.2-betalatest
  • 0.34.1-beta
  • 0.34.0-beta
  • 0.33.4-beta
  • 0.33.3-beta
  • 0.33.2-beta
  • 0.33.1-beta
  • 0.33.0-beta
  • 0.32.0-beta

Statements

Every statement the language has, with what each is for and where it is explained.

There is no separate schema language, no migration tool and no administrative API. Defining a table, reading records, declaring a user and taking a backup are all statements, which is why this one list is the whole surface.

Tenancy

statementdoes
USE NAMESPACE prod DATABASE ordersselects where the statements after it apply — at least one of the two
DEFINE NAMESPACE prodcreates a namespace; REPLICATION FACTOR 3 or REPLICATION NONE says how many copies a cluster keeps, and MULTI MASTER or SINGLE LEADER says how many nodes may write it; ACKNOWLEDGE LEADER, LOCAL MAJORITY or MAJORITY (OR WEAKER to let a request ask for less) says how many copies hold a write before its caller is answered (When a write is acknowledged)
ALTER NAMESPACE prod REPLICATION FACTOR 2moves the replication in either direction, with no repair step after it; ALTER NAMESPACE prod ACKNOWLEDGE … moves the acknowledgement level
DEFINE DATABASE orderscreates a database in the selected namespace
DROP DATABASE ordersremoves it — refused while it still holds tables
DROP NAMESPACE prodremoves it — refused while it still holds databases

See Your first store.

Structure

statementdoes
DEFINE TABLE users (name string, email string)creates a table that declares its columns and refuses a field nobody named; EDGE makes it a relation table, SCHEMALESS keeps the lenient reading, and IDENTITY uuid mints identities that disclose neither order nor count where the default IDENTITY int is a per-table counter; LAST WRITER WINS or REFUSE CONFLICTS says what it does with a write it cannot order against what is stored
DEFINE TABLE orders (total int) IDENTITY uuid SPLIT AT 'g', 'p'a table split into shards at those identities — each logged, led and replicated on its own; moved later by ALTER TABLE orders SPLIT AT 'm' and MERGE SHARD 4, 5 while it serves (Splitting a table)
DEFINE COLLECTION notescreates a table for records whose shape is not fixed — every field is accepted
DEFINE TABLE message (body string) EXPIRE AFTER 7da table whose records expire, 7d after each is created unless a write says EXPIRE <when> or EXPIRE NONE; ALTER TABLE message SET EXPIRE [AFTER d] and DROP EXPIRE change it, never retroactively (Records that expire)
DEFINE SPACE sessionsa table whose records hold a single value; MAX 10000 keeps at most that many keys, evicting the least recently written, and MAX 500 EVICT NONE refuses the key past it (A space with a limit)
DEFINE BUCKET mediaa table whose records are files; MAX 5242880 declares the largest file it takes
DEFINE GRAPH sociala graph, and the collection its own nodes live in under the same name — tables you already have join it with IN social
DEFINE EDGE works_at IN social FROM person TO companyan edge kind, whose entries are held beside the node rather than as records
DEFINE VECTOR embeddings DIMENSION 768 DISTANCE cosinea vector store — both clauses are required and neither has a default
DEFINE GEO placesa store of shapes — one word, and no clause
DEFINE VAULT teama store whose declared fields can be SECRET, sealed under a key minted here — the one declaration that needs the store unsealed
DEFINE VAULT team PASSPHRASE '…'the same, with its key under that passphrase instead of the store's — needs nothing unsealed, and the store's passphrase opens nothing in it
DEFINE QUEUE jobs TIMEOUT 30s ATTEMPTS 5a queue; TIMEOUT is required and has no default, ATTEMPTS is optional and absent means unlimited; PRIORITY BY severity hands out the greatest value first and NOT BEFORE send_at holds a record back until that time (from 0.22.0-beta); DEDUPLICATE 10m makes a CREATE of an identity written inside the window write nothing (from 0.33.4-beta)
DEFINE PARAM $grace VALUE 10ma named value the database holds, read as $grace by scripts, events and views; DROP PARAM $grace removes it (Parameters)
DEFINE TOPIC events RETAIN 7d MAX BYTES 4096an append-only order of messages with dense positions; RETAIN, RETAIN BYTES n (keep at most n bytes, oldest removed first — from 0.22.0-beta) and MAX BYTES are optional, and PUBLIC RATE 60 PER 1m (which needs MAX BYTES) lets an unsigned caller append and do nothing else (Topics)
DEFINE SERIES readings RETAIN 30d TIME ata table whose reads stop at a floor; RETAIN is required and has no default, and TIME at orders it by each event's own at rather than by arrival (Time-series)
DEFINE ROLLUP hourly FROM readings WINDOW 1h BY sensor COMPUTE count(*) AS n, sum(v) AS total RETAIN 365dper-window count, sum, min, max, approx_distinct and approx_quantile(v, q) of a TIME series, kept in the transaction that writes it; runs outside BEGIN … COMMIT
DEFINE VIEW engineers AS SELECT …names a read — expanded where it is used, resolved against the caller's permissions; MATERIALIZED keeps its answer, current from the table's changes (from 0.22.0-beta)
DEFINE EVENT audit ON orders FOR CREATE THEN CREATE order_log = { order: $id }logic run after each write of a record of the table, in the writer's transaction and as the writer; WHEN filters, THEN { … } holds several statements (from 0.22.0-beta, Events)
DEFINE FIELD email ON users TYPE stringdeclares a field's type, and what it may demand
DEFINE INDEX by_email ON users FIELDS email UNIQUEan index — SEARCH, SPATIAL and VECTOR are the other kinds; VECTOR cosine QUANTIZED keeps one byte per component and reranks on the full vectors (from 0.22.0-beta)
DEFINE ANALYZER simple FILTERS lowercasehow text becomes terms
DEFINE SEARCH knowledge ON notes FIELDS title WEIGHT 3, body ON articles FIELDS text ANALYZER plainseveral fields of several tables ranked as one collection; per field WEIGHT, SNIPPET, SYNONYMS <set>, NO FUZZY, NO PREFIX, NO PHRASE, and STOPWORDS <set> for the search (One search over several tables)
DEFINE SYNONYMS machines { engine: ['loom', 'machine'] }a store-wide set of one-word alternatives, read when a search runs
DEFINE STOPWORDS common ['the', 'a', 'of']a store-wide set of words a search drops from a query outside a quoted phrase
ALTER TABLE users SET SCHEMAFULLor SCHEMALESS — whether undeclared fields are allowed
ALTER TABLE users ADD FIELD city TYPE stringdeclares a field on a table that exists
ALTER TABLE users ALTER FIELD age TYPE stringchanges what a field demands
ALTER TABLE users DROP FIELD ageremoves the declaration, not the data — refused on a strict table while a record still carries the field
DROP TABLE usersremoves the definition and releases the name — the records are left where they are, and nothing reaches them again
DROP FIELD email ON usersremoves the declaration, not the data
DROP INDEX by_email ON usersremoves the index
DROP SPACE sessionsremoves a space's definition
DROP BUCKET mediaremoves a bucket's definition
DROP GRAPH socialremoves the graph and its own node collection — refused while an attached table or an edge kind still belongs to it
DROP EDGE works_atremoves the edge kind, and every adjacency entry it wrote
DROP VECTOR embeddingsremoves the store's definition — and refuses a table that is not a vector store
DROP GEO placesthe same, for a geo store — and the same refusal
DROP VAULT teamdestroys the key, which is what makes the records unreadable — see Vaults
DROP QUEUE jobsremoves the queue and its records, held ones included — and refuses a table of another kind
DROP SERIES readingsremoves the table and everything in it, including records past the floor that reads had stopped answering with — refused while a rollup is kept of it
DROP ROLLUP hourlyremoves the rollup and its windows; the series is untouched
DROP VIEW engineersremoves the definition and leaves the records alone
DROP EVENT audit ON ordersremoves the event; writes after the commit run nothing
DROP ANALYZER simpleremoves it — refused while a field or a search still names it
DROP SEARCH knowledgeremoves the search and every member; the tables are untouched
DROP SYNONYMS machinesremoves the set — refused while a search names it
DROP STOPWORDS commonthe same, for a stop-word set
REBUILD INDEX by_at ON pointsrebuilds it — a no-op except for a vector index
ANALYZE TABLE userskeeps, beside each non-unique value index, its entry count, distinct values, sixteen most common values and sixty-four buckets, so a plan can estimate how many records a condition reaches; EXPLAIN then carries estimate and estimated_by (from 0.22.0-beta)
CHECK TABLE usersanswers with every stored record that disagrees with what the table declares now — the record, the field, the rule and the reason; an empty list means it holds
DEFINE NAMESPACE prod;
USE NAMESPACE prod;
DEFINE DATABASE orders;
USE DATABASE orders;
DEFINE TABLE scratch (note string);
DROP TABLE scratch;

A drop removes its own definition and nothing beneath it, and refuses while anything still points at it — naming and counting what is in the way, so acting on the refusal needs no second query. There is no CASCADE: a destructive statement carrying no predicate at all is the widest thing this language can be asked to run, and it is the same rule that makes DELETE FROM t WHERE c require a bound.

That sentence is worth reading twice where a table is concerned. The records are not deleted — removing them is bulk work, and a catalog call that did it quietly would hide the cost — but they are not recoverable either, because the id is never reused and a table redefined under the same name is a new table:

DEFINE TABLE keep (note string);
CREATE keep:1 = { note: 'first' };
DROP TABLE keep;
DEFINE TABLE keep (note string);
SELECT * FROM keep;
(0 record(s), via scan)

Reading at a version from before the drop answers the same way. DROP TABLE is not a recycle bin: what it costs is the whole table, and the only difference from a delete is where the bytes sit afterwards.

DROP NODE does not exist. DEFINE NODE writes this process's own configuration, so its inverse is an edit to a file and a restart — a store where one node could undeclare another's identity over the wire would be answering a question nobody asked it.

See Definitions.

Records

statementdoes
CREATE users = { … }writes a record at an identity the store produces, and answers with it
CREATE users:1 = { … }the same, at an identity you name — writes only if nothing is there, never replaces
INSERT INTO users (name, email) VALUES ( … ), ( … )writes several records in one statement and one transaction, at identities the store produces, and answers with them in the order the rows were written
SELECT * FROM …reads
SELECT * FROM … ANSWERED BY LEADERreads, and admits only the node that decides writes for these records; ANSWERED BY ANY is the default and composes with STALENESS — see Reads, routing and staleness
UPDATE users:1 = { … }replaces a record that is there — never invents
UPDATE users:1 MERGE { … }changes the fields named, keeps the rest
UPDATE users:1 SET age = 18changes named fields to computed values
UPDATE users:1 = { … } WHERE version = 4the same edit, but only if the condition holds — a false condition refuses and discards the whole transaction, which is what makes it a compare-and-set rather than a filter. The condition may be added to any of the three edit forms
UPSERT users:1 = { … }replaces it if it is there, creates it if it is not
DELETE users:1removes one, by identity
DELETE FROM readings WHERE at < … LIMIT ALLremoves what a condition matches — the bound is required, and ALL is how you say every one
DELETE FROM readings:1..3 LIMIT ALLremoves a span of identities — .. excludes the upper end, ..= includes it, and the bound is required here too
RELATE users:1->follows->users:2writes an edge
DELETE users:1->follows->users:2removes one — the same arrow, and no FROM
THROW 'message'abandons the transaction, with that message

A write either names one record or states a condition and a bound; neither DELETE FROM r; nor DELETE FROM r WHERE …; is accepted on its own. RETURN BEFORE and RETURN AFTER make a write answer with the record. Most writes answer ok without them; the two that already answer with something are CREATE at an identity the store produced and INSERT, and what they answer with is the identities they wrote.

See Records, Writing safely and Graphs.

Queues

statementdoes
CLAIM FROM jobstakes the head of the queue and holds it until its deadline; nothing claimable answers no records and no error
CLAIM 5 FROM jobstakes at most that many; above the store's ceiling is refused rather than trimmed; CLAIM $n FROM jobs takes the count from a parameter
CLAIM jobs:7holds the record you name — a held one answers nothing, one that is not there raises
RELEASE jobs:1ends a hold before its deadline; refused when another consumer holds it
RELEASE jobs:1 NOT BEFORE 10mends the hold and holds the record back until then, in one write — on a queue declaring NOT BEFORE
RELEASE jobs:1 FOR CONSUMER 'billing'ends that group's hold on one record, whichever of its instances took it
RELEASE ALL FROM jobsends every hold this session's instance has in one queue, and answers the records
RELEASE ALL FROM jobs FOR CONSUMER 'billing'ends every hold that group has there, live sessions included
USE CONSUMER 'billing'names this session's consumer group; the store mints its instance

A claim is an ordinary write in the ordinary transaction, so it replicates and recovers like one. See Queues.

Topics

statementdoes
CREATE events = { … }appends a message; its position is decided when it commits
READ FROM events AFTER 41 LIMIT 100reads messages after a position, at most 100 when no LIMIT is written
READ FROM events FOR CONSUMER 'billing'reads after the position stored for that reader and moves it, in the reader's own transaction
DEFINE GROUP 'billing' ON TOPIC events ACK DEADLINE 30smakes the readers under that name a group that hands messages out and waits for each to be acknowledged; DELIVERIES n, IN FLIGHT n (default 1) and DEAD LETTER TO t (which needs DELIVERIES) are optional
READ FROM events FOR CONSUMER 'billing' LIMIT 10on a group, hands out messages and holds each in flight until its deadline
ACK events FOR CONSUMER 'billing' AT 41, 42those messages are done; answers how many were in flight
NACK events FOR CONSUMER 'billing' AT 43 DELAY 10shands them out again, now or after the delay
ALTER GROUP 'billing' ON TOPIC events START AT 40the group next hands out position 41 and forgets what it held in flight
DROP GROUP 'billing' ON TOPIC eventsremoves the group
INFO FOR TOPIC eventsthe first and last position, each reader's position and lag, and each group's position, committed position, lag and messages in flight
DROP TOPIC eventsremoves the topic
DEFINE TOPIC payments DEDUPLICATE 5m ON payment_ida message repeating a payment_id published inside the window is not appended

UPDATE, UPSERT and DELETE of a message are refused. See Topics.

Keys and files

statementdoes
GET sessions:'abc'reads one key's value
SET sessions:'abc' = …writes it, whole
DEL sessions:'abc'removes it
SET sessions:'abc' = … EXPIRE 30mwrites it with an expiry — a duration from now or a datetime
SET locks:'r' = 'me' IF ABSENTwrites only if there is no key (IF PRESENT, IF = value likewise) and answers whether it wrote
INCR counters:'hits' BY 10adds to a number and answers the result; a missing key counts from zero
EXPIRE sessions:'abc' 10m · PERSIST sessions:'abc' · TTL sessions:'abc'moves, clears and reads an expiry
KEYS FROM sessions RANGE 'a'..'m'lists a space's keys, optionally bounded
KEYS FROM sessions PREFIX 'user:42:' AFTER 'k' LIMIT 100lists the keys that begin with a prefix, a page at a time
PUT media:'/logo.png' = 0x0a1bwrites a file, or a range of one
READ media:'/logo.png'reads a file, or a range of one

See Spaces, Expiry, counters and locks and Files.

Transactions

statementdoes
BEGINopens a boundary
COMMITlands everything in it; COMMIT ACKNOWLEDGE MAJORITY waits for that many copies, as a single write may with ACKNOWLEDGE after it
CANCELdiscards everything in it, without checking — every check that refuses a write runs inside the commit
VERIFYruns every check a COMMIT runs, then discards the work — a rehearsal, so a write can be tried without making it

See Transactions.

Access

statementdoes
DEFINE USER ada ON prod.orders ROLE editor PASSWORD '…'declares a user — the first one closes the store
ALTER USER ada SET PASSWORD '…'or SET ROLE … — one change at a time
DROP USER adaremoves one
DEFINE USER ops AUTHORITIES operate PASSWORD '…'declares a user by the set of authorities instead of by a role
GRANT read, write ON orders TO adanarrows a user to named tables, and optionally named fields
REVOKE write ON orders FROM adatakes part of that back — never the last grant
GRANT manage ON NAMESPACE prod TO kimhands out one authority over a reach
REVOKE manage ON NAMESPACE prod FROM kimtakes one back
REVEAL token FROM team:'github'opens a vault's sealed fields for a recipient who holds a key
REVEAL * FROM team:'github'every sealed field of that record, on the same terms
ADD RECIPIENT 'ada@example.com' TO team:'github' KEY 0x…gives a recipient their own wrapped copy of the record's key
REMOVE RECIPIENT 'ada@example.com' FROM team:'github'takes it back — the record stays, that recipient can no longer open it
UNSEAL VAULT WITH '…'puts the store's master key into this process for the node's period — ten minutes unless it was started with --unseal-for
SEAL VAULTtakes it back out, and the process can no longer open anything in the store's custody
CHANGE VAULT PASSPHRASE FROM '…' TO '…'wraps the same master key under a new passphrase; nothing is re-encrypted
UNSEAL VAULT team WITH '…' · SEAL VAULT team · CHANGE VAULT team PASSPHRASE FROM '…' TO '…'the same three for a vault declared with a passphrase of its own, and for that vault alone

The last two are the GRANT ON REACH and REVOKE ON REACH forms, and they are separate forms rather than a wider spelling of the two above them: a grant names a table, while these name a reach — STORE, NAMESPACE x or DATABASE x.y. Every reach is led by a keyword so that a table name can never be read as one.

See Users, Authority and Grants.

The node

statementdoes
DEFINE NODE ROLES serving, writable ENDPOINTS 'host:9000'what this machine is for — local, and not in a backup
DEFINE NODE RETAIN 100000 RECORDS / RETAIN NONEhow much of the log this machine keeps — local, 100 000 by default, and a prune is irreversible; see Bounding the log
DEFINE FAILOVER AWARENESS 10s COLLECTION 10s ROUND 1s CAMPAIGN 1s LEASE 30show long the cluster waits before replacing a leader — a replicated row, all five clauses required, written by the node holding the leadership
DEFINE REPLICA second AT 'host:9001'a peer — a catalog record, and replicated; NODE '<id>' binds it to one machine, FINGERPRINT '<sha256>' pins the one peer certificate that may bind it, CLIENTS AT and HTTP AT say where clients reach it, ROLES … says what that peer is for, and REPLICATES STORE | NAMESPACE n | DATABASE n.d | SHARD n.d.t k is the subscription that decides what it may collect, and LEADS NAMESPACE | DATABASE | SHARD … names the one range the node stands to lead (Splitting a table); REGION 'eu' names the region the node runs in, unset unless stated, which ACKNOWLEDGE LOCAL MAJORITY counts (When a write is acknowledged)
ALTER REPLICA second ROLES servingamends a peer one clause at a time — AT, ROLES, CLIENTS AT / CLIENTS NONE, HTTP AT / HTTP NONE, LEADS, REGION '…' / REGION NONE — keeping the node it is bound to and its subscription
DROP REPLICA secondremoves the peer; a row that named a node also removes the node for good — its greeting is refused at the handshake and no row may name it again
CREATE JOIN TOKEN FOR REPLICA second EXPIRES 10manswers, once, a token that binds the row to the first node offering it; the row keeps only its digest (Adding a node)
REVOKE CERTIFICATE '<sha256>'refuses a peer certificate on every node, in both directions, and ends a link already open on it — store operators only, never taken back (Encryption in transit)
BACKUP / BACKUP LOG / BACKUP FROM 42a snapshot of the store's current state (the routine backup), or its log, whole or from a sequence
BACKUP STATE / BACKUP SCRIPTa snapshot, or the store as TessariQL
BACKUP SCRIPT OF NAMESPACE crm, prod.ordersa script of chosen namespaces and databases, without users
BACKUP … TO 'nightly.tessarilog'written by the node into its backup folder, never over an existing file
RESTORE SCRIPT FROM 'crm.tessariql'runs a script from that folder beside what the store holds; it only creates

See Adding a node, Leadership and failover, Bounding the log and Backup and restore.

Ingestion

statementdoes
DEFINE TOPIC CONSUMER events_in FROM … GROUP … INTO … IDENTITY … MAP … ON FAILURE …reads a topic of this store into a table through a group, each message applied exactly once, and the node runs it
DROP TOPIC CONSUMER events_instops it within a second and forgets the declaration; the group keeps its place
DEFINE KAFKA CONSUMER orders_in FROM … TOPIC … GROUP … FORMAT … INTO … IDENTITY … MAP … ON FAILURE …reads a Kafka topic into a table, at least once, and a build with the kafka feature runs it
DROP KAFKA CONSUMER orders_inforgets the declaration and frees the name

See Stream ingestion, which also states the delivery guarantee — at-least-once, never exactly-once.

Scripts

statementwhat it does
LET $city = 'Paris'binds a value for the statements after it in the same script
RETURN 1 + 1answers with an expression, without reading a table

A binding is a value, not a fragment of text: it is substituted where a value goes and never where a table or a field name goes, so it cannot widen what a statement reaches. RETURN is also the word a write takes to answer with the record — RETURN BEFORE and RETURN AFTER, listed under Records.

Looking

statementdoes
EXPLAIN SELECT …the plan a read would take, without taking it
INFO FOR STOREthe namespaces
INFO FOR NAMESPACEthe databases in the selected one
INFO FOR DATABASEthe tables, and the searches, topics and vaults
INFO FOR SEARCH knowledgethe analyzer, the stop words and, per member, the table, its record count and each field's weight and options
INFO FOR TABLE usersfields, indexes and what else the catalog holds
INFO FOR GRAPH socialthe tables and edge kinds that belong to one graph — a graph just declared already lists its own node collection
INFO FOR VECTOR embeddingsone vector store's width, distance, and the recall it was measured at — or NONE
INFO FOR GEO placesone geo store's field and index, and what refining that index's candidates last cost
INFO FOR USER adaone user's role, scope and grants
INFO FOR USERSevery user of the tenancy you administer
INFO FOR ACCESS TO TABLE orderswho reaches one table, and whether they read it or write it
INFO FOR SEAL / INFO FOR SEAL OF teamwhether this process can open secrets and until when; for a vault, also its custody
INFO FOR VAULT team RECORDS [AFTER team:'x'] [LIMIT n]a vault's record ids a page at a time, with no value
INFO FOR NODEthis machine's roles and endpoints, its peers, which of them have actually collected, the time left on its lease, the log window it retains (null is unbounded), and the failover policy the cluster is running (null is nobody has set one)
INFO FOR VERSIONS OF person:1one record's surviving versions, the node that wrote the one an ordinary read answers with, and whether another survives
INFO FOR HISTORY OF person:1what one record became, and when — one entry per write, newest first, with complete saying whether the read reached the start of the log
INFO FOR TOPIC CONSUMER events_inone topic consumer: what was declared, what this node is doing with it, and what it guarantees
INFO FOR KAFKA CONSUMER orders_inone consumer: what was declared, what this node is doing with it, and what it does not promise
INFO FOR KAFKA CONSUMERSevery declared consumer, and which are running here

INFO has a subject per question rather than one with a filter argument, because each one has exactly one rule deciding what the caller may see — and a statement whose answer mixed two permission levels could only give a partial answer or a confusing refusal.

DEFINE TABLE users (name string, email string);
DEFINE INDEX by_email ON users FIELDS email UNIQUE;
INFO FOR DATABASE;
EXPLAIN SELECT * FROM users WHERE email = 'ada@example.com';
{ params: {  }, searches: [], tables: ['keep', 'users'], topics: [], vaults: [] }
{ access: 'index', at_most: 1, columns: 1, exact: true, index: 'by_email', shape: 'equality', table: 'users' }

IF NOT EXISTS

Every DEFINE takes it, and without it a name already in use is refused rather than quietly replaced — so a migration that runs twice tells you, unless you said it was allowed to:

DEFINE TABLE IF NOT EXISTS users (name string, email string);
DEFINE INDEX IF NOT EXISTS by_email ON users FIELDS email UNIQUE;

That is what makes a start-up script re-runnable, which is what a deployment that restarts actually needs.

The other half is DROP IF EXISTS: every DROP that names one object takes IF EXISTS and answers ok when the object is not there (from 0.33.4-beta) — DROP EVENT IF EXISTS audit ON orders, DROP INDEX IF EXISTS by_email ON users, DROP PARAM IF EXISTS $grace. An event that must change without a moment of not existing is replaced with DEFINE EVENT OR REPLACE rather than dropped and defined again.

Statements — TessariDB