// Per-candidate predicate specialization: during one bind-chain level's // candidate iteration every row slot except the level's own is fixed, so // a pushed-down conjunct is a unary function of the candidate id. The // dominant conjunct shape -- the fused prop-vs-const comparison -- runs // as a monomorphic closure reading its hoisted typed column directly, // skipping the compiled tree's dispatch and per-candidate boxing. Every // pairing whose comparison semantics are not trivially same-kind boxes // the column value once and routes through value.Compare, so results are // exactly the general path's (missing property = failed compare = Null = // prune, for all six operators).
package compile import ( ) // CandPred reports whether the conjunct holds for a node candidate bound // at its specialized slot. The row carries the level's fixed outer // bindings -- only the carried-list rebuild (once per match-call epoch) // reads it; hot paths touch the candidate alone. type CandPred func(ctx *eval.Ctx, row []value.Value, id graph.NodeID) bool // CandidatePred specializes c when its only per-candidate dependency is // the node at slot and its shape is the fused prop-vs-const comparison or // a constant- or carried-list membership over the candidate's property // (or over the candidate itself: a bare-variable element probes the node // value directly, no read at all); ok=false keeps the general row // evaluation. func ( *Compiled, int, map[string]int) (CandPred, bool) { // inElem resolves a membership element over slot: a property read, or // the identity (the candidate node itself); nil declines. := func( cnode) func( graph.NodeID) value.Value { switch n := .(type) { case *cProp: if .slot != { return nil } := .reader return .readNode case *cSlot: if .s != { return nil } return func( graph.NodeID) value.Value { return value.Node() } } return nil } // bareSlot reports whether the membership element IS the candidate // itself -- then a node-id membership probes the sorted id set with // the raw candidate: no boxing, no resultFor dispatch. (memNodes is // built from an all-node list, so hasNull is impossible; a miss // prunes either way under the predicate's truthy collapse.) := func( cnode) bool { , := .(*cSlot) return && .s == } if , := .c.(*cInConst); { := (.e) if == nil { return nil, false } if (.e) && .m.kind == memNodes { := &.m return func( *eval.Ctx, []value.Value, graph.NodeID) bool { return .hasNode(uint32()) }, true } // A string column probing a constant list compares interned atom // ids: the list's atoms resolve once, a probe is one column read // and a small search -- no string resolution, no encoded hash key. if , := .e.(*cProp); && .reader.node.kind == colStr { if , := .m.atomSet(.g); { := .reader.node.str return func( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .ID(uint32()) if ! || == 0 { return false } , := slices.BinarySearch(, ) return }, true } } := &.m return func( *eval.Ctx, []value.Value, graph.NodeID) bool { := () if .IsNull() { return false } return .resultFor().IsTruthy() }, true } if , := .c.(*cInCarried); { := (.e) if == nil { return nil, false } := (.e) := .g return func( *eval.Ctx, []value.Value, graph.NodeID) bool { .refresh(, , , ) if .notList { return false } // The carried list may change per epoch; re-check the // representation per call (one byte compare). if && .m.kind == memNodes { return .m.hasNode(uint32()) } := () if .IsNull() { return false } return .m.resultFor().IsTruthy() }, true } // IS [NOT] NULL over the candidate's property is a pure presence // probe: one typed column read, no value materialization (the general // path resolves a string column's text just to discard it). The // string form mirrors the reader's empty-means-absent fold by atom // id, declining when a non-zero atom resolves empty (the one case // where the id test could diverge). if , := .c.(*cIsNull); { , := .e.(*cProp) if ! || .slot != { return nil, false } := .reader.node := .negated switch .kind { case colI64: := .i64 return func( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .Get(uint32()) return == }, true case colF64: := .f64 return func( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .Get(uint32()) return == }, true case colBool: := .bool return func( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .Get(uint32()) return == }, true case colStr: if , := .g.PropertyKey(""); && != 0 { return nil, false } := .str return func( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .ID(uint32()) return ( && != 0) == }, true } return nil, false } , := .c.(*cCmpPropConst) if ! || .prop.slot != { return nil, false } := .prop.reader := opKeep(.op) := .rev := .c // Same-kind numeric pairs compare unboxed -- through float64 exactly // like value.Compare's asNum path (Int-vs-Int also routes through // float64 there; mirroring is the invariant, not improving on it). // Everything else boxes the column read once and uses the shared // kernel. if .node.kind == colI64 && .Kind() == value.KindInt { , := .AsInt() := float64() return func( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .node.i64.Get(uint32()) if ! { return false } , := float64(), if { , = , float64() } , := cmpFloat(, ) return && () }, true } // An i64 column against a temporal constant compares exactly as // int64 epoch millis (value.Compare's Temporal/Int case is cmpInt -- // exact, always comparable), so the typed closure needs no float // detour. if .node.kind == colI64 && .Kind() == value.KindTemporal { , , := .AsTemporal() return func( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .node.i64.Get(uint32()) if ! { return false } , := , if { , = , } var int switch { case < : = -1 case > : = 1 } return () }, true } // String equality against a constant compares interned atom ids: the // constant resolves through the shared atom table once, so a probe is // one column read and an integer compare -- no string resolution, no // byte comparison. Only =/<> qualify (atom ids carry no lexicographic // order), and both are operand-order symmetric, so rev is moot. The // general path folds absent, atom 0, and empty text to Null (prune); // equality against a non-empty constant needs no empty check (a // matching atom IS the constant's non-empty text), while inequality // declines when a non-zero atom resolves empty (only then could the // id test diverge from the reader's empty-means-absent fold). if .node.kind == colStr && .Kind() == value.KindStr && (.op == ast.OpEq || .op == ast.OpNeq) { , := .AsStr() := .node.str , := .g.PropertyKey() if .op == ast.OpEq { if == "" || ! { // No stored non-empty text can equal it: always prune. return func( *eval.Ctx, []value.Value, graph.NodeID) bool { return false }, true } return func( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .ID(uint32()) return && == }, true } if , := .g.PropertyKey(""); && != 0 { // A non-zero empty atom exists: keep the general path, whose // resolve-and-check folds it to absent. return nil, false } if == "" || ! { // Present non-empty text is unequal to it by construction. return func( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .ID(uint32()) return && != 0 }, true } return func( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .ID(uint32()) return && != 0 && != }, true } if .node.kind == colF64 && .Kind() == value.KindFloat { , := .AsFloat() return func( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .node.f64.Get(uint32()) if ! { return false } , := , if { , = , } , := cmpFloat(, ) return && () }, true } return func( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .readNode(), if { , = , } , := value.Compare(, ) return && () }, true } // CandBatch sweeps a whole candidate buffer for one conjunct: it clears // keep[i] for every candidate that fails, touching only still-kept // entries. The batch form exists for shapes whose per-candidate work is // pure array arithmetic -- no closure call, no dispatch per element. type CandBatch func(ctx *eval.Ctx, row []value.Value, cand []graph.NodeID, keep []bool) // CandidateBatch specializes c to a buffer sweep when its shape allows // the columnar form: today the fused prop-vs-const comparison over an // i64 column with a contiguous-presence window (SliceRange), where the // filter reduces to vals[id-start] compared against a pre-encoded // constant -- the dominant pushed-down filter shape over LDBC-style // label-block columns. Semantics mirror the scalar predicate exactly // (value.Compare's float64 partial order; out-of-window = absent = // prune). ok=false keeps the scalar path. func ( *Compiled, int) (CandBatch, bool) { , := .c.(*cCmpPropConst) if ! || .prop.slot != { return nil, false } := .prop.reader.node if .kind != colI64 || .c.Kind() != value.KindInt { return nil, false } , , := .i64.SliceRange() if ! { return nil, false } , := .c.AsInt() := float64() := opKeep(.op) := .rev return func( *eval.Ctx, []value.Value, []graph.NodeID, []bool) { for , := range { if ![] { continue } := uint32() - if >= uint32(len()) { [] = false continue } , := float64([]), if { , = , float64([]) } , := cmpFloat(, ) if ! || !() { [] = false } } }, true } // opKeep is the six comparison operators' order acceptance. func opKeep( ast.BinOp) func(int) bool { switch { case ast.OpEq: return func( int) bool { return == 0 } case ast.OpNeq: return func( int) bool { return != 0 } case ast.OpLt: return func( int) bool { return < 0 } case ast.OpLte: return func( int) bool { return <= 0 } case ast.OpGt: return func( int) bool { return > 0 } default: // ast.OpGte return func( int) bool { return >= 0 } } } // cmpFloat is value.Compare's float64 partial order: NaN on either side // is incomparable. func cmpFloat(, float64) (int, bool) { switch { case < : return -1, true case > : return 1, true case == : return 0, true } return 0, false } // IntSweep bulk-filters a candidate chunk over an i64 NODE column // against a folded constant: one GetMany then a hoisted compare loop -- // the batch form of the cCmpPropConst per-candidate predicate for // columns the windowed CandidateBatch cannot serve (narrow storage // classes) and constants it declines (temporals, which fold to their // epoch millis and compare through the exact int order, per constI64Val). // vals/present are caller scratch of at least len(ids); keep is the // running mask. Candidates must be node ids, the same contract as // CandidateBatch. type IntSweep func(ids []uint32, vals []int64, present, keep []bool) // CandidateIntSweep specializes a compiled conjunct to an IntSweep; // ok=false leaves the caller on the per-candidate path. func ( *Compiled, int) (IntSweep, bool) { , := .c.(*cCmpPropConst) if ! || .prop.slot != { return nil, false } := .prop.reader.node if .kind != colI64 { return nil, false } , , := constI64Val(.c) if ! { return nil, false } := .i64 // A reversed operand order mirrors the operator, so the loops below // always compare value-vs-constant. := .op if .rev { switch { case ast.OpLt: = ast.OpGt case ast.OpLte: = ast.OpGte case ast.OpGt: = ast.OpLt case ast.OpGte: = ast.OpLte } } if { // The exact int order inlines per operator: the per-id closure // call and compare-function call were the sweep loop's dominant // cost at 5.7M candidates/query. var IntSweep switch { case ast.OpEq: = func( []uint32, []int64, , []bool) { .GetMany(, , ) for := range { [] = [] && [] && [] == } } case ast.OpNeq: = func( []uint32, []int64, , []bool) { .GetMany(, , ) for := range { [] = [] && [] && [] != } } case ast.OpLt: = func( []uint32, []int64, , []bool) { .GetMany(, , ) for := range { [] = [] && [] && [] < } } case ast.OpLte: = func( []uint32, []int64, , []bool) { .GetMany(, , ) for := range { [] = [] && [] && [] <= } } case ast.OpGt: = func( []uint32, []int64, , []bool) { .GetMany(, , ) for := range { [] = [] && [] && [] > } } default: // ast.OpGte = func( []uint32, []int64, , []bool) { .GetMany(, , ) for := range { [] = [] && [] && [] >= } } } return , true } := opKeep() return func( []uint32, []int64, , []bool) { .GetMany(, , ) for := range { if ![] || ![] { [] = false continue } , := cmpFloat(float64([]), float64()) [] = && () } }, true }