// Package exec runs a compiled Plan against the graph seam. This file is // the RowEval seam: every filter/projection evaluates through it, so the // executor never cares whether the interpreted or the columnar compiled // form is behind a given expression.
package exec import ( ) // RowEval is a bound expression in per-row-evaluable form. type RowEval interface { Eval(ctx *eval.Ctx, row []value.Value, slots map[string]int) value.Value } // interpExpr is the portable per-row evaluable: the AST run through the // interpreter. type interpExpr struct{ e ast.Expr } func ( interpExpr) ( *eval.Ctx, []value.Value, map[string]int) value.Value { return eval.Eval(, .e, , ) } // interpPinned counts expressions the differential test hook routed to // the interpreter, so the dual-path harness can assert its forced leg // really took the interpreted path -- a silently dead hook would compare // compiled against compiled and prove nothing (the colAggFired pattern). var interpPinned int // InterpPinned exposes the counter to the harness's liveness assertion. func () int { return interpPinned } // compileEval binds an expression for per-row evaluation: the columnar // compiled form when the graph asserts the native capability (and the // differential test hook doesn't pin the interpreter), else interpreted. func compileEval( *eval.Ctx, ast.Expr, map[string]int) RowEval { if .ForceInterp { interpPinned++ return interpExpr{} } if , := .G.(graph.Native); { return compile.New(, , , .Snapshot()) } return interpExpr{} } // hoistEval applies the backend's IN-list hoisting to a bound expression: // batch-constant lists bake a membership index, carried loop-invariant // lists cache per match-call. The interpreted form re-evaluates lists per // row (correct, just not pre-hashed), mirroring the Rust portable default. func hoistEval( *eval.Ctx, RowEval, , func(int) bool, []value.Value, map[string]int) RowEval { , := .(*compile.Compiled) if ! { return } return compile.HoistCarriedIn(compile.HoistConstIn(, , , , ), ) } // evalPushdown reports the row slots a bound expression reads (through // slots), whether it contains a node that pins it to the last op level, // and whether it walks the graph despite being slot-resolved (a memoized // subquery: reads only its memo slots, but every distinct correlation // tuple still pays a graph walk). The compiled form introspects its // resolved nodes; the interpreted form walks the AST conservatively. func evalPushdown( RowEval, ast.Expr, map[string]int, *[]int, , *bool) { if , := .(*compile.Compiled); { , , := compile.Slots() * = append(*, ...) * = * || * = * || return } astPushdown(, , , ) } // astPushdown is the interpreted path's conservative slot analysis. func astPushdown( ast.Expr, map[string]int, *[]int, *bool) { switch n := .(type) { case *ast.Lit: case *ast.Var: if , := [.Name]; { * = append(*, ) } case *ast.Prop: if , := [.Var]; { * = append(*, ) } case *ast.HasLabelExpr: if , := [.Var]; { * = append(*, ) } case *ast.Unary: (.Expr, , , ) case *ast.Binary: (.LHS, , , ) (.RHS, , , ) case *ast.ListExpr: for , := range .Elems { (, , , ) } case *ast.IsTruth: (.Expr, , , ) case *ast.IsTyped: (.Expr, , , ) case *ast.In: (.Expr, , , ) (.List, , , ) case *ast.IsNull: (.Expr, , , ) case *ast.Case: if .Operand != nil { (.Operand, , , ) } for , := range .Whens { (.Cond, , , ) (.Result, , , ) } if .Else != nil { (.Else, , , ) } default: // Subqueries, function calls, comprehensions, cost, map forms: // conservative last-level placement. * = true } }