Skip to main content

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}