//! AST for `.wo` schema-layer declarations. //! //! Stage 2 scope: `type` declarations with fields and `service rest` blocks. //! Policies, triggers, computed fields, link types, `##ui`, `##app`, `fn` bodies, //! and `##sql/##doc/##graph` blocks are parsed-and-discarded for now — the AST //! carries just enough to stand up a REST server that serves the declared types. #[derive(Debug, Clone, Default)] pub struct Schema { pub types: Vec, } #[derive(Debug, Clone)] pub struct TypeDecl { pub name: String, pub fields: Vec, pub services: Vec, /// Declared with `class` instead of `type`. Storage and REST are /// class-blind (plan 13 decision 5); the flag gates method parsing — /// `fn` members of a `class` compile into [`MethodDecl`]s (13b), while /// `fn` inside a plain `type` keeps the 13a parse-and-discard behaviour. pub is_class: bool, /// Row-scoped methods (plan 13b). Only populated for classes. pub methods: Vec, /// Storage configuration from a type-level `@table(...)` annotation. /// Every type/class IS a table regardless (plan 13 decisions 3/5) — /// `@table` configures storage, it never toggles it. pub table: TableCfg, } /// `@table(name: "prices", index: [product, at], index: [sku])` — optional /// storage configuration. `shard_key:`/`retention:` are reserved for later /// phases and rejected by the parser until they land. #[derive(Debug, Clone, Default)] pub struct TableCfg { /// Storage/table name override. Defaults to the type name. Consumed by /// the SQL layer and plans 10–12; WAL records keep the type name as the /// stable identifier. pub name: Option, /// Composite secondary indexes — one `index: [a, b]` entry each. pub indexes: Vec>, } /// `fn name(args) -> Ret [in txn [snapshot]] { body }` — a row-scoped /// transactional function with an implicit `self` receiver (plan 13 /// decision 2). Served over RPC as `POST /:id/`. #[derive(Debug, Clone)] pub struct MethodDecl { pub name: String, /// `(name, declared type)` — the type is diagnostic-only in Stage 2. pub params: Vec<(String, String)>, pub ret: Option, pub txn: TxnMode, pub body: Vec, } /// Transaction annotation on a method. The Stage-2 engine is single-threaded /// per shard, so every method already executes atomically and in isolation; /// the mode is recorded for diagnostics and for the future MVCC coordinator. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TxnMode { None, Txn, Snapshot, Serializable } /// Method-body statement — the schema-layer DML subset of /// `02-wo-language.md § Schema-Layer DML` that 13b executes. #[derive(Debug, Clone)] pub enum Stmt { /// `let name = expr` — binding is a snapshot (spec rule). Let { name: String, expr: Expr }, /// `insert Type { field: expr, ... }` — construction form. Insert { ty: String, fields: Vec<(String, Expr)> }, /// `return [expr]` Return { expr: Option }, /// `assert expr [otherwise abort ["msg"]]` — false aborts the txn. Assert { cond: Expr, msg: Option }, /// `if cond { ... } [else { ... }]` (else-if chains nest in `otherwise`). If { cond: Expr, then: Vec, otherwise: Vec }, } #[derive(Debug, Clone)] pub enum Expr { Int(i64), Str(String), Bool(bool), Null, /// Argument, `let` binding, or `self` (bound positionally — `self` stays /// a plain identifier in the lexer, plan 13 decision 4). Ident(String), /// `base.field` — plain object access, or relation resolution when the /// base is `self` and the field is a `multi`/`backlink` relation. Field(Box, String), /// `name(args)` — builtins: `latest`, `count`, `now`. Call(String, Vec), /// `select Type{ field == expr, other_field }` — schema-layer select per /// § Brace Disambiguation: operator entries are predicates, bare idents /// are projections. Evaluates to a set (array) of row objects, shaped by /// the projection when one is given. Equality predicates route through /// the engine's secondary indexes when the type declares a matching /// `@table(index: ...)`. Select { ty: String, predicates: Vec<(String, BinOp, Box)>, projection: Vec, }, Unary(UnOp, Box), Binary(BinOp, Box, Box), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum UnOp { Neg, Not } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BinOp { Add, Sub, Mul, Div, Mod, Eq, Ne, Lt, Le, Gt, Ge, And, Or, } #[derive(Debug, Clone)] pub struct Field { pub name: String, pub ty: FieldTy, pub nullable:bool, pub unique: bool, pub default: Option, /// True if this is a `ref T` / `multi T ...` / `backlink ...` field that doesn't /// correspond to a storage column in this type's table. The server ignores it /// for create/update/list scalar projection but exposes it via sub-endpoints. pub is_relation: bool, } #[derive(Debug, Clone)] pub enum FieldTy { /// Plain scalar — one of the well-known names (`Id`, `Text`, `Int`, etc.) or /// an unrecognised identifier that's compiled as opaque text for Stage 2. Scalar(String), /// `[T]` — array of the inner type. Array(Box), /// `{ k: T, ... }` — inline embedded-document struct. Struct(Vec), /// Tagged union: `A | B | C`. Stage 2 stores variants as strings. Union(Vec), /// `ref TypeName` — scalar FK to another type. Ref(String), /// `multi TypeName @edge(:TAG)` — zero-prop graph edge. MultiEdge { target: String, tag: Option }, /// `multi TypeName via LinkType` — graph edge with properties. MultiVia { target: String, link: String }, /// `backlink TypeName.field` — inverse relation. Backlink { target: String, field: String }, } #[derive(Debug, Clone)] pub enum DefaultExpr { Str(String), Int(i64), Bool(bool), Null, Now, Enum(String), // a bare identifier — e.g. `Customer` in a union default Opaque(String), // anything we didn't bother to evaluate (computed, etc.) } #[derive(Debug, Clone)] pub struct ServiceDecl { pub kind: ServiceKind, pub path: String, pub expose: Vec, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ServiceKind { Rest, Graphql, Native } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Operation { List, Get, Create, Update, Delete, Subscribe, Me, Custom, // anything we don't special-case — reserved } impl Operation { pub fn from_ident(s: &str) -> Operation { match s { "list" => Operation::List, "get" => Operation::Get, "create" => Operation::Create, "update" => Operation::Update, "delete" => Operation::Delete, "subscribe" => Operation::Subscribe, "me" => Operation::Me, _ => Operation::Custom, } } } impl Schema { pub fn merge(&mut self, other: Schema) { self.types.extend(other.types); } }