Start small. Ask a useful question.
This table is the foundation for the examples below. Values permit unknown unless constrained otherwise.
create table player (
id generated,
name text not unknown,
level int32 not unknown default 1,
gold int64 not unknown default 0,
last_seen instant
)
select id, name, level
from player
where level >= :minimum_level
order by level desc, id asc
limit 50
:minimum_level is supplied by the caller. Explicit ordering makes the intent clear; ordering by identity resolves equal levels.
Ask whether a value is missing.
select id, name
from player
where last_seen is unknown
order by id asc
Do not write last_seen = unknown. Direct comparison against the literal is invalid. An unknown value is not implicitly a zero timestamp.
Make a protected update.
This database-local routine debits a known player. Both invalid input and insufficient funds raise errors instead of silently doing nothing. It assumes trusted invocation; application authorization belongs in policies or additional checks.
routine spend_gold(player_id, amount)
if amount is unknown or amount <= 0 then
error('amount must be positive')
end
local buyer = select id, gold
from player:player_id
for update
if buyer is unknown then
error('player not found')
end
if buyer.gold < amount then
error('insufficient gold')
end
update player:player_id
set gold = gold - amount
end
for update requests update protection without prescribing a physical lock. Persistence is the explicit update, not a change to the local buyer value. An uncaught error rolls back the routine transaction.
A normal routine cannot perform external I/O. Sending a payment or webhook outside the database needs the external-routine rules, not an assumption that a retry is harmless.
Give a relationship its own data.
Relations connect records and can carry fields. Assume players 10 and 20 already exist.
create relation friendship
from player
to player
{
since instant,
trust float32
}
relate player:10
to player:20
through friendship {
since = now(),
trust = 0.8
}
select friend.id, friend.name
from player:10
through friendship
to player as friend
where friendship.trust > 0.5
This is not a separate graph consistency model. Relations participate in the same transactions, authorization, indexing, and change mechanisms as other records.
Keep a query result current.
subscribe experienced_players as
select id, name, level
from player
where level >= :minimum_level
The subscription first yields a snapshot, then ordered committed deltas. Delta order follows commits; it does not imply a row sort order for the result. Rolled-back changes never appear.
Use changes player since :cursor when you want mutation history instead. A maintained result and a durable changefeed answer different questions.
From the specifications
This guide explains the design. The original documents carry the precise requirements and recommendations.
- sql_language_spec.md, section 5. Table Definition
- sql_language_spec.md, section 9. Queries
- sql_language_spec.md, section 12. Comparison and Unknown Tests
- sql_language_spec.md, section 26. Relations
- sql_language_spec.md, section 28. Routines
- sql_language_spec.md, section 38. Subscriptions