AST nodes
The canonical intermediate language Vadyl compiles to provider-specific execution. Read, write, DDL, document, key-value, graph, capabilities.
Every database operation in Vadyl is expressed as a typed AST. Providers compile the AST to their native dialect (T-SQL, PL/pgSQL, MongoDB aggregation pipeline, Cypher, CQL, …). There is no raw SQL string-building outside provider implementations — the AST is the canonical contract every plane consumes.
AST families
- ASTCore — primitives (literals, references, columns, scalar expressions).
- ASTRead — query plans (project, filter, sort, page, group, join, union).
- ASTWrite — mutation plans (insert, update, delete, upsert, returning).
- ASTDDL — schema operations (create table, alter column, create index, RLS policy, …).
- ASTRelational — relational-specific (CTEs, window functions, lateral joins).
- ASTDocument — document operations (collection scan, aggregation pipeline, doc filter, doc project).
- ASTKeyValue — KV operations (get, put, delete, scan, batch, expire).
- ASTGraph — graph operations (vertex scan, edge scan, traverse, shortest path, neighbors).
- ASTMigrations — typed migration ops (add field, rename, drop, change type, copy, backfill).
- ASTCapabilities — capability declarations and constraints.
BoolExpr
Predicates use a typed BoolExpr tree:
type BoolExpr =
| { kind: "BoolLiteral"; value: boolean }
| { kind: "And"; left: BoolExpr; right: BoolExpr }
| { kind: "Or"; left: BoolExpr; right: BoolExpr }
| { kind: "Not"; inner: BoolExpr }
| { kind: "Cmp"; op: CmpOp; left: ScalarExpr; right: ScalarExpr }
| { kind: "IsNull"; expr: ScalarExpr }
| { kind: "InList"; expr: ScalarExpr; values: ScalarExpr[] }
| { kind: "Between"; expr: ScalarExpr; low: ScalarExpr; high: ScalarExpr }
| { kind: "Like"; expr: ScalarExpr; pattern: ScalarExpr; caseInsensitive?: boolean }
| { kind: "Exists"; sub: QueryPlan }
| { kind: "InSubquery"; expr: ScalarExpr; sub: QueryPlan }
| { kind: "QuantifiedCmp"; op: CmpOp; expr: ScalarExpr; sub: QueryPlan; quantifier: "Any" | "All" }
// Access predicates — resolved before reaching renderers
| { kind: "HasRole"; role: string }
| { kind: "HasClaim"; name: string; value: ScalarExpr }
| { kind: "InContextSet"; setName: string }
| { kind: "AuthStrengthAtLeast"; level: AuthStrength }
| { kind: "AuthenticatedVia"; connector: string }
| { kind: "SubjectTypeIs"; subject: SubjectType }
| { kind: "SessionAgeLt"; duration: Duration };ScalarExpr
type ScalarExpr =
| { kind: "Literal"; value: unknown; type: ScalarTypeRef }
| { kind: "ColRef"; columnName: string }
| { kind: "ContextValue"; key: string }
| { kind: "Func"; name: string; args: ScalarExpr[] }
| { kind: "Cast"; expr: ScalarExpr; targetType: ScalarTypeRef }
| { kind: "Case"; whens: CaseWhen[]; else?: ScalarExpr }
| { kind: "Coalesce"; exprs: ScalarExpr[] };Semantic lowering
AstSemanticLowering normalizes ASTs at plan consumption boundaries before any renderer sees them. The canonical example: Cmp(Eq, x, Literal(null)) rewrites to IsNull(x); Cmp(Ne, x, Literal(null)) rewrites to Not(IsNull(x)). Renderers can assume Cmp operands are non-null after lowering.
Provider compilation
Each provider implements the canonical render contracts:
SharedQueryRendering.RenderPredicateCore— SQL-standard BoolExpr to dialect SQL.SharedMutationRendering.RenderBoolExprCore— same for mutation paths with parameterized bindings.IDialectAdapter— provider-specific hooks (case-insensitive LIKE, regex, IS DISTINCT FROM, security predicates, collation, IN-list limits).
Adding a new BoolExpr / ScalarExpr node type requires updating both shared cores plus dialect implementations on every relational provider. Document / KV / graph providers handle their own AST families.
Why an AST
An AST gives Vadyl one canonical place to apply security predicates, cache decisions, audit annotations, query optimization, and cross-provider plan rewriting — without depending on string-level SQL parsing. It's how the same canonical operation works across seven storage families.