lpg

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 2, 2026 License: MIT Imports: 8 Imported by: 0

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 the label half of that contract (see [SetNodeLabel], [SetEdgeLabel]); typed properties are added by subsequent tasks in the same sprint.

Concurrency

The Graph type is safe for concurrent use. Label operations are guarded by their own RWMutexes; the underlying adjacency list retains its own contracts.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type EdgeHandleTriple

type EdgeHandleTriple struct {
	Src    graph.NodeID
	Dst    graph.NodeID
	Handle uint64
}

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

func (g *Graph[N, W]) AddEdge(src, dst N, w W) error

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

func (g *Graph[N, W]) AddEdgeH(src, dst N, w W) (handle uint64, err error)

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

func (g *Graph[N, W]) AddEdgeHIfAbsent(src, dst N, w W, handle uint64) (inserted bool, err error)

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]) AddNode

func (g *Graph[N, W]) AddNode(n N) error

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]) AdjList

func (g *Graph[N, W]) AdjList() *adjlist.AdjList[N, W]

AdjList returns the underlying adjacency-list backend.

func (*Graph[N, W]) ApplyAtomically

func (g *Graph[N, W]) ApplyAtomically(fn func() error) error

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 (the RWMutex is not re-entrant). 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.

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]) DelEdgeProperty

func (g *Graph[N, W]) DelEdgeProperty(src, dst N, key string)

DelEdgeProperty removes the named property from the directed edge (src, dst). No-op if absent.

func (*Graph[N, W]) DelNodeProperty

func (g *Graph[N, W]) DelNodeProperty(n N, key string)

DelNodeProperty removes the named property from n. No-op if absent.

func (*Graph[N, W]) EdgeCreateCount

func (g *Graph[N, W]) EdgeCreateCount(src, dst N) int64

EdgeCreateCount returns the CREATE multiplicity counter for the directed edge (src, dst), or 0 when no CREATE was recorded.

EdgeCreateCount is safe for concurrent use.

func (*Graph[N, W]) EdgeIndex

func (g *Graph[N, W]) EdgeIndex() *label.Index

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

func (g *Graph[N, W]) EdgeLabels(src, dst N) []string

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.

func (*Graph[N, W]) EdgeLabelsAt

func (g *Graph[N, W]) EdgeLabelsAt(src, dst N, idx int64) []string

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.

EdgeLabelsAt is safe for concurrent use.

func (*Graph[N, W]) EdgeLabelsByHandle

func (g *Graph[N, W]) EdgeLabelsByHandle(src, dst N, handle uint64) []string

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.

EdgeLabelsByHandle is safe for concurrent use.

func (*Graph[N, W]) EdgeLabelsByHandleID

func (g *Graph[N, W]) EdgeLabelsByHandleID(srcID, dstID graph.NodeID, handle uint64) []string

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]) 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).

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.

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.

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]) 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.

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]) HasEdgeHandle

func (g *Graph[N, W]) HasEdgeHandle(src, dst N, handle uint64) bool

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

func (g *Graph[N, W]) HasEdgeLabel(src, dst N, name string) bool

HasEdgeLabel reports whether the directed edge (src, dst) carries name as a label.

func (*Graph[N, W]) HasNodeLabel

func (g *Graph[N, W]) HasNodeLabel(n N, name string) bool

HasNodeLabel reports whether n carries the named label.

func (*Graph[N, W]) IncEdgeCreateCount

func (g *Graph[N, W]) IncEdgeCreateCount(src, dst N) int64

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

func (g *Graph[N, W]) IndexManager() *index.Manager

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.

func (*Graph[N, W]) IsTombstoned

func (g *Graph[N, W]) IsTombstoned(id graph.NodeID) bool

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]) LiveOrder

func (g *Graph[N, W]) LiveOrder() uint64

LiveOrder returns the number of non-tombstoned interned nodes.

func (*Graph[N, W]) NextEdgeHandle

func (g *Graph[N, W]) NextEdgeHandle() uint64

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]) NodeIndex

func (g *Graph[N, W]) NodeIndex() *label.Index

NodeIndex returns the label index over nodes.

func (*Graph[N, W]) NodeLabels

func (g *Graph[N, W]) NodeLabels(n N) []string

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

func (g *Graph[N, W]) NodeLabelsByID(id graph.NodeID) []string

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]) 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]) PropertyKeys

func (g *Graph[N, W]) PropertyKeys() *PropertyKeyRegistry

PropertyKeys returns the property-key registry.

func (*Graph[N, W]) Registry

func (g *Graph[N, W]) Registry() *LabelRegistry

Registry returns the underlying label registry.

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

func (g *Graph[N, W]) RemoveEdgeInstance(src, dst N, idx int64)

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

func (g *Graph[N, W]) RemoveEdgeInstanceByHandle(src, dst N, handle uint64)

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]) RemoveNode

func (g *Graph[N, W]) RemoveNode(n N)

RemoveNode marks the node n as removed. Subsequent reads through IsTombstoned / WalkLiveNodes 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

func (g *Graph[N, W]) RemoveNodeLabel(n N, name string)

RemoveNodeLabel detaches name from n. No-op if absent.

func (*Graph[N, W]) RestoreTombstones

func (g *Graph[N, W]) RestoreTombstones(ids []graph.NodeID)

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]) SeedEdgeHandle

func (g *Graph[N, W]) SeedEdgeHandle(next uint64)

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]) SetEdgeLabel

func (g *Graph[N, W]) SetEdgeLabel(src, dst N, name string)

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.

func (*Graph[N, W]) SetEdgeLabelAt

func (g *Graph[N, W]) SetEdgeLabelAt(src, dst N, idx int64, name string)

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

func (g *Graph[N, W]) SetEdgeLabelByHandle(src, dst N, handle uint64, name string)

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

func (g *Graph[N, W]) SetEdgeLabelByHandleID(srcID, dstID graph.NodeID, handle uint64, name string)

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.

func (*Graph[N, W]) SetEdgePropertyAt

func (g *Graph[N, W]) SetEdgePropertyAt(src, dst N, idx int64, key string, value PropertyValue)

SetEdgePropertyAt records the property `key`=`value` for the directed edge instance (src, dst) at the supplied 1-based CREATE index.

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)

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.

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.

SetEdgePropertyByHandleID is safe for concurrent use.

func (*Graph[N, W]) SetIndexManager

func (g *Graph[N, W]) SetIndexManager(m *index.Manager)

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 intended to be called during graph construction (before any concurrent mutators are spawned). It is safe to call from any goroutine, but readers that captured g.IndexManager() before the swap continue to see the previous value until they re-read.

func (*Graph[N, W]) SetNodeLabel

func (g *Graph[N, W]) SetNodeLabel(n N, name string) error

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.

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]) TombstoneCount

func (g *Graph[N, W]) TombstoneCount() int

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

func (g *Graph[N, W]) TombstonedIDs() []graph.NodeID

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]) 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 (the RWMutex is not re-entrant); nested View calls are permitted only when no writer is contending, so callers should not nest View.

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.

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. The fast path takes a read lock only.

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.

func (*LabelRegistry) Resolve

func (r *LabelRegistry) Resolve(id LabelID) (string, bool)

Resolve returns the name interned under id, or the empty string and false when id is unknown.

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.

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.

func (*PropertyKeyRegistry) Lookup

func (r *PropertyKeyRegistry) Lookup(name string) (PropertyKeyID, bool)

Lookup returns the PropertyKeyID for name and true when known.

func (*PropertyKeyRegistry) Resolve

func (r *PropertyKeyRegistry) Resolve(id PropertyKeyID) (string, bool)

Resolve returns the name interned under id.

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.).

func BoolValue

func BoolValue(b bool) PropertyValue

BoolValue builds a PropBool.

func BytesValue

func BytesValue(b []byte) PropertyValue

BytesValue builds a PropBytes wrapping b (no copy).

func Float64Value

func Float64Value(f float64) PropertyValue

Float64Value builds a PropFloat64.

func Int64Value

func Int64Value(i int64) PropertyValue

Int64Value builds a PropInt64.

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 StringValue

func StringValue(s string) PropertyValue

StringValue builds a PropString.

func TimeValue

func TimeValue(t time.Time) PropertyValue

TimeValue builds a PropTime.

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.

func (PropertyValue) Time

func (p PropertyValue) Time() (time.Time, bool)

Time returns the time.Time value and true when v carries one.

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.

Implementations must be safe for concurrent use.

Directories

Path Synopsis
Package schema declares the optional type schema for a labelled property graph: which labels exist, which property keys exist, and which [PropertyKind] each property carries.
Package schema declares the optional type schema for a labelled property graph: which labels exist, which property keys exist, and which [PropertyKind] each property carries.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL