Skip to main content

grafeo_core/graph/
traits.rs

1//! Storage traits for the graph engine.
2//!
3//! These traits capture the minimal surface that query operators need from
4//! the graph store. The split is intentional:
5//!
6//! - [`GraphStore`]: Read-only operations (scans, lookups, traversal, statistics)
7//! - [`GraphStoreMut`]: Write operations (create, delete, mutate)
8//!
9//! Admin operations (index management, MVCC internals, schema introspection,
10//! statistics recomputation, WAL recovery) stay on the concrete [`LpgStore`]
11//! and are not part of these traits.
12//!
13//! ## Design rationale
14//!
15//! The traits work with typed graph objects (`Node`, `Edge`, `Value`) rather
16//! than raw bytes. This preserves zero-overhead access for in-memory storage
17//! while allowing future backends (SpilloverStore, disk-backed) to implement
18//! the same interface with transparent serialization where needed.
19//!
20//! [`LpgStore`]: crate::graph::lpg::LpgStore
21
22use crate::graph::Direction;
23use crate::graph::lpg::CompareOp;
24use crate::graph::lpg::{Edge, Node};
25use crate::statistics::Statistics;
26use arcstr::ArcStr;
27use grafeo_common::types::{EdgeId, EpochId, NodeId, PropertyKey, TransactionId, Value};
28use grafeo_common::utils::hash::FxHashMap;
29use std::sync::Arc;
30
31/// Read-only graph operations used by the query engine.
32///
33/// This trait captures the minimal surface that scan, expand, filter,
34/// project, and shortest-path operators need. Implementations may serve
35/// data from memory, disk, or a hybrid of both.
36///
37/// # Object safety
38///
39/// This trait is object-safe: you can use `Arc<dyn GraphStore>` for dynamic
40/// dispatch. Traversal methods return `Vec` instead of `impl Iterator` to
41/// enable this.
42pub trait GraphStore: Send + Sync {
43    // --- Point lookups ---
44
45    /// Returns a node by ID (latest visible version at current epoch).
46    fn get_node(&self, id: NodeId) -> Option<Node>;
47
48    /// Returns an edge by ID (latest visible version at current epoch).
49    fn get_edge(&self, id: EdgeId) -> Option<Edge>;
50
51    /// Returns a node visible to a specific transaction.
52    fn get_node_versioned(
53        &self,
54        id: NodeId,
55        epoch: EpochId,
56        transaction_id: TransactionId,
57    ) -> Option<Node>;
58
59    /// Returns an edge visible to a specific transaction.
60    fn get_edge_versioned(
61        &self,
62        id: EdgeId,
63        epoch: EpochId,
64        transaction_id: TransactionId,
65    ) -> Option<Edge>;
66
67    /// Returns a node using pure epoch-based visibility (no transaction context).
68    ///
69    /// The node is visible if `created_epoch <= epoch` and not deleted at or
70    /// before `epoch`. Used for time-travel queries where transaction ownership
71    /// must not bypass the epoch check.
72    fn get_node_at_epoch(&self, id: NodeId, epoch: EpochId) -> Option<Node>;
73
74    /// Returns an edge using pure epoch-based visibility (no transaction context).
75    fn get_edge_at_epoch(&self, id: EdgeId, epoch: EpochId) -> Option<Edge>;
76
77    // --- Property access (fast path, avoids loading full entity) ---
78
79    /// Gets a single property from a node without loading all properties.
80    fn get_node_property(&self, id: NodeId, key: &PropertyKey) -> Option<Value>;
81
82    /// Gets a single property from an edge without loading all properties.
83    fn get_edge_property(&self, id: EdgeId, key: &PropertyKey) -> Option<Value>;
84
85    /// Gets a property for multiple nodes in a single batch operation.
86    fn get_node_property_batch(&self, ids: &[NodeId], key: &PropertyKey) -> Vec<Option<Value>>;
87
88    /// Gets all properties for multiple nodes in a single batch operation.
89    fn get_nodes_properties_batch(&self, ids: &[NodeId]) -> Vec<FxHashMap<PropertyKey, Value>>;
90
91    /// Gets selected properties for multiple nodes (projection pushdown).
92    fn get_nodes_properties_selective_batch(
93        &self,
94        ids: &[NodeId],
95        keys: &[PropertyKey],
96    ) -> Vec<FxHashMap<PropertyKey, Value>>;
97
98    /// Gets selected properties for multiple edges (projection pushdown).
99    fn get_edges_properties_selective_batch(
100        &self,
101        ids: &[EdgeId],
102        keys: &[PropertyKey],
103    ) -> Vec<FxHashMap<PropertyKey, Value>>;
104
105    // --- Traversal ---
106
107    /// Returns neighbor node IDs in the specified direction.
108    ///
109    /// Returns `Vec` instead of an iterator for object safety. The underlying
110    /// `ChunkedAdjacency` already produces a `Vec` internally.
111    fn neighbors(&self, node: NodeId, direction: Direction) -> Vec<NodeId>;
112
113    /// Returns (target_node, edge_id) pairs for edges from a node.
114    fn edges_from(&self, node: NodeId, direction: Direction) -> Vec<(NodeId, EdgeId)>;
115
116    /// Returns the out-degree of a node (number of outgoing edges).
117    fn out_degree(&self, node: NodeId) -> usize;
118
119    /// Returns the in-degree of a node (number of incoming edges).
120    fn in_degree(&self, node: NodeId) -> usize;
121
122    /// Whether backward adjacency is available for incoming edge queries.
123    fn has_backward_adjacency(&self) -> bool;
124
125    // --- Scans ---
126
127    /// Returns all non-deleted node IDs, sorted by ID.
128    fn node_ids(&self) -> Vec<NodeId>;
129
130    /// Returns all node IDs including uncommitted/PENDING versions.
131    ///
132    /// Unlike `node_ids()` which pre-filters by current epoch, this method
133    /// returns every node that has a version chain entry. Used by scan operators
134    /// that perform their own MVCC visibility filtering (e.g. with transaction context).
135    fn all_node_ids(&self) -> Vec<NodeId> {
136        // Default: fall back to node_ids() for stores without MVCC
137        self.node_ids()
138    }
139
140    /// Returns node IDs with a specific label.
141    fn nodes_by_label(&self, label: &str) -> Vec<NodeId>;
142
143    /// Returns the total number of non-deleted nodes.
144    fn node_count(&self) -> usize;
145
146    /// Returns the total number of non-deleted edges.
147    fn edge_count(&self) -> usize;
148
149    // --- Entity metadata ---
150
151    /// Returns the type string of an edge.
152    fn edge_type(&self, id: EdgeId) -> Option<ArcStr>;
153
154    /// Returns the type string of an edge visible to a specific transaction.
155    ///
156    /// Falls back to epoch-based `edge_type` if not overridden.
157    fn edge_type_versioned(
158        &self,
159        id: EdgeId,
160        epoch: EpochId,
161        transaction_id: TransactionId,
162    ) -> Option<ArcStr> {
163        let _ = (epoch, transaction_id);
164        self.edge_type(id)
165    }
166
167    // --- Index introspection ---
168
169    /// Returns `true` if a property index exists for the given property.
170    ///
171    /// The default returns `false`, which is correct for stores without indexes.
172    fn has_property_index(&self, _property: &str) -> bool {
173        false
174    }
175
176    // --- Filtered search ---
177
178    /// Finds all nodes with a specific property value. Uses indexes when available.
179    fn find_nodes_by_property(&self, property: &str, value: &Value) -> Vec<NodeId>;
180
181    /// Finds nodes matching multiple property equality conditions.
182    fn find_nodes_by_properties(&self, conditions: &[(&str, Value)]) -> Vec<NodeId>;
183
184    /// Finds nodes whose property value falls within a range.
185    fn find_nodes_in_range(
186        &self,
187        property: &str,
188        min: Option<&Value>,
189        max: Option<&Value>,
190        min_inclusive: bool,
191        max_inclusive: bool,
192    ) -> Vec<NodeId>;
193
194    // --- Zone maps (skip pruning) ---
195
196    /// Returns `true` if a node property predicate might match any nodes.
197    /// Uses zone maps for early filtering.
198    fn node_property_might_match(
199        &self,
200        property: &PropertyKey,
201        op: CompareOp,
202        value: &Value,
203    ) -> bool;
204
205    /// Returns `true` if an edge property predicate might match any edges.
206    fn edge_property_might_match(
207        &self,
208        property: &PropertyKey,
209        op: CompareOp,
210        value: &Value,
211    ) -> bool;
212
213    // --- Statistics (for cost-based optimizer) ---
214
215    /// Returns the current statistics snapshot (cheap Arc clone).
216    fn statistics(&self) -> Arc<Statistics>;
217
218    /// Estimates cardinality for a label scan.
219    fn estimate_label_cardinality(&self, label: &str) -> f64;
220
221    /// Estimates average degree for an edge type.
222    fn estimate_avg_degree(&self, edge_type: &str, outgoing: bool) -> f64;
223
224    // --- Epoch ---
225
226    /// Returns the current MVCC epoch.
227    fn current_epoch(&self) -> EpochId;
228
229    // --- Schema introspection ---
230
231    /// Returns all label names in the database.
232    fn all_labels(&self) -> Vec<String> {
233        Vec::new()
234    }
235
236    /// Returns all edge type names in the database.
237    fn all_edge_types(&self) -> Vec<String> {
238        Vec::new()
239    }
240
241    /// Returns all property key names used in the database.
242    fn all_property_keys(&self) -> Vec<String> {
243        Vec::new()
244    }
245
246    // --- Visibility checks (fast path, avoids building full entities) ---
247
248    /// Checks if a node is visible at the given epoch without building the full Node.
249    ///
250    /// More efficient than `get_node_at_epoch(...).is_some()` because it skips
251    /// label and property loading. Override in concrete stores for optimal
252    /// performance.
253    fn is_node_visible_at_epoch(&self, id: NodeId, epoch: EpochId) -> bool {
254        self.get_node_at_epoch(id, epoch).is_some()
255    }
256
257    /// Checks if a node is visible to a specific transaction without building
258    /// the full Node.
259    fn is_node_visible_versioned(
260        &self,
261        id: NodeId,
262        epoch: EpochId,
263        transaction_id: TransactionId,
264    ) -> bool {
265        self.get_node_versioned(id, epoch, transaction_id).is_some()
266    }
267
268    /// Filters node IDs to only those visible at the given epoch (batch).
269    ///
270    /// More efficient than per-node calls because implementations can hold
271    /// a single lock for the entire batch.
272    fn filter_visible_node_ids(&self, ids: &[NodeId], epoch: EpochId) -> Vec<NodeId> {
273        ids.iter()
274            .copied()
275            .filter(|id| self.is_node_visible_at_epoch(*id, epoch))
276            .collect()
277    }
278
279    /// Filters node IDs to only those visible to a transaction (batch).
280    fn filter_visible_node_ids_versioned(
281        &self,
282        ids: &[NodeId],
283        epoch: EpochId,
284        transaction_id: TransactionId,
285    ) -> Vec<NodeId> {
286        ids.iter()
287            .copied()
288            .filter(|id| self.is_node_visible_versioned(*id, epoch, transaction_id))
289            .collect()
290    }
291
292    // --- History ---
293
294    /// Returns all versions of a node with their creation/deletion epochs, newest first.
295    ///
296    /// Each entry is `(created_epoch, deleted_epoch, Node)`. Properties and labels
297    /// reflect the current state (they are not versioned per-epoch).
298    ///
299    /// Default returns empty (not all backends track version history).
300    fn get_node_history(&self, _id: NodeId) -> Vec<(EpochId, Option<EpochId>, Node)> {
301        Vec::new()
302    }
303
304    /// Returns all versions of an edge with their creation/deletion epochs, newest first.
305    ///
306    /// Each entry is `(created_epoch, deleted_epoch, Edge)`. Properties reflect
307    /// the current state (they are not versioned per-epoch).
308    ///
309    /// Default returns empty (not all backends track version history).
310    fn get_edge_history(&self, _id: EdgeId) -> Vec<(EpochId, Option<EpochId>, Edge)> {
311        Vec::new()
312    }
313}
314
315/// Write operations for graph mutation.
316///
317/// Separated from [`GraphStore`] so read-only wrappers (snapshots, read
318/// replicas) can implement only `GraphStore`. Any mutable store is also
319/// readable via the supertrait bound.
320pub trait GraphStoreMut: GraphStore {
321    // --- Node creation ---
322
323    /// Creates a new node with the given labels.
324    fn create_node(&self, labels: &[&str]) -> NodeId;
325
326    /// Creates a new node within a transaction context.
327    fn create_node_versioned(
328        &self,
329        labels: &[&str],
330        epoch: EpochId,
331        transaction_id: TransactionId,
332    ) -> NodeId;
333
334    // --- Edge creation ---
335
336    /// Creates a new edge between two nodes.
337    fn create_edge(&self, src: NodeId, dst: NodeId, edge_type: &str) -> EdgeId;
338
339    /// Creates a new edge within a transaction context.
340    fn create_edge_versioned(
341        &self,
342        src: NodeId,
343        dst: NodeId,
344        edge_type: &str,
345        epoch: EpochId,
346        transaction_id: TransactionId,
347    ) -> EdgeId;
348
349    /// Creates multiple edges in batch (single lock acquisition).
350    fn batch_create_edges(&self, edges: &[(NodeId, NodeId, &str)]) -> Vec<EdgeId>;
351
352    // --- Deletion ---
353
354    /// Deletes a node. Returns `true` if the node existed.
355    fn delete_node(&self, id: NodeId) -> bool;
356
357    /// Deletes a node within a transaction context. Returns `true` if the node existed.
358    fn delete_node_versioned(
359        &self,
360        id: NodeId,
361        epoch: EpochId,
362        transaction_id: TransactionId,
363    ) -> bool;
364
365    /// Deletes all edges connected to a node (DETACH DELETE).
366    fn delete_node_edges(&self, node_id: NodeId);
367
368    /// Deletes an edge. Returns `true` if the edge existed.
369    fn delete_edge(&self, id: EdgeId) -> bool;
370
371    /// Deletes an edge within a transaction context. Returns `true` if the edge existed.
372    fn delete_edge_versioned(
373        &self,
374        id: EdgeId,
375        epoch: EpochId,
376        transaction_id: TransactionId,
377    ) -> bool;
378
379    // --- Property mutation ---
380
381    /// Sets a property on a node.
382    fn set_node_property(&self, id: NodeId, key: &str, value: Value);
383
384    /// Sets a property on an edge.
385    fn set_edge_property(&self, id: EdgeId, key: &str, value: Value);
386
387    /// Sets a node property within a transaction, recording the previous value
388    /// so it can be restored on rollback.
389    ///
390    /// Default delegates to [`set_node_property`](Self::set_node_property).
391    fn set_node_property_versioned(
392        &self,
393        id: NodeId,
394        key: &str,
395        value: Value,
396        _transaction_id: TransactionId,
397    ) {
398        self.set_node_property(id, key, value);
399    }
400
401    /// Sets an edge property within a transaction, recording the previous value
402    /// so it can be restored on rollback.
403    ///
404    /// Default delegates to [`set_edge_property`](Self::set_edge_property).
405    fn set_edge_property_versioned(
406        &self,
407        id: EdgeId,
408        key: &str,
409        value: Value,
410        _transaction_id: TransactionId,
411    ) {
412        self.set_edge_property(id, key, value);
413    }
414
415    /// Removes a property from a node. Returns the previous value if it existed.
416    fn remove_node_property(&self, id: NodeId, key: &str) -> Option<Value>;
417
418    /// Removes a property from an edge. Returns the previous value if it existed.
419    fn remove_edge_property(&self, id: EdgeId, key: &str) -> Option<Value>;
420
421    /// Removes a node property within a transaction, recording the previous value
422    /// so it can be restored on rollback.
423    ///
424    /// Default delegates to [`remove_node_property`](Self::remove_node_property).
425    fn remove_node_property_versioned(
426        &self,
427        id: NodeId,
428        key: &str,
429        _transaction_id: TransactionId,
430    ) -> Option<Value> {
431        self.remove_node_property(id, key)
432    }
433
434    /// Removes an edge property within a transaction, recording the previous value
435    /// so it can be restored on rollback.
436    ///
437    /// Default delegates to [`remove_edge_property`](Self::remove_edge_property).
438    fn remove_edge_property_versioned(
439        &self,
440        id: EdgeId,
441        key: &str,
442        _transaction_id: TransactionId,
443    ) -> Option<Value> {
444        self.remove_edge_property(id, key)
445    }
446
447    // --- Label mutation ---
448
449    /// Adds a label to a node. Returns `true` if the label was new.
450    fn add_label(&self, node_id: NodeId, label: &str) -> bool;
451
452    /// Removes a label from a node. Returns `true` if the label existed.
453    fn remove_label(&self, node_id: NodeId, label: &str) -> bool;
454
455    /// Adds a label within a transaction, recording the change for rollback.
456    ///
457    /// Default delegates to [`add_label`](Self::add_label).
458    fn add_label_versioned(
459        &self,
460        node_id: NodeId,
461        label: &str,
462        _transaction_id: TransactionId,
463    ) -> bool {
464        self.add_label(node_id, label)
465    }
466
467    /// Removes a label within a transaction, recording the change for rollback.
468    ///
469    /// Default delegates to [`remove_label`](Self::remove_label).
470    fn remove_label_versioned(
471        &self,
472        node_id: NodeId,
473        label: &str,
474        _transaction_id: TransactionId,
475    ) -> bool {
476        self.remove_label(node_id, label)
477    }
478
479    // --- Convenience (with default implementations) ---
480
481    /// Creates a new node with labels and properties in one call.
482    ///
483    /// The default implementation calls [`create_node`](Self::create_node)
484    /// followed by [`set_node_property`](Self::set_node_property) for each
485    /// property. Implementations may override for atomicity or performance.
486    fn create_node_with_props(
487        &self,
488        labels: &[&str],
489        properties: &[(PropertyKey, Value)],
490    ) -> NodeId {
491        let id = self.create_node(labels);
492        for (key, value) in properties {
493            self.set_node_property(id, key.as_str(), value.clone());
494        }
495        id
496    }
497
498    /// Creates a new edge with properties in one call.
499    ///
500    /// The default implementation calls [`create_edge`](Self::create_edge)
501    /// followed by [`set_edge_property`](Self::set_edge_property) for each
502    /// property. Implementations may override for atomicity or performance.
503    fn create_edge_with_props(
504        &self,
505        src: NodeId,
506        dst: NodeId,
507        edge_type: &str,
508        properties: &[(PropertyKey, Value)],
509    ) -> EdgeId {
510        let id = self.create_edge(src, dst, edge_type);
511        for (key, value) in properties {
512            self.set_edge_property(id, key.as_str(), value.clone());
513        }
514        id
515    }
516}