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, LiveResultsWriter,
35 OutboxWriter, ResultIndex, 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 /// Outbox writer for persisting query results for reaction replay.
86 /// `None` for volatile backends or when outbox persistence is not needed.
87 pub outbox_writer: Option<Arc<dyn OutboxWriter>>,
88 /// Live results writer for persisting the current result snapshot.
89 /// `None` for volatile backends or when live result persistence is not needed.
90 pub live_results_writer: Option<Arc<dyn LiveResultsWriter>>,
91}
92
93impl fmt::Debug for CreatedIndexes {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 f.debug_struct("CreatedIndexes")
96 .field("set", &self.set)
97 .field(
98 "checkpoint_store",
99 &self.checkpoint_store.as_ref().map(|_| "<trait object>"),
100 )
101 .field(
102 "outbox_writer",
103 &self.outbox_writer.as_ref().map(|_| "<trait object>"),
104 )
105 .field(
106 "live_results_writer",
107 &self.live_results_writer.as_ref().map(|_| "<trait object>"),
108 )
109 .finish()
110 }
111}
112
113/// Plugin trait for external index storage backends.
114///
115/// Each storage backend (RocksDB, Garnet, etc.) implements this trait to provide
116/// all index types needed for query evaluation from a single shared backend instance.
117///
118/// # Thread Safety
119///
120/// Implementations must be `Send + Sync` to allow use across async tasks.
121///
122/// # Example
123///
124/// ```ignore
125/// use drasi_core::interface::{CreatedIndexes, IndexBackendPlugin, IndexError};
126///
127/// pub struct MyIndexProvider {
128/// // configuration fields
129/// }
130///
131/// #[async_trait]
132/// impl IndexBackendPlugin for MyIndexProvider {
133/// async fn create_indexes(&self, query_id: &str) -> Result<CreatedIndexes, IndexError> {
134/// // Create and return all indexes (and an optional checkpoint store)
135/// // from a shared backend instance
136/// }
137/// fn is_volatile(&self) -> bool { false }
138/// }
139/// ```
140#[async_trait]
141pub trait IndexBackendPlugin: Send + Sync {
142 /// Create all indexes (and an optional checkpoint store) for a query
143 /// from a single shared backend instance.
144 ///
145 /// This method creates the element index, archive index, result index,
146 /// future queue, and session control backed by a shared storage resource
147 /// (e.g., a single RocksDB database or Redis connection). This reduces
148 /// resource overhead and enables cross-index atomic transactions.
149 ///
150 /// Persistent backends additionally return a [`CheckpointStore`] that
151 /// shares the same session state as the returned `SessionControl`, so
152 /// `stage_checkpoint` writes land in the same database transaction as
153 /// index updates. Volatile backends return `checkpoint_store: None`.
154 async fn create_indexes(&self, query_id: &str) -> Result<CreatedIndexes, IndexError>;
155
156 /// Returns true if this backend is volatile (data lost on restart).
157 ///
158 /// Volatile backends (like in-memory) require re-bootstrapping after restart,
159 /// while persistent backends (like RocksDB) retain data.
160 fn is_volatile(&self) -> bool;
161}