Skip to main content

linkrs_query/executor/base/
traits.rs

1//! Search Provider Trait
2//!
3//! This module defines the common trait for search providers (fulltext, vector)
4//! to enable unified management and discovery in the execution context.
5//!
6//! The [`SearchProvider`] trait covers metadata and lifecycle operations that
7//! every search backend must support (listing, dropping, describing indexes).
8//! Provider-specific operations (e.g. vector embedding, fulltext engine type)
9//! remain on the concrete wrapper types ([`FulltextProvider`], [`VectorProvider`])
10//! and are accessed through the `SearchContext` when the operator knows the
11//! concrete backend.
12
13use std::fmt::Debug;
14#[cfg(any(feature = "fulltext", feature = "vector"))]
15use std::sync::Arc;
16
17/// Search provider type enumeration
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub enum SearchProviderType {
20    Fulltext,
21    Vector,
22}
23
24impl std::fmt::Display for SearchProviderType {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        match self {
27            SearchProviderType::Fulltext => write!(f, "FULLTEXT"),
28            SearchProviderType::Vector => write!(f, "VECTOR"),
29        }
30    }
31}
32
33/// Minimal metadata about a search index returned by
34/// [`SearchProvider::list_indexes`].
35#[derive(Debug, Clone)]
36pub struct IndexInfo {
37    pub space_id: u64,
38    pub tag_name: String,
39    pub field_name: String,
40    pub index_name: Option<String>,
41    pub status: String,
42}
43
44/// Common trait for search providers
45///
46/// This trait provides a unified interface for different search backends
47/// (fulltext, vector) to be discovered and managed through a common API.
48/// It covers index metadata, lifecycle, and basic search operations that
49/// every backend supports.
50///
51/// Provider-specific operations (vector embedding, fulltext engine type,
52/// consistency options) remain on the concrete wrapper types and are
53/// accessed through [`SearchContext`](super::SearchContext) when the
54/// operator knows which backend it is talking to.
55pub trait SearchProvider: Send + Sync + Debug + 'static {
56    /// Return the name of this search provider
57    fn name(&self) -> &str;
58
59    /// Return the type of this search provider
60    fn provider_type(&self) -> SearchProviderType;
61
62    /// Return a human-readable description of this provider
63    fn description(&self) -> String {
64        format!("{} search provider: {}", self.provider_type(), self.name())
65    }
66
67    /// List search indexes for a given space.
68    ///
69    /// Returns all indexes (fulltext or vector) that belong to `space_id`.
70    /// When `space_id` is `0` or not applicable, returns all indexes.
71    fn list_indexes(&self, space_id: u64) -> Vec<IndexInfo>;
72
73    /// Drop a search index identified by `(space_id, tag_name, field_name)`.
74    ///
75    /// Returns `Ok(())` on success, or an error string describing what went
76    /// wrong.  The error is intentionally opaque (`String`) so that both
77    /// `SearchError` and `VectorCoordinatorError` can be surfaced without
78    /// introducing a shared error enum at the trait level.
79    fn drop_index(&self, space_id: u64, tag_name: &str, field_name: &str) -> Result<(), String>;
80}
81
82/// Newtype wrapper for FulltextIndexManager to implement SearchProvider
83#[cfg(feature = "fulltext")]
84pub struct FulltextProvider {
85    manager: Arc<linkrs_fulltext::manager::FulltextIndexManager>,
86}
87
88#[cfg(feature = "fulltext")]
89impl FulltextProvider {
90    pub fn new(manager: Arc<linkrs_fulltext::manager::FulltextIndexManager>) -> Self {
91        Self { manager }
92    }
93
94    pub fn manager(&self) -> &Arc<linkrs_fulltext::manager::FulltextIndexManager> {
95        &self.manager
96    }
97}
98
99#[cfg(feature = "fulltext")]
100impl Debug for FulltextProvider {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        f.debug_struct("FulltextProvider")
103            .field("index_count", &self.manager.list_indexes().len())
104            .finish()
105    }
106}
107
108#[cfg(feature = "fulltext")]
109impl SearchProvider for FulltextProvider {
110    fn name(&self) -> &str {
111        "fulltext"
112    }
113
114    fn provider_type(&self) -> SearchProviderType {
115        SearchProviderType::Fulltext
116    }
117
118    fn description(&self) -> String {
119        format!(
120            "Fulltext search provider ({} indexes)",
121            self.manager.list_indexes().len()
122        )
123    }
124
125    fn list_indexes(&self, space_id: u64) -> Vec<IndexInfo> {
126        self.manager
127            .list_indexes()
128            .into_iter()
129            .filter(|m| space_id == 0 || m.space_id == space_id)
130            .map(|m| IndexInfo {
131                space_id: m.space_id,
132                tag_name: m.tag_name,
133                field_name: m.field_name,
134                index_name: Some(m.index_name.clone()),
135                status: format!("{:?}", m.status),
136            })
137            .collect()
138    }
139
140    fn drop_index(&self, space_id: u64, tag_name: &str, field_name: &str) -> Result<(), String> {
141        // Block on the async drop_index from a sync context.
142        let manager = self.manager.clone();
143        let tag = tag_name.to_string();
144        let field = field_name.to_string();
145        tokio::task::block_in_place(|| {
146            tokio::runtime::Handle::current()
147                .block_on(async move { manager.drop_index(space_id, &tag, &field).await })
148        })
149        .map_err(|e| e.to_string())
150    }
151}
152
153/// Newtype wrapper for VectorSyncCoordinator to implement SearchProvider
154#[cfg(feature = "vector")]
155pub struct VectorProvider {
156    coordinator: Arc<linkrs_sync::vector_sync::VectorSyncCoordinator>,
157}
158
159#[cfg(feature = "vector")]
160impl VectorProvider {
161    pub fn new(coordinator: Arc<linkrs_sync::vector_sync::VectorSyncCoordinator>) -> Self {
162        Self { coordinator }
163    }
164
165    pub fn coordinator(&self) -> &Arc<linkrs_sync::vector_sync::VectorSyncCoordinator> {
166        &self.coordinator
167    }
168}
169
170#[cfg(feature = "vector")]
171impl Debug for VectorProvider {
172    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173        f.debug_struct("VectorProvider")
174            .field("index_count", &self.coordinator.list_indexes().len())
175            .finish()
176    }
177}
178
179#[cfg(feature = "vector")]
180impl SearchProvider for VectorProvider {
181    fn name(&self) -> &str {
182        "vector"
183    }
184
185    fn provider_type(&self) -> SearchProviderType {
186        SearchProviderType::Vector
187    }
188
189    fn description(&self) -> String {
190        format!(
191            "Vector search provider ({} indexes)",
192            self.coordinator.list_indexes().len()
193        )
194    }
195
196    fn list_indexes(&self, space_id: u64) -> Vec<IndexInfo> {
197        self.coordinator
198            .list_indexes()
199            .into_iter()
200            .filter(|m| space_id == 0 || m.space_id == space_id)
201            .map(|m| IndexInfo {
202                space_id: m.space_id,
203                tag_name: m.tag_name,
204                field_name: m.field_name,
205                index_name: m.index_name.clone(),
206                status: "Active".to_string(),
207            })
208            .collect()
209    }
210
211    fn drop_index(&self, space_id: u64, tag_name: &str, field_name: &str) -> Result<(), String> {
212        let coordinator = self.coordinator.clone();
213        let tag = tag_name.to_string();
214        let field = field_name.to_string();
215        tokio::task::block_in_place(|| {
216            tokio::runtime::Handle::current().block_on(async move {
217                coordinator.drop_vector_index(space_id, &tag, &field).await
218            })
219        })
220        .map_err(|e| e.to_string())
221    }
222}