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 Slice {
30 start: Option<i64>,
31 end: Option<i64>,
32 step: Option<i64>,
33 },
34 Wildcard,
36 RecursiveDescent(String),
38}
39
40#[derive(Debug, Clone, PartialEq)]
42#[non_exhaustive]
43pub enum Operation {
44 Where(Condition),
46 Select(Vec<SelectExpr>),
48 Sort { field: String, descending: bool },
50 Limit(usize),
52 Count { field: Option<String> },
54 Sum { field: String },
56 Avg { field: String },
58 Min { field: String },
60 Max { field: String },
62 Distinct { field: String },
64 Median { field: String },
66 Percentile { field: String, p: f64 },
68 Stddev { field: String },
70 Variance { field: String },
72 Mode { field: String },
74 GroupConcat { field: String, separator: String },
76 GroupBy {
79 fields: Vec<String>,
80 having: Option<Condition>,
81 aggregates: Vec<GroupAggregate>,
82 },
83 Unique,
85 UniqueBy { field: String },
87 AddField { name: String, expr: Expr },
89 MapField { name: String, expr: Expr },
91 Explode { field: String },
93 Unpivot {
96 value_columns: Vec<String>,
98 key_name: String,
100 value_name: String,
102 },
103 Pivot {
106 index_fields: Vec<String>,
108 columns_field: String,
110 values_field: String,
112 },
113}
114
115#[derive(Debug, Clone, PartialEq)]
117pub struct GroupAggregate {
118 pub func: AggregateFunc,
119 pub field: Option<String>,
120 pub alias: String,
121}
122
123#[derive(Debug, Clone, PartialEq)]
125#[non_exhaustive]
126pub enum AggregateFunc {
127 Count,
128 Sum,
129 Avg,
130 Min,
131 Max,
132 Median,
133 Percentile(f64),
134 Stddev,
135 Variance,
136 Mode,
137 GroupConcat(String),
138}
139
140#[derive(Debug, Clone, PartialEq)]
142#[non_exhaustive]
143pub enum Condition {
144 Comparison(Comparison),
146 And(Box<Condition>, Box<Condition>),
148 Or(Box<Condition>, Box<Condition>),
150}
151
152#[derive(Debug, Clone, PartialEq)]
154pub struct Comparison {
155 pub field: String,
156 pub op: CompareOp,
157 pub value: LiteralValue,
158}
159
160#[derive(Debug, Clone, PartialEq)]
162#[non_exhaustive]
163pub enum CompareOp {
164 Eq, Ne, Gt, Lt, Ge, Le, Contains, StartsWith, EndsWith, In, NotIn, Matches, NotMatches, }
178
179#[derive(Debug, Clone, PartialEq)]
181#[non_exhaustive]
182pub enum LiteralValue {
183 String(String),
184 Integer(i64),
185 Float(f64),
186 Bool(bool),
187 Null,
188 List(Vec<LiteralValue>),
189}
190
191#[derive(Debug, Clone, PartialEq)]
193pub enum ArithmeticOp {
194 Add, Sub, Mul, Div, }
199
200#[derive(Debug, Clone, PartialEq)]
202#[non_exhaustive]
203pub enum Expr {
204 Field(String),
206 Literal(LiteralValue),
208 FuncCall { name: String, args: Vec<Expr> },
210 BinaryOp {
212 op: ArithmeticOp,
213 left: Box<Expr>,
214 right: Box<Expr>,
215 },
216 If {
218 condition: Condition,
219 then_expr: Box<Expr>,
220 else_expr: Box<Expr>,
221 },
222 Case {
224 branches: Vec<(Condition, Expr)>,
225 default: Option<Box<Expr>>,
226 },
227}
228
229#[derive(Debug, Clone, PartialEq)]
231pub struct SelectExpr {
232 pub expr: Expr,
233 pub alias: Option<String>,
235}
236
237pub(crate) struct Parser {
241 input: Vec<char>,
242 pos: usize,
243}
244
245impl Parser {
246 pub(crate) fn new(input: &str) -> Self {
247 Self {
248 input: input.chars().collect(),
249 pos: 0,
250 }
251 }
252
253 pub(crate) fn parse(&mut self) -> Result<Query, DkitError> {
255 self.skip_whitespace();
256 let path = self.parse_path()?;
257 self.skip_whitespace();
258
259 let mut operations = Vec::new();
261 while self.peek() == Some('|') {
262 self.advance(); self.skip_whitespace();
264 operations.push(self.parse_operation()?);
265 self.skip_whitespace();
266 }
267
268 if self.pos != self.input.len() {
269 return Err(DkitError::QueryError(format!(
270 "unexpected character '{}' at position {}",
271 self.input[self.pos], self.pos
272 )));
273 }
274
275 Ok(Query { path, operations })
276 }
277
278 fn parse_path(&mut self) -> Result<Path, DkitError> {
280 if !self.consume_char('.') {
281 return Err(DkitError::QueryError(
282 "query must start with '.'".to_string(),
283 ));
284 }
285
286 let mut segments = Vec::new();
287
288 if self.is_at_end() {
290 return Ok(Path { segments });
291 }
292
293 if self.peek() == Some('.') {
295 self.advance(); let field = self.parse_field()?;
297 if let Segment::Field(name) = field {
298 segments.push(Segment::RecursiveDescent(name));
299 }
300 return Ok(Path { segments });
301 }
302
303 if self.peek() == Some('[') {
306 segments.push(self.parse_bracket()?);
307 } else if self.peek_is_identifier_start() {
308 segments.push(self.parse_field()?);
309 }
310
311 while !self.is_at_end() {
313 self.skip_whitespace();
314 if self.peek() == Some('.') {
315 if self.pos + 1 < self.input.len() && self.input[self.pos + 1] == '.' {
317 self.advance(); self.advance(); let field = self.parse_field()?;
320 if let Segment::Field(name) = field {
321 segments.push(Segment::RecursiveDescent(name));
322 }
323 } else {
324 self.advance(); if self.peek() == Some('[') {
326 segments.push(self.parse_bracket()?);
327 } else {
328 segments.push(self.parse_field()?);
329 }
330 }
331 } else if self.peek() == Some('[') {
332 segments.push(self.parse_bracket()?);
333 } else {
334 break;
335 }
336 }
337
338 Ok(Path { segments })
339 }
340
341 fn parse_field(&mut self) -> Result<Segment, DkitError> {
343 let start = self.pos;
344 while !self.is_at_end() {
345 let c = self.input[self.pos];
346 if c.is_alphanumeric() || c == '_' || c == '-' {
347 self.pos += 1;
348 } else {
349 break;
350 }
351 }
352
353 if self.pos == start {
354 return Err(DkitError::QueryError(format!(
355 "expected field name at position {}",
356 self.pos
357 )));
358 }
359
360 let name: String = self.input[start..self.pos].iter().collect();
361 Ok(Segment::Field(name))
362 }
363
364 fn parse_bracket(&mut self) -> Result<Segment, DkitError> {
366 if !self.consume_char('[') {
367 return Err(DkitError::QueryError(format!(
368 "expected '[' at position {}",
369 self.pos
370 )));
371 }
372
373 self.skip_whitespace();
374
375 if self.peek() == Some(']') {
377 self.advance();
378 return Ok(Segment::Iterate);
379 }
380
381 if self.peek() == Some('*') {
383 self.advance();
384 self.skip_whitespace();
385 if !self.consume_char(']') {
386 return Err(DkitError::QueryError(format!(
387 "expected ']' after '*' at position {}",
388 self.pos
389 )));
390 }
391 return Ok(Segment::Wildcard);
392 }
393
394 if self.peek() == Some(':') {
396 return self.parse_slice(None);
397 }
398
399 let negative = self.consume_char('-');
401 let start = self.pos;
402 while !self.is_at_end() && self.input[self.pos].is_ascii_digit() {
403 self.pos += 1;
404 }
405 if self.pos == start {
406 return Err(DkitError::QueryError(format!(
407 "expected integer index at position {}",
408 self.pos
409 )));
410 }
411
412 let num_str: String = self.input[start..self.pos].iter().collect();
413 let num: i64 = num_str.parse().map_err(|_| {
414 DkitError::QueryError(format!("invalid index '{}' at position {}", num_str, start))
415 })?;
416 let num = if negative { -num } else { num };
417
418 self.skip_whitespace();
419
420 if self.peek() == Some(':') {
422 return self.parse_slice(Some(num));
423 }
424
425 if !self.consume_char(']') {
427 return Err(DkitError::QueryError(format!(
428 "expected ']' or ':' at position {}",
429 self.pos
430 )));
431 }
432
433 Ok(Segment::Index(num))
434 }
435
436 fn parse_slice(&mut self, start: Option<i64>) -> Result<Segment, DkitError> {
438 if !self.consume_char(':') {
440 return Err(DkitError::QueryError(format!(
441 "expected ':' at position {}",
442 self.pos
443 )));
444 }
445
446 self.skip_whitespace();
447
448 let end = if self.peek() == Some(']') || self.peek() == Some(':') {
450 None
451 } else {
452 Some(self.parse_signed_integer()?)
453 };
454
455 self.skip_whitespace();
456
457 let step = if self.peek() == Some(':') {
459 self.advance();
460 self.skip_whitespace();
461 if self.peek() == Some(']') {
462 None
463 } else {
464 Some(self.parse_signed_integer()?)
465 }
466 } else {
467 None
468 };
469
470 self.skip_whitespace();
471 if !self.consume_char(']') {
472 return Err(DkitError::QueryError(format!(
473 "expected ']' at position {}",
474 self.pos
475 )));
476 }
477
478 Ok(Segment::Slice { start, end, step })
479 }
480
481 fn parse_signed_integer(&mut self) -> Result<i64, DkitError> {
483 let negative = self.consume_char('-');
484 let start = self.pos;
485 while !self.is_at_end() && self.input[self.pos].is_ascii_digit() {
486 self.pos += 1;
487 }
488 if self.pos == start {
489 return Err(DkitError::QueryError(format!(
490 "expected integer at position {}",
491 self.pos
492 )));
493 }
494 let num_str: String = self.input[start..self.pos].iter().collect();
495 let num: i64 = num_str.parse().map_err(|_| {
496 DkitError::QueryError(format!(
497 "invalid integer '{}' at position {}",
498 num_str, start
499 ))
500 })?;
501 Ok(if negative { -num } else { num })
502 }
503
504 fn parse_operation(&mut self) -> Result<Operation, DkitError> {
508 let keyword = self.parse_keyword()?;
509 match keyword.as_str() {
510 "where" => {
511 self.skip_whitespace();
512 let condition = self.parse_condition()?;
513 Ok(Operation::Where(condition))
514 }
515 "select" => {
516 self.skip_whitespace();
517 let exprs = self.parse_select_expr_list()?;
518 Ok(Operation::Select(exprs))
519 }
520 "sort" => {
521 self.skip_whitespace();
522 let field = self.parse_identifier()?;
523 self.skip_whitespace();
524 let descending = self.try_consume_keyword("desc");
525 Ok(Operation::Sort { field, descending })
526 }
527 "limit" => {
528 self.skip_whitespace();
529 let n = self.parse_positive_integer()?;
530 Ok(Operation::Limit(n))
531 }
532 "count" => {
533 self.skip_whitespace();
534 let field = self.try_parse_identifier();
535 Ok(Operation::Count { field })
536 }
537 "sum" => {
538 self.skip_whitespace();
539 let field = self.parse_identifier()?;
540 Ok(Operation::Sum { field })
541 }
542 "avg" => {
543 self.skip_whitespace();
544 let field = self.parse_identifier()?;
545 Ok(Operation::Avg { field })
546 }
547 "min" => {
548 self.skip_whitespace();
549 let field = self.parse_identifier()?;
550 Ok(Operation::Min { field })
551 }
552 "max" => {
553 self.skip_whitespace();
554 let field = self.parse_identifier()?;
555 Ok(Operation::Max { field })
556 }
557 "distinct" => {
558 self.skip_whitespace();
559 let field = self.parse_identifier()?;
560 Ok(Operation::Distinct { field })
561 }
562 "median" => {
563 self.skip_whitespace();
564 let field = self.parse_identifier()?;
565 Ok(Operation::Median { field })
566 }
567 "percentile" => {
568 self.skip_whitespace();
569 let field = self.parse_identifier()?;
570 self.skip_whitespace();
571 let p = self.parse_float_value()?;
572 if !(0.0..=1.0).contains(&p) {
573 return Err(DkitError::QueryError(
574 "percentile: p must be between 0.0 and 1.0".to_string(),
575 ));
576 }
577 Ok(Operation::Percentile { field, p })
578 }
579 "stddev" => {
580 self.skip_whitespace();
581 let field = self.parse_identifier()?;
582 Ok(Operation::Stddev { field })
583 }
584 "variance" => {
585 self.skip_whitespace();
586 let field = self.parse_identifier()?;
587 Ok(Operation::Variance { field })
588 }
589 "mode" => {
590 self.skip_whitespace();
591 let field = self.parse_identifier()?;
592 Ok(Operation::Mode { field })
593 }
594 "group_concat" => {
595 self.skip_whitespace();
596 let field = self.parse_identifier()?;
597 self.skip_whitespace();
598 let separator = if self.peek() == Some('"') {
599 match self.parse_string_literal()? {
600 LiteralValue::String(s) => s,
601 _ => ", ".to_string(),
602 }
603 } else {
604 ", ".to_string()
605 };
606 Ok(Operation::GroupConcat { field, separator })
607 }
608 "group_by" => {
609 self.skip_whitespace();
610 let fields = self.parse_identifier_list()?;
611 self.skip_whitespace();
612
613 let aggregates = self.parse_group_aggregates()?;
615
616 let having = if self.try_consume_keyword("having") {
618 self.skip_whitespace();
619 Some(self.parse_condition()?)
620 } else {
621 None
622 };
623
624 Ok(Operation::GroupBy {
625 fields,
626 having,
627 aggregates,
628 })
629 }
630 _ => Err(DkitError::QueryError(format!(
631 "unknown operation '{}' at position {}",
632 keyword,
633 self.pos - keyword.chars().count()
634 ))),
635 }
636 }
637
638 fn parse_group_aggregates(&mut self) -> Result<Vec<GroupAggregate>, DkitError> {
640 let mut aggregates = Vec::new();
641
642 loop {
643 let saved_pos = self.pos;
644 if let Some(agg) = self.try_parse_single_aggregate()? {
645 aggregates.push(agg);
646 self.skip_whitespace();
647 if !self.consume_char(',') {
648 break;
650 }
651 self.skip_whitespace();
652 } else {
653 self.pos = saved_pos;
654 break;
655 }
656 }
657
658 Ok(aggregates)
659 }
660
661 fn try_parse_single_aggregate(&mut self) -> Result<Option<GroupAggregate>, DkitError> {
663 let saved_pos = self.pos;
664
665 let func_name = match self.parse_keyword() {
667 Ok(name) => name,
668 Err(_) => {
669 self.pos = saved_pos;
670 return Ok(None);
671 }
672 };
673
674 let is_known = matches!(
676 func_name.as_str(),
677 "count"
678 | "sum"
679 | "avg"
680 | "min"
681 | "max"
682 | "median"
683 | "percentile"
684 | "stddev"
685 | "variance"
686 | "mode"
687 | "group_concat"
688 );
689 if !is_known {
690 self.pos = saved_pos;
691 return Ok(None);
692 }
693
694 self.skip_whitespace();
695
696 if !self.consume_char('(') {
698 self.pos = saved_pos;
699 return Ok(None);
700 }
701
702 self.skip_whitespace();
703
704 let field = if self.peek() == Some(')') {
706 None
707 } else {
708 Some(self.parse_identifier()?)
709 };
710
711 self.skip_whitespace();
712
713 let func = match func_name.as_str() {
715 "count" => AggregateFunc::Count,
716 "sum" => AggregateFunc::Sum,
717 "avg" => AggregateFunc::Avg,
718 "min" => AggregateFunc::Min,
719 "max" => AggregateFunc::Max,
720 "median" => AggregateFunc::Median,
721 "percentile" => {
722 if !self.consume_char(',') {
723 return Err(DkitError::QueryError(format!(
724 "percentile() requires a second argument (p value) at position {}",
725 self.pos
726 )));
727 }
728 self.skip_whitespace();
729 let p = self.parse_float_value()?;
730 if !(0.0..=1.0).contains(&p) {
731 return Err(DkitError::QueryError(
732 "percentile: p must be between 0.0 and 1.0".to_string(),
733 ));
734 }
735 AggregateFunc::Percentile(p)
736 }
737 "stddev" => AggregateFunc::Stddev,
738 "variance" => AggregateFunc::Variance,
739 "mode" => AggregateFunc::Mode,
740 "group_concat" => {
741 let separator = if self.consume_char(',') {
742 self.skip_whitespace();
743 match self.parse_string_literal()? {
744 LiteralValue::String(s) => s,
745 _ => ", ".to_string(),
746 }
747 } else {
748 ", ".to_string()
749 };
750 AggregateFunc::GroupConcat(separator)
751 }
752 _ => unreachable!(),
753 };
754
755 self.skip_whitespace();
756
757 if !self.consume_char(')') {
758 return Err(DkitError::QueryError(format!(
759 "expected ')' at position {}",
760 self.pos
761 )));
762 }
763
764 let alias = match &field {
766 Some(f) => format!("{}_{}", func_name, f),
767 None => func_name.clone(),
768 };
769
770 Ok(Some(GroupAggregate { func, field, alias }))
771 }
772
773 fn parse_select_expr_list(&mut self) -> Result<Vec<SelectExpr>, DkitError> {
775 let mut exprs = vec![self.parse_select_expr()?];
776 loop {
777 self.skip_whitespace();
778 if self.consume_char(',') {
779 self.skip_whitespace();
780 exprs.push(self.parse_select_expr()?);
781 } else {
782 break;
783 }
784 }
785 Ok(exprs)
786 }
787
788 fn parse_select_expr(&mut self) -> Result<SelectExpr, DkitError> {
790 let expr = self.parse_expr()?;
791 self.skip_whitespace();
792 let alias = {
794 let saved = self.pos;
795 if let Ok(keyword) = self.parse_keyword() {
796 if keyword == "as" {
797 self.skip_whitespace();
798 Some(self.parse_identifier()?)
799 } else {
800 self.pos = saved;
801 None
802 }
803 } else {
804 self.pos = saved;
805 None
806 }
807 };
808 Ok(SelectExpr { expr, alias })
809 }
810
811 fn parse_expr(&mut self) -> Result<Expr, DkitError> {
813 self.parse_additive_expr()
814 }
815
816 fn parse_additive_expr(&mut self) -> Result<Expr, DkitError> {
818 let mut left = self.parse_multiplicative_expr()?;
819
820 loop {
821 self.skip_whitespace();
822 match self.peek() {
823 Some('+') => {
824 self.advance();
825 self.skip_whitespace();
826 let right = self.parse_multiplicative_expr()?;
827 left = Expr::BinaryOp {
828 op: ArithmeticOp::Add,
829 left: Box::new(left),
830 right: Box::new(right),
831 };
832 }
833 Some('-') => {
834 self.advance();
837 self.skip_whitespace();
838 let right = self.parse_multiplicative_expr()?;
839 left = Expr::BinaryOp {
840 op: ArithmeticOp::Sub,
841 left: Box::new(left),
842 right: Box::new(right),
843 };
844 }
845 _ => break,
846 }
847 }
848
849 Ok(left)
850 }
851
852 fn parse_multiplicative_expr(&mut self) -> Result<Expr, DkitError> {
854 let mut left = self.parse_atom_expr()?;
855
856 loop {
857 self.skip_whitespace();
858 match self.peek() {
859 Some('*') => {
860 self.advance();
861 self.skip_whitespace();
862 let right = self.parse_atom_expr()?;
863 left = Expr::BinaryOp {
864 op: ArithmeticOp::Mul,
865 left: Box::new(left),
866 right: Box::new(right),
867 };
868 }
869 Some('/') => {
870 self.advance();
871 self.skip_whitespace();
872 let right = self.parse_atom_expr()?;
873 left = Expr::BinaryOp {
874 op: ArithmeticOp::Div,
875 left: Box::new(left),
876 right: Box::new(right),
877 };
878 }
879 _ => break,
880 }
881 }
882
883 Ok(left)
884 }
885
886 fn parse_atom_expr(&mut self) -> Result<Expr, DkitError> {
888 match self.peek() {
889 Some('(') => {
890 self.advance(); self.skip_whitespace();
892 let expr = self.parse_expr()?;
893 self.skip_whitespace();
894 if !self.consume_char(')') {
895 return Err(DkitError::QueryError(format!(
896 "expected ')' at position {}",
897 self.pos
898 )));
899 }
900 Ok(expr)
901 }
902 Some('"') => {
903 let lit = self.parse_string_literal()?;
904 Ok(Expr::Literal(lit))
905 }
906 Some(c) if c.is_ascii_digit() => {
907 let lit = self.parse_number_literal()?;
908 Ok(Expr::Literal(lit))
909 }
910 Some(c) if c.is_alphabetic() || c == '_' => {
911 let name = self.parse_identifier()?;
912 match name.as_str() {
914 "true" => return Ok(Expr::Literal(LiteralValue::Bool(true))),
915 "false" => return Ok(Expr::Literal(LiteralValue::Bool(false))),
916 "null" => return Ok(Expr::Literal(LiteralValue::Null)),
917 "if" => return self.parse_if_expr(),
919 "case" => return self.parse_case_expr(),
921 _ => {}
922 }
923 if self.peek() == Some('(') {
925 self.advance(); self.skip_whitespace();
927 let mut args = Vec::new();
928 if self.peek() != Some(')') {
929 args.push(self.parse_expr()?);
930 loop {
931 self.skip_whitespace();
932 if self.consume_char(',') {
933 self.skip_whitespace();
934 args.push(self.parse_expr()?);
935 } else {
936 break;
937 }
938 }
939 }
940 self.skip_whitespace();
941 if !self.consume_char(')') {
942 return Err(DkitError::QueryError(format!(
943 "expected ')' at position {}",
944 self.pos
945 )));
946 }
947 Ok(Expr::FuncCall { name, args })
948 } else {
949 Ok(Expr::Field(name))
950 }
951 }
952 Some(c) => Err(DkitError::QueryError(format!(
953 "expected expression at position {}, found '{}'",
954 self.pos, c
955 ))),
956 None => Err(DkitError::QueryError(format!(
957 "expected expression at position {}",
958 self.pos
959 ))),
960 }
961 }
962
963 fn parse_if_expr(&mut self) -> Result<Expr, DkitError> {
965 self.skip_whitespace();
966 if !self.consume_char('(') {
967 return Err(DkitError::QueryError(format!(
968 "expected '(' after 'if' at position {}",
969 self.pos
970 )));
971 }
972 self.skip_whitespace();
973 let condition = self.parse_condition()?;
974 self.skip_whitespace();
975 if !self.consume_char(',') {
976 return Err(DkitError::QueryError(format!(
977 "expected ',' after condition in if() at position {}",
978 self.pos
979 )));
980 }
981 self.skip_whitespace();
982 let then_expr = self.parse_expr()?;
983 self.skip_whitespace();
984 if !self.consume_char(',') {
985 return Err(DkitError::QueryError(format!(
986 "expected ',' after then-expression in if() at position {}",
987 self.pos
988 )));
989 }
990 self.skip_whitespace();
991 let else_expr = self.parse_expr()?;
992 self.skip_whitespace();
993 if !self.consume_char(')') {
994 return Err(DkitError::QueryError(format!(
995 "expected ')' at position {}",
996 self.pos
997 )));
998 }
999 Ok(Expr::If {
1000 condition,
1001 then_expr: Box::new(then_expr),
1002 else_expr: Box::new(else_expr),
1003 })
1004 }
1005
1006 fn parse_case_expr(&mut self) -> Result<Expr, DkitError> {
1008 let mut branches = Vec::new();
1009 let mut default = None;
1010
1011 loop {
1012 self.skip_whitespace();
1013 let saved_pos = self.pos;
1014 let keyword = self.parse_keyword().unwrap_or_default();
1015 match keyword.as_str() {
1016 "when" => {
1017 self.skip_whitespace();
1018 let condition = self.parse_condition()?;
1019 self.skip_whitespace();
1020 let then_kw = self.parse_keyword()?;
1021 if then_kw != "then" {
1022 return Err(DkitError::QueryError(format!(
1023 "expected 'then' after condition in case expression, found '{}'",
1024 then_kw
1025 )));
1026 }
1027 self.skip_whitespace();
1028 let expr = self.parse_expr()?;
1029 branches.push((condition, expr));
1030 }
1031 "else" => {
1032 self.skip_whitespace();
1033 default = Some(Box::new(self.parse_expr()?));
1034 }
1035 "end" => {
1036 break;
1037 }
1038 _ => {
1039 self.pos = saved_pos;
1040 return Err(DkitError::QueryError(format!(
1041 "expected 'when', 'else', or 'end' in case expression at position {}",
1042 self.pos
1043 )));
1044 }
1045 }
1046 }
1047
1048 if branches.is_empty() {
1049 return Err(DkitError::QueryError(
1050 "case expression requires at least one 'when' branch".to_string(),
1051 ));
1052 }
1053
1054 Ok(Expr::Case { branches, default })
1055 }
1056
1057 fn parse_identifier_list(&mut self) -> Result<Vec<String>, DkitError> {
1059 let mut fields = vec![self.parse_identifier()?];
1060 loop {
1061 self.skip_whitespace();
1062 if self.consume_char(',') {
1063 self.skip_whitespace();
1064 fields.push(self.parse_identifier()?);
1065 } else {
1066 break;
1067 }
1068 }
1069 Ok(fields)
1070 }
1071
1072 fn parse_keyword(&mut self) -> Result<String, DkitError> {
1074 let start = self.pos;
1075 while !self.is_at_end() {
1076 let c = self.input[self.pos];
1077 if c.is_alphabetic() || c == '_' {
1078 self.pos += 1;
1079 } else {
1080 break;
1081 }
1082 }
1083 if self.pos == start {
1084 return Err(DkitError::QueryError(format!(
1085 "expected operation keyword at position {}",
1086 self.pos
1087 )));
1088 }
1089 Ok(self.input[start..self.pos].iter().collect())
1090 }
1091
1092 fn parse_condition(&mut self) -> Result<Condition, DkitError> {
1094 let mut left = Condition::Comparison(self.parse_comparison()?);
1095
1096 loop {
1097 self.skip_whitespace();
1098 let saved_pos = self.pos;
1099 if let Ok(keyword) = self.parse_keyword() {
1100 match keyword.as_str() {
1101 "and" => {
1102 self.skip_whitespace();
1103 let right = Condition::Comparison(self.parse_comparison()?);
1104 left = Condition::And(Box::new(left), Box::new(right));
1105 }
1106 "or" => {
1107 self.skip_whitespace();
1108 let right = Condition::Comparison(self.parse_comparison()?);
1109 left = Condition::Or(Box::new(left), Box::new(right));
1110 }
1111 _ => {
1112 self.pos = saved_pos;
1114 break;
1115 }
1116 }
1117 } else {
1118 break;
1119 }
1120 }
1121
1122 Ok(left)
1123 }
1124
1125 fn parse_comparison(&mut self) -> Result<Comparison, DkitError> {
1128 let field = self.parse_identifier()?;
1130 self.skip_whitespace();
1131
1132 let saved_pos = self.pos;
1134 if let Ok(keyword) = self.parse_keyword() {
1135 match keyword.as_str() {
1136 "in" => {
1137 self.skip_whitespace();
1138 let list = self.parse_literal_list()?;
1139 return Ok(Comparison {
1140 field,
1141 op: CompareOp::In,
1142 value: LiteralValue::List(list),
1143 });
1144 }
1145 "not" => {
1146 self.skip_whitespace();
1147 let saved_pos2 = self.pos;
1148 if let Ok(kw2) = self.parse_keyword() {
1149 if kw2 == "in" {
1150 self.skip_whitespace();
1151 let list = self.parse_literal_list()?;
1152 return Ok(Comparison {
1153 field,
1154 op: CompareOp::NotIn,
1155 value: LiteralValue::List(list),
1156 });
1157 } else if kw2 == "matches" {
1158 self.skip_whitespace();
1159 let value = self.parse_literal_value()?;
1160 return Ok(Comparison {
1161 field,
1162 op: CompareOp::NotMatches,
1163 value,
1164 });
1165 }
1166 }
1167 self.pos = saved_pos2;
1168 self.pos = saved_pos;
1169 }
1170 _ => {
1171 self.pos = saved_pos;
1172 }
1173 }
1174 } else {
1175 self.pos = saved_pos;
1176 }
1177
1178 let op = self.parse_compare_op()?;
1180 self.skip_whitespace();
1181
1182 let value = self.parse_literal_value()?;
1184
1185 Ok(Comparison { field, op, value })
1186 }
1187
1188 fn parse_identifier(&mut self) -> Result<String, DkitError> {
1190 let start = self.pos;
1191 while !self.is_at_end() {
1192 let c = self.input[self.pos];
1193 if c.is_alphanumeric() || c == '_' || c == '-' {
1194 self.pos += 1;
1195 } else {
1196 break;
1197 }
1198 }
1199 if self.pos == start {
1200 return Err(DkitError::QueryError(format!(
1201 "expected field name at position {}",
1202 self.pos
1203 )));
1204 }
1205 Ok(self.input[start..self.pos].iter().collect())
1206 }
1207
1208 fn parse_compare_op(&mut self) -> Result<CompareOp, DkitError> {
1210 let c1 = self.peek().ok_or_else(|| {
1211 DkitError::QueryError(format!(
1212 "expected comparison operator at position {}",
1213 self.pos
1214 ))
1215 })?;
1216
1217 match c1 {
1218 '=' => {
1219 self.advance();
1220 if self.consume_char('=') {
1221 Ok(CompareOp::Eq)
1222 } else {
1223 Err(DkitError::QueryError(format!(
1224 "expected '==' at position {}",
1225 self.pos - 1
1226 )))
1227 }
1228 }
1229 '!' => {
1230 self.advance();
1231 if self.consume_char('=') {
1232 Ok(CompareOp::Ne)
1233 } else {
1234 Err(DkitError::QueryError(format!(
1235 "expected '!=' at position {}",
1236 self.pos - 1
1237 )))
1238 }
1239 }
1240 '>' => {
1241 self.advance();
1242 if self.consume_char('=') {
1243 Ok(CompareOp::Ge)
1244 } else {
1245 Ok(CompareOp::Gt)
1246 }
1247 }
1248 '<' => {
1249 self.advance();
1250 if self.consume_char('=') {
1251 Ok(CompareOp::Le)
1252 } else {
1253 Ok(CompareOp::Lt)
1254 }
1255 }
1256 c if c.is_alphabetic() => {
1257 let saved_pos = self.pos;
1258 let keyword = self.parse_keyword()?;
1259 match keyword.as_str() {
1260 "contains" => Ok(CompareOp::Contains),
1261 "starts_with" => Ok(CompareOp::StartsWith),
1262 "ends_with" => Ok(CompareOp::EndsWith),
1263 "matches" => Ok(CompareOp::Matches),
1264 _ => {
1265 self.pos = saved_pos;
1266 Err(DkitError::QueryError(format!(
1267 "expected comparison operator at position {}, found '{}'",
1268 saved_pos, keyword
1269 )))
1270 }
1271 }
1272 }
1273 _ => Err(DkitError::QueryError(format!(
1274 "expected comparison operator at position {}, found '{}'",
1275 self.pos, c1
1276 ))),
1277 }
1278 }
1279
1280 fn parse_literal_value(&mut self) -> Result<LiteralValue, DkitError> {
1282 match self.peek() {
1283 Some('"') => self.parse_string_literal(),
1284 Some(c) if c.is_ascii_digit() || c == '-' => self.parse_number_literal(),
1285 Some(c) if c.is_alphabetic() => {
1286 let word = self.parse_keyword()?;
1287 match word.as_str() {
1288 "true" => Ok(LiteralValue::Bool(true)),
1289 "false" => Ok(LiteralValue::Bool(false)),
1290 "null" => Ok(LiteralValue::Null),
1291 _ => Err(DkitError::QueryError(format!(
1292 "unexpected value '{}' at position {}",
1293 word,
1294 self.pos - word.len()
1295 ))),
1296 }
1297 }
1298 Some(c) => Err(DkitError::QueryError(format!(
1299 "unexpected character '{}' at position {}",
1300 c, self.pos
1301 ))),
1302 None => Err(DkitError::QueryError(format!(
1303 "expected value at position {}",
1304 self.pos
1305 ))),
1306 }
1307 }
1308
1309 fn parse_literal_list(&mut self) -> Result<Vec<LiteralValue>, DkitError> {
1311 if !self.consume_char('(') {
1312 return Err(DkitError::QueryError(format!(
1313 "expected '(' at position {}",
1314 self.pos
1315 )));
1316 }
1317
1318 let mut values = Vec::new();
1319 self.skip_whitespace();
1320
1321 if self.peek() == Some(')') {
1323 self.advance();
1324 return Ok(values);
1325 }
1326
1327 values.push(self.parse_literal_value()?);
1329
1330 loop {
1331 self.skip_whitespace();
1332 if self.consume_char(')') {
1333 break;
1334 }
1335 if !self.consume_char(',') {
1336 return Err(DkitError::QueryError(format!(
1337 "expected ',' or ')' at position {}",
1338 self.pos
1339 )));
1340 }
1341 self.skip_whitespace();
1342 values.push(self.parse_literal_value()?);
1343 }
1344
1345 Ok(values)
1346 }
1347
1348 fn parse_string_literal(&mut self) -> Result<LiteralValue, DkitError> {
1350 if !self.consume_char('"') {
1351 return Err(DkitError::QueryError(format!(
1352 "expected '\"' at position {}",
1353 self.pos
1354 )));
1355 }
1356 let start = self.pos;
1357 while !self.is_at_end() && self.input[self.pos] != '"' {
1358 self.pos += 1;
1359 }
1360 if self.is_at_end() {
1361 return Err(DkitError::QueryError(format!(
1362 "unterminated string starting at position {}",
1363 start - 1
1364 )));
1365 }
1366 let s: String = self.input[start..self.pos].iter().collect();
1367 self.advance(); Ok(LiteralValue::String(s))
1369 }
1370
1371 fn parse_number_literal(&mut self) -> Result<LiteralValue, DkitError> {
1373 let start = self.pos;
1374 if self.peek() == Some('-') {
1375 self.advance();
1376 }
1377 while !self.is_at_end() && self.input[self.pos].is_ascii_digit() {
1378 self.pos += 1;
1379 }
1380 let mut is_float = false;
1381 if self.peek() == Some('.') {
1382 is_float = true;
1383 self.advance();
1384 while !self.is_at_end() && self.input[self.pos].is_ascii_digit() {
1385 self.pos += 1;
1386 }
1387 }
1388 if self.pos == start || (self.pos == start + 1 && self.input[start] == '-') {
1389 return Err(DkitError::QueryError(format!(
1390 "expected number at position {}",
1391 start
1392 )));
1393 }
1394 let num_str: String = self.input[start..self.pos].iter().collect();
1395 if is_float {
1396 let f: f64 = num_str.parse().map_err(|_| {
1397 DkitError::QueryError(format!(
1398 "invalid number '{}' at position {}",
1399 num_str, start
1400 ))
1401 })?;
1402 Ok(LiteralValue::Float(f))
1403 } else {
1404 let n: i64 = num_str.parse().map_err(|_| {
1405 DkitError::QueryError(format!(
1406 "invalid number '{}' at position {}",
1407 num_str, start
1408 ))
1409 })?;
1410 Ok(LiteralValue::Integer(n))
1411 }
1412 }
1413
1414 fn peek(&self) -> Option<char> {
1417 self.input.get(self.pos).copied()
1418 }
1419
1420 fn peek_is_identifier_start(&self) -> bool {
1421 self.peek().is_some_and(|c| c.is_alphabetic() || c == '_')
1422 }
1423
1424 fn advance(&mut self) {
1425 self.pos += 1;
1426 }
1427
1428 fn consume_char(&mut self, expected: char) -> bool {
1429 if self.peek() == Some(expected) {
1430 self.advance();
1431 true
1432 } else {
1433 false
1434 }
1435 }
1436
1437 fn skip_whitespace(&mut self) {
1438 while self.peek().is_some_and(|c| c.is_whitespace()) {
1439 self.advance();
1440 }
1441 }
1442
1443 fn is_at_end(&self) -> bool {
1444 self.pos >= self.input.len()
1445 }
1446
1447 fn try_parse_identifier(&mut self) -> Option<String> {
1449 if !self.peek_is_identifier_start() {
1450 return None;
1451 }
1452 let saved_pos = self.pos;
1453 match self.parse_identifier() {
1454 Ok(id) => Some(id),
1455 Err(_) => {
1456 self.pos = saved_pos;
1457 None
1458 }
1459 }
1460 }
1461
1462 fn try_consume_keyword(&mut self, keyword: &str) -> bool {
1464 let saved_pos = self.pos;
1465 if let Ok(word) = self.parse_keyword() {
1466 if word == keyword {
1467 return true;
1468 }
1469 }
1470 self.pos = saved_pos;
1471 false
1472 }
1473
1474 fn parse_float_value(&mut self) -> Result<f64, DkitError> {
1477 let start = self.pos;
1478 if self.peek() == Some('-') {
1479 self.advance();
1480 }
1481 while !self.is_at_end() && self.input[self.pos].is_ascii_digit() {
1482 self.pos += 1;
1483 }
1484 if self.peek() == Some('.') {
1485 self.advance();
1486 while !self.is_at_end() && self.input[self.pos].is_ascii_digit() {
1487 self.pos += 1;
1488 }
1489 }
1490 if self.pos == start {
1491 return Err(DkitError::QueryError(format!(
1492 "expected number at position {}",
1493 self.pos
1494 )));
1495 }
1496 let num_str: String = self.input[start..self.pos].iter().collect();
1497 num_str.parse().map_err(|_| {
1498 DkitError::QueryError(format!(
1499 "invalid number '{}' at position {}",
1500 num_str, start
1501 ))
1502 })
1503 }
1504
1505 fn parse_positive_integer(&mut self) -> Result<usize, DkitError> {
1506 let start = self.pos;
1507 while !self.is_at_end() && self.input[self.pos].is_ascii_digit() {
1508 self.pos += 1;
1509 }
1510 if self.pos == start {
1511 return Err(DkitError::QueryError(format!(
1512 "expected positive integer at position {}",
1513 self.pos
1514 )));
1515 }
1516 let num_str: String = self.input[start..self.pos].iter().collect();
1517 num_str.parse().map_err(|_| {
1518 DkitError::QueryError(format!(
1519 "invalid integer '{}' at position {}",
1520 num_str, start
1521 ))
1522 })
1523 }
1524}
1525
1526pub fn parse_query(input: &str) -> Result<Query, DkitError> {
1528 Parser::new(input).parse()
1529}
1530
1531pub fn parse_add_field_expr(input: &str) -> Result<(String, Expr), DkitError> {
1534 let mut parser = Parser::new(input);
1535 parser.skip_whitespace();
1536 let name = parser.parse_identifier().map_err(|_| {
1537 DkitError::QueryError(format!(
1538 "expected field name in --add-field expression: '{input}'"
1539 ))
1540 })?;
1541 parser.skip_whitespace();
1542 if !parser.consume_char('=') {
1543 return Err(DkitError::QueryError(format!(
1544 "expected '=' after field name in --add-field expression: '{input}'"
1545 )));
1546 }
1547 parser.skip_whitespace();
1548 let expr = parser.parse_expr()?;
1549 parser.skip_whitespace();
1550 if parser.pos != parser.input.len() {
1551 return Err(DkitError::QueryError(format!(
1552 "unexpected character '{}' at position {} in --add-field expression",
1553 parser.input[parser.pos], parser.pos
1554 )));
1555 }
1556 Ok((name, expr))
1557}
1558
1559pub fn parse_condition_expr(input: &str) -> Result<Condition, DkitError> {
1562 let mut parser = Parser::new(input);
1563 parser.skip_whitespace();
1564 let condition = parser.parse_condition()?;
1565 parser.skip_whitespace();
1566 if parser.pos != parser.input.len() {
1567 return Err(DkitError::QueryError(format!(
1568 "unexpected character '{}' at position {} in where expression",
1569 parser.input[parser.pos], parser.pos
1570 )));
1571 }
1572 Ok(condition)
1573}
1574
1575#[cfg(test)]
1576mod tests {
1577 use super::*;
1578
1579 #[test]
1582 fn test_root_path() {
1583 let q = parse_query(".").unwrap();
1584 assert!(q.path.segments.is_empty());
1585 }
1586
1587 #[test]
1588 fn test_single_field() {
1589 let q = parse_query(".name").unwrap();
1590 assert_eq!(q.path.segments, vec![Segment::Field("name".to_string())]);
1591 }
1592
1593 #[test]
1594 fn test_nested_fields() {
1595 let q = parse_query(".database.host").unwrap();
1596 assert_eq!(
1597 q.path.segments,
1598 vec![
1599 Segment::Field("database".to_string()),
1600 Segment::Field("host".to_string()),
1601 ]
1602 );
1603 }
1604
1605 #[test]
1606 fn test_deeply_nested_fields() {
1607 let q = parse_query(".a.b.c.d").unwrap();
1608 assert_eq!(q.path.segments.len(), 4);
1609 assert_eq!(q.path.segments[3], Segment::Field("d".to_string()));
1610 }
1611
1612 #[test]
1615 fn test_array_index() {
1616 let q = parse_query(".users[0]").unwrap();
1617 assert_eq!(
1618 q.path.segments,
1619 vec![Segment::Field("users".to_string()), Segment::Index(0),]
1620 );
1621 }
1622
1623 #[test]
1624 fn test_array_negative_index() {
1625 let q = parse_query(".users[-1]").unwrap();
1626 assert_eq!(
1627 q.path.segments,
1628 vec![Segment::Field("users".to_string()), Segment::Index(-1),]
1629 );
1630 }
1631
1632 #[test]
1633 fn test_array_index_with_field_after() {
1634 let q = parse_query(".users[0].name").unwrap();
1635 assert_eq!(
1636 q.path.segments,
1637 vec![
1638 Segment::Field("users".to_string()),
1639 Segment::Index(0),
1640 Segment::Field("name".to_string()),
1641 ]
1642 );
1643 }
1644
1645 #[test]
1646 fn test_large_index() {
1647 let q = parse_query(".items[999]").unwrap();
1648 assert_eq!(
1649 q.path.segments,
1650 vec![Segment::Field("items".to_string()), Segment::Index(999),]
1651 );
1652 }
1653
1654 #[test]
1657 fn test_array_iterate() {
1658 let q = parse_query(".users[]").unwrap();
1659 assert_eq!(
1660 q.path.segments,
1661 vec![Segment::Field("users".to_string()), Segment::Iterate,]
1662 );
1663 }
1664
1665 #[test]
1666 fn test_array_iterate_with_field() {
1667 let q = parse_query(".users[].name").unwrap();
1668 assert_eq!(
1669 q.path.segments,
1670 vec![
1671 Segment::Field("users".to_string()),
1672 Segment::Iterate,
1673 Segment::Field("name".to_string()),
1674 ]
1675 );
1676 }
1677
1678 #[test]
1679 fn test_array_iterate_nested() {
1680 let q = parse_query(".data[].items[].name").unwrap();
1681 assert_eq!(
1682 q.path.segments,
1683 vec![
1684 Segment::Field("data".to_string()),
1685 Segment::Iterate,
1686 Segment::Field("items".to_string()),
1687 Segment::Iterate,
1688 Segment::Field("name".to_string()),
1689 ]
1690 );
1691 }
1692
1693 #[test]
1696 fn test_array_wildcard() {
1697 let q = parse_query(".[*]").unwrap();
1698 assert_eq!(q.path.segments, vec![Segment::Wildcard]);
1699 }
1700
1701 #[test]
1702 fn test_array_wildcard_with_field() {
1703 let q = parse_query(".users[*].name").unwrap();
1704 assert_eq!(
1705 q.path.segments,
1706 vec![
1707 Segment::Field("users".to_string()),
1708 Segment::Wildcard,
1709 Segment::Field("name".to_string()),
1710 ]
1711 );
1712 }
1713
1714 #[test]
1717 fn test_array_slice_basic() {
1718 let q = parse_query(".[0:3]").unwrap();
1719 assert_eq!(
1720 q.path.segments,
1721 vec![Segment::Slice {
1722 start: Some(0),
1723 end: Some(3),
1724 step: None
1725 }]
1726 );
1727 }
1728
1729 #[test]
1730 fn test_array_slice_open_end() {
1731 let q = parse_query(".[1:]").unwrap();
1732 assert_eq!(
1733 q.path.segments,
1734 vec![Segment::Slice {
1735 start: Some(1),
1736 end: None,
1737 step: None
1738 }]
1739 );
1740 }
1741
1742 #[test]
1743 fn test_array_slice_open_start() {
1744 let q = parse_query(".[:3]").unwrap();
1745 assert_eq!(
1746 q.path.segments,
1747 vec![Segment::Slice {
1748 start: None,
1749 end: Some(3),
1750 step: None
1751 }]
1752 );
1753 }
1754
1755 #[test]
1756 fn test_array_slice_negative() {
1757 let q = parse_query(".[-2:]").unwrap();
1758 assert_eq!(
1759 q.path.segments,
1760 vec![Segment::Slice {
1761 start: Some(-2),
1762 end: None,
1763 step: None
1764 }]
1765 );
1766 }
1767
1768 #[test]
1769 fn test_array_slice_with_step() {
1770 let q = parse_query(".[1:5:2]").unwrap();
1771 assert_eq!(
1772 q.path.segments,
1773 vec![Segment::Slice {
1774 start: Some(1),
1775 end: Some(5),
1776 step: Some(2)
1777 }]
1778 );
1779 }
1780
1781 #[test]
1782 fn test_array_slice_full_open() {
1783 let q = parse_query(".[:]").unwrap();
1784 assert_eq!(
1785 q.path.segments,
1786 vec![Segment::Slice {
1787 start: None,
1788 end: None,
1789 step: None
1790 }]
1791 );
1792 }
1793
1794 #[test]
1795 fn test_array_slice_with_field() {
1796 let q = parse_query(".users[0:3].name").unwrap();
1797 assert_eq!(
1798 q.path.segments,
1799 vec![
1800 Segment::Field("users".to_string()),
1801 Segment::Slice {
1802 start: Some(0),
1803 end: Some(3),
1804 step: None
1805 },
1806 Segment::Field("name".to_string()),
1807 ]
1808 );
1809 }
1810
1811 #[test]
1812 fn test_array_slice_reverse_step() {
1813 let q = parse_query(".[::-1]").unwrap();
1814 assert_eq!(
1815 q.path.segments,
1816 vec![Segment::Slice {
1817 start: None,
1818 end: None,
1819 step: Some(-1)
1820 }]
1821 );
1822 }
1823
1824 #[test]
1827 fn test_complex_path() {
1828 let q = parse_query(".data.users[0].address.city").unwrap();
1829 assert_eq!(
1830 q.path.segments,
1831 vec![
1832 Segment::Field("data".to_string()),
1833 Segment::Field("users".to_string()),
1834 Segment::Index(0),
1835 Segment::Field("address".to_string()),
1836 Segment::Field("city".to_string()),
1837 ]
1838 );
1839 }
1840
1841 #[test]
1844 fn test_field_with_underscore() {
1845 let q = parse_query(".user_name").unwrap();
1846 assert_eq!(
1847 q.path.segments,
1848 vec![Segment::Field("user_name".to_string())]
1849 );
1850 }
1851
1852 #[test]
1853 fn test_field_with_hyphen() {
1854 let q = parse_query(".content-type").unwrap();
1855 assert_eq!(
1856 q.path.segments,
1857 vec![Segment::Field("content-type".to_string())]
1858 );
1859 }
1860
1861 #[test]
1862 fn test_field_with_digits() {
1863 let q = parse_query(".field1").unwrap();
1864 assert_eq!(q.path.segments, vec![Segment::Field("field1".to_string())]);
1865 }
1866
1867 #[test]
1870 fn test_error_no_dot() {
1871 let err = parse_query("name").unwrap_err();
1872 assert!(matches!(err, DkitError::QueryError(_)));
1873 }
1874
1875 #[test]
1876 fn test_error_empty() {
1877 let err = parse_query("").unwrap_err();
1878 assert!(matches!(err, DkitError::QueryError(_)));
1879 }
1880
1881 #[test]
1882 fn test_error_unclosed_bracket() {
1883 let err = parse_query(".users[0").unwrap_err();
1884 assert!(matches!(err, DkitError::QueryError(_)));
1885 }
1886
1887 #[test]
1888 fn test_error_invalid_index() {
1889 let err = parse_query(".users[abc]").unwrap_err();
1890 assert!(matches!(err, DkitError::QueryError(_)));
1891 }
1892
1893 #[test]
1894 fn test_error_trailing_garbage() {
1895 let err = parse_query(".name xyz").unwrap_err();
1896 assert!(matches!(err, DkitError::QueryError(_)));
1897 }
1898
1899 #[test]
1902 fn test_whitespace_around() {
1903 let q = parse_query(" .name ").unwrap();
1904 assert_eq!(q.path.segments, vec![Segment::Field("name".to_string())]);
1905 }
1906
1907 #[test]
1910 fn test_root_array_index() {
1911 let q = parse_query(".[0]").unwrap();
1912 assert_eq!(q.path.segments, vec![Segment::Index(0)]);
1913 }
1914
1915 #[test]
1916 fn test_root_array_iterate() {
1917 let q = parse_query(".[]").unwrap();
1918 assert_eq!(q.path.segments, vec![Segment::Iterate]);
1919 }
1920
1921 #[test]
1922 fn test_root_iterate_with_field() {
1923 let q = parse_query(".[].name").unwrap();
1924 assert_eq!(
1925 q.path.segments,
1926 vec![Segment::Iterate, Segment::Field("name".to_string()),]
1927 );
1928 }
1929
1930 #[test]
1933 fn test_where_eq_integer() {
1934 let q = parse_query(".users[] | where age == 30").unwrap();
1935 assert_eq!(
1936 q.path.segments,
1937 vec![Segment::Field("users".to_string()), Segment::Iterate]
1938 );
1939 assert_eq!(q.operations.len(), 1);
1940 assert_eq!(
1941 q.operations[0],
1942 Operation::Where(Condition::Comparison(Comparison {
1943 field: "age".to_string(),
1944 op: CompareOp::Eq,
1945 value: LiteralValue::Integer(30),
1946 }))
1947 );
1948 }
1949
1950 #[test]
1951 fn test_where_ne_string() {
1952 let q = parse_query(".items[] | where status != \"inactive\"").unwrap();
1953 assert_eq!(
1954 q.operations[0],
1955 Operation::Where(Condition::Comparison(Comparison {
1956 field: "status".to_string(),
1957 op: CompareOp::Ne,
1958 value: LiteralValue::String("inactive".to_string()),
1959 }))
1960 );
1961 }
1962
1963 #[test]
1964 fn test_where_gt() {
1965 let q = parse_query(".[] | where age > 25").unwrap();
1966 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1967 panic!("expected Comparison");
1968 };
1969 assert_eq!(cmp.field, "age");
1970 assert_eq!(cmp.op, CompareOp::Gt);
1971 assert_eq!(cmp.value, LiteralValue::Integer(25));
1972 }
1973
1974 #[test]
1975 fn test_where_lt() {
1976 let q = parse_query(".[] | where price < 100").unwrap();
1977 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1978 panic!("expected Comparison");
1979 };
1980 assert_eq!(cmp.op, CompareOp::Lt);
1981 assert_eq!(cmp.value, LiteralValue::Integer(100));
1982 }
1983
1984 #[test]
1985 fn test_where_ge() {
1986 let q = parse_query(".[] | where score >= 80").unwrap();
1987 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1988 panic!("expected Comparison");
1989 };
1990 assert_eq!(cmp.op, CompareOp::Ge);
1991 assert_eq!(cmp.value, LiteralValue::Integer(80));
1992 }
1993
1994 #[test]
1995 fn test_where_le() {
1996 let q = parse_query(".[] | where price <= 1000").unwrap();
1997 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1998 panic!("expected Comparison");
1999 };
2000 assert_eq!(cmp.op, CompareOp::Le);
2001 assert_eq!(cmp.value, LiteralValue::Integer(1000));
2002 }
2003
2004 #[test]
2005 fn test_where_float_literal() {
2006 let q = parse_query(".[] | where score > 3.14").unwrap();
2007 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2008 panic!("expected Comparison");
2009 };
2010 assert_eq!(cmp.value, LiteralValue::Float(3.14));
2011 }
2012
2013 #[test]
2014 fn test_where_negative_number() {
2015 let q = parse_query(".[] | where temp > -10").unwrap();
2016 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2017 panic!("expected Comparison");
2018 };
2019 assert_eq!(cmp.value, LiteralValue::Integer(-10));
2020 }
2021
2022 #[test]
2023 fn test_where_bool_literal() {
2024 let q = parse_query(".[] | where active == true").unwrap();
2025 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2026 panic!("expected Comparison");
2027 };
2028 assert_eq!(cmp.value, LiteralValue::Bool(true));
2029 }
2030
2031 #[test]
2032 fn test_where_null_literal() {
2033 let q = parse_query(".[] | where value == null").unwrap();
2034 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2035 panic!("expected Comparison");
2036 };
2037 assert_eq!(cmp.value, LiteralValue::Null);
2038 }
2039
2040 #[test]
2041 fn test_where_no_operations_for_path_only() {
2042 let q = parse_query(".users[0].name").unwrap();
2043 assert!(q.operations.is_empty());
2044 }
2045
2046 #[test]
2047 fn test_where_with_extra_whitespace() {
2048 let q = parse_query(".[] | where age > 30 ").unwrap();
2049 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2050 panic!("expected Comparison");
2051 };
2052 assert_eq!(cmp.field, "age");
2053 assert_eq!(cmp.op, CompareOp::Gt);
2054 assert_eq!(cmp.value, LiteralValue::Integer(30));
2055 }
2056
2057 #[test]
2060 fn test_error_where_missing_field() {
2061 let err = parse_query(".[] | where == 30").unwrap_err();
2062 assert!(matches!(err, DkitError::QueryError(_)));
2063 }
2064
2065 #[test]
2066 fn test_error_where_missing_operator() {
2067 let err = parse_query(".[] | where age 30").unwrap_err();
2068 assert!(matches!(err, DkitError::QueryError(_)));
2069 }
2070
2071 #[test]
2072 fn test_error_where_missing_value() {
2073 let err = parse_query(".[] | where age >").unwrap_err();
2074 assert!(matches!(err, DkitError::QueryError(_)));
2075 }
2076
2077 #[test]
2078 fn test_error_where_unterminated_string() {
2079 let err = parse_query(".[] | where name == \"hello").unwrap_err();
2080 assert!(matches!(err, DkitError::QueryError(_)));
2081 }
2082
2083 #[test]
2084 fn test_error_unknown_operation() {
2085 let err = parse_query(".[] | foobar age > 30").unwrap_err();
2086 assert!(matches!(err, DkitError::QueryError(_)));
2087 }
2088
2089 #[test]
2092 fn test_where_contains() {
2093 let q = parse_query(".[] | where email contains \"@gmail\"").unwrap();
2094 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2095 panic!("expected Comparison");
2096 };
2097 assert_eq!(cmp.field, "email");
2098 assert_eq!(cmp.op, CompareOp::Contains);
2099 assert_eq!(cmp.value, LiteralValue::String("@gmail".to_string()));
2100 }
2101
2102 #[test]
2103 fn test_where_starts_with() {
2104 let q = parse_query(".[] | where name starts_with \"A\"").unwrap();
2105 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2106 panic!("expected Comparison");
2107 };
2108 assert_eq!(cmp.field, "name");
2109 assert_eq!(cmp.op, CompareOp::StartsWith);
2110 assert_eq!(cmp.value, LiteralValue::String("A".to_string()));
2111 }
2112
2113 #[test]
2114 fn test_where_ends_with() {
2115 let q = parse_query(".[] | where file ends_with \".json\"").unwrap();
2116 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2117 panic!("expected Comparison");
2118 };
2119 assert_eq!(cmp.field, "file");
2120 assert_eq!(cmp.op, CompareOp::EndsWith);
2121 assert_eq!(cmp.value, LiteralValue::String(".json".to_string()));
2122 }
2123
2124 #[test]
2125 fn test_where_matches() {
2126 let q = parse_query(".[] | where email matches \".*@gmail\\.com$\"").unwrap();
2127 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2128 panic!("expected Comparison");
2129 };
2130 assert_eq!(cmp.field, "email");
2131 assert_eq!(cmp.op, CompareOp::Matches);
2132 assert_eq!(
2133 cmp.value,
2134 LiteralValue::String(".*@gmail\\.com$".to_string())
2135 );
2136 }
2137
2138 #[test]
2139 fn test_where_not_matches() {
2140 let q = parse_query(".[] | where name not matches \"^test_\"").unwrap();
2141 let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2142 panic!("expected Comparison");
2143 };
2144 assert_eq!(cmp.field, "name");
2145 assert_eq!(cmp.op, CompareOp::NotMatches);
2146 assert_eq!(cmp.value, LiteralValue::String("^test_".to_string()));
2147 }
2148
2149 #[test]
2152 fn test_where_and() {
2153 let q = parse_query(".[] | where age > 25 and city == \"Seoul\"").unwrap();
2154 let Operation::Where(cond) = &q.operations[0] else {
2155 panic!("expected Where operation");
2156 };
2157 match cond {
2158 Condition::And(left, right) => {
2159 let Condition::Comparison(l) = left.as_ref() else {
2160 panic!("expected left Comparison");
2161 };
2162 assert_eq!(l.field, "age");
2163 assert_eq!(l.op, CompareOp::Gt);
2164 assert_eq!(l.value, LiteralValue::Integer(25));
2165 let Condition::Comparison(r) = right.as_ref() else {
2166 panic!("expected right Comparison");
2167 };
2168 assert_eq!(r.field, "city");
2169 assert_eq!(r.op, CompareOp::Eq);
2170 assert_eq!(r.value, LiteralValue::String("Seoul".to_string()));
2171 }
2172 _ => panic!("expected And condition"),
2173 }
2174 }
2175
2176 #[test]
2177 fn test_where_or() {
2178 let q = parse_query(".[] | where role == \"admin\" or role == \"manager\"").unwrap();
2179 let Operation::Where(cond) = &q.operations[0] else {
2180 panic!("expected Where operation");
2181 };
2182 match cond {
2183 Condition::Or(left, right) => {
2184 let Condition::Comparison(l) = left.as_ref() else {
2185 panic!("expected left Comparison");
2186 };
2187 assert_eq!(l.field, "role");
2188 assert_eq!(l.value, LiteralValue::String("admin".to_string()));
2189 let Condition::Comparison(r) = right.as_ref() else {
2190 panic!("expected right Comparison");
2191 };
2192 assert_eq!(r.field, "role");
2193 assert_eq!(r.value, LiteralValue::String("manager".to_string()));
2194 }
2195 _ => panic!("expected Or condition"),
2196 }
2197 }
2198
2199 #[test]
2200 fn test_where_and_with_string_op() {
2201 let q = parse_query(".[] | where name starts_with \"A\" and age > 20").unwrap();
2202 let Operation::Where(cond) = &q.operations[0] else {
2203 panic!("expected Where operation");
2204 };
2205 assert!(matches!(cond, Condition::And(_, _)));
2206 }
2207
2208 #[test]
2209 fn test_where_chained_and() {
2210 let q = parse_query(".[] | where a == 1 and b == 2 and c == 3").unwrap();
2211 let Operation::Where(cond) = &q.operations[0] else {
2212 panic!("expected Where operation");
2213 };
2214 match cond {
2216 Condition::And(left, right) => {
2217 assert!(matches!(left.as_ref(), Condition::And(_, _)));
2218 assert!(matches!(right.as_ref(), Condition::Comparison(_)));
2219 }
2220 _ => panic!("expected And condition"),
2221 }
2222 }
2223
2224 fn field(name: &str) -> SelectExpr {
2227 SelectExpr {
2228 expr: Expr::Field(name.to_string()),
2229 alias: None,
2230 }
2231 }
2232
2233 fn fields(names: &[&str]) -> Operation {
2234 Operation::Select(names.iter().map(|n| field(n)).collect())
2235 }
2236
2237 #[test]
2238 fn test_select_single_field() {
2239 let q = parse_query(".users[] | select name").unwrap();
2240 assert_eq!(q.operations.len(), 1);
2241 assert_eq!(q.operations[0], fields(&["name"]));
2242 }
2243
2244 #[test]
2245 fn test_select_multiple_fields() {
2246 let q = parse_query(".users[] | select name, email").unwrap();
2247 assert_eq!(q.operations[0], fields(&["name", "email"]));
2248 }
2249
2250 #[test]
2251 fn test_select_three_fields() {
2252 let q = parse_query(".users[] | select name, age, email").unwrap();
2253 assert_eq!(q.operations[0], fields(&["name", "age", "email"]));
2254 }
2255
2256 #[test]
2257 fn test_select_with_extra_whitespace() {
2258 let q = parse_query(".[] | select name , email ").unwrap();
2259 assert_eq!(q.operations[0], fields(&["name", "email"]));
2260 }
2261
2262 #[test]
2263 fn test_select_field_with_underscore() {
2264 let q = parse_query(".[] | select user_name, created_at").unwrap();
2265 assert_eq!(q.operations[0], fields(&["user_name", "created_at"]));
2266 }
2267
2268 #[test]
2269 fn test_select_field_with_hyphen() {
2270 let q = parse_query(".[] | select content-type").unwrap();
2271 assert_eq!(q.operations[0], fields(&["content-type"]));
2272 }
2273
2274 #[test]
2275 fn test_where_then_select() {
2276 let q = parse_query(".users[] | where age > 30 | select name, email").unwrap();
2277 assert_eq!(q.operations.len(), 2);
2278 assert!(matches!(&q.operations[0], Operation::Where(_)));
2279 assert_eq!(q.operations[1], fields(&["name", "email"]));
2280 }
2281
2282 #[test]
2283 fn test_error_select_missing_fields() {
2284 let err = parse_query(".[] | select").unwrap_err();
2285 assert!(matches!(err, DkitError::QueryError(_)));
2286 }
2287
2288 #[test]
2289 fn test_select_func_single() {
2290 let q = parse_query(".[] | select upper(name)").unwrap();
2291 assert_eq!(
2292 q.operations[0],
2293 Operation::Select(vec![SelectExpr {
2294 expr: Expr::FuncCall {
2295 name: "upper".to_string(),
2296 args: vec![Expr::Field("name".to_string())],
2297 },
2298 alias: None,
2299 }])
2300 );
2301 }
2302
2303 #[test]
2304 fn test_select_func_with_alias() {
2305 let q = parse_query(".[] | select upper(name) as NAME").unwrap();
2306 assert_eq!(
2307 q.operations[0],
2308 Operation::Select(vec![SelectExpr {
2309 expr: Expr::FuncCall {
2310 name: "upper".to_string(),
2311 args: vec![Expr::Field("name".to_string())],
2312 },
2313 alias: Some("NAME".to_string()),
2314 }])
2315 );
2316 }
2317
2318 #[test]
2319 fn test_select_func_nested() {
2320 let q = parse_query(".[] | select upper(trim(name))").unwrap();
2321 assert_eq!(
2322 q.operations[0],
2323 Operation::Select(vec![SelectExpr {
2324 expr: Expr::FuncCall {
2325 name: "upper".to_string(),
2326 args: vec![Expr::FuncCall {
2327 name: "trim".to_string(),
2328 args: vec![Expr::Field("name".to_string())],
2329 }],
2330 },
2331 alias: None,
2332 }])
2333 );
2334 }
2335
2336 #[test]
2337 fn test_select_func_with_literal_arg() {
2338 let q = parse_query(".[] | select round(price, 2)").unwrap();
2339 assert_eq!(
2340 q.operations[0],
2341 Operation::Select(vec![SelectExpr {
2342 expr: Expr::FuncCall {
2343 name: "round".to_string(),
2344 args: vec![
2345 Expr::Field("price".to_string()),
2346 Expr::Literal(LiteralValue::Integer(2)),
2347 ],
2348 },
2349 alias: None,
2350 }])
2351 );
2352 }
2353
2354 #[test]
2355 fn test_select_mixed_fields_and_funcs() {
2356 let q = parse_query(".[] | select name, upper(city)").unwrap();
2357 assert_eq!(
2358 q.operations[0],
2359 Operation::Select(vec![
2360 field("name"),
2361 SelectExpr {
2362 expr: Expr::FuncCall {
2363 name: "upper".to_string(),
2364 args: vec![Expr::Field("city".to_string())],
2365 },
2366 alias: None,
2367 }
2368 ])
2369 );
2370 }
2371
2372 #[test]
2375 fn test_sort_asc() {
2376 let q = parse_query(".users[] | sort age").unwrap();
2377 assert_eq!(q.operations.len(), 1);
2378 assert_eq!(
2379 q.operations[0],
2380 Operation::Sort {
2381 field: "age".to_string(),
2382 descending: false,
2383 }
2384 );
2385 }
2386
2387 #[test]
2388 fn test_sort_desc() {
2389 let q = parse_query(".users[] | sort age desc").unwrap();
2390 assert_eq!(q.operations.len(), 1);
2391 assert_eq!(
2392 q.operations[0],
2393 Operation::Sort {
2394 field: "age".to_string(),
2395 descending: true,
2396 }
2397 );
2398 }
2399
2400 #[test]
2401 fn test_sort_with_extra_whitespace() {
2402 let q = parse_query(".[] | sort name ").unwrap();
2403 assert_eq!(
2404 q.operations[0],
2405 Operation::Sort {
2406 field: "name".to_string(),
2407 descending: false,
2408 }
2409 );
2410 }
2411
2412 #[test]
2413 fn test_sort_desc_with_extra_whitespace() {
2414 let q = parse_query(".[] | sort name desc ").unwrap();
2415 assert_eq!(
2416 q.operations[0],
2417 Operation::Sort {
2418 field: "name".to_string(),
2419 descending: true,
2420 }
2421 );
2422 }
2423
2424 #[test]
2425 fn test_sort_field_with_underscore() {
2426 let q = parse_query(".[] | sort created_at").unwrap();
2427 assert_eq!(
2428 q.operations[0],
2429 Operation::Sort {
2430 field: "created_at".to_string(),
2431 descending: false,
2432 }
2433 );
2434 }
2435
2436 #[test]
2437 fn test_error_sort_missing_field() {
2438 let err = parse_query(".[] | sort").unwrap_err();
2439 assert!(matches!(err, DkitError::QueryError(_)));
2440 }
2441
2442 #[test]
2445 fn test_limit() {
2446 let q = parse_query(".users[] | limit 10").unwrap();
2447 assert_eq!(q.operations.len(), 1);
2448 assert_eq!(q.operations[0], Operation::Limit(10));
2449 }
2450
2451 #[test]
2452 fn test_limit_one() {
2453 let q = parse_query(".[] | limit 1").unwrap();
2454 assert_eq!(q.operations[0], Operation::Limit(1));
2455 }
2456
2457 #[test]
2458 fn test_limit_with_extra_whitespace() {
2459 let q = parse_query(".[] | limit 5 ").unwrap();
2460 assert_eq!(q.operations[0], Operation::Limit(5));
2461 }
2462
2463 #[test]
2464 fn test_error_limit_missing_number() {
2465 let err = parse_query(".[] | limit").unwrap_err();
2466 assert!(matches!(err, DkitError::QueryError(_)));
2467 }
2468
2469 #[test]
2470 fn test_error_limit_negative() {
2471 let err = parse_query(".[] | limit -5").unwrap_err();
2472 assert!(matches!(err, DkitError::QueryError(_)));
2473 }
2474
2475 #[test]
2478 fn test_where_sort_limit() {
2479 let q = parse_query(".users[] | where age > 20 | sort age desc | limit 5").unwrap();
2480 assert_eq!(q.operations.len(), 3);
2481 assert!(matches!(&q.operations[0], Operation::Where(_)));
2482 assert_eq!(
2483 q.operations[1],
2484 Operation::Sort {
2485 field: "age".to_string(),
2486 descending: true,
2487 }
2488 );
2489 assert_eq!(q.operations[2], Operation::Limit(5));
2490 }
2491
2492 #[test]
2493 fn test_where_select_sort() {
2494 let q = parse_query(".users[] | where age > 30 | select name, email | sort name").unwrap();
2495 assert_eq!(q.operations.len(), 3);
2496 assert!(matches!(&q.operations[0], Operation::Where(_)));
2497 assert_eq!(q.operations[1], fields(&["name", "email"]));
2498 assert_eq!(
2499 q.operations[2],
2500 Operation::Sort {
2501 field: "name".to_string(),
2502 descending: false,
2503 }
2504 );
2505 }
2506
2507 #[test]
2508 fn test_group_by_single_field() {
2509 let q = parse_query(".[] | group_by category").unwrap();
2510 assert_eq!(q.operations.len(), 1);
2511 match &q.operations[0] {
2512 Operation::GroupBy {
2513 fields,
2514 having,
2515 aggregates,
2516 } => {
2517 assert_eq!(fields, &vec!["category".to_string()]);
2518 assert!(having.is_none());
2519 assert!(aggregates.is_empty());
2520 }
2521 _ => panic!("expected GroupBy"),
2522 }
2523 }
2524
2525 #[test]
2526 fn test_group_by_multiple_fields() {
2527 let q = parse_query(".[] | group_by region, category").unwrap();
2528 match &q.operations[0] {
2529 Operation::GroupBy { fields, .. } => {
2530 assert_eq!(fields, &vec!["region".to_string(), "category".to_string()]);
2531 }
2532 _ => panic!("expected GroupBy"),
2533 }
2534 }
2535
2536 #[test]
2537 fn test_group_by_with_aggregates() {
2538 let q = parse_query(".[] | group_by category count(), sum(price), avg(score)").unwrap();
2539 match &q.operations[0] {
2540 Operation::GroupBy { aggregates, .. } => {
2541 assert_eq!(aggregates.len(), 3);
2542 assert_eq!(aggregates[0].func, AggregateFunc::Count);
2543 assert_eq!(aggregates[0].field, None);
2544 assert_eq!(aggregates[0].alias, "count");
2545 assert_eq!(aggregates[1].func, AggregateFunc::Sum);
2546 assert_eq!(aggregates[1].field, Some("price".to_string()));
2547 assert_eq!(aggregates[1].alias, "sum_price");
2548 assert_eq!(aggregates[2].func, AggregateFunc::Avg);
2549 assert_eq!(aggregates[2].field, Some("score".to_string()));
2550 assert_eq!(aggregates[2].alias, "avg_score");
2551 }
2552 _ => panic!("expected GroupBy"),
2553 }
2554 }
2555
2556 #[test]
2557 fn test_group_by_with_having() {
2558 let q = parse_query(".[] | group_by category count() having count > 5").unwrap();
2559 match &q.operations[0] {
2560 Operation::GroupBy {
2561 fields,
2562 having,
2563 aggregates,
2564 } => {
2565 assert_eq!(fields, &vec!["category".to_string()]);
2566 assert!(having.is_some());
2567 assert_eq!(aggregates.len(), 1);
2568 }
2569 _ => panic!("expected GroupBy"),
2570 }
2571 }
2572
2573 #[test]
2574 fn test_group_by_with_min_max() {
2575 let q = parse_query(".[] | group_by category min(price), max(price)").unwrap();
2576 match &q.operations[0] {
2577 Operation::GroupBy { aggregates, .. } => {
2578 assert_eq!(aggregates.len(), 2);
2579 assert_eq!(aggregates[0].func, AggregateFunc::Min);
2580 assert_eq!(aggregates[1].func, AggregateFunc::Max);
2581 }
2582 _ => panic!("expected GroupBy"),
2583 }
2584 }
2585
2586 #[test]
2587 fn test_group_by_pipeline() {
2588 let q = parse_query(".[] | group_by category count() | sort count desc | limit 5").unwrap();
2589 assert_eq!(q.operations.len(), 3);
2590 assert!(matches!(&q.operations[0], Operation::GroupBy { .. }));
2591 assert!(matches!(
2592 &q.operations[1],
2593 Operation::Sort {
2594 descending: true,
2595 ..
2596 }
2597 ));
2598 assert_eq!(q.operations[2], Operation::Limit(5));
2599 }
2600
2601 #[test]
2604 fn test_add_field_simple_arithmetic() {
2605 let (name, expr) = parse_add_field_expr("total = amount * quantity").unwrap();
2606 assert_eq!(name, "total");
2607 assert!(matches!(
2608 expr,
2609 Expr::BinaryOp {
2610 op: ArithmeticOp::Mul,
2611 ..
2612 }
2613 ));
2614 }
2615
2616 #[test]
2617 fn test_add_field_string_concat() {
2618 let (name, expr) =
2619 parse_add_field_expr("full_name = first_name + \" \" + last_name").unwrap();
2620 assert_eq!(name, "full_name");
2621 assert!(matches!(
2623 expr,
2624 Expr::BinaryOp {
2625 op: ArithmeticOp::Add,
2626 ..
2627 }
2628 ));
2629 }
2630
2631 #[test]
2632 fn test_add_field_with_literal() {
2633 let (name, expr) = parse_add_field_expr("tax = price * 0.1").unwrap();
2634 assert_eq!(name, "tax");
2635 assert!(matches!(
2636 expr,
2637 Expr::BinaryOp {
2638 op: ArithmeticOp::Mul,
2639 ..
2640 }
2641 ));
2642 }
2643
2644 #[test]
2645 fn test_add_field_complex_expr() {
2646 let (name, expr) = parse_add_field_expr("total = price + price * 0.1").unwrap();
2647 assert_eq!(name, "total");
2648 if let Expr::BinaryOp { op, left, right } = &expr {
2650 assert_eq!(*op, ArithmeticOp::Add);
2651 assert!(matches!(left.as_ref(), Expr::Field(f) if f == "price"));
2652 assert!(matches!(
2653 right.as_ref(),
2654 Expr::BinaryOp {
2655 op: ArithmeticOp::Mul,
2656 ..
2657 }
2658 ));
2659 } else {
2660 panic!("expected BinaryOp");
2661 }
2662 }
2663
2664 #[test]
2665 fn test_add_field_with_parens() {
2666 let (name, expr) = parse_add_field_expr("total = (price + tax) * quantity").unwrap();
2667 assert_eq!(name, "total");
2668 if let Expr::BinaryOp { op, left, right } = &expr {
2669 assert_eq!(*op, ArithmeticOp::Mul);
2670 assert!(matches!(
2671 left.as_ref(),
2672 Expr::BinaryOp {
2673 op: ArithmeticOp::Add,
2674 ..
2675 }
2676 ));
2677 assert!(matches!(right.as_ref(), Expr::Field(f) if f == "quantity"));
2678 } else {
2679 panic!("expected BinaryOp");
2680 }
2681 }
2682
2683 #[test]
2684 fn test_add_field_with_function() {
2685 let (name, expr) = parse_add_field_expr("name_upper = upper(name)").unwrap();
2686 assert_eq!(name, "name_upper");
2687 assert!(matches!(expr, Expr::FuncCall { .. }));
2688 }
2689
2690 #[test]
2691 fn test_add_field_missing_equals() {
2692 let result = parse_add_field_expr("total amount * quantity");
2693 assert!(result.is_err());
2694 }
2695
2696 #[test]
2699 fn test_expr_division() {
2700 let q = parse_query(".items[] | select total / count as avg").unwrap();
2701 assert_eq!(q.operations.len(), 1);
2702 }
2703
2704 #[test]
2705 fn test_expr_subtraction() {
2706 let q = parse_query(".items[] | select price - discount as net").unwrap();
2707 assert_eq!(q.operations.len(), 1);
2708 }
2709
2710 #[test]
2713 fn test_recursive_descent_root() {
2714 let q = parse_query("..name").unwrap();
2715 assert_eq!(
2716 q.path.segments,
2717 vec![Segment::RecursiveDescent("name".to_string())]
2718 );
2719 assert!(q.operations.is_empty());
2720 }
2721
2722 #[test]
2723 fn test_recursive_descent_after_field() {
2724 let q = parse_query(".config..host").unwrap();
2725 assert_eq!(
2726 q.path.segments,
2727 vec![
2728 Segment::Field("config".to_string()),
2729 Segment::RecursiveDescent("host".to_string()),
2730 ]
2731 );
2732 }
2733
2734 #[test]
2735 fn test_recursive_descent_with_pipeline() {
2736 let q = parse_query("..name | where name == \"Alice\"").unwrap();
2737 assert_eq!(
2738 q.path.segments,
2739 vec![Segment::RecursiveDescent("name".to_string())]
2740 );
2741 assert_eq!(q.operations.len(), 1);
2742 }
2743
2744 #[test]
2747 fn test_if_expr_simple() {
2748 let q = parse_query(".items[] | select if(age < 18, \"minor\", \"adult\") as category")
2749 .unwrap();
2750 assert_eq!(q.operations.len(), 1);
2751 if let Operation::Select(exprs) = &q.operations[0] {
2752 assert_eq!(exprs.len(), 1);
2753 assert_eq!(exprs[0].alias, Some("category".to_string()));
2754 assert!(matches!(&exprs[0].expr, Expr::If { .. }));
2755 } else {
2756 panic!("expected Select operation");
2757 }
2758 }
2759
2760 #[test]
2761 fn test_if_expr_nested() {
2762 let q = parse_query(
2763 ".[] | select if(age < 18, \"minor\", if(age < 65, \"adult\", \"senior\")) as cat",
2764 )
2765 .unwrap();
2766 if let Operation::Select(exprs) = &q.operations[0] {
2767 if let Expr::If { else_expr, .. } = &exprs[0].expr {
2768 assert!(matches!(else_expr.as_ref(), Expr::If { .. }));
2769 } else {
2770 panic!("expected If expression");
2771 }
2772 } else {
2773 panic!("expected Select operation");
2774 }
2775 }
2776
2777 #[test]
2778 fn test_if_expr_with_and_condition() {
2779 let q = parse_query(".[] | select if(age > 18 and age < 65, \"adult\", \"other\") as cat")
2780 .unwrap();
2781 if let Operation::Select(exprs) = &q.operations[0] {
2782 if let Expr::If { condition, .. } = &exprs[0].expr {
2783 assert!(matches!(condition, Condition::And(_, _)));
2784 } else {
2785 panic!("expected If expression");
2786 }
2787 } else {
2788 panic!("expected Select operation");
2789 }
2790 }
2791
2792 #[test]
2793 fn test_if_expr_missing_paren() {
2794 let result = parse_query(".[] | select if age < 18, \"minor\", \"adult\"");
2795 assert!(result.is_err());
2796 }
2797
2798 #[test]
2801 fn test_case_simple() {
2802 let q = parse_query(
2803 ".[] | select case when status == \"active\" then \"A\" else \"I\" end as code",
2804 )
2805 .unwrap();
2806 if let Operation::Select(exprs) = &q.operations[0] {
2807 if let Expr::Case { branches, default } = &exprs[0].expr {
2808 assert_eq!(branches.len(), 1);
2809 assert!(default.is_some());
2810 } else {
2811 panic!("expected Case expression");
2812 }
2813 } else {
2814 panic!("expected Select operation");
2815 }
2816 }
2817
2818 #[test]
2819 fn test_case_multiple_when() {
2820 let q = parse_query(
2821 ".[] | select case when age < 18 then \"minor\" when age < 65 then \"adult\" else \"senior\" end as category",
2822 )
2823 .unwrap();
2824 if let Operation::Select(exprs) = &q.operations[0] {
2825 if let Expr::Case { branches, default } = &exprs[0].expr {
2826 assert_eq!(branches.len(), 2);
2827 assert!(default.is_some());
2828 } else {
2829 panic!("expected Case expression");
2830 }
2831 } else {
2832 panic!("expected Select operation");
2833 }
2834 }
2835
2836 #[test]
2837 fn test_case_no_else() {
2838 let q =
2839 parse_query(".[] | select case when status == \"active\" then \"yes\" end as active")
2840 .unwrap();
2841 if let Operation::Select(exprs) = &q.operations[0] {
2842 if let Expr::Case { branches, default } = &exprs[0].expr {
2843 assert_eq!(branches.len(), 1);
2844 assert!(default.is_none());
2845 } else {
2846 panic!("expected Case expression");
2847 }
2848 } else {
2849 panic!("expected Select operation");
2850 }
2851 }
2852
2853 #[test]
2854 fn test_case_no_when_fails() {
2855 let result = parse_query(".[] | select case else \"x\" end as y");
2856 assert!(result.is_err());
2857 }
2858
2859 #[test]
2862 fn test_add_field_with_if() {
2863 let (name, expr) =
2864 parse_add_field_expr("tier = if(revenue > 10000, \"gold\", \"silver\")").unwrap();
2865 assert_eq!(name, "tier");
2866 assert!(matches!(expr, Expr::If { .. }));
2867 }
2868
2869 #[test]
2870 fn test_add_field_with_case() {
2871 let (name, expr) = parse_add_field_expr(
2872 "tier = case when revenue > 10000 then \"gold\" when revenue > 5000 then \"silver\" else \"bronze\" end",
2873 )
2874 .unwrap();
2875 assert_eq!(name, "tier");
2876 if let Expr::Case { branches, default } = expr {
2877 assert_eq!(branches.len(), 2);
2878 assert!(default.is_some());
2879 } else {
2880 panic!("expected Case expression");
2881 }
2882 }
2883
2884 #[test]
2887 fn test_parse_median() {
2888 let q = parse_query(".[] | median score").unwrap();
2889 assert!(matches!(&q.operations[0], Operation::Median { field } if field == "score"));
2890 }
2891
2892 #[test]
2893 fn test_parse_percentile() {
2894 let q = parse_query(".[] | percentile score 0.95").unwrap();
2895 if let Operation::Percentile { field, p } = &q.operations[0] {
2896 assert_eq!(field, "score");
2897 assert!((p - 0.95).abs() < f64::EPSILON);
2898 } else {
2899 panic!("expected Percentile operation");
2900 }
2901 }
2902
2903 #[test]
2904 fn test_parse_percentile_invalid_p() {
2905 let result = parse_query(".[] | percentile score 1.5");
2906 assert!(result.is_err());
2907 }
2908
2909 #[test]
2910 fn test_parse_stddev() {
2911 let q = parse_query(".[] | stddev salary").unwrap();
2912 assert!(matches!(&q.operations[0], Operation::Stddev { field } if field == "salary"));
2913 }
2914
2915 #[test]
2916 fn test_parse_variance() {
2917 let q = parse_query(".[] | variance salary").unwrap();
2918 assert!(matches!(&q.operations[0], Operation::Variance { field } if field == "salary"));
2919 }
2920
2921 #[test]
2922 fn test_parse_mode() {
2923 let q = parse_query(".[] | mode category").unwrap();
2924 assert!(matches!(&q.operations[0], Operation::Mode { field } if field == "category"));
2925 }
2926
2927 #[test]
2928 fn test_parse_group_concat() {
2929 let q = parse_query(".[] | group_concat name \", \"").unwrap();
2930 if let Operation::GroupConcat { field, separator } = &q.operations[0] {
2931 assert_eq!(field, "name");
2932 assert_eq!(separator, ", ");
2933 } else {
2934 panic!("expected GroupConcat operation");
2935 }
2936 }
2937
2938 #[test]
2939 fn test_parse_group_concat_default_separator() {
2940 let q = parse_query(".[] | group_concat name").unwrap();
2941 if let Operation::GroupConcat { field, separator } = &q.operations[0] {
2942 assert_eq!(field, "name");
2943 assert_eq!(separator, ", ");
2944 } else {
2945 panic!("expected GroupConcat operation");
2946 }
2947 }
2948
2949 #[test]
2950 fn test_parse_group_by_with_median() {
2951 let q = parse_query(".[] | group_by dept median(score)").unwrap();
2952 if let Operation::GroupBy {
2953 fields, aggregates, ..
2954 } = &q.operations[0]
2955 {
2956 assert_eq!(fields, &["dept"]);
2957 assert_eq!(aggregates.len(), 1);
2958 assert!(matches!(aggregates[0].func, AggregateFunc::Median));
2959 assert_eq!(aggregates[0].field, Some("score".to_string()));
2960 assert_eq!(aggregates[0].alias, "median_score");
2961 } else {
2962 panic!("expected GroupBy operation");
2963 }
2964 }
2965
2966 #[test]
2967 fn test_parse_group_by_with_percentile() {
2968 let q = parse_query(".[] | group_by dept percentile(score, 0.95)").unwrap();
2969 if let Operation::GroupBy { aggregates, .. } = &q.operations[0] {
2970 assert_eq!(aggregates.len(), 1);
2971 if let AggregateFunc::Percentile(p) = aggregates[0].func {
2972 assert!((p - 0.95).abs() < f64::EPSILON);
2973 } else {
2974 panic!("expected Percentile aggregate");
2975 }
2976 } else {
2977 panic!("expected GroupBy operation");
2978 }
2979 }
2980
2981 #[test]
2982 fn test_parse_group_by_with_group_concat() {
2983 let q = parse_query(".[] | group_by dept group_concat(name, \", \")").unwrap();
2984 if let Operation::GroupBy { aggregates, .. } = &q.operations[0] {
2985 assert_eq!(aggregates.len(), 1);
2986 if let AggregateFunc::GroupConcat(sep) = &aggregates[0].func {
2987 assert_eq!(sep, ", ");
2988 } else {
2989 panic!("expected GroupConcat aggregate");
2990 }
2991 } else {
2992 panic!("expected GroupBy operation");
2993 }
2994 }
2995
2996 #[test]
2997 fn test_parse_group_by_mixed_aggregates() {
2998 let q = parse_query(".[] | group_by dept count(), median(score), stddev(score)").unwrap();
2999 if let Operation::GroupBy { aggregates, .. } = &q.operations[0] {
3000 assert_eq!(aggregates.len(), 3);
3001 assert!(matches!(aggregates[0].func, AggregateFunc::Count));
3002 assert!(matches!(aggregates[1].func, AggregateFunc::Median));
3003 assert!(matches!(aggregates[2].func, AggregateFunc::Stddev));
3004 } else {
3005 panic!("expected GroupBy operation");
3006 }
3007 }
3008}