Skip to main content

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)
24#[cfg(feature = "native-v2")]
25pub use crate::backend::native::v2::kv_store::types::{KvStoreError, KvValue};
26
27// Pub/Sub types (re-exported for public API)
28#[cfg(feature = "native-v2")]
29pub use crate::backend::native::v2::pubsub::{PubSubEvent, SubscriptionFilter};
30
31use crate::{
32    SqliteGraphError,
33    graph::GraphEntity,
34    pattern::{PatternMatch, PatternQuery},
35    snapshot::SnapshotId,
36};
37
38/// Backend trait defining the interface for graph database backends.
39///
40/// Each trait method delegates to backend-specific primitives while ensuring
41/// deterministic behavior and a single integration surface for consumers.
42///
43/// # Snapshot Isolation
44///
45/// All read operations require a `snapshot_id: SnapshotId` parameter to enforce
46/// ACID compliance. Reads only observe data committed at or before the snapshot.
47pub trait GraphBackend {
48    // Write operations (unchanged - commit returns SnapshotId in future)
49    fn insert_node(&self, node: NodeSpec) -> Result<i64, SqliteGraphError>;
50    fn insert_edge(&self, edge: EdgeSpec) -> Result<i64, SqliteGraphError>;
51
52    // Read operations (require snapshot_id parameter)
53    fn get_node(&self, snapshot_id: SnapshotId, id: i64) -> Result<GraphEntity, SqliteGraphError>;
54    fn neighbors(
55        &self,
56        snapshot_id: SnapshotId,
57        node: i64,
58        query: NeighborQuery,
59    ) -> Result<Vec<i64>, SqliteGraphError>;
60    fn bfs(
61        &self,
62        snapshot_id: SnapshotId,
63        start: i64,
64        depth: u32,
65    ) -> Result<Vec<i64>, SqliteGraphError>;
66    fn shortest_path(
67        &self,
68        snapshot_id: SnapshotId,
69        start: i64,
70        end: i64,
71    ) -> Result<Option<Vec<i64>>, SqliteGraphError>;
72    fn node_degree(
73        &self,
74        snapshot_id: SnapshotId,
75        node: i64,
76    ) -> Result<(usize, usize), SqliteGraphError>;
77    fn k_hop(
78        &self,
79        snapshot_id: SnapshotId,
80        start: i64,
81        depth: u32,
82        direction: BackendDirection,
83    ) -> Result<Vec<i64>, SqliteGraphError>;
84    fn k_hop_filtered(
85        &self,
86        snapshot_id: SnapshotId,
87        start: i64,
88        depth: u32,
89        direction: BackendDirection,
90        allowed_edge_types: &[&str],
91    ) -> Result<Vec<i64>, SqliteGraphError>;
92    fn chain_query(
93        &self,
94        snapshot_id: SnapshotId,
95        start: i64,
96        chain: &[crate::multi_hop::ChainStep],
97    ) -> Result<Vec<i64>, SqliteGraphError>;
98    fn pattern_search(
99        &self,
100        snapshot_id: SnapshotId,
101        start: i64,
102        pattern: &PatternQuery,
103    ) -> Result<Vec<PatternMatch>, SqliteGraphError>;
104
105    /// Trigger WAL checkpoint for backends that support write-ahead logging
106    ///
107    /// For Native backend with WAL: flushes WAL to graph file
108    /// For SQLite backend: executes PRAGMA wal_checkpoint(TRUNCATE)
109    /// For backends without WAL: returns Ok(()) as no-op
110    fn checkpoint(&self) -> Result<(), SqliteGraphError>;
111
112    /// Create a backup of the database
113    ///
114    /// Creates a consistent snapshot of the database including all data pages.
115    /// For Native V2 backend, optionally checkpoints before backup to ensure
116    /// WAL is applied and snapshot is consistent.
117    ///
118    /// # Arguments
119    /// * `backup_dir` - Destination directory for backup files
120    ///
121    /// # Returns
122    /// Backup result with paths, checksum, and metadata
123    fn backup(&self, backup_dir: &std::path::Path) -> Result<BackupResult, SqliteGraphError>;
124
125    /// Export database snapshot to the specified directory
126    ///
127    /// Creates a consistent snapshot of the current database state.
128    /// For Native backend: uses V2 snapshot format
129    /// For SQLite backend: uses JSON dump format
130    ///
131    /// # Arguments
132    /// * `export_dir` - Directory path where snapshot will be written
133    ///
134    /// # Returns
135    /// Snapshot metadata including file paths and size information
136    fn snapshot_export(
137        &self,
138        export_dir: &std::path::Path,
139    ) -> Result<SnapshotMetadata, SqliteGraphError>;
140
141    /// Import database snapshot from the specified directory
142    ///
143    /// Restores database state from a previously created snapshot.
144    /// For Native backend: loads V2 snapshot format
145    /// For SQLite backend: loads JSON dump format
146    ///
147    /// # Arguments
148    /// * `import_dir` - Directory path containing snapshot files
149    ///
150    /// # Returns
151    /// Import metadata including number of records imported
152    fn snapshot_import(
153        &self,
154        import_dir: &std::path::Path,
155    ) -> Result<ImportMetadata, SqliteGraphError>;
156
157    /// Get a value from the KV store at the given snapshot
158    ///
159    /// # Arguments
160    /// * `snapshot_id` - Only return data committed at or before this snapshot
161    /// * `key` - Key to retrieve (arbitrary bytes)
162    ///
163    /// # Returns
164    /// The value if found and visible at snapshot, or None if not found
165    #[cfg(feature = "native-v2")]
166    fn kv_get(
167        &self,
168        snapshot_id: SnapshotId,
169        key: &[u8],
170    ) -> Result<Option<crate::backend::native::v2::kv_store::types::KvValue>, SqliteGraphError>;
171
172    /// Set a value in the KV store
173    ///
174    /// This operation participates in the current transaction and will
175    /// be committed atomically with other graph operations.
176    ///
177    /// # Arguments
178    /// * `key` - Key to set (arbitrary bytes)
179    /// * `value` - Value to store
180    /// * `ttl_seconds` - Optional TTL in seconds (None = no expiration)
181    #[cfg(feature = "native-v2")]
182    fn kv_set(
183        &self,
184        key: Vec<u8>,
185        value: crate::backend::native::v2::kv_store::types::KvValue,
186        ttl_seconds: Option<u64>,
187    ) -> Result<(), SqliteGraphError>;
188
189    /// Delete a value from the KV store
190    ///
191    /// This operation participates in the current transaction and will
192    /// be committed atomically with other graph operations.
193    ///
194    /// # Arguments
195    /// * `key` - Key to delete
196    #[cfg(feature = "native-v2")]
197    fn kv_delete(&self, key: &[u8]) -> Result<(), SqliteGraphError>;
198
199    // Pub/Sub operations (in-process event notification)
200
201    /// Subscribe to graph change events
202    ///
203    /// Returns a subscriber ID and a receiver channel for events.
204    /// The receiver will receive events that match the given filter.
205    ///
206    /// # Events
207    ///
208    /// Events are emitted ONLY on transaction commit:
209    /// - `NodeChanged` - node created or modified
210    /// - `EdgeChanged` - edge created or modified
211    /// - `KVChanged` - KV entry created, modified, or deleted
212    /// - `SnapshotCommitted` - transaction committed
213    ///
214    /// # Best-Effort Delivery
215    ///
216    /// - No persistence of events
217    /// - If receiver is dropped, events are silently dropped
218    /// - If channel is full, events are silently dropped
219    /// - No delivery guarantees
220    ///
221    /// # Example
222    ///
223    /// ```ignore
224    /// let (sub_id, rx) = graph.subscribe(SubscriptionFilter::all());
225    /// // In another thread/task:
226    /// for event in rx {
227    ///     match event {
228    ///         PubSubEvent::NodeChanged { node_id, snapshot_id } => {
229    ///             // Read node state at snapshot_id
230    ///         }
231    ///         _ => {}
232    ///     }
233    /// }
234    /// }
235    /// ```
236    #[cfg(feature = "native-v2")]
237    fn subscribe(
238        &self,
239        filter: SubscriptionFilter,
240    ) -> Result<(u64, std::sync::mpsc::Receiver<PubSubEvent>), SqliteGraphError>;
241
242    /// Unsubscribe from events
243    ///
244    /// Cancels the subscription and stops receiving events.
245    /// Returns true if subscription existed and was removed.
246    ///
247    /// # Arguments
248    /// * `subscriber_id` - The subscriber ID returned by subscribe()
249    #[cfg(feature = "native-v2")]
250    fn unsubscribe(&self, subscriber_id: u64) -> Result<bool, SqliteGraphError>;
251
252    // ========== Pub/Sub Enhancement APIs (v1.4.0) ==========
253
254    /// Scan all KV entries with a given prefix
255    ///
256    /// Returns all keys that start with the given prefix, along with their values.
257    /// Results are in lexicographic order by key.
258    ///
259    /// # Arguments
260    /// * `snapshot_id` - Only return data committed at or before this snapshot
261    /// * `prefix` - Prefix to match (empty prefix returns all keys)
262    ///
263    /// # Returns
264    /// Vector of (key, value) pairs for all matching keys
265    #[cfg(feature = "native-v2")]
266    fn kv_prefix_scan(
267        &self,
268        snapshot_id: SnapshotId,
269        prefix: &[u8],
270    ) -> Result<Vec<(Vec<u8>, crate::backend::native::v2::kv_store::types::KvValue)>, SqliteGraphError>;
271
272    /// Query all nodes with a given kind
273    ///
274    /// Returns all node IDs where the node's kind equals the given string.
275    /// Results are sorted by node ID for deterministic output.
276    ///
277    /// # Arguments
278    /// * `snapshot_id` - Only return data committed at or before this snapshot
279    /// * `kind` - Kind string to match (case-sensitive)
280    ///
281    /// # Returns
282    /// Vector of node IDs with matching kind
283    fn query_nodes_by_kind(
284        &self,
285        snapshot_id: SnapshotId,
286        kind: &str,
287    ) -> Result<Vec<i64>, SqliteGraphError>;
288
289    /// Query nodes by name pattern using glob matching
290    ///
291    /// Returns all node IDs where the node's label matches the glob pattern.
292    /// Pattern syntax:
293    /// - `*` matches any sequence of characters
294    /// - `?` matches exactly one character
295    ///
296    /// # Arguments
297    /// * `snapshot_id` - Only return data committed at or before this snapshot
298    /// * `pattern` - Glob pattern to match against node labels
299    ///
300    /// # Returns
301    /// Vector of node IDs with matching labels
302    fn query_nodes_by_name_pattern(
303        &self,
304        snapshot_id: SnapshotId,
305        pattern: &str,
306    ) -> Result<Vec<i64>, SqliteGraphError>;
307}
308
309/// Metadata returned by snapshot export operations
310#[derive(Debug, Clone)]
311pub struct SnapshotMetadata {
312    /// Path to the snapshot file
313    pub snapshot_path: std::path::PathBuf,
314    /// Snapshot size in bytes
315    pub size_bytes: u64,
316    /// Number of entities in snapshot
317    pub entity_count: u64,
318    /// Number of edges in snapshot
319    pub edge_count: u64,
320}
321
322/// Metadata returned by snapshot import operations
323#[derive(Debug, Clone)]
324pub struct ImportMetadata {
325    /// Path to the imported snapshot
326    pub snapshot_path: std::path::PathBuf,
327    /// Number of entities imported
328    pub entities_imported: u64,
329    /// Number of edges imported
330    pub edges_imported: u64,
331}
332
333/// Result returned by backup operations
334#[derive(Debug, Clone)]
335pub struct BackupResult {
336    /// Path to backup snapshot file
337    pub snapshot_path: std::path::PathBuf,
338
339    /// Path to backup manifest file
340    pub manifest_path: std::path::PathBuf,
341
342    /// Backup size in bytes
343    pub size_bytes: u64,
344
345    /// Backup checksum
346    pub checksum: u64,
347
348    /// Number of records in backup
349    pub record_count: u64,
350
351    /// Backup duration in seconds
352    pub duration_secs: f64,
353
354    /// Backup timestamp (Unix epoch)
355    pub timestamp: u64,
356
357    /// Whether checkpoint was performed before backup
358    pub checkpoint_performed: bool,
359}
360
361/// Reference implementation for GraphBackend trait that works with references.
362impl<B> GraphBackend for &B
363where
364    B: GraphBackend + ?Sized,
365{
366    fn insert_node(&self, node: NodeSpec) -> Result<i64, SqliteGraphError> {
367        (*self).insert_node(node)
368    }
369
370    fn get_node(&self, snapshot_id: SnapshotId, id: i64) -> Result<GraphEntity, SqliteGraphError> {
371        (*self).get_node(snapshot_id, id)
372    }
373
374    fn insert_edge(&self, edge: EdgeSpec) -> Result<i64, SqliteGraphError> {
375        (*self).insert_edge(edge)
376    }
377
378    fn neighbors(
379        &self,
380        snapshot_id: SnapshotId,
381        node: i64,
382        query: NeighborQuery,
383    ) -> Result<Vec<i64>, SqliteGraphError> {
384        (*self).neighbors(snapshot_id, node, query)
385    }
386
387    fn bfs(
388        &self,
389        snapshot_id: SnapshotId,
390        start: i64,
391        depth: u32,
392    ) -> Result<Vec<i64>, SqliteGraphError> {
393        (*self).bfs(snapshot_id, start, depth)
394    }
395
396    fn shortest_path(
397        &self,
398        snapshot_id: SnapshotId,
399        start: i64,
400        end: i64,
401    ) -> Result<Option<Vec<i64>>, SqliteGraphError> {
402        (*self).shortest_path(snapshot_id, start, end)
403    }
404
405    fn node_degree(
406        &self,
407        snapshot_id: SnapshotId,
408        node: i64,
409    ) -> Result<(usize, usize), SqliteGraphError> {
410        (*self).node_degree(snapshot_id, node)
411    }
412
413    fn k_hop(
414        &self,
415        snapshot_id: SnapshotId,
416        start: i64,
417        depth: u32,
418        direction: BackendDirection,
419    ) -> Result<Vec<i64>, SqliteGraphError> {
420        (*self).k_hop(snapshot_id, start, depth, direction)
421    }
422
423    fn k_hop_filtered(
424        &self,
425        snapshot_id: SnapshotId,
426        start: i64,
427        depth: u32,
428        direction: BackendDirection,
429        allowed_edge_types: &[&str],
430    ) -> Result<Vec<i64>, SqliteGraphError> {
431        (*self).k_hop_filtered(snapshot_id, start, depth, direction, allowed_edge_types)
432    }
433
434    fn chain_query(
435        &self,
436        snapshot_id: SnapshotId,
437        start: i64,
438        chain: &[crate::multi_hop::ChainStep],
439    ) -> Result<Vec<i64>, SqliteGraphError> {
440        (*self).chain_query(snapshot_id, start, chain)
441    }
442
443    fn pattern_search(
444        &self,
445        snapshot_id: SnapshotId,
446        start: i64,
447        pattern: &PatternQuery,
448    ) -> Result<Vec<PatternMatch>, SqliteGraphError> {
449        (*self).pattern_search(snapshot_id, start, pattern)
450    }
451
452    fn checkpoint(&self) -> Result<(), SqliteGraphError> {
453        (*self).checkpoint()
454    }
455
456    fn backup(&self, backup_dir: &std::path::Path) -> Result<BackupResult, SqliteGraphError> {
457        (*self).backup(backup_dir)
458    }
459
460    fn snapshot_export(
461        &self,
462        export_dir: &std::path::Path,
463    ) -> Result<SnapshotMetadata, SqliteGraphError> {
464        (*self).snapshot_export(export_dir)
465    }
466
467    fn snapshot_import(
468        &self,
469        import_dir: &std::path::Path,
470    ) -> Result<ImportMetadata, SqliteGraphError> {
471        (*self).snapshot_import(import_dir)
472    }
473
474    #[cfg(feature = "native-v2")]
475    fn kv_get(
476        &self,
477        snapshot_id: SnapshotId,
478        key: &[u8],
479    ) -> Result<Option<crate::backend::native::v2::kv_store::types::KvValue>, SqliteGraphError>
480    {
481        (*self).kv_get(snapshot_id, key)
482    }
483
484    #[cfg(feature = "native-v2")]
485    fn kv_set(
486        &self,
487        key: Vec<u8>,
488        value: crate::backend::native::v2::kv_store::types::KvValue,
489        ttl_seconds: Option<u64>,
490    ) -> Result<(), SqliteGraphError> {
491        (*self).kv_set(key, value, ttl_seconds)
492    }
493
494    #[cfg(feature = "native-v2")]
495    fn kv_delete(&self, key: &[u8]) -> Result<(), SqliteGraphError> {
496        (*self).kv_delete(key)
497    }
498
499    #[cfg(feature = "native-v2")]
500    fn subscribe(
501        &self,
502        filter: SubscriptionFilter,
503    ) -> Result<(u64, std::sync::mpsc::Receiver<PubSubEvent>), SqliteGraphError> {
504        (*self).subscribe(filter)
505    }
506
507    #[cfg(feature = "native-v2")]
508    fn unsubscribe(&self, subscriber_id: u64) -> Result<bool, SqliteGraphError> {
509        (*self).unsubscribe(subscriber_id)
510    }
511
512    #[cfg(feature = "native-v2")]
513    fn kv_prefix_scan(
514        &self,
515        snapshot_id: SnapshotId,
516        prefix: &[u8],
517    ) -> Result<Vec<(Vec<u8>, crate::backend::native::v2::kv_store::types::KvValue)>, SqliteGraphError> {
518        (*self).kv_prefix_scan(snapshot_id, prefix)
519    }
520
521    fn query_nodes_by_kind(
522        &self,
523        snapshot_id: SnapshotId,
524        kind: &str,
525    ) -> Result<Vec<i64>, SqliteGraphError> {
526        (*self).query_nodes_by_kind(snapshot_id, kind)
527    }
528
529    fn query_nodes_by_name_pattern(
530        &self,
531        snapshot_id: SnapshotId,
532        pattern: &str,
533    ) -> Result<Vec<i64>, SqliteGraphError> {
534        (*self).query_nodes_by_name_pattern(snapshot_id, pattern)
535    }
536}