1use serde::{Deserialize, Serialize};
29
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32pub struct Query {
33 pub root: QueryNode,
35}
36
37impl Query {
38 pub fn new(root: QueryNode) -> Self {
40 Self { root }
41 }
42
43 pub fn field_eq(field: impl Into<String>, value: impl Into<String>) -> Self {
45 Self::new(QueryNode::Field(FieldQuery {
46 field: field.into(),
47 operator: FieldOperator::Equals,
48 value: QueryValue::Text(value.into()),
49 }))
50 }
51
52 pub fn tags(field: impl Into<String>, values: Vec<String>) -> Self {
54 Self::new(QueryNode::Field(FieldQuery {
55 field: field.into(),
56 operator: FieldOperator::In,
57 value: QueryValue::Tags(values),
58 }))
59 }
60
61 pub fn numeric_range(field: impl Into<String>, min: Option<f64>, max: Option<f64>) -> Self {
63 Self::new(QueryNode::Field(FieldQuery {
64 field: field.into(),
65 operator: FieldOperator::Range,
66 value: QueryValue::NumericRange { min, max },
67 }))
68 }
69
70 pub fn text_search(field: impl Into<String>, text: impl Into<String>) -> Self {
72 Self::new(QueryNode::Field(FieldQuery {
73 field: field.into(),
74 operator: FieldOperator::Contains,
75 value: QueryValue::Text(text.into()),
76 }))
77 }
78
79 pub fn prefix(field: impl Into<String>, prefix: impl Into<String>) -> Self {
81 Self::new(QueryNode::Field(FieldQuery {
82 field: field.into(),
83 operator: FieldOperator::Prefix,
84 value: QueryValue::Text(prefix.into()),
85 }))
86 }
87
88 pub fn fuzzy(field: impl Into<String>, text: impl Into<String>) -> Self {
90 Self::new(QueryNode::Field(FieldQuery {
91 field: field.into(),
92 operator: FieldOperator::Fuzzy,
93 value: QueryValue::Text(text.into()),
94 }))
95 }
96
97 pub fn vector(field: impl Into<String>, vector: Vec<f32>, k: usize) -> Self {
113 Self::new(QueryNode::Vector(VectorQuery {
114 field: field.into(),
115 vector,
116 k,
117 }))
118 }
119
120 pub fn vector_filtered(
136 filter: Query,
137 field: impl Into<String>,
138 vector: Vec<f32>,
139 k: usize,
140 ) -> Self {
141 Self::new(QueryNode::And(vec![
144 filter.root,
145 QueryNode::Vector(VectorQuery {
146 field: field.into(),
147 vector,
148 k,
149 }),
150 ]))
151 }
152
153 pub fn and(self, other: Query) -> Self {
155 Self::new(QueryNode::And(vec![self.root, other.root]))
156 }
157
158 pub fn or(self, other: Query) -> Self {
160 Self::new(QueryNode::Or(vec![self.root, other.root]))
161 }
162
163 pub fn negate(self) -> Self {
165 Self::new(QueryNode::Not(Box::new(self.root)))
166 }
167}
168
169#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
171pub enum QueryNode {
172 Field(FieldQuery),
174 And(Vec<QueryNode>),
176 Or(Vec<QueryNode>),
178 Not(Box<QueryNode>),
180 Vector(VectorQuery),
182}
183
184#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
186pub struct VectorQuery {
187 pub field: String,
189 pub vector: Vec<f32>,
191 pub k: usize,
193}
194
195#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
197pub struct FieldQuery {
198 pub field: String,
200 pub operator: FieldOperator,
202 pub value: QueryValue,
204}
205
206#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
208pub enum FieldOperator {
209 Equals,
211 Contains,
213 Range,
215 In,
217 Prefix,
219 Fuzzy,
221}
222
223#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
225pub enum QueryValue {
226 Text(String),
228 Numeric(f64),
230 NumericRange { min: Option<f64>, max: Option<f64> },
232 Tags(Vec<String>),
234 Boolean(bool),
236}
237
238#[derive(Default)]
240pub struct QueryBuilder {
241 nodes: Vec<QueryNode>,
242}
243
244impl QueryBuilder {
245 pub fn new() -> Self {
247 Self { nodes: Vec::new() }
248 }
249
250 pub fn field_eq(mut self, field: impl Into<String>, value: impl Into<String>) -> Self {
252 self.nodes.push(QueryNode::Field(FieldQuery {
253 field: field.into(),
254 operator: FieldOperator::Equals,
255 value: QueryValue::Text(value.into()),
256 }));
257 self
258 }
259
260 pub fn numeric_eq(mut self, field: impl Into<String>, value: f64) -> Self {
262 self.nodes.push(QueryNode::Field(FieldQuery {
263 field: field.into(),
264 operator: FieldOperator::Equals,
265 value: QueryValue::Numeric(value),
266 }));
267 self
268 }
269
270 pub fn numeric_range(mut self, field: impl Into<String>, min: Option<f64>, max: Option<f64>) -> Self {
272 self.nodes.push(QueryNode::Field(FieldQuery {
273 field: field.into(),
274 operator: FieldOperator::Range,
275 value: QueryValue::NumericRange { min, max },
276 }));
277 self
278 }
279
280 pub fn tags(mut self, field: impl Into<String>, values: Vec<String>) -> Self {
282 self.nodes.push(QueryNode::Field(FieldQuery {
283 field: field.into(),
284 operator: FieldOperator::In,
285 value: QueryValue::Tags(values),
286 }));
287 self
288 }
289
290 pub fn contains(mut self, field: impl Into<String>, text: impl Into<String>) -> Self {
292 self.nodes.push(QueryNode::Field(FieldQuery {
293 field: field.into(),
294 operator: FieldOperator::Contains,
295 value: QueryValue::Text(text.into()),
296 }));
297 self
298 }
299
300 pub fn prefix(mut self, field: impl Into<String>, prefix: impl Into<String>) -> Self {
302 self.nodes.push(QueryNode::Field(FieldQuery {
303 field: field.into(),
304 operator: FieldOperator::Prefix,
305 value: QueryValue::Text(prefix.into()),
306 }));
307 self
308 }
309
310 pub fn fuzzy(mut self, field: impl Into<String>, text: impl Into<String>) -> Self {
312 self.nodes.push(QueryNode::Field(FieldQuery {
313 field: field.into(),
314 operator: FieldOperator::Fuzzy,
315 value: QueryValue::Text(text.into()),
316 }));
317 self
318 }
319
320 pub fn build_and(self) -> Query {
322 if self.nodes.is_empty() {
323 Query::new(QueryNode::Field(FieldQuery {
325 field: "*".to_string(),
326 operator: FieldOperator::Equals,
327 value: QueryValue::Text("*".to_string()),
328 }))
329 } else if self.nodes.len() == 1 {
330 Query::new(self.nodes.into_iter().next().unwrap())
331 } else {
332 Query::new(QueryNode::And(self.nodes))
333 }
334 }
335
336 pub fn build_or(self) -> Query {
338 if self.nodes.is_empty() {
339 Query::new(QueryNode::Field(FieldQuery {
341 field: "*".to_string(),
342 operator: FieldOperator::Equals,
343 value: QueryValue::Text("*".to_string()),
344 }))
345 } else if self.nodes.len() == 1 {
346 Query::new(self.nodes.into_iter().next().unwrap())
347 } else {
348 Query::new(QueryNode::Or(self.nodes))
349 }
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
358 fn test_simple_field_query() {
359 let query = Query::field_eq("name", "Alice");
360 assert_eq!(
361 query.root,
362 QueryNode::Field(FieldQuery {
363 field: "name".to_string(),
364 operator: FieldOperator::Equals,
365 value: QueryValue::Text("Alice".to_string()),
366 })
367 );
368 }
369
370 #[test]
371 fn test_and_query() {
372 let query = Query::field_eq("name", "Alice")
373 .and(Query::numeric_range("age", Some(25.0), Some(40.0)));
374
375 match query.root {
376 QueryNode::And(nodes) => {
377 assert_eq!(nodes.len(), 2);
378 }
379 _ => panic!("Expected And node"),
380 }
381 }
382
383 #[test]
384 fn test_or_query() {
385 let query = Query::field_eq("status", "active")
386 .or(Query::field_eq("status", "pending"));
387
388 match query.root {
389 QueryNode::Or(nodes) => {
390 assert_eq!(nodes.len(), 2);
391 }
392 _ => panic!("Expected Or node"),
393 }
394 }
395
396 #[test]
397 fn test_not_query() {
398 let query = Query::field_eq("deleted", "true").negate();
399
400 match query.root {
401 QueryNode::Not(_) => {}
402 _ => panic!("Expected Not node"),
403 }
404 }
405
406 #[test]
407 fn test_tag_query() {
408 let query = Query::tags("tags", vec!["rust".to_string(), "database".to_string()]);
409
410 match query.root {
411 QueryNode::Field(FieldQuery { field, operator, value }) => {
412 assert_eq!(field, "tags");
413 assert_eq!(operator, FieldOperator::In);
414 assert_eq!(value, QueryValue::Tags(vec!["rust".to_string(), "database".to_string()]));
415 }
416 _ => panic!("Expected Field node"),
417 }
418 }
419
420 #[test]
421 fn test_query_builder_and() {
422 let query = QueryBuilder::new()
423 .field_eq("name", "Alice")
424 .numeric_range("age", Some(25.0), Some(40.0))
425 .tags("tags", vec!["rust".to_string()])
426 .build_and();
427
428 match query.root {
429 QueryNode::And(nodes) => {
430 assert_eq!(nodes.len(), 3);
431 }
432 _ => panic!("Expected And node"),
433 }
434 }
435
436 #[test]
437 fn test_query_builder_or() {
438 let query = QueryBuilder::new()
439 .field_eq("status", "active")
440 .field_eq("status", "pending")
441 .build_or();
442
443 match query.root {
444 QueryNode::Or(nodes) => {
445 assert_eq!(nodes.len(), 2);
446 }
447 _ => panic!("Expected Or node"),
448 }
449 }
450
451 #[test]
452 fn test_complex_query() {
453 let alice_query = Query::field_eq("name", "Alice")
455 .and(Query::numeric_range("age", Some(25.0), Some(40.0)));
456
457 let bob_query = Query::field_eq("name", "Bob")
458 .and(Query::numeric_range("age", Some(30.0), Some(50.0)));
459
460 let query = alice_query.or(bob_query);
461
462 match query.root {
463 QueryNode::Or(nodes) => {
464 assert_eq!(nodes.len(), 2);
465 for node in nodes {
467 match node {
468 QueryNode::And(inner_nodes) => {
469 assert_eq!(inner_nodes.len(), 2);
470 }
471 _ => panic!("Expected And node"),
472 }
473 }
474 }
475 _ => panic!("Expected Or node"),
476 }
477 }
478
479 #[test]
480 fn test_prefix_query() {
481 let query = Query::prefix("email", "admin@");
482 match query.root {
483 QueryNode::Field(FieldQuery { operator, .. }) => {
484 assert_eq!(operator, FieldOperator::Prefix);
485 }
486 _ => panic!("Expected Field node"),
487 }
488 }
489
490 #[test]
491 fn test_fuzzy_query() {
492 let query = Query::fuzzy("name", "alice");
493 match query.root {
494 QueryNode::Field(FieldQuery { operator, .. }) => {
495 assert_eq!(operator, FieldOperator::Fuzzy);
496 }
497 _ => panic!("Expected Field node"),
498 }
499 }
500
501 #[test]
502 fn test_empty_builder_and() {
503 let query = QueryBuilder::new().build_and();
504 match query.root {
506 QueryNode::Field(FieldQuery { field, .. }) => {
507 assert_eq!(field, "*");
508 }
509 _ => panic!("Expected Field node"),
510 }
511 }
512}