graph

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: 7 Imported by: 0

Documentation

Overview

Package graph defines the core types and interfaces shared by every backend in the gograph module.

The package establishes a small set of contracts:

  • NodeID is the compact internal identifier used by all storage backends. It is an opaque alias of uint64.
  • Mapper interns arbitrary comparable user-facing identifiers to stable NodeID values, keeping the public API generic while the storage stays cache-friendly.
  • Graph is the minimal directed-graph contract every backend implements. Specialised interfaces (undirected, weighted, property graphs) extend it in dedicated subpackages.

Concurrency. The interfaces declared here do not, on their own, prescribe a concurrency model: each implementation documents its own contract on its concrete types. As a rule of thumb, gograph implementations are safe for concurrent reads on an immutable snapshot, and serialise writes per backend.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrMapperEntryCorrupted = errors.New("graph: Mapper.LoadFrom entries corrupted")

ErrMapperEntryCorrupted is returned by Mapper.LoadFrom when the supplied entries violate the on-disk invariants the snapshot writer is responsible for upholding: an intra-shard index that disagrees with the natural key's hash-derived shard, a non-contiguous intra-shard slot sequence, or a duplicate (NodeID, key) record.

View Source
var ErrMapperNotEmpty = errors.New("graph: Mapper.LoadFrom on non-empty mapper")

ErrMapperNotEmpty is returned by Mapper.LoadFrom when the caller tries to seed a Mapper that already holds at least one interned value. LoadFrom is intended for one-shot recovery initialisation against a fresh (zero-state) Mapper; reseeding a live Mapper would silently shadow its existing entries and is a programmer error.

Functions

func MapperShardCount

func MapperShardCount() int

MapperShardCount returns the number of independently locked shards every Mapper uses internally. Test and tooling code that needs to reason about shard placement at runtime — e.g. adversarial key generators that collapse many distinct keys into a single shard — should call this function instead of hardcoding the constant so the caller is automatically robust to future configuration changes.

func MapperShardOf

func MapperShardOf(id NodeID) uint64

MapperShardOf returns the shard index encoded in id. It is the public counterpart of [unpackNodeID] restricted to the shard component and exists so external tooling can validate placement without depending on the internal [packNodeID] layout.

Types

type Graph

type Graph[N comparable, W any] interface {
	// Order returns the number of distinct nodes currently in the graph.
	Order() uint64

	// Size returns the number of edges currently in the graph. For
	// multigraphs, parallel edges count separately.
	Size() uint64

	// Neighbours returns an iterator over the out-neighbours of n and
	// the weight of each connecting edge. The iterator yields no values
	// when n is not present in the graph. The order in which neighbours
	// are visited is implementation-defined.
	Neighbours(n N) iter.Seq2[N, W]

	// HasEdge reports whether an edge from src to dst is present.
	// It returns false when either endpoint is unknown.
	HasEdge(src, dst N) bool

	// AddNode inserts n if not already present. It is a no-op when n is
	// already known. Implementations return an error when a bounded
	// resource (for example, a sharded slot array with an explicit
	// upper bound) is exhausted; callers must handle that error and
	// stop offering further work on the affected shard.
	AddNode(n N) error

	// AddEdge inserts a directed edge from src to dst with weight w,
	// adding the endpoints if they are not yet present. Multigraph
	// implementations accept parallel edges; simple implementations
	// document their de-duplication policy. Implementations return an
	// error when a bounded resource is exhausted (see [Graph.AddNode]);
	// no edge is published in that case.
	AddEdge(src, dst N, w W) error

	// RemoveEdge removes the directed edge from src to dst if present.
	// It is a no-op when no such edge exists. The endpoints remain in
	// the graph.
	RemoveEdge(src, dst N)
}

Graph is the minimal directed-graph contract.

Implementations choose their own storage layout (adjacency list, CSR, mmap'd file, …) and document their own concurrency contract; this interface only fixes the shape callers see.

N is the user-facing node type (any comparable type). W is the edge weight type; use struct{} for unweighted graphs.

type Mapper

type Mapper[N comparable] struct {
	// contains filtered or unexported fields
}

Mapper interns user-facing identifiers of type N as compact NodeID values. Interning is stable for the lifetime of the Mapper: a value always resolves to the same NodeID and a NodeID always resolves back to the value that produced it.

Mapper is safe for concurrent use by any number of goroutines. The implementation is sharded into 256 independent stripes keyed by the hash of N, so contention only arises between goroutines operating on values that collide on the lowest 8 bits of their hash.

The zero value of Mapper is not usable; construct one with NewMapper.

Example

ExampleMapper shows the core contract of a Mapper: arbitrary comparable user keys are interned to compact NodeID values, and the mapping is stable — the same key always yields the same NodeID, which resolves back to the original key.

package main

import (
	"fmt"

	"github.com/FlavioCFOliveira/GoGraph/graph"
)

func main() {
	m := graph.NewMapper[string]()

	// Interning a key returns its NodeID; re-interning the same key
	// returns the identical NodeID (the mapping is stable for the
	// Mapper's lifetime).
	alice := m.Intern("alice")
	again := m.Intern("alice")
	bob := m.Intern("bob")

	// Resolve reverses the mapping back to the user key.
	key, ok := m.Resolve(alice)

	fmt.Println("alice stable:", alice == again)
	fmt.Println("alice != bob:", alice != bob)
	fmt.Println("resolve:", key, ok)
	fmt.Println("interned:", m.Len())
}
Output:
alice stable: true
alice != bob: true
resolve: alice true
interned: 2

func NewMapper

func NewMapper[N comparable]() *Mapper[N]

NewMapper returns a fresh, empty Mapper ready for concurrent use.

func (*Mapper[N]) Intern

func (m *Mapper[N]) Intern(k N) NodeID

Intern returns the NodeID associated with k, allocating a new one on first encounter. Subsequent calls with the same value return the same NodeID. The fast path (k already interned) takes a read lock only and performs no heap allocation.

func (*Mapper[N]) Len

func (m *Mapper[N]) Len() int

Len returns the total number of values currently interned across every shard. The returned count is a consistent snapshot per shard but may not reflect concurrent inserts in other shards.

func (*Mapper[N]) LoadFrom

func (m *Mapper[N]) LoadFrom(entries []MapperEntry[N]) error

LoadFrom rebuilds m's internal state from a snapshot's entries. It is intended for one-shot recovery initialisation against a fresh (zero-state) Mapper.

The pre-conditions enforced are:

  1. m must be empty in every shard (ErrMapperNotEmpty otherwise).
  2. Each entry's NodeID, when unpacked, must yield a shard index equal to mapperShardFor(entry.Key). The writer guarantees this because it composes NodeIDs via [packNodeID] from the same hash.
  3. After grouping entries by shard and sorting by intra-index, intra-indexes must form the contiguous sequence 0..N-1. Any gap surfaces as ErrMapperEntryCorrupted.
  4. Within a shard, no two entries may collide on the natural key.

Post-condition: subsequent Mapper.Intern calls with a previously seeded key return the original NodeID; new keys get fresh slots after the seeded ones (the next intra-index is len(reverse)).

LoadFrom is safe for concurrent goroutines only with respect to other LoadFrom calls (which would all fail with ErrMapperNotEmpty after the first); it must not run concurrently with any Intern/Lookup/Resolve/Walk call on the same Mapper.

func (*Mapper[N]) Lookup

func (m *Mapper[N]) Lookup(k N) (NodeID, bool)

Lookup returns the NodeID previously assigned to k and true, or the zero NodeID and false when k has not been interned. The fast path holds a read lock only and performs no heap allocation. Unlike Mapper.Intern, Lookup never mutates the Mapper, which makes it the right primitive for read-only operations (HasEdge, Neighbours, existence checks) on backends layered above the Mapper.

Example

ExampleMapper_Lookup distinguishes Lookup (read-only) from Intern (which assigns a NodeID on first reference). Lookup never mutates the Mapper, so it reports whether a key has already been interned.

package main

import (
	"fmt"

	"github.com/FlavioCFOliveira/GoGraph/graph"
)

func main() {
	m := graph.NewMapper[string]()
	m.Intern("alice")

	_, known := m.Lookup("alice")
	_, unknown := m.Lookup("carol")

	fmt.Println("alice known:", known)
	fmt.Println("carol known:", unknown)
}
Output:
alice known: true
carol known: false

func (*Mapper[N]) MaxNodeID

func (m *Mapper[N]) MaxNodeID() NodeID

MaxNodeID returns one more than the largest NodeID that has been assigned by this Mapper. It is the natural size for an array indexed directly by NodeID (e.g. a CSR offsets array). Returns 0 when no value has been interned.

func (*Mapper[N]) Resolve

func (m *Mapper[N]) Resolve(id NodeID) (N, bool)

Resolve returns the value previously interned under id, or the zero value of N and false when id was not produced by this Mapper.

func (*Mapper[N]) Walk

func (m *Mapper[N]) Walk(fn func(NodeID, N) bool)

Walk invokes fn for every interned (NodeID, value) pair, taking each shard's RLock once for the whole iteration instead of once per Resolve call. Returns early when fn returns false.

Concurrency: Walk holds each shard's RLock while iterating that shard, so concurrent Intern calls on the same shard block until Walk advances past it. Concurrent Resolve calls on the same shard also block (the read lock is held for the duration of the inner loop). Use Walk for bulk export where many Resolves would otherwise dominate; prefer Resolve for individual lookups.

Example

ExampleMapper_Walk performs a bulk export of every interned (NodeID, key) pair. Walk visits each pair exactly once; the iteration order follows the Mapper's internal sharding rather than insertion order, so callers that need a stable order sort the result themselves.

package main

import (
	"fmt"
	"sort"

	"github.com/FlavioCFOliveira/GoGraph/graph"
)

func main() {
	m := graph.NewMapper[string]()
	m.Intern("alice")
	m.Intern("bob")
	m.Intern("carol")

	var keys []string
	m.Walk(func(_ graph.NodeID, key string) bool {
		keys = append(keys, key)
		return true // returning false would stop the walk early
	})
	sort.Strings(keys)

	fmt.Println(keys)
}
Output:
[alice bob carol]

type MapperEntry

type MapperEntry[N comparable] struct {
	ID  NodeID
	Key N
}

MapperEntry describes one (NodeID -> natural key) pair as serialised by the snapshot writer. The NodeID is packed so the unpacked shard index matches mapperShardFor(Key), and the intra-shard index agrees with the slot the key would occupy if all entries for that shard were interned in NodeID-ascending order. The snapshot writer guarantees both invariants by enumerating pairs via Mapper.Walk.

type NodeID

type NodeID uint64

NodeID is the compact internal identifier assigned by a Mapper to a user-facing node value. It is an opaque uint64; callers must not interpret its bit layout, which is an implementation detail of the Mapper that produced it.

The zero value of NodeID is a valid identifier (the first node a mapper interns); use the bool returned by Mapper.Resolve to distinguish "unknown" from "first node".

Directories

Path Synopsis
Package adjlist provides a mutable, sharded adjacency-list backend for the gograph module.
Package adjlist provides a mutable, sharded adjacency-list backend for the gograph module.
Package csr provides an immutable Compressed Sparse Row (CSR) view of a graph for read-mostly analytical workloads.
Package csr provides an immutable Compressed Sparse Row (CSR) view of a graph for read-mostly analytical workloads.
Package generation publishes immutable graph snapshots (typically csr.CSR views) under a refcount-protected pointer so readers can observe a consistent generation while a new one is being prepared in the background.
Package generation publishes immutable graph snapshots (typically csr.CSR views) under a refcount-protected pointer so readers can observe a consistent generation while a new one is being prepared in the background.
Package index coordinates the secondary indexes attached to a labelled property graph.
Package index coordinates the secondary indexes attached to a labelled property graph.
btree
Package btree provides an order-preserving property index over a constraints.Ordered value type, answering range predicates against the NodeIDs that carry each value.
Package btree provides an order-preserving property index over a constraints.Ordered value type, answering range predicates against the NodeIDs that carry each value.
hash
Package hash provides a sharded hash index from arbitrary comparable property values to the set of NodeIDs that carry them, represented as a 64-bit Roaring bitmap.
Package hash provides a sharded hash index from arbitrary comparable property values to the set of NodeIDs that carry them, represented as a 64-bit Roaring bitmap.
label
Package label provides a Roaring-bitmap-backed inverted index from label identifiers to the NodeIDs that carry them.
Package label provides a Roaring-bitmap-backed inverted index from label identifiers to the NodeIDs that carry them.
io
csv
Package csv reads and writes graphs as edge lists in CSV format.
Package csv reads and writes graphs as edge lists in CSV format.
dot
Package dot writes graphs in the Graphviz DOT format (https://graphviz.org/doc/info/lang.html).
Package dot writes graphs in the Graphviz DOT format (https://graphviz.org/doc/info/lang.html).
graphml
Package graphml reads and writes graphs in the GraphML XML dialect (http://graphml.graphdrawing.org/).
Package graphml reads and writes graphs in the GraphML XML dialect (http://graphml.graphdrawing.org/).
jsonl
Package jsonl reads and writes graphs in newline-delimited JSON (NDJSON / JSON Lines) format.
Package jsonl reads and writes graphs in newline-delimited JSON (NDJSON / JSON Lines) format.
lpg
Package lpg implements the Labelled Property Graph model on top of the github.com/FlavioCFOliveira/GoGraph/graph/adjlist mutable adjacency-list backend.
Package lpg implements the Labelled Property Graph model on top of the github.com/FlavioCFOliveira/GoGraph/graph/adjlist mutable adjacency-list backend.
schema
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.
Package query provides a fluent, type-safe programmatic API for expressing MATCH-style pattern queries against a labelled property graph snapshot.
Package query provides a fluent, type-safe programmatic API for expressing MATCH-style pattern queries against a labelled property graph snapshot.

Jump to

Keyboard shortcuts

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