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,
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]
147 pub fn is_insert_node_query(&self) -> bool {
148 matches!(self.dml, Some(DmlStatement::InsertNode(_)))
149 }
150
151 #[must_use]
153 pub fn dml_collection_name(&self) -> Option<&str> {
154 let name = match self.dml.as_ref()? {
155 DmlStatement::Insert(s) | DmlStatement::Upsert(s) => &s.table,
156 DmlStatement::Update(s) => &s.table,
157 DmlStatement::Delete(s) => &s.table,
158 DmlStatement::InsertEdge(s) => &s.collection,
159 DmlStatement::DeleteEdge(s) => &s.collection,
160 DmlStatement::SelectEdges(s) => &s.collection,
161 DmlStatement::InsertNode(s) => &s.collection,
162 };
163 if name.is_empty() {
164 None
165 } else {
166 Some(name)
167 }
168 }
169
170 #[must_use]
172 pub fn new_select(select: SelectStatement) -> Self {
173 Self {
174 let_bindings: Vec::new(),
175 select,
176 compound: None,
177 match_clause: None,
178 dml: None,
179 train: None,
180 ddl: None,
181 introspection: None,
182 admin: None,
183 }
184 }
185
186 #[must_use]
188 pub fn new_match(match_clause: crate::velesql::MatchClause) -> Self {
189 let mut select = SelectStatement::empty();
190 select.where_clause.clone_from(&match_clause.where_clause);
191 select.limit = match_clause.return_clause.limit;
192 Self {
193 let_bindings: Vec::new(),
194 select,
195 compound: None,
196 match_clause: Some(match_clause),
197 dml: None,
198 train: None,
199 ddl: None,
200 introspection: None,
201 admin: None,
202 }
203 }
204
205 #[must_use]
207 pub fn new_dml(dml: DmlStatement) -> Self {
208 Self {
209 let_bindings: Vec::new(),
210 select: SelectStatement::empty(),
211 compound: None,
212 match_clause: None,
213 dml: Some(dml),
214 train: None,
215 ddl: None,
216 introspection: None,
217 admin: None,
218 }
219 }
220
221 #[must_use]
223 pub fn new_train(train: TrainStatement) -> Self {
224 Self {
225 let_bindings: Vec::new(),
226 select: SelectStatement::empty(),
227 compound: None,
228 match_clause: None,
229 dml: None,
230 train: Some(train),
231 ddl: None,
232 introspection: None,
233 admin: None,
234 }
235 }
236
237 #[must_use]
239 pub fn new_ddl(ddl: DdlStatement) -> Self {
240 Self {
241 let_bindings: Vec::new(),
242 select: SelectStatement::empty(),
243 compound: None,
244 match_clause: None,
245 dml: None,
246 train: None,
247 ddl: Some(ddl),
248 introspection: None,
249 admin: None,
250 }
251 }
252
253 #[must_use]
255 pub fn new_introspection(stmt: IntrospectionStatement) -> Self {
256 Self {
257 let_bindings: Vec::new(),
258 select: SelectStatement::empty(),
259 compound: None,
260 match_clause: None,
261 dml: None,
262 train: None,
263 ddl: None,
264 introspection: Some(stmt),
265 admin: None,
266 }
267 }
268
269 #[must_use]
271 pub fn new_admin(stmt: AdminStatement) -> Self {
272 Self {
273 let_bindings: Vec::new(),
274 select: SelectStatement::empty(),
275 compound: None,
276 match_clause: None,
277 dml: None,
278 train: None,
279 ddl: None,
280 introspection: None,
281 admin: Some(stmt),
282 }
283 }
284}
285
286#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
288#[non_exhaustive]
289pub enum SetOperator {
290 Union,
292 UnionAll,
294 Intersect,
296 Except,
298}
299
300#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
305pub struct CompoundQuery {
306 pub operations: Vec<(SetOperator, SelectStatement)>,
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313
314 #[test]
315 fn test_with_clause_new() {
316 let clause = WithClause::new();
317 assert!(clause.options.is_empty());
318 }
319
320 #[test]
321 fn test_with_clause_with_option() {
322 let clause = WithClause::new()
323 .with_option("mode", WithValue::String("accurate".to_string()))
324 .with_option("ef_search", WithValue::Integer(512));
325 assert_eq!(clause.options.len(), 2);
326 }
327
328 #[test]
329 fn test_with_clause_get() {
330 let clause = WithClause::new().with_option("mode", WithValue::String("fast".to_string()));
331 assert!(clause.get("mode").is_some());
332 assert!(clause.get("MODE").is_some());
333 assert!(clause.get("unknown").is_none());
334 }
335
336 #[test]
337 fn test_with_clause_get_mode() {
338 let clause =
339 WithClause::new().with_option("mode", WithValue::String("accurate".to_string()));
340 assert_eq!(clause.get_mode(), Some("accurate"));
341 }
342
343 #[test]
344 fn test_with_value_as_str() {
345 let v = WithValue::String("test".to_string());
346 assert_eq!(v.as_str(), Some("test"));
347 }
348
349 #[test]
350 fn test_with_value_as_integer() {
351 let v = WithValue::Integer(100);
352 assert_eq!(v.as_integer(), Some(100));
353 }
354
355 #[test]
356 fn test_with_value_as_float() {
357 let v = WithValue::Float(1.234);
358 assert!((v.as_float().unwrap() - 1.234).abs() < 1e-5);
359 }
360
361 #[test]
362 fn test_interval_to_seconds() {
363 assert_eq!(
364 IntervalValue {
365 magnitude: 30,
366 unit: IntervalUnit::Seconds
367 }
368 .to_seconds(),
369 30
370 );
371 assert_eq!(
372 IntervalValue {
373 magnitude: 1,
374 unit: IntervalUnit::Days
375 }
376 .to_seconds(),
377 86400
378 );
379 }
380
381 #[test]
382 fn test_temporal_now() {
383 let expr = TemporalExpr::Now;
384 let epoch = expr.to_epoch_seconds();
385 assert!(epoch > 1_577_836_800);
386 }
387
388 #[test]
389 fn test_value_from_i64() {
390 let v: Value = 42i64.into();
391 assert_eq!(v, Value::Integer(42));
392 }
393
394 #[test]
395 fn test_fusion_config_default() {
396 let config = FusionConfig::default();
397 assert_eq!(config.strategy, "rrf");
398 }
399
400 #[test]
401 fn test_fusion_config_rrf() {
402 let config = FusionConfig::rrf();
403 assert_eq!(config.strategy, "rrf");
404 assert!((config.params.get("k").unwrap() - 60.0).abs() < 1e-5);
405 }
406
407 #[test]
408 fn test_fusion_clause_default() {
409 let clause = FusionClause::default();
410 assert_eq!(clause.strategy, FusionStrategyType::Rrf);
411 assert_eq!(clause.k, Some(60));
412 }
413
414 #[test]
415 fn test_group_by_clause_default() {
416 let clause = GroupByClause::default();
417 assert!(clause.columns.is_empty());
418 }
419
420 #[test]
421 fn test_having_clause_default() {
422 let clause = HavingClause::default();
423 assert!(clause.conditions.is_empty());
424 }
425}