Documentation
¶
Overview ¶
Package graphene is an application-specific graph storage engine designed for Indicer's forensic micro-artefact platform. It provides:
- A pluggable GraphStore interface (store.GraphStore)
- An in-memory reference implementation (memory.Store)
- An on-disk, bulk-ingest-optimised CSR implementation (disk.Store)
- Core traversal algorithms: BFS, DFS, bidirectional-BFS shortest path, and VF2-inspired subgraph pattern matching
- Secondary indexes: type index, temporal index, and property index
Quick start ¶
// In-memory (development / small cases)
g := graphene.NewInMemory()
// On-disk (production)
g, err := graphene.Open("/data/cases/case01")
// Add artefacts
caseID, _ := g.AddNode(&store.Node{Labels: []store.NodeType{store.NodeTypeCase}})
fileID, _ := g.AddNode(&store.Node{Labels: []store.NodeType{store.NodeTypeEvidenceFile}})
artID, _ := g.AddNode(&store.Node{Labels: []store.NodeType{store.NodeTypeMicroArtefact}})
g.AddEdge(&store.Edge{Src: fileID, Dst: artID, Labels: []store.EdgeType{store.EdgeTypeContains}})
g.AddEdge(&store.Edge{Src: fileID, Dst: caseID, Labels: []store.EdgeType{store.EdgeTypeBelongsTo}})
// Index a decoded property value for fast lookup
g.IndexNodeProperty(artID, "sha256", []byte("d4e5f6..."))
hits, _ := g.NodesByProperty("sha256", []byte("d4e5f6..."))
// Modify or remove entities (durable; edge endpoints are immutable,
// DeleteNode cascades to incident edges)
g.UpdateEdge(&store.Edge{ID: eid, Labels: []store.EdgeType{store.EdgeTypeReuse}, Weight: 0.4})
g.DeleteNode(artID)
// k-hop neighbourhood
result, _ := g.BFS(artID, 2, store.DirectionBoth, nil)
// Provenance chain back to evidence file
chain, _ := g.ProvenanceChain(artID, 10, []store.EdgeType{store.EdgeTypeContains})
// Shortest path
path, _ := g.ShortestPath(artID, caseID, nil)
Index ¶
- Variables
- func EdgesFromBFS(r *traversal.BFSResult) []*store.Edge
- func FilterEdgesByLabel(es []*store.Edge, label store.EdgeType) []*store.Edge
- func FilterNodesByLabel(ns []*store.Node, label store.NodeType) []*store.Node
- func NodeIDsFromBFS(r *traversal.BFSResult) []store.NodeID
- func NodeIDsFromPath(r *traversal.PathResult) []store.NodeID
- func NodesFromBFS(r *traversal.BFSResult) []*store.Node
- type Graph
- func (g *Graph) AddEdges(edges []*store.Edge) ([]store.EdgeID, error)
- func (g *Graph) AddNodes(nodes []*store.Node) ([]store.NodeID, error)
- func (g *Graph) BFS(origin store.NodeID, maxDepth int, dir store.Direction, ...) (*traversal.BFSResult, error)
- func (g *Graph) BFSIDs(origin store.NodeID, maxDepth int, dir store.Direction, ...) ([]store.NodeID, error)
- func (g *Graph) Begin() *Tx
- func (g *Graph) Compact() error
- func (g *Graph) DFS(origin store.NodeID, maxDepth int, dir store.Direction, ...) (*traversal.BFSResult, error)
- func (g *Graph) DeclareOrderedEdgeProperty(key string) error
- func (g *Graph) DeclareOrderedProperty(key string) error
- func (g *Graph) Degree(id store.NodeID, edgeTypes []store.EdgeType) (int, error)
- func (g *Graph) EdgeExists(src, dst store.NodeID, edgeTypes []store.EdgeType) (bool, error)
- func (g *Graph) EdgesByAnyType(types []store.EdgeType) ([]store.EdgeID, error)
- func (g *Graph) EdgesByAnyTypeSelector(selectors []string) ([]store.EdgeID, error)
- func (g *Graph) EdgesByProperties(props map[string][]byte) ([]store.EdgeID, error)
- func (g *Graph) EdgesByTypeSelector(selector string) ([]store.EdgeID, error)
- func (g *Graph) EdgesWithProperties(props map[string][]byte) ([]*store.Edge, error)
- func (g *Graph) ExplainEdgeQuery(q store.EdgeQuery) (store.QueryPlan, error)
- func (g *Graph) ExplainNodeQuery(q store.NodeQuery) (store.QueryPlan, error)
- func (g *Graph) FindPatterns(pattern *traversal.Pattern, scope []store.NodeID, maxMatches int) ([]traversal.SubgraphMatch, error)
- func (g *Graph) Forensics() (*disk.Store, bool)
- func (g *Graph) GetEdges(ids []store.EdgeID) (found []*store.Edge, missing []store.EdgeID, err error)
- func (g *Graph) GetNodes(ids []store.NodeID) (found []*store.Node, missing []store.NodeID, err error)
- func (g *Graph) HandleSignals(signals ...os.Signal) func()
- func (g *Graph) HasCycle(origin store.NodeID, maxDepth int, edgeTypes []store.EdgeType) (bool, error)
- func (g *Graph) InDegree(id store.NodeID, edgeTypes []store.EdgeType) (int, error)
- func (g *Graph) IndexEdgeProperties(id store.EdgeID, props map[string][]byte) error
- func (g *Graph) IndexNodeProperties(id store.NodeID, props map[string][]byte) error
- func (g *Graph) InducedSubgraph(nodeIDs []store.NodeID) ([]*store.Node, []*store.Edge, error)
- func (g *Graph) IsConnected(src, dst store.NodeID) (bool, error)
- func (g *Graph) NeighboursByNodeType(id store.NodeID, dir store.Direction, nodeType store.NodeType, ...) ([]*store.Node, error)
- func (g *Graph) NodesByAnyType(types []store.NodeType) ([]store.NodeID, error)
- func (g *Graph) NodesByAnyTypeSelector(selectors []string) ([]store.NodeID, error)
- func (g *Graph) NodesByProperties(props map[string][]byte) ([]store.NodeID, error)
- func (g *Graph) NodesByTypeSelector(selector string) ([]store.NodeID, error)
- func (g *Graph) NodesWithProperties(props map[string][]byte) ([]*store.Node, error)
- func (g *Graph) OrderedProperties() (nodeKeys, edgeKeys []string)
- func (g *Graph) OutDegree(id store.NodeID, edgeTypes []store.EdgeType) (int, error)
- func (g *Graph) ProvenanceChain(origin store.NodeID, maxDepth int, edgeTypes []store.EdgeType) (*traversal.DFSResult, error)
- func (g *Graph) QueryEdgeIDs(query store.EdgeQuery) ([]store.EdgeID, error)
- func (g *Graph) QueryEdges(query store.EdgeQuery) ([]*store.Edge, error)
- func (g *Graph) QueryNodeIDs(query store.NodeQuery) ([]store.NodeID, error)
- func (g *Graph) QueryNodes(query store.NodeQuery) ([]*store.Node, error)
- func (g *Graph) QueryRelationIDs(query store.RelationQuery) ([]store.EdgeID, error)
- func (g *Graph) QueryRelations(query store.RelationQuery) ([]*store.Edge, error)
- func (g *Graph) RebuildIndexes() error
- func (g *Graph) ReindexPolicy() store.ReindexPolicy
- func (g *Graph) SetReindexPolicy(p store.ReindexPolicy)
- func (g *Graph) ShortestPath(src, dst store.NodeID, edgeTypes []store.EdgeType) (*traversal.PathResult, error)
- func (g *Graph) ShouldCompact(p store.CompactionPolicy) (bool, string)
- func (g *Graph) Stats() (*GraphStats, error)
- func (g *Graph) StorageStats() (store.StorageStats, bool)
- func (g *Graph) Sync() error
- func (g *Graph) UpdateEdgeIndexed(e *store.Edge, props map[string][]byte) error
- func (g *Graph) UpdateNodeIndexed(n *store.Node, props map[string][]byte) error
- func (g *Graph) VerifyIndexes() error
- type GraphStats
- type Tx
- func (tx *Tx) AddEdge(e *store.Edge) store.EdgeID
- func (tx *Tx) AddNode(n *store.Node) store.NodeID
- func (tx *Tx) As(ctx store.TxContext) *Tx
- func (tx *Tx) Atomic() bool
- func (tx *Tx) Attributed() bool
- func (tx *Tx) Commit() error
- func (tx *Tx) DeleteEdge(id store.EdgeID)
- func (tx *Tx) DeleteNode(id store.NodeID)
- func (tx *Tx) Len() (nodes, edges int)
- func (tx *Tx) Ops() int
- func (tx *Tx) Rollback() error
- func (tx *Tx) UpdateEdge(e *store.Edge)
- func (tx *Tx) UpdateNode(n *store.Node)
Constants ¶
This section is empty.
Variables ¶
var ErrTxDone = errors.New("graphene: transaction already finished")
ErrTxDone is returned by any method called on a transaction that has already been committed or rolled back.
Functions ¶
func EdgesFromBFS ¶
EdgesFromBFS returns the slice of edges from a BFS result. Nil-safe.
func FilterEdgesByLabel ¶
FilterEdgesByLabel returns only the edges from es that carry the given label.
func FilterNodesByLabel ¶
FilterNodesByLabel returns only the nodes from ns that carry the given label.
func NodeIDsFromBFS ¶
NodeIDsFromBFS returns the node IDs from a BFS result for use as scope in follow-up queries (e.g. FindPatterns).
func NodeIDsFromPath ¶
func NodeIDsFromPath(r *traversal.PathResult) []store.NodeID
NodeIDsFromPath returns the ordered node IDs from a PathResult.
Types ¶
type Graph ¶
type Graph struct {
store.GraphStore
}
Graph wraps a GraphStore and exposes the traversal API in one place. This is the primary entry point for Indicer consumers.
func NewInMemory ¶
func NewInMemory() *Graph
NewInMemory returns a Graph backed by the in-memory store. Suitable for development, testing, and small investigations.
func Open ¶
Open returns a Graph backed by the on-disk CSR store rooted at dir. dir is created if it does not exist. On restart, the WAL is replayed automatically. Call Graph.Compact() after bulk ingest to rebuild the CSR and free WAL space.
func OpenWithOptions ¶
OpenWithOptions returns a Graph backed by a disk store opened with opts.
Open gives you the historical defaults: unsigned commits, no verification on open, no audit log. That is the right default for a graph database and the wrong one for a store holding evidence, and there was previously no way to ask for the other posture without bypassing this package entirely.
key, pub, _ := signing.GenerateKey(1)
ring := signing.NewKeyring()
ring.Add(1, pub)
opts := disk.StrictOptions(key, ring, operatorActorID)
opts.Retention = disk.RetentionPolicy{MaxSegments: 50}
opts.Redaction = true
g, err := graphene.OpenWithOptions(dir, opts)
See docs/API_REFERENCE.md §22 for which options an evidentiary deployment wants and why.
func (*Graph) AddEdges ¶
AddEdges adds multiple edges in order, returning their assigned IDs.
On both bundled backends this is atomic, and every endpoint is validated before anything is written — so a batch containing one dangling edge adds nothing and returns ErrInvalidEdge. Third-party stores without the batch interface fall back to a non-atomic per-edge loop.
Endpoints must already exist. To create nodes and the edges between them together, use Begin.
func (*Graph) AddNodes ¶
AddNodes adds multiple nodes in order, returning their assigned IDs.
On both bundled backends this is atomic: either every node is added or none is, and a failure returns a nil ID slice rather than a partial one. Third-party stores that do not implement the batch interface fall back to a per-node loop, which is not atomic — it returns the IDs assigned so far alongside the error.
For a batch of nodes *and* the edges between them, use Begin: AddNodes followed by AddEdges is two transactions, and a crash between them leaves the nodes without their edges.
func (*Graph) BFS ¶
func (g *Graph) BFS(origin store.NodeID, maxDepth int, dir store.Direction, edgeTypes []store.EdgeType) (*traversal.BFSResult, error)
BFS performs a breadth-first traversal from origin up to maxDepth hops. Pass nil edgeTypes to follow all edge types.
func (*Graph) BFSIDs ¶
func (g *Graph) BFSIDs(origin store.NodeID, maxDepth int, dir store.Direction, edgeTypes []store.EdgeType) ([]store.NodeID, error)
BFSIDs performs the same walk as BFS but returns only the reachable node IDs, in discovery order, starting with origin.
It never materialises a node or edge record, so on the bundled backends the whole traversal allocates only its visited set and result slice, no matter how many edges it crosses. Prefer it over BFS whenever the records are not needed: reachability checks, scoping a pattern match, or feeding IDs into a query.
func (*Graph) Begin ¶
Begin starts a transaction.
If the backend does not implement store.Transactor, the transaction still works but commits by replaying the buffered writes through the batch APIs, which is *not* atomic across the node/edge boundary. Callers who need the guarantee can check Atomic.
func (*Graph) Compact ¶
Compact is available when the Graph is backed by a disk.Store. It merges the delta layer into the CSR and truncates the WAL. Call it after a bulk ingest is complete.
func (*Graph) DFS ¶
func (g *Graph) DFS(origin store.NodeID, maxDepth int, dir store.Direction, edgeTypes []store.EdgeType) (*traversal.BFSResult, error)
DFS performs a depth-first traversal from origin up to maxDepth hops.
func (*Graph) DeclareOrderedEdgeProperty ¶
DeclareOrderedEdgeProperty is DeclareOrderedProperty for edge properties.
func (*Graph) DeclareOrderedProperty ¶
DeclareOrderedProperty builds and maintains an ordered index over a node property key, so that range filters (`>`, `>=`, `<`, `<=`, `Between`) and `Prefix` on that key are answered by binary search instead of by scanning every entry registered under it. Entries already present are absorbed, so this can be called at any point.
**Declaring a key changes how its range predicates compare.** Undeclared keys use the scan-path rule: try numeric comparison, fall back to byte order. That rule is fine value-by-value but is not a valid sort order — "9" < "10" < "1x" < "9" under it — so no ordered structure can be built on it. A declared key is compared byte-wise throughout. Encode values so byte order matches your intent:
// zero-padded fixed width, or index/encoding for real numbers
g.IndexNodeProperty(id, "score", encoding.Int64(score))
g.DeclareOrderedProperty("score")
g.QueryNodes(store.NodeQuery{Filters: []store.PropertyFilter{{
Key: "score", Op: store.PropertyOpBetweenInclusive,
Value: encoding.Int64(100), ValueUpper: encoding.Int64(200),
}}})
Equality lookups are unaffected. Backends without the extension ignore this and keep scanning.
func (*Graph) Degree ¶
Degree returns the total (in + out) edge count for node id. Pass nil edgeTypes to count all edges. Note that for undirected use-cases, edges that appear in both directions are counted twice.
func (*Graph) EdgeExists ¶
EdgeExists reports whether at least one direct edge exists from src to dst. Pass nil edgeTypes to consider edges of any type.
func (*Graph) EdgesByAnyType ¶
EdgesByAnyType returns all EdgeIDs that carry at least one of the given labels (OR semantics). Duplicate IDs are deduplicated.
func (*Graph) EdgesByAnyTypeSelector ¶
EdgesByAnyTypeSelector returns all EdgeIDs matching at least one selector.
func (*Graph) EdgesByProperties ¶
EdgesByProperties returns the intersection of all EdgeIDs that match every key-value pair in props (AND semantics). Returns an empty slice when props is empty.
func (*Graph) EdgesByTypeSelector ¶
EdgesByTypeSelector parses selector and returns matching edge IDs. Supports built-in names and custom selectors such as "custom:7".
func (*Graph) EdgesWithProperties ¶
EdgesWithProperties returns hydrated edges matching all key-value pairs.
func (*Graph) ExplainEdgeQuery ¶
ExplainEdgeQuery reports how the planner resolves q. See ExplainNodeQuery.
func (*Graph) ExplainNodeQuery ¶
ExplainNodeQuery reports how the planner resolves q: which index drove it, how many candidates that produced, and how each remaining filter was applied.
This is how planner behaviour gets verified. A query can return the right answer while doing far more work than it needed to, and the difference is invisible from the results alone — a test that asserts only on results cannot tell an index lookup from a full scan that happened to agree with it.
The plan is diagnostic output. Which index the planner picks may change as the cost model improves; the results a query returns may not.
func (*Graph) FindPatterns ¶
func (g *Graph) FindPatterns(pattern *traversal.Pattern, scope []store.NodeID, maxMatches int) ([]traversal.SubgraphMatch, error)
FindPatterns searches for all subgraphs matching pattern within scope. scope limits the candidate nodes; pass nil to search all nodes of the matching type (expensive on large graphs — prefer scoping to a case BFS result). maxMatches caps output; pass 0 for no cap.
func (*Graph) Forensics ¶
Forensics returns the disk store behind this Graph, and whether there is one.
The integrity machinery — signed commits, snapshot roots, inclusion proofs, attributed redaction, chain of custody, checkpoints and anchoring — lives on disk.Store rather than here, and this is the supported way to reach it.
Why an accessor and not forty methods ¶
Forwarding each call would double an API surface of about fifty symbols, and every one of them would be a place for the façade's version to drift from the engine's. It would also flatten a distinction worth keeping: none of that machinery works on the in-memory backend, so a Graph that cannot do it should say so once rather than fail fifty times. Returning false is that answer.
if s, ok := g.Forensics(); ok {
proof, err := s.ProveNode(id)
}
The store is the same one the Graph is using, not a copy — calls through it are visible to the Graph immediately, and closing the Graph closes it.
See SECURITY.md for what each mechanism proves and does not, and docs/FORENSICS.md for how to use them.
func (*Graph) GetEdges ¶
func (g *Graph) GetEdges(ids []store.EdgeID) (found []*store.Edge, missing []store.EdgeID, err error)
GetEdges fetches multiple edges by ID in the order given. If any ID is not found the error is returned immediately. GetEdges fetches multiple edges by ID, preserving request order. A missing ID is reported in missing rather than returned as an error — see GetNodes.
func (*Graph) GetNodes ¶
func (g *Graph) GetNodes(ids []store.NodeID) (found []*store.Node, missing []store.NodeID, err error)
GetNodes fetches multiple nodes by ID in the order given. If any ID is not found the error is returned immediately. GetNodes fetches multiple nodes by ID, preserving request order.
**A missing ID is not an error.** It is reported in missing, and err is reserved for genuine failures. That is deliberate: under the read model (API_REFERENCE §16) an ID can be deleted between the call that produced it and the call that resolves it, so treating that as exceptional forced callers back into the per-item loop this method exists to replace.
found is compacted — missing IDs leave no nil holes — and each record carries its own ID, so a caller needing to correlate results back to requested IDs can read node.ID rather than relying on position.
func (*Graph) HandleSignals ¶
HandleSignals registers a graceful shutdown hook that closes the graph when any of the provided signals is received. If no signals are provided, a platform-appropriate default set is used.
The returned stop function unregisters the signal handler.
func (*Graph) HasCycle ¶
func (g *Graph) HasCycle(origin store.NodeID, maxDepth int, edgeTypes []store.EdgeType) (bool, error)
HasCycle reports whether any cycle is reachable from origin within maxDepth hops following outbound edges. It uses DFS and detects back-edges in the recursion stack. Pass nil edgeTypes to follow all edge types.
func (*Graph) InDegree ¶
InDegree returns the number of inbound edges for node id. Pass nil edgeTypes to count all inbound edges.
func (*Graph) IndexEdgeProperties ¶
IndexEdgeProperties indexes all key-value pairs in props for the given edge. Indexing stops and the error is returned on first failure.
func (*Graph) IndexNodeProperties ¶
IndexNodeProperties indexes all key-value pairs in props for the given node. Indexing stops and the error is returned on first failure.
func (*Graph) InducedSubgraph ¶
InducedSubgraph returns the nodes and all edges between them for the given set of node IDs. The result edges are those whose Src AND Dst are both in the provided set.
func (*Graph) IsConnected ¶
IsConnected reports whether src and dst are reachable from one another via any sequence of edges. It uses the shortest-path algorithm internally and considers all edge types.
func (*Graph) NeighboursByNodeType ¶
func (g *Graph) NeighboursByNodeType(id store.NodeID, dir store.Direction, nodeType store.NodeType, edgeTypes []store.EdgeType) ([]*store.Node, error)
NeighboursByNodeType returns all directly connected nodes of a specific NodeType, optionally filtered by edge types. Pass nil edgeTypes to follow all edge types.
func (*Graph) NodesByAnyType ¶
NodesByAnyType returns all NodeIDs that carry at least one of the given labels (OR semantics). Duplicate IDs are deduplicated.
func (*Graph) NodesByAnyTypeSelector ¶
NodesByAnyTypeSelector returns all NodeIDs matching at least one selector.
func (*Graph) NodesByProperties ¶
NodesByProperties returns the intersection of all NodeIDs that match every key-value pair in props (AND semantics). Returns an empty slice when props is empty.
func (*Graph) NodesByTypeSelector ¶
NodesByTypeSelector parses selector and returns matching node IDs. Supports built-in names and custom selectors such as "custom:7".
func (*Graph) NodesWithProperties ¶
NodesWithProperties returns hydrated nodes matching all key-value pairs.
func (*Graph) OrderedProperties ¶
OrderedProperties returns the node and edge property keys currently backed by an ordered index, each sorted.
func (*Graph) OutDegree ¶
OutDegree returns the number of outbound edges for node id. Pass nil edgeTypes to count all outbound edges.
func (*Graph) ProvenanceChain ¶
func (g *Graph) ProvenanceChain(origin store.NodeID, maxDepth int, edgeTypes []store.EdgeType) (*traversal.DFSResult, error)
ProvenanceChain walks inbound edges from origin back to the root evidence source (e.g. the EvidenceFile node), following the given edge types. Pass nil edgeTypes to follow all inbound edges.
func (*Graph) QueryEdgeIDs ¶
QueryEdgeIDs returns edge IDs that satisfy query constraints.
func (*Graph) QueryEdges ¶
QueryEdges returns hydrated edges that satisfy query constraints.
func (*Graph) QueryNodeIDs ¶
QueryNodeIDs returns node IDs that satisfy query constraints.
func (*Graph) QueryNodes ¶
QueryNodes returns hydrated nodes that satisfy query constraints.
func (*Graph) QueryRelationIDs ¶
QueryRelations returns relation edges around anchor nodes using direction-aware matching.
func (*Graph) QueryRelations ¶
QueryRelations returns relation edges around anchor nodes using direction-aware matching.
func (*Graph) RebuildIndexes ¶
RebuildIndexes discards and recomputes every index derivable from the stored records — label postings and adjacency — and drops property-index entries whose entity no longer exists. Backends that do not support it return nil.
It repairs structure, not content: property-index *values* are supplied by the caller and cannot be recovered from the records, so entries for live entities are left as they are. The disk backend runs this automatically on Open when its own verification fails, so calling it by hand is normally unnecessary.
func (*Graph) ReindexPolicy ¶
func (g *Graph) ReindexPolicy() store.ReindexPolicy
ReindexPolicy returns the configured policy, or store.ReindexKeep if the backend does not support configuring one.
func (*Graph) SetReindexPolicy ¶
func (g *Graph) SetReindexPolicy(p store.ReindexPolicy)
SetReindexPolicy controls what UpdateNode / UpdateEdge do to the property index. See store.ReindexPolicy for the trade-off between the two modes; the default (store.ReindexKeep) preserves historical behaviour.
Prefer UpdateNodeIndexed / UpdateEdgeIndexed over either policy where you can: they update and re-register in one step, so the index is never stale and never silently loses entries.
func (*Graph) ShortestPath ¶
func (g *Graph) ShortestPath(src, dst store.NodeID, edgeTypes []store.EdgeType) (*traversal.PathResult, error)
ShortestPath finds the shortest undirected path between src and dst using bidirectional BFS.
func (*Graph) ShouldCompact ¶
func (g *Graph) ShouldCompact(p store.CompactionPolicy) (bool, string)
ShouldCompact reports whether the store has breached policy, and which rule fired.
Advisory only. Nothing in the engine acts on it, and calling it changes nothing — compaction rebuilds the entire image, so when to pay that is the caller's decision, not the engine's. A backend that cannot report its storage state returns false.
The intended shape is a periodic check in the caller's own loop:
if due, why := g.ShouldCompact(store.DefaultCompactionPolicy()); due {
log.Printf("compacting: %s", why)
g.Compact()
}
This exists because nothing else bounds delta growth. Everything written since the last compaction stays in memory and is replayed at every open, so a store that is never compacted degrades in memory, open time, and read speed at once — with no error and no warning until someone measures it.
func (*Graph) Stats ¶
func (g *Graph) Stats() (*GraphStats, error)
Stats returns high-level counts for the graph, and storage detail where the backend can supply it.
func (*Graph) StorageStats ¶
func (g *Graph) StorageStats() (store.StorageStats, bool)
StorageStats reports the backend's storage state, and whether it could.
Cheaper than Stats when only the operational figures are wanted: it does not count nodes and edges, which on the disk backend means it does not merge the CSR and delta views.
func (*Graph) Sync ¶
Sync forces everything written so far to durable storage, returning once it survives power loss.
This matters because individual writes are *not* synced as they happen: an fsync per AddNode would turn a ~6 µs operation into a ~1 ms one. Batch commits sync by default; single writes rely on this, on Compact, or on Close.
On a backend without durability — the in-memory store — this is a no-op and returns nil.
func (*Graph) UpdateEdgeIndexed ¶
UpdateEdgeIndexed updates an edge and replaces its property-index entries in one step. See UpdateNodeIndexed.
func (*Graph) UpdateNodeIndexed ¶
UpdateNodeIndexed updates a node and replaces its property-index entries in one step: every entry previously registered for the node is dropped and props is registered in its place.
This is the correct way to edit a node whose properties are indexed. Plain UpdateNode cannot maintain the index — the engine does not know how to decode your Properties blob — so it either leaves stale entries behind or (under store.ReindexPurge) drops entries that were still valid. Passing the full desired index state here avoids both.
Pass a nil or empty props map to update the node and leave it un-indexed.
func (*Graph) VerifyIndexes ¶
VerifyIndexes cross-checks every index against the records it describes and returns the first inconsistency found, or nil if they all agree. Both bundled backends support it; a backend that does not returns nil.
It validates structure — postings ordering, reverse-map agreement, adjacency endpoints, and that no index entry outlives its entity. It cannot validate that an indexed *value* still matches the entity's properties: those values are caller-encoded and opaque to the engine. See SetReindexPolicy.
Intended for tests, for CI, and after recovering a store whose indexes may have been rebuilt from a partial log.
type GraphStats ¶
type GraphStats struct {
NodeCount uint64
EdgeCount uint64
// Storage describes what the backend is holding — delta size, log size, and
// when it last compacted. Valid only when HasStorage is true; backends
// without a delta layer or a log have nothing to report.
Storage store.StorageStats
HasStorage bool
}
GraphStats holds high-level statistics about the graph.
type Tx ¶
type Tx struct {
// contains filtered or unexported fields
}
Tx is a set of writes that commit together or not at all.
A Tx is **not** safe for concurrent use by multiple goroutines. It is a caller-side buffer; the store lock is taken once, at Commit.
Writes are buffered in memory until Commit, so a transaction costs memory proportional to its size. That is the same trade the slice APIs make, but it means a single enormous transaction is not free — for bulk loads that do not need whole-file atomicity, commit in chunks.
func (*Tx) AddEdge ¶
AddEdge buffers an edge and returns the ID it will have once committed.
Src and Dst may name nodes that already exist or nodes added earlier in this same transaction. Endpoints are validated at Commit, under the store lock — validating here would be racy, because a node can be deleted between buffering and committing.
func (*Tx) AddNode ¶
AddNode buffers a node and returns the ID it will have once committed.
The returned ID is usable immediately as an edge endpoint within this transaction. It is reserved, not created: if the transaction is rolled back or fails, the ID is never used by anything.
The node is copied, so the caller may reuse its slices as soon as this returns — the same contract as AddNode.
func (*Tx) As ¶
As records who is making this transaction. It returns tx so it can be chained onto Begin.
The actor is written into the commit record alongside the commit's sequence number and wall-clock time, which makes the change attributable when the log is read back. It is recorded, not verified — see store.TxContext. Attribution is per-transaction because that is the unit the log can record it against: writes made through the plain APIs outside a transaction produce no commit record and are therefore unattributed.
Attributed reports whether the actor will actually be durable, which is false on backends that keep no log.
func (*Tx) Atomic ¶
Atomic reports whether Commit is all-or-nothing on this backend. It is false only for third-party stores that do not implement store.Transactor; both bundled backends return true.
func (*Tx) Attributed ¶
Attributed reports whether this transaction's actor will be recorded durably on commit. It is false when no actor has been set, and false on a backend that does not implement store.ActorTransactor — the in-memory store, for instance, accepts an actor and has nowhere to keep it.
func (*Tx) Commit ¶
Commit applies every buffered operation as one unit, in the order issued.
On error nothing is applied and the store is unchanged. The transaction is finished either way: a failed Commit does not need, and does not accept, a Rollback.
func (*Tx) DeleteEdge ¶
DeleteEdge buffers an edge deletion.
func (*Tx) DeleteNode ¶
DeleteNode buffers a node deletion.
Deletion cascades: every edge incident to the node goes too, including edges created earlier in this same transaction. The cascade is computed at commit, under the store lock — computing it at buffer time would resolve against a graph that can still change before the transaction commits.
func (*Tx) Len ¶
Len reports how many nodes and edges this transaction *creates*. It does not count updates or deletes; use Ops for the total.
func (*Tx) Rollback ¶
Rollback discards the transaction. It costs nothing: nothing has been written.
Rolling back a transaction that has already finished returns ErrTxDone, so a deferred Rollback after a successful Commit is harmless but not silent — ignore its error in that idiom:
tx := g.Begin()
defer func() { _ = tx.Rollback() }()
func (*Tx) UpdateEdge ¶
UpdateEdge buffers a replacement for an existing edge. Same rules as UpdateNode; both endpoints must also resolve at commit.
func (*Tx) UpdateNode ¶
UpdateNode buffers a replacement for an existing node.
The node must exist when the transaction commits — either in the store, or created earlier in this same transaction. Labels must be non-empty. Update replaces the record wholesale, exactly as Graph.UpdateNode does.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
graphene
command
Command graphene inspects a Graphene store from a shell.
|
Command graphene inspects a Graphene store from a shell. |
|
Package examples demonstrates common Graphene usage patterns for the Indicer forensic platform.
|
Package examples demonstrates common Graphene usage patterns for the Indicer forensic platform. |
|
encoding
Package encoding provides order-preserving encodings for property values.
|
Package encoding provides order-preserving encodings for property values. |
|
Package merkle implements the RFC 6962 Merkle tree used for snapshot roots and inclusion proofs.
|
Package merkle implements the RFC 6962 Merkle tree used for snapshot roots and inclusion proofs. |
|
Package signing provides an Ed25519 implementation of store.Signer and store.Verifier, plus a keyring for verifying against several keys.
|
Package signing provides an Ed25519 implementation of store.Signer and store.Verifier, plus a keyring for verifying against several keys. |