lpg

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jun 5, 2026 License: MIT Imports: 11 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 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

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 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]) Config added in v0.2.0

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

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.

func (*Graph[N, W]) DecrEdgesRemoved added in v0.2.0

func (g *Graph[N, W]) DecrEdgesRemoved()

DecrEdgesRemoved subtracts one from the removed-edge counter.

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

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.

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

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

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.

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

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.

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]) EdgeWeight added in v0.2.0

func (g *Graph[N, W]) EdgeWeight(src, dst N) (W, bool)

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.

func (*Graph[N, W]) FirstEdgeHandle added in v0.2.0

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

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]) 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]) RemoveEdgeLabel added in v0.2.0

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

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

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.

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]) 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]) ValidateNode added in v0.2.0

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

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.

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

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.

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.

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.

Jump to

Keyboard shortcuts

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