reddb_server/application/
query.rs1use crate::runtime::{
2 ContextSearchResult, RedDBRuntime, RuntimeFilter, RuntimeGraphPattern, RuntimeIvfSearchResult,
3 RuntimeQueryExplain, RuntimeQueryResult, RuntimeQueryWeights, ScanCursor, ScanPage,
4};
5use crate::storage::unified::devx::SimilarResult;
6use crate::storage::unified::dsl::QueryResult as DslQueryResult;
7use crate::RedDBResult;
8
9#[derive(Debug, Clone)]
10pub struct ExecuteQueryInput {
11 pub query: String,
12}
13
14#[derive(Debug, Clone)]
15pub struct ExplainQueryInput {
16 pub query: String,
17}
18
19#[derive(Debug, Clone)]
20pub struct ScanCollectionInput {
21 pub collection: String,
22 pub offset: usize,
23 pub limit: usize,
24}
25
26#[derive(Debug, Clone)]
27pub struct SearchSimilarInput {
28 pub collection: String,
29 pub vector: Vec<f32>,
30 pub k: usize,
31 pub min_score: f32,
32 pub text: Option<String>,
34 pub provider: Option<String>,
36}
37
38#[derive(Debug, Clone)]
39pub struct SearchIvfInput {
40 pub collection: String,
41 pub vector: Vec<f32>,
42 pub k: usize,
43 pub n_lists: usize,
44 pub n_probes: Option<usize>,
45}
46
47#[derive(Debug, Clone)]
48pub struct SearchTextInput {
49 pub query: String,
50 pub collections: Option<Vec<String>>,
51 pub entity_types: Option<Vec<String>>,
52 pub capabilities: Option<Vec<String>>,
53 pub fields: Option<Vec<String>>,
54 pub limit: Option<usize>,
55 pub fuzzy: bool,
56}
57
58#[derive(Debug, Clone)]
59pub struct SearchMultimodalInput {
60 pub query: String,
61 pub collections: Option<Vec<String>>,
62 pub entity_types: Option<Vec<String>>,
63 pub capabilities: Option<Vec<String>>,
64 pub limit: Option<usize>,
65}
66
67#[derive(Debug, Clone)]
68pub struct SearchIndexInput {
69 pub index: String,
70 pub value: String,
71 pub exact: bool,
72 pub collections: Option<Vec<String>>,
73 pub entity_types: Option<Vec<String>>,
74 pub capabilities: Option<Vec<String>>,
75 pub limit: Option<usize>,
76}
77
78#[derive(Debug, Clone)]
79pub struct SearchHybridInput {
80 pub vector: Option<Vec<f32>>,
81 pub query: Option<String>,
82 pub k: Option<usize>,
83 pub collections: Option<Vec<String>>,
84 pub entity_types: Option<Vec<String>>,
85 pub capabilities: Option<Vec<String>>,
86 pub graph_pattern: Option<RuntimeGraphPattern>,
87 pub filters: Vec<RuntimeFilter>,
88 pub weights: Option<RuntimeQueryWeights>,
89 pub min_score: Option<f32>,
90 pub limit: Option<usize>,
91}
92
93#[derive(Debug, Clone)]
94pub struct SearchContextInput {
95 pub query: String,
96 pub field: Option<String>,
97 pub vector: Option<Vec<f32>>,
98 pub collections: Option<Vec<String>>,
99 pub graph_depth: Option<usize>,
100 pub graph_max_edges: Option<usize>,
101 pub max_cross_refs: Option<usize>,
102 pub follow_cross_refs: Option<bool>,
103 pub expand_graph: Option<bool>,
104 pub global_scan: Option<bool>,
105 pub reindex: Option<bool>,
106 pub limit: Option<usize>,
107 pub min_score: Option<f32>,
108}
109
110impl RedDBRuntime {
111 pub fn execute(&self, input: ExecuteQueryInput) -> RedDBResult<RuntimeQueryResult> {
112 self.execute_query(&input.query)
113 }
114
115 pub fn explain(&self, input: ExplainQueryInput) -> RedDBResult<RuntimeQueryExplain> {
116 self.explain_query(&input.query)
117 }
118
119 pub fn scan(&self, input: ScanCollectionInput) -> RedDBResult<ScanPage> {
120 self.scan_collection(
121 &input.collection,
122 Some(ScanCursor {
123 offset: input.offset,
124 }),
125 input.limit,
126 )
127 }
128
129 pub fn search_similar_input(
130 &self,
131 mut input: SearchSimilarInput,
132 ) -> RedDBResult<Vec<SimilarResult>> {
133 if let Some(text) = input.text.take() {
135 if input.vector.is_empty() {
136 let provider = match input.provider.as_deref() {
137 Some(p) => crate::ai::parse_provider(p)?,
138 None => {
139 let name = std::env::var("REDDB_AI_PROVIDER")
140 .ok()
141 .unwrap_or_else(|| "openai".to_string());
142 crate::ai::parse_provider(&name)?
143 }
144 };
145 crate::runtime::ai::provider_gate::enforce(self, &provider)?;
149 if matches!(provider, crate::ai::AiProvider::Local) {
154 return Err(crate::ai::local_embeddings_unavailable_error());
155 }
156 if !provider.is_openai_compatible() {
157 return Err(crate::RedDBError::Query(format!(
158 "SEARCH SIMILAR: embeddings are not yet available for provider '{}'. \
159 Use an OpenAI-compatible provider (openai, groq, ollama, openrouter, \
160 together, venice, deepseek, or a custom base URL).",
161 provider.token()
162 )));
163 }
164 let api_key = crate::ai::resolve_api_key_from_runtime(&provider, None, self)?;
165 let model = std::env::var(format!(
166 "REDDB_{}_EMBEDDING_MODEL",
167 provider.token().to_ascii_uppercase()
168 ))
169 .ok()
170 .or_else(|| std::env::var("REDDB_OPENAI_EMBEDDING_MODEL").ok())
171 .filter(|v| !v.trim().is_empty())
172 .unwrap_or_else(|| provider.default_embedding_model().to_string());
173 let transport = crate::runtime::ai::transport::AiTransport::new(
174 crate::runtime::ai::transport::AiTransportConfig::default(),
175 );
176 let request = crate::ai::OpenAiEmbeddingRequest {
177 api_key,
178 model,
179 inputs: vec![text],
180 dimensions: None,
181 api_base: provider.resolve_api_base(),
182 };
183 let response = crate::runtime::ai::block_on_ai(async move {
184 crate::ai::openai_embeddings_async(&transport, request).await
185 })
186 .and_then(|result| result)?;
187 input.vector = response.embeddings.into_iter().next().ok_or_else(|| {
188 crate::RedDBError::Query("embedding API returned no vectors".to_string())
189 })?;
190 }
191 }
192 self.search_similar(&input.collection, &input.vector, input.k, input.min_score)
193 }
194
195 pub fn search_ivf_input(&self, input: SearchIvfInput) -> RedDBResult<RuntimeIvfSearchResult> {
196 RedDBRuntime::search_ivf(
197 self,
198 &input.collection,
199 &input.vector,
200 input.k,
201 input.n_lists,
202 input.n_probes,
203 )
204 }
205
206 pub fn search_text_input(&self, input: SearchTextInput) -> RedDBResult<DslQueryResult> {
207 RedDBRuntime::search_text(
208 self,
209 input.query,
210 input.collections,
211 input.entity_types,
212 input.capabilities,
213 input.fields,
214 input.limit,
215 input.fuzzy,
216 )
217 }
218
219 pub fn search_multimodal_input(
220 &self,
221 input: SearchMultimodalInput,
222 ) -> RedDBResult<DslQueryResult> {
223 RedDBRuntime::search_multimodal(
224 self,
225 input.query,
226 input.collections,
227 input.entity_types,
228 input.capabilities,
229 input.limit,
230 )
231 }
232
233 pub fn search_index_input(&self, input: SearchIndexInput) -> RedDBResult<DslQueryResult> {
234 RedDBRuntime::search_index(
235 self,
236 input.index,
237 input.value,
238 input.exact,
239 input.collections,
240 input.entity_types,
241 input.capabilities,
242 input.limit,
243 )
244 }
245
246 pub fn search_hybrid_input(&self, input: SearchHybridInput) -> RedDBResult<DslQueryResult> {
247 RedDBRuntime::search_hybrid(
248 self,
249 input.vector,
250 input.query,
251 input.k,
252 input.collections,
253 input.entity_types,
254 input.capabilities,
255 input.graph_pattern,
256 input.filters,
257 input.weights,
258 input.min_score,
259 input.limit,
260 )
261 }
262
263 pub fn search_context_input(
264 &self,
265 input: SearchContextInput,
266 ) -> RedDBResult<ContextSearchResult> {
267 RedDBRuntime::search_context(self, input)
268 }
269}