1use crate::error::DkitError;
2
3#[derive(Debug, Clone, PartialEq)]
5pub struct Query {
6 pub path: Path,
8 pub operations: Vec<Operation>,
10}
11
12#[derive(Debug, Clone, PartialEq)]
14pub struct Path {
15 pub segments: Vec<Segment>,
16}
17
18#[derive(Debug, Clone, PartialEq)]
20#[non_exhaustive]
21pub enum Segment {
22 Field(String),
24 Index(i64),
26 Iterate,
28}
29
30#[derive(Debug, Clone, PartialEq)]
32#[non_exhaustive]
33pub enum Operation {
34 Where(Condition),
36 Select(Vec<SelectExpr>),
38 Sort { field: String, descending: bool },
40 Limit(usize),
42 Count { field: Option<String> },
44 Sum { field: String },
46 Avg { field: String },
48 Min { field: String },
50 Max { field: String },
52 Distinct { field: String },
54 GroupBy {
57 fields: Vec<String>,
58 having: Option<Condition>,
59 aggregates: Vec<GroupAggregate>,
60 },
61}
62
63#[derive(Debug, Clone, PartialEq)]
65pub struct GroupAggregate {
66 pub func: AggregateFunc,
67 pub field: Option<String>,
68 pub alias: String,
69}
70
71#[derive(Debug, Clone, PartialEq)]
73#[non_exhaustive]
74pub enum AggregateFunc {
75 Count,
76 Sum,
77 Avg,
78 Min,
79 Max,
80}
81
82#[derive(Debug, Clone, PartialEq)]
84#[non_exhaustive]
85pub enum Condition {
86 Comparison(Comparison),
88 And(Box<Condition>, Box<Condition>),
90 Or(Box<Condition>, Box<Condition>),
92}
93
94#[derive(Debug, Clone, PartialEq)]
96pub struct Comparison {
97 pub field: String,
98 pub op: CompareOp,
99 pub value: LiteralValue,
100}
101
102#[derive(Debug, Clone, PartialEq)]
104#[non_exhaustive]
105pub enum CompareOp {
106 Eq, Ne, Gt, Lt, Ge, Le, Contains, StartsWith, EndsWith, }
116
117#[derive(Debug, Clone, PartialEq)]
119#[non_exhaustive]
120pub enum LiteralValue {
121 String(String),
122 Integer(i64),
123 Float(f64),
124 Bool(bool),
125 Null,
126}
127
128#[derive(Debug, Clone, PartialEq)]
130#[non_exhaustive]
131pub enum Expr {
132 Field(String),
134 Literal(LiteralValue),
136 FuncCall { name: String, args: Vec<Expr> },
138}
139
140#[derive(Debug, Clone, PartialEq)]
142pub struct SelectExpr {
143 pub expr: Expr,
144 pub alias: Option<String>,
146}
147
148pub(crate) struct Parser {
152 input: Vec<char>,
153 pos: usize,
154}
155
156impl Parser {
157 pub(crate) fn new(input: &str) -> Self {
158 Self {
159 input: input.chars().collect(),
160 pos: 0,
161 }
162 }
163
164 pub(crate) fn parse(&mut self) -> Result<Query, DkitError> {
166 self.skip_whitespace();
167 let path = self.parse_path()?;
168 self.skip_whitespace();
169
170 let mut operations = Vec::new();
172 while self.peek() == Some('|') {
173 self.advance(); self.skip_whitespace();
175 operations.push(self.parse_operation()?);
176 self.skip_whitespace();
177 }
178
179 if self.pos != self.input.len() {
180 return Err(DkitError::QueryError(format!(
181 "unexpected character '{}' at position {}",
182 self.input[self.pos], self.pos
183 )));
184 }
185
186 Ok(Query { path, operations })
187 }
188
189 fn parse_path(&mut self) -> Result<Path, DkitError> {
191 if !self.consume_char('.') {
192 return Err(DkitError::QueryError(
193 "query must start with '.'".to_string(),
194 ));
195 }
196
197 let mut segments = Vec::new();
198
199 if self.is_at_end() {
201 return Ok(Path { segments });
202 }
203
204 if self.peek() == Some('[') {
206 segments.push(self.parse_bracket()?);
207 } else if self.peek_is_identifier_start() {
208 segments.push(self.parse_field()?);
209 }
210
211 while !self.is_at_end() {
213 self.skip_whitespace();
214 if self.peek() == Some('.') {
215 self.advance(); if self.peek() == Some('[') {
217 segments.push(self.parse_bracket()?);
218 } else {
219 segments.push(self.parse_field()?);
220 }
221 } else if self.peek() == Some('[') {
222 segments.push(self.parse_bracket()?);
223 } else {
224 break;
225 }
226 }
227
228 Ok(Path { segments })
229 }
230
231 fn parse_field(&mut self) -> Result<Segment, DkitError> {
233 let start = self.pos;
234 while !self.is_at_end() {
235 let c = self.input[self.pos];
236 if c.is_alphanumeric() || c == '_' || c == '-' {
237 self.pos += 1;
238 } else {
239 break;
240 }
241 }
242
243 if self.pos == start {
244 return Err(DkitError::QueryError(format!(
245 "expected field name at position {}",
246 self.pos
247 )));
248 }
249
250 let name: String = self.input[start..self.pos].iter().collect();
251 Ok(Segment::Field(name))
252 }
253
254 fn parse_bracket(&mut self) -> Result<Segment, DkitError> {
256 if !self.consume_char('[') {
257 return Err(DkitError::QueryError(format!(
258 "expected '[' at position {}",
259 self.pos
260 )));
261 }
262
263 self.skip_whitespace();
264
265 if self.peek() == Some(']') {
267 self.advance();
268 return Ok(Segment::Iterate);
269 }
270
271 let negative = self.consume_char('-');
273 let start = self.pos;
274 while !self.is_at_end() && self.input[self.pos].is_ascii_digit() {
275 self.pos += 1;
276 }
277 if self.pos == start {
278 return Err(DkitError::QueryError(format!(
279 "expected integer index at position {}",
280 self.pos
281 )));
282 }
283
284 let num_str: String = self.input[start..self.pos].iter().collect();
285 let index: i64 = num_str.parse().map_err(|_| {
286 DkitError::QueryError(format!("invalid index '{}' at position {}", num_str, start))
287 })?;
288
289 self.skip_whitespace();
290 if !self.consume_char(']') {
291 return Err(DkitError::QueryError(format!(
292 "expected ']' at position {}",
293 self.pos
294 )));
295 }
296
297 Ok(Segment::Index(if negative { -index } else { index }))
298 }
299
300 fn parse_operation(&mut self) -> Result<Operation, DkitError> {
304 let keyword = self.parse_keyword()?;
305 match keyword.as_str() {
306 "where" => {
307 self.skip_whitespace();
308 let condition = self.parse_condition()?;
309 Ok(Operation::Where(condition))
310 }
311 "select" => {
312 self.skip_whitespace();
313 let exprs = self.parse_select_expr_list()?;
314 Ok(Operation::Select(exprs))
315 }
316 "sort" => {
317 self.skip_whitespace();
318 let field = self.parse_identifier()?;
319 self.skip_whitespace();
320 let descending = self.try_consume_keyword("desc");
321 Ok(Operation::Sort { field, descending })
322 }
323 "limit" => {
324 self.skip_whitespace();
325 let n = self.parse_positive_integer()?;
326 Ok(Operation::Limit(n))
327 }
328 "count" => {
329 self.skip_whitespace();
330 let field = self.try_parse_identifier();
331 Ok(Operation::Count { field })
332 }
333 "sum" => {
334 self.skip_whitespace();
335 let field = self.parse_identifier()?;
336 Ok(Operation::Sum { field })
337 }
338 "avg" => {
339 self.skip_whitespace();
340 let field = self.parse_identifier()?;
341 Ok(Operation::Avg { field })
342 }
343 "min" => {
344 self.skip_whitespace();
345 let field = self.parse_identifier()?;
346 Ok(Operation::Min { field })
347 }
348 "max" => {
349 self.skip_whitespace();
350 let field = self.parse_identifier()?;
351 Ok(Operation::Max { field })
352 }
353 "distinct" => {
354 self.skip_whitespace();
355 let field = self.parse_identifier()?;
356 Ok(Operation::Distinct { field })
357 }
358 "group_by" => {
359 self.skip_whitespace();
360 let fields = self.parse_identifier_list()?;
361 self.skip_whitespace();
362
363 let aggregates = self.parse_group_aggregates()?;
365
366 let having = if self.try_consume_keyword("having") {
368 self.skip_whitespace();
369 Some(self.parse_condition()?)
370 } else {
371 None
372 };
373
374 Ok(Operation::GroupBy {
375 fields,
376 having,
377 aggregates,
378 })
379 }
380 _ => Err(DkitError::QueryError(format!(
381 "unknown operation '{}' at position {}",
382 keyword,
383 self.pos - keyword.chars().count()
384 ))),
385 }
386 }
387
388 fn parse_group_aggregates(&mut self) -> Result<Vec<GroupAggregate>, DkitError> {
390 let mut aggregates = Vec::new();
391
392 loop {
393 let saved_pos = self.pos;
394 if let Some(agg) = self.try_parse_single_aggregate()? {
395 aggregates.push(agg);
396 self.skip_whitespace();
397 if !self.consume_char(',') {
398 break;
400 }
401 self.skip_whitespace();
402 } else {
403 self.pos = saved_pos;
404 break;
405 }
406 }
407
408 Ok(aggregates)
409 }
410
411 fn try_parse_single_aggregate(&mut self) -> Result<Option<GroupAggregate>, DkitError> {
413 let saved_pos = self.pos;
414
415 let func_name = match self.parse_keyword() {
417 Ok(name) => name,
418 Err(_) => {
419 self.pos = saved_pos;
420 return Ok(None);
421 }
422 };
423
424 let func = match func_name.as_str() {
425 "count" => AggregateFunc::Count,
426 "sum" => AggregateFunc::Sum,
427 "avg" => AggregateFunc::Avg,
428 "min" => AggregateFunc::Min,
429 "max" => AggregateFunc::Max,
430 _ => {
431 self.pos = saved_pos;
433 return Ok(None);
434 }
435 };
436
437 self.skip_whitespace();
438
439 if !self.consume_char('(') {
441 self.pos = saved_pos;
442 return Ok(None);
443 }
444
445 self.skip_whitespace();
446
447 let field = if self.peek() == Some(')') {
449 None
450 } else {
451 Some(self.parse_identifier()?)
452 };
453
454 self.skip_whitespace();
455
456 if !self.consume_char(')') {
457 return Err(DkitError::QueryError(format!(
458 "expected ')' at position {}",
459 self.pos
460 )));
461 }
462
463 let alias = match &field {
465 Some(f) => format!("{}_{}", func_name, f),
466 None => func_name.clone(),
467 };
468
469 Ok(Some(GroupAggregate { func, field, alias }))
470 }
471
472 fn parse_select_expr_list(&mut self) -> Result<Vec<SelectExpr>, DkitError> {
474 let mut exprs = vec![self.parse_select_expr()?];
475 loop {
476 self.skip_whitespace();
477 if self.consume_char(',') {
478 self.skip_whitespace();
479 exprs.push(self.parse_select_expr()?);
480 } else {
481 break;
482 }
483 }
484 Ok(exprs)
485 }
486
487 fn parse_select_expr(&mut self) -> Result<SelectExpr, DkitError> {
489 let expr = self.parse_expr()?;
490 self.skip_whitespace();
491 let alias = {
493 let saved = self.pos;
494 if let Ok(keyword) = self.parse_keyword() {
495 if keyword == "as" {
496 self.skip_whitespace();
497 Some(self.parse_identifier()?)
498 } else {
499 self.pos = saved;
500 None
501 }
502 } else {
503 self.pos = saved;
504 None
505 }
506 };
507 Ok(SelectExpr { expr, alias })
508 }
509
510 fn parse_expr(&mut self) -> Result<Expr, DkitError> {
512 match self.peek() {
513 Some('"') => {
514 let lit = self.parse_string_literal()?;
515 Ok(Expr::Literal(lit))
516 }
517 Some(c) if c.is_ascii_digit() => {
518 let lit = self.parse_number_literal()?;
519 Ok(Expr::Literal(lit))
520 }
521 Some(c) if c.is_alphabetic() || c == '_' => {
522 let name = self.parse_identifier()?;
523 match name.as_str() {
525 "true" => return Ok(Expr::Literal(LiteralValue::Bool(true))),
526 "false" => return Ok(Expr::Literal(LiteralValue::Bool(false))),
527 "null" => return Ok(Expr::Literal(LiteralValue::Null)),
528 _ => {}
529 }
530 if self.peek() == Some('(') {
532 self.advance(); self.skip_whitespace();
534 let mut args = Vec::new();
535 if self.peek() != Some(')') {
536 args.push(self.parse_expr()?);
537 loop {
538 self.skip_whitespace();
539 if self.consume_char(',') {
540 self.skip_whitespace();
541 args.push(self.parse_expr()?);
542 } else {
543 break;
544 }
545 }
546 }
547 self.skip_whitespace();
548 if !self.consume_char(')') {
549 return Err(DkitError::QueryError(format!(
550 "expected ')' at position {}",
551 self.pos
552 )));
553 }
554 Ok(Expr::FuncCall { name, args })
555 } else {
556 Ok(Expr::Field(name))
557 }
558 }
559 Some(c) => Err(DkitError::QueryError(format!(
560 "expected expression at position {}, found '{}'",
561 self.pos, c
562 ))),
563 None => Err(DkitError::QueryError(format!(
564 "expected expression at position {}",
565 self.pos
566 ))),
567 }
568 }
569
570 fn parse_identifier_list(&mut self) -> Result<Vec<String>, DkitError> {
572 let mut fields = vec![self.parse_identifier()?];
573 loop {
574 self.skip_whitespace();
575 if self.consume_char(',') {
576 self.skip_whitespace();
577 fields.push(self.parse_identifier()?);
578 } else {
579 break;
580 }
581 }
582 Ok(fields)
583 }
584
585 fn parse_keyword(&mut self) -> Result<String, DkitError> {
587 let start = self.pos;
588 while !self.is_at_end() {
589 let c = self.input[self.pos];
590 if c.is_alphabetic() || c == '_' {
591 self.pos += 1;
592 } else {
593 break;
594 }
595 }
596 if self.pos == start {
597 return Err(DkitError::QueryError(format!(
598 "expected operation keyword at position {}",
599 self.pos
600 )));
601 }
602 Ok(self.input[start..self.pos].iter().collect())
603 }
604
605 fn parse_condition(&mut self) -> Result<Condition, DkitError> {
607 let mut left = Condition::Comparison(self.parse_comparison()?);
608
609 loop {
610 self.skip_whitespace();
611 let saved_pos = self.pos;
612 if let Ok(keyword) = self.parse_keyword() {
613 match keyword.as_str() {
614 "and" => {
615 self.skip_whitespace();
616 let right = Condition::Comparison(self.parse_comparison()?);
617 left = Condition::And(Box::new(left), Box::new(right));
618 }
619 "or" => {
620 self.skip_whitespace();
621 let right = Condition::Comparison(self.parse_comparison()?);
622 left = Condition::Or(Box::new(left), Box::new(right));
623 }
624 _ => {
625 self.pos = saved_pos;
627 break;
628 }
629 }
630 } else {
631 break;
632 }
633 }
634
635 Ok(left)
636 }
637
638 fn parse_comparison(&mut self) -> Result<Comparison, DkitError> {
640 let field = self.parse_identifier()?;
642 self.skip_whitespace();
643
644 let op = self.parse_compare_op()?;
646 self.skip_whitespace();
647
648 let value = self.parse_literal_value()?;
650
651 Ok(Comparison { field, op, value })
652 }
653
654 fn parse_identifier(&mut self) -> Result<String, DkitError> {
656 let start = self.pos;
657 while !self.is_at_end() {
658 let c = self.input[self.pos];
659 if c.is_alphanumeric() || c == '_' || c == '-' {
660 self.pos += 1;
661 } else {
662 break;
663 }
664 }
665 if self.pos == start {
666 return Err(DkitError::QueryError(format!(
667 "expected field name at position {}",
668 self.pos
669 )));
670 }
671 Ok(self.input[start..self.pos].iter().collect())
672 }
673
674 fn parse_compare_op(&mut self) -> Result<CompareOp, DkitError> {
676 let c1 = self.peek().ok_or_else(|| {
677 DkitError::QueryError(format!(
678 "expected comparison operator at position {}",
679 self.pos
680 ))
681 })?;
682
683 match c1 {
684 '=' => {
685 self.advance();
686 if self.consume_char('=') {
687 Ok(CompareOp::Eq)
688 } else {
689 Err(DkitError::QueryError(format!(
690 "expected '==' at position {}",
691 self.pos - 1
692 )))
693 }
694 }
695 '!' => {
696 self.advance();
697 if self.consume_char('=') {
698 Ok(CompareOp::Ne)
699 } else {
700 Err(DkitError::QueryError(format!(
701 "expected '!=' at position {}",
702 self.pos - 1
703 )))
704 }
705 }
706 '>' => {
707 self.advance();
708 if self.consume_char('=') {
709 Ok(CompareOp::Ge)
710 } else {
711 Ok(CompareOp::Gt)
712 }
713 }
714 '<' => {
715 self.advance();
716 if self.consume_char('=') {
717 Ok(CompareOp::Le)
718 } else {
719 Ok(CompareOp::Lt)
720 }
721 }
722 c if c.is_alphabetic() => {
723 let saved_pos = self.pos;
724 let keyword = self.parse_keyword()?;
725 match keyword.as_str() {
726 "contains" => Ok(CompareOp::Contains),
727 "starts_with" => Ok(CompareOp::StartsWith),
728 "ends_with" => Ok(CompareOp::EndsWith),
729 _ => {
730 self.pos = saved_pos;
731 Err(DkitError::QueryError(format!(
732 "expected comparison operator at position {}, found '{}'",
733 saved_pos, keyword
734 )))
735 }
736 }
737 }
738 _ => Err(DkitError::QueryError(format!(
739 "expected comparison operator at position {}, found '{}'",
740 self.pos, c1
741 ))),
742 }
743 }
744
745 fn parse_literal_value(&mut self) -> Result<LiteralValue, DkitError> {
747 match self.peek() {
748 Some('"') => self.parse_string_literal(),
749 Some(c) if c.is_ascii_digit() || c == '-' => self.parse_number_literal(),
750 Some(c) if c.is_alphabetic() => {
751 let word = self.parse_keyword()?;
752 match word.as_str() {
753 "true" => Ok(LiteralValue::Bool(true)),
754 "false" => Ok(LiteralValue::Bool(false)),
755 "null" => Ok(LiteralValue::Null),
756 _ => Err(DkitError::QueryError(format!(
757 "unexpected value '{}' at position {}",
758 word,
759 self.pos - word.len()
760 ))),
761 }
762 }
763 Some(c) => Err(DkitError::QueryError(format!(
764 "unexpected character '{}' at position {}",
765 c, self.pos
766 ))),
767 None => Err(DkitError::QueryError(format!(
768 "expected value at position {}",
769 self.pos
770 ))),
771 }
772 }
773
774 fn parse_string_literal(&mut self) -> Result<LiteralValue, DkitError> {
776 if !self.consume_char('"') {
777 return Err(DkitError::QueryError(format!(
778 "expected '\"' at position {}",
779 self.pos
780 )));
781 }
782 let start = self.pos;
783 while !self.is_at_end() && self.input[self.pos] != '"' {
784 self.pos += 1;
785 }
786 if self.is_at_end() {
787 return Err(DkitError::QueryError(format!(
788 "unterminated string starting at position {}",
789 start - 1
790 )));
791 }
792 let s: String = self.input[start..self.pos].iter().collect();
793 self.advance(); Ok(LiteralValue::String(s))
795 }
796
797 fn parse_number_literal(&mut self) -> Result<LiteralValue, DkitError> {
799 let start = self.pos;
800 if self.peek() == Some('-') {
801 self.advance();
802 }
803 while !self.is_at_end() && self.input[self.pos].is_ascii_digit() {
804 self.pos += 1;
805 }
806 let mut is_float = false;
807 if self.peek() == Some('.') {
808 is_float = true;
809 self.advance();
810 while !self.is_at_end() && self.input[self.pos].is_ascii_digit() {
811 self.pos += 1;
812 }
813 }
814 if self.pos == start || (self.pos == start + 1 && self.input[start] == '-') {
815 return Err(DkitError::QueryError(format!(
816 "expected number at position {}",
817 start
818 )));
819 }
820 let num_str: String = self.input[start..self.pos].iter().collect();
821 if is_float {
822 let f: f64 = num_str.parse().map_err(|_| {
823 DkitError::QueryError(format!(
824 "invalid number '{}' at position {}",
825 num_str, start
826 ))
827 })?;
828 Ok(LiteralValue::Float(f))
829 } else {
830 let n: i64 = num_str.parse().map_err(|_| {
831 DkitError::QueryError(format!(
832 "invalid number '{}' at position {}",
833 num_str, start
834 ))
835 })?;
836 Ok(LiteralValue::Integer(n))
837 }
838 }
839
840 fn peek(&self) -> Option<char> {
843 self.input.get(self.pos).copied()
844 }
845
846 fn peek_is_identifier_start(&self) -> bool {
847 self.peek().is_some_and(|c| c.is_alphabetic() || c == '_')
848 }
849
850 fn advance(&mut self) {
851 self.pos += 1;
852 }
853
854 fn consume_char(&mut self, expected: char) -> bool {
855 if self.peek() == Some(expected) {
856 self.advance();
857 true
858 } else {
859 false
860 }
861 }
862
863 fn skip_whitespace(&mut self) {
864 while self.peek().is_some_and(|c| c.is_whitespace()) {
865 self.advance();
866 }
867 }
868
869 fn is_at_end(&self) -> bool {
870 self.pos >= self.input.len()
871 }
872
873 fn try_parse_identifier(&mut self) -> Option<String> {
875 if !self.peek_is_identifier_start() {
876 return None;
877 }
878 let saved_pos = self.pos;
879 match self.parse_identifier() {
880 Ok(id) => Some(id),
881 Err(_) => {
882 self.pos = saved_pos;
883 None
884 }
885 }
886 }
887
888 fn try_consume_keyword(&mut self, keyword: &str) -> bool {
890 let saved_pos = self.pos;
891 if let Ok(word) = self.parse_keyword() {
892 if word == keyword {
893 return true;
894 }
895 }
896 self.pos = saved_pos;
897 false
898 }
899
900 fn parse_positive_integer(&mut self) -> Result<usize, DkitError> {
902 let start = self.pos;
903 while !self.is_at_end() && self.input[self.pos].is_ascii_digit() {
904 self.pos += 1;
905 }
906 if self.pos == start {
907 return Err(DkitError::QueryError(format!(
908 "expected positive integer at position {}",
909 self.pos
910 )));
911 }
912 let num_str: String = self.input[start..self.pos].iter().collect();
913 num_str.parse().map_err(|_| {
914 DkitError::QueryError(format!(
915 "invalid integer '{}' at position {}",
916 num_str, start
917 ))
918 })
919 }
920}
921
922pub fn parse_query(input: &str) -> Result<Query, DkitError> {
924 Parser::new(input).parse()
925}
926
927pub fn parse_condition_expr(input: &str) -> Result<Condition, DkitError> {
930 let mut parser = Parser::new(input);
931 parser.skip_whitespace();
932 let condition = parser.parse_condition()?;
933 parser.skip_whitespace();
934 if parser.pos != parser.input.len() {
935 return Err(DkitError::QueryError(format!(
936 "unexpected character '{}' at position {} in where expression",
937 parser.input[parser.pos], parser.pos
938 )));
939 }
940 Ok(condition)
941}
942
943#[cfg(test)]
944mod tests {
945 use super::*;
946
947 #[test]
950 fn test_root_path() {
951 let q = parse_query(".").unwrap();
952 assert!(q.path.segments.is_empty());
953 }
954
955 #[test]
956 fn test_single_field() {
957 let q = parse_query(".name").unwrap();
958 assert_eq!(q.path.segments, vec![Segment::Field("name".to_string())]);
959 }
960
961 #[test]
962 fn test_nested_fields() {
963 let q = parse_query(".database.host").unwrap();
964 assert_eq!(
965 q.path.segments,
966 vec![
967 Segment::Field("database".to_string()),
968 Segment::Field("host".to_string()),
969 ]
970 );
971 }
972
973 #[test]
974 fn test_deeply_nested_fields() {
975 let q = parse_query(".a.b.c.d").unwrap();
976 assert_eq!(q.path.segments.len(), 4);
977 assert_eq!(q.path.segments[3], Segment::Field("d".to_string()));
978 }
979
980 #[test]
983 fn test_array_index() {
984 let q = parse_query(".users[0]").unwrap();
985 assert_eq!(
986 q.path.segments,
987 vec![Segment::Field("users".to_string()), Segment::Index(0),]
988 );
989 }
990
991 #[test]
992 fn test_array_negative_index() {
993 let q = parse_query(".users[-1]").unwrap();
994 assert_eq!(
995 q.path.segments,
996 vec![Segment::Field("users".to_string()), Segment::Index(-1),]
997 );
998 }
999
1000 #[test]
1001 fn test_array_index_with_field_after() {
1002 let q = parse_query(".users[0].name").unwrap();
1003 assert_eq!(
1004 q.path.segments,
1005 vec![
1006 Segment::Field("users".to_string()),
1007 Segment::Index(0),
1008 Segment::Field("name".to_string()),
1009 ]
1010 );
1011 }
1012
1013 #[test]
1014 fn test_large_index() {
1015 let q = parse_query(".items[999]").unwrap();
1016 assert_eq!(
1017 q.path.segments,
1018 vec![Segment::Field("items".to_string()), Segment::Index(999),]
1019 );
1020 }
1021
1022 #[test]
1025 fn test_array_iterate() {
1026 let q = parse_query(".users[]").unwrap();
1027 assert_eq!(
1028 q.path.segments,
1029 vec![Segment::Field("users".to_string()), Segment::Iterate,]
1030 );
1031 }
1032
1033 #[test]
1034 fn test_array_iterate_with_field() {
1035 let q = parse_query(".users[].name").unwrap();
1036 assert_eq!(
1037 q.path.segments,
1038 vec![
1039 Segment::Field("users".to_string()),
1040 Segment::Iterate,
1041 Segment::Field("name".to_string()),
1042 ]
1043 );
1044 }
1045
1046 #[test]
1047 fn test_array_iterate_nested() {
1048 let q = parse_query(".data[].items[].name").unwrap();
1049 assert_eq!(
1050 q.path.segments,
1051 vec![
1052 Segment::Field("data".to_string()),
1053 Segment::Iterate,
1054 Segment::Field("items".to_string()),
1055 Segment::Iterate,
1056 Segment::Field("name".to_string()),
1057 ]
1058 );
1059 }
1060
1061 #[test]
1064 fn test_complex_path() {
1065 let q = parse_query(".data.users[0].address.city").unwrap();
1066 assert_eq!(
1067 q.path.segments,
1068 vec![
1069 Segment::Field("data".to_string()),
1070 Segment::Field("users".to_string()),
1071 Segment::Index(0),
1072 Segment::Field("address".to_string()),
1073 Segment::Field("city".to_string()),
1074 ]
1075 );
1076 }
1077
1078 #[test]
1081 fn test_field_with_underscore() {
1082 let q = parse_query(".user_name").unwrap();
1083 assert_eq!(
1084 q.path.segments,
1085 vec![Segment::Field("user_name".to_string())]
1086 );
1087 }
1088
1089 #[test]
1090 fn test_field_with_hyphen() {
1091 let q = parse_query(".content-type").unwrap();
1092 assert_eq!(
1093 q.path.segments,
1094 vec![Segment::Field("content-type".to_string())]
1095 );
1096 }
1097
1098 #[test]
1099 fn test_field_with_digits() {
1100 let q = parse_query(".field1").unwrap();
1101 assert_eq!(q.path.segments, vec![Segment::Field("field1".to_string())]);
1102 }
1103
1104 #[test]
1107 fn test_error_no_dot() {
1108 let err = parse_query("name").unwrap_err();
1109 assert!(matches!(err, DkitError::QueryError(_)));
1110 }
1111
1112 #[test]
1113 fn test_error_empty() {
1114 let err = parse_query("").unwrap_err();
1115 assert!(matches!(err, DkitError::QueryError(_)));
1116 }
1117
1118 #[test]
1119 fn test_error_unclosed_bracket() {
1120 let err = parse_query(".users[0").unwrap_err();
1121 assert!(matches!(err, DkitError::QueryError(_)));
1122 }
1123
1124 #[test]
1125 fn test_error_invalid_index() {
1126 let err = parse_query(".users[abc]").unwrap_err();
1127 assert!(matches!(err, DkitError::QueryError(_)));
1128 }
1129
1130 #[test]
1131 fn test_error_trailing_garbage() {
1132 let err = parse_query(".name xyz").unwrap_err();
1133 assert!(matches!(err, DkitError::QueryError(_)));
1134 }
1135
1136 #[test]
1139 fn test_whitespace_around() {
1140 let q = parse_query(" .name ").unwrap();
1141 assert_eq!(q.path.segments, vec![Segment::Field("name".to_string())]);
1142 }
1143
1144 #[test]
1147 fn test_root_array_index() {
1148 let q = parse_query(".[0]").unwrap();
1149 assert_eq!(q.path.segments, vec![Segment::Index(0)]);
1150 }
1151
1152 #[test]
1153 fn test_root_array_iterate() {
1154 let q = parse_query(".[]").unwrap();
1155 assert_eq!(q.path.segments, vec![Segment::Iterate]);
1156 }
1157
1158 #[test]
1159 fn test_root_iterate_with_field() {
1160 let q = parse_query(".[].name").unwrap();
1161 assert_eq!(
1162 q.path.segments,
1163 vec![Segment::Iterate, Segment::Field("name".to_string()),]
1164 );
1165 }
1166
1167 #[test]
1170 fn test_where_eq_integer() {
1171 let q = parse_query(".users[] | where age == 30").unwrap();
1172 assert_eq!(
1173 q.path.segments,
1174 vec![Segment::Field("users".to_string()), Segment::Iterate]
1175 );
1176 assert_eq!(q.operations.len(), 1);
1177 assert_eq!(
1178 q.operations[0],
1179 Operation::Where(Condition::Comparison(Comparison {
1180 field: "age".to_string(),
1181 op: CompareOp::Eq,
1182 value: LiteralValue::Integer(30),
1183 }))
1184 );
1185 }
1186
1187 #[test]
1188 fn test_where_ne_string() {
1189 let q = parse_query(".items[] | where status != \"inactive\"").unwrap();
1190 assert_eq!(
1191 q.operations[0],
1192 Operation::Where(Condition::Comparison(Comparison {
1193 field: "status".to_string(),
1194 op: CompareOp::Ne,
1195 value: LiteralValue::String("inactive".to_string()),
1196 }))
1197 );
1198 }
1199
1200 #[test]
1201 fn test_where_gt() {
1202 let q = parse_query(".[] | where age > 25").unwrap();
1203 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1204 panic!("expected Comparison");
1205 };
1206 assert_eq!(cmp.field, "age");
1207 assert_eq!(cmp.op, CompareOp::Gt);
1208 assert_eq!(cmp.value, LiteralValue::Integer(25));
1209 }
1210
1211 #[test]
1212 fn test_where_lt() {
1213 let q = parse_query(".[] | where price < 100").unwrap();
1214 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1215 panic!("expected Comparison");
1216 };
1217 assert_eq!(cmp.op, CompareOp::Lt);
1218 assert_eq!(cmp.value, LiteralValue::Integer(100));
1219 }
1220
1221 #[test]
1222 fn test_where_ge() {
1223 let q = parse_query(".[] | where score >= 80").unwrap();
1224 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1225 panic!("expected Comparison");
1226 };
1227 assert_eq!(cmp.op, CompareOp::Ge);
1228 assert_eq!(cmp.value, LiteralValue::Integer(80));
1229 }
1230
1231 #[test]
1232 fn test_where_le() {
1233 let q = parse_query(".[] | where price <= 1000").unwrap();
1234 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1235 panic!("expected Comparison");
1236 };
1237 assert_eq!(cmp.op, CompareOp::Le);
1238 assert_eq!(cmp.value, LiteralValue::Integer(1000));
1239 }
1240
1241 #[test]
1242 fn test_where_float_literal() {
1243 let q = parse_query(".[] | where score > 3.14").unwrap();
1244 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1245 panic!("expected Comparison");
1246 };
1247 assert_eq!(cmp.value, LiteralValue::Float(3.14));
1248 }
1249
1250 #[test]
1251 fn test_where_negative_number() {
1252 let q = parse_query(".[] | where temp > -10").unwrap();
1253 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1254 panic!("expected Comparison");
1255 };
1256 assert_eq!(cmp.value, LiteralValue::Integer(-10));
1257 }
1258
1259 #[test]
1260 fn test_where_bool_literal() {
1261 let q = parse_query(".[] | where active == true").unwrap();
1262 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1263 panic!("expected Comparison");
1264 };
1265 assert_eq!(cmp.value, LiteralValue::Bool(true));
1266 }
1267
1268 #[test]
1269 fn test_where_null_literal() {
1270 let q = parse_query(".[] | where value == null").unwrap();
1271 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1272 panic!("expected Comparison");
1273 };
1274 assert_eq!(cmp.value, LiteralValue::Null);
1275 }
1276
1277 #[test]
1278 fn test_where_no_operations_for_path_only() {
1279 let q = parse_query(".users[0].name").unwrap();
1280 assert!(q.operations.is_empty());
1281 }
1282
1283 #[test]
1284 fn test_where_with_extra_whitespace() {
1285 let q = parse_query(".[] | where age > 30 ").unwrap();
1286 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1287 panic!("expected Comparison");
1288 };
1289 assert_eq!(cmp.field, "age");
1290 assert_eq!(cmp.op, CompareOp::Gt);
1291 assert_eq!(cmp.value, LiteralValue::Integer(30));
1292 }
1293
1294 #[test]
1297 fn test_error_where_missing_field() {
1298 let err = parse_query(".[] | where == 30").unwrap_err();
1299 assert!(matches!(err, DkitError::QueryError(_)));
1300 }
1301
1302 #[test]
1303 fn test_error_where_missing_operator() {
1304 let err = parse_query(".[] | where age 30").unwrap_err();
1305 assert!(matches!(err, DkitError::QueryError(_)));
1306 }
1307
1308 #[test]
1309 fn test_error_where_missing_value() {
1310 let err = parse_query(".[] | where age >").unwrap_err();
1311 assert!(matches!(err, DkitError::QueryError(_)));
1312 }
1313
1314 #[test]
1315 fn test_error_where_unterminated_string() {
1316 let err = parse_query(".[] | where name == \"hello").unwrap_err();
1317 assert!(matches!(err, DkitError::QueryError(_)));
1318 }
1319
1320 #[test]
1321 fn test_error_unknown_operation() {
1322 let err = parse_query(".[] | foobar age > 30").unwrap_err();
1323 assert!(matches!(err, DkitError::QueryError(_)));
1324 }
1325
1326 #[test]
1329 fn test_where_contains() {
1330 let q = parse_query(".[] | where email contains \"@gmail\"").unwrap();
1331 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1332 panic!("expected Comparison");
1333 };
1334 assert_eq!(cmp.field, "email");
1335 assert_eq!(cmp.op, CompareOp::Contains);
1336 assert_eq!(cmp.value, LiteralValue::String("@gmail".to_string()));
1337 }
1338
1339 #[test]
1340 fn test_where_starts_with() {
1341 let q = parse_query(".[] | where name starts_with \"A\"").unwrap();
1342 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1343 panic!("expected Comparison");
1344 };
1345 assert_eq!(cmp.field, "name");
1346 assert_eq!(cmp.op, CompareOp::StartsWith);
1347 assert_eq!(cmp.value, LiteralValue::String("A".to_string()));
1348 }
1349
1350 #[test]
1351 fn test_where_ends_with() {
1352 let q = parse_query(".[] | where file ends_with \".json\"").unwrap();
1353 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1354 panic!("expected Comparison");
1355 };
1356 assert_eq!(cmp.field, "file");
1357 assert_eq!(cmp.op, CompareOp::EndsWith);
1358 assert_eq!(cmp.value, LiteralValue::String(".json".to_string()));
1359 }
1360
1361 #[test]
1364 fn test_where_and() {
1365 let q = parse_query(".[] | where age > 25 and city == \"Seoul\"").unwrap();
1366 let Operation::Where(cond) = &q.operations[0] else {
1367 panic!("expected Where operation");
1368 };
1369 match cond {
1370 Condition::And(left, right) => {
1371 let Condition::Comparison(l) = left.as_ref() else {
1372 panic!("expected left Comparison");
1373 };
1374 assert_eq!(l.field, "age");
1375 assert_eq!(l.op, CompareOp::Gt);
1376 assert_eq!(l.value, LiteralValue::Integer(25));
1377 let Condition::Comparison(r) = right.as_ref() else {
1378 panic!("expected right Comparison");
1379 };
1380 assert_eq!(r.field, "city");
1381 assert_eq!(r.op, CompareOp::Eq);
1382 assert_eq!(r.value, LiteralValue::String("Seoul".to_string()));
1383 }
1384 _ => panic!("expected And condition"),
1385 }
1386 }
1387
1388 #[test]
1389 fn test_where_or() {
1390 let q = parse_query(".[] | where role == \"admin\" or role == \"manager\"").unwrap();
1391 let Operation::Where(cond) = &q.operations[0] else {
1392 panic!("expected Where operation");
1393 };
1394 match cond {
1395 Condition::Or(left, right) => {
1396 let Condition::Comparison(l) = left.as_ref() else {
1397 panic!("expected left Comparison");
1398 };
1399 assert_eq!(l.field, "role");
1400 assert_eq!(l.value, LiteralValue::String("admin".to_string()));
1401 let Condition::Comparison(r) = right.as_ref() else {
1402 panic!("expected right Comparison");
1403 };
1404 assert_eq!(r.field, "role");
1405 assert_eq!(r.value, LiteralValue::String("manager".to_string()));
1406 }
1407 _ => panic!("expected Or condition"),
1408 }
1409 }
1410
1411 #[test]
1412 fn test_where_and_with_string_op() {
1413 let q = parse_query(".[] | where name starts_with \"A\" and age > 20").unwrap();
1414 let Operation::Where(cond) = &q.operations[0] else {
1415 panic!("expected Where operation");
1416 };
1417 assert!(matches!(cond, Condition::And(_, _)));
1418 }
1419
1420 #[test]
1421 fn test_where_chained_and() {
1422 let q = parse_query(".[] | where a == 1 and b == 2 and c == 3").unwrap();
1423 let Operation::Where(cond) = &q.operations[0] else {
1424 panic!("expected Where operation");
1425 };
1426 match cond {
1428 Condition::And(left, right) => {
1429 assert!(matches!(left.as_ref(), Condition::And(_, _)));
1430 assert!(matches!(right.as_ref(), Condition::Comparison(_)));
1431 }
1432 _ => panic!("expected And condition"),
1433 }
1434 }
1435
1436 fn field(name: &str) -> SelectExpr {
1439 SelectExpr {
1440 expr: Expr::Field(name.to_string()),
1441 alias: None,
1442 }
1443 }
1444
1445 fn fields(names: &[&str]) -> Operation {
1446 Operation::Select(names.iter().map(|n| field(n)).collect())
1447 }
1448
1449 #[test]
1450 fn test_select_single_field() {
1451 let q = parse_query(".users[] | select name").unwrap();
1452 assert_eq!(q.operations.len(), 1);
1453 assert_eq!(q.operations[0], fields(&["name"]));
1454 }
1455
1456 #[test]
1457 fn test_select_multiple_fields() {
1458 let q = parse_query(".users[] | select name, email").unwrap();
1459 assert_eq!(q.operations[0], fields(&["name", "email"]));
1460 }
1461
1462 #[test]
1463 fn test_select_three_fields() {
1464 let q = parse_query(".users[] | select name, age, email").unwrap();
1465 assert_eq!(q.operations[0], fields(&["name", "age", "email"]));
1466 }
1467
1468 #[test]
1469 fn test_select_with_extra_whitespace() {
1470 let q = parse_query(".[] | select name , email ").unwrap();
1471 assert_eq!(q.operations[0], fields(&["name", "email"]));
1472 }
1473
1474 #[test]
1475 fn test_select_field_with_underscore() {
1476 let q = parse_query(".[] | select user_name, created_at").unwrap();
1477 assert_eq!(q.operations[0], fields(&["user_name", "created_at"]));
1478 }
1479
1480 #[test]
1481 fn test_select_field_with_hyphen() {
1482 let q = parse_query(".[] | select content-type").unwrap();
1483 assert_eq!(q.operations[0], fields(&["content-type"]));
1484 }
1485
1486 #[test]
1487 fn test_where_then_select() {
1488 let q = parse_query(".users[] | where age > 30 | select name, email").unwrap();
1489 assert_eq!(q.operations.len(), 2);
1490 assert!(matches!(&q.operations[0], Operation::Where(_)));
1491 assert_eq!(q.operations[1], fields(&["name", "email"]));
1492 }
1493
1494 #[test]
1495 fn test_error_select_missing_fields() {
1496 let err = parse_query(".[] | select").unwrap_err();
1497 assert!(matches!(err, DkitError::QueryError(_)));
1498 }
1499
1500 #[test]
1501 fn test_select_func_single() {
1502 let q = parse_query(".[] | select upper(name)").unwrap();
1503 assert_eq!(
1504 q.operations[0],
1505 Operation::Select(vec![SelectExpr {
1506 expr: Expr::FuncCall {
1507 name: "upper".to_string(),
1508 args: vec![Expr::Field("name".to_string())],
1509 },
1510 alias: None,
1511 }])
1512 );
1513 }
1514
1515 #[test]
1516 fn test_select_func_with_alias() {
1517 let q = parse_query(".[] | select upper(name) as NAME").unwrap();
1518 assert_eq!(
1519 q.operations[0],
1520 Operation::Select(vec![SelectExpr {
1521 expr: Expr::FuncCall {
1522 name: "upper".to_string(),
1523 args: vec![Expr::Field("name".to_string())],
1524 },
1525 alias: Some("NAME".to_string()),
1526 }])
1527 );
1528 }
1529
1530 #[test]
1531 fn test_select_func_nested() {
1532 let q = parse_query(".[] | select upper(trim(name))").unwrap();
1533 assert_eq!(
1534 q.operations[0],
1535 Operation::Select(vec![SelectExpr {
1536 expr: Expr::FuncCall {
1537 name: "upper".to_string(),
1538 args: vec![Expr::FuncCall {
1539 name: "trim".to_string(),
1540 args: vec![Expr::Field("name".to_string())],
1541 }],
1542 },
1543 alias: None,
1544 }])
1545 );
1546 }
1547
1548 #[test]
1549 fn test_select_func_with_literal_arg() {
1550 let q = parse_query(".[] | select round(price, 2)").unwrap();
1551 assert_eq!(
1552 q.operations[0],
1553 Operation::Select(vec![SelectExpr {
1554 expr: Expr::FuncCall {
1555 name: "round".to_string(),
1556 args: vec![
1557 Expr::Field("price".to_string()),
1558 Expr::Literal(LiteralValue::Integer(2)),
1559 ],
1560 },
1561 alias: None,
1562 }])
1563 );
1564 }
1565
1566 #[test]
1567 fn test_select_mixed_fields_and_funcs() {
1568 let q = parse_query(".[] | select name, upper(city)").unwrap();
1569 assert_eq!(
1570 q.operations[0],
1571 Operation::Select(vec![
1572 field("name"),
1573 SelectExpr {
1574 expr: Expr::FuncCall {
1575 name: "upper".to_string(),
1576 args: vec![Expr::Field("city".to_string())],
1577 },
1578 alias: None,
1579 }
1580 ])
1581 );
1582 }
1583
1584 #[test]
1587 fn test_sort_asc() {
1588 let q = parse_query(".users[] | sort age").unwrap();
1589 assert_eq!(q.operations.len(), 1);
1590 assert_eq!(
1591 q.operations[0],
1592 Operation::Sort {
1593 field: "age".to_string(),
1594 descending: false,
1595 }
1596 );
1597 }
1598
1599 #[test]
1600 fn test_sort_desc() {
1601 let q = parse_query(".users[] | sort age desc").unwrap();
1602 assert_eq!(q.operations.len(), 1);
1603 assert_eq!(
1604 q.operations[0],
1605 Operation::Sort {
1606 field: "age".to_string(),
1607 descending: true,
1608 }
1609 );
1610 }
1611
1612 #[test]
1613 fn test_sort_with_extra_whitespace() {
1614 let q = parse_query(".[] | sort name ").unwrap();
1615 assert_eq!(
1616 q.operations[0],
1617 Operation::Sort {
1618 field: "name".to_string(),
1619 descending: false,
1620 }
1621 );
1622 }
1623
1624 #[test]
1625 fn test_sort_desc_with_extra_whitespace() {
1626 let q = parse_query(".[] | sort name desc ").unwrap();
1627 assert_eq!(
1628 q.operations[0],
1629 Operation::Sort {
1630 field: "name".to_string(),
1631 descending: true,
1632 }
1633 );
1634 }
1635
1636 #[test]
1637 fn test_sort_field_with_underscore() {
1638 let q = parse_query(".[] | sort created_at").unwrap();
1639 assert_eq!(
1640 q.operations[0],
1641 Operation::Sort {
1642 field: "created_at".to_string(),
1643 descending: false,
1644 }
1645 );
1646 }
1647
1648 #[test]
1649 fn test_error_sort_missing_field() {
1650 let err = parse_query(".[] | sort").unwrap_err();
1651 assert!(matches!(err, DkitError::QueryError(_)));
1652 }
1653
1654 #[test]
1657 fn test_limit() {
1658 let q = parse_query(".users[] | limit 10").unwrap();
1659 assert_eq!(q.operations.len(), 1);
1660 assert_eq!(q.operations[0], Operation::Limit(10));
1661 }
1662
1663 #[test]
1664 fn test_limit_one() {
1665 let q = parse_query(".[] | limit 1").unwrap();
1666 assert_eq!(q.operations[0], Operation::Limit(1));
1667 }
1668
1669 #[test]
1670 fn test_limit_with_extra_whitespace() {
1671 let q = parse_query(".[] | limit 5 ").unwrap();
1672 assert_eq!(q.operations[0], Operation::Limit(5));
1673 }
1674
1675 #[test]
1676 fn test_error_limit_missing_number() {
1677 let err = parse_query(".[] | limit").unwrap_err();
1678 assert!(matches!(err, DkitError::QueryError(_)));
1679 }
1680
1681 #[test]
1682 fn test_error_limit_negative() {
1683 let err = parse_query(".[] | limit -5").unwrap_err();
1684 assert!(matches!(err, DkitError::QueryError(_)));
1685 }
1686
1687 #[test]
1690 fn test_where_sort_limit() {
1691 let q = parse_query(".users[] | where age > 20 | sort age desc | limit 5").unwrap();
1692 assert_eq!(q.operations.len(), 3);
1693 assert!(matches!(&q.operations[0], Operation::Where(_)));
1694 assert_eq!(
1695 q.operations[1],
1696 Operation::Sort {
1697 field: "age".to_string(),
1698 descending: true,
1699 }
1700 );
1701 assert_eq!(q.operations[2], Operation::Limit(5));
1702 }
1703
1704 #[test]
1705 fn test_where_select_sort() {
1706 let q = parse_query(".users[] | where age > 30 | select name, email | sort name").unwrap();
1707 assert_eq!(q.operations.len(), 3);
1708 assert!(matches!(&q.operations[0], Operation::Where(_)));
1709 assert_eq!(q.operations[1], fields(&["name", "email"]));
1710 assert_eq!(
1711 q.operations[2],
1712 Operation::Sort {
1713 field: "name".to_string(),
1714 descending: false,
1715 }
1716 );
1717 }
1718
1719 #[test]
1720 fn test_group_by_single_field() {
1721 let q = parse_query(".[] | group_by category").unwrap();
1722 assert_eq!(q.operations.len(), 1);
1723 match &q.operations[0] {
1724 Operation::GroupBy {
1725 fields,
1726 having,
1727 aggregates,
1728 } => {
1729 assert_eq!(fields, &vec!["category".to_string()]);
1730 assert!(having.is_none());
1731 assert!(aggregates.is_empty());
1732 }
1733 _ => panic!("expected GroupBy"),
1734 }
1735 }
1736
1737 #[test]
1738 fn test_group_by_multiple_fields() {
1739 let q = parse_query(".[] | group_by region, category").unwrap();
1740 match &q.operations[0] {
1741 Operation::GroupBy { fields, .. } => {
1742 assert_eq!(fields, &vec!["region".to_string(), "category".to_string()]);
1743 }
1744 _ => panic!("expected GroupBy"),
1745 }
1746 }
1747
1748 #[test]
1749 fn test_group_by_with_aggregates() {
1750 let q = parse_query(".[] | group_by category count(), sum(price), avg(score)").unwrap();
1751 match &q.operations[0] {
1752 Operation::GroupBy { aggregates, .. } => {
1753 assert_eq!(aggregates.len(), 3);
1754 assert_eq!(aggregates[0].func, AggregateFunc::Count);
1755 assert_eq!(aggregates[0].field, None);
1756 assert_eq!(aggregates[0].alias, "count");
1757 assert_eq!(aggregates[1].func, AggregateFunc::Sum);
1758 assert_eq!(aggregates[1].field, Some("price".to_string()));
1759 assert_eq!(aggregates[1].alias, "sum_price");
1760 assert_eq!(aggregates[2].func, AggregateFunc::Avg);
1761 assert_eq!(aggregates[2].field, Some("score".to_string()));
1762 assert_eq!(aggregates[2].alias, "avg_score");
1763 }
1764 _ => panic!("expected GroupBy"),
1765 }
1766 }
1767
1768 #[test]
1769 fn test_group_by_with_having() {
1770 let q = parse_query(".[] | group_by category count() having count > 5").unwrap();
1771 match &q.operations[0] {
1772 Operation::GroupBy {
1773 fields,
1774 having,
1775 aggregates,
1776 } => {
1777 assert_eq!(fields, &vec!["category".to_string()]);
1778 assert!(having.is_some());
1779 assert_eq!(aggregates.len(), 1);
1780 }
1781 _ => panic!("expected GroupBy"),
1782 }
1783 }
1784
1785 #[test]
1786 fn test_group_by_with_min_max() {
1787 let q = parse_query(".[] | group_by category min(price), max(price)").unwrap();
1788 match &q.operations[0] {
1789 Operation::GroupBy { aggregates, .. } => {
1790 assert_eq!(aggregates.len(), 2);
1791 assert_eq!(aggregates[0].func, AggregateFunc::Min);
1792 assert_eq!(aggregates[1].func, AggregateFunc::Max);
1793 }
1794 _ => panic!("expected GroupBy"),
1795 }
1796 }
1797
1798 #[test]
1799 fn test_group_by_pipeline() {
1800 let q = parse_query(".[] | group_by category count() | sort count desc | limit 5").unwrap();
1801 assert_eq!(q.operations.len(), 3);
1802 assert!(matches!(&q.operations[0], Operation::GroupBy { .. }));
1803 assert!(matches!(
1804 &q.operations[1],
1805 Operation::Sort {
1806 descending: true,
1807 ..
1808 }
1809 ));
1810 assert_eq!(q.operations[2], Operation::Limit(5));
1811 }
1812}