// SnapshotGraph adapts *chickpeas.Snapshot to the Graph seam, forwarding // each method to the engine's read surface and resolving stored properties // to runtime values.
package graph import ( chickpeas ) // SnapshotGraph is the native Graph implementation over an immutable // engine snapshot. type SnapshotGraph struct { g *chickpeas.Snapshot } // New wraps an engine snapshot as a Graph. func ( *chickpeas.Snapshot) *SnapshotGraph { return &SnapshotGraph{g: } } // Snapshot exposes the underlying engine snapshot (the Native capability // for kernel offload and the columnar compiled eval path). func ( *SnapshotGraph) () *chickpeas.Snapshot { return .g } // NodeCount is the number of distinct node ids. func ( *SnapshotGraph) () uint32 { return .g.NodeCount() } // IDSpace is the CSR id-space upper bound (exceeds NodeCount under sparse // ids). func ( *SnapshotGraph) () uint32 { return .g.CSRIDSpace() } // NodeExists forwards the engine's existence oracle. func ( *SnapshotGraph) ( NodeID) bool { return .g.NodeExists(chickpeas.NodeID()) } // AvgDegree forwards the engine's per-type degree statistic. func ( *SnapshotGraph) ( string, chickpeas.Direction) float64 { return .g.AvgDegree(, ) } // AvgDegreeByLabel forwards the engine's label-conditional degree // statistic. func ( *SnapshotGraph) (, string, chickpeas.Direction) (float64, bool) { return .g.AvgDegreeByLabel(, , ) } // Degree forwards the O(1) untyped incident-relationship count. func ( *SnapshotGraph) ( chickpeas.NodeID, chickpeas.Direction) int { return .g.Degree(, ) } // HasLabel reports whether node carries label. func ( *SnapshotGraph) ( chickpeas.NodeID, string) bool { return .g.HasLabel(, ) } // LabelNames lists the graph's labels. func ( *SnapshotGraph) () []string { return .g.Labels() } // NodesWithLabel is the shared label-index set; nil when the label is // unknown. func ( *SnapshotGraph) ( string) *nodeset.Set { , := .g.NodesWithLabel() if ! { return nil } return } // LabelCardinality answers from the label index without materializing ids. func ( *SnapshotGraph) ( string) uint64 { , := .g.NodesWithLabel() if ! { return 0 } return uint64(.Len()) } // NodesWithProperty serves the {key: value} anchor from the engine's lazy // inverted property index, dispatched on the value's scalar kind. A // non-scalar value (or a string absent from the interner) matches nothing. func ( *SnapshotGraph) (, string, value.Value) *nodeset.Set { , := engineValue(.g, ) if ! { return nil } , := .g.NodesWithValue(, , ) if ! { return nil } return } // NodeProp reads a node property as a runtime value; ok is false when // absent (including the engine's empty-string-means-missing convention). func ( *SnapshotGraph) ( chickpeas.NodeID, string) (value.Value, bool) { return propValue(.g.Prop(, )) } // NodePropEq compares a stored node property against v: absent equals only // Null; present values compare with value.Equal's coercion. func ( *SnapshotGraph) ( chickpeas.NodeID, string, value.Value) bool { , := .NodeProp(, ) if ! { return .IsNull() } return value.Equal(, ) } // NodePropKeys is the node's populated property keys in ascending order. func ( *SnapshotGraph) ( chickpeas.NodeID) []string { := .g.NodePropertyKeys() sort.Strings() return } // RelProp reads a relationship property (by CSR position) as a runtime // value; ok is false when absent. func ( *SnapshotGraph) ( uint32, string) (value.Value, bool) { return propValue(.g.RelProp(, )) } // RelEndpoints is the (source, target) of the relationship at pos. func ( *SnapshotGraph) ( uint32) (, chickpeas.NodeID, bool) { return .g.RelEndpoints() } // RelTypeAt is the type name of the relationship at pos. func ( *SnapshotGraph) ( uint32) (string, bool) { return .g.RelTypeAt() } // Neighbors iterates node's neighbors over dir, any type. func ( *SnapshotGraph) ( chickpeas.NodeID, chickpeas.Direction) iter.Seq[chickpeas.NodeID] { return .g.Neighbors(, ) } // NeighborsByType iterates node's neighbors over dir restricted to types. func ( *SnapshotGraph) ( chickpeas.NodeID, chickpeas.Direction, []string) iter.Seq[chickpeas.NodeID] { return .g.Neighbors(, , ...) } // NeighborsMatched iterates through a pre-resolved matcher. func ( *SnapshotGraph) ( chickpeas.NodeID, chickpeas.Direction, *RelMatcher) iter.Seq[chickpeas.NodeID] { return .g.NeighborsMatch(, , .m) } // Relationships iterates traversed relationships as (neighbor, csrPos). func ( *SnapshotGraph) ( chickpeas.NodeID, chickpeas.Direction, []string) iter.Seq2[chickpeas.NodeID, uint32] { return func( func(chickpeas.NodeID, uint32) bool) { for := range .g.Rels(, , ...) { if !(.Neighbor, .Pos) { return } } } } // RelationshipsMatched iterates traversed relationships as (neighbor, // csrPos) through a pre-resolved matcher, skipping the per-call type-name // resolution Relationships pays. func ( *SnapshotGraph) ( chickpeas.NodeID, chickpeas.Direction, *RelMatcher) iter.Seq2[chickpeas.NodeID, uint32] { return func( func(chickpeas.NodeID, uint32) bool) { for := range .g.RelsMatch(, , .m) { if !(.Neighbor, .Pos) { return } } } } // AppendNeighborsMatched appends one entry per matching dir relationship // of node in CSR order (per-rel multiplicity -- pattern-match row // semantics), delegating to the core AppendNeighborsEach so the fill // stays allocation-free across the package boundary (the iter.Seq form // would escape a per-call yield closure to the heap). The engine's // deduped-ascending neighbor-ID set contract lives on // Snapshot.AppendNeighborsMatch, which the executor deliberately does // not use. func ( *SnapshotGraph) ( []chickpeas.NodeID, chickpeas.NodeID, chickpeas.Direction, *RelMatcher) []chickpeas.NodeID { return .g.AppendNeighborsEach(, , , .m) } // AppendNeighborsByType appends node's dir neighbors over types to dst, // resolving the type match once and delegating to the core append accessor // so the fill stays allocation-free (the iter.Seq form escapes a per-call // yield closure across the package boundary). func ( *SnapshotGraph) ( []chickpeas.NodeID, chickpeas.NodeID, chickpeas.Direction, []string) []chickpeas.NodeID { return .g.AppendNeighborsEach(, , , .g.Match(...)) } // AppendRelationshipsMatched appends traversed relationships' neighbors // and CSR positions to the parallel nodes/poss slices through a // pre-resolved matcher, skipping the per-call type-name resolution on hot // walk paths (the core iterator is a thin inlinable closure constructor, // so the range stays allocation-free). func ( *SnapshotGraph) ( []chickpeas.NodeID, []uint32, chickpeas.NodeID, chickpeas.Direction, *RelMatcher) ([]chickpeas.NodeID, []uint32) { for := range .g.RelsMatch(, , .m) { = append(, .Neighbor) = append(, .Pos) } return , } // CountNeighborsMatched counts u's m-matched dir relationships to v via // the core degree-adaptive probe. func ( *SnapshotGraph) (, chickpeas.NodeID, chickpeas.Direction, *RelMatcher) int { return .g.CountNeighborsMatch(, , , .m) } // AppendRelsBetween appends the stored CSR positions of each m-matched dir // relationship between u and v to poss (multiplicity preserved), scanning // the lower-degree endpoint -- the bound-pair position seek that lets a // named-rel rebind expand skip enumerating the from-node's whole degree. func ( *SnapshotGraph) ( []uint32, , chickpeas.NodeID, chickpeas.Direction, *RelMatcher) []uint32 { return .g.AppendRelsBetweenMatch(, , , , .m) } // ChainRootsVia resolves the chain-collapse capability through the core // structural checks: one rel type, tried against each candidate label. func ( *SnapshotGraph) ( []string, chickpeas.Direction, []string) (chickpeas.RootsVia, bool) { if len() != 1 { return nil, false } for , := range { if , := .g.ChainCollapseVia([0], , ); { return , true } } return nil, false } // FunctionalVia resolves the single-type functionality check. func ( *SnapshotGraph) ( []string, chickpeas.Direction) bool { return len() == 1 && .g.FunctionalVia([0], ) } // SubstringCandidates: the native backend keeps its scan-filter (no // trigram index), matching the Rust native default. func ( *SnapshotGraph) (, , string) (*nodeset.Set, bool) { return nil, false } // FullTextSearch serves the boolean-AND token scan from the engine's lazy // BM25 field index. func ( *SnapshotGraph) (, , string) (*nodeset.Set, bool) { return .g.FullTextSearch(, , ), true } // GeoWithinRadius serves the great-circle radius scan from the engine's // lazy k-d tree index. func ( *SnapshotGraph) (, , string, , , float64) (*nodeset.Set, bool) { return .g.GeoWithinRadius(, , , , , ), true } // GeoWithinBBox serves the bounding-box scan from the engine's geo index. func ( *SnapshotGraph) (, , string, , , , float64) (*nodeset.Set, bool) { return .g.GeoWithinBBox(, , , , , , ), true } // RelWeightReader hoists the weight column once per search: the returned // reader answers 1.0 for an absent or non-numeric weight, matching the // per-rel property fallback. A key with NO column at all returns nil -- // every edge would read 1.0, making the weighted search a unit-weight // Dijkstra, so the caller must classify that case as unit weights and // take the BFS dispatch (the third degraded shape of the constant-cost // bug: the unit Dijkstra coming back through the property door). func ( *SnapshotGraph) ( string) func( uint32) float64 { , := .g.RelColIndexed() if ! { return nil } switch .Dtype() { case chickpeas.DtypeF64: := .F64() return func( uint32) float64 { if , := .Get(); { return } return 1.0 } case chickpeas.DtypeI64: := .I64() return func( uint32) float64 { if , := .Get(); { return float64() } return 1.0 } default: return func(uint32) float64 { return 1.0 } } } // propValue converts an engine property read to a runtime value, folding // the engine's absent conventions (missing, and empty/unresolvable // strings) to not-ok. func propValue( chickpeas.Prop) (value.Value, bool) { , := .Value() if ! { return value.Value{}, false } switch .Kind() { case chickpeas.KindI64: , := .I64() return value.Int(), true case chickpeas.KindF64: , := .F64() return value.Float(), true case chickpeas.KindBool: , := .Bool() return value.Bool(), true default: , := .Str() if ! { return value.Value{}, false } return value.Str(), true } } // engineValue converts a scalar runtime value to the engine's columnar // Value for index lookups; ok is false for non-scalars and for strings // absent from the interner (which can match nothing). func engineValue( *chickpeas.Snapshot, value.Value) (chickpeas.Value, bool) { switch .Kind() { case value.KindInt: , := .AsInt() return chickpeas.I64Value(), true case value.KindFloat: , := .AsFloat() return chickpeas.F64Value(), true case value.KindBool: , := .AsBool() return chickpeas.BoolValue(), true case value.KindStr: , := .AsStr() return .ValueFromString() default: return chickpeas.Value{}, false } } // compile-time check that SnapshotGraph satisfies the seam and the native // capability. var ( _ Graph = (*SnapshotGraph)(nil) _ Native = (*SnapshotGraph)(nil) )