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