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, TxId, 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(&self, id: NodeId, epoch: EpochId, tx_id: TxId) -> Option<Node>;
53
54    /// Returns an edge visible to a specific transaction.
55    fn get_edge_versioned(&self, id: EdgeId, epoch: EpochId, tx_id: TxId) -> Option<Edge>;
56
57    // --- Property access (fast path, avoids loading full entity) ---
58
59    /// Gets a single property from a node without loading all properties.
60    fn get_node_property(&self, id: NodeId, key: &PropertyKey) -> Option<Value>;
61
62    /// Gets a single property from an edge without loading all properties.
63    fn get_edge_property(&self, id: EdgeId, key: &PropertyKey) -> Option<Value>;
64
65    /// Gets a property for multiple nodes in a single batch operation.
66    fn get_node_property_batch(&self, ids: &[NodeId], key: &PropertyKey) -> Vec<Option<Value>>;
67
68    /// Gets all properties for multiple nodes in a single batch operation.
69    fn get_nodes_properties_batch(&self, ids: &[NodeId]) -> Vec<FxHashMap<PropertyKey, Value>>;
70
71    /// Gets selected properties for multiple nodes (projection pushdown).
72    fn get_nodes_properties_selective_batch(
73        &self,
74        ids: &[NodeId],
75        keys: &[PropertyKey],
76    ) -> Vec<FxHashMap<PropertyKey, Value>>;
77
78    /// Gets selected properties for multiple edges (projection pushdown).
79    fn get_edges_properties_selective_batch(
80        &self,
81        ids: &[EdgeId],
82        keys: &[PropertyKey],
83    ) -> Vec<FxHashMap<PropertyKey, Value>>;
84
85    // --- Traversal ---
86
87    /// Returns neighbor node IDs in the specified direction.
88    ///
89    /// Returns `Vec` instead of an iterator for object safety. The underlying
90    /// `ChunkedAdjacency` already produces a `Vec` internally.
91    fn neighbors(&self, node: NodeId, direction: Direction) -> Vec<NodeId>;
92
93    /// Returns (target_node, edge_id) pairs for edges from a node.
94    fn edges_from(&self, node: NodeId, direction: Direction) -> Vec<(NodeId, EdgeId)>;
95
96    /// Returns the out-degree of a node (number of outgoing edges).
97    fn out_degree(&self, node: NodeId) -> usize;
98
99    /// Returns the in-degree of a node (number of incoming edges).
100    fn in_degree(&self, node: NodeId) -> usize;
101
102    /// Whether backward adjacency is available for incoming edge queries.
103    fn has_backward_adjacency(&self) -> bool;
104
105    // --- Scans ---
106
107    /// Returns all non-deleted node IDs, sorted by ID.
108    fn node_ids(&self) -> Vec<NodeId>;
109
110    /// Returns node IDs with a specific label.
111    fn nodes_by_label(&self, label: &str) -> Vec<NodeId>;
112
113    /// Returns the total number of non-deleted nodes.
114    fn node_count(&self) -> usize;
115
116    /// Returns the total number of non-deleted edges.
117    fn edge_count(&self) -> usize;
118
119    // --- Entity metadata ---
120
121    /// Returns the type string of an edge.
122    fn edge_type(&self, id: EdgeId) -> Option<ArcStr>;
123
124    // --- Index introspection ---
125
126    /// Returns `true` if a property index exists for the given property.
127    ///
128    /// The default returns `false`, which is correct for stores without indexes.
129    fn has_property_index(&self, _property: &str) -> bool {
130        false
131    }
132
133    // --- Filtered search ---
134
135    /// Finds all nodes with a specific property value. Uses indexes when available.
136    fn find_nodes_by_property(&self, property: &str, value: &Value) -> Vec<NodeId>;
137
138    /// Finds nodes matching multiple property equality conditions.
139    fn find_nodes_by_properties(&self, conditions: &[(&str, Value)]) -> Vec<NodeId>;
140
141    /// Finds nodes whose property value falls within a range.
142    fn find_nodes_in_range(
143        &self,
144        property: &str,
145        min: Option<&Value>,
146        max: Option<&Value>,
147        min_inclusive: bool,
148        max_inclusive: bool,
149    ) -> Vec<NodeId>;
150
151    // --- Zone maps (skip pruning) ---
152
153    /// Returns `true` if a node property predicate might match any nodes.
154    /// Uses zone maps for early filtering.
155    fn node_property_might_match(
156        &self,
157        property: &PropertyKey,
158        op: CompareOp,
159        value: &Value,
160    ) -> bool;
161
162    /// Returns `true` if an edge property predicate might match any edges.
163    fn edge_property_might_match(
164        &self,
165        property: &PropertyKey,
166        op: CompareOp,
167        value: &Value,
168    ) -> bool;
169
170    // --- Statistics (for cost-based optimizer) ---
171
172    /// Returns the current statistics snapshot (cheap Arc clone).
173    fn statistics(&self) -> Arc<Statistics>;
174
175    /// Estimates cardinality for a label scan.
176    fn estimate_label_cardinality(&self, label: &str) -> f64;
177
178    /// Estimates average degree for an edge type.
179    fn estimate_avg_degree(&self, edge_type: &str, outgoing: bool) -> f64;
180
181    // --- Epoch ---
182
183    /// Returns the current MVCC epoch.
184    fn current_epoch(&self) -> EpochId;
185}
186
187/// Write operations for graph mutation.
188///
189/// Separated from [`GraphStore`] so read-only wrappers (snapshots, read
190/// replicas) can implement only `GraphStore`. Any mutable store is also
191/// readable via the supertrait bound.
192pub trait GraphStoreMut: GraphStore {
193    // --- Node creation ---
194
195    /// Creates a new node with the given labels.
196    fn create_node(&self, labels: &[&str]) -> NodeId;
197
198    /// Creates a new node within a transaction context.
199    fn create_node_versioned(&self, labels: &[&str], epoch: EpochId, tx_id: TxId) -> NodeId;
200
201    // --- Edge creation ---
202
203    /// Creates a new edge between two nodes.
204    fn create_edge(&self, src: NodeId, dst: NodeId, edge_type: &str) -> EdgeId;
205
206    /// Creates a new edge within a transaction context.
207    fn create_edge_versioned(
208        &self,
209        src: NodeId,
210        dst: NodeId,
211        edge_type: &str,
212        epoch: EpochId,
213        tx_id: TxId,
214    ) -> EdgeId;
215
216    /// Creates multiple edges in batch (single lock acquisition).
217    fn batch_create_edges(&self, edges: &[(NodeId, NodeId, &str)]) -> Vec<EdgeId>;
218
219    // --- Deletion ---
220
221    /// Deletes a node. Returns `true` if the node existed.
222    fn delete_node(&self, id: NodeId) -> bool;
223
224    /// Deletes a node within a transaction context. Returns `true` if the node existed.
225    fn delete_node_versioned(&self, id: NodeId, epoch: EpochId, tx_id: TxId) -> bool;
226
227    /// Deletes all edges connected to a node (DETACH DELETE).
228    fn delete_node_edges(&self, node_id: NodeId);
229
230    /// Deletes an edge. Returns `true` if the edge existed.
231    fn delete_edge(&self, id: EdgeId) -> bool;
232
233    /// Deletes an edge within a transaction context. Returns `true` if the edge existed.
234    fn delete_edge_versioned(&self, id: EdgeId, epoch: EpochId, tx_id: TxId) -> bool;
235
236    // --- Property mutation ---
237
238    /// Sets a property on a node.
239    fn set_node_property(&self, id: NodeId, key: &str, value: Value);
240
241    /// Sets a property on an edge.
242    fn set_edge_property(&self, id: EdgeId, key: &str, value: Value);
243
244    /// Removes a property from a node. Returns the previous value if it existed.
245    fn remove_node_property(&self, id: NodeId, key: &str) -> Option<Value>;
246
247    /// Removes a property from an edge. Returns the previous value if it existed.
248    fn remove_edge_property(&self, id: EdgeId, key: &str) -> Option<Value>;
249
250    // --- Label mutation ---
251
252    /// Adds a label to a node. Returns `true` if the label was new.
253    fn add_label(&self, node_id: NodeId, label: &str) -> bool;
254
255    /// Removes a label from a node. Returns `true` if the label existed.
256    fn remove_label(&self, node_id: NodeId, label: &str) -> bool;
257
258    // --- Convenience (with default implementations) ---
259
260    /// Creates a new node with labels and properties in one call.
261    ///
262    /// The default implementation calls [`create_node`](Self::create_node)
263    /// followed by [`set_node_property`](Self::set_node_property) for each
264    /// property. Implementations may override for atomicity or performance.
265    fn create_node_with_props(
266        &self,
267        labels: &[&str],
268        properties: &[(PropertyKey, Value)],
269    ) -> NodeId {
270        let id = self.create_node(labels);
271        for (key, value) in properties {
272            self.set_node_property(id, key.as_str(), value.clone());
273        }
274        id
275    }
276
277    /// Creates a new edge with properties in one call.
278    ///
279    /// The default implementation calls [`create_edge`](Self::create_edge)
280    /// followed by [`set_edge_property`](Self::set_edge_property) for each
281    /// property. Implementations may override for atomicity or performance.
282    fn create_edge_with_props(
283        &self,
284        src: NodeId,
285        dst: NodeId,
286        edge_type: &str,
287        properties: &[(PropertyKey, Value)],
288    ) -> EdgeId {
289        let id = self.create_edge(src, dst, edge_type);
290        for (key, value) in properties {
291            self.set_edge_property(id, key.as_str(), value.clone());
292        }
293        id
294    }
295}