Documentation
¶
Overview ¶
Package lpg implements the Labelled Property Graph model on top of the github.com/FlavioCFOliveira/GoGraph/graph/adjlist mutable adjacency-list backend.
An LPG decorates each node and each edge with a set of labels (interned strings identifying classes/types) and a bag of typed properties. This package provides labels (see Graph.SetNodeLabel, Graph.SetEdgeLabel) and typed properties (see Graph.SetNodeProperty, Graph.SetEdgeProperty).
Concurrency ¶
The Graph type is safe for concurrent use: every individual operation is internally synchronised — label and property shards by RWMutex, adjacency by lock-free atomic per-shard snapshots, and the per-instance, edge-create-count, and edge-handle stores by mutex — so no single accessor races another.
Transaction-atomic visibility, however, is OPT-IN. A committed transaction may span several operations across several substructures (adjacency, node/edge labels, node/edge properties, tombstones, the roaring label bitmaps, and the secondary indexes). To observe a whole transaction atomically — never a partial transaction, never a torn cross-substructure view — reads must run inside Graph.View and writes inside Graph.ApplyAtomically, which flip a transaction's writes visible as one step under a single visibility barrier:
- Per-operation atomicity holds for every accessor, always.
- Partial-transaction-free reads hold ONLY inside Graph.View.
- Cross-substructure consistency (e.g. "if the edge exists, both of its endpoint labels exist") holds ONLY inside Graph.View.
A direct accessor call made outside Graph.View therefore observes a consistent single operation, but may observe a multi-operation transaction half-applied. The full model — and the tracked lock-free per-shard snapshot that will make every read transaction-consistent without the barrier — is described in docs/isolation-design.md.
Index ¶
- type EdgeHandleTriple
- type Graph
- func (g *Graph[N, W]) AddEdge(src, dst N, w W) error
- func (g *Graph[N, W]) AddEdgeH(src, dst N, w W) (handle uint64, err error)
- func (g *Graph[N, W]) AddEdgeHIfAbsent(src, dst N, w W, handle uint64) (inserted bool, err error)
- func (g *Graph[N, W]) AddEdgeLabeled(src, dst N, w W, relType string) error
- func (g *Graph[N, W]) AddEdgeLabeledWithProperty(src, dst N, w W, relType, key string, value PropertyValue) error
- func (g *Graph[N, W]) AddNode(n N) error
- func (g *Graph[N, W]) AddStoreConstraint(kind uint8, labelName, property string)
- func (g *Graph[N, W]) AddStoreIndex(name string)
- func (g *Graph[N, W]) AdjList() *adjlist.AdjList[N, W]
- func (g *Graph[N, W]) ApplyAtomically(fn func() error) error
- func (g *Graph[N, W]) ApplyInsideLocked(fn func() error) error
- func (g *Graph[N, W]) BumpTopoGeneration()
- func (g *Graph[N, W]) ClearStoreConstraints()
- func (g *Graph[N, W]) ClearStoreIndexes()
- func (g *Graph[N, W]) Config() adjlist.Config
- func (g *Graph[N, W]) DecEdgeCreateCount(src, dst N)
- func (g *Graph[N, W]) DecrEdgesAdded()
- func (g *Graph[N, W]) DecrEdgesRemoved()
- func (g *Graph[N, W]) DecrNodesAdded()
- func (g *Graph[N, W]) DecrNodesRemoved()
- func (g *Graph[N, W]) DelEdgeProperty(src, dst N, key string)
- func (g *Graph[N, W]) DelEdgePropertyByHandle(src, dst N, handle uint64, key string)
- func (g *Graph[N, W]) DelEdgePropertyByHandleID(srcID, dstID graph.NodeID, handle uint64, key string)
- func (g *Graph[N, W]) DelNodeProperty(n N, key string)
- func (g *Graph[N, W]) EdgeCreateCount(src, dst N) int64
- func (g *Graph[N, W]) EdgeHasProperty(src, dst N, key string) bool
- func (g *Graph[N, W]) EdgeIndex() *label.Index
- func (g *Graph[N, W]) EdgeLabels(src, dst N) []string
- func (g *Graph[N, W]) EdgeLabelsAt(src, dst N, idx int64) []string
- func (g *Graph[N, W]) EdgeLabelsByHandle(src, dst N, handle uint64) []string
- func (g *Graph[N, W]) EdgeLabelsByHandleID(srcID, dstID graph.NodeID, handle uint64) []string
- func (g *Graph[N, W]) EdgeLabelsByID(srcID, dstID graph.NodeID) []string
- func (g *Graph[N, W]) EdgeProperties(src, dst N) map[string]PropertyValue
- func (g *Graph[N, W]) EdgePropertiesAt(src, dst N, idx int64) map[string]PropertyValue
- func (g *Graph[N, W]) EdgePropertiesByHandle(src, dst N, handle uint64) map[string]PropertyValue
- func (g *Graph[N, W]) EdgePropertiesByHandleID(srcID, dstID graph.NodeID, handle uint64) map[string]PropertyValue
- func (g *Graph[N, W]) EdgePropertiesByID(srcID, dstID graph.NodeID) map[string]PropertyValue
- func (g *Graph[N, W]) EdgeWeight(src, dst N) (W, bool)
- func (g *Graph[N, W]) FirstEdgeHandle(src, dst N) (uint64, bool)
- func (g *Graph[N, W]) ForEachEdgeLabelByID(srcID, dstID graph.NodeID, visit func(name string))
- func (g *Graph[N, W]) ForEachEdgeProperty(src, dst N, visit func(name string, pv PropertyValue))
- func (g *Graph[N, W]) ForEachEdgePropertyByID(srcID, dstID graph.NodeID, visit func(name string, pv PropertyValue))
- func (g *Graph[N, W]) ForEachNodeLabelByID(id graph.NodeID, visit func(name string))
- func (g *Graph[N, W]) GetEdgeProperty(src, dst N, key string) (PropertyValue, bool)
- func (g *Graph[N, W]) GetNodeProperty(n N, key string) (PropertyValue, bool)
- func (g *Graph[N, W]) HasConstraints() bool
- func (g *Graph[N, W]) HasEdgeHandle(src, dst N, handle uint64) bool
- func (g *Graph[N, W]) HasEdgeLabel(src, dst N, name string) bool
- func (g *Graph[N, W]) HasIndexes() bool
- func (g *Graph[N, W]) HasNodeLabel(n N, name string) bool
- func (g *Graph[N, W]) HasNodeLabelByID(id graph.NodeID, name string) bool
- func (g *Graph[N, W]) IncEdgeCreateCount(src, dst N) int64
- func (g *Graph[N, W]) IncrEdgesAdded()
- func (g *Graph[N, W]) IncrEdgesRemoved()
- func (g *Graph[N, W]) IncrNodesAdded()
- func (g *Graph[N, W]) IncrNodesRemoved()
- func (g *Graph[N, W]) IndexManager() *index.Manager
- func (g *Graph[N, W]) IsTombstoned(id graph.NodeID) bool
- func (g *Graph[N, W]) LiveNodeFilter() func(graph.NodeID) bool
- func (g *Graph[N, W]) LiveOrder() uint64
- func (g *Graph[N, W]) LockBarrier()
- func (g *Graph[N, W]) NextEdgeHandle() uint64
- func (g *Graph[N, W]) NodeIndex() *label.Index
- func (g *Graph[N, W]) NodeLabels(n N) []string
- func (g *Graph[N, W]) NodeLabelsByID(id graph.NodeID) []string
- func (g *Graph[N, W]) NodeLabelsInUse() []string
- func (g *Graph[N, W]) NodeProperties(n N) map[string]PropertyValue
- func (g *Graph[N, W]) NodePropertiesByID(id graph.NodeID) map[string]PropertyValue
- func (g *Graph[N, W]) NodePropertiesByIDFunc(id graph.NodeID, visit func(name string, pv PropertyValue))
- func (g *Graph[N, W]) NodePropertyByID(id graph.NodeID, key string) (PropertyValue, bool)
- func (g *Graph[N, W]) PropertyKeys() *PropertyKeyRegistry
- func (g *Graph[N, W]) PropertyKeysInUse() []string
- func (g *Graph[N, W]) Registry() *LabelRegistry
- func (g *Graph[N, W]) RelationshipTypesInUse() []string
- func (g *Graph[N, W]) RemoveAllEdgesFrom(src N)
- func (g *Graph[N, W]) RemoveEdge(src, dst N)
- func (g *Graph[N, W]) RemoveEdgeInstance(src, dst N, idx int64)
- func (g *Graph[N, W]) RemoveEdgeInstanceByHandle(src, dst N, handle uint64)
- func (g *Graph[N, W]) RemoveEdgeLabel(src, dst N, name string)
- func (g *Graph[N, W]) RemoveNode(n N)
- func (g *Graph[N, W]) RemoveNodeLabel(n N, name string)
- func (g *Graph[N, W]) RemoveStoreConstraint(kind uint8, labelName, property string)
- func (g *Graph[N, W]) RemoveStoreIndex(name string)
- func (g *Graph[N, W]) RestoreTombstones(ids []graph.NodeID)
- func (g *Graph[N, W]) Revive(n N)
- func (g *Graph[N, W]) SeedEdgeHandle(next uint64)
- func (g *Graph[N, W]) SetActiveConstraintCount(n int64)
- func (g *Graph[N, W]) SetActiveIndexCount(n int64)
- func (g *Graph[N, W]) SetEdgeLabel(src, dst N, name string)
- func (g *Graph[N, W]) SetEdgeLabelAt(src, dst N, idx int64, name string)
- func (g *Graph[N, W]) SetEdgeLabelByHandle(src, dst N, handle uint64, name string)
- func (g *Graph[N, W]) SetEdgeLabelByHandleID(srcID, dstID graph.NodeID, handle uint64, name string)
- func (g *Graph[N, W]) SetEdgeProperty(src, dst N, key string, value PropertyValue) error
- func (g *Graph[N, W]) SetEdgePropertyAt(src, dst N, idx int64, key string, value PropertyValue) error
- func (g *Graph[N, W]) SetEdgePropertyByHandle(src, dst N, handle uint64, key string, value PropertyValue) error
- func (g *Graph[N, W]) SetEdgePropertyByHandleID(srcID, dstID graph.NodeID, handle uint64, key string, value PropertyValue)
- func (g *Graph[N, W]) SetIndexManager(m *index.Manager)
- func (g *Graph[N, W]) SetNodeLabel(n N, name string) error
- func (g *Graph[N, W]) SetNodeProperty(n N, key string, value PropertyValue) error
- func (g *Graph[N, W]) SetValidator(v SchemaValidator)
- func (g *Graph[N, W]) SideEffectCounters() (nodesAdded, nodesRemoved, edgesAdded, edgesRemoved uint64)
- func (g *Graph[N, W]) StoreConstraints() []StoreConstraint
- func (g *Graph[N, W]) TombstoneCount() int
- func (g *Graph[N, W]) TombstonedIDs() []graph.NodeID
- func (g *Graph[N, W]) TopoGeneration() uint64
- func (g *Graph[N, W]) UnlockBarrier()
- func (g *Graph[N, W]) ValidateNode(n N) error
- func (g *Graph[N, W]) View(fn func())
- func (g *Graph[N, W]) WalkEdgeHandles(fn func(EdgeHandleTriple) bool)
- type LabelID
- type LabelRegistry
- type NodeValidator
- type PropertyKeyID
- type PropertyKeyRegistry
- type PropertyKind
- type PropertyValue
- func BoolValue(b bool) PropertyValue
- func BytesValue(b []byte) PropertyValue
- func DateValue(t time.Time) PropertyValue
- func Float64Value(f float64) PropertyValue
- func Int64Value(i int64) PropertyValue
- func ListValue(elems []PropertyValue) PropertyValue
- func StringValue(s string) PropertyValue
- func TimeValue(t time.Time) PropertyValue
- func (p PropertyValue) Bool() (val, ok bool)
- func (p PropertyValue) Bytes() ([]byte, bool)
- func (p PropertyValue) Float64() (float64, bool)
- func (p PropertyValue) Int64() (int64, bool)
- func (p PropertyValue) Kind() PropertyKind
- func (p PropertyValue) List() ([]PropertyValue, bool)
- func (p PropertyValue) String() (string, bool)
- func (p PropertyValue) Time() (time.Time, bool)
- type SchemaValidator
- type StoreConstraint
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type EdgeHandleTriple ¶
EdgeHandleTriple is one live durable edge identity: the (src, dst) endpoint NodeIDs and the stable handle stamped on that slot. Emitted by Graph.WalkEdgeHandles for the snapshot writer.
type Graph ¶
type Graph[N comparable, W any] struct { // contains filtered or unexported fields }
Graph is a labelled property graph generic over the user node type N and edge weight type W. It composes an adjlist.AdjList with a label registry and per-vertex / per-edge label storage backed by label.Index bitmaps.
Example ¶
ExampleGraph builds a small labelled property graph: nodes carry labels (their classes) and typed properties, and edges connect them. The Config is forwarded to the underlying adjacency list, so Directed selects a directed graph here.
package main
import (
"fmt"
"github.com/FlavioCFOliveira/GoGraph/graph/adjlist"
"github.com/FlavioCFOliveira/GoGraph/graph/lpg"
)
func main() {
g := lpg.New[string, int](adjlist.Config{Directed: true})
// Create two nodes and tag each with a label.
_ = g.AddNode("alice")
_ = g.AddNode("bob")
_ = g.SetNodeLabel("alice", "Person")
_ = g.SetNodeLabel("bob", "Person")
// Attach typed properties via the PropertyValue constructors.
_ = g.SetNodeProperty("alice", "name", lpg.StringValue("Alice"))
_ = g.SetNodeProperty("alice", "age", lpg.Int64Value(30))
// Connect them with a labelled edge.
_ = g.AddEdge("alice", "bob", 0)
g.SetEdgeLabel("alice", "bob", "KNOWS")
name, _ := g.GetNodeProperty("alice", "name")
nameStr, _ := name.String()
age, _ := g.GetNodeProperty("alice", "age")
ageInt, _ := age.Int64()
fmt.Println("alice is Person:", g.HasNodeLabel("alice", "Person"))
fmt.Println("alice.name:", nameStr)
fmt.Println("alice.age:", ageInt)
fmt.Println("alice KNOWS bob:", g.HasEdgeLabel("alice", "bob", "KNOWS"))
}
Output: alice is Person: true alice.name: Alice alice.age: 30 alice KNOWS bob: true
func New ¶
func New[N comparable, W any](cfg adjlist.Config) *Graph[N, W]
New returns a fresh LPG built on top of a new adjlist.AdjList configured by cfg.
func (*Graph[N, W]) AddEdge ¶
AddEdge inserts a directed edge (mirrored when the graph is undirected) from src to dst with weight w. The error contract matches the underlying adjlist.AdjList.AddEdge: callers must propagate adjlist.ErrShardFull when the responsible shard is at adjlist.Config.MaxShardCapacity.
AddEdge does NOT revive a tombstoned endpoint: only Graph.AddNode clears a tombstone. The contract is that callers materialise node patterns via AddNode before linking them, so a live edge is never created onto a logically-removed node. The query executor upholds this (CREATE routes every endpoint through the mutator's AddNode).
func (*Graph[N, W]) AddEdgeH ¶
AddEdgeH inserts a directed edge exactly like Graph.AddEdge but first allocates a stable per-edge handle for it and stamps that handle onto the adjacency slot (via adjlist.AdjList.AddEdgeH). It returns the handle so the caller can key per-instance edge metadata (SetEdgeLabelByHandle / SetEdgePropertyByHandle) by an identity that survives sibling-edge deletion, instead of the positional CREATE index that the old read path re-derived from CSR slot order.
The returned handle is always non-zero. On the simple-graph collapse of a duplicate (src, dst) the underlying adjacency no-ops the slot write and the supplied handle is not stored, but a fresh handle value is still consumed (monotonicity is a property of the counter, not of storage), so callers must treat the handle as advisory in simple-graph mode and keep using the per-pair / per-CREATE-index surfaces there. See edge_handle.go.
AddEdgeH honours the same error and revival contract as Graph.AddEdge.
func (*Graph[N, W]) AddEdgeHIfAbsent ¶
AddEdgeHIfAbsent inserts a directed edge (src, dst, w) stamped with the explicit stable `handle`, but only when no edge with that handle already exists on the (src, dst) pair (Graph.HasEdgeHandle). When the handle is already present the call is a no-op and returns (false, nil): the edge was loaded by the snapshot or applied by an earlier WAL frame, so re-inserting it would create a spurious parallel duplicate. When the handle is absent the edge is inserted via the explicit-handle adjacency path (adjlist.AdjList.AddEdgeH) and the call returns (true, nil).
AddEdgeHIfAbsent is the replay primitive that makes snapshot + full-WAL recovery idempotent without a second live-handle index. It does NOT advance the handle counter — the handle is supplied by the durable record, not freshly minted; Graph.SeedEdgeHandle re-seeds the counter once after replay.
A handle of 0 is treated as "no durable identity" and falls back to a plain Graph.AddEdge so a pre-Stage-2 WAL frame (which carried no handle) still replays. AddEdgeHIfAbsent is NOT safe for concurrent use.
func (*Graph[N, W]) AddEdgeLabeled ¶ added in v0.6.0
AddEdgeLabeled inserts a directed edge (mirrored when the graph is undirected) from src to dst with weight w and tags it with the relationship-type name in a SINGLE adjacency operation: the type is interned and written into the edge's inline label slot AT insertion time, instead of the two-step Graph.AddEdge + Graph.SetEdgeLabel which copies the whole label column after the append. For a bulk labelled build this restores O(degree) amortised cost per source (the fused append is O(1) amortised), versus the O(degree²) a per-edge column copy-on-write would cost.
AddEdgeLabeled is the labelled-build fast path. For the simple single-label case its observable result is identical to AddEdge followed by SetEdgeLabel: the type lands in the first dst-matching inline slot, so Graph.EdgeLabels, Graph.HasEdgeLabel, the per-slot label scan, and the TCK read path all see exactly the same derived label set. To ADD A SECOND distinct type to an already-labelled pair, or to (re)label a PRE-EXISTING edge, use Graph.SetEdgeLabel; that path keeps its general copy-on-write semantics and the overflow spill for multi-label pairs.
The coarse src-keyed edge-label index (g.edgeIdx) is updated exactly as SetEdgeLabel updates it, so index-driven candidate enumeration is unaffected.
AddEdgeLabeled honours the same error and revival contract as Graph.AddEdge: it propagates adjlist.ErrShardFull and does NOT revive a tombstoned endpoint. When the underlying adjacency no-ops the insertion (a simple-graph duplicate (src, dst)) the supplied type is not stamped on the existing slot; callers that may re-label an existing edge must use SetEdgeLabel.
AddEdgeLabeled is safe for concurrent use.
func (*Graph[N, W]) AddEdgeLabeledWithProperty ¶ added in v0.6.0
func (g *Graph[N, W]) AddEdgeLabeledWithProperty(src, dst N, w W, relType, key string, value PropertyValue) error
AddEdgeLabeledWithProperty inserts a directed edge (mirrored when the graph is undirected) from src to dst with weight w, tags it with the relationship-type name, AND records one property (key, value) on it — all in a SINGLE adjacency operation. Both the type and the property value are written into the new edge's inline slot AT insertion time, instead of the three-step Graph.AddEdgeLabeled + Graph.SetEdgeProperty whose final step copies the whole per-source property column. For a bulk property-carrying build this restores O(degree) amortised cost per source (the fused append is O(1) amortised), versus the O(degree²) the per-edge column copy-on-write of Graph.SetEdgeProperty costs.
AddEdgeLabeledWithProperty is the property-carrying labelled-build fast path. Its observable result is identical to AddEdgeLabeled followed by SetEdgeProperty for the simple single-edge-per-pair case the bulk builders use: the type lands in the first dst-matching inline slot and the value lands on the new slot's columnar block, so Graph.EdgeProperties, Graph.GetEdgeProperty, the per-pair coalesce, and the TCK read path all see exactly the same derived state. To set a SECOND property on the edge, or to mutate a PRE-EXISTING edge, use Graph.SetEdgeProperty; that path keeps its general copy-on-write semantics.
If the installed SchemaValidator rejects the value the edge is NOT inserted and the error is returned (validation runs before any mutation), so the fused write keeps the same all-or-nothing contract as a validated SetEdgeProperty. AddEdgeLabeledWithProperty otherwise honours the same error and revival contract as Graph.AddEdge: it propagates adjlist.ErrShardFull and does NOT revive a tombstoned endpoint. When the underlying adjacency no-ops the insertion (a simple-graph duplicate (src, dst)) neither the type nor the property is stamped on the existing slot.
A date-shaped string value (a Cypher Date delivered as a SOH-tagged canonical string) is folded into the int32 epoch-day column exactly as SetEdgeProperty folds it, so it round-trips to a native Date through the Cypher read path.
AddEdgeLabeledWithProperty is safe for concurrent use.
func (*Graph[N, W]) AddNode ¶
AddNode inserts n if not already present. The error contract matches the underlying adjlist.AdjList.AddNode: callers must propagate adjlist.ErrShardFull when the responsible shard is at adjlist.Config.MaxShardCapacity.
AddNode also clears any tombstone on n: re-creating a node that was previously removed via Graph.RemoveNode brings it back to life under the same stable NodeID (resurrection). This is the single node- materialising entry point through which a delete→recreate cycle flows — in-process, on WAL replay, and on snapshot apply — so it is the one place that must revive. Graph.SetNodeLabel does not revive: a tombstoned node is never matched by a read clause, so a label can only reach a removed key after AddNode has already revived it.
func (*Graph[N, W]) AddStoreConstraint ¶ added in v0.6.0
AddStoreConstraint records that a schema constraint of the given kind on (label, property) is declared through the txn.Store-direct API. It is the store-layer dual of the cypher engine's syncConstraintCount: the txn.Store commit-apply path calls it for every committed OpCreateConstraint so that Graph.HasConstraints reports the constraint to a WAL-truncating checkpoint, independent of whether a cypher engine is wired in (#1756).
The (kind, label, property) key makes re-declaring the same constraint idempotent — the active count never over-counts a single durable constraint, the only direction that could let a checkpoint silently drop it.
AddStoreConstraint is safe for concurrent use.
func (*Graph[N, W]) AddStoreIndex ¶ added in v0.6.0
AddStoreIndex records that a secondary index named name is declared through the txn.Store-direct API. It is the store-layer dual of the cypher engine's index-def registry: the txn.Store commit-apply path calls it for every committed OpCreateIndex so that Graph.HasIndexes reports the index to a WAL-truncating checkpoint, independent of whether a cypher engine is wired in (#1755).
The index NAME key makes re-declaring the same index idempotent — the active count never over-counts a single durable index, the only direction that could let a checkpoint silently drop it.
AddStoreIndex is safe for concurrent use.
func (*Graph[N, W]) ApplyAtomically ¶
ApplyAtomically runs fn while holding the graph's transaction-visibility write lock. Every mutation fn performs (across adjacency, labels, properties, tombstones, bitmaps, and indexes) becomes visible to Graph.View readers as a single atomic step: a concurrent View reader observes either none of fn's writes or all of them, never a partial set. fn is the in-memory apply of one durable transaction; callers invoke it only after the transaction's WAL frames are fsynced.
ApplyAtomically must not be called re-entrantly, and the mutations inside fn must not call Graph.View or Graph.ApplyAtomically (the RWMutex is not re-entrant, so a nested acquisition from this goroutine would deadlock). That invariant is enforced: a nested call from a goroutine already inside the barrier panics with a clear message instead of deadlocking. The panic indicates a programmer error and is not recovered by this package. The graph's per-shard write methods that fn calls take their own shard locks beneath visMu, which is safe because visMu is acquired only here and in View.
Concurrent calls from DIFFERENT goroutines are unaffected: they serialise on visMu as before, and the guard never trips on them.
func (*Graph[N, W]) ApplyInsideLocked ¶ added in v0.3.0
ApplyInsideLocked is the barrier-already-held variant of Graph.ApplyAtomically. It runs fn directly without acquiring or releasing visMu — the caller MUST already hold the barrier via Graph.LockBarrier. The re-entrancy guard is NOT re-checked (the caller's stamp stays in effect) and the lock is NOT released afterward.
This method exists solely to satisfy callers that hold the barrier for the lifetime of an explicit transaction (task #1412) and need to run a sub-operation (e.g. one Exec statement) under the same already-held lock. Calling this method without first calling LockBarrier yields undefined behaviour.
func (*Graph[N, W]) BumpTopoGeneration ¶ added in v0.7.0
func (g *Graph[N, W]) BumpTopoGeneration()
BumpTopoGeneration advances the edge-topology generation counter by one. Deliberately separate from Graph.IncrEdgesAdded / Graph.IncrEdgesRemoved (which bump topoGeneration too, alongside the unrelated TCK side-effect counters): a caller that mutates edge topology WITHOUT an enclosing Cypher statement — a direct store/txn.Store/store/txn.Tx user, bypassing the engine's write adapters entirely — has no Cypher-statement side-effect count to attribute an Incr/Decr to, but the graph's edge topology still changed, so any CSR-position-keyed cache still needs invalidating. Calling this alone leaves edgesAddedCount/edgesRemovedCount untouched, which is correct: those counters answer "how many edges did this Cypher statement add/remove," a question a store-direct write was never part of. Safe for concurrent use.
func (*Graph[N, W]) ClearStoreConstraints ¶ added in v0.6.0
func (g *Graph[N, W]) ClearStoreConstraints()
ClearStoreConstraints empties the store-direct constraint set, returning the store-direct count to zero. The cypher engine calls it when it takes ownership of a recovered graph: from that point the engine's own count (SetActiveConstraintCount) is the authoritative source for HasConstraints, so the store-direct count — seeded by recovery for the engine-less case — must not linger and force a checkpoint to over-retain the WAL after the engine later drops a constraint.
ClearStoreConstraints is safe for concurrent use.
func (*Graph[N, W]) ClearStoreIndexes ¶ added in v0.6.0
func (g *Graph[N, W]) ClearStoreIndexes()
ClearStoreIndexes empties the store-direct index set, returning the store-direct count to zero. The cypher engine calls it when it takes ownership of a recovered graph: from that point the engine's own index-def registry is the authoritative source it threads into the checkpoint, so the store-direct count — seeded by recovery for the engine-less case — must not linger and force a checkpoint to over-retain the WAL after the engine later drops an index.
ClearStoreIndexes is safe for concurrent use.
func (*Graph[N, W]) Config ¶ added in v0.2.0
Config returns the adjlist.Config the graph was constructed with. It delegates to the underlying adjlist.AdjList.Config; the configuration is fixed at New and never mutated, so Config is safe to call concurrently with any other operation and always returns the same value for the lifetime of the graph. The snapshot writer reads it to persist the directed/multigraph shape into the manifest.
func (*Graph[N, W]) DecEdgeCreateCount ¶
func (g *Graph[N, W]) DecEdgeCreateCount(src, dst N)
DecEdgeCreateCount decrements the counter by one (floor 0). Used by Graph.RemoveEdge callers (DELETE) so subsequent MERGEs see the updated multiplicity.
DecEdgeCreateCount is safe for concurrent use.
func (*Graph[N, W]) DecrEdgesAdded ¶ added in v0.2.0
func (g *Graph[N, W]) DecrEdgesAdded()
DecrEdgesAdded subtracts one from the added-edge counter. topoGeneration is NOT decremented — it only ever increases, on the Incr side too, because an undo is itself a topology-changing event for any CSR-position-keyed cache: the graph's content afterward differs from the content the moment before the undo ran, even though it matches the content from further back.
func (*Graph[N, W]) DecrEdgesRemoved ¶ added in v0.2.0
func (g *Graph[N, W]) DecrEdgesRemoved()
DecrEdgesRemoved subtracts one from the removed-edge counter. See Graph.DecrEdgesAdded for why topoGeneration still only ever increases.
func (*Graph[N, W]) DecrNodesAdded ¶ added in v0.2.0
func (g *Graph[N, W]) DecrNodesAdded()
DecrNodesAdded / DecrNodesRemoved / DecrEdgesAdded / DecrEdgesRemoved are the exact inverses of the Incr* counters above. They exist for one purpose: the Cypher executor's transaction-undo path replays the inverse of every eagerly applied mutation when a write query errors or panics, and the per-query side- effect deltas the openCypher TCK asserts (Graph.SideEffectCounters) must not retain the increments of a rolled-back statement. Each subtracts one from the matching monotone counter.
These must only be called to invert a prior Incr* on the same graph; they do not floor at zero, so a stray over-decrement would underflow the unsigned counter. The undo log guarantees one Decr per recorded Incr.
Decr* are safe for concurrent use.
func (*Graph[N, W]) DecrNodesRemoved ¶ added in v0.2.0
func (g *Graph[N, W]) DecrNodesRemoved()
DecrNodesRemoved subtracts one from the removed-node counter.
func (*Graph[N, W]) DelEdgeProperty ¶
DelEdgeProperty removes the named property from the directed edge (src, dst). No-op if absent. The key is cleared on every dst-matching slot so the per-pair view no longer reports it.
func (*Graph[N, W]) DelEdgePropertyByHandle ¶ added in v0.6.0
DelEdgePropertyByHandle removes exactly key from the property bag of the edge identified by handle on the (src, dst) pair, leaving every other property of that handle — and every sibling handle on the same pair — untouched. No-op when handle is 0 (the no-handle sentinel), when either endpoint is unknown to the mapper, when no handle store exists for the pair, or when the handle never carried key. When the removal empties the handle's bag the inner byHandle[handle] entry is pruned, and when that leaves the pair with no handles the outer sh.m[k] entry is pruned too, mirroring the pruning Graph.RemoveEdgeInstanceByHandle performs.
It is the single-key analogue of Graph.RemoveEdgeInstanceByHandle (which drops ALL of a handle's labels and properties): a Cypher REMOVE r.x or SET r.x = null on one parallel edge must delete only x from that one instance, not the whole instance. The per-pair coalesced store is mutated separately by the caller (dual-write); this method only touches the handle-keyed per-instance store.
DelEdgePropertyByHandle is safe for concurrent use.
func (*Graph[N, W]) DelEdgePropertyByHandleID ¶ added in v0.6.0
func (g *Graph[N, W]) DelEdgePropertyByHandleID(srcID, dstID graph.NodeID, handle uint64, key string)
DelEdgePropertyByHandleID removes exactly key from the property bag of the edge identified by `handle` on the directed (srcID, dstID) NodeID pair. It is the NodeID-keyed dual of Graph.DelEdgePropertyByHandle, provided for parity with the other durable NodeID-keyed setters; the WAL recovery path itself uses the natural-key Graph.DelEdgePropertyByHandle because its endpoints are codec-decoded natural keys at replay time. No-op when handle is 0, when the key was never interned, when no handle store exists for the pair, or when the handle never carried key. Empties are pruned exactly as Graph.DelEdgePropertyByHandle prunes them.
DelEdgePropertyByHandleID is safe for concurrent use.
func (*Graph[N, W]) DelNodeProperty ¶
DelNodeProperty removes the named property from n. No-op if absent.
func (*Graph[N, W]) EdgeCreateCount ¶
EdgeCreateCount returns the CREATE multiplicity counter for the directed edge (src, dst), or 0 when no CREATE was recorded.
This counter is guarded by its own per-shard mutex and is only per-operation atomic: it is NOT cross-store consistent with the adjacency layer, Graph.EdgeLabelsAt, or Graph.EdgePropertiesAt outside a transaction barrier. A reader that correlates this count with the populated per-instance indices (or any other substructure) while a multi-CREATE multigraph transaction is committing can observe a partial cross-store state — e.g. the count already at 2 while only one instance has been populated. To read a consistent cross-store view, bracket the correlated reads in Graph.View (writers commit under Graph.ApplyAtomically); see docs/isolation-design.md.
EdgeCreateCount is safe for concurrent use.
func (*Graph[N, W]) EdgeHasProperty ¶ added in v0.6.0
EdgeHasProperty reports whether the directed edge (src, dst) carries a value under key that would materialise to a NON-NULL Cypher value — without building that value. It is the storage-presence fast path behind a bound relationship's `r.key IS NOT NULL` / `IS NULL` predicate: the caller needs only the boolean presence, so fetching and boxing the value (as Graph.GetEdgeProperty / Graph.EdgeProperties would) is pure waste.
Congruence with the value path is BY CONSTRUCTION, on two axes:
- Per-pair coalescing — it folds parallel edges exactly as Graph.EdgeProperties does: the LATEST dst-matching adjacency slot that carries key wins. The returned answer therefore reflects the same single coalesced value the Cypher evaluator would observe via EdgeProperties, never an earlier shadowed write.
- Kind gating — the winning slot's storage kind is tested with [kindMapsToNonNullCypher], which mirrors cypher.lpgPropToExpr's nullability table. A present-but-null-mapping property (a stored PropTime or PropBytes) reads as Null through Cypher, so this reports false for it, exactly as `r.key IS NOT NULL` would evaluate to false.
The scan reads only validity bits and per-column kind tags (no value cell), so it allocates nothing. Returns false when either endpoint or key is unknown, or when no dst-matching slot carries a non-null-mapping value for key.
Concurrency-safe under the same lock-free contract as Graph.GetEdgeProperty: it reads an immutable published columnar block and bounds its scan by the shorter of the block and the neighbours snapshot, so a concurrent copy-on-write writer is observed atomically (old block or new, never half-built).
func (*Graph[N, W]) EdgeIndex ¶
EdgeIndex returns the label index over edges. Edge bitmaps are keyed by the source NodeID; this is suitable for label-filtered out-neighbour scans but not for direct edge enumeration.
func (*Graph[N, W]) EdgeLabels ¶
EdgeLabels returns the names of every label attached to the directed edge (src, dst) in unspecified order. The returned slice is freshly allocated and may be mutated by the caller. If either endpoint is unknown or the endpoint pair has no labels attached, EdgeLabels returns nil.
EdgeLabels is the dual of Graph.NodeLabels. It is safe for concurrent use; the snapshot is taken under the per-shard RWMutex (one of 16 stripes keyed by the src endpoint) and the registry's own lock.
The returned set is DERIVED: the union of the relationship type stored inline in each dst-matching adjacency slot and the per-shard overflow store (the second-and-later types of a multi-label pair and any orphaned types). Distinct labels are deduplicated across both sources, so a multigraph pair whose parallel slots happen to share a type reports it once.
func (*Graph[N, W]) EdgeLabelsAt ¶
EdgeLabelsAt returns the labels recorded at instance `idx` of the directed edge (src, dst). Returns nil when the instance was never labelled, when either endpoint is unknown, or when no per-instance store has been initialised for this pair.
This per-instance store is guarded by its own per-shard mutex and is only per-operation atomic: it is NOT cross-store consistent with Graph.EdgeCreateCount, Graph.EdgePropertiesAt, or the adjacency layer outside a transaction barrier. A reader correlating this with Graph.EdgeCreateCount while a multi-CREATE multigraph transaction commits can observe a partial cross-store state. To read a consistent cross-store view, bracket the correlated reads in Graph.View (writers commit under Graph.ApplyAtomically); see docs/isolation-design.md.
EdgeLabelsAt is safe for concurrent use.
func (*Graph[N, W]) EdgeLabelsByHandle ¶
EdgeLabelsByHandle returns the labels recorded for the edge identified by handle on the (src, dst) pair. Returns nil when handle is 0, the handle was never labelled, either endpoint is unknown, or no handle store has been initialised for this pair.
Like the (src, dst, idx) instance stores, this handle store is guarded by its own per-shard mutex and is only per-operation atomic: it is NOT cross-store consistent with Graph.EdgeCreateCount, Graph.EdgePropertiesByHandle, or the adjacency layer outside a transaction barrier. To read a consistent cross-store view, bracket the correlated reads in Graph.View (writers commit under Graph.ApplyAtomically); see docs/isolation-design.md.
EdgeLabelsByHandle is safe for concurrent use.
func (*Graph[N, W]) EdgeLabelsByHandleID ¶
EdgeLabelsByHandleID returns the labels recorded for the edge identified by `handle` on the directed (srcID, dstID) NodeID pair, resolving NodeIDs directly rather than through the natural key. It is the NodeID-keyed dual of Graph.EdgeLabelsByHandle used by the snapshot writer, which walks the adjacency by NodeID and must not pay a Resolve→Lookup round trip per handle. Returns nil when handle is 0, the handle was never labelled, or no handle store exists for the pair.
EdgeLabelsByHandleID is safe for concurrent use.
func (*Graph[N, W]) EdgeLabelsByID ¶ added in v0.6.0
EdgeLabelsByID is the NodeID-keyed counterpart of Graph.EdgeLabels: it returns the labels attached to the directed edge identified by the endpoint NodeIDs (srcID, dstID), in unspecified order, or nil when the pair carries no labels. It is the edge dual of Graph.NodeLabelsByID.
Unlike Graph.EdgeLabels it performs NO Mapper access — no external-key → NodeID lookup — so a caller that already holds both endpoint NodeIDs can resolve edge labels without re-entering the Mapper. This is precisely what the snapshot collectors require: they enumerate endpoints from inside graph.Mapper.Walk, which holds a Mapper shard read lock across its callback, and the Mapper contract forbids re-entry there while a writer may be running (graph/mapper.go:337-345, #1648). The label snapshot is still taken under the per-shard edge-label RWMutex and the registry's own lock, so EdgeLabelsByID is safe for concurrent use.
func (*Graph[N, W]) EdgeProperties ¶
func (g *Graph[N, W]) EdgeProperties(src, dst N) map[string]PropertyValue
EdgeProperties returns a snapshot of every property currently attached to the directed edge (src, dst). When several parallel edges connect the pair the result is the latest-wins coalesced union across their slots.
func (*Graph[N, W]) EdgePropertiesAt ¶
func (g *Graph[N, W]) EdgePropertiesAt(src, dst N, idx int64) map[string]PropertyValue
EdgePropertiesAt returns the property map recorded at instance `idx` of the directed edge (src, dst). Returns nil when the instance was never written or when either endpoint is unknown.
This per-instance store is guarded by its own per-shard mutex and is only per-operation atomic: it is NOT cross-store consistent with Graph.EdgeCreateCount, Graph.EdgeLabelsAt, or the adjacency layer outside a transaction barrier. A reader correlating the count of populated instance indices with Graph.EdgeCreateCount while a multi-CREATE multigraph transaction commits can observe a partial cross-store state (count ahead of the populated indices). To read a consistent cross-store view, bracket the correlated reads in Graph.View (writers commit under Graph.ApplyAtomically); see docs/isolation-design.md.
EdgePropertiesAt is safe for concurrent use.
func (*Graph[N, W]) EdgePropertiesByHandle ¶
func (g *Graph[N, W]) EdgePropertiesByHandle(src, dst N, handle uint64) map[string]PropertyValue
EdgePropertiesByHandle returns the property map recorded for the edge identified by handle on the (src, dst) pair. Returns nil when handle is 0, the handle was never written, or either endpoint is unknown.
Like the (src, dst, idx) instance stores, this handle store is guarded by its own per-shard mutex and is only per-operation atomic: it is NOT cross-store consistent with Graph.EdgeCreateCount, Graph.EdgeLabelsByHandle, or the adjacency layer outside a transaction barrier. To read a consistent cross-store view, bracket the correlated reads in Graph.View (writers commit under Graph.ApplyAtomically); see docs/isolation-design.md.
EdgePropertiesByHandle is safe for concurrent use.
func (*Graph[N, W]) EdgePropertiesByHandleID ¶
func (g *Graph[N, W]) EdgePropertiesByHandleID(srcID, dstID graph.NodeID, handle uint64) map[string]PropertyValue
EdgePropertiesByHandleID returns the property map recorded for the edge identified by `handle` on the directed (srcID, dstID) NodeID pair. It is the NodeID-keyed dual of Graph.EdgePropertiesByHandle used by the snapshot writer. Returns nil when handle is 0, the handle was never written, or no handle store exists for the pair.
EdgePropertiesByHandleID is safe for concurrent use.
func (*Graph[N, W]) EdgePropertiesByID ¶ added in v0.6.0
func (g *Graph[N, W]) EdgePropertiesByID(srcID, dstID graph.NodeID) map[string]PropertyValue
EdgePropertiesByID is the NodeID-keyed counterpart of Graph.EdgeProperties: it returns the latest-wins coalesced property map of the directed edge identified by the endpoint NodeIDs (srcID, dstID), or nil when the pair carries no properties. It is the edge dual of Graph.NodePropertiesByID.
Unlike Graph.EdgeProperties it performs NO Mapper access — no external-key → NodeID lookup — so a caller that already holds both endpoint NodeIDs can resolve edge properties without re-entering the Mapper. This is precisely what the snapshot collectors require: they enumerate endpoints from inside graph.Mapper.Walk, which holds a Mapper shard read lock across its callback, and the Mapper contract forbids re-entry there while a writer may be running (graph/mapper.go:337-345, #1648). The read is served from the lock-free immutable adjacency entry, so EdgePropertiesByID is safe for concurrent use.
func (*Graph[N, W]) EdgeWeight ¶ added in v0.2.0
EdgeWeight returns the weight of the first edge from src to dst and true when such an edge exists, or the zero weight and false otherwise. When several parallel edges connect the pair it returns the weight of the first slot, which is sufficient for the executor's transaction-undo path: it captures the weight of an edge before a failed write query removes it so the inverse Graph.AddEdge restores the same weight.
EdgeWeight performs an O(out-degree) scan of src's adjacency and allocates nothing. It is safe for concurrent use under the same lock-free adjacency snapshot contract as adjlist.AdjList.LoadEntry.
For a weightless graph (adjlist.Config.Weightless) the adjacency carries no weights column, so a present edge reports the zero value of W with ok=true.
func (*Graph[N, W]) FirstEdgeHandle ¶ added in v0.2.0
FirstEdgeHandle returns the stable handle stamped on the FIRST adjacency slot from src to dst — the slot a subsequent Graph.RemoveEdge would remove, because adjlist.AdjList.RemoveEdge removes the lowest-indexed occurrence and compacts the handle column in lock-step. The boolean reports whether such a slot exists AND carries a non-zero handle; it is false when either endpoint is unknown, no src→dst edge exists, or the matched slot has the 0 "no handle" sentinel (a simple-graph or pre-Stage-2 edge).
It lets the write-query transaction-undo log capture the identity of the exact parallel edge instance a DELETE is about to remove, so the inverse can re-add that instance with its ORIGINAL handle (via Graph.AddEdgeHIfAbsent) and the surviving siblings keep theirs — fully reverting an "remove one parallel edge, then fail a later row" rollback without renumbering any handle. See cypher/undo_record.go.
FirstEdgeHandle reads an immutable adjacency snapshot (adjlist.AdjList.LoadEntryH) and allocates nothing; it is safe for concurrent use under the same lock-free contract as Graph.EdgeWeight.
func (*Graph[N, W]) ForEachEdgeLabelByID ¶ added in v0.6.0
ForEachEdgeLabelByID streams the distinct labels of the directed edge (src, dst), invoking visit once per resolved label name without materialising the []string that Graph.EdgeLabelsByID returns. It is the allocation-fusing counterpart of EdgeLabelsByID — the edge-label analogue of Graph.ForEachNodeLabelByID — chiefly for the snapshot writer.
The distinct label ids (inline slots + overflow, deduplicated) are gathered under the edge-label shard read lock; names are resolved and visited after the lock is released, exactly as EdgeLabelsByID does, so visit may safely read the graph. The dedup scratch is the same small per-call slice EdgeLabelsByID uses; the saving is the []string result slice the caller would otherwise range over.
func (*Graph[N, W]) ForEachEdgeProperty ¶ added in v0.6.0
func (g *Graph[N, W]) ForEachEdgeProperty(src, dst N, visit func(name string, pv PropertyValue))
ForEachEdgeProperty streams the latest-wins coalesced property set of the directed edge (src, dst), invoking visit once per emitted (name, value) without building the intermediate per-pair map that Graph.EdgeProperties returns. It is the allocation-fusing counterpart of Graph.EdgeProperties, the edge analogue of Graph.NodePropertiesByIDFunc: a caller that re-keys every property into a different map (chiefly the Cypher result path, which converts each lpg.PropertyValue into a cypher/expr value) would otherwise allocate a throwaway map[string]PropertyValue only to range over it once. Streaming the values lets the caller build its target map directly, removing that intermediate allocation per relationship row.
visit is called zero times when either endpoint is unknown or the pair carries no properties. See Graph.ForEachEdgePropertyByID for the coalescing and concurrency contract.
func (*Graph[N, W]) ForEachEdgePropertyByID ¶ added in v0.6.0
func (g *Graph[N, W]) ForEachEdgePropertyByID(srcID, dstID graph.NodeID, visit func(name string, pv PropertyValue))
ForEachEdgePropertyByID is the NodeID-keyed counterpart of Graph.ForEachEdgeProperty and the streaming counterpart of Graph.EdgePropertiesByID: it invokes visit once per (name, value) of the latest-wins coalesced property set of the edge identified by the endpoint NodeIDs (srcID, dstID), without materialising the intermediate map.
Like Graph.EdgePropertiesByID it performs NO Mapper access, so a caller that already holds both endpoint NodeIDs avoids re-entering the Mapper.
Coalescing: the per-pair view folds parallel edges by taking the LATEST dst-matching adjacency slot per key. Because the columns are per-slot and a key is present in at most one column per slot, visit fires at most once per name PER SLOT; across the parallel slots a name MAY be visited more than once, so a consumer that needs the single coalesced value MUST apply last-write-wins (the last emission for a name is the coalesced winner, exactly as Graph.EdgePropertiesByID records out[name] = v). A map-building consumer gets this for free.
Concurrency-safe under the same lock-free contract as Graph.EdgePropertiesByID: it reads an immutable, atomically-published columnar block and neighbours snapshot and bounds the scan by the shorter of the two, so a concurrent copy-on-write writer is observed atomically (old snapshot or new, never half-built). Unlike Graph.NodePropertiesByIDFunc NO lock is held across visit — the reads are lock-free atomic-pointer loads — so visit imposes no re-entrancy restriction. The PropertyValue passed to visit is a value copy of the immutable cell, so copying it out (or deriving an independent value from it) is safe; for the boxed Bytes/List kinds the same slice-aliasing caveat as Graph.GetEdgeProperty applies.
func (*Graph[N, W]) ForEachNodeLabelByID ¶ added in v0.6.0
ForEachNodeLabelByID streams the labels of the node identified by id, invoking visit once per resolved label name without materialising the []string that Graph.NodeLabelsByID returns. It is the allocation-fusing counterpart of NodeLabelsByID — the label analogue of Graph.NodePropertiesByIDFunc — chiefly for the snapshot writer, which re-keys every label into its own string table and would otherwise allocate a throwaway slice per node.
Concurrency: visit runs while the node-label shard's read lock is held, so it observes a consistent snapshot of the node's labels relative to any concurrent writer holding the shard write lock — identical to Graph.NodeLabelsByID. visit therefore MUST NOT call back into any Graph method that takes a node-label-shard lock (it would deadlock); copying the name string out is safe.
func (*Graph[N, W]) GetEdgeProperty ¶
func (g *Graph[N, W]) GetEdgeProperty(src, dst N, key string) (PropertyValue, bool)
GetEdgeProperty returns the property value attached to the directed edge (src, dst) under key. When several parallel edges connect the pair the latest-winning value across their slots is returned (the slots carry the identical value by the SetEdgeProperty fan-out, so this is well-defined).
func (*Graph[N, W]) GetNodeProperty ¶
func (g *Graph[N, W]) GetNodeProperty(n N, key string) (PropertyValue, bool)
GetNodeProperty returns the property value attached to n under key, and a bool reporting whether the property is set.
func (*Graph[N, W]) HasConstraints ¶ added in v0.3.0
HasConstraints reports whether the cypher engine currently has any schema constraint registered on this graph. It reads a lock-free counter maintained by the engine (SetActiveConstraintCount), so it is cheap enough for the checkpointer to consult on every checkpoint to gate the constraints.bin self-sufficiency requirement (#1464).
HasConstraints is safe for concurrent use.
It reports true when EITHER the engine-maintained count (SetActiveConstraintCount) OR the store-direct count (AddStoreConstraint, maintained by the txn.Store apply path) is positive, so the checkpoint fail-safe is correct whether the constraint was declared through the cypher engine or directly through txn.Tx.CreateConstraint (#1756).
func (*Graph[N, W]) HasEdgeHandle ¶
HasEdgeHandle reports whether the directed (src, dst) pair carries a stored edge whose stable handle equals `handle`. It scans the pair's parallel handle column on the adjacency slot — the single source of truth for which handles are live on which pair — and returns false when handle is 0 (the no-handle sentinel), when either endpoint is unknown to the mapper, or when the pair has no slot stamped with that handle.
HasEdgeHandle is the idempotency predicate WAL replay uses: an OpAddEdgeH whose handle is already present (loaded from the snapshot or applied by an earlier frame) is a no-op, so snapshot + full-WAL recovery does not double the edge.
HasEdgeHandle is safe for concurrent use.
func (*Graph[N, W]) HasEdgeLabel ¶
HasEdgeLabel reports whether the directed edge (src, dst) carries name as a label.
func (*Graph[N, W]) HasIndexes ¶ added in v0.6.0
HasIndexes reports whether any secondary index has been declared through the txn.Store-direct API on this graph. It reads a lock-free counter maintained by the txn.Store apply path (AddStoreIndex / RemoveStoreIndex), so it is cheap enough for the checkpointer to consult on every checkpoint to gate the indexdefs.bin self-sufficiency requirement: a checkpoint that truncates the WAL prefix which first declared an index must carry the index definition in the snapshot, or the index is silently lost on the next reopen (#1755).
It reports true when EITHER the engine-maintained count (SetActiveIndexCount) OR the store-direct count (AddStoreIndex, maintained by the txn.Store apply path) is positive, so the checkpoint fail-safe is correct whether the index was declared through the cypher engine or directly through txn.Tx.CreateIndex (#1755). Two sources are required because the engine's CREATE INDEX commits via Tx.CommitWALOnly, which never replays through the store apply path, so storeIndexActive alone would be blind to every engine-declared index.
HasIndexes is safe for concurrent use.
func (*Graph[N, W]) HasNodeLabel ¶
HasNodeLabel reports whether n carries the named label.
func (*Graph[N, W]) HasNodeLabelByID ¶ added in v0.6.0
HasNodeLabelByID is the NodeID-keyed, allocation-free counterpart of Graph.HasNodeLabel: it reports whether the node identified by id carries the named label without the external-key → NodeID Mapper lookup and without materialising the node's label slice (which [NodeLabelsByID] would).
It backs the lazy `n:Label` predicate fast path in the Cypher engine, which holds the NodeID already and only needs a membership test. An unknown label name (never interned) is a definite "absent" answer, mirroring Graph.HasNodeLabel.
func (*Graph[N, W]) IncEdgeCreateCount ¶
IncEdgeCreateCount bumps the CREATE multiplicity counter for the directed edge (src, dst) by one. Returns the new count.
Idempotent across "edge already exists" calls: simple-graph upsertEdge no-ops the underlying storage write, but the counter still moves so a subsequent MERGE sees the correct multiplicity.
IncEdgeCreateCount is safe for concurrent use.
func (*Graph[N, W]) IncrEdgesAdded ¶
func (g *Graph[N, W]) IncrEdgesAdded()
IncrEdgesAdded records that one edge was freshly added.
func (*Graph[N, W]) IncrEdgesRemoved ¶
func (g *Graph[N, W]) IncrEdgesRemoved()
IncrEdgesRemoved records that one edge was removed.
func (*Graph[N, W]) IncrNodesAdded ¶
func (g *Graph[N, W]) IncrNodesAdded()
IncrNodesAdded / IncrNodesRemoved / IncrEdgesAdded / IncrEdgesRemoved expose the per-direction counters to the cypher executor so the mutator adapters can record each event as it happens. The graph itself does not call these — node and edge mutation flow through the adapters, which know whether a given AddNode/AddEdge was a fresh allocation or a no-op re-intern. IncrNodesAdded records that one node was freshly added.
func (*Graph[N, W]) IncrNodesRemoved ¶
func (g *Graph[N, W]) IncrNodesRemoved()
IncrNodesRemoved records that one node was removed.
func (*Graph[N, W]) IndexManager ¶
IndexManager returns the manager of secondary indexes attached to this graph, or nil when no manager has been set. Callers that need snapshot-durable indexes must register them via index.Manager.CreateIndex on a manager set via Graph.SetIndexManager.
IndexManager is safe for concurrent use; the pointer is loaded with sequential consistency.
func (*Graph[N, W]) IsTombstoned ¶
IsTombstoned reports whether id has been marked removed via Graph.RemoveNode. Used by the Cypher executor's AllNodesScan to skip phantom nodes (those that the Mapper still indexes but that the graph treats as deleted).
func (*Graph[N, W]) LiveNodeFilter ¶ added in v0.6.0
LiveNodeFilter returns a predicate reporting whether a NodeID is live (not tombstoned), or nil when the graph carries no tombstones at all. It is the liveness argument for [csr.BuildFromAdjListLive]: passing it builds a search CSR that omits the ghost edges left behind by Graph.RemoveNode (which tombstones a node without stripping its incident edges), while the nil return on a tombstone-free graph preserves the zero-overhead build fast path (#1790).
The returned predicate is a point-in-time view: it closes over the graph and re-reads tombstone state on each call, so it must be used against a quiescent graph (the same single state the CSR build snapshots).
func (*Graph[N, W]) LockBarrier ¶ added in v0.3.0
func (g *Graph[N, W]) LockBarrier()
LockBarrier acquires the graph's transaction-visibility write lock and stamps the calling goroutine as the barrier holder, identical to Graph.ApplyAtomically but split into a manual acquire/release pair for callers that need to hold the barrier across multiple operations (e.g. an explicit multi-statement transaction that must block concurrent readers for its whole lifetime, task #1412).
The caller MUST release the lock with exactly one paired call to Graph.UnlockBarrier, even if an error or panic occurs — failing to do so deadlocks the engine. The typical pattern is:
g.LockBarrier() defer g.UnlockBarrier()
While the lock is held, any operation inside the barrier that needs to run under the same lock (e.g. [Engine.execUnderBarrier] called from an in-flight Exec) MUST use Graph.ApplyInsideLocked instead of Graph.ApplyAtomically; calling ApplyAtomically from the goroutine that holds the barrier via LockBarrier panics (re-entrancy guard).
LockBarrier must not be called from a goroutine already inside the barrier (ApplyAtomically or a previous LockBarrier); it panics instead of deadlocking.
func (*Graph[N, W]) NextEdgeHandle ¶
NextEdgeHandle returns a fresh, never-reused stable edge handle from the per-graph monotone counter (the exported form of [Graph.nextEdgeHandle]). It is used by the transactional store (store/txn) to mint the handle stamped onto a durable OpAddEdgeH WAL frame BEFORE the edge is applied, so the same handle is written to the log and to the in-memory adjacency. Handles start at 1; 0 is the reserved "no handle" sentinel. The counter is re-seeded after recovery via Graph.SeedEdgeHandle so handles stay monotone across a reopen.
NextEdgeHandle is safe for concurrent use.
func (*Graph[N, W]) NodeLabels ¶
NodeLabels returns the names of every label attached to n in unspecified order.
Example ¶
ExampleGraph_NodeLabels shows that a node may carry several labels at once. NodeLabels returns them in an unspecified order, so callers that need a stable order sort the result.
package main
import (
"fmt"
"sort"
"github.com/FlavioCFOliveira/GoGraph/graph/adjlist"
"github.com/FlavioCFOliveira/GoGraph/graph/lpg"
)
func main() {
g := lpg.New[string, int](adjlist.Config{Directed: true})
_ = g.AddNode("alice")
_ = g.SetNodeLabel("alice", "Person")
_ = g.SetNodeLabel("alice", "Employee")
labels := g.NodeLabels("alice")
sort.Strings(labels)
fmt.Println(labels)
}
Output: [Employee Person]
func (*Graph[N, W]) NodeLabelsByID ¶
NodeLabelsByID is the NodeID-keyed counterpart of Graph.NodeLabels. It skips the external-key → NodeID Mapper lookup for callers that already hold the NodeID (the Cypher result-materialisation path), returning the label names in unspecified order, or nil when id carries no labels.
func (*Graph[N, W]) NodeLabelsInUse ¶ added in v0.5.0
NodeLabelsInUse returns the distinct names of every label currently attached to at least one non-tombstoned node, in unspecified order. Labels borne only by tombstoned (removed) nodes are excluded.
The returned slice is freshly allocated and non-nil; when no live node carries a label it is empty (len 0). The caller owns the slice and may mutate it.
NodeLabelsInUse is safe for concurrent use. It snapshots each of the 16 node-label shards under that shard's RLock (one at a time) and resolves ids through the lock-free LabelRegistry. The result is a point-in-time view and is not guaranteed to be consistent across shards.
func (*Graph[N, W]) NodeProperties ¶
func (g *Graph[N, W]) NodeProperties(n N) map[string]PropertyValue
NodeProperties returns a snapshot of every property currently attached to n.
func (*Graph[N, W]) NodePropertiesByID ¶
func (g *Graph[N, W]) NodePropertiesByID(id graph.NodeID) map[string]PropertyValue
NodePropertiesByID is the NodeID-keyed counterpart of Graph.NodeProperties. It skips the external-key → NodeID Mapper lookup, so callers that already hold the NodeID — chiefly the Cypher result-materialisation path, which resolves the NodeID once for identity and then needs both properties and labels — avoid a redundant Mapper round-trip per node. The returned map is a fresh copy owned by the caller; it is nil when id has no recorded properties. Concurrency-safe under the same contract as NodeProperties.
func (*Graph[N, W]) NodePropertiesByIDFunc ¶ added in v0.3.1
func (g *Graph[N, W]) NodePropertiesByIDFunc(id graph.NodeID, visit func(name string, pv PropertyValue))
NodePropertiesByIDFunc invokes visit once per property attached to the node identified by id, passing the resolved property name and a value copy of the PropertyValue. It is the allocation-fusing counterpart of Graph.NodePropertiesByID: callers that immediately re-key every property into a different map (chiefly the Cypher result-materialisation path, which converts each lpg.PropertyValue into a cypher/expr value) would otherwise allocate a throwaway intermediate map[string]PropertyValue only to range over it once. Streaming the bag through visit lets the caller build its target map directly, removing that intermediate allocation per returned node.
visit is called zero times for a node with no recorded properties (and for an unknown id). The iteration order is unspecified, matching Go map iteration.
Concurrency and isolation: visit runs while the property shard's read lock is held, so it observes a consistent snapshot of the node's properties relative to any concurrent writer holding the shard write lock — identical to the guarantee of Graph.NodePropertiesByID. visit therefore MUST NOT call back into any Graph method that takes a property-shard lock (it would deadlock) and MUST NOT retain the PropertyValue beyond the callback in a way that aliases graph-internal state; the PropertyValue passed in is a value copy, so copying it out (or deriving an independent value from it) is safe and is the intended use.
func (*Graph[N, W]) NodePropertyByID ¶ added in v0.3.1
NodePropertyByID returns the single property keyed by name attached to the node identified by id, without materialising the node's full property map. It is the single-key counterpart of Graph.NodePropertiesByID and exists for the Cypher scalar-projection fast path: a predicate or projection that reads only n.name from a bound node fetches just that one value instead of copying every property into a fresh map per row.
The boolean reports whether the property is present (false for both an unknown key name and a node that carries no such property), mirroring the missing-key-is-null semantics of openCypher property access. The returned PropertyValue is a value copy owned by the caller. Concurrency-safe under the same contract as Graph.NodeProperties: the read holds the property shard's read lock for the duration of the lookup, so it observes a consistent view of the node's properties relative to any concurrent writer holding the shard write lock.
func (*Graph[N, W]) PropertyKeys ¶
func (g *Graph[N, W]) PropertyKeys() *PropertyKeyRegistry
PropertyKeys returns the property-key registry.
func (*Graph[N, W]) PropertyKeysInUse ¶ added in v0.5.0
PropertyKeysInUse returns the distinct names of every property key present on at least one non-tombstoned node, or on at least one edge whose endpoints are both non-tombstoned, in unspecified order. The result is the union across the node and edge property stores.
The returned slice is freshly allocated and non-nil; when no live element carries a property it is empty (len 0). The caller owns the slice and may mutate it.
PropertyKeysInUse is safe for concurrent use. It snapshots each of the 16 node-property shards and each of the 16 edge-property shards under that shard's RLock (one at a time) and resolves ids through the lock-free PropertyKeyRegistry. The result is a point-in-time view and is not guaranteed to be consistent across shards.
func (*Graph[N, W]) Registry ¶
func (g *Graph[N, W]) Registry() *LabelRegistry
Registry returns the underlying label registry.
func (*Graph[N, W]) RelationshipTypesInUse ¶ added in v0.5.0
RelationshipTypesInUse returns the distinct names of every edge label attached to at least one edge whose endpoints are both non-tombstoned, in unspecified order. An edge label survives only while at least one live edge (both endpoints live) still bears it.
The returned slice is freshly allocated and non-nil; when no live edge carries a label it is empty (len 0). The caller owns the slice and may mutate it.
RelationshipTypesInUse is safe for concurrent use. It walks the inline per-slot label column of every source's adjacency (the lock-free snapshot) and each of the 16 edge-label overflow shards under that shard's RLock (one at a time), and resolves ids through the lock-free LabelRegistry. The result is a point-in-time view and is not guaranteed to be consistent across shards.
func (*Graph[N, W]) RemoveAllEdgesFrom ¶ added in v0.3.0
func (g *Graph[N, W]) RemoveAllEdgesFrom(src N)
RemoveAllEdgesFrom removes all edges incident from src in O(d) time for a degree-d hub, rather than the O(d²) cost of d sequential Graph.RemoveEdge calls. After clearing the adjacency layer it also clears the per-pair edge state (labels, properties, handles, instance records, CREATE counters) for every endpoint pair that src was involved in, exactly as Graph.RemoveEdge does for each individual edge.
For directed graphs the outgoing edges are removed and their forward per-pair state is cleared. For undirected graphs the mirror entries are also removed and both directions' per-pair state are cleared.
RemoveAllEdgesFrom is safe for concurrent use.
func (*Graph[N, W]) RemoveEdge ¶
func (g *Graph[N, W]) RemoveEdge(src, dst N)
RemoveEdge removes one edge (src, dst) from the adjacency layer (and the mirrored (dst, src) edge when the graph is undirected). When this leaves the endpoint pair with NO remaining edge — the last parallel edge between them is gone — RemoveEdge also strips the per-pair edge labels and edge properties, so re-creating an edge between the same endpoints later does not resurrect the removed edge's labels or properties (the edge analogue of node-tombstone hygiene). While any parallel edge between the pair survives, the shared per-pair label and property surfaces are left intact.
RemoveEdge is the edge-deletion entry point used by the Cypher executor and WAL replay, so the in-memory state and the recovered state agree. Callers that operate purely on adjacency (e.g. search algorithms) may keep using adjlist.AdjList.RemoveEdge directly; that path does not touch labels or properties.
Example ¶
ExampleGraph_RemoveEdge shows that deleting an edge clears its per-pair label/property surface once the endpoint pair is fully disconnected, so re-creating an edge between the same endpoints does not resurrect the removed relationship's type.
package main
import (
"fmt"
"github.com/FlavioCFOliveira/GoGraph/graph/adjlist"
"github.com/FlavioCFOliveira/GoGraph/graph/lpg"
)
func main() {
g := lpg.New[string, int](adjlist.Config{Directed: true})
_ = g.AddEdge("alice", "bob", 0)
g.SetEdgeLabel("alice", "bob", "KNOWS")
fmt.Println("before delete:", g.HasEdgeLabel("alice", "bob", "KNOWS"))
g.RemoveEdge("alice", "bob")
_ = g.AddEdge("alice", "bob", 0) // re-create the same pair
fmt.Println("after re-create:", g.HasEdgeLabel("alice", "bob", "KNOWS"))
}
Output: before delete: true after re-create: false
func (*Graph[N, W]) RemoveEdgeInstance ¶
RemoveEdgeInstance discards every per-instance label and property for (src, dst) at `idx` so subsequent reads (EdgeLabelsAt / EdgePropertiesAt) return empty. Used by DELETE to drop a specific logical edge while leaving sibling instances at other indices untouched.
RemoveEdgeInstance is safe for concurrent use.
func (*Graph[N, W]) RemoveEdgeInstanceByHandle ¶
RemoveEdgeInstanceByHandle discards every per-handle label and property for (src, dst) at handle so subsequent reads (EdgeLabelsByHandle / EdgePropertiesByHandle) return empty. The handle-keyed analogue of Graph.RemoveEdgeInstance; used by DELETE to drop one logical edge while leaving sibling handles untouched. No-op when handle is 0.
RemoveEdgeInstanceByHandle is safe for concurrent use.
func (*Graph[N, W]) RemoveEdgeLabel ¶ added in v0.2.0
RemoveEdgeLabel detaches name from the directed edge (src, dst). It is the exported inverse of Graph.SetEdgeLabel used by the Cypher executor's transaction-undo path to strip a label a failed write query had attached. No-op when either endpoint is unknown, name was never interned, or the label is not present on the pair. Unlike Graph.SetEdgeLabel it does not require the edge to still exist in the adjacency, so it can also undo a label that was set on an edge later removed within the same failed statement.
Like [Graph.clearEdgePairState], the coarse src-keyed edge label index (g.edgeIdx) is intentionally left untouched: it is read only as an over-approximation the executor verifies against the authoritative per-pair labels, so a stale entry can cost at most a filtered-out candidate, never a wrong result.
RemoveEdgeLabel is safe for concurrent use.
func (*Graph[N, W]) RemoveNode ¶
func (g *Graph[N, W]) RemoveNode(n N)
RemoveNode marks the node n as removed. Subsequent reads through IsTombstoned / LiveOrder / TombstonedIDs treat n as absent. The underlying Mapper retains the slot (NodeID stability is a hard contract), but label, property, and adjacency reads on the tombstoned id remain safe; callers should also strip labels / properties / incident edges before calling RemoveNode so the tombstone reflects the fully-deleted node state. No-op when n was never interned or is already tombstoned.
Example ¶
ExampleGraph_RemoveNode shows that node deletion is a tombstone — the NodeID slot is permanent, so the node is excluded from the live count rather than reusing its id — and that re-creating the same key revives the node under the SAME stable NodeID. This is what makes a delete-then-recreate cycle yield exactly one live node, and (once the tombstone set is persisted) survive a store reopen.
package main
import (
"fmt"
"github.com/FlavioCFOliveira/GoGraph/graph/adjlist"
"github.com/FlavioCFOliveira/GoGraph/graph/lpg"
)
func main() {
g := lpg.New[string, int](adjlist.Config{Directed: true})
_ = g.SetNodeLabel("auth", "Spec")
id, _ := g.AdjList().Mapper().Lookup("auth")
g.RemoveNode("auth")
fmt.Println("tombstoned:", g.IsTombstoned(id), "live:", g.LiveOrder())
// Re-create the same key: revived under the same NodeID.
_ = g.AddNode("auth")
id2, _ := g.AdjList().Mapper().Lookup("auth")
fmt.Println("revived:", !g.IsTombstoned(id), "sameID:", id == id2, "live:", g.LiveOrder())
}
Output: tombstoned: true live: 0 revived: true sameID: true live: 1
func (*Graph[N, W]) RemoveNodeLabel ¶
RemoveNodeLabel detaches name from n. No-op if absent.
func (*Graph[N, W]) RemoveStoreConstraint ¶ added in v0.6.0
RemoveStoreConstraint drops the store-direct constraint slot identified by (kind, label, property), the dual of Graph.AddStoreConstraint for a committed OpDropConstraint. Dropping a constraint that was never recorded is a no-op, so a DROP that suppresses a CREATE folded away by a prior checkpoint cannot drive the active count negative.
RemoveStoreConstraint is safe for concurrent use.
func (*Graph[N, W]) RemoveStoreIndex ¶ added in v0.6.0
RemoveStoreIndex drops the store-direct index slot identified by name, the dual of Graph.AddStoreIndex for a committed OpDropIndex. Dropping an index that was never recorded is a no-op, so a DROP that suppresses a CREATE folded away by a prior checkpoint cannot drive the active count negative.
RemoveStoreIndex is safe for concurrent use.
func (*Graph[N, W]) RestoreTombstones ¶
RestoreTombstones marks every id in ids as removed, reconstructing the tombstone set captured by Graph.TombstonedIDs at snapshot time. It is the load-phase dual of Graph.RemoveNode used by snapshot recovery: it re-tombstones by NodeID directly and does not require the natural key to be resolvable. A later Graph.AddNode for the same id still revives it, so a delete→recreate that straddles a snapshot resolves correctly.
RestoreTombstones is intended for the one-shot snapshot-load phase of recovery and is not safe to call concurrently with other mutations or reads on g.
func (*Graph[N, W]) Revive ¶ added in v0.2.0
func (g *Graph[N, W]) Revive(n N)
Revive clears any tombstone on the node interned under key n, marking it live again. It is the exported, key-addressed inverse of Graph.RemoveNode used by the Cypher executor's transaction-undo path to restore a node that a failed write query had tombstoned. No-op when n was never interned or is not currently tombstoned. The clear is taken under the same lock as Graph.IsTombstoned/Graph.LiveOrder, so it is atomic against those readers.
Revive is safe for concurrent use.
func (*Graph[N, W]) SeedEdgeHandle ¶
SeedEdgeHandle raises the per-graph stable-handle high-water counter so the next Graph.AddEdgeH returns a value strictly greater than `next-1` — i.e. at least `next`. It is called once at the end of recovery with max(live handle)+1 so a post-recovery edge creation never re-mints a handle that is already live on disk (invariant I5: handles stay unique and monotone across a reopen).
The operation is monotone: seeding with a value at or below the current counter is a no-op, so calling it with a stale `next` cannot rewind the counter. SeedEdgeHandle is safe for concurrent use, though recovery calls it from the single load goroutine.
func (*Graph[N, W]) SetActiveConstraintCount ¶ added in v0.3.0
SetActiveConstraintCount records the number of schema constraints currently registered, for HasConstraints to report. The cypher engine calls it under its single-writer lock after every constraint registration, drop, and recovery re-seed, so the value never under-counts a durably-registered constraint that a concurrent checkpoint might otherwise miss.
SetActiveConstraintCount is safe for concurrent use.
func (*Graph[N, W]) SetActiveIndexCount ¶ added in v0.6.0
SetActiveIndexCount records the number of secondary indexes currently registered in the cypher engine's index-def registry, for HasIndexes to report. The cypher engine calls it under its single-writer lock after every index registration, drop, and recovery re-seed (Engine.syncIndexCount), so the value never under-counts a durably-registered index that a concurrent checkpoint might otherwise miss — the index analogue of SetActiveConstraintCount (#1755).
SetActiveIndexCount is safe for concurrent use.
func (*Graph[N, W]) SetEdgeLabel ¶
SetEdgeLabel attaches label to the directed edge (src, dst). The edge must already exist in the underlying adjacency list; otherwise the call is a no-op. The label is associated with the source NodeID's row in the edge index.
The first relationship type of a pair is stored inline in the adjacency slot's label column; a second distinct type spills to the per-shard overflow store. The two together form the pair's derived label set returned by Graph.EdgeLabels. The whole update runs under the pair's edge-label shard write lock so the slot and overflow halves transition together with respect to a concurrent reader.
func (*Graph[N, W]) SetEdgeLabelAt ¶
SetEdgeLabelAt attaches `name` to the directed edge instance (src, dst) at the supplied 1-based CREATE index. No-op when either endpoint is unknown to the underlying mapper.
SetEdgeLabelAt is safe for concurrent use.
func (*Graph[N, W]) SetEdgeLabelByHandle ¶
SetEdgeLabelByHandle attaches name to the directed edge identified by the stable handle on the (src, dst) pair. No-op when handle is 0 (the no-handle sentinel) or when either endpoint is unknown to the mapper.
SetEdgeLabelByHandle is safe for concurrent use.
func (*Graph[N, W]) SetEdgeLabelByHandleID ¶
SetEdgeLabelByHandleID attaches `name` to the edge identified by `handle` on the directed (srcID, dstID) NodeID pair, resolving by NodeID rather than natural key. It is the NodeID-keyed dual of Graph.SetEdgeLabelByHandle used by the snapshot/WAL recovery path, which has already restored the mapper by NodeID and must not pay a Resolve→Lookup round trip. No-op when handle is 0.
SetEdgeLabelByHandleID is safe for concurrent use.
func (*Graph[N, W]) SetEdgeProperty ¶
func (g *Graph[N, W]) SetEdgeProperty(src, dst N, key string, value PropertyValue) error
SetEdgeProperty records the named property on the directed edge (src, dst). The edge must already exist; otherwise the call is a no-op (mirroring SetEdgeLabel). Returns any error returned by the installed SchemaValidator; when the validator rejects the write the graph state is left unchanged.
The value is written into the per-slot columnar block of src at every slot whose neighbour is dst, so the per-pair view coalesces to the latest value for the key. The write is copy-on-write under the adjacency shard lock: a new immutable column block is built with every dst-matching slot updated and is published with a single atomic store, so a concurrent lock-free reader observes either the prior block or the fully-updated one.
func (*Graph[N, W]) SetEdgePropertyAt ¶
func (g *Graph[N, W]) SetEdgePropertyAt(src, dst N, idx int64, key string, value PropertyValue) error
SetEdgePropertyAt records the property `key`=`value` for the directed edge instance (src, dst) at the supplied 1-based CREATE index. Returns any error returned by the installed SchemaValidator; when the validator rejects the write the graph state is left unchanged.
SetEdgePropertyAt is safe for concurrent use.
func (*Graph[N, W]) SetEdgePropertyByHandle ¶
func (g *Graph[N, W]) SetEdgePropertyByHandle(src, dst N, handle uint64, key string, value PropertyValue) error
SetEdgePropertyByHandle records key=value for the edge identified by handle on the (src, dst) pair. No-op when handle is 0 or when either endpoint is unknown to the mapper. Returns any error returned by the installed SchemaValidator; when the validator rejects the write the graph state is left unchanged.
SetEdgePropertyByHandle is safe for concurrent use.
func (*Graph[N, W]) SetEdgePropertyByHandleID ¶
func (g *Graph[N, W]) SetEdgePropertyByHandleID(srcID, dstID graph.NodeID, handle uint64, key string, value PropertyValue)
SetEdgePropertyByHandleID records key=value on the edge identified by `handle` on the directed (srcID, dstID) NodeID pair. It is the NodeID-keyed dual of Graph.SetEdgePropertyByHandle used by the snapshot/WAL recovery path. No-op when handle is 0.
This method is intentionally called only by the snapshot/WAL recovery path and bypasses the SchemaValidator: values replayed here were validated at the time of the original write and must not fail during recovery.
SetEdgePropertyByHandleID is safe for concurrent use.
func (*Graph[N, W]) SetIndexManager ¶
SetIndexManager installs m as the manager of secondary indexes on this graph. Passing nil detaches the current manager. The Graph retains a borrowed reference to m; the caller owns m's lifetime.
SetIndexManager is safe for concurrent use; the pointer is stored with sequential consistency. Goroutines that call Graph.IndexManager after this store returns will observe m (or a later value).
func (*Graph[N, W]) SetNodeLabel ¶
SetNodeLabel attaches label to n, inserting n if needed. Returns the error from the underlying adjlist.AdjList.AddNode (which can only happen via a future bounded-growth implementation); the current adjlist.AdjList.AddNode never fails, so callers in codepaths that do not configure adjlist.Config.MaxShardCapacity may safely ignore the return.
func (*Graph[N, W]) SetNodeProperty ¶
func (g *Graph[N, W]) SetNodeProperty(n N, key string, value PropertyValue) error
SetNodeProperty records the named property on n with the given value, inserting n into the graph if necessary. Returns the error from the underlying adjlist.AdjList.AddNode when present, or any error returned by the installed SchemaValidator.
func (*Graph[N, W]) SetValidator ¶
func (g *Graph[N, W]) SetValidator(v SchemaValidator)
SetValidator installs v as the runtime schema validator for this graph. Once set, every call to Graph.SetNodeProperty and Graph.SetEdgeProperty will invoke v.Validate before applying the write; a non-nil error from Validate causes the write to be rejected and the error returned to the caller.
When v also implements NodeValidator (as *schema.Schema does), whole-node invariants such as required-property existence are enforced separately, at the node-finalisation boundary, via Graph.ValidateNode. Per-property typing is enforced eagerly here at each Graph.SetNodeProperty; existence cannot be, because a node acquires its properties one mutation at a time and is not complete until finalised.
Pass nil to remove any previously installed validator.
SetValidator is safe for concurrent use.
func (*Graph[N, W]) SideEffectCounters ¶
func (g *Graph[N, W]) SideEffectCounters() (nodesAdded, nodesRemoved, edgesAdded, edgesRemoved uint64)
SideEffectCounters returns the per-direction counters maintained by the graph: nodes added, nodes removed, edges added, edges removed since SnapshotSideEffectCounters was last called. Used by the Cypher TCK side-effect comparator to verify +nodes / -nodes / +relationships / -relationships are accurate counts (not net changes).
func (*Graph[N, W]) StoreConstraints ¶ added in v0.8.0
func (g *Graph[N, W]) StoreConstraints() []StoreConstraint
StoreConstraints returns a snapshot of the store-direct schema constraints recorded on this graph (seeded by recovery, or by the txn.Store apply path). It lets the cypher engine re-enforce durable UNIQUE / NOT NULL constraints when it opens over a recovered store even if the caller did not thread them explicitly (see cypher.NewEngineWithStore). The returned order is unspecified; the slice is a fresh copy the caller owns.
StoreConstraints is safe for concurrent use.
func (*Graph[N, W]) TombstoneCount ¶
TombstoneCount returns the number of NodeIDs currently marked removed. It reads a lock-free counter, so it is cheap enough to gate the optional emission of the snapshot tombstone component on every checkpoint.
TombstoneCount is safe for concurrent use.
func (*Graph[N, W]) TombstonedIDs ¶
TombstonedIDs returns the NodeIDs currently marked removed via Graph.RemoveNode, in ascending order. The result is a fresh slice the caller owns; an empty (never-deleted) graph returns a zero-length slice. Used by the snapshot writer to persist the tombstone set durably so node deletions survive a store reopen.
TombstonedIDs is safe for concurrent use.
func (*Graph[N, W]) TopoGeneration ¶ added in v0.7.0
TopoGeneration returns the current value of the graph's edge-topology generation counter (rmp #1871): a purely monotonic count of edge additions, removals, and undos of either, since the graph was created. Two reads returning the same value guarantee no edge was added, removed, or had either undone in between, which is exactly the invalidation signal a CSR-position-keyed cache (such as the Cypher engine's edge-type-filter cache) needs. It says nothing about node-only or property-only mutations, which never shift an existing edge's CSR position; see the topoGeneration field doc for why that scope is sufficient and intentional. Safe for concurrent use.
func (*Graph[N, W]) UnlockBarrier ¶ added in v0.3.0
func (g *Graph[N, W]) UnlockBarrier()
UnlockBarrier releases the transaction-visibility write lock acquired via Graph.LockBarrier. It MUST be called from the same goroutine that called LockBarrier, and exactly once per LockBarrier call. After this call completes, concurrent Graph.View readers may proceed and Graph.ApplyAtomically may be called again from any goroutine.
func (*Graph[N, W]) ValidateNode ¶ added in v0.2.0
ValidateNode enforces the installed validator's whole-node invariants against the current, complete label and property set of the node interned under n. It is the node-finalisation hook: a caller building a node (one Graph.AddNode, then any number of Graph.SetNodeLabel and Graph.SetNodeProperty calls) invokes ValidateNode once the node is fully populated to reject it when it violates a required-property/existence constraint that the per-value Graph.SetNodeProperty check cannot detect.
Enforcement is deliberately split from the mutation point. Per-property typing is checked eagerly inside Graph.SetNodeProperty because a single value can be judged in isolation; required-property existence cannot, since a legitimate node receives its label before the property that the label requires (for example CREATE (:User {email:'a@b'}) sets the User label before the email property). Validating existence at the mutation point would reject such a node mid-construction, so existence is enforced here instead, once the node is finalised.
ValidateNode returns nil when no validator is installed, when the installed validator does not implement NodeValidator, or when the node satisfies every whole-node invariant. It does not mutate the graph; on a non-nil return the caller is responsible for rolling back or discarding the half-built node.
ValidateNode is safe for concurrent use, under the same per-operation snapshot contract as Graph.NodeLabels and Graph.NodeProperties: it reads a consistent label set and a consistent property bag, but a writer mutating the same node concurrently may change the node between the two reads. Build a node to completion before finalising it.
func (*Graph[N, W]) View ¶
func (g *Graph[N, W]) View(fn func())
View runs fn while holding the graph's transaction-visibility read lock, so fn observes a consistent snapshot of the graph in which no in-flight transaction is partially applied: any transaction committed via Graph.ApplyAtomically is visible to fn either entirely or not at all, and that view is stable for fn's whole duration (snapshot isolation for the bracketed reads). Concurrent View readers do not block one another.
Transactional readers that must not observe a partial transaction — the query executor's read clauses, and any goroutine reading the mutable graph concurrently with writers — should perform their reads inside View. Reads issued outside View remain per-operation atomic (the long-standing concurrency contract) but may observe a partially-applied multi-op transaction; View is what closes that window.
fn must not perform writes and must not call Graph.ApplyAtomically or Graph.View (the RWMutex is not re-entrant). A nested Graph.View would deadlock the instant any writer queues behind the outer read lock, and a nested Graph.ApplyAtomically always deadlocks; both are enforced — a nested call from a goroutine already inside the barrier panics with a clear message instead of deadlocking. The panic indicates a programmer error and is not recovered by this package.
Concurrent View readers from DIFFERENT goroutines do not block one another and never trip the guard; only a same-goroutine nested acquisition does.
Example ¶
ExampleGraph_View shows the recommended way to read a graph that may be mutated concurrently: wrap a multi-op transaction in lpg.Graph.ApplyAtomically and the reads that must observe it whole in lpg.Graph.View. Per-operation accessors are always individually atomic, but only View guarantees a reader never sees a multi-op transaction half-applied — here, the edge without its endpoint labels. Inside View the cross-substructure invariant "the edge exists ⇔ both endpoint labels exist" always holds.
package main
import (
"fmt"
"github.com/FlavioCFOliveira/GoGraph/graph/adjlist"
"github.com/FlavioCFOliveira/GoGraph/graph/lpg"
)
func main() {
g := lpg.New[string, int](adjlist.Config{Directed: true})
// One transaction establishes a cross-substructure invariant: the edge
// alice→bob and both endpoint :Hot labels become visible together.
_ = g.ApplyAtomically(func() error {
_ = g.AddEdge("alice", "bob", 0)
_ = g.SetNodeLabel("alice", "Hot")
_ = g.SetNodeLabel("bob", "Hot")
return nil
})
// A consistent read pins the whole transaction for its duration.
g.View(func() {
edge := g.AdjList().HasEdge("alice", "bob")
srcHot := g.HasNodeLabel("alice", "Hot")
dstHot := g.HasNodeLabel("bob", "Hot")
// The invariant "edge ⇔ src:Hot ⇔ dst:Hot": all three observations
// agree, so the set of distinct values has size one.
consistent := edge == srcHot && srcHot == dstHot
fmt.Println("edge:", edge, "src:Hot:", srcHot, "dst:Hot:", dstHot)
fmt.Println("invariant holds:", consistent)
})
}
Output: edge: true src:Hot: true dst:Hot: true invariant holds: true
func (*Graph[N, W]) WalkEdgeHandles ¶
func (g *Graph[N, W]) WalkEdgeHandles(fn func(EdgeHandleTriple) bool)
WalkEdgeHandles calls fn once for every live directed edge slot that carries a non-zero stable handle. It returns early if fn returns false. Slots with a 0 handle (the no-handle sentinel, e.g. a simple-graph edge or a pre-Stage-2 edge) are skipped: there is no durable identity to persist for them.
The walk is the snapshot writer's enumeration of the adjacency handle column. It iterates source nodes in the underlying mapper's [Walk] order — the exact order the CSR, labels and properties snapshot writers use — and within each source in adjacency slot order (insertion order). That makes the persisted edgehandles.bin component byte-stable across writes of the same logical state and aligned with the CSR component, honouring the cross-process byte-equality contract the snapshot relies on.
WalkEdgeHandles is NOT safe for concurrent use with mutations on g.
type LabelID ¶
type LabelID uint32
LabelID is the compact internal identifier produced by the LabelRegistry for an interned label string.
type LabelRegistry ¶
type LabelRegistry struct {
// contains filtered or unexported fields
}
LabelRegistry interns label names and assigns sequential LabelIDs. It is safe for concurrent use.
Both read paths are fully lock-free: LabelRegistry.Lookup (name→id) loads the immutable forward table through an atomic.Pointer and LabelRegistry.Resolve (id→name) loads the immutable id→name snapshot, neither taking any lock. The write path (LabelRegistry.Intern of a previously unseen name — a rare event) serialises under a mutex, builds fresh immutable tables extended by one entry, and publishes them — the id→name snapshot before the name→id table — so any reader that observes an id from Lookup can already Resolve it, and any reader that observes an id in a bag observes (by release/acquire ordering through that bag's own publication) tables at least as new as the ones Intern published. Lookup and Resolve therefore never miss a live id.
func NewLabelRegistry ¶
func NewLabelRegistry() *LabelRegistry
NewLabelRegistry returns an empty registry.
func (*LabelRegistry) Intern ¶
func (r *LabelRegistry) Intern(name string) LabelID
Intern returns a stable LabelID for name, allocating one on first encounter. It runs on the write path only (label assignment). A lock-free fast path returns an already-interned id without taking the mutex; only the first interning of a previously unseen name serialises under mu to publish the extended tables. The steady-state label vocabulary is small and stable.
func (*LabelRegistry) Lookup ¶
func (r *LabelRegistry) Lookup(name string) (LabelID, bool)
Lookup returns the LabelID for name and true, or 0 and false when name has not been interned. It is lock-free: it loads the immutable name→id table once and reads it, so concurrent per-row label-predicate lookups never serialise nor bounce a shared reader-count cache line.
type NodeValidator ¶ added in v0.2.0
type NodeValidator interface {
ValidateNode(labels []string, props map[string]PropertyValue) error
}
NodeValidator is the optional whole-node enforcement hook. A SchemaValidator installed via Graph.SetValidator that also implements NodeValidator gains required-property/existence enforcement: callers invoke Graph.ValidateNode at the point a node is finalised (after all of its labels and properties are set) to reject a node that violates a whole-node invariant the per-value SchemaValidator.Validate cannot see.
ValidateNode receives the node's complete label set and property bag and returns a non-nil error to reject it. It is satisfied by *schema.Schema, whose github.com/FlavioCFOliveira/GoGraph/graph/lpg/schema.Schema.ValidateNode has the matching signature, so an installed schema enforces required properties through Graph.ValidateNode without any extra wiring.
Implementations must be safe for concurrent use.
type PropertyKeyID ¶
type PropertyKeyID uint32
PropertyKeyID is the compact identifier of an interned property name.
type PropertyKeyRegistry ¶
type PropertyKeyRegistry struct {
// contains filtered or unexported fields
}
PropertyKeyRegistry interns property names and assigns sequential PropertyKeyIDs. It is safe for concurrent use.
Both read paths are fully lock-free: PropertyKeyRegistry.Lookup (name→id) loads the immutable forward table through an atomic.Pointer and PropertyKeyRegistry.Resolve (id→name) loads the immutable id→name snapshot, neither taking any lock. The write path (PropertyKeyRegistry.Intern of a previously unseen name) serialises under a mutex, builds fresh immutable tables extended by one entry, and publishes them — the id→name snapshot first, then the name→id table — so any reader that observes an id from Lookup can already Resolve it, and any reader that observes id in a property bag observes (by release/acquire ordering through that bag's own publication) tables at least as new as the ones Intern published. Lookup/Resolve therefore never miss a live id. Per-row property predicates hit Lookup once per access per reader; making it lock-free removes the RWMutex reader-count atomic that otherwise bounces across cores under concurrent scans. The O(n) copy on intern is a deliberate trade: the property-key vocabulary is append-mostly schema, interned at warm-up and read billions of times.
func NewPropertyKeyRegistry ¶
func NewPropertyKeyRegistry() *PropertyKeyRegistry
NewPropertyKeyRegistry returns an empty registry.
func (*PropertyKeyRegistry) Intern ¶
func (r *PropertyKeyRegistry) Intern(name string) PropertyKeyID
Intern returns a stable PropertyKeyID for name. It runs on the write path only (property assignment). A lock-free fast path returns an already-interned id without taking the mutex; only the first interning of a previously unseen name serialises under mu to publish the extended tables. The steady-state property vocabulary is small and stable.
func (*PropertyKeyRegistry) Lookup ¶
func (r *PropertyKeyRegistry) Lookup(name string) (PropertyKeyID, bool)
Lookup returns the PropertyKeyID for name and true when known. It is lock-free: it loads the immutable name→id table once and reads it, so concurrent per-row property-predicate lookups never serialise nor bounce a shared reader-count cache line.
func (*PropertyKeyRegistry) Resolve ¶
func (r *PropertyKeyRegistry) Resolve(id PropertyKeyID) (string, bool)
Resolve returns the name interned under id. It is lock-free: it loads the immutable id→name snapshot once and indexes into it.
type PropertyKind ¶
type PropertyKind uint8
PropertyKind tags a PropertyValue with its underlying Go type.
const ( PropString PropertyKind = iota + 1 PropInt64 PropFloat64 PropBool PropTime PropBytes PropList // ordered list of PropertyValue elements; v is []PropertyValue )
The supported property kinds. They are stable across releases — new kinds extend this enum; existing values must not be reordered or reused.
type PropertyValue ¶
type PropertyValue struct {
// contains filtered or unexported fields
}
PropertyValue is a tagged union of typed property values. It is laid out as a single (kind, any) pair, totalling 24 bytes on a 64-bit platform regardless of the inhabited variant. The zero value is invalid; values are constructed via the typed constructors (StringValue, Int64Value, etc.).
A PropertyValue is immutable after construction and is copied by value, so it is safe for concurrent reads by multiple goroutines without external locking. The one caveat is the slice-bearing variants: PropertyValue.Bytes and PropertyValue.List return slices that alias the value's backing store, so callers must not mutate the returned slice (doing so would mutate the otherwise-immutable value and break the concurrency guarantee).
func BytesValue ¶
func BytesValue(b []byte) PropertyValue
BytesValue builds a PropBytes wrapping b (no copy).
func DateValue ¶ added in v0.6.0
func DateValue(t time.Time) PropertyValue
DateValue builds a Cypher-visible Date property from t's calendar date — its year, month and day in t's location; any time-of-day and time zone are ignored. The value is the canonical SOH-tagged date string that the columnar storage tier folds into its compact int32 epoch-day column (~4 bytes/value) and that the Cypher read path decodes back to a native Date.
Prefer DateValue over a hand-formatted ISO string (StringValue) for date properties written through the Go API: an untagged string stays in the 16-byte-header string column and reads back as a String, whereas a DateValue costs ~4 bytes/value and round-trips as a Date — the same on-disk and in-memory form a date written through Cypher produces. (Contrast TimeValue/ PropTime, which is not Cypher-visible and reads back as Null.)
func ListValue ¶
func ListValue(elems []PropertyValue) PropertyValue
ListValue builds a PropList from elems. The slice is stored directly (no copy); callers must not modify elems after calling ListValue.
func (PropertyValue) Bool ¶
func (p PropertyValue) Bool() (val, ok bool)
Bool returns the bool value and true when v carries a bool.
func (PropertyValue) Bytes ¶
func (p PropertyValue) Bytes() ([]byte, bool)
Bytes returns the []byte value and true when v carries one. The returned slice aliases the value held by v.
func (PropertyValue) Float64 ¶
func (p PropertyValue) Float64() (float64, bool)
Float64 returns the float64 value and true when v carries a float64.
func (PropertyValue) Int64 ¶
func (p PropertyValue) Int64() (int64, bool)
Int64 returns the int64 value and true when v carries an int64.
func (PropertyValue) Kind ¶
func (p PropertyValue) Kind() PropertyKind
Kind returns the underlying type tag.
func (PropertyValue) List ¶
func (p PropertyValue) List() ([]PropertyValue, bool)
List returns the []PropertyValue elements and true when v carries a PropList. The returned slice aliases the value held by v; callers must not modify it.
func (PropertyValue) String ¶
func (p PropertyValue) String() (string, bool)
String returns the string value and true when v carries a string, the zero value and false otherwise.
type SchemaValidator ¶
type SchemaValidator interface {
Validate(propertyName string, value PropertyValue) error
}
SchemaValidator is the interface that schema enforcement hooks implement. It is satisfied by *schema.Schema after properties have been registered.
Validate receives the property name and the value about to be written. A nil return allows the write; a non-nil return rejects it with the returned error, leaving the graph state unchanged.
Validate enforces only per-property typing — a single value examined in isolation — because it runs at the mutation point, where the node is not yet complete (a node acquires its labels and properties one mutation at a time; see Graph.SetNodeProperty). Whole-node invariants such as required-property existence cannot be decided from one value and are enforced separately by NodeValidator/Graph.ValidateNode at the node-finalisation boundary.
Implementations must be safe for concurrent use.
type StoreConstraint ¶ added in v0.8.0
type StoreConstraint struct {
// Kind is the constraint kind (0 = UNIQUE, 1 = NOT NULL), matching the
// txn package's ConstraintKind ordinals.
Kind uint8
// Label is the constrained node label.
Label string
// Property is the constrained property key.
Property string
}
StoreConstraint is a durable schema-constraint slot recorded on the graph by the txn.Store apply path or by recovery (see Graph.AddStoreConstraint). It carries the constraint's enforcement identity — kind, label, property — but not its user-defined name, which the store-direct path does not retain.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package schema declares the optional type schema for a labelled property graph: which labels exist, which property keys exist, which lpg.PropertyKind each property carries, and which properties each label requires.
|
Package schema declares the optional type schema for a labelled property graph: which labels exist, which property keys exist, which lpg.PropertyKind each property carries, and which properties each label requires. |