// Pre-resolved pattern matchers (port of the Rust NativeNodeMatcher / // NativeRelMatcher): a node pattern's label and inline-property names are // resolved to snapshot handles once per operator, so the per-candidate // accept test is a bitmap contains plus a direct column read instead of // interner lookups.
package graph import ( chickpeas ) // RelMatcher is an expand's relationship types pre-resolved to interned // ids. Wraps the engine's RelMatch, which has the exact semantics the seam // needs: no types matches all, unknown names drop, all-unknown matches // nothing. type RelMatcher struct { m chickpeas.RelMatch } // propPredKind discriminates one inline-property predicate. type propPredKind uint8 const ( // predNoColumn: the key has no column, so every node reads absent for // it -- matching only a Null value. predNoColumn propPredKind = iota // predStrEq: a string equality with the comparison text pre-interned // to its atom once; the per-candidate check is an id compare. atomOK // false means the text is absent from the interner, so nothing can // equal it. predStrEq // predGeneric: read the stored value and compare via value.Equal // (numeric coercion, bool, Null-absent), byte-identical to NodePropEq. predGeneric ) // propPred is one inline {key: value} predicate with its key pre-resolved // to a column reader. type propPred struct { kind propPredKind read func(pos uint32) (value.Value, bool) atom uint32 atomOK bool readAtom func(pos uint32) (uint32, bool) expected value.Value } // labelTest is one compiled label membership check: a dense label probes // a word bitmap (one load and mask), the rest the compressed node set. An // empty test (unknown label) rejects every candidate. A set-backed test // counts its probes and self-densifies past adaptiveDenseProbes: probe // volume, not label density, is what amortizes the id-space-proportional // bitmap, and the engine caches it per label so one hot execution pays // the build for every later compile. Matchers are compiled per stage per // execution and evaluated single-threaded, so the plain counter is safe. type labelTest struct { bits []uint64 set *nodeset.Set label string probes uint32 } // adaptiveDenseProbes is the set-probe count past which a label test // requests the floor-free bitmap (LabelDenseForced); heavy levels probe // millions of times, so the ~2 ms build repays within one execution. const adaptiveDenseProbes = 1 << 16 func ( *labelTest) ( *chickpeas.Snapshot, uint32) bool { if .bits != nil { := int() >> 6 return < len(.bits) && .bits[]>>(&63)&1 == 1 } if .set == nil { return false } .probes++ if .probes >= adaptiveDenseProbes { if .bits = .LabelDenseForced(.label); .bits != nil { := int() >> 6 return < len(.bits) && .bits[]>>(&63)&1 == 1 } } return .set.Contains() } // NodeMatcher is a node pattern's labels and inline properties pre-resolved // to snapshot handles. type NodeMatcher struct { labels []labelTest props []propPred } // CompileNodeMatcher resolves each label to its membership test (the // engine's dense word bitmap when the label has one, the node set // otherwise) and each inline key to its column once, so the per-candidate // NodeMatcherAccepts avoids re-interning constant names. func ( *SnapshotGraph) ( []string, []PropSpec) *NodeMatcher { := &NodeMatcher{labels: make([]labelTest, len()), props: make([]propPred, len())} for , := range { if , := .g.NodesWithLabel(); { .labels[] = labelTest{bits: .g.LabelDense(), set: , label: } } } for , := range { , := .g.ColIndexed(.Key) if ! { .props[] = propPred{kind: predNoColumn, expected: .Val} continue } if , := .Val.AsStr(); { := propPred{kind: predStrEq, readAtom: strColReader()} if , := .g.ValueFromString(); { .atom, .atomOK = .StrID() } .props[] = continue } .props[] = propPred{kind: predGeneric, read: .colReader(), expected: .Val} } return } // NodeMatcherAccepts reports whether node carries every label and matches // every inline property of the compiled matcher. func ( *SnapshotGraph) ( *NodeMatcher, chickpeas.NodeID) bool { for := range .labels { if !.labels[].contains(.g, uint32()) { return false } } for := range .props { := &.props[] switch .kind { case predNoColumn: if !.expected.IsNull() { return false } case predStrEq: , := .readAtom(uint32()) if ! || !.atomOK || != .atom { return false } case predGeneric: , := .read(uint32()) if ! { if !.expected.IsNull() { return false } } else if !value.Equal(, .expected) { return false } } } return true } // CompileRelMatcher pre-resolves relationship-type names via the engine's // Match (empty = all, unknown names drop, all-unknown = none). func ( *SnapshotGraph) ( []string) *RelMatcher { return &RelMatcher{m: .g.Match(...)} } // FilterMatchedTail compacts ids[start:] in place to the matcher-accepted // entries and returns the shortened slice; rels, when non-nil, is a // parallel array compacted identically from relStart. Result-identical to // testing each candidate with NodeMatcherAccepts -- the batch form exists // so a whole expansion tail filters through one call, with the dominant // matcher shape (one dense label, no inline properties) reduced to a // hoisted word-bitmap loop: no per-candidate dispatch at all. Set-backed // labels keep the per-candidate test, whose adaptive densification then // upgrades later batches. func ( *SnapshotGraph) ( *NodeMatcher, []chickpeas.NodeID, int, []uint32, int) ([]chickpeas.NodeID, []uint32) { if len(.props) == 0 && len(.labels) == 1 && .labels[0].bits != nil { := .labels[0].bits := for , := range [:] { := int() >> 6 if < len() && []>>(uint32()&63)&1 == 1 { [] = if != nil { [+(-)] = [+] } ++ } } if != nil { = [:+(-)] } return [:], } := for , := range [:] { if .NodeMatcherAccepts(, ) { [] = if != nil { [+(-)] = [+] } ++ } } if != nil { = [:+(-)] } return [:], } // strColReader reads the stored atom id at a position, folding the dense // string column's atom-0-means-missing convention to absent (the raw // StrCol.ID does not). func strColReader( chickpeas.Col) func( uint32) (uint32, bool) { := .Str() return func( uint32) (uint32, bool) { , := .ID() if ! || == 0 { return 0, false } return , true } } // colReader narrows a column once by dtype and returns a positional runtime // -value reader (strings resolved through the interner, empty folding to // absent per the engine's Prop convention). func ( *SnapshotGraph) ( chickpeas.Col) func( uint32) (value.Value, bool) { switch .Dtype() { case chickpeas.DtypeI64: := .I64() return func( uint32) (value.Value, bool) { , := .Get() if ! { return value.Value{}, false } return value.Int(), true } case chickpeas.DtypeF64: := .F64() return func( uint32) (value.Value, bool) { , := .Get() if ! { return value.Value{}, false } return value.Float(), true } case chickpeas.DtypeBool: := .Bool() return func( uint32) (value.Value, bool) { , := .Get() if ! { return value.Value{}, false } return value.Bool(), true } default: := strColReader() return func( uint32) (value.Value, bool) { , := () if ! { return value.Value{}, false } , := .g.ResolveString() if ! || == "" { return value.Value{}, false } return value.Str(), true } } }