package plan

Import Path
	github.com/freeeve/gochickpeas/gql/internal/plan (on go.dev)

Dependency Relation
	imports 11 packages, and imported by 3 packages

Involved Source Files buildSegment: lower one run of stage specs plus its projection boundary into a Segment (port of the Rust plan.rs::build_segment, cost branch hard-wired as the only strategy). Anchor scan-source selection and pruning analysis for match-stage lowering: choosing an anchor's scan source (id seek, text-index scan, EXISTS-seed candidate scan, or the base label/property scan), deriving the backward seed walks from a correlated EXISTS, and flagging bounded var-expands whose duplicate endpoints can collapse under a multiplicity-insensitive projection. Split from build.go, which holds the segment and match-stage lowering. CALL-procedure planning (port of the Rust plan/call.rs, grown for expression arguments): resolve the procedure kind by name, fold constant arguments and validate them at bind time, and map the YIELD fields to output slots. A call with any non-constant argument is correlated: its arguments are checked against the in-scope variables here and evaluated per input row at exec, where a row whose arguments fail validation yields no rows (total-eval semantics; static mistakes still error here). Argument coercion and validation helpers for procedure resolution: each reads one positional CALL argument (or a list form) as a typed value, returning a plan error naming the procedure, argument, and position on a mismatch. Split from call.go, which holds the CALL stage building and procedure resolution. Columnar-aggregate candidate marking: a segment whose whole job is a bare single-label scan feeding an aggregation is a candidate for the fused one-pass column operator (exec's tryColumnarAgg). The plan side checks only STRUCTURE -- stage and aggregate shapes -- and never looks at expressions or the graph; the executor classifies every filter, group key, and aggregate argument against typed columns at run time and falls back to the general chain when anything declines, so the marking is purely an opportunity, never a semantic commitment. The trigger is shape-generic by construction: any unseen query with the same structure is marked identically. Cardinality-aware planning helpers (port of the Rust plan/cost.rs). Every count is an exact resident statistic, never an estimate; a probe ABSTAINS on Param/NamedParam/Null literals (no value at plan time -- resolving one would bake a call's value into a shared cached plan), so autoparam'd templates plan value-independently. Heuristic forward cardinality estimates for EXPLAIN/PROFILE (port of the Rust estimate.rs): one estimated output row-count per operator, propagated from the anchor's exact leaf cardinality through each expand's average fan-out, plus the reconstructed [anchor: ...] note for each anchored MATCH stage. Estimates describe the shape of cardinality growth, not a guarantee; they render unconditionally here (the planner is always cost-based). The exhaustive expression-variable walkers: the shared read descent behind the reorder correlation guard (collectAllVars: every mentioned name, binder locals included) and the scope-subset validators (freeVarsOutside: free references only), plus the alpha-rename (renameFree). A new AST kind is added here and covered by the per-kind roll call in the walker invariant tests. Sort-limit fusion: a trailing segment that only orders, paginates, and re-projects the previous segment's columns forces that segment to materialize every row it produces, even though at most skip+limit can ever surface. Donating the ORDER BY / OFFSET / LIMIT upstream lets the producing segment's bounded sink retain skip+limit rows and refuse the rest before they are built (the NEXT-authored ORDER BY form of the LDBC IC queries). The donor keeps its projection, now over the already ordered, already truncated rows. Early shortest-path row gating: when a segment's whole job is one non-expanding ANY SHORTEST stage and the following boundary chain is a pure per-row LET/FILTER pipeline, the path search plus every gate-evaluable filter conjunct also runs INSIDE the previous segment, inserted before its remaining expansion stages -- so rows the chain would kill never pay the expansion (the BFS-first order a hand-written kernel uses). The injection is a semijoin-style copy, not a move: the downstream stages run unchanged, which keeps the transform sound by construction -- the gate only removes rows a later deterministic per-row filter would remove anyway (the path search is a pure function of its endpoint pair and the graph). Bindings hide past the segment's projected names, so nothing else can observe them. OPTIONAL-MATCH group-join decorrelation (port of the Rust sibling's technique): when an OPTIONAL MATCH's bindings are consumed ONLY through bare null-skipping duplicate-decomposable aggregates grouped by outer variables, the clause is semantically a grouped left join whose inner pattern can be planned as a standalone query -- free to take its own selective anchor, where the nested walk is chained to the outer rows' bindings. The inner executes once into a key -> aggregate-values table, each outer row binds synthetic columns by correlation-key lookup with the aggregates' empty-group identities as the fill, and the segment projection re-aggregates those columns (count -> sum), which reproduces the nested left-join answer exactly, including over duplicate outer rows. The rewrite's dual: a SELECTIVE outer's nested walk is cheap while the standalone inner enumerates globally, so it is gated on the estimated outer breadth -- below the floor the nested execution stays. An inner-side estimate gate was tried and rejected on the Rust side: the estimator prices the inner at raw average fan, blind to the selective anchor that actually runs. Hash-join extraction: when a segment's bind chain re-executes an independent branch of the pattern per row of a large intermediate (the branch reads nothing the intermediate bound), nesting multiplies the two branches' costs. This pass rewrites that shape into a HashJoinStage -- build the independent branch once, keyed by the variable a connecting expand joins it back through, and probe per outer row -- purely on estimated cardinality structure, never on query identity. It runs after markRelUniqueness so every op keeps the uniqueness flags of the original nested order; the executor's capture/replay protocol (see exec) makes the reorganized row multiset exactly the nested one. Hash-join detection: the cost model that decides where the rewrite in hashjoin.go applies. hashJoinOnce walks the stage list once, estimating each stage's input/output cardinality and the cumulative estimate at which every slot was first bound, then gates pivots on the thresholds below -- a stage worth protecting from a multiplying re-expansion. All decisions read estimated cardinality structure, never query identity. Package plan compiles a desugared query into the plan IR the executor consumes: a sequence of Segments split at each projection boundary, each a scan/expand chain plus a projection. This file holds the operator IR (port of the Rust plan/ir.rs, minus the recognizer-only kernel specs); planning logic lives in plan.go/build.go and the lowering helpers. Projection-side IR: the compiled projection (ProjPlan), segments, the whole Plan, the pre-segment stage specs, and the plan-error helper. Core lowering primitives (port of the Rust plan/lower.rs): slot assignment, scan-source selection, hop building, var-length hop-predicate extraction, projection binding, and the shared AND-chain helpers. The anchor-seek recognizers live in lower_seeks.go. Anchor-seek recognizers (split from lower.go): the WHERE/inline-prop conjunct forms that let a fresh node anchor on an index seek instead of a label scan -- id equality (literal, param, and bound-variable), substring text match, property equality, and property IN lists. Monotonic var-length predicate pushdown (port of the Rust plan/mono.rs): one normalizer answers "is this conjunct equivalent to sorted(L) / reverse-sorted(L)?" over a canonicalized consecutive-pair core, and one pass over the fully built segments lifts each recognized conjunct onto its bounded var-expand as a MonoHopSpec so non-monotonic hops are pruned during the walk. Mis-recognition is impossible, only missed optimization: an unconsumed conjunct stays a correct post-hoc filter. Plan entry points: desugar + split a query into UNION branches and projection-boundary segments (port of the Rust plan.rs top half). The planner is cost-based only: the Rust engine's rank/cost mode switch and thread-local override do not exist here -- the cost branch is the one and only strategy, layered on the same rank tiers so plan shapes match the Rust cost mode exactly. Projection binding (split from lower.go for the file-size norm): compile a projection body against the segment scope, resolve ORDER BY keys, and hoist nested aggregates into hidden accumulator slots. Aggregating-projection helpers: substituting grouping-key subexpressions with references to their output columns, and compiling a top-level aggregate call into an AggCol. Split from proj_bind.go, which holds the projection binding and nested-aggregate extraction. Join reordering and interior-anchor splitting (split from cost.go for the file-size norm): the cost pre-passes that run before slot assignment, plus their name-based selectivity and correlation guards. Clause rewrite pass (port of the Rust plan/rewrite.rs, keeping only fuse_projection_before_aggregate -- the window-count fusion belonged to the skipped recognizer kernels): a pure-projection boundary immediately followed by an aggregating boundary is inlined into the aggregate, so the preceding MATCH's scan and the aggregation land in one segment. Shortest-path stage lowering (port of the Rust plan/sp.rs): turn an ANY/ALL SHORTEST path-search clause into an SpStage, validating the optional weight, extracting the per-hop predicate, and resolving the bound endpoint slots. MATCH-scope relationship-uniqueness marking (port of the Rust recognize.rs::mark_rel_uniqueness): within one MATCH clause (= one scope, spanning its comma patterns and any planner splits) every relationship must be pairwise distinct -- ISO GQL's DIFFERENT EDGES default match mode / openCypher's relationship isomorphism. Small shared helpers for the planner.
Package-Level Type Names (total 33)
/* sort by: | */
AggCol is one aggregate output column. // nil for count(*) Arg2 is the percentile aggregates' second argument, a plan-time constant literal (a number or a parameter). Distinct bool Kind AggKind OutIdx int
AggKind is an aggregate function kind. const AggAvg const AggCollect const AggCount const AggMax const AggMin const AggPercentileCont const AggPercentileDisc const AggStddevPop const AggStddevSamp const AggSum
BindOp is one operator of a MATCH stage's bind chain. Acyclic forbids repeated nodes within the expansion (the ACYCLIC path mode); the default trail semantics forbids repeated rels only. DedupEndpoints bool Dir graph.Direction // expand endpoints (slots) Kind OpKind Target-node constraints (all ops; scan uses Slot's own). // nil = unbounded OpVarExpand only. MonoHop *MonoHopSpec Props []ast.PropEntry // To is already bound (join/cycle/carried-in) RelPred *RelHopPred // slot for a named rel variable; NoSlot when unbound // the pattern's rel variable name ("" anonymous) // OpScan: the bound node's slot // OpScan // expand endpoints (slots) Types []string Uniq is the op's MATCH-scope relationship-uniqueness participation (nil = untracked); OpExpand and OpVarExpand only.
BoundReturn is one projected column. Expr ast.Expr IsAgg bool Name string func OrderColIndex(expr ast.Expr, columns []string, returns []BoundReturn) int
CallProc is a CALL procedure with validated, concrete arguments. Damping float64 Desc bool Directed bool Direction graph.Direction Field string FilterMax int64 FilterMin int64 // "" absent Iters uint32 Kind ProcKind Km float64 Label string Lat float64 LatField string Lon float64 LonField string MaxDepth uint32 MaxLat float64 MaxLon float64 MinLat float64 MinLon float64 MinValue float64 Query string RelType string RelTypes []string // "" absent SeedVals []float64 ProcPropagate (Snapshot.PropagateBFS parameters). Source graph.NodeID TruncLimit int ValueProp string Weighted bool func ResolveCallProc(proc string, args []value.Value) (CallProc, error)
CallStage runs a procedure, binding the yielded columns. ArgExprs are the correlated call's argument expressions, evaluated against each input row. DepthSlot is algo.propagate's yielded depth slot (NoSlot if not yielded). NodeSlot is the yielded node column's slot (NoSlot if not yielded). Proc is the resolved procedure. For a correlated call only its Kind is set at plan time; the full CallProc resolves per input row from ArgExprs. ProcName is the procedure name of a correlated call ("" when the arguments were constant and Proc is fully resolved). ValueSlot is the yielded per-node scalar's slot (NoSlot if not yielded; the search procedures have no scalar column). *CallStage : Stage
CallSubqueryStage is CALL { subquery }: a correlated lateral join. Sub is planned with inCols = the import list, so its first segment carries the imported columns in slots 0..len(ImportSlots). ImportSlots []int OutSlots []int Sub *Plan *CallSubqueryStage : Stage
Estimates holds per-segment estimates in branch-major order. Segs []SegEst func Estimate(p *Plan, g graph.Graph) *Estimates func github.com/freeeve/gochickpeas/gql/internal/explain.Canonical(p *Plan, est *Estimates) []string func github.com/freeeve/gochickpeas/gql/internal/explain.Render(p *Plan, prof *explain.Profile, planTime time.Duration, est *Estimates) []string
GateDerived is one hoisted LET column: Expr evaluates against the segment's row (path and earlier derived slots included) into Slot. Expr ast.Expr Slot int
Stage is one pipeline stage of a segment. GateStage is an injected early row gate (gate.go): a copy of a later segment's shortest-path stage plus the downstream LET/FILTER chain that depends only on slots bound here, run before this segment's remaining expansion stages so rows the chain would kill never expand. The path and derived values bind to hidden slots past the segment's projected names; the original downstream stages still run unchanged (the path search is deterministic per endpoint pair, so re-running it is result-identical and cheap under the pair memo). Derived []GateDerived Sp SpStage Where ast.Expr *GateStage : Stage
GroupJoinFill is a decorrelated aggregate's empty-group identity: the value an outer row binds when the inner table has no entry for its key. const FillNull const FillZero
GroupJoinStage is a decorrelated OPTIONAL-MATCH aggregate join (groupjoin.go): the OPTIONAL clause's pattern planned as a standalone query (free to take its own selective anchor) whose projection is the correlation keys followed by the consumed aggregates. Sub executes once into a key -> aggregate-values table; each outer row binds OutSlots by KeySlots lookup, with Fills as the empty-group identities. The segment projection's aggregates are rewritten to re-aggregate the synthetic columns (count -> sum), which reproduces the nested left-join answer exactly, including over duplicate outer rows. Fills []GroupJoinFill KeySlots []int OutSlots []int Sub *Plan *GroupJoinStage : Stage
HashJoinStage joins an independent branch of a MATCH scope by hash probe instead of nested re-execution: Build's chain (which reads only ExtSlots plus its own bindings) runs once per distinct ExtSlots value tuple over a seed row, materializing its rows keyed by KeySlot with each row's relationship-uniqueness pairs captured; then per outer row the Probe expansion (the consumed connecting expand, re-oriented to run from its outer-bound endpoint) generates key candidates whose table hits bind PayloadSlots -- with the captured Check pairs replayed against the outer used-pair env and the captured live pairs pushed for downstream ops, so the row multiset is exactly the nested order's. Where carries the moved cross-branch conjuncts evaluable once the payload binds. Build []*MatchStage Cartesian marks the keyless disconnected join: no expand and no equality relates the branch, so the build materializes once and EVERY build row emits per outer row -- the classic cartesian build-once, which exists to stop the branch's scan re-running per outer row (the output multiset is the same product either way). ExtSlots []int KeyBuild/KeyProbe carry a value-keyed join: the consumed equality conjunct's two sides, KeyBuild reading only branch (and external) slots, KeyProbe only pre-bound outer ones. When set, Probe and KeySlot are unused -- the probe is a table lookup by the outer row's key value (a null on either side never matches, per equality semantics). This is the disconnected-components join: no expand connects the branch, so a WHERE equality is the key. KeyBuild/KeyProbe carry a value-keyed join: the consumed equality conjunct's two sides, KeyBuild reading only branch (and external) slots, KeyProbe only pre-bound outer ones. When set, Probe and KeySlot are unused -- the probe is a table lookup by the outer row's key value (a null on either side never matches, per equality semantics). This is the disconnected-components join: no expand connects the branch, so a WHERE equality is the key. KeySlot int PayloadSlots []int Probe is the connecting expand: From is the outer-bound endpoint, To is KeySlot. When Reversed, the original pattern hop ran from the build side (its direction here is already flipped; uniqPair canonicalization is reversal-invariant) and its target constraints (Labels/Props) belong to the OUTER endpoint, checked once per row. Reversed bool Where ast.Expr *HashJoinStage : Stage
MatchStage is one MATCH clause's bind chain. Optional rows that fail to match survive with the stage's new variables left null (left join). Ops []BindOp Optional bool PathBind is set for MATCH p = (a)-[...]->(b): assemble the named path after the pattern binds. Scope identifies the source MATCH clause: the relationship- uniqueness scope spanning its comma patterns and any planner splits (ISO GQL's DIFFERENT EDGES default / openCypher rel isomorphism). Walk is the REPEATABLE ELEMENTS match mode: this clause's expands take no part in relationship-uniqueness marking. // nil when absent *MatchStage : Stage
MonoHopSpec is a path-ordered monotonic constraint on a bounded var-expand: each hop's RelKey property must strictly continue the order vs the previous hop, pruned during the walk with the same three-valued value.Compare the source filter uses (any comparable kind, not just i64). NullsPass carries the recognized shape's null semantics: an incomparable pair (missing key, NaN, mixed kinds) fails an all()-shape comparison and prunes, but is not a violation in the violation-count shape and passes. Ascending bool NullsPass bool RelKey string
OpKind discriminates a BindOp. const OpExpand const OpScan const OpVarExpand
PathBindSpec says how to assemble a named path: the start node lives in FromSlot, the hop's rel(s) at RelsSlot (a rel for a fixed hop, a list for a quantified hop); the node sequence is reconstructed by walking Dir/Types from the start, and the path value binds to PathSlot. Dir graph.Direction FromSlot int PathSlot int RelsSlot int Types []string
Plan is a compiled query: one segment pipeline per UNION branch, the combinators between them, and the output column names (identical across branches, validated at plan time). Alt is a sibling plan identical to this one except that a single auto-parameterized anchor tie (both endpoints unbound param seeks, whose real degrees the planner cannot see) is oriented the opposite way. It is a FULL second planning pass, so every rewrite/recognizer pass ran over it too -- never a spare op chain hidden inside a stage. The cached executor resolves both anchors from the now-bound params and runs whichever seeds from the lower-degree end; nil when the query has no qualifying tie (the common case). Alt itself never carries an Alt. Branches [][]*Segment Columns []string Ties is how many auto-parameterized anchor ties the primary pass collected. Alt covers the first; a count above one means the rest stayed on the static fallback (the measurement for whether a fuller choice mechanism would ever fire). Union []ast.UnionKind func Build(q *ast.Query, g graph.Graph) (*Plan, error) func BuildWithInCols(q *ast.Query, inCols []string, g graph.Graph) (*Plan, error) func Estimate(p *Plan, g graph.Graph) *Estimates func github.com/freeeve/gochickpeas/gql/internal/exec.Execute(ctx *eval.Ctx, p *Plan) ([][]value.Value, error) func github.com/freeeve/gochickpeas/gql/internal/exec.ExecuteProfiled(ctx *eval.Ctx, p *Plan) *explain.Profile func github.com/freeeve/gochickpeas/gql/internal/explain.Canonical(p *Plan, est *Estimates) []string func github.com/freeeve/gochickpeas/gql/internal/explain.Render(p *Plan, prof *explain.Profile, planTime time.Duration, est *Estimates) []string
PostProj is one post-aggregation scalar projection: output column Col is computed by Expr (rewritten to read hidden __agg{k} slots) after the groups finalize. Col int Expr ast.Expr
ProcKind discriminates a CallProc. const ProcBfs const ProcCdlp const ProcFtsSearch const ProcGeoWithinBBox const ProcGeoWithinRadius const ProcLcc const ProcPageRank const ProcPropagate const ProcSssp const ProcWcc const ProcWccAll
ProjPlan is a compiled projection (a With boundary or the final RETURN). Aggregated bool Aggs []AggCol Columns []string Distinct bool GroupIdx are the non-aggregate output columns (the group key). Limit *uint64 NHidden int OrderBy []ast.SortItem Post are the nested-aggregate scalar wrappers; NHidden counts the hidden accumulator slots feeding them (per-group rows are sized len(Returns)+NHidden, truncated back after Post runs). Returns []BoundReturn Skip *uint64
RelHopPred is a per-hop relationship predicate lifted from all(r IN rels(e) WHERE pred) onto a var-expand: each traversed rel must satisfy Pred (which references only Var) or the hop is pruned. Pred ast.Expr Var string
RelUniq is a rel-binding op's MATCH-scope relationship-uniqueness participation, set by markRelUniqueness only when the op's types can intersect another rel-binding op's in the same MATCH clause (Scope) -- everything else stays untracked and zero-cost. Check means an intersecting op binds EARLIER in execution order, so this op's candidate rel must not reuse a pair already on the scope's used stack; Contribute means an intersecting op binds LATER, so this op pushes its bound rel pair(s) for those to check against. The used-rel identity is the canonical endpoint pair (see exec's uniqPair) -- collapsing parallel rels between one node pair, the documented multigraph deviation, matching the trail walk's key. Check bool Contribute bool Scope uint32
ScanKind discriminates a ScanSource. const ScanAll const ScanArg const ScanExistsSeed const ScanLabel const ScanNodeID const ScanNodeIDVar const ScanProperty const ScanPropertyIn const ScanTextMatch
ScanSource is where a Scan op's candidate nodes come from. // ScanTextMatch // ScanProperty Kind ScanKind Label string // ScanTextMatch // ScanExistsSeed: one chain per EXISTS disjunct // ScanArg / ScanNodeIDVar // ScanProperty value / ScanNodeID id / ScanTextMatch needle // ScanPropertyIn: the IN list's literals
SeedChain walks one EXISTS disjunct's pattern from its bound anchor (AnchorSlot's row value) to the scan variable. AnchorSlot int Hops []SeedHop
SeedHop is one backward step of an EXISTS seed walk: traverse Types in Dir (already reversed for the walk), landing on nodes carrying Labels. Dir graph.Direction Labels []string Types []string
SegEst estimates one segment; mirrors the profiler's segment shape so the renderer can zip them. AnchorNotes has one entry per stage: the [anchor: ...] note for a stage that made a cost anchor choice, else "". PostWhereRows is the estimated rows after a boundary WHERE, if any. ProjRows is the estimated rows after projection; nil when aggregated (group count not estimated). Stages []StageEst
Segment is one pipeline stage run: the stages in order, then the projection. Output columns feed the next segment. ColAgg marks a columnar-aggregate candidate (colagg.go): the executor may fuse the scan, filters, and aggregation into one typed column pass, falling back to the general chain when any expression declines classification. PostWhere filters projected rows (references output columns) -- the projection boundary's WHERE. Proj ProjPlan RowWidth int Slots map[string]int Stages []Stage func ColAggChainLen(segments []*Segment, i int) int
SpStage binds PathSlot to the minimum-hop path between the bound endpoint slots From and To. When All, the stage is row-expanding: one row per minimum-hop path. All bool Dir graph.Direction From int // nil = unbounded hop count Optional bool PathSlot int RelPred *RelHopPred To int Types []string // nil = hop-minimal WeightVar is the path's rel variable when Weight is a per-edge expression (bound per candidate hop). *SpStage : Stage
StageEst estimates one stage: Match holds one estimate per op plus a trailing one for the stage WHERE; Single is a one-output stage. Match []uint64 Single *uint64
UnwindStage is FOR x IN list: each input row evaluates List; a list emits one row per element bound to OutSlot, null emits none, any other scalar emits a single row bound to it. List ast.Expr OutSlot int *UnwindStage : Stage
Package-Level Functions (total 9)
Build desugars and plans a query against g's statistics.
BuildWithInCols plans a query whose branches each begin with inCols already bound, carried in from an outer scope (a CALL {} subquery plans its body with inCols = its import list). Adaptive anchoring is scoped to the top-level Build pass BY DECISION, not accident: a subquery body executes once per outer row, so a per-execution anchor probe there would run per row rather than per statement -- the probe's O(1)-ish budget assumes statement frequency. Subquery bodies stay on the static fallback until a per-row-amortized scoring exists.
ColAggChainLen is the number of segments a columnar-aggregate fusion starting at i would consume: the scan segment itself when its own boundary aggregates, or the scan plus a run of stage-less pure per-row boundaries (the LET pipeline) ending in a stage-less aggregated segment. 0 means segment i heads no fusable chain. Shared with the executor so the plan marking and the runtime walk agree.
Estimate computes every segment's estimates for a planned query.
GroupDigits renders 2860664 as "2,860,664" (shared with the renderer).
MentionsVar reports whether e references name anywhere (binder locals included -- over-reporting only declines an optimization, never unsound).
OrderColIndex resolves an ORDER BY key to an output column as a whole: a bare variable naming a projected column, or a key expression structurally equal to a projected one. -1 when the key is a composite the executor evaluates against the projected row instead.
ResolveCallProc validates resolved argument values into a concrete CallProc -- at bind time for constant arguments, per input row from exec for correlated ones.
SplitAnd walks a WHERE's top-level AND chain, appending each conjunct (exported: the executor's conjunct bucketing splits the same chains).
Package-Level Variables (total 9)
gjAnchorDeclines counts inner plans refused as unselective with no outer repetition to amortize the table, so tests can assert the discriminator engaged. DisableGJAnchorDecline pins differential tests to the always-admit behavior the discriminator replaced.
DisablePropInSeek pins result identity in tests: the label-scan + filter evaluation must produce exactly the rows the IN seek does, in the same order.
DisableSortLimitFusion pins result identity in tests: the unfused pipeline must produce exactly the rows the fused one does, in the same order.
GroupJoinMinCoverage is the minimum fraction of the correlation variable's population the outer must reach. The standalone inner enumerates its pattern for EVERY correlation node while the nested walk only expands the outer's slice, so their cost ratio IS the coverage: a broad-but-narrow outer (thousands of rows over millions of candidates) keeps the cheap nested walk (measured: a tag-seek outer of ~4k messages against 3M messages regressed 65x under a breadth-only gate).
GroupJoinMinOuterRows is the estimated-outer-breadth floor below which the nested OPTIONAL execution stays (a variable so tests can force the rewrite onto fixture-scale graphs).
HashJoinExtDivisor bounds the branch's external references: a slot qualifies only when it was bound at a cumulative estimate at most estIn(pivot)/HashJoinExtDivisor, which also bounds the number of distinct memo tuples (re-builds) to the same fraction.
HashJoinFanFactor is the minimum estimated output/input multiply of the pivot stage before the rewrite is considered.
HashJoinMaxBuildRows caps the estimated materialized branch (a memory bound; the win condition is the pivot gate's multiply).
HashJoinMinRows is the smallest estimated intermediate worth protecting from a multiplying re-expansion.
Package-Level Constants (total 36)
AggAvg is avg(x).
AggCollect is collect(x).
AggCount is count(x) / count(*).
AggMax is max(x).
AggMin is min(x).
AggPercentileCont / AggPercentileDisc are the two-argument percentile aggregates: numeric values collect per group and sort at finalize; cont interpolates linearly (always Float), disc picks the nearest-rank collected value (Neo4j semantics). Empty group or a percentile outside [0,1] finalizes to Null.
AggStddevSamp / AggStddevPop are the sample / population standard deviations (Welford accumulation; insufficient rows finalize to 0, matching Neo4j's stdev/stdevp).
AggSum is sum(x).
FillNull is the null-skipping aggregates' identity (sum/min/max over an empty group is null).
FillZero is count's identity (an empty group counts zero).
NoSlot marks an absent optional slot (rel variable, group index).
OpExpand traverses one relationship hop.
OpScan binds a fresh node variable from a ScanSource.
OpVarExpand traverses a quantified (variable-length) hop. Bounded (min>=1, max set) enumerates trails, one row per path; zero-length or unbounded resolves the distinct reachable set via dedup'd BFS.
ProcBfs is algo.bfs(source[, directed]).
ProcCdlp is algo.cdlp([directed][, iterations][, seedProp]).
ProcFtsSearch is fts.search(label, field, searchTerm).
ProcGeoWithinBBox is geo.withinBBox(label, latField, longField, minLat, minLon, maxLat, maxLon).
ProcGeoWithinRadius is geo.withinRadius(label, latField, longField, lat, lon, km).
ProcLcc is algo.lcc([directed]).
ProcPageRank is algo.pagerank([directed][, damping][, iterations]).
ProcPropagate is algo.propagate(seeds, values, relTypes, direction, maxDepth, valueProp, order, truncLimit[, minValue[, filterProp, filterMin, filterMax]]) -- first-claim value propagation (Snapshot.PropagateBFS), yielding node, value, depth per reached node.
ProcSssp is algo.sssp(source[, directed][, weighted]).
ProcWcc is wcc(relType[, direction]).
ProcWccAll is algo.wcc().
ScanAll is every node.
ScanArg is the node already bound in row slot Slot (carried in or reused across MATCH clauses).
ScanExistsSeed is an EXISTS-driven candidate scan: the WHERE holds an EXISTS (or an OR of EXISTSes) correlated to the scan variable whose pattern anchors at an already-bound variable, so candidates enumerate backward from the anchor's current value instead of scanning the label. Candidates are a superset (interior property and WHERE predicates are ignored during the walk); the kept EXISTS conjunct finalizes, so like ScanTextMatch this is purely a narrower source.
ScanLabel is all nodes of Label.
ScanNodeID is a direct seek to the node whose id equals the Value literal (an int or lifted param), recognized from WHERE id(n) = N. The WHERE conjunct is kept and re-checked, so the result is identical to the scan-then-filter.
ScanNodeIDVar is a per-row seek to the node whose id is the integer in row slot Slot, recognized from WHERE id(n) = boundVar.
ScanProperty is an indexed anchor: nodes of Label whose Key == Value.
ScanPropertyIn is an indexed multi-value anchor: nodes of Label whose Key equals ANY of Values, served as one property seek per value, unioned, sorted, and deduplicated. The originating IN conjunct is kept and re-checked, so the source only narrows.
ScanTextMatch is a substring-index candidate scan: nodes of Label whose Field satisfies Mode (STARTS WITH/ENDS WITH/CONTAINS) against the Value needle. Candidates are a superset; the kept WHERE conjunct finalizes, so this is purely a faster source.