A relational starting point.
Tables are strict by default. Declare fields, types, and constraints, then use familiar select, insert, update, and delete statements. Flexible tables and nested structures are opt-in.
create table player (
id generated,
name text not unknown,
level int32 default 1,
gold int64 default 0
)
Keywords are case-insensitive, with lowercase as the canonical form. Strings use single quotes. Double quotes identify names, not strings. Parameters are named, such as :minimum_level.
Queries do not promise an implicit row order. State an order by when order matters, and include a stable tie-breaker for predictable pagination. Persistent schema-bound objects name their projected fields explicitly rather than using select *.
Missing is not zero. A conversion is a choice.
unknown represents missing, indeterminate, or unavailable data. It replaces neither a number nor an empty string. The language does not define null.
| When you need to... | Make the meaning explicit |
|---|---|
| Test for missing data | Use is unknown, not = unknown. |
| Filter rows or enter a condition | Only true passes. false and unknown do not. |
| Convert potentially invalid input | cast raises an error; try_cast returns unknown. |
| Divide integers | / is mathematical division. div is integer division. |
| Convert a civil timestamp to an instant | Supply an explicit timezone. |
Implicit conversions must be lossless and infallible. Integer overflow is an error, and exact decimal arithmetic must not quietly become binary floating-point arithmetic.
Connect records without losing their identity.
A record identity such as player:42 can appear where a record reference is valid. Declare a field as record<player> to give it a typed reference.
select
name,
owner.name
from item
Explicit joins remain available. For graph-shaped data, relations are first-class records with a source, destination, relation type, and optional fields. Their traversals use the same transactions and policies as ordinary queries.
Put a complete change in one routine.
A normal routine is database-local and transactional. A top-level call starts a transaction unless one already exists. Successful completion commits; an uncaught error rolls back its mutations. Nested calls share the transaction.
Control flow borrows familiar ideas from Lua: local, if, for, and end. A query in scalar-row context returns at most one row. No rows produce unknown; more than one raises cardinality_error.
Local changes stay local. Editing a local row value does not persist it. A database mutation remains explicit in the source.
Safe database-local routines may be retried after a serialization conflict. An external routine can perform I/O, but it is not automatically retryable and cannot make outside side effects atomic. Query-visible functions, in contrast, are deterministic and free of persistent mutations or external effects.
The semantics require default isolation to prevent lost updates. They do not fix a particular lock strategy or promise strict serializability.
One set of access rules.
Policies use intrinsic authentication context to describe access. Authorization must apply across direct SQL, routines, reference and relation traversal, subscriptions, and generated client bindings.
create policy own_inventory
on inventory
where player = auth.player
Foreign keys are always enforced. Cascades are explicit, constraints are immediate unless declared deferred, and schema changes must not silently discard values.
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 11. Parameters
- sql_language_spec.md, section 19. Reference Traversal
- sql_semantics_spec.md, section 5. The `unknown` Value
- sql_semantics_spec.md, section 21. Mutation Semantics
- sql_semantics_spec.md, section 25. Routine Semantics
- sql_semantics_spec.md, section 32. Policy Semantics