Churcuring and CQL#
1. Overview#
Churcuring is a declarative programming toolchain targeting MuduDB. Its core language is CQL (Churcuring Query Language) — a declarative, strongly typed query and business-logic language. CQL borrows set comprehensions, quantifiers, and definition structure from TLA+, uses Rust-style naming, and aims to compile formal specifications directly into executable, verifiable database logic.
Key design points:
No JOIN, ever: every cross-table association is an explicit
lookupover keyed tables (table = { Key → Object }). Joins are eliminated at the language and compiler level, not merely discouraged.Effect tiers: all computation is pure; the only side effects are table reads (
read/lookup) and writes (insert/update/delete), organized into three operator tiers (L0function/ L1query/ L2action) that may only compose upward.Total determinism: same snapshot + same parameters ⇒ bit-identical results, including identical trap behavior.
Provable termination by default: comprehensions, quantifiers,
fold, and structural recursion (function recursive, checked by a termination pass) are theorem-level terminating; general recursion is an escape hatch verified by bounded model checking (with depth n).Verifiable: built-in
testblocks and a Stateright-based bounded model checker give per-property verdicts forinvariants and temporalpropertys.
Relationship to MuduDB: MuduDB is one of CQL’s target backends — the plan is to compile CQL into MuduDB WASI components that execute through query/command syscalls (see “Development Status” below; that backend is currently a proposal-stage placeholder). The default backend available today is Rust code generation.
2. Development Status#
Note: Churcuring is under active development and not all features are implemented yet. The following limitations apply to the current version:
usesupports only single-segment module names (use util;); cross-module query/action calls are not supported (compile-time error).The
mududbbackend is a proposal-stage placeholder:cqlc build --backend mududbonly emits a deployment-plan text file and does not build a component; the syscall contract is still a proposal.Model checking covers a v1 fragment: bool/int expressions only, tables with int keys and a single int value field; prime (next-state),
~>, anduntilare skipped with a warning by the Stateright backend;fairnessdeclarations are accepted but no backend enforces them (warning only);--replay(counterexample replay) is unimplemented.The z3 model-checking backend is unavailable by default (requires the extra
z3feature and a prebuilt Z3);cqlc verifyexposes only the Stateright backend.IndexScan read plans currently compile to filtered full scans (result semantics unaffected — performance only).
The tree-sitter wasm grammar package for the VSCode extension is not built yet; syntax highlighting degrades gracefully.
3. Syntax Overview#
This section is a tour; normative details live in doc/cn/cql.md of the churcuring
repository (Chinese authoritative version).
3.1 Modules and Projects#
Each .cql file is a module whose first line declares the module name. A project is
delimited by cql.toml; cqlc builds a dependency graph from use declarations and
compiles in topological order — cyclic dependencies are forbidden.
module shop;
use util; // import all public items of module util
table users { id: int, name: string, city: string } primary key {id}
query large_orders() -> set<orders> == {
read(orders, lambda(o) { is_large_amount(o.amount) })
}
Declarations marked public can be used across modules; tables cannot cross modules
(a schema and the queries/actions that access it directly must live in the same module).
3.2 Types#
Basic types: bool, int (i64), float (f64), string (UTF-8), decimal(m, n)
arbitrary-precision fixed-point, date.
Container and composite types:
option<int> // possibly absent; constructors some(x) / none
vector<int> // ordered sequence, [1, 2, 3]
set<string> // unordered, deduplicated; set {1, 2}
bag<float> // multiset; bag {1.0, 2.0, 2.0}
map<string, int> // pure associative value; map { "a": 1 }
(int, string) // tuple; projection t.0 / t.1
{ id: int, name: string } // record type
int -> int // pure function type (right-associative)
A table declaration table users { ... } automatically derives three types: users (the
full row type), key users (the key type), and value users (the non-key field record);
lookup(users, k) returns option<value users>. There are also enums (variants may
carry payloads, be generic, and be recursive) and generic functions (explicit arguments use
turbofish: f::<int>(x)). No implicit conversions; as conversions follow a whitelist.
3.3 Declarations at a Glance#
const max_retries: int == 3; // compile-time constant
type user_id == int; // type alias
table orders { order_id: int, user_id: int, amount: float }
primary key {order_id}
foreign key {user_id} references users // table + key constraints
index sessions_by_user on sessions(user_id) // secondary index
function is_adult(age: int) -> bool == { age >= 18 } // pure function (L0)
query orders_by_user(user_id: int) -> set<orders> == { // query (L1, snapshot reads)
read(orders, lambda [user_id](o) { o.user_id = user_id })
}
action add_user(id: int, name: string, city: string) -> set<write_op> == { // action (L2)
set { insert(users, record { id: id, name: name, city: city }) }
}
invariant non_negative on orders == \A o \in orders : o.amount >= 0.0
property balance_ok == [](total_balance() = 10000) // temporal property
test transfer_basic { // test block
fixture accounts == [record { id: 1, owner: "a", balance: 6000 }];
expect total_balance() == 6000;
}
Key points: definitions always use ==, predicate equality uses =; operator bodies are
always blocks { ... }.
3.4 Effect Tiers#
Tier |
Construct |
Allowed effects |
|---|---|---|
L0 |
|
none (pure) |
L1 |
|
snapshot reads ( |
L2 |
|
snapshot reads + produce |
Tiers in the call graph may only stay level or rise: a function may only call
functions; a query may call functions/querys (sharing one snapshot); an action
may call all tiers (write_op sets of called actions merge in; atomicity only at the top
level). Calling downward is a compile error. Lambda bodies are always L0.
Three write_op constructors: insert(t, row) (key must not exist), update(t, k, f) (key
must exist; f is evaluated against the current row value at apply time), delete(t, k)
(no-op if the key does not exist). Apply order: conflict check → FK validation → invariant
validation; any violation rejects the whole action (no writes applied).
3.5 Expressions#
-- blocks and let (the block's value = its final expression)
{ let active == set { v \in users if v.active };
set { u.name : u \in active } }
-- if / match (expressions; match exhaustiveness is statically checked)
if f.balance >= amt then set { ... } else set {}
match lookup(users, id) { some(v) => v.name, none => "unknown" }
-- set comprehension (two forms; result is set<T>, deduplicated)
set { x \in users if x.active } -- filter form
set { (o.order_id, u.name) : o \in orders, u \in lookup(users, o.user_id) } -- map form
-- quantifiers
\A o \in orders : o.amount >= 0.0
\E u \in users : u.city = "x"
-- lambda: the capture list must explicitly name referenced outer locals
lambda [new_city](v) { record { v with city: new_city } }
-- ? propagation sugar: none makes the whole operator body none
{ let u == lookup(users, user_id)?; some(u.city) }
3.6 Termination#
Two tiers:
function recursive inorder(t: tree) -> vector<int> == { -- structural recursion: termination pass proves termination
match t {
leaf(v) => [v],
node(l, x, r) => concat_vector(concat_vector(inorder(l), [x]), inorder(r))
}
}
function gcd(a: int, b: int) -> int == { -- general recursion: free-form, may trap on stack exhaustion
if b = 0 then a else gcd(b, a % b)
}
query subordinates(mgr_id: int) -> set<int> with depth 32 == { ... } -- model-checking depth bound
4. Compilation and Toolchain#
The Churcuring toolchain is a Rust workspace. Main components:
Component |
Role |
|---|---|
|
tree-sitter grammar and parser |
|
compiler library: parse → name resolution → effect check → type check → termination → desugar → optimize → codegen |
|
runtime library: Value types, Table trait, in-memory tables, pure stdlib functions |
|
model checker (Stateright / z3 backends) |
|
the |
4.1 Building the Toolchain#
Prerequisite: Rust nightly 1.94. From the churcuring repository root:
cargo build --workspace --offline # produces ./target/debug/cqlc
cargo test --workspace --offline # full test suite
4.2 cqlc Subcommands#
cqlc <cmd> [path] searches upward from path for cql.toml to enter project mode;
passing a .cql file directly selects single-file zero-config mode.
cqlc new <name>: scaffolds<name>/cql.toml+<name>/src/main.cql.cqlc check [path]: runs the full pipeline and reports diagnostics only (rendered graphically via miette, with source snippets and spec section references).cqlc build [path] [--backend rust|mududb]: after a successful check, generates code per module in topological order intoout_dir, then runscargo build --offline. Therustbackend (default) writes a standalone cargo crate; themududbbackend currently only emits a deployment-plan text file (proposal-stage placeholder).cqlc test [path]: generates the crate and runscargo test(CQLtestblocks compile to Rust#[test]s).cqlc verify [path]: model checking (see section 5).cqlc clean [path]: deletesout_dir.
4.3 cql.toml#
[package]
name = "shop" # required; also used as the generated crate's package name
version = "0.1.0" # optional, default "0.1.0"
[build]
source_root = "src" # source root, default "src"
out_dir = "target/cql" # generated-code directory, default "target/cql"
backend = "rust" # "rust" (default) | "mududb" (proposal-stage placeholder)
[mududb] # used when backend = "mududb" (draft)
app = "shop"
sql_adapter = "off" # "off" (default) | "sqlite" | "postgres" | "mysql"
5. Testing and Verification#
5.1 test Blocks#
CQL source files may contain test blocks: fixture builds in-memory tables by primary
key, expect compares with predicate equality. cqlc test compiles them into Rust
#[test]s and runs cargo test:
test transfer_basic {
fixture accounts == [record { id: 1, owner: "a", balance: 6000 }];
expect total_balance() == 6000;
}
./target/debug/cqlc test examples/bank_project
5.2 Model Checking (cqlc verify)#
cqlc verify (project mode only) lowers the desugared AST to a model-checking spec (v1
fragment: bool/int expressions + tables with int keys and a single int value field) and
the Stateright explicit-state backend gives a verdict per property:
$ ./target/debug/cqlc verify examples/bank_project
verifying `bank` (stateright): 1 table(s), 1 transition(s), 2 of 2 propert(ies), k=8
PROVED(stateright-exhaustive) balance_conserved
PROVED(stateright-exhaustive) no_negative
result: all 2 propert(ies) hold within the bounds
Checking has two layers: bounded (Always: invariants/safety) and temporal (Eventually); select them with
--bounded/--temporal;--depth Noverrides recursion depth bounds;--trace Noverrides the trace length bound (default k=8).Counterexamples come with a shortest path (BFS), each step rendering the action, arguments, result, and state diff.
Discipline of claims:
PROVEDmeans proved within the bounded model only; prime/~>/untilproperties are skipped (warning, not counted in the verdict).
verify.toml (at the project root, next to cql.toml; all defaults if missing):
[depth]
default = 32 # default recursion depth bound
subordinates = 16 # per-operator override
[domain]
accounts.id = "1..2" # key/field domain: range string
accounts.balance = [0, 6000, 4000] # or an explicit value set (integers only)
[trace]
length = 8 # trace length bound k (default 8)
6. Example Projects#
The churcuring repository’s examples/ directory contains compilable examples:
Example |
Contents |
|---|---|
|
multi-file project ( |
|
bank transfer example with |
|
single-file zero-config example |
Common smoke-test commands:
./target/debug/cqlc check examples/shop_project
./target/debug/cqlc test examples/bank_project
./target/debug/cqlc verify examples/bank_project
7. References#
Specifications and guides inside the churcuring repository (churcuring-p/):
Document |
Contents |
|---|---|
|
CQL language specification (type system, modules, expressions, semantic rules) |
|
CQL language tour (syntax walkthrough with compilable examples) |
|
full reference for |
|
build guide (environment, offline builds, platform gotchas) |
|
formal model-checking mechanism (bounded/temporal layers, dual-backend architecture) |
|
mududb backend: query/command syscall contract (proposal) |
(The doc/en/ directory contains English translations of these documents.)