sqlitegraph/backend.rs
1//! Backend trait bridging sqlitegraph with higher-level graph consumers.
2//!
3//! This module contains the core GraphBackend trait and redirects to modular
4//! backend implementations. SQLite-specific implementations are in the sqlite submodule.
5
6// Include the modular backend structure
7mod sqlite;
8
9// Include native backend storage layer (no GraphBackend implementation yet)
10pub mod native;
11
12// Re-export from sqlite submodule
13pub use sqlite::SqliteGraphBackend;
14
15// Re-export from native submodule
16pub use native::NativeGraphBackend;
17
18// Re-export types for external users
19pub use crate::multi_hop::ChainStep;
20#[allow(unused_imports)] // Backend trait API types for future GraphBackend implementations
21pub use sqlite::types::{BackendDirection, EdgeSpec, NeighborQuery, NodeSpec};
22
23// KV store types (re-exported for public API)
24pub use crate::backend::native::types::{KvStoreError, KvValue};
25
26/// Types of pub/sub events
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub enum PubSubEventType {
29 /// Node created or modified
30 NodeChanged = 1,
31 /// Edge created or modified
32 EdgeChanged = 2,
33 /// KV entry created, modified, or deleted
34 KvChanged = 3,
35 /// Transaction committed
36 SnapshotCommitted = 4,
37 /// All event types
38 All = 255,
39}
40
41/// Event delivered to subscribers
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum PubSubEvent {
44 /// Node created or modified
45 NodeChanged {
46 /// Node ID that changed
47 node_id: i64,
48 /// Snapshot ID when change was committed
49 snapshot_id: u64,
50 },
51 /// Edge created or modified
52 EdgeChanged {
53 /// Edge ID that changed
54 edge_id: i64,
55 /// Source node ID
56 from_node: i64,
57 /// Target node ID
58 to_node: i64,
59 /// Snapshot ID when change was committed
60 snapshot_id: u64,
61 },
62 /// KV entry changed
63 KvChanged {
64 /// Key hash for the KV entry
65 key_hash: u64,
66 /// Snapshot ID when change was committed
67 snapshot_id: u64,
68 },
69 /// Transaction committed
70 SnapshotCommitted {
71 /// Snapshot ID that was committed
72 snapshot_id: u64,
73 },
74}
75
76/// Filter for subscriptions
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
78pub struct SubscriptionFilter {
79 /// Subscribe to NodeChanged events
80 pub node_changes: bool,
81 /// Subscribe to EdgeChanged events
82 pub edge_changes: bool,
83 /// Subscribe to KvChanged events
84 pub kv_changes: bool,
85 /// Subscribe to SnapshotCommitted events
86 pub snapshot_commits: bool,
87}
88
89impl SubscriptionFilter {
90 /// Create a filter that receives all event types
91 pub fn all() -> Self {
92 Self {
93 node_changes: true,
94 edge_changes: true,
95 kv_changes: true,
96 snapshot_commits: true,
97 }
98 }
99}
100
101use crate::{
102 SqliteGraphError,
103 graph::GraphEntity,
104 pattern::{PatternMatch, PatternQuery},
105 pattern_engine::{PatternTriple, TripleMatch},
106 snapshot::SnapshotId,
107};
108///
109/// Each trait method delegates to backend-specific primitives while ensuring
110/// deterministic behavior and a single integration surface for consumers.
111///
112/// # Snapshot Isolation
113///
114/// All read operations require a `snapshot_id: SnapshotId` parameter to enforce
115/// ACID compliance. Reads only observe data committed at or before the snapshot.
116pub trait GraphBackend {
117 // Write operations (unchanged - commit returns SnapshotId in future)
118 fn insert_node(&self, node: NodeSpec) -> Result<i64, SqliteGraphError>;
119
120 /// Insert a node with a manually specified ID.
121 ///
122 /// Backends that support manual ID assignment (e.g. V3Backend, SqliteGraphBackend)
123 /// will respect this ID. Default implementation injects "id" into the node spec data.
124 fn insert_node_with_id(
125 &self,
126 mut node: NodeSpec,
127 node_id: i64,
128 ) -> Result<i64, SqliteGraphError> {
129 if let Some(obj) = node.data.as_object_mut() {
130 obj.insert("id".to_string(), serde_json::Value::Number(node_id.into()));
131 } else {
132 let mut obj = serde_json::Map::new();
133 obj.insert("id".to_string(), serde_json::Value::Number(node_id.into()));
134 node.data = serde_json::Value::Object(obj);
135 }
136 self.insert_node(node)
137 }
138
139 fn insert_edge(&self, edge: EdgeSpec) -> Result<i64, SqliteGraphError>;
140
141 /// Insert many nodes atomically.
142 ///
143 /// Default implementation loops single-insert for each item; backends
144 /// override with a single-transaction path. Returns the new node IDs in
145 /// the same order as `nodes`. All-or-nothing: if any insert fails the
146 /// caller should assume nothing was committed.
147 fn insert_nodes_bulk(&self, nodes: &[NodeSpec]) -> Result<Vec<i64>, SqliteGraphError> {
148 let mut ids = Vec::with_capacity(nodes.len());
149 for n in nodes {
150 ids.push(self.insert_node(n.clone())?);
151 }
152 Ok(ids)
153 }
154
155 /// Insert many edges atomically.
156 ///
157 /// Default implementation loops single-insert for each item; backends
158 /// override with a single-transaction path. Returns the new edge IDs in
159 /// the same order as `edges`. All-or-nothing semantics on the bulk path;
160 /// the default fallback inherits whatever atomicity the single
161 /// `insert_edge` provides.
162 fn insert_edges_bulk(&self, edges: &[EdgeSpec]) -> Result<Vec<i64>, SqliteGraphError> {
163 let mut ids = Vec::with_capacity(edges.len());
164 for e in edges {
165 ids.push(self.insert_edge(e.clone())?);
166 }
167 Ok(ids)
168 }
169
170 /// Update an existing node in place without allocating a new node ID
171 ///
172 /// This modifies the data associated with an existing node while preserving
173 /// its node_id.
174 ///
175 /// # Arguments
176 /// * `node_id` - The ID of the node to update (must exist)
177 /// * `node` - New node specification (kind, name, file_path, data)
178 ///
179 /// # Returns
180 /// The same `node_id` that was passed in, if update succeeded
181 ///
182 /// # Errors
183 /// Returns `SqliteGraphError` if:
184 /// - The node_id doesn't exist
185 /// - The update operation fails
186 /// - The backend doesn't support in-place updates
187 ///
188 /// # Behavior by Backend
189 /// - **Native**: Uses WAL to update node data in place, preserving node_id
190 /// - **SQLite**: Uses UPDATE SQL query on entities table
191 ///
192 /// # Example
193 /// ```ignore
194 /// // Create a node
195 /// let node_id = backend.insert_node(NodeSpec {
196 /// kind: "File".to_string(),
197 /// name: "main.rs".to_string(),
198 /// file_path: Some("main.rs".to_string()),
199 /// data: serde_json::json!({"hash": "abc123"}),
200 /// })?;
201 ///
202 /// // Update it - this does NOT allocate a new node_id
203 /// let updated_id = backend.update_node(node_id, NodeSpec {
204 /// kind: "File".to_string(),
205 /// name: "main.rs".to_string(),
206 /// file_path: Some("main.rs".to_string()),
207 /// data: serde_json::json!({"hash": "def456", "modified": true}),
208 /// })?;
209 ///
210 /// assert_eq!(updated_id, node_id); // Same ID!
211 /// ```
212 fn update_node(&self, node_id: i64, node: NodeSpec) -> Result<i64, SqliteGraphError>;
213
214 /// Delete an entity (node) from the graph by ID
215 ///
216 /// This removes the entity and all associated edges from the graph.
217 /// For SQLite backend: deletes from entities table and cascades to edges
218 /// For Native backend: marks node as deleted and updates adjacency indexes
219 fn delete_entity(&self, id: i64) -> Result<(), SqliteGraphError>;
220
221 /// Get all entity IDs from the graph
222 ///
223 /// Returns a vector of all node IDs currently stored in the graph.
224 /// For SQLite backend: queries all IDs from entities table
225 /// For Native backend: iterates over node store
226 fn entity_ids(&self) -> Result<Vec<i64>, SqliteGraphError>;
227
228 /// Alias for `entity_ids()` for backward compatibility with algorithms
229 ///
230 /// Default implementation delegates to `entity_ids()`.
231 fn all_entity_ids(&self) -> Result<Vec<i64>, SqliteGraphError> {
232 self.entity_ids()
233 }
234
235 /// Get outgoing neighbors for a node (convenience method)
236 ///
237 /// Default implementation uses `neighbors` with Outgoing direction.
238 fn fetch_outgoing(&self, node: i64) -> Result<Vec<i64>, SqliteGraphError> {
239 self.neighbors(
240 SnapshotId::current(),
241 node,
242 NeighborQuery {
243 direction: BackendDirection::Outgoing,
244 edge_type: None,
245 },
246 )
247 }
248
249 /// Get incoming neighbors for a node (convenience method)
250 ///
251 /// Default implementation uses `neighbors` with Incoming direction.
252 fn fetch_incoming(&self, node: i64) -> Result<Vec<i64>, SqliteGraphError> {
253 self.neighbors(
254 SnapshotId::current(),
255 node,
256 NeighborQuery {
257 direction: BackendDirection::Incoming,
258 edge_type: None,
259 },
260 )
261 }
262
263 // Read operations (require snapshot_id parameter)
264 fn get_node(&self, snapshot_id: SnapshotId, id: i64) -> Result<GraphEntity, SqliteGraphError>;
265 fn neighbors(
266 &self,
267 snapshot_id: SnapshotId,
268 node: i64,
269 query: NeighborQuery,
270 ) -> Result<Vec<i64>, SqliteGraphError>;
271 fn bfs(
272 &self,
273 snapshot_id: SnapshotId,
274 start: i64,
275 depth: u32,
276 ) -> Result<Vec<i64>, SqliteGraphError>;
277 fn shortest_path(
278 &self,
279 snapshot_id: SnapshotId,
280 start: i64,
281 end: i64,
282 ) -> Result<Option<Vec<i64>>, SqliteGraphError>;
283 fn node_degree(
284 &self,
285 snapshot_id: SnapshotId,
286 node: i64,
287 ) -> Result<(usize, usize), SqliteGraphError>;
288 fn k_hop(
289 &self,
290 snapshot_id: SnapshotId,
291 start: i64,
292 depth: u32,
293 direction: BackendDirection,
294 ) -> Result<Vec<i64>, SqliteGraphError>;
295 fn k_hop_filtered(
296 &self,
297 snapshot_id: SnapshotId,
298 start: i64,
299 depth: u32,
300 direction: BackendDirection,
301 allowed_edge_types: &[&str],
302 ) -> Result<Vec<i64>, SqliteGraphError>;
303 fn bfs_filtered(
304 &self,
305 snapshot_id: SnapshotId,
306 start: i64,
307 depth: u32,
308 direction: BackendDirection,
309 allowed_edge_types: &[&str],
310 ) -> Result<Vec<i64>, SqliteGraphError>;
311 fn shortest_path_filtered(
312 &self,
313 snapshot_id: SnapshotId,
314 start: i64,
315 end: i64,
316 allowed_edge_types: &[&str],
317 ) -> Result<Option<Vec<i64>>, SqliteGraphError>;
318 fn chain_query(
319 &self,
320 snapshot_id: SnapshotId,
321 start: i64,
322 chain: &[crate::multi_hop::ChainStep],
323 ) -> Result<Vec<i64>, SqliteGraphError>;
324 fn pattern_search(
325 &self,
326 snapshot_id: SnapshotId,
327 start: i64,
328 pattern: &PatternQuery,
329 ) -> Result<Vec<PatternMatch>, SqliteGraphError>;
330
331 /// Match triples based on a pattern triple
332 ///
333 /// Provides pattern matching capability for Cypher queries.
334 /// Default implementation returns unsupported operation error.
335 ///
336 /// # Arguments
337 /// * `pattern` - The pattern triple to match
338 ///
339 /// # Returns
340 /// Vector of triple matches with start_id, end_id, and edge_id
341 fn match_triples(&self, pattern: &PatternTriple) -> Result<Vec<TripleMatch>, SqliteGraphError> {
342 Err(SqliteGraphError::unsupported(format!(
343 "match_triples not implemented for pattern: {:?}",
344 pattern
345 )))
346 }
347
348 /// Search HNSW vector index for nearest neighbors
349 ///
350 /// Provides vector similarity search capability for Cypher queries.
351 /// Default implementation returns unsupported operation error.
352 ///
353 /// # Arguments
354 /// * `_snapshot` - Snapshot ID for read isolation
355 /// * `index_name` - Name of the HNSW index to search
356 /// * `_vector` - Query vector
357 /// * `_k` - Number of nearest neighbors to return
358 ///
359 /// # Returns
360 /// Vector of (node_id, distance) tuples for the k nearest neighbors
361 fn vector_search(
362 &self,
363 _snapshot: SnapshotId,
364 index_name: &str,
365 _vector: &[f32],
366 _k: usize,
367 ) -> Result<Vec<(i64, f32)>, SqliteGraphError> {
368 Err(SqliteGraphError::unsupported(format!(
369 "vector_search not implemented for index '{}'",
370 index_name
371 )))
372 }
373
374 /// Get reference to underlying SqliteGraph (if available)
375 ///
376 /// Returns None for non-SQLite backends (e.g., native-v3).
377 /// This method enables backend-agnostic introspection while
378 /// maintaining compatibility with SQLite-specific features.
379 ///
380 /// # Returns
381 /// Some(&SqliteGraph) for SQLite backends, None otherwise
382 fn get_graph_ref(&self) -> Option<&crate::graph::SqliteGraph> {
383 None
384 }
385
386 /// Trigger WAL checkpoint for backends that support write-ahead logging
387 ///
388 /// For Native backend with WAL: flushes WAL to graph file
389 /// For SQLite backend: executes PRAGMA wal_checkpoint(TRUNCATE)
390 /// For backends without WAL: returns Ok(()) as no-op
391 fn checkpoint(&self) -> Result<(), SqliteGraphError>;
392
393 /// Force immediate flush of WAL buffer to disk
394 ///
395 /// Ensures all buffered WAL records (including KV writes) are persisted
396 /// immediately, making them visible to other processes.
397 ///
398 /// For Native backend with WAL: flushes WAL buffer to disk
399 /// For SQLite backend: returns Ok(()) as no-op (SQLite handles sync)
400 /// For backends without WAL: returns Ok(()) as no-op
401 fn flush(&self) -> Result<(), SqliteGraphError>;
402
403 /// Create a backup of the database
404 ///
405 /// Creates a consistent snapshot of the database including all data pages.
406 ///
407 /// # Arguments
408 /// * `backup_dir` - Destination directory for backup files
409 ///
410 /// # Returns
411 /// Backup result with paths, checksum, and metadata
412 fn backup(&self, backup_dir: &std::path::Path) -> Result<BackupResult, SqliteGraphError>;
413
414 /// Export database snapshot to the specified directory
415 ///
416 /// Creates a consistent snapshot of the current database state.
417 /// For Native backend: uses snapshot format
418 /// For SQLite backend: uses JSON dump format
419 ///
420 /// # Arguments
421 /// * `export_dir` - Directory path where snapshot will be written
422 ///
423 /// # Returns
424 /// Snapshot metadata including file paths and size information
425 fn snapshot_export(
426 &self,
427 export_dir: &std::path::Path,
428 ) -> Result<SnapshotMetadata, SqliteGraphError>;
429
430 /// Import database snapshot from the specified directory
431 ///
432 /// Restores database state from a previously created snapshot.
433 /// For Native backend: loads snapshot format
434 /// For SQLite backend: loads JSON dump format
435 ///
436 /// # Arguments
437 /// * `import_dir` - Directory path containing snapshot files
438 ///
439 /// # Returns
440 /// Import metadata including number of records imported
441 fn snapshot_import(
442 &self,
443 import_dir: &std::path::Path,
444 ) -> Result<ImportMetadata, SqliteGraphError>;
445
446 /// Get a value from the KV store at the given snapshot
447 ///
448 /// # Arguments
449 /// * `snapshot_id` - Only return data committed at or before this snapshot
450 /// * `key` - Key to retrieve (arbitrary bytes)
451 ///
452 /// # Returns
453 /// The value if found and visible at snapshot, or None if not found
454 fn kv_get(
455 &self,
456 snapshot_id: SnapshotId,
457 key: &[u8],
458 ) -> Result<Option<crate::backend::native::types::KvValue>, SqliteGraphError>;
459
460 /// Set a value in the KV store
461 ///
462 /// This operation participates in the current transaction and will
463 /// be committed atomically with other graph operations.
464 ///
465 /// # Arguments
466 /// * `key` - Key to set (arbitrary bytes)
467 /// * `value` - Value to store
468 /// * `ttl_seconds` - Optional TTL in seconds (None = no expiration)
469 fn kv_set(
470 &self,
471 key: Vec<u8>,
472 value: crate::backend::native::types::KvValue,
473 ttl_seconds: Option<u64>,
474 ) -> Result<(), SqliteGraphError>;
475
476 /// Delete a value from the KV store
477 ///
478 /// This operation participates in the current transaction and will
479 /// be committed atomically with other graph operations.
480 ///
481 /// # Arguments
482 /// * `key` - Key to delete
483 fn kv_delete(&self, key: &[u8]) -> Result<(), SqliteGraphError>;
484
485 // Pub/Sub operations (in-process event notification)
486
487 /// Subscribe to graph change events
488 ///
489 /// Returns a subscriber ID and a receiver channel for events.
490 /// The receiver will receive events that match the given filter.
491 ///
492 /// # Events
493 ///
494 /// Events are emitted ONLY on transaction commit:
495 /// - `NodeChanged` - node created or modified
496 /// - `EdgeChanged` - edge created or modified
497 /// - `KvChanged` - KV entry created, modified, or deleted
498 /// - `SnapshotCommitted` - transaction committed
499 ///
500 /// # Best-Effort Delivery
501 ///
502 /// - No persistence of events
503 /// - If receiver is dropped, events are silently dropped
504 /// - If channel is full, events are silently dropped
505 /// - No delivery guarantees
506 ///
507 /// # Example
508 ///
509 /// ```ignore
510 /// let (sub_id, rx) = graph.subscribe(SubscriptionFilter::all());
511 /// // In another thread/task:
512 /// for event in rx {
513 /// match event {
514 /// PubSubEvent::NodeChanged { node_id, snapshot_id } => {
515 /// // Read node state at snapshot_id
516 /// }
517 /// _ => {}
518 /// }
519 /// }
520 /// }
521 /// ```
522 fn subscribe(
523 &self,
524 filter: SubscriptionFilter,
525 ) -> Result<(u64, std::sync::mpsc::Receiver<PubSubEvent>), SqliteGraphError>;
526
527 /// Unsubscribe from events
528 ///
529 /// Cancels the subscription and stops receiving events.
530 /// Returns true if subscription existed and was removed.
531 ///
532 /// # Arguments
533 /// * `subscriber_id` - The subscriber ID returned by subscribe()
534 fn unsubscribe(&self, subscriber_id: u64) -> Result<bool, SqliteGraphError>;
535
536 // ========== Pub/Sub Enhancement APIs (v1.4.0) ==========
537
538 /// Scan all KV entries with a given prefix
539 ///
540 /// Returns all keys that start with the given prefix, along with their values.
541 /// Results are in lexicographic order by key.
542 ///
543 /// # Arguments
544 /// * `snapshot_id` - Only return data committed at or before this snapshot
545 /// * `prefix` - Prefix to match (empty prefix returns all keys)
546 ///
547 /// # Returns
548 /// Vector of (key, value) pairs for all matching keys
549 fn kv_prefix_scan(
550 &self,
551 snapshot_id: SnapshotId,
552 prefix: &[u8],
553 ) -> Result<Vec<(Vec<u8>, crate::backend::native::types::KvValue)>, SqliteGraphError>;
554
555 /// Query all nodes with a given kind
556 ///
557 /// Returns all node IDs where the node's kind equals the given string.
558 /// Results are sorted by node ID for deterministic output.
559 ///
560 /// # Arguments
561 /// * `snapshot_id` - Only return data committed at or before this snapshot
562 /// * `kind` - Kind string to match (case-sensitive)
563 ///
564 /// # Returns
565 /// Vector of node IDs with matching kind
566 fn query_nodes_by_kind(
567 &self,
568 snapshot_id: SnapshotId,
569 kind: &str,
570 ) -> Result<Vec<i64>, SqliteGraphError>;
571
572 /// Query nodes by name pattern using glob matching
573 ///
574 /// Returns all node IDs where the node's label matches the glob pattern.
575 /// Pattern syntax:
576 /// - `*` matches any sequence of characters
577 /// - `?` matches exactly one character
578 ///
579 /// # Arguments
580 /// * `snapshot_id` - Only return data committed at or before this snapshot
581 /// * `pattern` - Glob pattern to match against node labels
582 ///
583 /// # Returns
584 /// Vector of node IDs with matching labels
585 fn query_nodes_by_name_pattern(
586 &self,
587 snapshot_id: SnapshotId,
588 pattern: &str,
589 ) -> Result<Vec<i64>, SqliteGraphError>;
590}
591
592/// Metadata returned by snapshot export operations
593#[derive(Debug, Clone)]
594pub struct SnapshotMetadata {
595 /// Path to the snapshot file
596 pub snapshot_path: std::path::PathBuf,
597 /// Snapshot size in bytes
598 pub size_bytes: u64,
599 /// Number of entities in snapshot
600 pub entity_count: u64,
601 /// Number of edges in snapshot
602 pub edge_count: u64,
603}
604
605/// Metadata returned by snapshot import operations
606#[derive(Debug, Clone)]
607pub struct ImportMetadata {
608 /// Path to the imported snapshot
609 pub snapshot_path: std::path::PathBuf,
610 /// Number of entities imported
611 pub entities_imported: u64,
612 /// Number of edges imported
613 pub edges_imported: u64,
614}
615
616/// Result returned by backup operations
617#[derive(Debug, Clone)]
618pub struct BackupResult {
619 /// Path to backup snapshot file
620 pub snapshot_path: std::path::PathBuf,
621
622 /// Path to backup manifest file
623 pub manifest_path: std::path::PathBuf,
624
625 /// Backup size in bytes
626 pub size_bytes: u64,
627
628 /// Backup checksum
629 pub checksum: u64,
630
631 /// Number of records in backup
632 pub record_count: u64,
633
634 /// Backup duration in seconds
635 pub duration_secs: f64,
636
637 /// Backup timestamp (Unix epoch)
638 pub timestamp: u64,
639
640 /// Whether checkpoint was performed before backup
641 pub checkpoint_performed: bool,
642}
643
644/// Reference implementation for GraphBackend trait that works with references.
645impl<B> GraphBackend for &B
646where
647 B: GraphBackend + ?Sized,
648{
649 fn insert_node(&self, node: NodeSpec) -> Result<i64, SqliteGraphError> {
650 (*self).insert_node(node)
651 }
652
653 fn insert_node_with_id(&self, node: NodeSpec, node_id: i64) -> Result<i64, SqliteGraphError> {
654 (*self).insert_node_with_id(node, node_id)
655 }
656
657 fn get_node(&self, snapshot_id: SnapshotId, id: i64) -> Result<GraphEntity, SqliteGraphError> {
658 (*self).get_node(snapshot_id, id)
659 }
660
661 fn insert_edge(&self, edge: EdgeSpec) -> Result<i64, SqliteGraphError> {
662 (*self).insert_edge(edge)
663 }
664
665 fn update_node(&self, node_id: i64, node: NodeSpec) -> Result<i64, SqliteGraphError> {
666 (*self).update_node(node_id, node)
667 }
668
669 fn delete_entity(&self, id: i64) -> Result<(), SqliteGraphError> {
670 (*self).delete_entity(id)
671 }
672
673 fn entity_ids(&self) -> Result<Vec<i64>, SqliteGraphError> {
674 (*self).entity_ids()
675 }
676
677 fn insert_nodes_bulk(&self, nodes: &[NodeSpec]) -> Result<Vec<i64>, SqliteGraphError> {
678 (*self).insert_nodes_bulk(nodes)
679 }
680
681 fn insert_edges_bulk(&self, edges: &[EdgeSpec]) -> Result<Vec<i64>, SqliteGraphError> {
682 (*self).insert_edges_bulk(edges)
683 }
684
685 fn neighbors(
686 &self,
687 snapshot_id: SnapshotId,
688 node: i64,
689 query: NeighborQuery,
690 ) -> Result<Vec<i64>, SqliteGraphError> {
691 (*self).neighbors(snapshot_id, node, query)
692 }
693
694 fn bfs(
695 &self,
696 snapshot_id: SnapshotId,
697 start: i64,
698 depth: u32,
699 ) -> Result<Vec<i64>, SqliteGraphError> {
700 (*self).bfs(snapshot_id, start, depth)
701 }
702
703 fn shortest_path(
704 &self,
705 snapshot_id: SnapshotId,
706 start: i64,
707 end: i64,
708 ) -> Result<Option<Vec<i64>>, SqliteGraphError> {
709 (*self).shortest_path(snapshot_id, start, end)
710 }
711
712 fn node_degree(
713 &self,
714 snapshot_id: SnapshotId,
715 node: i64,
716 ) -> Result<(usize, usize), SqliteGraphError> {
717 (*self).node_degree(snapshot_id, node)
718 }
719
720 fn k_hop(
721 &self,
722 snapshot_id: SnapshotId,
723 start: i64,
724 depth: u32,
725 direction: BackendDirection,
726 ) -> Result<Vec<i64>, SqliteGraphError> {
727 (*self).k_hop(snapshot_id, start, depth, direction)
728 }
729
730 fn k_hop_filtered(
731 &self,
732 snapshot_id: SnapshotId,
733 start: i64,
734 depth: u32,
735 direction: BackendDirection,
736 allowed_edge_types: &[&str],
737 ) -> Result<Vec<i64>, SqliteGraphError> {
738 (*self).k_hop_filtered(snapshot_id, start, depth, direction, allowed_edge_types)
739 }
740
741 fn bfs_filtered(
742 &self,
743 snapshot_id: SnapshotId,
744 start: i64,
745 depth: u32,
746 direction: BackendDirection,
747 allowed_edge_types: &[&str],
748 ) -> Result<Vec<i64>, SqliteGraphError> {
749 (*self).bfs_filtered(snapshot_id, start, depth, direction, allowed_edge_types)
750 }
751
752 fn shortest_path_filtered(
753 &self,
754 snapshot_id: SnapshotId,
755 start: i64,
756 end: i64,
757 allowed_edge_types: &[&str],
758 ) -> Result<Option<Vec<i64>>, SqliteGraphError> {
759 (*self).shortest_path_filtered(snapshot_id, start, end, allowed_edge_types)
760 }
761
762 fn chain_query(
763 &self,
764 snapshot_id: SnapshotId,
765 start: i64,
766 chain: &[crate::multi_hop::ChainStep],
767 ) -> Result<Vec<i64>, SqliteGraphError> {
768 (*self).chain_query(snapshot_id, start, chain)
769 }
770
771 fn pattern_search(
772 &self,
773 snapshot_id: SnapshotId,
774 start: i64,
775 pattern: &PatternQuery,
776 ) -> Result<Vec<PatternMatch>, SqliteGraphError> {
777 (*self).pattern_search(snapshot_id, start, pattern)
778 }
779
780 fn checkpoint(&self) -> Result<(), SqliteGraphError> {
781 (*self).checkpoint()
782 }
783
784 fn flush(&self) -> Result<(), SqliteGraphError> {
785 (*self).flush()
786 }
787
788 fn backup(&self, backup_dir: &std::path::Path) -> Result<BackupResult, SqliteGraphError> {
789 (*self).backup(backup_dir)
790 }
791
792 fn snapshot_export(
793 &self,
794 export_dir: &std::path::Path,
795 ) -> Result<SnapshotMetadata, SqliteGraphError> {
796 (*self).snapshot_export(export_dir)
797 }
798
799 fn snapshot_import(
800 &self,
801 import_dir: &std::path::Path,
802 ) -> Result<ImportMetadata, SqliteGraphError> {
803 (*self).snapshot_import(import_dir)
804 }
805
806 fn kv_get(
807 &self,
808 snapshot_id: SnapshotId,
809 key: &[u8],
810 ) -> Result<Option<crate::backend::native::types::KvValue>, SqliteGraphError> {
811 (*self).kv_get(snapshot_id, key)
812 }
813
814 fn kv_set(
815 &self,
816 key: Vec<u8>,
817 value: crate::backend::native::types::KvValue,
818 ttl_seconds: Option<u64>,
819 ) -> Result<(), SqliteGraphError> {
820 (*self).kv_set(key, value, ttl_seconds)
821 }
822
823 fn kv_delete(&self, key: &[u8]) -> Result<(), SqliteGraphError> {
824 (*self).kv_delete(key)
825 }
826
827 fn subscribe(
828 &self,
829 filter: SubscriptionFilter,
830 ) -> Result<(u64, std::sync::mpsc::Receiver<PubSubEvent>), SqliteGraphError> {
831 (*self).subscribe(filter)
832 }
833
834 fn unsubscribe(&self, subscriber_id: u64) -> Result<bool, SqliteGraphError> {
835 (*self).unsubscribe(subscriber_id)
836 }
837
838 fn kv_prefix_scan(
839 &self,
840 snapshot_id: SnapshotId,
841 prefix: &[u8],
842 ) -> Result<Vec<(Vec<u8>, crate::backend::native::types::KvValue)>, SqliteGraphError> {
843 (*self).kv_prefix_scan(snapshot_id, prefix)
844 }
845
846 fn query_nodes_by_kind(
847 &self,
848 snapshot_id: SnapshotId,
849 kind: &str,
850 ) -> Result<Vec<i64>, SqliteGraphError> {
851 (*self).query_nodes_by_kind(snapshot_id, kind)
852 }
853
854 fn query_nodes_by_name_pattern(
855 &self,
856 snapshot_id: SnapshotId,
857 pattern: &str,
858 ) -> Result<Vec<i64>, SqliteGraphError> {
859 (*self).query_nodes_by_name_pattern(snapshot_id, pattern)
860 }
861
862 fn match_triples(&self, pattern: &PatternTriple) -> Result<Vec<TripleMatch>, SqliteGraphError> {
863 (*self).match_triples(pattern)
864 }
865
866 fn vector_search(
867 &self,
868 snapshot: SnapshotId,
869 index_name: &str,
870 vector: &[f32],
871 k: usize,
872 ) -> Result<Vec<(i64, f32)>, SqliteGraphError> {
873 (*self).vector_search(snapshot, index_name, vector, k)
874 }
875
876 fn get_graph_ref(&self) -> Option<&crate::graph::SqliteGraph> {
877 (*self).get_graph_ref()
878 }
879}
880
881/// AsyncGraphBackend defines non-blocking graph operations.
882/// Supports async/await natively using Rust 2024 edition features.
883pub trait AsyncGraphBackend: Send + Sync {
884 /// Retrieve a node asynchronously from the snapshot.
885 fn get_node(
886 &self,
887 snapshot_id: SnapshotId,
888 id: i64,
889 ) -> impl std::future::Future<Output = Result<GraphEntity, SqliteGraphError>> + Send;
890
891 /// Retrieve neighbor node IDs for a given source node asynchronously.
892 fn neighbors(
893 &self,
894 snapshot_id: SnapshotId,
895 node: i64,
896 query: NeighborQuery,
897 ) -> impl std::future::Future<Output = Result<Vec<i64>, SqliteGraphError>> + Send;
898}