1mod admin;
6mod aggregation;
7pub(crate) mod condition;
8mod ddl;
9mod dml;
10mod fusion;
11mod introspection;
12mod join;
13mod select;
14mod train;
15mod values;
16mod window;
17mod with_clause;
18
19use serde::{Deserialize, Serialize};
20
21pub use admin::{AdminStatement, FlushStatement};
23pub use aggregation::{
24 AggregateArg, AggregateFunction, AggregateType, GroupByClause, HavingClause, HavingCondition,
25 LogicalOp,
26};
27pub use condition::{
28 BetweenCondition, CompareOp, Comparison, Condition, ContainsCondition, ContainsMode,
29 ContainsTextCondition, GeoBboxCondition, GeoDistanceCondition, GraphMatchPredicate,
30 InCondition, IsNullCondition, LikeCondition, MatchCondition, SimilarityCondition,
31 SparseVectorExpr, SparseVectorSearch, VectorFusedSearch, VectorSearch,
32};
33pub use ddl::{
34 AlterCollectionStatement, AnalyzeStatement, CreateCollectionKind, CreateCollectionStatement,
35 CreateIndexStatement, DdlStatement, DropCollectionStatement, DropIndexStatement,
36 GraphCollectionParams, GraphSchemaMode, SchemaDefinition, TruncateStatement,
37 VectorCollectionParams,
38};
39pub use dml::{
40 DeleteEdgeStatement, DeleteStatement, DmlStatement, InsertEdgeStatement, InsertNodeStatement,
41 InsertStatement, SelectEdgesStatement, UpdateAssignment, UpdateStatement,
42};
43pub use fusion::{FusionClause, FusionConfig, FusionStrategyType};
44pub use introspection::{DescribeCollectionStatement, IntrospectionStatement};
45pub use join::{ColumnRef, JoinClause, JoinCondition, JoinType};
46pub use select::{
47 ArithmeticExpr, ArithmeticOp, Column, DistinctMode, LetBinding, OrderByExpr, SelectColumns,
48 SelectOrderBy, SelectStatement, SimilarityOrderBy, SimilarityScoreExpr, DEFAULT_SELECT_LIMIT,
49};
50pub use train::TrainStatement;
51pub use values::{
52 CorrelatedColumn, IntervalUnit, IntervalValue, Subquery, TemporalExpr, Value, VectorExpr,
53};
54pub use window::{OverClause, WindowFunction, WindowFunctionType, WindowOrderBy};
55pub use with_clause::{QuantizationMode, WithClause, WithOption, WithValue};
56
57#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
59pub struct Query {
60 #[serde(default, skip_serializing_if = "Vec::is_empty")]
65 pub let_bindings: Vec<LetBinding>,
66 pub select: SelectStatement,
68 #[serde(default)]
70 pub compound: Option<CompoundQuery>,
71 #[serde(default)]
73 pub match_clause: Option<crate::velesql::MatchClause>,
74 #[serde(default)]
76 pub dml: Option<DmlStatement>,
77 #[serde(default, skip_serializing_if = "Option::is_none")]
79 pub train: Option<TrainStatement>,
80 #[serde(default, skip_serializing_if = "Option::is_none")]
82 pub ddl: Option<DdlStatement>,
83 #[serde(default, skip_serializing_if = "Option::is_none")]
85 pub introspection: Option<IntrospectionStatement>,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub admin: Option<AdminStatement>,
89}
90
91impl Query {
92 #[must_use]
94 pub fn is_match_query(&self) -> bool {
95 self.match_clause.is_some()
96 }
97
98 #[must_use]
100 pub fn is_select_query(&self) -> bool {
101 self.match_clause.is_none()
102 && self.dml.is_none()
103 && self.train.is_none()
104 && self.ddl.is_none()
105 && self.introspection.is_none()
106 && self.admin.is_none()
107 }
108
109 #[must_use]
111 pub fn is_dml_query(&self) -> bool {
112 self.dml.is_some()
113 }
114
115 #[must_use]
117 pub fn is_train(&self) -> bool {
118 self.train.is_some()
119 }
120
121 #[must_use]
123 pub fn is_ddl_query(&self) -> bool {
124 self.ddl.is_some()
125 }
126
127 #[must_use]
129 pub fn is_introspection_query(&self) -> bool {
130 self.introspection.is_some()
131 }
132
133 #[must_use]
135 pub fn is_admin_query(&self) -> bool {
136 self.admin.is_some()
137 }
138
139 #[must_use]
141 pub fn is_select_edges_query(&self) -> bool {
142 matches!(self.dml, Some(DmlStatement::SelectEdges(_)))
143 }
144
145 #[must_use]
152 pub fn has_having_subquery(&self) -> bool {
153 let compound_stmts = self
154 .compound
155 .iter()
156 .flat_map(|c| c.operations.iter().map(|(_, stmt)| stmt));
157 std::iter::once(&self.select)
158 .chain(compound_stmts)
159 .filter_map(|stmt| stmt.having.as_ref())
160 .any(HavingClause::has_subquery)
161 }
162
163 #[must_use]
165 pub fn is_insert_node_query(&self) -> bool {
166 matches!(self.dml, Some(DmlStatement::InsertNode(_)))
167 }
168
169 #[must_use]
171 pub fn dml_collection_name(&self) -> Option<&str> {
172 let name = match self.dml.as_ref()? {
173 DmlStatement::Insert(s) | DmlStatement::Upsert(s) => &s.table,
174 DmlStatement::Update(s) => &s.table,
175 DmlStatement::Delete(s) => &s.table,
176 DmlStatement::InsertEdge(s) => &s.collection,
177 DmlStatement::DeleteEdge(s) => &s.collection,
178 DmlStatement::SelectEdges(s) => &s.collection,
179 DmlStatement::InsertNode(s) => &s.collection,
180 };
181 if name.is_empty() {
182 None
183 } else {
184 Some(name)
185 }
186 }
187
188 #[must_use]
190 pub fn new_select(select: SelectStatement) -> Self {
191 Self {
192 let_bindings: Vec::new(),
193 select,
194 compound: None,
195 match_clause: None,
196 dml: None,
197 train: None,
198 ddl: None,
199 introspection: None,
200 admin: None,
201 }
202 }
203
204 #[must_use]
206 pub fn new_match(match_clause: crate::velesql::MatchClause) -> Self {
207 let mut select = SelectStatement::empty();
208 select.where_clause.clone_from(&match_clause.where_clause);
209 select.limit = match_clause.return_clause.limit;
210 Self {
211 let_bindings: Vec::new(),
212 select,
213 compound: None,
214 match_clause: Some(match_clause),
215 dml: None,
216 train: None,
217 ddl: None,
218 introspection: None,
219 admin: None,
220 }
221 }
222
223 #[must_use]
225 pub fn new_dml(dml: DmlStatement) -> Self {
226 Self {
227 let_bindings: Vec::new(),
228 select: SelectStatement::empty(),
229 compound: None,
230 match_clause: None,
231 dml: Some(dml),
232 train: None,
233 ddl: None,
234 introspection: None,
235 admin: None,
236 }
237 }
238
239 #[must_use]
241 pub fn new_train(train: TrainStatement) -> Self {
242 Self {
243 let_bindings: Vec::new(),
244 select: SelectStatement::empty(),
245 compound: None,
246 match_clause: None,
247 dml: None,
248 train: Some(train),
249 ddl: None,
250 introspection: None,
251 admin: None,
252 }
253 }
254
255 #[must_use]
257 pub fn new_ddl(ddl: DdlStatement) -> Self {
258 Self {
259 let_bindings: Vec::new(),
260 select: SelectStatement::empty(),
261 compound: None,
262 match_clause: None,
263 dml: None,
264 train: None,
265 ddl: Some(ddl),
266 introspection: None,
267 admin: None,
268 }
269 }
270
271 #[must_use]
273 pub fn new_introspection(stmt: IntrospectionStatement) -> Self {
274 Self {
275 let_bindings: Vec::new(),
276 select: SelectStatement::empty(),
277 compound: None,
278 match_clause: None,
279 dml: None,
280 train: None,
281 ddl: None,
282 introspection: Some(stmt),
283 admin: None,
284 }
285 }
286
287 #[must_use]
289 pub fn new_admin(stmt: AdminStatement) -> Self {
290 Self {
291 let_bindings: Vec::new(),
292 select: SelectStatement::empty(),
293 compound: None,
294 match_clause: None,
295 dml: None,
296 train: None,
297 ddl: None,
298 introspection: None,
299 admin: Some(stmt),
300 }
301 }
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
306#[non_exhaustive]
307pub enum SetOperator {
308 Union,
310 UnionAll,
312 Intersect,
314 Except,
316}
317
318#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
323pub struct CompoundQuery {
324 pub operations: Vec<(SetOperator, SelectStatement)>,
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331
332 #[test]
333 fn test_with_clause_new() {
334 let clause = WithClause::new();
335 assert!(clause.options.is_empty());
336 }
337
338 #[test]
339 fn test_with_clause_with_option() {
340 let clause = WithClause::new()
341 .with_option("mode", WithValue::String("accurate".to_string()))
342 .with_option("ef_search", WithValue::Integer(512));
343 assert_eq!(clause.options.len(), 2);
344 }
345
346 #[test]
347 fn test_with_clause_get() {
348 let clause = WithClause::new().with_option("mode", WithValue::String("fast".to_string()));
349 assert!(clause.get("mode").is_some());
350 assert!(clause.get("MODE").is_some());
351 assert!(clause.get("unknown").is_none());
352 }
353
354 #[test]
355 fn test_with_clause_get_mode() {
356 let clause =
357 WithClause::new().with_option("mode", WithValue::String("accurate".to_string()));
358 assert_eq!(clause.get_mode(), Some("accurate"));
359 }
360
361 #[test]
362 fn test_with_value_as_str() {
363 let v = WithValue::String("test".to_string());
364 assert_eq!(v.as_str(), Some("test"));
365 }
366
367 #[test]
368 fn test_with_value_as_integer() {
369 let v = WithValue::Integer(100);
370 assert_eq!(v.as_integer(), Some(100));
371 }
372
373 #[test]
374 fn test_with_value_as_float() {
375 let v = WithValue::Float(1.234);
376 assert!((v.as_float().unwrap() - 1.234).abs() < 1e-5);
377 }
378
379 #[test]
380 fn test_interval_to_seconds() {
381 assert_eq!(
382 IntervalValue {
383 magnitude: 30,
384 unit: IntervalUnit::Seconds
385 }
386 .to_seconds(),
387 30
388 );
389 assert_eq!(
390 IntervalValue {
391 magnitude: 1,
392 unit: IntervalUnit::Days
393 }
394 .to_seconds(),
395 86400
396 );
397 }
398
399 #[test]
400 fn test_temporal_now() {
401 let expr = TemporalExpr::Now;
402 let epoch = expr.to_epoch_seconds();
403 assert!(epoch > 1_577_836_800);
404 }
405
406 #[test]
407 fn test_value_from_i64() {
408 let v: Value = 42i64.into();
409 assert_eq!(v, Value::Integer(42));
410 }
411
412 #[test]
413 fn test_fusion_config_default() {
414 let config = FusionConfig::default();
415 assert_eq!(config.strategy, "rrf");
416 }
417
418 #[test]
419 fn test_fusion_config_rrf() {
420 let config = FusionConfig::rrf();
421 assert_eq!(config.strategy, "rrf");
422 assert!((config.params.get("k").unwrap() - 60.0).abs() < 1e-5);
423 }
424
425 #[test]
426 fn test_fusion_clause_default() {
427 let clause = FusionClause::default();
428 assert_eq!(clause.strategy, FusionStrategyType::Rrf);
429 assert_eq!(clause.k, Some(60));
430 }
431
432 #[test]
433 fn test_group_by_clause_default() {
434 let clause = GroupByClause::default();
435 assert!(clause.columns.is_empty());
436 }
437
438 #[test]
439 fn test_having_clause_default() {
440 let clause = HavingClause::default();
441 assert!(clause.conditions.is_empty());
442 }
443}