593 lines
23 KiB
Plaintext
593 lines
23 KiB
Plaintext
|
|
// RelAlgIR: String Diagram IR for Relational Algebra
|
|||
|
|
//
|
|||
|
|
// Query plans are instances of this theory. The string diagram structure:
|
|||
|
|
// - Wire elements are edges (carrying typed data streams)
|
|||
|
|
// - Op elements are boxes (transforming data)
|
|||
|
|
// - Composition is implicit via wire sharing (same Wire as output of one Op and input of another)
|
|||
|
|
// - Cycles are allowed; well-formedness axioms ensure they contain delays
|
|||
|
|
//
|
|||
|
|
// See loose_thoughts/2026-01-19_19:45_relalg_ir_design.md for full design.
|
|||
|
|
//
|
|||
|
|
// This theory extends GeologMeta to get Srt, Func, Elem, etc.
|
|||
|
|
// References use qualified names: GeologMeta/Srt, GeologMeta/Func, etc.
|
|||
|
|
|
|||
|
|
theory RelAlgIR extends GeologMeta {
|
|||
|
|
|
|||
|
|
// ============================================================
|
|||
|
|
// SCHEMAS (types of data on wires)
|
|||
|
|
// ============================================================
|
|||
|
|
// Schemas describe the "shape" of tuples flowing on a wire.
|
|||
|
|
// They mirror DSort but are specific to the relational algebra context.
|
|||
|
|
|
|||
|
|
Schema : Sort;
|
|||
|
|
|
|||
|
|
// Unit schema: empty tuple (for sources with no input)
|
|||
|
|
UnitSchema : Sort;
|
|||
|
|
UnitSchema/schema : UnitSchema -> Schema;
|
|||
|
|
|
|||
|
|
// Base schema: single column of a given sort
|
|||
|
|
BaseSchema : Sort;
|
|||
|
|
BaseSchema/schema : BaseSchema -> Schema;
|
|||
|
|
BaseSchema/srt : BaseSchema -> GeologMeta/Srt;
|
|||
|
|
|
|||
|
|
// Product schema: S ⊗ T (concatenation of columns)
|
|||
|
|
ProdSchema : Sort;
|
|||
|
|
ProdSchema/schema : ProdSchema -> Schema;
|
|||
|
|
ProdSchema/left : ProdSchema -> Schema;
|
|||
|
|
ProdSchema/right : ProdSchema -> Schema;
|
|||
|
|
|
|||
|
|
// ============================================================
|
|||
|
|
// WIRES (edges in the string diagram)
|
|||
|
|
// ============================================================
|
|||
|
|
// Wires are first-class citizens. Each wire carries a stream of
|
|||
|
|
// tuples with a given schema. Composition is encoded by the same
|
|||
|
|
// Wire appearing as output of one Op and input of another.
|
|||
|
|
|
|||
|
|
Wire : Sort;
|
|||
|
|
Wire/schema : Wire -> Schema;
|
|||
|
|
|
|||
|
|
// ============================================================
|
|||
|
|
// OPERATIONS (boxes in the string diagram)
|
|||
|
|
// ============================================================
|
|||
|
|
|
|||
|
|
Op : Sort;
|
|||
|
|
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
// Sources (no input wires)
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
|
|||
|
|
// Scan: emit all elements of a sort
|
|||
|
|
// () → BaseSchema(srt)
|
|||
|
|
ScanOp : Sort;
|
|||
|
|
ScanOp/op : ScanOp -> Op;
|
|||
|
|
ScanOp/srt : ScanOp -> GeologMeta/Srt;
|
|||
|
|
ScanOp/out : ScanOp -> Wire;
|
|||
|
|
|
|||
|
|
// Constant: emit a single known element
|
|||
|
|
// () → BaseSchema(elem's sort)
|
|||
|
|
ConstOp : Sort;
|
|||
|
|
ConstOp/op : ConstOp -> Op;
|
|||
|
|
ConstOp/elem : ConstOp -> GeologMeta/Elem;
|
|||
|
|
ConstOp/out : ConstOp -> Wire;
|
|||
|
|
|
|||
|
|
// Empty: emit nothing (identity for union)
|
|||
|
|
// () → S
|
|||
|
|
EmptyOp : Sort;
|
|||
|
|
EmptyOp/op : EmptyOp -> Op;
|
|||
|
|
EmptyOp/out : EmptyOp -> Wire;
|
|||
|
|
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
// Unary operations (one input wire, one output wire)
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
|
|||
|
|
// Filter: keep tuples satisfying a predicate
|
|||
|
|
// S → S
|
|||
|
|
FilterOp : Sort;
|
|||
|
|
FilterOp/op : FilterOp -> Op;
|
|||
|
|
FilterOp/in : FilterOp -> Wire;
|
|||
|
|
FilterOp/out : FilterOp -> Wire;
|
|||
|
|
FilterOp/pred : FilterOp -> Pred;
|
|||
|
|
|
|||
|
|
// Project: select and reorder columns
|
|||
|
|
// S → T
|
|||
|
|
ProjectOp : Sort;
|
|||
|
|
ProjectOp/op : ProjectOp -> Op;
|
|||
|
|
ProjectOp/in : ProjectOp -> Wire;
|
|||
|
|
ProjectOp/out : ProjectOp -> Wire;
|
|||
|
|
ProjectOp/mapping : ProjectOp -> ProjMapping;
|
|||
|
|
|
|||
|
|
// Distinct: deduplicate tuples (collapse multiplicities to 0/1)
|
|||
|
|
// S → S
|
|||
|
|
DistinctOp : Sort;
|
|||
|
|
DistinctOp/op : DistinctOp -> Op;
|
|||
|
|
DistinctOp/in : DistinctOp -> Wire;
|
|||
|
|
DistinctOp/out : DistinctOp -> Wire;
|
|||
|
|
|
|||
|
|
// Negate: flip multiplicities (for computing differences)
|
|||
|
|
// S → S
|
|||
|
|
NegateOp : Sort;
|
|||
|
|
NegateOp/op : NegateOp -> Op;
|
|||
|
|
NegateOp/in : NegateOp -> Wire;
|
|||
|
|
NegateOp/out : NegateOp -> Wire;
|
|||
|
|
|
|||
|
|
// Apply function: add a column by applying a function
|
|||
|
|
// S → S ⊗ BaseSchema(cod)
|
|||
|
|
ApplyOp : Sort;
|
|||
|
|
ApplyOp/op : ApplyOp -> Op;
|
|||
|
|
ApplyOp/in : ApplyOp -> Wire;
|
|||
|
|
ApplyOp/out : ApplyOp -> Wire;
|
|||
|
|
ApplyOp/func : ApplyOp -> GeologMeta/Func;
|
|||
|
|
ApplyOp/arg_col : ApplyOp -> ColRef;
|
|||
|
|
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
// Binary operations (two input wires, one output wire)
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
|
|||
|
|
// Join: combine tuples from two sources where condition holds
|
|||
|
|
// S × T → S ⊗ T (filtered)
|
|||
|
|
JoinOp : Sort;
|
|||
|
|
JoinOp/op : JoinOp -> Op;
|
|||
|
|
JoinOp/left_in : JoinOp -> Wire;
|
|||
|
|
JoinOp/right_in : JoinOp -> Wire;
|
|||
|
|
JoinOp/out : JoinOp -> Wire;
|
|||
|
|
JoinOp/cond : JoinOp -> JoinCond;
|
|||
|
|
|
|||
|
|
// Union: combine tuples from two sources (Z-set addition)
|
|||
|
|
// S × S → S
|
|||
|
|
UnionOp : Sort;
|
|||
|
|
UnionOp/op : UnionOp -> Op;
|
|||
|
|
UnionOp/left_in : UnionOp -> Wire;
|
|||
|
|
UnionOp/right_in : UnionOp -> Wire;
|
|||
|
|
UnionOp/out : UnionOp -> Wire;
|
|||
|
|
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
// DBSP Temporal Operators
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
// These operate on streams over discrete time.
|
|||
|
|
// They are essential for incremental computation and feedback loops.
|
|||
|
|
|
|||
|
|
// Delay: z⁻¹, output at time t is input at time t-1
|
|||
|
|
// S → S
|
|||
|
|
// IMPORTANT: Delays break instantaneous cycles, making feedback well-founded.
|
|||
|
|
DelayOp : Sort;
|
|||
|
|
DelayOp/op : DelayOp -> Op;
|
|||
|
|
DelayOp/in : DelayOp -> Wire;
|
|||
|
|
DelayOp/out : DelayOp -> Wire;
|
|||
|
|
|
|||
|
|
// Differentiate: δ = 1 - z⁻¹, compute changes since last timestep
|
|||
|
|
// S → S (output is the delta/diff of input)
|
|||
|
|
DiffOp : Sort;
|
|||
|
|
DiffOp/op : DiffOp -> Op;
|
|||
|
|
DiffOp/in : DiffOp -> Wire;
|
|||
|
|
DiffOp/out : DiffOp -> Wire;
|
|||
|
|
|
|||
|
|
// Integrate: ∫ = Σ, accumulate all inputs over time
|
|||
|
|
// S → S (output is running sum of all inputs)
|
|||
|
|
// NOTE: Has implicit delay semantics, also breaks instantaneous cycles.
|
|||
|
|
IntegrateOp : Sort;
|
|||
|
|
IntegrateOp/op : IntegrateOp -> Op;
|
|||
|
|
IntegrateOp/in : IntegrateOp -> Wire;
|
|||
|
|
IntegrateOp/out : IntegrateOp -> Wire;
|
|||
|
|
|
|||
|
|
// ============================================================
|
|||
|
|
// PREDICATES (for filter conditions)
|
|||
|
|
// ============================================================
|
|||
|
|
|
|||
|
|
Pred : Sort;
|
|||
|
|
|
|||
|
|
// True: always satisfied
|
|||
|
|
TruePred : Sort;
|
|||
|
|
TruePred/pred : TruePred -> Pred;
|
|||
|
|
|
|||
|
|
// False: never satisfied
|
|||
|
|
FalsePred : Sort;
|
|||
|
|
FalsePred/pred : FalsePred -> Pred;
|
|||
|
|
|
|||
|
|
// Column equality: col_i = col_j
|
|||
|
|
ColEqPred : Sort;
|
|||
|
|
ColEqPred/pred : ColEqPred -> Pred;
|
|||
|
|
ColEqPred/left : ColEqPred -> ColRef;
|
|||
|
|
ColEqPred/right : ColEqPred -> ColRef;
|
|||
|
|
|
|||
|
|
// Constant equality: col = constant element
|
|||
|
|
ConstEqPred : Sort;
|
|||
|
|
ConstEqPred/pred : ConstEqPred -> Pred;
|
|||
|
|
ConstEqPred/col : ConstEqPred -> ColRef;
|
|||
|
|
ConstEqPred/val : ConstEqPred -> GeologMeta/Elem;
|
|||
|
|
|
|||
|
|
// Function result equality: f(col_arg) = col_result
|
|||
|
|
FuncEqPred : Sort;
|
|||
|
|
FuncEqPred/pred : FuncEqPred -> Pred;
|
|||
|
|
FuncEqPred/func : FuncEqPred -> GeologMeta/Func;
|
|||
|
|
FuncEqPred/arg : FuncEqPred -> ColRef;
|
|||
|
|
FuncEqPred/result : FuncEqPred -> ColRef;
|
|||
|
|
|
|||
|
|
// Function result equals constant: f(col_arg) = expected_elem
|
|||
|
|
FuncConstEqPred : Sort;
|
|||
|
|
FuncConstEqPred/pred : FuncConstEqPred -> Pred;
|
|||
|
|
FuncConstEqPred/func : FuncConstEqPred -> GeologMeta/Func;
|
|||
|
|
FuncConstEqPred/arg : FuncConstEqPred -> ColRef;
|
|||
|
|
FuncConstEqPred/expected : FuncConstEqPred -> GeologMeta/Elem;
|
|||
|
|
|
|||
|
|
// Conjunction: p ∧ q
|
|||
|
|
AndPred : Sort;
|
|||
|
|
AndPred/pred : AndPred -> Pred;
|
|||
|
|
AndPred/left : AndPred -> Pred;
|
|||
|
|
AndPred/right : AndPred -> Pred;
|
|||
|
|
|
|||
|
|
// Disjunction: p ∨ q
|
|||
|
|
OrPred : Sort;
|
|||
|
|
OrPred/pred : OrPred -> Pred;
|
|||
|
|
OrPred/left : OrPred -> Pred;
|
|||
|
|
OrPred/right : OrPred -> Pred;
|
|||
|
|
|
|||
|
|
// ============================================================
|
|||
|
|
// JOIN CONDITIONS
|
|||
|
|
// ============================================================
|
|||
|
|
|
|||
|
|
JoinCond : Sort;
|
|||
|
|
|
|||
|
|
// Equijoin: left.col_i = right.col_j
|
|||
|
|
EquiJoinCond : Sort;
|
|||
|
|
EquiJoinCond/cond : EquiJoinCond -> JoinCond;
|
|||
|
|
EquiJoinCond/left_col : EquiJoinCond -> ColRef;
|
|||
|
|
EquiJoinCond/right_col : EquiJoinCond -> ColRef;
|
|||
|
|
|
|||
|
|
// Cross join: cartesian product (no condition)
|
|||
|
|
CrossJoinCond : Sort;
|
|||
|
|
CrossJoinCond/cond : CrossJoinCond -> JoinCond;
|
|||
|
|
|
|||
|
|
// General predicate join
|
|||
|
|
PredJoinCond : Sort;
|
|||
|
|
PredJoinCond/cond : PredJoinCond -> JoinCond;
|
|||
|
|
PredJoinCond/pred : PredJoinCond -> Pred;
|
|||
|
|
|
|||
|
|
// ============================================================
|
|||
|
|
// COLUMN REFERENCES
|
|||
|
|
// ============================================================
|
|||
|
|
// References to specific columns within a schema.
|
|||
|
|
// Used in predicates and projections.
|
|||
|
|
|
|||
|
|
ColRef : Sort;
|
|||
|
|
ColRef/wire : ColRef -> Wire; // which wire's schema we're referencing
|
|||
|
|
ColRef/path : ColRef -> ColPath; // path into the schema
|
|||
|
|
|
|||
|
|
// Column path: navigate into nested product schemas
|
|||
|
|
ColPath : Sort;
|
|||
|
|
|
|||
|
|
// Here: we're at the target
|
|||
|
|
HerePath : Sort;
|
|||
|
|
HerePath/path : HerePath -> ColPath;
|
|||
|
|
|
|||
|
|
// Left: descend into left of product
|
|||
|
|
LeftPath : Sort;
|
|||
|
|
LeftPath/path : LeftPath -> ColPath;
|
|||
|
|
LeftPath/rest : LeftPath -> ColPath;
|
|||
|
|
|
|||
|
|
// Right: descend into right of product
|
|||
|
|
RightPath : Sort;
|
|||
|
|
RightPath/path : RightPath -> ColPath;
|
|||
|
|
RightPath/rest : RightPath -> ColPath;
|
|||
|
|
|
|||
|
|
// ============================================================
|
|||
|
|
// PROJECTION MAPPINGS
|
|||
|
|
// ============================================================
|
|||
|
|
// Specifies how to construct output columns from input columns.
|
|||
|
|
|
|||
|
|
ProjMapping : Sort;
|
|||
|
|
|
|||
|
|
// Projection entries (which input columns become which output columns)
|
|||
|
|
ProjEntry : Sort;
|
|||
|
|
ProjEntry/mapping : ProjEntry -> ProjMapping;
|
|||
|
|
ProjEntry/source : ProjEntry -> ColRef;
|
|||
|
|
ProjEntry/target_path : ProjEntry -> ColPath;
|
|||
|
|
|
|||
|
|
// ============================================================
|
|||
|
|
// REACHABILITY RELATIONS (for cycle analysis)
|
|||
|
|
// ============================================================
|
|||
|
|
|
|||
|
|
// w1 reaches w2 via some path through operations
|
|||
|
|
reaches : [from: Wire, to: Wire] -> Prop;
|
|||
|
|
|
|||
|
|
// Reachability through each operation type
|
|||
|
|
ax/reaches/scan : forall s : ScanOp, w : Wire.
|
|||
|
|
s ScanOp/out = w |- [from: w, to: w] reaches; // trivial self-reach for source
|
|||
|
|
|
|||
|
|
ax/reaches/filter : forall f : FilterOp, w1 : Wire, w2 : Wire.
|
|||
|
|
f FilterOp/in = w1, f FilterOp/out = w2 |- [from: w1, to: w2] reaches;
|
|||
|
|
|
|||
|
|
ax/reaches/project : forall p : ProjectOp, w1 : Wire, w2 : Wire.
|
|||
|
|
p ProjectOp/in = w1, p ProjectOp/out = w2 |- [from: w1, to: w2] reaches;
|
|||
|
|
|
|||
|
|
ax/reaches/distinct : forall d : DistinctOp, w1 : Wire, w2 : Wire.
|
|||
|
|
d DistinctOp/in = w1, d DistinctOp/out = w2 |- [from: w1, to: w2] reaches;
|
|||
|
|
|
|||
|
|
ax/reaches/negate : forall n : NegateOp, w1 : Wire, w2 : Wire.
|
|||
|
|
n NegateOp/in = w1, n NegateOp/out = w2 |- [from: w1, to: w2] reaches;
|
|||
|
|
|
|||
|
|
ax/reaches/apply : forall a : ApplyOp, w1 : Wire, w2 : Wire.
|
|||
|
|
a ApplyOp/in = w1, a ApplyOp/out = w2 |- [from: w1, to: w2] reaches;
|
|||
|
|
|
|||
|
|
ax/reaches/join/left : forall j : JoinOp, w1 : Wire, w2 : Wire.
|
|||
|
|
j JoinOp/left_in = w1, j JoinOp/out = w2 |- [from: w1, to: w2] reaches;
|
|||
|
|
|
|||
|
|
ax/reaches/join/right : forall j : JoinOp, w1 : Wire, w2 : Wire.
|
|||
|
|
j JoinOp/right_in = w1, j JoinOp/out = w2 |- [from: w1, to: w2] reaches;
|
|||
|
|
|
|||
|
|
ax/reaches/union/left : forall u : UnionOp, w1 : Wire, w2 : Wire.
|
|||
|
|
u UnionOp/left_in = w1, u UnionOp/out = w2 |- [from: w1, to: w2] reaches;
|
|||
|
|
|
|||
|
|
ax/reaches/union/right : forall u : UnionOp, w1 : Wire, w2 : Wire.
|
|||
|
|
u UnionOp/right_in = w1, u UnionOp/out = w2 |- [from: w1, to: w2] reaches;
|
|||
|
|
|
|||
|
|
ax/reaches/delay : forall d : DelayOp, w1 : Wire, w2 : Wire.
|
|||
|
|
d DelayOp/in = w1, d DelayOp/out = w2 |- [from: w1, to: w2] reaches;
|
|||
|
|
|
|||
|
|
ax/reaches/diff : forall d : DiffOp, w1 : Wire, w2 : Wire.
|
|||
|
|
d DiffOp/in = w1, d DiffOp/out = w2 |- [from: w1, to: w2] reaches;
|
|||
|
|
|
|||
|
|
ax/reaches/integrate : forall i : IntegrateOp, w1 : Wire, w2 : Wire.
|
|||
|
|
i IntegrateOp/in = w1, i IntegrateOp/out = w2 |- [from: w1, to: w2] reaches;
|
|||
|
|
|
|||
|
|
// Transitive closure
|
|||
|
|
ax/reaches/trans : forall w1 : Wire, w2 : Wire, w3 : Wire.
|
|||
|
|
[from: w1, to: w2] reaches, [from: w2, to: w3] reaches |-
|
|||
|
|
[from: w1, to: w3] reaches;
|
|||
|
|
|
|||
|
|
// ============================================================
|
|||
|
|
// INSTANTANEOUS REACHABILITY (paths without delay)
|
|||
|
|
// ============================================================
|
|||
|
|
// This relation tracks paths that do NOT go through DelayOp or IntegrateOp.
|
|||
|
|
// Used to detect "bad" feedback loops that would require instantaneous computation.
|
|||
|
|
|
|||
|
|
reaches_instant : [from: Wire, to: Wire] -> Prop;
|
|||
|
|
|
|||
|
|
// Same axioms as reaches, EXCEPT for DelayOp and IntegrateOp
|
|||
|
|
ax/reaches_instant/filter : forall f : FilterOp, w1 : Wire, w2 : Wire.
|
|||
|
|
f FilterOp/in = w1, f FilterOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
|||
|
|
|
|||
|
|
ax/reaches_instant/project : forall p : ProjectOp, w1 : Wire, w2 : Wire.
|
|||
|
|
p ProjectOp/in = w1, p ProjectOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
|||
|
|
|
|||
|
|
ax/reaches_instant/distinct : forall d : DistinctOp, w1 : Wire, w2 : Wire.
|
|||
|
|
d DistinctOp/in = w1, d DistinctOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
|||
|
|
|
|||
|
|
ax/reaches_instant/negate : forall n : NegateOp, w1 : Wire, w2 : Wire.
|
|||
|
|
n NegateOp/in = w1, n NegateOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
|||
|
|
|
|||
|
|
ax/reaches_instant/apply : forall a : ApplyOp, w1 : Wire, w2 : Wire.
|
|||
|
|
a ApplyOp/in = w1, a ApplyOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
|||
|
|
|
|||
|
|
ax/reaches_instant/join/left : forall j : JoinOp, w1 : Wire, w2 : Wire.
|
|||
|
|
j JoinOp/left_in = w1, j JoinOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
|||
|
|
|
|||
|
|
ax/reaches_instant/join/right : forall j : JoinOp, w1 : Wire, w2 : Wire.
|
|||
|
|
j JoinOp/right_in = w1, j JoinOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
|||
|
|
|
|||
|
|
ax/reaches_instant/union/left : forall u : UnionOp, w1 : Wire, w2 : Wire.
|
|||
|
|
u UnionOp/left_in = w1, u UnionOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
|||
|
|
|
|||
|
|
ax/reaches_instant/union/right : forall u : UnionOp, w1 : Wire, w2 : Wire.
|
|||
|
|
u UnionOp/right_in = w1, u UnionOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
|||
|
|
|
|||
|
|
// NOTE: No axioms for DelayOp or IntegrateOp!
|
|||
|
|
// They break instantaneous reachability.
|
|||
|
|
|
|||
|
|
// DiffOp is instantaneous (it uses delay internally but outputs immediately)
|
|||
|
|
ax/reaches_instant/diff : forall d : DiffOp, w1 : Wire, w2 : Wire.
|
|||
|
|
d DiffOp/in = w1, d DiffOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
|||
|
|
|
|||
|
|
// Transitive closure
|
|||
|
|
ax/reaches_instant/trans : forall w1 : Wire, w2 : Wire, w3 : Wire.
|
|||
|
|
[from: w1, to: w2] reaches_instant, [from: w2, to: w3] reaches_instant |-
|
|||
|
|
[from: w1, to: w3] reaches_instant;
|
|||
|
|
|
|||
|
|
// ============================================================
|
|||
|
|
// WELL-FORMEDNESS: NO INSTANTANEOUS CYCLES
|
|||
|
|
// ============================================================
|
|||
|
|
// Every cycle must contain at least one DelayOp or IntegrateOp.
|
|||
|
|
// This ensures feedback loops are computable via iteration.
|
|||
|
|
|
|||
|
|
ax/wf/no_instant_cycle : forall w : Wire.
|
|||
|
|
[from: w, to: w] reaches_instant |- false;
|
|||
|
|
|
|||
|
|
// ============================================================
|
|||
|
|
// WELL-FORMEDNESS: SCHEMA CONSISTENCY
|
|||
|
|
// ============================================================
|
|||
|
|
// Operations must connect wires with compatible schemas.
|
|||
|
|
|
|||
|
|
// Filter preserves schema
|
|||
|
|
ax/wf/filter_schema : forall f : FilterOp, w1 : Wire, w2 : Wire.
|
|||
|
|
f FilterOp/in = w1, f FilterOp/out = w2 |-
|
|||
|
|
w1 Wire/schema = w2 Wire/schema;
|
|||
|
|
|
|||
|
|
// Distinct preserves schema
|
|||
|
|
ax/wf/distinct_schema : forall d : DistinctOp, w1 : Wire, w2 : Wire.
|
|||
|
|
d DistinctOp/in = w1, d DistinctOp/out = w2 |-
|
|||
|
|
w1 Wire/schema = w2 Wire/schema;
|
|||
|
|
|
|||
|
|
// Negate preserves schema
|
|||
|
|
ax/wf/negate_schema : forall n : NegateOp, w1 : Wire, w2 : Wire.
|
|||
|
|
n NegateOp/in = w1, n NegateOp/out = w2 |-
|
|||
|
|
w1 Wire/schema = w2 Wire/schema;
|
|||
|
|
|
|||
|
|
// Delay preserves schema
|
|||
|
|
ax/wf/delay_schema : forall d : DelayOp, w1 : Wire, w2 : Wire.
|
|||
|
|
d DelayOp/in = w1, d DelayOp/out = w2 |-
|
|||
|
|
w1 Wire/schema = w2 Wire/schema;
|
|||
|
|
|
|||
|
|
// Diff preserves schema
|
|||
|
|
ax/wf/diff_schema : forall d : DiffOp, w1 : Wire, w2 : Wire.
|
|||
|
|
d DiffOp/in = w1, d DiffOp/out = w2 |-
|
|||
|
|
w1 Wire/schema = w2 Wire/schema;
|
|||
|
|
|
|||
|
|
// Integrate preserves schema
|
|||
|
|
ax/wf/integrate_schema : forall i : IntegrateOp, w1 : Wire, w2 : Wire.
|
|||
|
|
i IntegrateOp/in = w1, i IntegrateOp/out = w2 |-
|
|||
|
|
w1 Wire/schema = w2 Wire/schema;
|
|||
|
|
|
|||
|
|
// Union requires same schema on both inputs
|
|||
|
|
ax/wf/union_schema_left : forall u : UnionOp, wl : Wire, wr : Wire, wo : Wire.
|
|||
|
|
u UnionOp/left_in = wl, u UnionOp/right_in = wr, u UnionOp/out = wo |-
|
|||
|
|
wl Wire/schema = wo Wire/schema;
|
|||
|
|
|
|||
|
|
ax/wf/union_schema_right : forall u : UnionOp, wl : Wire, wr : Wire, wo : Wire.
|
|||
|
|
u UnionOp/left_in = wl, u UnionOp/right_in = wr, u UnionOp/out = wo |-
|
|||
|
|
wr Wire/schema = wo Wire/schema;
|
|||
|
|
|
|||
|
|
// Scan output schema must be BaseSchema of the scanned sort
|
|||
|
|
// (This requires existential in conclusion, which geometric logic supports)
|
|||
|
|
ax/wf/scan_schema : forall s : ScanOp, srt : GeologMeta/Srt, w : Wire.
|
|||
|
|
s ScanOp/srt = srt, s ScanOp/out = w |-
|
|||
|
|
exists bs : BaseSchema. bs BaseSchema/srt = srt, w Wire/schema = bs BaseSchema/schema;
|
|||
|
|
|
|||
|
|
// Join output schema is product of input schemas
|
|||
|
|
ax/wf/join_schema : forall j : JoinOp, wl : Wire, wr : Wire, wo : Wire.
|
|||
|
|
j JoinOp/left_in = wl, j JoinOp/right_in = wr, j JoinOp/out = wo |-
|
|||
|
|
exists ps : ProdSchema.
|
|||
|
|
ps ProdSchema/left = wl Wire/schema,
|
|||
|
|
ps ProdSchema/right = wr Wire/schema,
|
|||
|
|
wo Wire/schema = ps ProdSchema/schema;
|
|||
|
|
|
|||
|
|
// ============================================================
|
|||
|
|
// ALGEBRAIC LAWS (for query optimization)
|
|||
|
|
// ============================================================
|
|||
|
|
// These axioms express equivalences between query plans.
|
|||
|
|
// An optimizer uses these to transform plans into more efficient forms.
|
|||
|
|
//
|
|||
|
|
// Notation: We describe semantic equivalence between operators.
|
|||
|
|
// In practice, equivalence means the output wire produces the same Z-set.
|
|||
|
|
//
|
|||
|
|
// These are stated as properties (Prop-valued relations) rather than
|
|||
|
|
// equational axioms, since geolog's geometric logic doesn't have
|
|||
|
|
// built-in equality on terms. The optimizer interprets these as rewrite rules.
|
|||
|
|
|
|||
|
|
// Wire equivalence: two wires produce the same Z-set
|
|||
|
|
equiv : [a: Wire, b: Wire] -> Prop;
|
|||
|
|
|
|||
|
|
// Reflexivity
|
|||
|
|
ax/equiv/refl : forall w : Wire.
|
|||
|
|
|- [a: w, b: w] equiv;
|
|||
|
|
|
|||
|
|
// Symmetry
|
|||
|
|
ax/equiv/sym : forall w1 : Wire, w2 : Wire.
|
|||
|
|
[a: w1, b: w2] equiv |- [a: w2, b: w1] equiv;
|
|||
|
|
|
|||
|
|
// Transitivity
|
|||
|
|
ax/equiv/trans : forall w1 : Wire, w2 : Wire, w3 : Wire.
|
|||
|
|
[a: w1, b: w2] equiv, [a: w2, b: w3] equiv |- [a: w1, b: w3] equiv;
|
|||
|
|
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
// Filter Laws
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
|
|||
|
|
// Filter(True, x) ≡ x
|
|||
|
|
ax/filter_true : forall f : FilterOp, t : TruePred, wi : Wire, wo : Wire.
|
|||
|
|
f FilterOp/pred = t TruePred/pred, f FilterOp/in = wi, f FilterOp/out = wo |-
|
|||
|
|
[a: wo, b: wi] equiv;
|
|||
|
|
|
|||
|
|
// Filter(False, x) ≡ Empty
|
|||
|
|
// (Every tuple is filtered out, result is empty)
|
|||
|
|
// This would need EmptyOp with matching schema; omitted for simplicity.
|
|||
|
|
|
|||
|
|
// Filter-Filter Fusion: Filter(p, Filter(q, x)) ≡ Filter(p ∧ q, x)
|
|||
|
|
// Expressed as: If f2.in = f1.out, then there exists a fused filter.
|
|||
|
|
ax/filter_fusion : forall f1 : FilterOp, f2 : FilterOp,
|
|||
|
|
p1 : Pred, p2 : Pred,
|
|||
|
|
wi : Wire, wm : Wire, wo : Wire.
|
|||
|
|
f1 FilterOp/in = wi, f1 FilterOp/out = wm, f1 FilterOp/pred = p1,
|
|||
|
|
f2 FilterOp/in = wm, f2 FilterOp/out = wo, f2 FilterOp/pred = p2 |-
|
|||
|
|
exists f3 : FilterOp, pa : AndPred.
|
|||
|
|
pa AndPred/left = p1, pa AndPred/right = p2,
|
|||
|
|
f3 FilterOp/in = wi, f3 FilterOp/pred = pa AndPred/pred,
|
|||
|
|
[a: wo, b: f3 FilterOp/out] equiv;
|
|||
|
|
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
// Distinct Laws
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
|
|||
|
|
// Distinct is idempotent: Distinct(Distinct(x)) ≡ Distinct(x)
|
|||
|
|
ax/distinct_idem : forall d1 : DistinctOp, d2 : DistinctOp,
|
|||
|
|
wi : Wire, wm : Wire, wo : Wire.
|
|||
|
|
d1 DistinctOp/in = wi, d1 DistinctOp/out = wm,
|
|||
|
|
d2 DistinctOp/in = wm, d2 DistinctOp/out = wo |-
|
|||
|
|
[a: wo, b: wm] equiv;
|
|||
|
|
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
// Union Laws
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
|
|||
|
|
// Union is commutative: Union(x, y) ≡ Union(y, x)
|
|||
|
|
ax/union_comm : forall u1 : UnionOp, wl : Wire, wr : Wire, wo : Wire.
|
|||
|
|
u1 UnionOp/left_in = wl, u1 UnionOp/right_in = wr, u1 UnionOp/out = wo |-
|
|||
|
|
exists u2 : UnionOp.
|
|||
|
|
u2 UnionOp/left_in = wr, u2 UnionOp/right_in = wl,
|
|||
|
|
[a: wo, b: u2 UnionOp/out] equiv;
|
|||
|
|
|
|||
|
|
// Union is associative: Union(x, Union(y, z)) ≡ Union(Union(x, y), z)
|
|||
|
|
ax/union_assoc : forall u1 : UnionOp, u2 : UnionOp,
|
|||
|
|
wa : Wire, wb : Wire, wc : Wire, wyz : Wire, wo : Wire.
|
|||
|
|
u2 UnionOp/left_in = wb, u2 UnionOp/right_in = wc, u2 UnionOp/out = wyz,
|
|||
|
|
u1 UnionOp/left_in = wa, u1 UnionOp/right_in = wyz, u1 UnionOp/out = wo |-
|
|||
|
|
exists u3 : UnionOp, u4 : UnionOp, wab : Wire.
|
|||
|
|
u3 UnionOp/left_in = wa, u3 UnionOp/right_in = wb, u3 UnionOp/out = wab,
|
|||
|
|
u4 UnionOp/left_in = wab, u4 UnionOp/right_in = wc,
|
|||
|
|
[a: wo, b: u4 UnionOp/out] equiv;
|
|||
|
|
|
|||
|
|
// Union with Empty: Union(x, Empty) ≡ x
|
|||
|
|
ax/union_empty_right : forall u : UnionOp, e : EmptyOp, wi : Wire, we : Wire, wo : Wire.
|
|||
|
|
e EmptyOp/out = we, u UnionOp/left_in = wi, u UnionOp/right_in = we, u UnionOp/out = wo |-
|
|||
|
|
[a: wo, b: wi] equiv;
|
|||
|
|
|
|||
|
|
ax/union_empty_left : forall u : UnionOp, e : EmptyOp, wi : Wire, we : Wire, wo : Wire.
|
|||
|
|
e EmptyOp/out = we, u UnionOp/left_in = we, u UnionOp/right_in = wi, u UnionOp/out = wo |-
|
|||
|
|
[a: wo, b: wi] equiv;
|
|||
|
|
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
// Negate Laws
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
|
|||
|
|
// Double negation: Negate(Negate(x)) ≡ x
|
|||
|
|
ax/negate_involution : forall n1 : NegateOp, n2 : NegateOp,
|
|||
|
|
wi : Wire, wm : Wire, wo : Wire.
|
|||
|
|
n1 NegateOp/in = wi, n1 NegateOp/out = wm,
|
|||
|
|
n2 NegateOp/in = wm, n2 NegateOp/out = wo |-
|
|||
|
|
[a: wo, b: wi] equiv;
|
|||
|
|
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
// Join Laws
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
|
|||
|
|
// Cross join is commutative (up to column reordering)
|
|||
|
|
// Note: The output schemas differ, so this needs a projection to swap columns.
|
|||
|
|
// Omitted for now as it requires more complex schema manipulation.
|
|||
|
|
|
|||
|
|
// Join with Empty: Join(x, Empty) ≡ Empty
|
|||
|
|
ax/join_empty_right : forall j : JoinOp, e : EmptyOp, wi : Wire, we : Wire, wo : Wire.
|
|||
|
|
e EmptyOp/out = we, j JoinOp/left_in = wi, j JoinOp/right_in = we, j JoinOp/out = wo |-
|
|||
|
|
exists e2 : EmptyOp. [a: wo, b: e2 EmptyOp/out] equiv;
|
|||
|
|
|
|||
|
|
ax/join_empty_left : forall j : JoinOp, e : EmptyOp, wi : Wire, we : Wire, wo : Wire.
|
|||
|
|
e EmptyOp/out = we, j JoinOp/left_in = we, j JoinOp/right_in = wi, j JoinOp/out = wo |-
|
|||
|
|
exists e2 : EmptyOp. [a: wo, b: e2 EmptyOp/out] equiv;
|
|||
|
|
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
// DBSP Laws
|
|||
|
|
// ------------------------------------------------------------
|
|||
|
|
|
|||
|
|
// Differentiation is inverse of integration (for streams of changes)
|
|||
|
|
// Diff(Integrate(x)) ≡ x (for Δ-streams)
|
|||
|
|
// Integrate(Diff(x)) ≡ x - x₀ (up to initial value)
|
|||
|
|
// These are more subtle and depend on stream semantics; omitted for now.
|
|||
|
|
|
|||
|
|
// Delay respects Union: z⁻¹(x ∪ y) ≡ z⁻¹(x) ∪ z⁻¹(y)
|
|||
|
|
ax/delay_union : forall u : UnionOp, d : DelayOp,
|
|||
|
|
wl : Wire, wr : Wire, wu : Wire, wo : Wire.
|
|||
|
|
u UnionOp/left_in = wl, u UnionOp/right_in = wr, u UnionOp/out = wu,
|
|||
|
|
d DelayOp/in = wu, d DelayOp/out = wo |-
|
|||
|
|
exists dl : DelayOp, dr : DelayOp, u2 : UnionOp.
|
|||
|
|
dl DelayOp/in = wl, dr DelayOp/in = wr,
|
|||
|
|
u2 UnionOp/left_in = dl DelayOp/out, u2 UnionOp/right_in = dr DelayOp/out,
|
|||
|
|
[a: wo, b: u2 UnionOp/out] equiv;
|
|||
|
|
}
|