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, 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}