OriginChain docs
docs · sql · constraints

FK + CHECK constraints.

Foreign keys and CHECK constraints are declared in the TOML manifest and enforced at write time. Foreign keys ship with three on-delete actions — no_action, restrict and set_null — and CHECK follows SQL’s 3-valued logic. A violation comes back as HTTP 409 with a typed constraint_violation body: no silent skip, no partial commit.

Foreign keys.

# manifest.toml
namespace   = "shop"
table       = "orders"
primary_key = ["id"]

# ... [[columns]] declarations ...

[[foreign_keys]]
name          = "fk_client"
from_col      = "client_id"
target_schema = "shop.clients"
target_col    = "id"
on_delete     = "restrict"     # no_action | restrict | set_null
actionstatusbehavior
no_action shipped Reject the delete if any child row references the parent. Same semantic as 'restrict' here.
restrict shipped Reject the delete with a typed FK error if any child row references the parent.
set_null shipped Set the referencing column to NULL in every child row, then delete the parent.
cascade deferred Recursively delete child rows. Roadmap.
set_default deferred Set the referencing column to its declared default. Roadmap.

CHECK expressions.

CHECK expressions support literals, = != < <= > >=, AND / OR / NOT, IS [NOT] NULL, and IN (..).

[[check_constraints]]
name       = "amount_positive"
expression = "amount >= 0 AND status IN ('pending', 'paid', 'shipped')"

[[check_constraints]]
name       = "refund_window"
expression = "refunded_at IS NULL OR refunded_at >= shipped_at"

3-valued logic.

CHECK uses the same semantic SQL has shipped for decades: any operand that is NULL makes the whole expression NULL, and a NULL expression passes the CHECK (it does not fail). That matches the industry-standard 3-valued-logic behaviour. If you want NULL to fail the CHECK, add an explicit IS NOT NULL guard.

Errors.

Constraint violations surface as typed HTTP errors: 409 Conflict with a constraint_violation body containing the constraint name, kind (foreign_key or check), and the rejected row's primary key. No silent skip, no partial commit.