drasi_core/interface/index_backend.rs
1// Copyright 2025 The Drasi Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Index Backend Plugin Trait
16//!
17//! This module defines the `IndexBackendPlugin` trait that external index backends
18//! (like RocksDB, Garnet/Redis) must implement to integrate with Drasi.
19//!
20//! # Architecture
21//!
22//! The index plugin system follows pure dependency inversion:
23//! - **Core** provides index traits (`ElementIndex`, `ResultIndex`, etc.) and a default
24//! in-memory implementation
25//! - **Lib** uses this plugin trait but has no knowledge of specific implementations
26//! - **External plugins** (in `components/indexes/`) implement this trait
27//! - **Applications** optionally inject plugins; if none provided, the in-memory default is used
28
29use async_trait::async_trait;
30use std::fmt;
31use std::sync::Arc;
32
33use super::{
34 CheckpointStore, ElementArchiveIndex, ElementIndex, FutureQueue, IndexError, ResultIndex,
35 SessionControl,
36};
37
38/// Set of indexes for a query.
39///
40/// Groups the index types and session control needed for query evaluation into
41/// a single unit.
42/// This enables backends to create all indexes from a shared underlying resource
43/// (e.g., a single RocksDB instance or Redis connection).
44pub struct IndexSet {
45 /// Element index for storing graph elements
46 pub element_index: Arc<dyn ElementIndex>,
47 /// Archive index for storing historical elements (for past() function)
48 pub archive_index: Arc<dyn ElementArchiveIndex>,
49 /// Result index for storing query results
50 pub result_index: Arc<dyn ResultIndex>,
51 /// Future queue for temporal queries
52 pub future_queue: Arc<dyn FutureQueue>,
53 /// Session control for atomic transaction lifecycle
54 pub session_control: Arc<dyn SessionControl>,
55}
56
57impl fmt::Debug for IndexSet {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 f.debug_struct("IndexSet")
60 .field("element_index", &"<trait object>")
61 .field("archive_index", &"<trait object>")
62 .field("result_index", &"<trait object>")
63 .field("future_queue", &"<trait object>")
64 .field("session_control", &"<trait object>")
65 .finish()
66 }
67}
68
69/// Result of [`IndexBackendPlugin::create_indexes`].
70///
71/// Bundles the [`IndexSet`] together with an optional [`CheckpointStore`]
72/// that shares the same underlying session state. Persistent backends return
73/// `Some(store)`; volatile (in-memory) backends return `None`.
74///
75/// The store's `stage_checkpoint` calls land in the same database transaction
76/// as index updates because both are derived from the same `SessionControl` /
77/// session state instance — that is why the plugin returns them together
78/// rather than via two separate calls.
79pub struct CreatedIndexes {
80 /// The set of indexes for the query.
81 pub set: IndexSet,
82 /// Atomic checkpoint store paired with the set's session state.
83 /// `None` for volatile backends (no persistent storage to checkpoint into).
84 pub checkpoint_store: Option<Arc<dyn CheckpointStore>>,
85}
86
87impl fmt::Debug for CreatedIndexes {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 f.debug_struct("CreatedIndexes")
90 .field("set", &self.set)
91 .field(
92 "checkpoint_store",
93 &self.checkpoint_store.as_ref().map(|_| "<trait object>"),
94 )
95 .finish()
96 }
97}
98
99/// Plugin trait for external index storage backends.
100///
101/// Each storage backend (RocksDB, Garnet, etc.) implements this trait to provide
102/// all index types needed for query evaluation from a single shared backend instance.
103///
104/// # Thread Safety
105///
106/// Implementations must be `Send + Sync` to allow use across async tasks.
107///
108/// # Example
109///
110/// ```ignore
111/// use drasi_core::interface::{CreatedIndexes, IndexBackendPlugin, IndexError};
112///
113/// pub struct MyIndexProvider {
114/// // configuration fields
115/// }
116///
117/// #[async_trait]
118/// impl IndexBackendPlugin for MyIndexProvider {
119/// async fn create_indexes(&self, query_id: &str) -> Result<CreatedIndexes, IndexError> {
120/// // Create and return all indexes (and an optional checkpoint store)
121/// // from a shared backend instance
122/// }
123/// fn is_volatile(&self) -> bool { false }
124/// }
125/// ```
126#[async_trait]
127pub trait IndexBackendPlugin: Send + Sync {
128 /// Create all indexes (and an optional checkpoint store) for a query
129 /// from a single shared backend instance.
130 ///
131 /// This method creates the element index, archive index, result index,
132 /// future queue, and session control backed by a shared storage resource
133 /// (e.g., a single RocksDB database or Redis connection). This reduces
134 /// resource overhead and enables cross-index atomic transactions.
135 ///
136 /// Persistent backends additionally return a [`CheckpointStore`] that
137 /// shares the same session state as the returned `SessionControl`, so
138 /// `stage_checkpoint` writes land in the same database transaction as
139 /// index updates. Volatile backends return `checkpoint_store: None`.
140 async fn create_indexes(&self, query_id: &str) -> Result<CreatedIndexes, IndexError>;
141
142 /// Returns true if this backend is volatile (data lost on restart).
143 ///
144 /// Volatile backends (like in-memory) require re-bootstrapping after restart,
145 /// while persistent backends (like RocksDB) retain data.
146 fn is_volatile(&self) -> bool;
147}