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