pub struct DatabaseEngine { /* private fields */ }Expand description
The core database engine. Manages the lifecycle of an OverGraph database.
Provides both low-level WAL access (write_op) and high-level graph APIs (upsert_node, upsert_edge, get_node, get_edge, batch operations).
Implementations§
Source§impl DatabaseEngine
impl DatabaseEngine
Sourcepub fn open(path: &Path, options: &DbOptions) -> Result<Self, EngineError>
pub fn open(path: &Path, options: &DbOptions) -> Result<Self, EngineError>
Open or create a database at the given directory path.
Sourcepub fn close(self) -> Result<(), EngineError>
pub fn close(self) -> Result<(), EngineError>
Close the database cleanly. Syncs WAL and writes manifest. Waits for any in-progress background compaction to finish.
Sourcepub fn close_fast(self) -> Result<(), EngineError>
pub fn close_fast(self) -> Result<(), EngineError>
Close the database, cancelling any in-progress background compaction instead of waiting for it to finish. Use this for fast shutdown when you don’t need the bg compaction result.
Sourcepub fn sync(&self) -> Result<(), EngineError>
pub fn sync(&self) -> Result<(), EngineError>
Force an immediate WAL fsync. Blocks until the sync completes. In Immediate mode, this is a no-op (every write already syncs).
Sourcepub fn flush(&mut self) -> Result<Option<SegmentInfo>, EngineError>
pub fn flush(&mut self) -> Result<Option<SegmentInfo>, EngineError>
Flush the current memtable to an immutable on-disk segment.
- Freeze current memtable, replace with empty one
- Write segment to tmp directory
- Atomic rename tmp → final
- Update manifest with new segment
- Truncate WAL
- Re-open WAL writer (Immediate) or truncate-and-reset (GroupCommit)
- Open segment reader for the new segment
Returns the new SegmentInfo, or None if memtable was empty.
Sourcepub fn ingest_mode(&mut self)
pub fn ingest_mode(&mut self)
Enter ingest mode: disables auto-compaction so bulk writes produce
segments without triggering background merges. Call end_ingest when
loading is complete to compact and restore normal operation.
Sourcepub fn end_ingest(&mut self) -> Result<Option<CompactionStats>, EngineError>
pub fn end_ingest(&mut self) -> Result<Option<CompactionStats>, EngineError>
Exit ingest mode: restores the previous auto-compaction threshold and immediately compacts all accumulated segments.
Sourcepub fn compact(&mut self) -> Result<Option<CompactionStats>, EngineError>
pub fn compact(&mut self) -> Result<Option<CompactionStats>, EngineError>
Compact all segments into a single segment.
Convenience wrapper around compact_with_progress that never cancels.
Sourcepub fn compact_with_progress<F>(
&mut self,
callback: F,
) -> Result<Option<CompactionStats>, EngineError>
pub fn compact_with_progress<F>( &mut self, callback: F, ) -> Result<Option<CompactionStats>, EngineError>
Compact all segments into a single segment with progress reporting.
The callback receives a CompactionProgress at key points during
compaction. Return true to continue, false to cancel. Cancellation
is safe. No state is modified until the output segment is fully written
and verified.
Uses a fast raw-merge path when segments are non-overlapping, contain no tombstones, and no prune policies are active. All other cases fall back to the unified V3 compaction path: metadata-only planning (winner selection from sidecars), raw binary copy of winning records, and metadata-driven index building. No MessagePack decode occurs on either path.
Returns CompactionStats on success, None if fewer than 2 segments,
or Err(CompactionCancelled) if the callback returned false.
Sourcepub fn write_op(&mut self, op: &WalOp) -> Result<(), EngineError>
pub fn write_op(&mut self, op: &WalOp) -> Result<(), EngineError>
Write a WalOp to the WAL and apply it to in-memory state. Note: Does not trigger auto-flush or backpressure. Use the high-level graph APIs for automatic flushing and memtable size management.
Sourcepub fn write_op_batch(&mut self, ops: &[WalOp]) -> Result<(), EngineError>
pub fn write_op_batch(&mut self, ops: &[WalOp]) -> Result<(), EngineError>
Write multiple WalOps with a single fsync at the end. Note: Does not trigger auto-flush or backpressure. Use the high-level graph APIs for automatic flushing and memtable size management.
Sourcepub fn manifest(&self) -> &ManifestState
pub fn manifest(&self) -> &ManifestState
Return a reference to the current manifest state.
Sourcepub fn node_count(&self) -> usize
pub fn node_count(&self) -> usize
Return the count of live nodes in memtable only.
Sourcepub fn edge_count(&self) -> usize
pub fn edge_count(&self) -> usize
Return the count of live edges in memtable only.
Sourcepub fn next_node_id(&self) -> u64
pub fn next_node_id(&self) -> u64
Return the next node ID that will be allocated.
Sourcepub fn next_edge_id(&self) -> u64
pub fn next_edge_id(&self) -> u64
Return the next edge ID that will be allocated.
Sourcepub fn segment_count(&self) -> usize
pub fn segment_count(&self) -> usize
Return the number of open segments.
Sourcepub fn segment_tombstone_node_count(&self) -> usize
pub fn segment_tombstone_node_count(&self) -> usize
Return the total number of tombstoned node IDs across all segments.
Sourcepub fn segment_tombstone_edge_count(&self) -> usize
pub fn segment_tombstone_edge_count(&self) -> usize
Return the total number of tombstoned edge IDs across all segments.
§impl DatabaseEngine
impl DatabaseEngine
pub fn degree(
&self,
node_id: u64,
direction: Direction,
type_filter: Option<&[u32]>,
at_epoch: Option<i64>,
) -> Result<u64, EngineError>
pub fn degree( &self, node_id: u64, direction: Direction, type_filter: Option<&[u32]>, at_epoch: Option<i64>, ) -> Result<u64, EngineError>
Count the number of edges incident to a node (its degree).
Counts adjacency postings across memtable + segments without
materializing the neighbor list. Deduplicates by edge_id and
skips tombstoned edges/nodes. Applies temporal filtering (edges
must be valid at at_epoch, defaulting to now). Respects prune policies.
direction:Outgoing,Incoming, orBoth.type_filter: ifSome, only count edges whose type_id is in the list.at_epoch: reference time in epoch millis.None→ now.
Returns 0 for nonexistent nodes (not an error).
pub fn sum_edge_weights(
&self,
node_id: u64,
direction: Direction,
type_filter: Option<&[u32]>,
at_epoch: Option<i64>,
) -> Result<f64, EngineError>
pub fn sum_edge_weights( &self, node_id: u64, direction: Direction, type_filter: Option<&[u32]>, at_epoch: Option<i64>, ) -> Result<f64, EngineError>
Sum of edge weights incident to a node.
Walks adjacency postings (where weight is embedded as f32) and
accumulates into f64 for precision. No edge record hydration.
Applies temporal filtering (edges must be valid at at_epoch,
defaulting to now). Respects prune policies.
at_epoch: reference time in epoch millis.None→ now.
Returns 0.0 for nonexistent or zero-degree nodes.
pub fn avg_edge_weight(
&self,
node_id: u64,
direction: Direction,
type_filter: Option<&[u32]>,
at_epoch: Option<i64>,
) -> Result<Option<f64>, EngineError>
pub fn avg_edge_weight( &self, node_id: u64, direction: Direction, type_filter: Option<&[u32]>, at_epoch: Option<i64>, ) -> Result<Option<f64>, EngineError>
Average edge weight incident to a node.
Returns None if the node has zero edges (avoids division by zero).
Uses f64 accumulator for precision. Applies temporal filtering
(edges must be valid at at_epoch, defaulting to now). Respects
prune policies.
at_epoch: reference time in epoch millis.None→ now.
pub fn degrees(
&self,
node_ids: &[u64],
direction: Direction,
type_filter: Option<&[u32]>,
at_epoch: Option<i64>,
) -> Result<NodeIdMap<u64>, EngineError>
pub fn degrees( &self, node_ids: &[u64], direction: Direction, type_filter: Option<&[u32]>, at_epoch: Option<i64>, ) -> Result<NodeIdMap<u64>, EngineError>
Batch degree counts for multiple nodes. Collects tombstones once, then uses a single adaptive cursor walk per segment for all node IDs.
Returns a NodeIdMap<u64> mapping each queried node_id to its degree.
NodeIdMap is a HashMap with identity hashing optimized for
engine-generated numeric IDs. Nodes with degree 0 are omitted
(consistent with neighbors_batch). Applies temporal filtering
(edges must be valid at at_epoch, defaulting to now). Respects
prune policies.
at_epoch: reference time in epoch millis.None→ now.
node_ids need not be sorted (sorted internally).
pub fn shortest_path(
&self,
from: u64,
to: u64,
direction: Direction,
edge_type_filter: Option<&[u32]>,
weight_field: Option<&str>,
at_epoch: Option<i64>,
max_depth: Option<u32>,
max_cost: Option<f64>,
) -> Result<Option<ShortestPath>, EngineError>
pub fn shortest_path( &self, from: u64, to: u64, direction: Direction, edge_type_filter: Option<&[u32]>, weight_field: Option<&str>, at_epoch: Option<i64>, max_depth: Option<u32>, max_cost: Option<f64>, ) -> Result<Option<ShortestPath>, EngineError>
Find the shortest path between two nodes.
Algorithm auto-selected from weight_field:
None→ bidirectional BFS (unweighted, hop count)Some("weight")→ bidirectional Dijkstra readingNeighborEntry.weightSome("<field>")→ bidirectional Dijkstra readingedge.props[field]as f64
Returns None if no path exists within the given constraints.
§Arguments
direction: edge direction for traversaledge_type_filter: restrict to these edge types (None = all)weight_field: weight source for Dijkstra; None = BFSat_epoch: temporal snapshot (None = now)max_depth: maximum hop count (None = unlimited)max_cost: maximum total cost for Dijkstra (None = unlimited; ignored for BFS)
§Examples
let a = db.upsert_node(1, "a", BTreeMap::new(), 1.0).unwrap();
let b = db.upsert_node(1, "b", BTreeMap::new(), 1.0).unwrap();
let c = db.upsert_node(1, "c", BTreeMap::new(), 1.0).unwrap();
db.upsert_edge(a, b, 1, BTreeMap::new(), 1.0, None, None).unwrap();
db.upsert_edge(b, c, 1, BTreeMap::new(), 1.0, None, None).unwrap();
// BFS shortest path (unweighted)
let path = db.shortest_path(a, c, Direction::Outgoing, None, None, None, None, None)
.unwrap().unwrap();
assert_eq!(path.nodes, vec![a, b, c]);
assert_eq!(path.total_cost, 2.0); // 2 hops
// Dijkstra shortest path (weighted by edge weight)
let path = db.shortest_path(a, c, Direction::Outgoing, None, Some("weight"), None, None, None)
.unwrap().unwrap();
assert_eq!(path.nodes, vec![a, b, c]);pub fn is_connected(
&self,
from: u64,
to: u64,
direction: Direction,
edge_type_filter: Option<&[u32]>,
at_epoch: Option<i64>,
max_depth: Option<u32>,
) -> Result<bool, EngineError>
pub fn is_connected( &self, from: u64, to: u64, direction: Direction, edge_type_filter: Option<&[u32]>, at_epoch: Option<i64>, max_depth: Option<u32>, ) -> Result<bool, EngineError>
Check if two nodes are connected (reachable via edges).
Uses bidirectional BFS with no parent tracking for minimal overhead.
Returns true if a path exists within max_depth hops.
§Examples
let a = db.upsert_node(1, "a", BTreeMap::new(), 1.0).unwrap();
let b = db.upsert_node(1, "b", BTreeMap::new(), 1.0).unwrap();
let c = db.upsert_node(1, "c", BTreeMap::new(), 1.0).unwrap();
db.upsert_edge(a, b, 1, BTreeMap::new(), 1.0, None, None).unwrap();
assert!(db.is_connected(a, b, Direction::Outgoing, None, None, None).unwrap());
assert!(!db.is_connected(a, c, Direction::Outgoing, None, None, None).unwrap());pub fn traverse(
&self,
start: u64,
min_depth: u32,
max_depth: u32,
direction: Direction,
edge_type_filter: Option<&[u32]>,
node_type_filter: Option<&[u32]>,
at_epoch: Option<i64>,
decay_lambda: Option<f64>,
limit: Option<usize>,
cursor: Option<&TraversalCursor>,
) -> Result<TraversalPageResult, EngineError>
pub fn traverse( &self, start: u64, min_depth: u32, max_depth: u32, direction: Direction, edge_type_filter: Option<&[u32]>, node_type_filter: Option<&[u32]>, at_epoch: Option<i64>, decay_lambda: Option<f64>, limit: Option<usize>, cursor: Option<&TraversalCursor>, ) -> Result<TraversalPageResult, EngineError>
Traverse outward from start with deterministic BFS ordering.
Results are emitted in (depth ASC, node_id ASC) order. node_type_filter
applies only to emitted hits; traversal still expands through visible nodes
that do not match the filter.
pub fn all_shortest_paths(
&self,
from: u64,
to: u64,
direction: Direction,
edge_type_filter: Option<&[u32]>,
weight_field: Option<&str>,
at_epoch: Option<i64>,
max_depth: Option<u32>,
max_cost: Option<f64>,
max_paths: Option<usize>,
) -> Result<Vec<ShortestPath>, EngineError>
pub fn all_shortest_paths( &self, from: u64, to: u64, direction: Direction, edge_type_filter: Option<&[u32]>, weight_field: Option<&str>, at_epoch: Option<i64>, max_depth: Option<u32>, max_cost: Option<f64>, max_paths: Option<usize>, ) -> Result<Vec<ShortestPath>, EngineError>
Enumerate all shortest paths between two nodes.
Returns all paths with the same minimum cost. Uses bidirectional
BFS (if weight_field is None) or bidirectional Dijkstra (if
weighted), then enumerates only paths consistent with the optimal
forward/backward search frontiers.
§Arguments
max_paths: maximum number of paths to return (default 100, 0 = no limit)
§Examples
let a = db.upsert_node(1, "a", BTreeMap::new(), 1.0).unwrap();
let b = db.upsert_node(1, "b", BTreeMap::new(), 1.0).unwrap();
let c = db.upsert_node(1, "c", BTreeMap::new(), 1.0).unwrap();
let d = db.upsert_node(1, "d", BTreeMap::new(), 1.0).unwrap();
// Diamond: a->b->d and a->c->d (two equal-cost paths)
db.upsert_edge(a, b, 1, BTreeMap::new(), 1.0, None, None).unwrap();
db.upsert_edge(a, c, 1, BTreeMap::new(), 1.0, None, None).unwrap();
db.upsert_edge(b, d, 1, BTreeMap::new(), 1.0, None, None).unwrap();
db.upsert_edge(c, d, 1, BTreeMap::new(), 1.0, None, None).unwrap();
let paths = db.all_shortest_paths(
a, d, Direction::Outgoing, None, None, None, None, None, None,
).unwrap();
assert_eq!(paths.len(), 2); // a->b->d and a->c->d
assert!(paths.iter().all(|p| p.total_cost == 2.0));pub fn neighbors(
&self,
node_id: u64,
direction: Direction,
type_filter: Option<&[u32]>,
limit: usize,
at_epoch: Option<i64>,
decay_lambda: Option<f32>,
) -> Result<Vec<NeighborEntry>, EngineError>
pub fn neighbors( &self, node_id: u64, direction: Direction, type_filter: Option<&[u32]>, limit: usize, at_epoch: Option<i64>, decay_lambda: Option<f32>, ) -> Result<Vec<NeighborEntry>, EngineError>
Query neighbors, merging results from memtable + segments. Memtable results come first, then segments newest-to-oldest. Deduplicates by edge_id (first-seen wins).
Nodes matching any registered prune policy are excluded from results. When policies are active, fetches all results first (ignoring limit), filters, then applies limit. This prevents short results when excluded nodes consume limit slots.
at_epoch: if Some(t), filter to edges valid at time t. If None, use current time.
decay_lambda: if Some(λ), compute score = weight * e^(-λ * age_hours) where
age_hours = (reference_time - valid_from) / 3_600_000 and sort descending by score.
pub fn neighbors_batch(
&self,
node_ids: &[u64],
direction: Direction,
type_filter: Option<&[u32]>,
at_epoch: Option<i64>,
decay_lambda: Option<f32>,
) -> Result<NodeIdMap<Vec<NeighborEntry>>, EngineError>
pub fn neighbors_batch( &self, node_ids: &[u64], direction: Direction, type_filter: Option<&[u32]>, at_epoch: Option<i64>, decay_lambda: Option<f32>, ) -> Result<NodeIdMap<Vec<NeighborEntry>>, EngineError>
Batch neighbor query for multiple node IDs. Single O(N+M) cursor walk per segment instead of O(M log N) individual binary searches.
node_ids need not be sorted (sorted internally). Returns a
NodeIdMap<Vec<NeighborEntry>> mapping each queried node_id to its
neighbor entries. NodeIdMap is a HashMap with identity hashing
optimized for engine-generated numeric IDs.
Applies prune policy filtering when policies are registered.
pub fn neighbors_paged(
&self,
node_id: u64,
direction: Direction,
type_filter: Option<&[u32]>,
page: &PageRequest,
at_epoch: Option<i64>,
decay_lambda: Option<f32>,
) -> Result<PageResult<NeighborEntry>, EngineError>
pub fn neighbors_paged( &self, node_id: u64, direction: Direction, type_filter: Option<&[u32]>, page: &PageRequest, at_epoch: Option<i64>, decay_lambda: Option<f32>, ) -> Result<PageResult<NeighborEntry>, EngineError>
Paginated version of neighbors. Returns a page of neighbor entries
sorted by edge_id with cursor-based pagination.
Three paths based on query complexity:
- Fast path (no decay, no policies): temporal validity in skip_fn + cursor binary-seek + early termination. O(K log N) seek + O(limit) merge.
- Policy path (no decay, has policies): temporal in skip_fn + cursor binary-seek (no limit), then batch policy filter + take limit. Skips items before cursor but still processes all post-cursor items for policy correctness.
- Decay path: collects all temporally-valid items (no cursor; results are re-sorted by score), applies decay scoring, sorts descending, then policy filter + truncate to limit.
pub fn top_k_neighbors(
&self,
node_id: u64,
direction: Direction,
type_filter: Option<&[u32]>,
k: usize,
scoring: ScoringMode,
at_epoch: Option<i64>,
) -> Result<Vec<NeighborEntry>, EngineError>
pub fn top_k_neighbors( &self, node_id: u64, direction: Direction, type_filter: Option<&[u32]>, k: usize, scoring: ScoringMode, at_epoch: Option<i64>, ) -> Result<Vec<NeighborEntry>, EngineError>
Return the top-k neighbors by the given scoring mode.
Uses a bounded min-heap of size k during merge for efficiency, avoids sorting the full neighbor set.
pub fn extract_subgraph(
&self,
start_node_id: u64,
max_depth: u32,
direction: Direction,
edge_type_filter: Option<&[u32]>,
at_epoch: Option<i64>,
) -> Result<Subgraph, EngineError>
pub fn extract_subgraph( &self, start_node_id: u64, max_depth: u32, direction: Direction, edge_type_filter: Option<&[u32]>, at_epoch: Option<i64>, ) -> Result<Subgraph, EngineError>
Extract a subgraph of all nodes and edges reachable within max_depth
hops of start_node_id.
Uses BFS with cycle detection. Edges to already-visited nodes (cross-edges and back-edges) are included in the result. The traversal respects direction and edge type filters.
max_depth: maximum number of hops. 0 returns just the start node.direction: which edge direction to follow during traversal.edge_type_filter: only traverse edges of these types.None= all.at_epoch: temporal filter. Only include edges valid at this time.
Returns an empty Subgraph if the start node does not exist.
pub fn connected_components(
&self,
edge_type_filter: Option<&[u32]>,
node_type_filter: Option<&[u32]>,
at_epoch: Option<i64>,
) -> Result<NodeIdMap<u64>, EngineError>
pub fn connected_components( &self, edge_type_filter: Option<&[u32]>, node_type_filter: Option<&[u32]>, at_epoch: Option<i64>, ) -> Result<NodeIdMap<u64>, EngineError>
Weakly connected components over the live visible graph.
WCC treats all edges as undirected: edge direction is ignored for
component membership. Returns a NodeIdMap<u64> mapping every
visible node ID to its deterministic component ID, where the
component ID is the minimum node ID in that component.
NodeIdMap is a HashMap with identity hashing optimized for
engine-generated numeric IDs.
edge_type_filter: only consider edges of these types.None= all.node_type_filter: only include nodes of these types.None= all.at_epoch: reference time for temporal edge visibility.None→ now.
Isolated nodes (no visible edges after filtering) become singleton components whose component ID equals the node’s own ID.
§Algorithm
Union-Find with path compression and union by rank over a global
outgoing adjacency scan. Each edge is seen exactly once (outgoing
direction), and union(from, to) captures undirected connectivity.
§Examples
let components = db.connected_components(None, None, None).unwrap();
// components: { node_id → component_id (min node in component) }pub fn component_of(
&self,
node_id: u64,
edge_type_filter: Option<&[u32]>,
node_type_filter: Option<&[u32]>,
at_epoch: Option<i64>,
) -> Result<Vec<u64>, EngineError>
pub fn component_of( &self, node_id: u64, edge_type_filter: Option<&[u32]>, node_type_filter: Option<&[u32]>, at_epoch: Option<i64>, ) -> Result<Vec<u64>, EngineError>
Returns the node set of the weakly connected component containing
node_id, sorted by node ID ascending.
Uses targeted BFS from the given node, treating all edges as
undirected (Direction::Both). Avoids full-graph computation.
Returns an empty Vec if the node doesn’t exist, is deleted, or is
hidden by prune policy. Returns an empty Vec if the node exists but
is excluded by node_type_filter.
§Examples
let members = db.component_of(42, None, None, None).unwrap();
// members: sorted Vec of node IDs in the same component as node 42§impl DatabaseEngine
impl DatabaseEngine
pub fn upsert_node(
&mut self,
type_id: u32,
key: &str,
props: BTreeMap<String, PropValue>,
weight: f32,
) -> Result<u64, EngineError>
pub fn upsert_node( &mut self, type_id: u32, key: &str, props: BTreeMap<String, PropValue>, weight: f32, ) -> Result<u64, EngineError>
Upsert a node. If a node with the same (type_id, key) exists, updates it. Otherwise allocates a new ID. Returns the node ID.
pub fn upsert_edge(
&mut self,
from: u64,
to: u64,
type_id: u32,
props: BTreeMap<String, PropValue>,
weight: f32,
valid_from: Option<i64>,
valid_to: Option<i64>,
) -> Result<u64, EngineError>
pub fn upsert_edge( &mut self, from: u64, to: u64, type_id: u32, props: BTreeMap<String, PropValue>, weight: f32, valid_from: Option<i64>, valid_to: Option<i64>, ) -> Result<u64, EngineError>
Upsert an edge. If edge_uniqueness is enabled and an edge with the same (from, to, type_id) exists, updates it. Otherwise allocates a new ID. Returns the edge ID.
pub fn batch_upsert_nodes(
&mut self,
inputs: &[NodeInput],
) -> Result<Vec<u64>, EngineError>
pub fn batch_upsert_nodes( &mut self, inputs: &[NodeInput], ) -> Result<Vec<u64>, EngineError>
Batch upsert nodes with a single fsync. Returns IDs in input order. Handles dedup within the batch and against existing memtable state.
pub fn batch_upsert_edges(
&mut self,
inputs: &[EdgeInput],
) -> Result<Vec<u64>, EngineError>
pub fn batch_upsert_edges( &mut self, inputs: &[EdgeInput], ) -> Result<Vec<u64>, EngineError>
Batch upsert edges with a single fsync. Returns IDs in input order.
pub fn delete_node(&mut self, id: u64) -> Result<(), EngineError>
pub fn delete_node(&mut self, id: u64) -> Result<(), EngineError>
Delete a node by ID. Cascade-deletes all incident edges (memtable + segments), then writes the node tombstone. Single fsync at the end.
pub fn delete_edge(&mut self, id: u64) -> Result<(), EngineError>
pub fn delete_edge(&mut self, id: u64) -> Result<(), EngineError>
Delete an edge by ID. Writes a tombstone to WAL. Idempotent: deleting a nonexistent or already-deleted edge writes a tombstone but is not an error.
pub fn invalidate_edge(
&mut self,
id: u64,
valid_to: i64,
) -> Result<Option<EdgeRecord>, EngineError>
pub fn invalidate_edge( &mut self, id: u64, valid_to: i64, ) -> Result<Option<EdgeRecord>, EngineError>
Invalidate an edge by closing its validity window. Sets valid_to on the edge. The edge remains in the database (not tombstoned) but is excluded from current-time neighbor queries. Returns the updated EdgeRecord, or None if the edge doesn’t exist.
pub fn graph_patch(
&mut self,
patch: &GraphPatch,
) -> Result<PatchResult, EngineError>
pub fn graph_patch( &mut self, patch: &GraphPatch, ) -> Result<PatchResult, EngineError>
Atomic graph patch: apply a mix of node upserts, edge upserts, edge invalidations, and deletes in a single WAL batch. Deterministic ordering: node upserts → edge upserts → edge invalidations → edge deletes → node deletes.
Node deletes cascade: incident edges are automatically deleted. Returns allocated IDs for upserted nodes and edges (input order preserved).
pub fn prune(
&mut self,
policy: &PrunePolicy,
) -> Result<PruneResult, EngineError>
pub fn prune( &mut self, policy: &PrunePolicy, ) -> Result<PruneResult, EngineError>
Prune nodes matching the given policy, cascade-deleting their incident edges.
All matching criteria combine with AND logic. At least one of max_age_ms or
max_weight must be set to prevent accidental mass deletion. All deletes are
applied in a single WAL batch for atomicity.
pub fn set_prune_policy(
&mut self,
name: &str,
policy: PrunePolicy,
) -> Result<(), EngineError>
pub fn set_prune_policy( &mut self, name: &str, policy: PrunePolicy, ) -> Result<(), EngineError>
Register a named prune policy. Persisted in the manifest and applied automatically during compaction. Multiple named policies are allowed; a node matching ANY policy is pruned (OR across policies, AND within).
pub fn remove_prune_policy(&mut self, name: &str) -> Result<bool, EngineError>
pub fn remove_prune_policy(&mut self, name: &str) -> Result<bool, EngineError>
Remove a named prune policy. Returns true if it existed.
pub fn list_prune_policies(&self) -> Vec<(String, PrunePolicy)>
pub fn list_prune_policies(&self) -> Vec<(String, PrunePolicy)>
List all registered prune policies.
§impl DatabaseEngine
impl DatabaseEngine
pub fn get_node(&self, id: u64) -> Result<Option<NodeRecord>, EngineError>
pub fn get_node(&self, id: u64) -> Result<Option<NodeRecord>, EngineError>
Get a node by ID. Checks memtable first, then segments newest-to-oldest. Returns None if not found, deleted, or excluded by a registered prune policy.
pub fn get_edge(&self, id: u64) -> Result<Option<EdgeRecord>, EngineError>
pub fn get_edge(&self, id: u64) -> Result<Option<EdgeRecord>, EngineError>
Get an edge by ID. Checks memtable first, then segments newest-to-oldest. Returns None if not found or deleted (tombstoned).
pub fn get_node_by_key(
&self,
type_id: u32,
key: &str,
) -> Result<Option<NodeRecord>, EngineError>
pub fn get_node_by_key( &self, type_id: u32, key: &str, ) -> Result<Option<NodeRecord>, EngineError>
Get a node by (type_id, key) across memtable + segments. Returns None if not found, deleted, or excluded by a registered prune policy.
pub fn get_edge_by_triple(
&self,
from: u64,
to: u64,
type_id: u32,
) -> Result<Option<EdgeRecord>, EngineError>
pub fn get_edge_by_triple( &self, from: u64, to: u64, type_id: u32, ) -> Result<Option<EdgeRecord>, EngineError>
Get an edge by (from, to, type_id) across memtable + segments. Returns the most recently written edge matching the triple. Returns None if not found or deleted (tombstoned).
pub fn get_nodes(
&self,
ids: &[u64],
) -> Result<Vec<Option<NodeRecord>>, EngineError>
pub fn get_nodes( &self, ids: &[u64], ) -> Result<Vec<Option<NodeRecord>>, EngineError>
Get multiple nodes by ID in a single call.
Returns a Vec<Option<NodeRecord>>, one per input ID (order preserved).
Missing, deleted, or policy-excluded nodes are None.
Uses a batched approach: resolves memtable hits first (O(1) each), then does a single sorted merge-walk per segment instead of N binary searches.
pub fn get_edges(
&self,
ids: &[u64],
) -> Result<Vec<Option<EdgeRecord>>, EngineError>
pub fn get_edges( &self, ids: &[u64], ) -> Result<Vec<Option<EdgeRecord>>, EngineError>
Get multiple edges by ID in a single call.
Returns a Vec<Option<EdgeRecord>>, one per input ID (order preserved).
Missing or deleted edges are None.
Uses a batched approach: resolves memtable hits first (O(1) each), then does a single sorted merge-walk per segment instead of N binary searches.
pub fn nodes_by_type(&self, type_id: u32) -> Result<Vec<u64>, EngineError>
pub fn nodes_by_type(&self, type_id: u32) -> Result<Vec<u64>, EngineError>
Return all live node IDs with the given type_id, merged across memtable and all segments. Excludes tombstoned and policy-excluded nodes.
pub fn edges_by_type(&self, type_id: u32) -> Result<Vec<u64>, EngineError>
pub fn edges_by_type(&self, type_id: u32) -> Result<Vec<u64>, EngineError>
Return all live edge IDs with the given type_id, merged across memtable and all segments. Excludes tombstoned edges.
pub fn get_nodes_by_type(
&self,
type_id: u32,
) -> Result<Vec<NodeRecord>, EngineError>
pub fn get_nodes_by_type( &self, type_id: u32, ) -> Result<Vec<NodeRecord>, EngineError>
Return all live node records with the given type_id, hydrated from memtable and segments. Excludes tombstoned and policy-excluded nodes.
Uses nodes_by_type() for the ID list (already policy-filtered), then
get_nodes_raw() for batch hydration (one merge-walk per segment,
not N individual lookups). Single policy pass, no redundant filtering.
pub fn get_edges_by_type(
&self,
type_id: u32,
) -> Result<Vec<EdgeRecord>, EngineError>
pub fn get_edges_by_type( &self, type_id: u32, ) -> Result<Vec<EdgeRecord>, EngineError>
Return all live edge records with the given type_id, hydrated from memtable and segments. Excludes tombstoned edges.
Uses edges_by_type() for the ID list, then get_edges() for batch
hydration (one merge-walk per segment, not N individual lookups).
pub fn count_nodes_by_type(&self, type_id: u32) -> Result<u64, EngineError>
pub fn count_nodes_by_type(&self, type_id: u32) -> Result<u64, EngineError>
Return the count of live nodes with the given type_id without hydrating records. Excludes tombstoned and policy-excluded nodes.
pub fn count_edges_by_type(&self, type_id: u32) -> Result<u64, EngineError>
pub fn count_edges_by_type(&self, type_id: u32) -> Result<u64, EngineError>
Return the count of live edges with the given type_id without hydrating records. Excludes tombstoned edges (edges are not subject to prune policies).
pub fn nodes_by_type_paged(
&self,
type_id: u32,
page: &PageRequest,
) -> Result<PageResult<u64>, EngineError>
pub fn nodes_by_type_paged( &self, type_id: u32, page: &PageRequest, ) -> Result<PageResult<u64>, EngineError>
Paginated version of nodes_by_type. Returns a page of node IDs sorted
by ID, with cursor-based pagination. Pass PageRequest::default() to get
all results (equivalent to nodes_by_type).
Uses K-way merge across already-sorted sources with early termination: O(cursor_position + limit) instead of O(N log N) when no prune policies are active. With policies, still saves the sort via merge, then applies policy filtering and cursor on the sorted result.
pub fn edges_by_type_paged(
&self,
type_id: u32,
page: &PageRequest,
) -> Result<PageResult<u64>, EngineError>
pub fn edges_by_type_paged( &self, type_id: u32, page: &PageRequest, ) -> Result<PageResult<u64>, EngineError>
Paginated version of edges_by_type. Returns a page of edge IDs sorted
by ID, with cursor-based pagination. Uses K-way merge with early
termination (edges are not subject to prune policies).
pub fn get_nodes_by_type_paged(
&self,
type_id: u32,
page: &PageRequest,
) -> Result<PageResult<NodeRecord>, EngineError>
pub fn get_nodes_by_type_paged( &self, type_id: u32, page: &PageRequest, ) -> Result<PageResult<NodeRecord>, EngineError>
Paginated version of get_nodes_by_type. Returns a page of hydrated node
records. Only hydrates records in the requested page (not all then slice).
In rare cases (data inconsistency), the page may contain fewer items than
limit even when next_cursor is Some.
pub fn get_edges_by_type_paged(
&self,
type_id: u32,
page: &PageRequest,
) -> Result<PageResult<EdgeRecord>, EngineError>
pub fn get_edges_by_type_paged( &self, type_id: u32, page: &PageRequest, ) -> Result<PageResult<EdgeRecord>, EngineError>
Paginated version of get_edges_by_type. Returns a page of hydrated edge
records. Only hydrates records in the requested page (not all then slice).
In rare cases (data inconsistency), the page may contain fewer items than
limit even when next_cursor is Some.
pub fn find_nodes(
&self,
type_id: u32,
prop_key: &str,
prop_value: &PropValue,
) -> Result<Vec<u64>, EngineError>
pub fn find_nodes( &self, type_id: u32, prop_key: &str, prop_value: &PropValue, ) -> Result<Vec<u64>, EngineError>
Find node IDs matching (type_id, prop_key == prop_value). Merges candidates from memtable + segments, deduplicates, and post-filters to verify actual property equality (handles hash collisions). Excludes nodes matching any registered prune policy.
pub fn find_nodes_paged(
&self,
type_id: u32,
prop_key: &str,
prop_value: &PropValue,
page: &PageRequest,
) -> Result<PageResult<u64>, EngineError>
pub fn find_nodes_paged( &self, type_id: u32, prop_key: &str, prop_value: &PropValue, page: &PageRequest, ) -> Result<PageResult<u64>, EngineError>
Paginated version of find_nodes. Returns a page of node IDs matching
(type_id, prop_key == prop_value), sorted by ID with cursor-based
pagination.
Uses K-way merge across sorted property hash buckets (segment IDs are written sorted at flush/compaction time). Post-filters for hash collisions, then applies cursor + limit on the verified results. Policy filtering applied when policies are active.
pub fn find_nodes_by_time_range(
&self,
type_id: u32,
from_ms: i64,
to_ms: i64,
) -> Result<Vec<u64>, EngineError>
pub fn find_nodes_by_time_range( &self, type_id: u32, from_ms: i64, to_ms: i64, ) -> Result<Vec<u64>, EngineError>
Find node IDs of a given type updated within a time range [from_ms, to_ms] (inclusive). Merges across memtable + segments with deduplication. Excludes tombstoned and policy-pruned nodes.
pub fn find_nodes_by_time_range_paged(
&self,
type_id: u32,
from_ms: i64,
to_ms: i64,
page: &PageRequest,
) -> Result<PageResult<u64>, EngineError>
pub fn find_nodes_by_time_range_paged( &self, type_id: u32, from_ms: i64, to_ms: i64, page: &PageRequest, ) -> Result<PageResult<u64>, EngineError>
Paginated version of find_nodes_by_time_range. Returns a page of node IDs
sorted by ID with cursor-based pagination.
Uses K-way merge across sorted sources (binary search for range in each segment, sort results by node_id). O(log N) seek per source + O(results) scan.
pub fn personalized_pagerank(
&self,
seed_node_ids: &[u64],
options: &PprOptions,
) -> Result<PprResult, EngineError>
pub fn personalized_pagerank( &self, seed_node_ids: &[u64], options: &PprOptions, ) -> Result<PprResult, EngineError>
Compute Personalized PageRank starting from seed nodes.
Uses BFS discovery + dense Vec power iteration with weighted transitions.
Phase 1: DFS from seeds discovers all reachable nodes and caches neighbors.
Phase 2: Builds dense node_id→index mapping with precomputed normalized weights.
Phase 3: Power iteration over contiguous Vec<f64> rank vectors.
Edge weights determine transition probabilities (proportional to weight). Dangling nodes (no outgoing edges) teleport back to the seed set.
Returns scored nodes sorted by score descending, with optional top-k cutoff.
pub fn export_adjacency(
&self,
options: &ExportOptions,
) -> Result<AdjacencyExport, EngineError>
pub fn export_adjacency( &self, options: &ExportOptions, ) -> Result<AdjacencyExport, EngineError>
Export the graph’s adjacency structure for external community detection.
Returns all live node IDs and edges (from, to, type_id, weight), filtered by optional node/edge type filters. Respects tombstones and prune policies. Each edge is emitted once (outgoing direction only).
Edges are only included if both endpoints are in the exported node set, ensuring a consistent subgraph for external tools.