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]
99 pub fn from_select(select: SelectStatement) -> Self {
100 Self::new_select(select)
101 }
102
103 #[must_use]
105 pub fn is_match_query(&self) -> bool {
106 self.match_clause.is_some()
107 }
108
109 #[must_use]
111 pub fn is_select_query(&self) -> bool {
112 self.match_clause.is_none()
113 && self.dml.is_none()
114 && self.train.is_none()
115 && self.ddl.is_none()
116 && self.introspection.is_none()
117 && self.admin.is_none()
118 }
119
120 #[must_use]
122 pub fn is_dml_query(&self) -> bool {
123 self.dml.is_some()
124 }
125
126 #[must_use]
128 pub fn is_train(&self) -> bool {
129 self.train.is_some()
130 }
131
132 #[must_use]
134 pub fn is_ddl_query(&self) -> bool {
135 self.ddl.is_some()
136 }
137
138 #[must_use]
140 pub fn is_introspection_query(&self) -> bool {
141 self.introspection.is_some()
142 }
143
144 #[must_use]
146 pub fn is_admin_query(&self) -> bool {
147 self.admin.is_some()
148 }
149
150 #[must_use]
152 pub fn is_select_edges_query(&self) -> bool {
153 matches!(self.dml, Some(DmlStatement::SelectEdges(_)))
154 }
155
156 fn having_clauses(&self) -> impl Iterator<Item = (&SelectStatement, &HavingClause)> {
162 let compound_stmts = self
163 .compound
164 .iter()
165 .flat_map(|c| c.operations.iter().map(|(_, stmt)| stmt));
166 std::iter::once(&self.select)
167 .chain(compound_stmts)
168 .filter_map(|stmt| stmt.having.as_ref().map(|h| (stmt, h)))
169 }
170
171 #[must_use]
173 pub fn has_having_subquery(&self) -> bool {
174 self.having_clauses()
175 .any(|(_, having)| having.has_subquery())
176 }
177
178 #[must_use]
183 pub fn has_correlated_having_subquery(&self) -> bool {
184 self.having_clauses()
185 .any(|(stmt, having)| having.has_correlated_subquery(&stmt.outer_table_scope()))
186 }
187
188 #[must_use]
190 pub fn is_insert_node_query(&self) -> bool {
191 matches!(self.dml, Some(DmlStatement::InsertNode(_)))
192 }
193
194 #[must_use]
196 pub fn dml_collection_name(&self) -> Option<&str> {
197 let name = match self.dml.as_ref()? {
198 DmlStatement::Insert(s) | DmlStatement::Upsert(s) => &s.table,
199 DmlStatement::Update(s) => &s.table,
200 DmlStatement::Delete(s) => &s.table,
201 DmlStatement::InsertEdge(s) => &s.collection,
202 DmlStatement::DeleteEdge(s) => &s.collection,
203 DmlStatement::SelectEdges(s) => &s.collection,
204 DmlStatement::InsertNode(s) => &s.collection,
205 };
206 if name.is_empty() {
207 None
208 } else {
209 Some(name)
210 }
211 }
212
213 #[must_use]
215 pub fn new_select(select: SelectStatement) -> Self {
216 Self {
217 let_bindings: Vec::new(),
218 select,
219 compound: None,
220 match_clause: None,
221 dml: None,
222 train: None,
223 ddl: None,
224 introspection: None,
225 admin: None,
226 }
227 }
228
229 #[must_use]
231 pub fn new_match(match_clause: crate::velesql::MatchClause) -> Self {
232 let mut select = SelectStatement::empty();
233 select.where_clause.clone_from(&match_clause.where_clause);
234 select.limit = match_clause.return_clause.limit;
235 Self {
236 let_bindings: Vec::new(),
237 select,
238 compound: None,
239 match_clause: Some(match_clause),
240 dml: None,
241 train: None,
242 ddl: None,
243 introspection: None,
244 admin: None,
245 }
246 }
247
248 #[must_use]
250 pub fn new_dml(dml: DmlStatement) -> Self {
251 Self {
252 let_bindings: Vec::new(),
253 select: SelectStatement::empty(),
254 compound: None,
255 match_clause: None,
256 dml: Some(dml),
257 train: None,
258 ddl: None,
259 introspection: None,
260 admin: None,
261 }
262 }
263
264 #[must_use]
266 pub fn new_train(train: TrainStatement) -> Self {
267 Self {
268 let_bindings: Vec::new(),
269 select: SelectStatement::empty(),
270 compound: None,
271 match_clause: None,
272 dml: None,
273 train: Some(train),
274 ddl: None,
275 introspection: None,
276 admin: None,
277 }
278 }
279
280 #[must_use]
282 pub fn new_ddl(ddl: DdlStatement) -> Self {
283 Self {
284 let_bindings: Vec::new(),
285 select: SelectStatement::empty(),
286 compound: None,
287 match_clause: None,
288 dml: None,
289 train: None,
290 ddl: Some(ddl),
291 introspection: None,
292 admin: None,
293 }
294 }
295
296 #[must_use]
298 pub fn new_introspection(stmt: IntrospectionStatement) -> Self {
299 Self {
300 let_bindings: Vec::new(),
301 select: SelectStatement::empty(),
302 compound: None,
303 match_clause: None,
304 dml: None,
305 train: None,
306 ddl: None,
307 introspection: Some(stmt),
308 admin: None,
309 }
310 }
311
312 #[must_use]
314 pub fn new_admin(stmt: AdminStatement) -> Self {
315 Self {
316 let_bindings: Vec::new(),
317 select: SelectStatement::empty(),
318 compound: None,
319 match_clause: None,
320 dml: None,
321 train: None,
322 ddl: None,
323 introspection: None,
324 admin: Some(stmt),
325 }
326 }
327}
328
329#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
331#[non_exhaustive]
332pub enum SetOperator {
333 Union,
335 UnionAll,
337 Intersect,
339 Except,
341}
342
343#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
348pub struct CompoundQuery {
349 pub operations: Vec<(SetOperator, SelectStatement)>,
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
358 fn test_with_clause_new() {
359 let clause = WithClause::new();
360 assert!(clause.options.is_empty());
361 }
362
363 #[test]
364 fn test_with_clause_with_option() {
365 let clause = WithClause::new()
366 .with_option("mode", WithValue::String("accurate".to_string()))
367 .with_option("ef_search", WithValue::Integer(512));
368 assert_eq!(clause.options.len(), 2);
369 }
370
371 #[test]
372 fn test_with_clause_get() {
373 let clause = WithClause::new().with_option("mode", WithValue::String("fast".to_string()));
374 assert!(clause.get("mode").is_some());
375 assert!(clause.get("MODE").is_some());
376 assert!(clause.get("unknown").is_none());
377 }
378
379 #[test]
380 fn test_with_clause_get_mode() {
381 let clause =
382 WithClause::new().with_option("mode", WithValue::String("accurate".to_string()));
383 assert_eq!(clause.get_mode(), Some("accurate"));
384 }
385
386 #[test]
387 fn test_with_value_as_str() {
388 let v = WithValue::String("test".to_string());
389 assert_eq!(v.as_str(), Some("test"));
390 }
391
392 #[test]
393 fn test_with_value_as_integer() {
394 let v = WithValue::Integer(100);
395 assert_eq!(v.as_integer(), Some(100));
396 }
397
398 #[test]
399 fn test_with_value_as_float() {
400 let v = WithValue::Float(1.234);
401 assert!((v.as_float().unwrap() - 1.234).abs() < 1e-5);
402 }
403
404 #[test]
405 fn test_interval_to_seconds() {
406 assert_eq!(
407 IntervalValue {
408 magnitude: 30,
409 unit: IntervalUnit::Seconds
410 }
411 .to_seconds(),
412 30
413 );
414 assert_eq!(
415 IntervalValue {
416 magnitude: 1,
417 unit: IntervalUnit::Days
418 }
419 .to_seconds(),
420 86400
421 );
422 }
423
424 #[test]
425 fn test_temporal_now() {
426 let expr = TemporalExpr::Now;
427 let epoch = expr.to_epoch_seconds();
428 assert!(epoch > 1_577_836_800);
429 }
430
431 #[test]
432 fn test_value_from_i64() {
433 let v: Value = 42i64.into();
434 assert_eq!(v, Value::Integer(42));
435 }
436
437 #[test]
438 fn test_fusion_config_default() {
439 let config = FusionConfig::default();
440 assert_eq!(config.strategy, "rrf");
441 }
442
443 #[test]
444 fn test_fusion_config_rrf() {
445 let config = FusionConfig::rrf();
446 assert_eq!(config.strategy, "rrf");
447 assert!((config.params.get("k").unwrap() - 60.0).abs() < 1e-5);
448 }
449
450 #[test]
451 fn test_fusion_clause_default() {
452 let clause = FusionClause::default();
453 assert_eq!(clause.strategy, FusionStrategyType::Rrf);
454 assert_eq!(clause.k, Some(60));
455 }
456
457 #[test]
458 fn test_group_by_clause_default() {
459 let clause = GroupByClause::default();
460 assert!(clause.columns.is_empty());
461 }
462
463 #[test]
464 fn test_having_clause_default() {
465 let clause = HavingClause::default();
466 assert!(clause.conditions.is_empty());
467 }
468}