1use crate::ast::*;
2use crate::lexer::lex;
3use crate::token::Token;
4
5const MAX_NESTING_DEPTH: usize = 64;
19
20#[derive(Debug, thiserror::Error)]
27pub enum ParseError {
28 #[error("at position {position}: {message}")]
30 Lex { message: String, position: usize },
31 #[error("{}", positioned(*position, &format!("expected {expected}, got {got}")))]
35 UnexpectedToken {
36 expected: String,
37 got: String,
38 position: Option<usize>,
39 },
40 #[error("query nesting depth exceeds maximum of {max}")]
42 NestingDepthExceeded { max: usize },
43 #[error("{feature}")]
45 Unsupported { feature: String },
46 #[error("{}", positioned(*position, message))]
49 Syntax {
50 message: String,
51 position: Option<usize>,
52 },
53}
54
55fn positioned(position: Option<usize>, body: &str) -> String {
60 match position {
61 Some(p) => format!("at position {p}: {body}"),
62 None => body.to_string(),
63 }
64}
65
66impl ParseError {
67 pub fn message(&self) -> String {
69 self.to_string()
70 }
71}
72
73fn token_to_scalar_fn(tok: &Token) -> ScalarFn {
74 match tok {
75 Token::Upper => ScalarFn::Upper,
76 Token::Lower => ScalarFn::Lower,
77 Token::Length => ScalarFn::Length,
78 Token::Trim => ScalarFn::Trim,
79 Token::Substring => ScalarFn::Substring,
80 Token::Concat => ScalarFn::Concat,
81 Token::Abs => ScalarFn::Abs,
82 Token::Round => ScalarFn::Round,
83 Token::Ceil => ScalarFn::Ceil,
84 Token::Floor => ScalarFn::Floor,
85 Token::Sqrt => ScalarFn::Sqrt,
86 Token::Pow => ScalarFn::Pow,
87 Token::Now => ScalarFn::Now,
88 Token::Extract => ScalarFn::Extract,
89 Token::DateAdd => ScalarFn::DateAdd,
90 Token::DateDiff => ScalarFn::DateDiff,
91 Token::JsonType => ScalarFn::JsonType,
92 Token::JsonText => ScalarFn::JsonText,
93 _ => unreachable!(),
94 }
95}
96
97struct Parser {
98 tokens: Vec<Token>,
99 pos: usize,
100 depth: usize,
101}
102
103pub fn parse(input: &str) -> Result<Statement, ParseError> {
125 let (tokens, spans) = crate::lexer::lex_with_spans(input).map_err(|e| ParseError::Lex {
126 message: e.message,
127 position: e.position,
128 })?;
129 parse_tokens_with_spans(tokens, Some(spans))
130}
131
132pub fn parse_with_params(input: &str, params: &[ParamValue]) -> Result<Statement, ParseError> {
147 let (mut tokens, spans) = crate::lexer::lex_with_spans(input).map_err(|e| ParseError::Lex {
149 message: e.message,
150 position: e.position,
151 })?;
152 for tok in tokens.iter_mut() {
153 if let Token::Param(name) = tok {
154 let n: usize = name.parse().map_err(|_| ParseError::Syntax {
155 message: format!(
156 "positional parameters must be numeric (`$1`, `$2`, …); got `${name}`"
157 ),
158 position: None,
159 })?;
160 if n == 0 {
161 return Err(ParseError::Syntax {
162 message: "parameter placeholders are 1-based; `$0` is invalid".into(),
163 position: None,
164 });
165 }
166 let p = params.get(n - 1).ok_or_else(|| ParseError::Syntax {
167 message: format!(
168 "query references ${n} but only {} parameter(s) were supplied",
169 params.len()
170 ),
171 position: None,
172 })?;
173 *tok = match p {
174 ParamValue::Null => Token::Null,
175 ParamValue::Int(v) => Token::IntLit(*v),
176 ParamValue::Float(v) => Token::FloatLit(*v),
177 ParamValue::Bool(v) => Token::BoolLit(*v),
178 ParamValue::Str(s) => Token::StringLit(s.clone()),
179 };
180 }
181 }
182 parse_tokens_with_spans(tokens, Some(spans))
183}
184
185fn edit_distance(a: &str, b: &str) -> usize {
186 let mut prev: Vec<usize> = (0..=b.len()).collect();
187 let mut curr = vec![0; b.len() + 1];
188 for (i, ca) in a.bytes().enumerate() {
189 curr[0] = i + 1;
190 for (j, cb) in b.bytes().enumerate() {
191 let cost = usize::from(ca != cb);
192 curr[j + 1] = (prev[j + 1] + 1).min(curr[j] + 1).min(prev[j] + cost);
193 }
194 std::mem::swap(&mut prev, &mut curr);
195 }
196 prev[b.len()]
197}
198
199const STATEMENT_KEYWORDS: &[&str] = &[
201 "alter", "begin", "commit", "delete", "drop", "explain", "insert", "refresh", "rollback",
202 "select", "type", "update", "upsert",
203];
204
205const PIPELINE_KEYWORDS: &[&str] = &[
211 "and",
212 "asc",
213 "between",
214 "conflict",
215 "cross",
216 "desc",
217 "distinct",
218 "exists",
219 "filter",
220 "group",
221 "having",
222 "inner",
223 "join",
224 "left",
225 "like",
226 "limit",
227 "match",
228 "not",
229 "offset",
230 "order",
231 "outer",
232 "returning",
233 "right",
234 "union",
235];
236
237const DISTANCE_2_MIN_LEN: usize = 8;
250
251fn suggestion_threshold(word: &str, keyword: &str) -> usize {
258 if word.len().min(keyword.len()) < DISTANCE_2_MIN_LEN {
259 1
260 } else {
261 2
262 }
263}
264
265fn closest_keyword<'a>(
269 word: &str,
270 candidates: impl Iterator<Item = &'a &'static str>,
271) -> Option<&'static str> {
272 let lower = word.to_ascii_lowercase();
273 let initial = lower.as_bytes().first().copied();
274 candidates
275 .copied()
276 .filter(|kw| {
277 kw.as_bytes().first().copied() == initial
284 })
285 .filter(|kw| {
286 let distance = edit_distance(&lower, kw);
287 distance >= 1 && distance <= suggestion_threshold(&lower, kw)
291 })
292 .min_by_key(|kw| edit_distance(&lower, kw))
296}
297
298fn keyword_suggestion(word: &str) -> Option<&'static str> {
301 closest_keyword(word, PIPELINE_KEYWORDS.iter().chain(STATEMENT_KEYWORDS))
302}
303
304fn statement_keyword_suggestion(word: &str) -> Option<&'static str> {
306 closest_keyword(word, STATEMENT_KEYWORDS.iter())
307}
308
309fn parse_tokens_with_spans(
320 tokens: Vec<Token>,
321 spans: Option<Vec<usize>>,
322) -> Result<Statement, ParseError> {
323 let mut parser = Parser {
324 tokens,
325 pos: 0,
326 depth: 0,
327 };
328 let stmt = match parser.parse_statement() {
329 Ok(stmt) => stmt,
330 Err(e) => return Err(attach_failing_position(e, &parser, spans.as_deref())),
331 };
332 if !matches!(parser.peek(), Token::Eof) {
338 let mut message = format!(
339 "unexpected trailing token near token {}: {}",
340 parser.pos,
341 parser.peek().display_name()
342 );
343 let offending = match parser.peek() {
350 Token::Ident(word) => {
351 keyword_suggestion(word).map(|kw| format!("; did you mean `{kw}`?"))
352 }
353 _ => None,
354 };
355 let suggestion = offending.or_else(|| {
356 let Some(Token::Ident(first)) = parser.tokens.first() else {
361 return None;
362 };
363 let kw = statement_keyword_suggestion(first)?;
364 Some(format!(
365 "; `{first}` is not a statement keyword, did you mean `{kw}`?"
366 ))
367 });
368 if let Some(suggestion) = suggestion {
369 message.push_str(&suggestion);
370 }
371 return Err(attach_failing_position(
372 ParseError::Syntax {
373 message,
374 position: None,
375 },
376 &parser,
377 spans.as_deref(),
378 ));
379 }
380 Ok(stmt)
381}
382
383fn attach_failing_position(
388 error: ParseError,
389 parser: &Parser,
390 spans: Option<&[usize]>,
391) -> ParseError {
392 let Some(spans) = spans else { return error };
393 let offset = spans
394 .get(parser.pos.min(spans.len().saturating_sub(1)))
395 .copied();
396 match error {
397 ParseError::UnexpectedToken {
398 expected,
399 got,
400 position: None,
401 } => ParseError::UnexpectedToken {
402 expected,
403 got,
404 position: offset,
405 },
406 ParseError::Syntax {
407 message,
408 position: None,
409 } => ParseError::Syntax {
410 message,
411 position: offset,
412 },
413 other => other,
414 }
415}
416
417fn substitute_projection_aliases(expr: Expr, fields: &[ProjectionField]) -> Expr {
422 match expr {
423 Expr::Field(ref name) => {
424 for f in fields {
425 if f.alias.as_deref() == Some(name.as_str()) {
426 return f.expr.clone();
427 }
428 }
429 expr
430 }
431 Expr::BinaryOp(l, op, r) => Expr::BinaryOp(
432 Box::new(substitute_projection_aliases(*l, fields)),
433 op,
434 Box::new(substitute_projection_aliases(*r, fields)),
435 ),
436 Expr::UnaryOp(op, inner) => {
437 Expr::UnaryOp(op, Box::new(substitute_projection_aliases(*inner, fields)))
438 }
439 Expr::Coalesce(l, r) => Expr::Coalesce(
440 Box::new(substitute_projection_aliases(*l, fields)),
441 Box::new(substitute_projection_aliases(*r, fields)),
442 ),
443 Expr::InList {
444 expr: e,
445 list,
446 negated,
447 } => Expr::InList {
448 expr: Box::new(substitute_projection_aliases(*e, fields)),
449 list: list
450 .into_iter()
451 .map(|i| substitute_projection_aliases(i, fields))
452 .collect(),
453 negated,
454 },
455 Expr::ScalarFunc(f, args) => Expr::ScalarFunc(
456 f,
457 args.into_iter()
458 .map(|a| substitute_projection_aliases(a, fields))
459 .collect(),
460 ),
461 other => other,
462 }
463}
464
465impl Parser {
466 fn peek(&self) -> &Token {
467 &self.tokens[self.pos]
468 }
469
470 fn advance(&mut self) -> Token {
471 let t = self.tokens[self.pos].clone();
472 self.pos += 1;
473 t
474 }
475
476 fn expect(&mut self, expected: &Token) -> Result<(), ParseError> {
477 let t = self.advance();
478 if &t == expected {
479 Ok(())
480 } else {
481 Err(ParseError::UnexpectedToken {
482 expected: expected.display_name(),
483 got: t.display_name(),
484 position: None,
485 })
486 }
487 }
488
489 fn unexpected(&self, expected: &str, got: &Token) -> ParseError {
491 ParseError::UnexpectedToken {
492 expected: expected.into(),
493 got: got.display_name(),
494 position: None,
495 }
496 }
497
498 fn expect_named_ident(&mut self, context: &str) -> Result<String, ParseError> {
503 match self.advance() {
504 Token::Ident(n) => Ok(n),
505 t => Err(self.named_ident_error(context, &t)),
506 }
507 }
508
509 fn named_ident_error(&self, context: &str, got: &Token) -> ParseError {
513 if let Some(kw) = got.keyword_str() {
514 ParseError::Syntax {
515 message: format!(
519 "syntax error: '{kw}' is a reserved word and cannot be used as a {context}; \
520 rename it or quote it as `{kw}`"
521 ),
522 position: None,
523 }
524 } else {
525 ParseError::UnexpectedToken {
526 expected: context.into(),
527 got: got.display_name(),
528 position: None,
529 }
530 }
531 }
532
533 fn parse_optional_if_not_exists(&mut self) -> bool {
536 if matches!(self.peek(), Token::Ident(w) if w == "if")
537 && matches!(self.tokens.get(self.pos + 1), Some(Token::Not))
538 && matches!(self.tokens.get(self.pos + 2), Some(Token::Exists))
539 {
540 self.pos += 3;
541 true
542 } else {
543 false
544 }
545 }
546
547 fn parse_optional_if_exists(&mut self) -> bool {
549 if matches!(self.peek(), Token::Ident(w) if w == "if")
550 && matches!(self.tokens.get(self.pos + 1), Some(Token::Exists))
551 {
552 self.pos += 2;
553 true
554 } else {
555 false
556 }
557 }
558
559 fn parse_statement(&mut self) -> Result<Statement, ParseError> {
560 self.depth += 1;
561 if self.depth > MAX_NESTING_DEPTH {
562 self.depth -= 1;
563 return Err(ParseError::NestingDepthExceeded {
564 max: MAX_NESTING_DEPTH,
565 });
566 }
567 if matches!(self.peek(), Token::Explain) {
568 self.advance();
569 let inner = self.parse_statement()?;
570 self.depth -= 1;
571 return Ok(Statement::Explain(Box::new(inner)));
572 }
573 let stmt = match self.peek() {
574 Token::Insert => self.parse_insert(),
575 Token::Upsert => self.parse_upsert(),
576 Token::Type => self.parse_create_type(),
577 Token::Link => self.parse_create_link(),
578 Token::Alter => self.parse_alter_table(),
579 Token::Drop => self.parse_drop_or_drop_view(),
580 Token::Materialized => self.parse_create_view(),
581 Token::Refresh => self.parse_refresh_view(),
582 Token::Begin => {
583 self.advance();
584 if *self.peek() == Token::Transaction {
586 self.advance();
587 }
588 return Ok(Statement::Begin);
589 }
590 Token::Commit => {
591 self.advance();
592 return Ok(Statement::Commit);
593 }
594 Token::Rollback => {
595 self.advance();
596 return Ok(Statement::Rollback);
597 }
598 Token::Schema => self.parse_schema(),
599 Token::Describe => self.parse_describe(),
600 Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
601 self.parse_aggregate_query()
602 }
603 Token::Ident(_) => self.parse_query_or_mutation(),
604 Token::Update => Err(ParseError::Syntax {
605 message: "'update' cannot start a statement — in PowQL, use pipeline syntax: \
606 TableName filter ... update { ... }"
607 .into(),
608 position: None,
609 }),
610 Token::Delete => Err(ParseError::Syntax {
611 message: "'delete' cannot start a statement — in PowQL, use pipeline syntax: \
612 TableName filter ... delete"
613 .into(),
614 position: None,
615 }),
616 _ => Err(self.unexpected("statement", self.peek())),
617 }?;
618 let result = self.maybe_parse_union(stmt);
620 self.depth -= 1;
621 result
622 }
623
624 fn parse_query_or_mutation(&mut self) -> Result<Statement, ParseError> {
625 let source = match self.advance() {
626 Token::Ident(name) => name,
627 t => {
628 return Err(ParseError::UnexpectedToken {
629 expected: "type name".into(),
630 got: t.display_name(),
631 position: None,
632 })
633 }
634 };
635 let alias = self.try_parse_alias();
636 let joins = self.parse_joins()?;
637
638 let mut filter = None;
642 let mut order = None;
643 let mut limit = None;
644 let mut offset = None;
645 let mut projection = None;
646 let mut distinct = false;
647 let mut group_by = None;
648 let mut having_chain = 0usize;
652
653 loop {
654 match self.peek() {
655 Token::Distinct => {
656 self.advance();
657 distinct = true;
658 }
659 Token::Group => {
660 self.advance();
661 group_by = Some(self.parse_group_by()?);
662 }
663 Token::Filter => {
664 self.advance();
665 filter = Some(self.parse_expr()?);
666 }
667 Token::Order => {
668 self.advance();
669 order = Some(self.parse_order()?);
670 }
671 Token::Limit => {
672 self.advance();
673 limit = Some(self.parse_expr()?);
674 }
675 Token::Offset => {
676 self.advance();
677 offset = Some(self.parse_expr()?);
678 }
679 Token::LBrace => {
680 projection = Some(self.parse_projection()?);
681 }
682 Token::Having => {
683 self.advance();
685 let having_expr = self.parse_expr()?;
686 let group = group_by.as_mut().ok_or_else(|| ParseError::Syntax {
687 message: "having without group by".into(),
688 position: None,
689 })?;
690 let rewritten = match projection.as_ref() {
691 Some(fields) => substitute_projection_aliases(having_expr, fields),
692 None => having_expr,
693 };
694 group.having = Some(match group.having.take() {
695 Some(existing) => {
696 having_chain += 1;
697 self.check_chain_depth(having_chain)?;
698 Expr::BinaryOp(Box::new(existing), BinOp::And, Box::new(rewritten))
699 }
700 None => rewritten,
701 });
702 }
703 Token::Update => {
704 if !joins.is_empty() {
705 return Err(ParseError::Unsupported {
706 feature: "update on a joined query is not supported".into(),
707 });
708 }
709 self.advance();
710 let assignments = self.parse_assignments()?;
711 let returning = *self.peek() == Token::Returning;
713 if returning {
714 self.advance();
715 }
716 return Ok(Statement::UpdateQuery(UpdateExpr {
717 source,
718 alias,
719 filter,
720 assignments,
721 returning,
722 }));
723 }
724 Token::Delete => {
725 if !joins.is_empty() {
726 return Err(ParseError::Unsupported {
727 feature: "delete on a joined query is not supported".into(),
728 });
729 }
730 self.advance();
731 let returning = *self.peek() == Token::Returning;
733 if returning {
734 self.advance();
735 }
736 return Ok(Statement::DeleteQuery(DeleteExpr {
737 source,
738 alias,
739 filter,
740 returning,
741 }));
742 }
743 _ => break,
744 }
745 }
746
747 Ok(Statement::Query(QueryExpr {
748 source,
749 alias,
750 joins,
751 filter,
752 order,
753 limit,
754 offset,
755 projection,
756 aggregation: None,
757 distinct,
758 group_by,
759 }))
760 }
761
762 fn parse_query_tail(&mut self, source: String) -> Result<QueryExpr, ParseError> {
768 let alias = self.try_parse_alias();
769 let joins = self.parse_joins()?;
770 let mut filter = None;
771 let mut order = None;
772 let mut limit = None;
773 let mut offset = None;
774 let mut projection = None;
775 let mut distinct = false;
776 let mut group_by = None;
777 let mut having_chain = 0usize;
781
782 loop {
783 match self.peek() {
784 Token::Distinct => {
785 self.advance();
786 distinct = true;
787 }
788 Token::Group => {
789 self.advance();
790 group_by = Some(self.parse_group_by()?);
791 }
792 Token::Filter => {
793 self.advance();
794 filter = Some(self.parse_expr()?);
795 }
796 Token::Order => {
797 self.advance();
798 order = Some(self.parse_order()?);
799 }
800 Token::Limit => {
801 self.advance();
802 limit = Some(self.parse_expr()?);
803 }
804 Token::Offset => {
805 self.advance();
806 offset = Some(self.parse_expr()?);
807 }
808 Token::LBrace => {
809 projection = Some(self.parse_projection()?);
810 }
811 Token::Having => {
812 self.advance();
818 let having_expr = self.parse_expr()?;
819 let group = group_by.as_mut().ok_or_else(|| ParseError::Syntax {
820 message: "having without group by".into(),
821 position: None,
822 })?;
823 let rewritten = match projection.as_ref() {
824 Some(fields) => substitute_projection_aliases(having_expr, fields),
825 None => having_expr,
826 };
827 group.having = Some(match group.having.take() {
828 Some(existing) => {
829 having_chain += 1;
830 self.check_chain_depth(having_chain)?;
831 Expr::BinaryOp(Box::new(existing), BinOp::And, Box::new(rewritten))
832 }
833 None => rewritten,
834 });
835 }
836 _ => break,
837 }
838 }
839
840 Ok(QueryExpr {
841 source,
842 alias,
843 joins,
844 filter,
845 order,
846 limit,
847 offset,
848 projection,
849 aggregation: None,
850 distinct,
851 group_by,
852 })
853 }
854
855 fn try_parse_alias(&mut self) -> Option<String> {
859 if *self.peek() == Token::As {
860 self.advance();
861 if let Token::Ident(name) = self.peek().clone() {
862 self.advance();
863 return Some(name);
864 }
865 }
866 None
867 }
868
869 fn parse_joins(&mut self) -> Result<Vec<JoinClause>, ParseError> {
876 let mut joins = Vec::new();
877 loop {
878 let kind = match self.peek() {
879 Token::Join => {
880 self.advance();
881 JoinKind::Inner
882 }
883 Token::Inner => {
884 self.advance();
885 self.expect(&Token::Join)?;
886 JoinKind::Inner
887 }
888 Token::LeftKw => {
889 self.advance();
890 if *self.peek() == Token::Outer {
891 self.advance();
892 }
893 self.expect(&Token::Join)?;
894 JoinKind::LeftOuter
895 }
896 Token::RightKw => {
897 self.advance();
898 if *self.peek() == Token::Outer {
899 self.advance();
900 }
901 self.expect(&Token::Join)?;
902 JoinKind::RightOuter
903 }
904 Token::Cross => {
905 self.advance();
906 self.expect(&Token::Join)?;
907 JoinKind::Cross
908 }
909 _ => break,
910 };
911
912 let source = match self.advance() {
913 Token::Ident(name) => name,
914 t => {
915 return Err(ParseError::UnexpectedToken {
916 expected: "type name after join".into(),
917 got: t.display_name(),
918 position: None,
919 });
920 }
921 };
922 let alias = self.try_parse_alias();
923 let on = if kind == JoinKind::Cross {
924 None
925 } else if *self.peek() == Token::On {
926 self.advance();
927 Some(self.parse_expr()?)
928 } else {
929 return Err(ParseError::Syntax {
930 message: format!("expected `on <expr>` after join {source}"),
931 position: None,
932 });
933 };
934
935 joins.push(JoinClause {
936 kind,
937 source,
938 alias,
939 on,
940 });
941 }
942 Ok(joins)
943 }
944
945 fn parse_insert(&mut self) -> Result<Statement, ParseError> {
946 self.expect(&Token::Insert)?;
947 let target = match self.advance() {
948 Token::Ident(name) => name,
949 t => {
950 return Err(ParseError::UnexpectedToken {
951 expected: "type name".into(),
952 got: t.display_name(),
953 position: None,
954 })
955 }
956 };
957 let mut rows = vec![self.parse_assignments()?];
961 while *self.peek() == Token::Comma {
962 self.advance(); rows.push(self.parse_assignments()?);
964 }
965 let returning = *self.peek() == Token::Returning;
967 if returning {
968 self.advance();
969 }
970 Ok(Statement::Insert(InsertExpr {
971 target,
972 rows,
973 returning,
974 }))
975 }
976
977 fn parse_upsert(&mut self) -> Result<Statement, ParseError> {
979 self.expect(&Token::Upsert)?;
980 let target = match self.advance() {
981 Token::Ident(name) => name,
982 t => {
983 return Err(ParseError::UnexpectedToken {
984 expected: "type name".into(),
985 got: t.display_name(),
986 position: None,
987 })
988 }
989 };
990 self.expect(&Token::On)?;
991 let key_column = match self.advance() {
992 Token::DotIdent(name) => name,
993 t => {
994 return Err(ParseError::UnexpectedToken {
995 expected: ".key_column".into(),
996 got: t.display_name(),
997 position: None,
998 })
999 }
1000 };
1001 let assignments = self.parse_assignments()?;
1002 let on_conflict = if *self.peek() == Token::On {
1003 self.advance(); self.expect(&Token::Conflict)?;
1005 self.parse_assignments()?
1006 } else {
1007 Vec::new()
1008 };
1009 Ok(Statement::Upsert(UpsertExpr {
1010 target,
1011 key_column,
1012 assignments,
1013 on_conflict,
1014 }))
1015 }
1016
1017 fn parse_assignments(&mut self) -> Result<Vec<Assignment>, ParseError> {
1018 self.expect(&Token::LBrace)?;
1019 let mut assignments = Vec::new();
1020 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1021 if matches!(self.peek(), Token::DotIdent(_) | Token::Ident(_))
1028 && matches!(self.tokens.get(self.pos + 1), Some(Token::Arrow))
1029 {
1030 let field = match self.peek() {
1031 Token::DotIdent(n) | Token::Ident(n) => n.clone(),
1032 _ => unreachable!("guarded by the matches! above"),
1033 };
1034 return Err(ParseError::Unsupported {
1035 feature: format!(
1036 "cannot assign to a JSON path target `.{field}->...` (at token {pos}): \
1037 JSON path assignment targets are not supported; write the whole JSON \
1038 column instead (path mutation such as json_set is not yet available)",
1039 pos = self.pos
1040 ),
1041 });
1042 }
1043 let field = self.expect_named_ident("field name")?;
1044 self.expect(&Token::Assign)?;
1045 let value = self.parse_expr()?;
1046 assignments.push(Assignment { field, value });
1047 if *self.peek() == Token::Comma {
1048 self.advance();
1049 }
1050 }
1051 self.expect(&Token::RBrace)?;
1052 Ok(assignments)
1053 }
1054
1055 fn parse_projection(&mut self) -> Result<Vec<ProjectionField>, ParseError> {
1056 self.expect(&Token::LBrace)?;
1057 let mut fields = Vec::new();
1058 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1059 if matches!(self.peek(), Token::Ident(_))
1067 && matches!(self.tokens.get(self.pos + 1), Some(Token::Colon))
1068 {
1069 let alias = match self.advance() {
1070 Token::Ident(name) => name,
1071 _ => unreachable!("guarded by the matches! above"),
1072 };
1073 self.advance(); self.reject_bare_dotted_path()?;
1075 let expr = if matches!(self.peek(), Token::Ident(_))
1078 && matches!(self.tokens.get(self.pos + 1), Some(Token::As))
1079 {
1080 Expr::NestedQuery(Box::new(self.parse_nested_query()?))
1081 } else if self.at_link_traversal() {
1082 Expr::NestedQuery(Box::new(self.parse_link_traversal()?))
1088 } else if self.at_scalar_link_path() {
1089 self.parse_scalar_link_path()?
1091 } else {
1092 self.parse_expr()?
1093 };
1094 fields.push(ProjectionField {
1095 alias: Some(alias),
1096 expr,
1097 });
1098 } else {
1099 if matches!(self.peek(), Token::Ident(_))
1100 && matches!(self.tokens.get(self.pos + 1), Some(Token::As))
1101 {
1102 return Err(ParseError::Syntax {
1103 message: "a nested projection needs a field name: \
1104 `<name>: <Table> as <alias> filter ... { ... }`"
1105 .into(),
1106 position: None,
1107 });
1108 }
1109 self.reject_bare_dotted_path()?;
1110 let expr = if self.at_scalar_link_path() {
1111 self.parse_scalar_link_path()?
1114 } else {
1115 self.parse_expr()?
1116 };
1117 fields.push(ProjectionField { alias: None, expr });
1118 }
1119 if *self.peek() == Token::Comma {
1120 self.advance();
1121 }
1122 }
1123 self.expect(&Token::RBrace)?;
1124 Ok(fields)
1125 }
1126
1127 fn reject_bare_dotted_path(&self) -> Result<(), ParseError> {
1134 if let (Token::DotIdent(first), Some(Token::DotIdent(second))) =
1135 (self.peek(), self.tokens.get(self.pos + 1))
1136 {
1137 return Err(ParseError::Syntax {
1138 message: format!(
1139 "`.{first}.{second}` is ambiguous in a projection: for a link \
1140 path, alias the table and qualify the path \
1141 (`Order as o {{ o.{first}.{second} }}`); for separate fields, \
1142 separate them with commas (`.{first}, .{second}`)"
1143 ),
1144 position: None,
1145 });
1146 }
1147 Ok(())
1148 }
1149
1150 fn at_link_traversal(&self) -> bool {
1156 matches!(self.peek(), Token::Ident(_))
1157 && matches!(self.tokens.get(self.pos + 1), Some(Token::DotIdent(_)))
1158 && matches!(
1159 self.tokens.get(self.pos + 2),
1160 Some(Token::LBrace | Token::Filter | Token::Order | Token::Limit | Token::Offset)
1161 )
1162 }
1163
1164 fn at_scalar_link_path(&self) -> bool {
1171 matches!(self.peek(), Token::Ident(_))
1172 && matches!(self.tokens.get(self.pos + 1), Some(Token::DotIdent(_)))
1173 && matches!(self.tokens.get(self.pos + 2), Some(Token::DotIdent(_)))
1174 }
1175
1176 fn parse_scalar_link_path(&mut self) -> Result<Expr, ParseError> {
1182 let outer_alias = match self.advance() {
1183 Token::Ident(n) => n,
1184 t => return Err(self.named_ident_error("link path outer alias", &t)),
1185 };
1186 let mut parts = Vec::new();
1187 while let Some(Token::DotIdent(_)) = self.tokens.get(self.pos) {
1188 match self.advance() {
1189 Token::DotIdent(n) => parts.push(n),
1190 _ => unreachable!("peeked DotIdent"),
1191 }
1192 }
1193 debug_assert!(parts.len() >= 2, "guarded by at_scalar_link_path");
1194 let column = parts.pop().expect("at least two parts");
1195 Ok(Expr::LinkPath {
1196 outer_alias,
1197 links: parts,
1198 column,
1199 })
1200 }
1201
1202 fn parse_link_traversal(&mut self) -> Result<NestedQuery, ParseError> {
1212 self.depth += 1;
1213 if self.depth > MAX_NESTING_DEPTH {
1214 self.depth -= 1;
1215 return Err(ParseError::NestingDepthExceeded {
1216 max: MAX_NESTING_DEPTH,
1217 });
1218 }
1219 let result = self.parse_link_traversal_inner();
1220 self.depth -= 1;
1221 result
1222 }
1223
1224 fn parse_link_traversal_inner(&mut self) -> Result<NestedQuery, ParseError> {
1225 let outer_alias = match self.advance() {
1226 Token::Ident(n) => n,
1227 t => return Err(self.named_ident_error("link outer alias", &t)),
1228 };
1229 let link_name = match self.advance() {
1230 Token::DotIdent(n) => n,
1231 t => {
1232 return Err(ParseError::UnexpectedToken {
1233 expected: "link name".into(),
1234 got: t.display_name(),
1235 position: None,
1236 })
1237 }
1238 };
1239 let child_alias = link_name.clone();
1242
1243 let mut residual = None;
1247 if *self.peek() == Token::Filter {
1248 self.advance();
1249 residual = Some(qualify_bare_fields(self.parse_expr()?, &child_alias));
1250 }
1251 let mut order = None;
1252 let mut limit = None;
1253 let mut offset = None;
1254 let mut offset_before_limit = false;
1255 loop {
1256 match self.peek() {
1257 Token::Order => {
1258 self.advance();
1259 let mut clause = self.parse_order()?;
1260 for key in &mut clause.keys {
1261 key.expr = qualify_bare_fields(
1262 std::mem::replace(&mut key.expr, Expr::Null),
1263 &child_alias,
1264 );
1265 }
1266 order = Some(clause);
1267 }
1268 Token::Limit => {
1269 self.advance();
1270 if offset.is_some() {
1271 offset_before_limit = true;
1272 }
1273 limit = Some(self.parse_expr()?);
1274 }
1275 Token::Offset => {
1276 self.advance();
1277 offset = Some(self.parse_expr()?);
1278 }
1279 _ => break,
1280 }
1281 }
1282 self.expect(&Token::LBrace)?;
1283 let mut fields = Vec::new();
1284 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1285 let alias = if matches!(self.peek(), Token::Ident(_))
1286 && matches!(self.tokens.get(self.pos + 1), Some(Token::Colon))
1287 {
1288 let alias = self.expect_named_ident("field alias")?;
1289 self.advance(); Some(alias)
1291 } else {
1292 None
1293 };
1294 self.reject_bare_dotted_path()?;
1295 let expr = qualify_bare_fields(self.parse_expr()?, &child_alias);
1296 fields.push(ProjectionField { alias, expr });
1297 if *self.peek() == Token::Comma {
1298 self.advance();
1299 }
1300 }
1301 self.expect(&Token::RBrace)?;
1302 if fields.is_empty() {
1303 return Err(ParseError::Syntax {
1304 message: "link traversal requires at least one field".into(),
1305 position: None,
1306 });
1307 }
1308 Ok(NestedQuery {
1309 source: String::new(),
1311 alias: child_alias,
1312 via_link: Some(ViaLink {
1313 outer_alias,
1314 link_name,
1315 }),
1316 filter: residual.unwrap_or(Expr::Literal(Literal::Bool(true))),
1319 order,
1320 limit,
1321 offset,
1322 offset_before_limit,
1323 fields,
1324 })
1325 }
1326
1327 fn parse_nested_query(&mut self) -> Result<NestedQuery, ParseError> {
1332 self.depth += 1;
1335 if self.depth > MAX_NESTING_DEPTH {
1336 self.depth -= 1;
1337 return Err(ParseError::NestingDepthExceeded {
1338 max: MAX_NESTING_DEPTH,
1339 });
1340 }
1341 let result = self.parse_nested_query_inner();
1342 self.depth -= 1;
1343 result
1344 }
1345
1346 fn parse_nested_query_inner(&mut self) -> Result<NestedQuery, ParseError> {
1347 let source = self.expect_named_ident("nested source type")?;
1348 self.expect(&Token::As)?;
1349 let alias = self.expect_named_ident("nested source alias")?;
1350 self.expect(&Token::Filter)?;
1351 let filter = self.parse_expr()?;
1352 let mut order = None;
1353 let mut limit = None;
1354 let mut offset = None;
1355 let mut offset_before_limit = false;
1356 loop {
1357 match self.peek() {
1358 Token::Order => {
1359 self.advance();
1360 order = Some(self.parse_order()?);
1361 }
1362 Token::Limit => {
1363 self.advance();
1364 if offset.is_some() {
1365 offset_before_limit = true;
1366 }
1367 limit = Some(self.parse_expr()?);
1368 }
1369 Token::Offset => {
1370 self.advance();
1371 offset = Some(self.parse_expr()?);
1372 }
1373 _ => break,
1374 }
1375 }
1376 self.expect(&Token::LBrace)?;
1377 let mut fields = Vec::new();
1378 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1379 let alias = if matches!(self.peek(), Token::Ident(_))
1380 && matches!(self.tokens.get(self.pos + 1), Some(Token::Colon))
1381 {
1382 let alias = self.expect_named_ident("field alias")?;
1383 self.advance(); Some(alias)
1385 } else {
1386 None
1387 };
1388 let expr = if matches!(self.peek(), Token::Ident(_))
1389 && matches!(self.tokens.get(self.pos + 1), Some(Token::As))
1390 {
1391 if alias.is_none() {
1392 return Err(ParseError::Syntax {
1393 message: "a nested projection needs a field name: \
1394 `<name>: <Table> as <alias> filter ... { ... }`"
1395 .into(),
1396 position: None,
1397 });
1398 }
1399 Expr::NestedQuery(Box::new(self.parse_nested_query()?))
1400 } else {
1401 self.reject_bare_dotted_path()?;
1402 self.parse_expr()?
1403 };
1404 fields.push(ProjectionField { alias, expr });
1405 if *self.peek() == Token::Comma {
1406 self.advance();
1407 }
1408 }
1409 self.expect(&Token::RBrace)?;
1410 if fields.is_empty() {
1411 return Err(ParseError::Syntax {
1412 message: "nested projection requires at least one field".into(),
1413 position: None,
1414 });
1415 }
1416 Ok(NestedQuery {
1417 source,
1418 alias,
1419 via_link: None,
1420 filter,
1421 order,
1422 limit,
1423 offset,
1424 offset_before_limit,
1425 fields,
1426 })
1427 }
1428
1429 fn parse_over_clause(&mut self) -> Result<(Vec<Expr>, Vec<OrderKey>), ParseError> {
1432 self.expect(&Token::Over)?;
1433 self.expect(&Token::LParen)?;
1434 let mut partition_by = Vec::new();
1435 let mut order_by = Vec::new();
1436 if *self.peek() == Token::Partition {
1437 self.advance();
1438 loop {
1439 partition_by.push(self.parse_expr()?);
1440 if *self.peek() == Token::Comma {
1441 if !matches!(
1442 self.tokens.get(self.pos + 1),
1443 Some(Token::Order | Token::RParen)
1444 ) {
1445 self.advance();
1446 } else {
1447 break;
1448 }
1449 } else {
1450 break;
1451 }
1452 }
1453 }
1454 if *self.peek() == Token::Order {
1455 self.advance();
1456 loop {
1457 let expr = self.parse_expr()?;
1458 let descending = match self.peek() {
1459 Token::Desc => {
1460 self.advance();
1461 true
1462 }
1463 Token::Asc => {
1464 self.advance();
1465 false
1466 }
1467 _ => false,
1468 };
1469 order_by.push(OrderKey { expr, descending });
1470 if *self.peek() == Token::Comma {
1471 self.advance();
1472 } else {
1473 break;
1474 }
1475 }
1476 }
1477 self.expect(&Token::RParen)?;
1478 Ok((partition_by, order_by))
1479 }
1480
1481 fn parse_cast_type(&mut self) -> Result<CastType, ParseError> {
1483 match self.advance() {
1484 Token::StringLit(s) => match s.as_str() {
1485 "int" | "Int" | "INT" => Ok(CastType::Int),
1486 "float" | "Float" | "FLOAT" => Ok(CastType::Float),
1487 "str" | "Str" | "STR" | "string" | "String" => Ok(CastType::Str),
1488 "bool" | "Bool" | "BOOL" | "boolean" => Ok(CastType::Bool),
1489 "datetime" | "DateTime" | "DATETIME" => Ok(CastType::DateTime),
1490 "uuid" | "Uuid" | "UUID" => Ok(CastType::Uuid),
1491 "bytes" | "Bytes" | "BYTES" | "bytea" => Ok(CastType::Bytes),
1492 other => Err(ParseError::Syntax {
1493 message: format!("invalid cast type: \"{other}\""),
1494 position: None,
1495 }),
1496 },
1497 t => Err(ParseError::UnexpectedToken {
1498 expected: "string literal for cast type".into(),
1499 got: t.display_name(),
1500 position: None,
1501 }),
1502 }
1503 }
1504
1505 fn parse_order(&mut self) -> Result<OrderClause, ParseError> {
1506 let mut keys = Vec::new();
1507 loop {
1508 let expr = self.parse_expr()?;
1509 let descending = match self.peek() {
1510 Token::Desc => {
1511 self.advance();
1512 true
1513 }
1514 Token::Asc => {
1515 self.advance();
1516 false
1517 }
1518 _ => false,
1519 };
1520 keys.push(OrderKey { expr, descending });
1521 if *self.peek() == Token::Comma {
1522 self.advance();
1523 } else {
1524 break;
1525 }
1526 }
1527 Ok(OrderClause { keys })
1528 }
1529
1530 fn parse_aggregate_query(&mut self) -> Result<Statement, ParseError> {
1531 let mut func = match self.advance() {
1532 Token::Count => AggFunc::Count,
1533 Token::Avg => AggFunc::Avg,
1534 Token::Sum => AggFunc::Sum,
1535 Token::Min => AggFunc::Min,
1536 Token::Max => AggFunc::Max,
1537 t => {
1538 return Err(ParseError::UnexpectedToken {
1539 expected: "aggregate function".into(),
1540 got: t.display_name(),
1541 position: None,
1542 })
1543 }
1544 };
1545 self.expect(&Token::LParen)?;
1546 let mode = if *self.peek() == Token::Raw {
1547 self.advance();
1548 AggregateMode::Raw
1549 } else {
1550 AggregateMode::Symmetric
1551 };
1552 if func == AggFunc::Count && *self.peek() == Token::Distinct {
1554 self.advance();
1555 func = AggFunc::CountDistinct;
1556 }
1557 let source = match self.advance() {
1558 Token::Ident(name) => name,
1559 t => {
1560 return Err(ParseError::UnexpectedToken {
1561 expected: "type name".into(),
1562 got: t.display_name(),
1563 position: None,
1564 })
1565 }
1566 };
1567 let mut query = self.parse_query_tail(source)?;
1571 self.expect(&Token::RParen)?;
1572
1573 let mut argument: Option<Expr> = None;
1586 if let Some(proj) = &query.projection {
1587 if proj.len() == 1 && proj[0].alias.is_none() {
1588 argument = Some(proj[0].expr.clone());
1589 }
1590 }
1591 if argument.is_some() {
1592 query.projection = None;
1593 }
1594 query.aggregation = Some(AggregateExpr {
1595 function: func,
1596 argument,
1597 mode,
1598 });
1599 Ok(Statement::Query(query))
1600 }
1601
1602 fn parse_expr(&mut self) -> Result<Expr, ParseError> {
1603 self.depth += 1;
1604 if self.depth > MAX_NESTING_DEPTH {
1605 self.depth -= 1;
1606 return Err(ParseError::NestingDepthExceeded {
1607 max: MAX_NESTING_DEPTH,
1608 });
1609 }
1610 let result = self.parse_or_expr();
1611 self.depth -= 1;
1612 result
1613 }
1614
1615 fn check_chain_depth(&self, chain: usize) -> Result<(), ParseError> {
1620 if self.depth + chain > MAX_NESTING_DEPTH {
1621 return Err(ParseError::NestingDepthExceeded {
1622 max: MAX_NESTING_DEPTH,
1623 });
1624 }
1625 Ok(())
1626 }
1627
1628 fn parse_or_expr(&mut self) -> Result<Expr, ParseError> {
1629 let mut left = self.parse_and_expr()?;
1630 let mut chain = 0usize;
1631 while *self.peek() == Token::Or {
1632 chain += 1;
1633 self.check_chain_depth(chain)?;
1634 self.advance();
1635 let right = self.parse_and_expr()?;
1636 left = Expr::BinaryOp(Box::new(left), BinOp::Or, Box::new(right));
1637 }
1638 Ok(left)
1639 }
1640
1641 fn parse_and_expr(&mut self) -> Result<Expr, ParseError> {
1642 let mut left = self.parse_comparison()?;
1643 let mut chain = 0usize;
1644 while *self.peek() == Token::And {
1645 chain += 1;
1646 self.check_chain_depth(chain)?;
1647 self.advance();
1648 let right = self.parse_comparison()?;
1649 left = Expr::BinaryOp(Box::new(left), BinOp::And, Box::new(right));
1650 }
1651 Ok(left)
1652 }
1653
1654 fn parse_comparison(&mut self) -> Result<Expr, ParseError> {
1655 let mut negations = 0usize;
1665 while *self.peek() == Token::Not
1666 && !matches!(self.tokens.get(self.pos + 1), Some(Token::Exists))
1667 {
1668 self.advance();
1669 negations += 1;
1670 self.check_chain_depth(negations)?;
1671 }
1672 let mut expr = self.parse_comparison_body()?;
1673 for _ in 0..negations {
1674 expr = Expr::UnaryOp(UnaryOp::Not, Box::new(expr));
1675 }
1676 Ok(expr)
1677 }
1678
1679 fn parse_comparison_body(&mut self) -> Result<Expr, ParseError> {
1680 let left = self.parse_additive()?;
1681
1682 if *self.peek() == Token::Is {
1684 self.advance();
1685 if *self.peek() == Token::Not {
1686 self.advance();
1687 self.expect(&Token::Null)?;
1688 return Ok(Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(left)));
1689 } else {
1690 self.expect(&Token::Null)?;
1691 return Ok(Expr::UnaryOp(UnaryOp::IsNull, Box::new(left)));
1692 }
1693 }
1694
1695 match self.peek() {
1698 Token::In => {
1699 self.advance();
1700 return self.parse_in_list(left, false);
1701 }
1702 Token::Like => {
1703 self.advance();
1704 let pattern = self.parse_additive()?;
1705 return Ok(Expr::BinaryOp(
1706 Box::new(left),
1707 BinOp::Like,
1708 Box::new(pattern),
1709 ));
1710 }
1711 Token::Between => {
1712 self.advance();
1713 return self.parse_between(left, false);
1714 }
1715 Token::Not => {
1716 let next = self.tokens.get(self.pos + 1);
1720 match next {
1721 Some(Token::In) => {
1722 self.advance(); self.advance(); return self.parse_in_list(left, true);
1725 }
1726 Some(Token::Like) => {
1727 self.advance(); self.advance(); let pattern = self.parse_additive()?;
1730 let like = Expr::BinaryOp(Box::new(left), BinOp::Like, Box::new(pattern));
1731 return Ok(Expr::UnaryOp(UnaryOp::Not, Box::new(like)));
1732 }
1733 Some(Token::Between) => {
1734 self.advance(); self.advance(); return self.parse_between(left, true);
1737 }
1738 _ => {}
1739 }
1740 }
1741 _ => {}
1742 }
1743
1744 let op = match self.peek() {
1745 Token::Eq => BinOp::Eq,
1746 Token::Neq => BinOp::Neq,
1747 Token::Lt => BinOp::Lt,
1748 Token::Gt => BinOp::Gt,
1749 Token::Lte => BinOp::Lte,
1750 Token::Gte => BinOp::Gte,
1751 _ => return Ok(left),
1752 };
1753 self.advance();
1754 if *self.peek() == Token::Null {
1758 match op {
1759 BinOp::Eq => {
1760 self.advance();
1761 return Ok(Expr::UnaryOp(UnaryOp::IsNull, Box::new(left)));
1762 }
1763 BinOp::Neq => {
1764 self.advance();
1765 return Ok(Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(left)));
1766 }
1767 _ => {}
1768 }
1769 }
1770 let right = self.parse_additive()?;
1771 Ok(Expr::BinaryOp(Box::new(left), op, Box::new(right)))
1772 }
1773
1774 fn parse_in_list(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParseError> {
1779 self.expect(&Token::LParen)?;
1780 if let Token::Ident(_) = self.peek() {
1782 let after = self.tokens.get(self.pos + 1);
1785 let is_subquery = !matches!(after, Some(Token::Comma) | Some(Token::RParen));
1786 if is_subquery {
1787 let source = match self.advance() {
1788 Token::Ident(name) => name,
1789 _ => unreachable!(),
1790 };
1791 let subquery = self.parse_query_tail(source)?;
1792 self.expect(&Token::RParen)?;
1793 return Ok(Expr::InSubquery {
1794 expr: Box::new(expr),
1795 subquery: Box::new(subquery),
1796 negated,
1797 });
1798 }
1799 }
1800 let mut list = Vec::new();
1801 while !matches!(self.peek(), Token::RParen | Token::Eof) {
1802 list.push(self.parse_expr()?);
1803 if *self.peek() == Token::Comma {
1804 self.advance();
1805 }
1806 }
1807 self.expect(&Token::RParen)?;
1808 Ok(Expr::InList {
1809 expr: Box::new(expr),
1810 list,
1811 negated,
1812 })
1813 }
1814
1815 fn try_parse_exists_subquery(&mut self) -> Result<Option<QueryExpr>, ParseError> {
1823 if *self.peek() != Token::LParen {
1824 return Ok(None);
1825 }
1826 let after_lparen = self.tokens.get(self.pos + 1);
1830 if !matches!(after_lparen, Some(Token::Ident(_))) {
1831 return Ok(None);
1832 }
1833 self.expect(&Token::LParen)?;
1834 let source = match self.advance() {
1835 Token::Ident(name) => name,
1836 _ => unreachable!(),
1837 };
1838 let subquery = self.parse_query_tail(source)?;
1839 self.expect(&Token::RParen)?;
1840 Ok(Some(subquery))
1841 }
1842
1843 fn parse_between(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParseError> {
1847 let low = self.parse_additive()?;
1848 self.expect(&Token::And)?;
1849 let high = self.parse_additive()?;
1850 if negated {
1851 Ok(Expr::BinaryOp(
1853 Box::new(Expr::BinaryOp(
1854 Box::new(expr.clone()),
1855 BinOp::Lt,
1856 Box::new(low),
1857 )),
1858 BinOp::Or,
1859 Box::new(Expr::BinaryOp(Box::new(expr), BinOp::Gt, Box::new(high))),
1860 ))
1861 } else {
1862 Ok(Expr::BinaryOp(
1864 Box::new(Expr::BinaryOp(
1865 Box::new(expr.clone()),
1866 BinOp::Gte,
1867 Box::new(low),
1868 )),
1869 BinOp::And,
1870 Box::new(Expr::BinaryOp(Box::new(expr), BinOp::Lte, Box::new(high))),
1871 ))
1872 }
1873 }
1874
1875 fn parse_group_by(&mut self) -> Result<GroupByClause, ParseError> {
1877 let mut keys = Vec::new();
1878 loop {
1879 let expr = self.parse_expr()?;
1880 let output_name = match &expr {
1881 Expr::Field(_) | Expr::QualifiedField { .. } | Expr::JsonPath { .. } => {
1882 expression_output_name(&expr)
1883 }
1884 _ => format!("__group_{}", keys.len()),
1885 };
1886 keys.push(GroupKey { expr, output_name });
1887 if *self.peek() == Token::Comma {
1888 self.advance();
1889 } else {
1890 break;
1891 }
1892 }
1893 if keys.is_empty() {
1894 return Err(ParseError::Syntax {
1895 message: "expected at least one group key after group".into(),
1896 position: None,
1897 });
1898 }
1899 let having = if *self.peek() == Token::Having {
1900 self.advance();
1901 Some(self.parse_expr()?)
1902 } else {
1903 None
1904 };
1905 Ok(GroupByClause { keys, having })
1906 }
1907
1908 fn parse_additive(&mut self) -> Result<Expr, ParseError> {
1909 let mut left = self.parse_multiplicative()?;
1910 let mut chain = 0usize;
1911 loop {
1912 let op = match self.peek() {
1913 Token::Plus => BinOp::Add,
1914 Token::Minus => BinOp::Sub,
1915 Token::Coalesce => {
1916 chain += 1;
1917 self.check_chain_depth(chain)?;
1918 self.advance();
1919 let right = self.parse_multiplicative()?;
1920 left = Expr::Coalesce(Box::new(left), Box::new(right));
1921 continue;
1922 }
1923 _ => break,
1924 };
1925 chain += 1;
1926 self.check_chain_depth(chain)?;
1927 self.advance();
1928 let right = self.parse_multiplicative()?;
1929 left = Expr::BinaryOp(Box::new(left), op, Box::new(right));
1930 }
1931 Ok(left)
1932 }
1933
1934 fn parse_multiplicative(&mut self) -> Result<Expr, ParseError> {
1935 let mut left = self.parse_primary()?;
1936 let mut chain = 0usize;
1937 loop {
1938 let op = match self.peek() {
1939 Token::Star => BinOp::Mul,
1940 Token::Slash => BinOp::Div,
1941 _ => break,
1942 };
1943 chain += 1;
1944 self.check_chain_depth(chain)?;
1945 self.advance();
1946 let right = self.parse_primary()?;
1947 left = Expr::BinaryOp(Box::new(left), op, Box::new(right));
1948 }
1949 Ok(left)
1950 }
1951
1952 fn parse_primary(&mut self) -> Result<Expr, ParseError> {
1953 self.depth += 1;
1958 if self.depth > MAX_NESTING_DEPTH {
1959 self.depth -= 1;
1960 return Err(ParseError::NestingDepthExceeded {
1961 max: MAX_NESTING_DEPTH,
1962 });
1963 }
1964 let result = self.parse_primary_inner();
1965 self.depth -= 1;
1966 self.parse_json_path_postfix(result?)
1972 }
1973
1974 fn parse_json_path_postfix(&mut self, base: Expr) -> Result<Expr, ParseError> {
1981 if *self.peek() != Token::Arrow {
1982 return Ok(base);
1983 }
1984 match &base {
1985 Expr::Field(_) | Expr::QualifiedField { .. } | Expr::JsonPath { .. } => {}
1986 _ => {
1987 return Err(ParseError::Syntax {
1988 message: "'->' JSON path access requires a field base \
1989 (e.g. .data->key or posts.data->author)"
1990 .into(),
1991 position: None,
1992 })
1993 }
1994 }
1995 let mut segments = Vec::new();
1996 while *self.peek() == Token::Arrow {
1997 self.advance(); let seg = match self.advance() {
1999 Token::Ident(name) => PathSeg::Key(name),
2001 Token::StringLit(s) => PathSeg::Key(s),
2004 Token::IntLit(v) => {
2008 let idx = u32::try_from(v).map_err(|_| ParseError::Syntax {
2009 message: format!(
2010 "invalid JSON path array index {v}: expected a non-negative integer that fits in 32 bits"
2011 ),
2012 position: None,
2013 })?;
2014 PathSeg::Index(idx)
2015 }
2016 other => {
2017 return Err(ParseError::Syntax {
2018 message: format!(
2019 "expected a JSON path segment (object key or array index) after '->', found {}",
2020 other.display_name()
2021 ),
2022 position: None,
2023 })
2024 }
2025 };
2026 segments.push(seg);
2027 }
2028 if let Expr::JsonPath {
2031 base: inner_base,
2032 segments: mut inner_segments,
2033 } = base
2034 {
2035 inner_segments.extend(segments);
2036 return Ok(Expr::JsonPath {
2037 base: inner_base,
2038 segments: inner_segments,
2039 });
2040 }
2041 Ok(Expr::JsonPath {
2042 base: Box::new(base),
2043 segments,
2044 })
2045 }
2046
2047 fn parse_primary_inner(&mut self) -> Result<Expr, ParseError> {
2048 match self.peek().clone() {
2049 Token::DotIdent(name) => {
2050 self.advance();
2051 Ok(Expr::Field(name))
2052 }
2053 Token::IntLit(v) => {
2054 self.advance();
2055 Ok(Expr::Literal(Literal::Int(v)))
2056 }
2057 Token::FloatLit(v) => {
2058 self.advance();
2059 Ok(Expr::Literal(Literal::Float(v)))
2060 }
2061 Token::StringLit(v) => {
2062 self.advance();
2063 Ok(Expr::Literal(Literal::String(v)))
2064 }
2065 Token::BoolLit(v) => {
2066 self.advance();
2067 Ok(Expr::Literal(Literal::Bool(v)))
2068 }
2069 Token::Null => {
2076 self.advance();
2077 Ok(Expr::Null)
2078 }
2079 Token::Not => {
2080 self.advance();
2081 if *self.peek() == Token::Exists {
2082 self.advance();
2083 if let Some(sub) = self.try_parse_exists_subquery()? {
2087 return Ok(Expr::ExistsSubquery {
2088 subquery: Box::new(sub),
2089 negated: true,
2090 });
2091 }
2092 let expr = self.parse_primary()?;
2093 Ok(Expr::UnaryOp(UnaryOp::NotExists, Box::new(expr)))
2094 } else {
2095 let expr = self.parse_primary()?;
2096 Ok(Expr::UnaryOp(UnaryOp::Not, Box::new(expr)))
2097 }
2098 }
2099 Token::Exists => {
2100 self.advance();
2101 if let Some(sub) = self.try_parse_exists_subquery()? {
2105 return Ok(Expr::ExistsSubquery {
2106 subquery: Box::new(sub),
2107 negated: false,
2108 });
2109 }
2110 let expr = self.parse_primary()?;
2111 Ok(Expr::UnaryOp(UnaryOp::Exists, Box::new(expr)))
2112 }
2113 Token::LParen => {
2114 self.advance();
2115 let expr = self.parse_expr()?;
2116 self.expect(&Token::RParen)?;
2117 Ok(expr)
2118 }
2119 Token::Ident(name) => {
2120 self.advance();
2121 if *self.peek() == Token::LParen {
2126 let cast_type = match name.as_str() {
2127 "uuid" => Some(CastType::Uuid),
2128 "bytes" => Some(CastType::Bytes),
2129 _ => None,
2130 };
2131 if let Some(cast_type) = cast_type {
2132 self.advance(); let inner = self.parse_expr()?;
2134 self.expect(&Token::RParen)?;
2135 return Ok(Expr::Cast(Box::new(inner), cast_type));
2136 }
2137 }
2138 if let Token::DotIdent(field) = self.peek().clone() {
2142 self.advance();
2143 return Ok(Expr::QualifiedField {
2144 qualifier: name,
2145 field,
2146 });
2147 }
2148 Ok(Expr::Field(name))
2149 }
2150 Token::RowNumber | Token::Rank | Token::DenseRank => {
2152 let wfunc = match self.advance() {
2153 Token::RowNumber => WindowFunc::RowNumber,
2154 Token::Rank => WindowFunc::Rank,
2155 Token::DenseRank => WindowFunc::DenseRank,
2156 _ => {
2157 return Err(ParseError::Syntax {
2158 message: "unexpected window function token".into(),
2159 position: None,
2160 })
2161 }
2162 };
2163 self.expect(&Token::LParen)?;
2164 self.expect(&Token::RParen)?;
2165 let (partition_by, order_by) = self.parse_over_clause()?;
2166 Ok(Expr::Window {
2167 function: wfunc,
2168 args: vec![],
2169 mode: AggregateMode::Symmetric,
2170 partition_by,
2171 order_by,
2172 })
2173 }
2174 Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
2178 let mut func = match self.advance() {
2179 Token::Count => AggFunc::Count,
2180 Token::Avg => AggFunc::Avg,
2181 Token::Sum => AggFunc::Sum,
2182 Token::Min => AggFunc::Min,
2183 Token::Max => AggFunc::Max,
2184 _ => {
2185 return Err(ParseError::Syntax {
2186 message: "unexpected aggregate token".into(),
2187 position: None,
2188 })
2189 }
2190 };
2191 self.expect(&Token::LParen)?;
2192 let mode = if *self.peek() == Token::Raw {
2193 self.advance();
2194 AggregateMode::Raw
2195 } else {
2196 AggregateMode::Symmetric
2197 };
2198 if func == AggFunc::Count && *self.peek() == Token::Star {
2200 self.advance();
2201 self.expect(&Token::RParen)?;
2202 if *self.peek() == Token::Over {
2204 let (partition_by, order_by) = self.parse_over_clause()?;
2205 return Ok(Expr::Window {
2206 function: WindowFunc::Count,
2207 args: vec![Expr::Field("*".into())],
2208 mode,
2209 partition_by,
2210 order_by,
2211 });
2212 }
2213 return Ok(Expr::FunctionCall(
2214 AggFunc::Count,
2215 Box::new(Expr::Field("*".into())),
2216 mode,
2217 ));
2218 }
2219 if func == AggFunc::Count && *self.peek() == Token::Distinct {
2221 self.advance();
2222 func = AggFunc::CountDistinct;
2223 }
2224 let inner = self.parse_expr()?;
2225 self.expect(&Token::RParen)?;
2226 if *self.peek() == Token::Over {
2228 let wfunc = match func {
2229 AggFunc::Count => WindowFunc::Count,
2230 AggFunc::Avg => WindowFunc::Avg,
2231 AggFunc::Sum => WindowFunc::Sum,
2232 AggFunc::Min => WindowFunc::Min,
2233 AggFunc::Max => WindowFunc::Max,
2234 _ => {
2235 return Err(ParseError::Unsupported {
2236 feature: "count(distinct ...) over (...) is not supported".into(),
2237 })
2238 }
2239 };
2240 let (partition_by, order_by) = self.parse_over_clause()?;
2241 return Ok(Expr::Window {
2242 function: wfunc,
2243 args: vec![inner],
2244 mode,
2245 partition_by,
2246 order_by,
2247 });
2248 }
2249 Ok(Expr::FunctionCall(func, Box::new(inner), mode))
2250 }
2251 Token::Upper
2252 | Token::Lower
2253 | Token::Length
2254 | Token::Trim
2255 | Token::Substring
2256 | Token::Concat
2257 | Token::Abs
2258 | Token::Round
2259 | Token::Ceil
2260 | Token::Floor
2261 | Token::Sqrt
2262 | Token::Pow
2263 | Token::Now
2264 | Token::Extract
2265 | Token::DateAdd
2266 | Token::DateDiff
2267 | Token::JsonType
2268 | Token::JsonText => {
2269 let tok = self.advance();
2270 let func = token_to_scalar_fn(&tok);
2271 self.expect(&Token::LParen)?;
2272 let mut args = Vec::new();
2273 while !matches!(self.peek(), Token::RParen | Token::Eof) {
2274 args.push(self.parse_expr()?);
2275 if *self.peek() == Token::Comma {
2276 self.advance();
2277 }
2278 }
2279 self.expect(&Token::RParen)?;
2280 Ok(Expr::ScalarFunc(func, args))
2281 }
2282 Token::Cast => {
2283 self.advance();
2284 self.expect(&Token::LParen)?;
2285 let inner = self.parse_expr()?;
2286 self.expect(&Token::Comma)?;
2287 let cast_type = self.parse_cast_type()?;
2288 self.expect(&Token::RParen)?;
2289 Ok(Expr::Cast(Box::new(inner), cast_type))
2290 }
2291 Token::Case => {
2292 self.advance();
2293 let mut whens = Vec::new();
2294 while *self.peek() == Token::When {
2295 self.advance();
2296 let condition = self.parse_expr()?;
2297 self.expect(&Token::Then)?;
2298 let result = self.parse_expr()?;
2299 whens.push((Box::new(condition), Box::new(result)));
2300 }
2301 let else_expr = if *self.peek() == Token::Else {
2302 self.advance();
2303 Some(Box::new(self.parse_expr()?))
2304 } else {
2305 None
2306 };
2307 self.expect(&Token::End)?;
2308 Ok(Expr::Case { whens, else_expr })
2309 }
2310 t => Err(ParseError::Syntax {
2311 message: format!("unexpected token in expression: {}", t.display_name()),
2312 position: None,
2313 }),
2314 }
2315 }
2316
2317 fn parse_create_link(&mut self) -> Result<Statement, ParseError> {
2325 self.expect(&Token::Link)?;
2326 let owner = self.expect_named_ident("link owner type")?;
2327 let name = match self.advance() {
2328 Token::DotIdent(n) => n,
2329 t => {
2330 return Err(ParseError::UnexpectedToken {
2331 expected: "`.<name>` after the owner type (link <Owner>.<name> -> ...)".into(),
2332 got: t.display_name(),
2333 position: None,
2334 })
2335 }
2336 };
2337 let (target, local_key, target_key) = self.parse_link_tail()?;
2338 Ok(Statement::CreateLink(CreateLinkExpr {
2339 owner,
2340 name,
2341 target,
2342 local_key,
2343 target_key,
2344 }))
2345 }
2346
2347 fn parse_link_tail(&mut self) -> Result<(String, String, String), ParseError> {
2350 self.expect(&Token::Arrow)?;
2351 let target = self.expect_named_ident("link target type")?;
2352 self.expect(&Token::On)?;
2353 let local_key = self.parse_link_column("link local key")?;
2354 self.expect(&Token::Eq)?;
2355 let target_key = self.parse_link_column("link target key")?;
2356 Ok((target, local_key, target_key))
2357 }
2358
2359 fn parse_link_column(&mut self, context: &str) -> Result<String, ParseError> {
2362 match self.advance() {
2363 Token::Ident(n) | Token::DotIdent(n) => Ok(n),
2364 t => Err(self.named_ident_error(context, &t)),
2365 }
2366 }
2367
2368 fn parse_alter_table(&mut self) -> Result<Statement, ParseError> {
2369 self.expect(&Token::Alter)?;
2370 let table = match self.advance() {
2371 Token::Ident(name) => name,
2372 t => {
2373 return Err(ParseError::UnexpectedToken {
2374 expected: "table name after alter".into(),
2375 got: t.display_name(),
2376 position: None,
2377 })
2378 }
2379 };
2380 match self.peek() {
2381 Token::Add => {
2382 self.advance();
2383 if *self.peek() == Token::Index {
2385 self.advance();
2386 let if_not_exists = self.parse_optional_if_not_exists();
2387 let target = self.parse_index_target("add index")?;
2388 return Ok(Statement::AlterTable(AlterTableExpr {
2389 table,
2390 action: AlterAction::AddIndex {
2391 target,
2392 if_not_exists,
2393 },
2394 }));
2395 }
2396 if *self.peek() == Token::Unique {
2398 self.advance();
2399 let if_not_exists = self.parse_optional_if_not_exists();
2400 let target = self.parse_index_target("add unique")?;
2401 return Ok(Statement::AlterTable(AlterTableExpr {
2402 table,
2403 action: AlterAction::AddUnique {
2404 target,
2405 if_not_exists,
2406 },
2407 }));
2408 }
2409 if *self.peek() == Token::Link {
2411 self.advance();
2412 let name = self.expect_named_ident("link name")?;
2413 let (target, local_key, target_key) = self.parse_link_tail()?;
2414 return Ok(Statement::AlterTable(AlterTableExpr {
2415 table,
2416 action: AlterAction::AddLink {
2417 name,
2418 target,
2419 local_key,
2420 target_key,
2421 },
2422 }));
2423 }
2424 if *self.peek() == Token::Column {
2426 self.advance();
2427 }
2428 let required = if *self.peek() == Token::Required {
2429 self.advance();
2430 true
2431 } else {
2432 false
2433 };
2434 let name = self.expect_named_ident("column name")?;
2435 self.expect(&Token::Colon)?;
2436 let type_name = match self.advance() {
2437 Token::Ident(n) => n,
2438 t => {
2439 return Err(ParseError::UnexpectedToken {
2440 expected: "type name".into(),
2441 got: t.display_name(),
2442 position: None,
2443 })
2444 }
2445 };
2446 Ok(Statement::AlterTable(AlterTableExpr {
2447 table,
2448 action: AlterAction::AddColumn {
2449 name,
2450 type_name,
2451 required,
2452 },
2453 }))
2454 }
2455 Token::Drop => {
2456 self.advance();
2457 if *self.peek() == Token::Index {
2458 self.advance();
2459 let if_exists = self.parse_optional_if_exists();
2460 let target = self.parse_index_target("drop index")?;
2461 return Ok(Statement::AlterTable(AlterTableExpr {
2462 table,
2463 action: AlterAction::DropIndex { target, if_exists },
2464 }));
2465 }
2466 if *self.peek() == Token::Column {
2468 self.advance();
2469 }
2470 let if_exists = self.parse_optional_if_exists();
2471 let name = self.expect_named_ident("column name")?;
2472 Ok(Statement::AlterTable(AlterTableExpr {
2473 table,
2474 action: AlterAction::DropColumn { name, if_exists },
2475 }))
2476 }
2477 t => Err(ParseError::UnexpectedToken {
2478 expected: "add or drop after alter <table>".into(),
2479 got: t.display_name(),
2480 position: None,
2481 }),
2482 }
2483 }
2484
2485 fn parse_index_target(&mut self, action: &str) -> Result<IndexTarget, ParseError> {
2490 match self.peek() {
2491 Token::DotIdent(_) => {
2492 if matches!(self.tokens.get(self.pos + 1), Some(Token::Arrow)) {
2493 return Err(ParseError::Syntax {
2494 message: format!(
2495 "JSON path index targets must be parenthesized after {action}; use `(.data->key)`"
2496 ),
2497 position: None,
2498 });
2499 }
2500 let Token::DotIdent(column) = self.advance() else {
2501 unreachable!("guarded by DotIdent match")
2502 };
2503 Ok(IndexTarget::Column(column))
2504 }
2505 Token::LParen => {
2506 self.advance();
2507 let expr = self.parse_expr().map_err(|error| match error {
2508 ParseError::NestingDepthExceeded { .. } => error,
2509 _ => ParseError::Syntax {
2510 message: format!(
2511 "invalid expression index target after {action}: expected an unqualified JSON path like `(.data->key)`"
2512 ),
2513 position: None,
2514 },
2515 })?;
2516 if *self.peek() != Token::RParen {
2517 return Err(ParseError::Syntax {
2518 message: format!(
2519 "invalid expression index target after {action}: only a direct JSON path is supported"
2520 ),
2521 position: None,
2522 });
2523 }
2524 self.advance();
2525
2526 match JsonPathIdentityV1::from_expr(&expr) {
2527 Some(identity) => identity.bind_table_local(None).map(IndexTarget::JsonPath).ok_or_else(|| {
2528 ParseError::Syntax {
2529 message: format!(
2530 "qualified JSON paths are not valid index targets after {action}; use an unqualified table-local path like `(.data->key)`"
2531 ),
2532 position: None,
2533 }
2534 }),
2535 None => match expr {
2536 Expr::Field(_) => Err(ParseError::Syntax {
2537 message: format!(
2538 "invalid expression index target after {action}: parentheses are reserved for a direct JSON path like `(.data->key)`; use `.column` for a stored column"
2539 ),
2540 position: None,
2541 }),
2542 Expr::QualifiedField { .. } => Err(ParseError::Syntax {
2543 message: format!(
2544 "qualified references are not valid index targets after {action}; use a table-local `.column` or `(.data->key)`"
2545 ),
2546 position: None,
2547 }),
2548 _ => Err(ParseError::Syntax {
2549 message: format!(
2550 "invalid expression index target after {action}: only a direct JSON path is supported"
2551 ),
2552 position: None,
2553 }),
2554 },
2555 }
2556 }
2557 token => Err(ParseError::UnexpectedToken {
2558 expected: format!(".<column> or parenthesized JSON path after {action}"),
2559 got: token.display_name(),
2560 position: None,
2561 }),
2562 }
2563 }
2564
2565 fn parse_drop_or_drop_view(&mut self) -> Result<Statement, ParseError> {
2567 self.expect(&Token::Drop)?;
2568 if *self.peek() == Token::View {
2569 self.advance(); let if_exists = self.parse_optional_if_exists();
2571 let name = match self.advance() {
2572 Token::Ident(name) => name,
2573 t => {
2574 return Err(ParseError::UnexpectedToken {
2575 expected: "view name after drop view".into(),
2576 got: t.display_name(),
2577 position: None,
2578 })
2579 }
2580 };
2581 return Ok(Statement::DropView(DropViewExpr { name, if_exists }));
2582 }
2583 let if_exists = self.parse_optional_if_exists();
2584 let table = match self.advance() {
2585 Token::Ident(name) => name,
2586 t => {
2587 return Err(ParseError::UnexpectedToken {
2588 expected: "table name after drop".into(),
2589 got: t.display_name(),
2590 position: None,
2591 })
2592 }
2593 };
2594 Ok(Statement::DropTable(DropTableExpr { table, if_exists }))
2595 }
2596
2597 fn parse_create_view(&mut self) -> Result<Statement, ParseError> {
2602 self.expect(&Token::Materialized)?;
2603 let name = match self.advance() {
2604 Token::Ident(name) => name,
2605 t => {
2606 return Err(ParseError::UnexpectedToken {
2607 expected: "view name after materialize".into(),
2608 got: t.display_name(),
2609 position: None,
2610 })
2611 }
2612 };
2613 self.expect(&Token::As)?;
2614 let query_start = self.pos;
2616 let source = match self.advance() {
2617 Token::Ident(s) => s,
2618 t => {
2619 return Err(ParseError::UnexpectedToken {
2620 expected: "source table name".into(),
2621 got: t.display_name(),
2622 position: None,
2623 })
2624 }
2625 };
2626 let query = self.parse_query_tail(source)?;
2627 let query_text = tokens_to_text(&self.tokens[query_start..self.pos])?;
2629 Ok(Statement::CreateView(CreateViewExpr {
2630 name,
2631 query,
2632 query_text,
2633 }))
2634 }
2635
2636 fn maybe_parse_union(&mut self, left: Statement) -> Result<Statement, ParseError> {
2639 if *self.peek() != Token::Union {
2640 return Ok(left);
2641 }
2642 if !matches!(left, Statement::Query(_) | Statement::Union(_)) {
2643 return Err(ParseError::Syntax {
2644 message: "UNION requires a query on the left side".into(),
2645 position: None,
2646 });
2647 }
2648 self.advance(); let all = if let Token::Ident(s) = self.peek() {
2650 if s == "all" {
2651 self.advance();
2652 true
2653 } else {
2654 false
2655 }
2656 } else {
2657 false
2658 };
2659 let right = self.parse_single_query()?;
2661 let union = Statement::Union(UnionExpr {
2662 left: Box::new(left),
2663 right: Box::new(right),
2664 all,
2665 });
2666 self.maybe_parse_union(union)
2668 }
2669
2670 fn parse_single_query(&mut self) -> Result<Statement, ParseError> {
2672 match self.peek() {
2673 Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
2674 self.parse_aggregate_query()
2675 }
2676 Token::Ident(_) => self.parse_query_or_mutation(),
2677 _ => Err(ParseError::Syntax {
2678 message: format!(
2679 "expected query after UNION, got {}",
2680 self.peek().display_name()
2681 ),
2682 position: None,
2683 }),
2684 }
2685 }
2686
2687 fn parse_refresh_view(&mut self) -> Result<Statement, ParseError> {
2689 self.expect(&Token::Refresh)?;
2690 let name = match self.advance() {
2691 Token::Ident(name) => name,
2692 t => {
2693 return Err(ParseError::UnexpectedToken {
2694 expected: "view name after refresh".into(),
2695 got: t.display_name(),
2696 position: None,
2697 })
2698 }
2699 };
2700 Ok(Statement::RefreshView(RefreshViewExpr { name }))
2701 }
2702
2703 fn parse_create_type(&mut self) -> Result<Statement, ParseError> {
2704 self.expect(&Token::Type)?;
2705 let name = self.expect_named_ident("type name")?;
2706 let if_not_exists = self.parse_optional_if_not_exists();
2707 self.expect(&Token::LBrace)?;
2708 let mut fields = Vec::new();
2709 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
2710 let (mut required, mut unique, mut auto) = (false, false, false);
2715 loop {
2716 let is_modifier =
2717 matches!(self.peek(), Token::Required | Token::Unique | Token::Auto)
2718 && !matches!(self.tokens.get(self.pos + 1), Some(Token::Colon));
2719 if !is_modifier {
2720 break;
2721 }
2722 match self.advance() {
2723 Token::Required => required = true,
2724 Token::Unique => unique = true,
2725 Token::Auto => auto = true,
2726 _ => unreachable!("guarded by is_modifier"),
2727 }
2728 }
2729 let field_name = self.expect_named_ident("field name")?;
2730 self.expect(&Token::Colon)?;
2731 let type_name = match self.advance() {
2732 Token::Ident(n) => n,
2733 t => {
2734 return Err(ParseError::UnexpectedToken {
2735 expected: "type name".into(),
2736 got: t.display_name(),
2737 position: None,
2738 })
2739 }
2740 };
2741 let default = if *self.peek() == Token::Default {
2744 self.advance();
2745 Some(self.parse_default_literal()?)
2746 } else {
2747 None
2748 };
2749 fields.push(FieldDef {
2750 name: field_name,
2751 type_name,
2752 required,
2753 unique,
2754 default,
2755 auto,
2756 });
2757 if *self.peek() == Token::Comma {
2758 self.advance();
2759 }
2760 }
2761 self.expect(&Token::RBrace)?;
2762 Ok(Statement::CreateType(CreateTypeExpr {
2763 name,
2764 fields,
2765 if_not_exists,
2766 }))
2767 }
2768
2769 fn parse_schema(&mut self) -> Result<Statement, ParseError> {
2772 self.expect(&Token::Schema)?;
2773 if let Token::Ident(name) = self.peek() {
2774 if name == "links" {
2775 self.advance();
2776 return Ok(Statement::ListLinks);
2777 }
2778 let table = self.expect_named_ident("type name")?;
2779 return Ok(Statement::Describe(table));
2780 }
2781 Ok(Statement::ListTypes)
2782 }
2783
2784 fn parse_describe(&mut self) -> Result<Statement, ParseError> {
2786 self.expect(&Token::Describe)?;
2787 let table = self.expect_named_ident("type name")?;
2788 Ok(Statement::Describe(table))
2789 }
2790
2791 fn parse_default_literal(&mut self) -> Result<Literal, ParseError> {
2795 match self.advance() {
2796 Token::IntLit(v) => Ok(Literal::Int(v)),
2797 Token::FloatLit(v) => Ok(Literal::Float(v)),
2798 Token::StringLit(v) => Ok(Literal::String(v)),
2799 Token::BoolLit(v) => Ok(Literal::Bool(v)),
2800 t => Err(ParseError::UnexpectedToken {
2801 expected: "literal default value".into(),
2802 got: t.display_name(),
2803 position: None,
2804 }),
2805 }
2806 }
2807}
2808
2809fn qualify_bare_fields(expr: Expr, alias: &str) -> Expr {
2816 let recur = |e: Expr| Box::new(qualify_bare_fields(e, alias));
2817 match expr {
2818 Expr::Field(field) => Expr::QualifiedField {
2819 qualifier: alias.to_string(),
2820 field,
2821 },
2822 Expr::BinaryOp(l, op, r) => Expr::BinaryOp(recur(*l), op, recur(*r)),
2823 Expr::UnaryOp(op, inner) => Expr::UnaryOp(op, recur(*inner)),
2824 Expr::Coalesce(l, r) => Expr::Coalesce(recur(*l), recur(*r)),
2825 Expr::Cast(inner, ty) => Expr::Cast(recur(*inner), ty),
2826 Expr::ScalarFunc(func, args) => Expr::ScalarFunc(
2827 func,
2828 args.into_iter()
2829 .map(|a| qualify_bare_fields(a, alias))
2830 .collect(),
2831 ),
2832 Expr::InList {
2833 expr,
2834 list,
2835 negated,
2836 } => Expr::InList {
2837 expr: recur(*expr),
2838 list: list
2839 .into_iter()
2840 .map(|a| qualify_bare_fields(a, alias))
2841 .collect(),
2842 negated,
2843 },
2844 Expr::Case { whens, else_expr } => Expr::Case {
2845 whens: whens
2846 .into_iter()
2847 .map(|(c, r)| (recur(*c), recur(*r)))
2848 .collect(),
2849 else_expr: else_expr.map(|e| recur(*e)),
2850 },
2851 Expr::JsonPath { base, segments } => Expr::JsonPath {
2852 base: recur(*base),
2853 segments,
2854 },
2855 other => other,
2857 }
2858}
2859
2860fn relexes_to(text: &str, tok: &Token) -> bool {
2865 match lex(text) {
2866 Ok(toks) => matches!(toks.as_slice(), [t, Token::Eof] if t == tok),
2867 Err(_) => false,
2868 }
2869}
2870
2871fn push_string_literal(out: &mut String, s: &str) {
2879 out.push('"');
2880 for c in s.chars() {
2881 match c {
2882 '"' => out.push_str("\\\""),
2883 '\\' => out.push_str("\\\\"),
2884 '\n' => out.push_str("\\n"),
2885 '\t' => out.push_str("\\t"),
2886 other => out.push(other),
2887 }
2888 }
2889 out.push('"');
2890}
2891
2892fn push_ident(out: &mut String, prefix: &str, name: &str, tok: &Token) -> Result<(), ParseError> {
2900 let bare = format!("{prefix}{name}");
2901 if relexes_to(&bare, tok) {
2902 out.push_str(&bare);
2903 return Ok(());
2904 }
2905 let quoted = format!("{prefix}`{name}`");
2906 if relexes_to("ed, tok) {
2907 out.push_str("ed);
2908 return Ok(());
2909 }
2910 Err(ParseError::Unsupported {
2911 feature: format!(
2912 "cannot store view source: identifier '{name}' has no PowQL spelling that reads back unchanged"
2913 ),
2914 })
2915}
2916
2917fn tokens_to_text(tokens: &[Token]) -> Result<String, ParseError> {
2927 let mut out = String::with_capacity(64);
2928 for tok in tokens {
2929 if !out.is_empty() && !matches!(tok, Token::Eof) {
2930 out.push(' ');
2931 }
2932 match tok {
2933 Token::Ident(s) => push_ident(&mut out, "", s, tok)?,
2934 Token::DotIdent(s) => push_ident(&mut out, ".", s, tok)?,
2935 Token::IntLit(v) => out.push_str(&v.to_string()),
2936 Token::FloatLit(v) => {
2937 if !v.is_finite() {
2938 return Err(ParseError::Unsupported {
2939 feature: "cannot store view source: non-finite number literal".into(),
2940 });
2941 }
2942 let rendered = v.to_string();
2943 out.push_str(&rendered);
2944 if !rendered.contains('.') {
2949 out.push_str(".0");
2950 }
2951 }
2952 Token::StringLit(s) => push_string_literal(&mut out, s),
2953 Token::BoolLit(v) => out.push_str(if *v { "true" } else { "false" }),
2954 Token::Param(s) => {
2955 out.push('$');
2956 out.push_str(s);
2957 }
2958 Token::Type => out.push_str("type"),
2959 Token::Filter => out.push_str("filter"),
2960 Token::Order => out.push_str("order"),
2961 Token::Limit => out.push_str("limit"),
2962 Token::Offset => out.push_str("offset"),
2963 Token::Insert => out.push_str("insert"),
2964 Token::Update => out.push_str("update"),
2965 Token::Delete => out.push_str("delete"),
2966 Token::Upsert => out.push_str("upsert"),
2967 Token::Returning => out.push_str("returning"),
2968 Token::Conflict => out.push_str("conflict"),
2969 Token::Select => out.push_str("select"),
2970 Token::Required => out.push_str("required"),
2971 Token::Default => out.push_str("default"),
2972 Token::Auto => out.push_str("auto"),
2973 Token::Multi => out.push_str("multi"),
2974 Token::Link => out.push_str("link"),
2975 Token::Index => out.push_str("index"),
2976 Token::Unique => out.push_str("unique"),
2977 Token::On => out.push_str("on"),
2978 Token::Asc => out.push_str("asc"),
2979 Token::Desc => out.push_str("desc"),
2980 Token::And => out.push_str("and"),
2981 Token::Or => out.push_str("or"),
2982 Token::Not => out.push_str("not"),
2983 Token::Exists => out.push_str("exists"),
2984 Token::Let => out.push_str("let"),
2985 Token::As => out.push_str("as"),
2986 Token::Match => out.push_str("match"),
2987 Token::Group => out.push_str("group"),
2988 Token::Join => out.push_str("join"),
2989 Token::Inner => out.push_str("inner"),
2990 Token::LeftKw => out.push_str("left"),
2991 Token::RightKw => out.push_str("right"),
2992 Token::Outer => out.push_str("outer"),
2993 Token::Cross => out.push_str("cross"),
2994 Token::Transaction => out.push_str("transaction"),
2995 Token::Begin => out.push_str("begin"),
2996 Token::Commit => out.push_str("commit"),
2997 Token::Rollback => out.push_str("rollback"),
2998 Token::View => out.push_str("view"),
2999 Token::Materialized => out.push_str("materialized"),
3000 Token::Refresh => out.push_str("refresh"),
3001 Token::Union => out.push_str("union"),
3002 Token::Having => out.push_str("having"),
3003 Token::Distinct => out.push_str("distinct"),
3004 Token::In => out.push_str("in"),
3005 Token::Between => out.push_str("between"),
3006 Token::Like => out.push_str("like"),
3007 Token::Count => out.push_str("count"),
3008 Token::Avg => out.push_str("avg"),
3009 Token::Sum => out.push_str("sum"),
3010 Token::Raw => out.push_str("raw"),
3011 Token::Min => out.push_str("min"),
3012 Token::Max => out.push_str("max"),
3013 Token::Is => out.push_str("is"),
3014 Token::Null => out.push_str("null"),
3015 Token::Upper => out.push_str("upper"),
3016 Token::Lower => out.push_str("lower"),
3017 Token::Length => out.push_str("length"),
3018 Token::Trim => out.push_str("trim"),
3019 Token::Substring => out.push_str("substring"),
3020 Token::Concat => out.push_str("concat"),
3021 Token::Abs => out.push_str("abs"),
3022 Token::Round => out.push_str("round"),
3023 Token::Ceil => out.push_str("ceil"),
3024 Token::Floor => out.push_str("floor"),
3025 Token::Sqrt => out.push_str("sqrt"),
3026 Token::Pow => out.push_str("pow"),
3027 Token::Now => out.push_str("now"),
3028 Token::Extract => out.push_str("extract"),
3029 Token::DateAdd => out.push_str("date_add"),
3030 Token::DateDiff => out.push_str("date_diff"),
3031 Token::JsonType => out.push_str("json_type"),
3032 Token::JsonText => out.push_str("json_text"),
3033 Token::Cast => out.push_str("cast"),
3034 Token::Case => out.push_str("case"),
3035 Token::When => out.push_str("when"),
3036 Token::Then => out.push_str("then"),
3037 Token::Else => out.push_str("else"),
3038 Token::End => out.push_str("end"),
3039 Token::Over => out.push_str("over"),
3040 Token::Partition => out.push_str("partition"),
3041 Token::RowNumber => out.push_str("row_number"),
3042 Token::Rank => out.push_str("rank"),
3043 Token::DenseRank => out.push_str("dense_rank"),
3044 Token::Alter => out.push_str("alter"),
3045 Token::Drop => out.push_str("drop"),
3046 Token::Add => out.push_str("add"),
3047 Token::Column => out.push_str("column"),
3048 Token::Eq => out.push('='),
3049 Token::Neq => out.push_str("!="),
3050 Token::Lt => out.push('<'),
3051 Token::Gt => out.push('>'),
3052 Token::Lte => out.push_str("<="),
3053 Token::Gte => out.push_str(">="),
3054 Token::Assign => out.push_str(":="),
3055 Token::Arrow => out.push_str("->"),
3056 Token::Pipe => out.push('|'),
3057 Token::Coalesce => out.push_str("??"),
3058 Token::Plus => out.push('+'),
3059 Token::Minus => out.push('-'),
3060 Token::Star => out.push('*'),
3061 Token::Slash => out.push('/'),
3062 Token::LBrace => out.push('{'),
3063 Token::RBrace => out.push('}'),
3064 Token::LParen => out.push('('),
3065 Token::RParen => out.push(')'),
3066 Token::Comma => out.push(','),
3067 Token::Colon => out.push(':'),
3068 Token::Dot => out.push('.'),
3069 Token::Explain => out.push_str("explain"),
3070 Token::Schema => out.push_str("schema"),
3071 Token::Describe => out.push_str("describe"),
3072 Token::Eof => {}
3073 }
3074 }
3075 let mut expected: Vec<Token> = tokens
3082 .iter()
3083 .filter(|t| **t != Token::Eof)
3084 .cloned()
3085 .collect();
3086 expected.push(Token::Eof);
3087 match lex(&out) {
3088 Ok(round_tripped) if round_tripped == expected => Ok(out),
3089 _ => Err(ParseError::Unsupported {
3090 feature: "cannot store view source: query text does not read back unchanged".into(),
3091 }),
3092 }
3093}
3094
3095#[cfg(test)]
3096mod tests {
3097 use super::*;
3098 #[test]
3099 fn test_parse_simple_query() {
3100 let stmt = parse("User").unwrap();
3101 match stmt {
3102 Statement::Query(q) => {
3103 assert_eq!(q.source, "User");
3104 assert!(q.filter.is_none());
3105 assert!(q.projection.is_none());
3106 }
3107 _ => panic!("expected query"),
3108 }
3109 }
3110
3111 #[test]
3112 fn test_parse_filter() {
3113 let stmt = parse("User filter .age > 30").unwrap();
3114 match stmt {
3115 Statement::Query(q) => {
3116 assert_eq!(q.source, "User");
3117 assert!(q.filter.is_some());
3118 }
3119 _ => panic!("expected query"),
3120 }
3121 }
3122
3123 #[test]
3124 fn test_parse_projection() {
3125 let stmt = parse("User { name, email }").unwrap();
3126 match stmt {
3127 Statement::Query(q) => {
3128 let proj = q.projection.unwrap();
3129 assert_eq!(proj.len(), 2);
3130 }
3131 _ => panic!("expected query"),
3132 }
3133 }
3134
3135 #[test]
3136 fn test_bare_dotted_path_projection_is_a_parse_error() {
3137 for q in [
3141 "Order { .user.name }",
3142 "Order { .id, uname: .user.name }",
3143 "Order { uname: .user.company.name }",
3144 ] {
3145 let err = parse(q).unwrap_err();
3146 let msg = err.to_string();
3147 assert!(
3148 msg.contains("alias the table"),
3149 "`{q}` should error with alias guidance, got: {msg}"
3150 );
3151 }
3152 }
3153
3154 #[test]
3155 fn test_aliased_scalar_link_path_parses_as_one_field() {
3156 let stmt = parse("Order as o { uname: o.user.name }").unwrap();
3157 match stmt {
3158 Statement::Query(q) => {
3159 let proj = q.projection.unwrap();
3160 assert_eq!(proj.len(), 1);
3161 match &proj[0].expr {
3162 Expr::LinkPath {
3163 outer_alias,
3164 links,
3165 column,
3166 } => {
3167 assert_eq!(outer_alias, "o");
3168 assert_eq!(links, &["user".to_string()]);
3169 assert_eq!(column, "name");
3170 }
3171 other => panic!("expected LinkPath, got {other:?}"),
3172 }
3173 }
3174 _ => panic!("expected query"),
3175 }
3176 }
3177
3178 #[test]
3179 fn test_parse_filter_order_limit() {
3180 let stmt = parse("User filter .age > 30 order .name desc limit 10").unwrap();
3181 match stmt {
3182 Statement::Query(q) => {
3183 assert!(q.filter.is_some());
3184 let order = q.order.unwrap();
3185 assert_eq!(order.keys.len(), 1);
3186 assert_eq!(order.keys[0].expr, Expr::Field("name".into()));
3187 assert!(order.keys[0].descending);
3188 assert!(q.limit.is_some());
3189 }
3190 _ => panic!("expected query"),
3191 }
3192 }
3193
3194 #[test]
3195 fn test_parse_insert() {
3196 let stmt = parse(r#"insert User { name := "Alice", age := 30 }"#).unwrap();
3197 match stmt {
3198 Statement::Insert(ins) => {
3199 assert_eq!(ins.target, "User");
3200 assert_eq!(ins.rows.len(), 1);
3201 assert_eq!(ins.rows[0].len(), 2);
3202 assert_eq!(ins.rows[0][0].field, "name");
3203 assert_eq!(ins.rows[0][1].field, "age");
3204 }
3205 _ => panic!("expected insert"),
3206 }
3207 }
3208
3209 #[test]
3210 fn test_parse_insert_multi_row() {
3211 let stmt =
3212 parse(r#"insert User { name := "Alice", age := 30 }, { name := "Bob", age := 25 }, { name := "Cy" }"#)
3213 .unwrap();
3214 match stmt {
3215 Statement::Insert(ins) => {
3216 assert_eq!(ins.target, "User");
3217 assert_eq!(ins.rows.len(), 3);
3218 assert_eq!(ins.rows[0].len(), 2);
3219 assert_eq!(ins.rows[1][0].field, "name");
3220 assert_eq!(ins.rows[2].len(), 1);
3221 assert_eq!(ins.rows[2][0].field, "name");
3222 }
3223 _ => panic!("expected insert"),
3224 }
3225 }
3226
3227 #[test]
3228 fn test_parse_update() {
3229 let stmt = parse(r#"User filter .email = "alice@ex.com" update { age := 31 }"#).unwrap();
3230 match stmt {
3231 Statement::UpdateQuery(upd) => {
3232 assert_eq!(upd.source, "User");
3233 assert!(upd.filter.is_some());
3234 assert_eq!(upd.assignments.len(), 1);
3235 assert!(!upd.returning);
3236 }
3237 _ => panic!("expected update"),
3238 }
3239 }
3240
3241 #[test]
3242 fn test_parse_update_returning() {
3243 let stmt = parse(r#"User filter .name = "Alice" update { age := 31 } returning"#).unwrap();
3244 match stmt {
3245 Statement::UpdateQuery(upd) => assert!(upd.returning),
3246 _ => panic!("expected update"),
3247 }
3248 }
3249
3250 #[test]
3251 fn test_parse_delete() {
3252 let stmt = parse("User filter .age < 18 delete").unwrap();
3253 match stmt {
3254 Statement::DeleteQuery(del) => {
3255 assert_eq!(del.source, "User");
3256 assert!(del.filter.is_some());
3257 assert!(!del.returning);
3258 }
3259 _ => panic!("expected delete"),
3260 }
3261 }
3262
3263 #[test]
3264 fn test_parse_delete_returning() {
3265 let stmt = parse("User filter .age < 18 delete returning").unwrap();
3266 match stmt {
3267 Statement::DeleteQuery(del) => assert!(del.returning),
3268 _ => panic!("expected delete"),
3269 }
3270 }
3271
3272 #[test]
3273 fn test_parse_count() {
3274 let stmt = parse("count(User)").unwrap();
3275 match stmt {
3276 Statement::Query(q) => {
3277 let agg = q.aggregation.unwrap();
3278 assert_eq!(agg.function, AggFunc::Count);
3279 assert!(q.filter.is_none());
3280 }
3281 _ => panic!("expected query with aggregation"),
3282 }
3283 }
3284
3285 #[test]
3286 fn test_parse_count_with_filter() {
3287 let stmt = parse("count(User filter .age > 30)").unwrap();
3290 match stmt {
3291 Statement::Query(q) => {
3292 assert_eq!(q.source, "User");
3293 let agg = q.aggregation.unwrap();
3294 assert_eq!(agg.function, AggFunc::Count);
3295 assert!(q.filter.is_some(), "filter should have been parsed");
3296 }
3297 _ => panic!("expected query with aggregation"),
3298 }
3299 }
3300
3301 #[test]
3302 fn test_parse_count_with_filter_and_limit() {
3303 let stmt = parse("count(User filter .age > 30 limit 100)").unwrap();
3304 match stmt {
3305 Statement::Query(q) => {
3306 assert_eq!(q.source, "User");
3307 assert!(q.filter.is_some());
3308 assert!(q.limit.is_some());
3309 assert_eq!(q.aggregation.unwrap().function, AggFunc::Count);
3310 }
3311 _ => panic!("expected query with aggregation"),
3312 }
3313 }
3314
3315 #[test]
3316 fn test_parse_create_type() {
3317 let stmt = parse("type User { required name: str, age: int }").unwrap();
3318 match stmt {
3319 Statement::CreateType(ct) => {
3320 assert_eq!(ct.name, "User");
3321 assert_eq!(ct.fields.len(), 2);
3322 assert!(ct.fields[0].required);
3323 assert!(!ct.fields[1].required);
3324 }
3325 _ => panic!("expected create type"),
3326 }
3327 }
3328
3329 #[test]
3330 fn test_parse_sum_with_field_projection() {
3331 let stmt = parse("sum(User filter .age > 30 { .age })").unwrap();
3334 match stmt {
3335 Statement::Query(q) => {
3336 let agg = q.aggregation.expect("aggregate");
3337 assert_eq!(agg.function, AggFunc::Sum);
3338 assert_eq!(agg.argument, Some(Expr::Field("age".into())));
3339 assert!(
3340 q.projection.is_none(),
3341 "projection should be lifted into agg.field"
3342 );
3343 }
3344 _ => panic!("expected query"),
3345 }
3346 }
3347
3348 #[test]
3349 fn test_parse_raw_aggregate_modes() {
3350 let Statement::Query(top_level) = parse("sum(raw User { .age })").unwrap() else {
3351 panic!("expected query");
3352 };
3353 assert_eq!(top_level.aggregation.unwrap().mode, AggregateMode::Raw);
3354
3355 let Statement::Query(grouped) = parse("User group .dept { total: sum(raw .age) }").unwrap()
3356 else {
3357 panic!("expected query");
3358 };
3359 assert!(matches!(
3360 grouped.projection.unwrap()[0].expr,
3361 Expr::FunctionCall(AggFunc::Sum, _, AggregateMode::Raw)
3362 ));
3363 }
3364
3365 #[test]
3366 fn test_parse_avg_min_max_with_field() {
3367 for (src, expected) in [
3368 ("avg(User { .age })", AggFunc::Avg),
3369 ("min(User { .age })", AggFunc::Min),
3370 ("max(User { .age })", AggFunc::Max),
3371 ] {
3372 let stmt = parse(src).unwrap();
3373 match stmt {
3374 Statement::Query(q) => {
3375 let agg = q.aggregation.unwrap();
3376 assert_eq!(agg.function, expected, "func mismatch for {src}");
3377 assert_eq!(
3378 agg.argument,
3379 Some(Expr::Field("age".into())),
3380 "field mismatch for {src}"
3381 );
3382 assert!(
3383 q.projection.is_none(),
3384 "projection should be cleared for {src}"
3385 );
3386 }
3387 _ => panic!("expected query for {src}"),
3388 }
3389 }
3390 }
3391
3392 #[test]
3393 fn test_parse_count_lifts_projection_into_argument() {
3394 let stmt = parse("count(User { .age })").unwrap();
3399 match stmt {
3400 Statement::Query(q) => {
3401 let agg = q.aggregation.unwrap();
3402 assert_eq!(agg.function, AggFunc::Count);
3403 assert_eq!(agg.argument, Some(Expr::Field("age".into())));
3404 assert!(q.projection.is_none(), "projection should be lifted");
3405 }
3406 _ => panic!("expected query"),
3407 }
3408 }
3409
3410 #[test]
3411 fn test_parse_count_without_projection_has_no_argument() {
3412 let stmt = parse("count(User)").unwrap();
3414 match stmt {
3415 Statement::Query(q) => {
3416 let agg = q.aggregation.unwrap();
3417 assert_eq!(agg.function, AggFunc::Count);
3418 assert!(agg.argument.is_none());
3419 }
3420 _ => panic!("expected query"),
3421 }
3422 }
3423
3424 #[test]
3429 fn test_parse_source_alias() {
3430 let stmt = parse("User as u filter u.age > 30").unwrap();
3431 match stmt {
3432 Statement::Query(q) => {
3433 assert_eq!(q.source, "User");
3434 assert_eq!(q.alias.as_deref(), Some("u"));
3435 assert!(q.joins.is_empty());
3436 match q.filter.unwrap() {
3437 Expr::BinaryOp(l, BinOp::Gt, _) => match *l {
3438 Expr::QualifiedField { qualifier, field } => {
3439 assert_eq!(qualifier, "u");
3440 assert_eq!(field, "age");
3441 }
3442 other => panic!("expected qualified field, got {other:?}"),
3443 },
3444 other => panic!("expected >, got {other:?}"),
3445 }
3446 }
3447 _ => panic!("expected query"),
3448 }
3449 }
3450
3451 #[test]
3452 fn test_parse_inner_join_on() {
3453 let stmt = parse("User as u inner join Order as o on u.id = o.user_id").unwrap();
3454 match stmt {
3455 Statement::Query(q) => {
3456 assert_eq!(q.source, "User");
3457 assert_eq!(q.alias.as_deref(), Some("u"));
3458 assert_eq!(q.joins.len(), 1);
3459 let j = &q.joins[0];
3460 assert_eq!(j.kind, JoinKind::Inner);
3461 assert_eq!(j.source, "Order");
3462 assert_eq!(j.alias.as_deref(), Some("o"));
3463 let on = j.on.as_ref().expect("on clause");
3464 match on {
3465 Expr::BinaryOp(l, BinOp::Eq, r) => {
3466 assert!(matches!(**l, Expr::QualifiedField { .. }));
3467 assert!(matches!(**r, Expr::QualifiedField { .. }));
3468 }
3469 other => panic!("expected eq, got {other:?}"),
3470 }
3471 }
3472 _ => panic!("expected query"),
3473 }
3474 }
3475
3476 #[test]
3477 fn test_parse_bare_join_defaults_to_inner() {
3478 let stmt = parse("User join Order on User.id = Order.user_id").unwrap();
3479 match stmt {
3480 Statement::Query(q) => {
3481 assert_eq!(q.joins.len(), 1);
3482 assert_eq!(q.joins[0].kind, JoinKind::Inner);
3483 }
3484 _ => panic!("expected query"),
3485 }
3486 }
3487
3488 #[test]
3489 fn test_parse_left_outer_join() {
3490 let stmt = parse("User as u left outer join Order as o on u.id = o.user_id").unwrap();
3491 match stmt {
3492 Statement::Query(q) => {
3493 assert_eq!(q.joins.len(), 1);
3494 assert_eq!(q.joins[0].kind, JoinKind::LeftOuter);
3495 }
3496 _ => panic!("expected query"),
3497 }
3498 }
3499
3500 #[test]
3501 fn test_parse_left_join_without_outer_keyword() {
3502 let stmt = parse("User as u left join Order as o on u.id = o.user_id").unwrap();
3504 match stmt {
3505 Statement::Query(q) => {
3506 assert_eq!(q.joins[0].kind, JoinKind::LeftOuter);
3507 }
3508 _ => panic!("expected query"),
3509 }
3510 }
3511
3512 #[test]
3513 fn test_parse_right_join() {
3514 let stmt = parse("User as u right join Order as o on u.id = o.user_id").unwrap();
3515 match stmt {
3516 Statement::Query(q) => {
3517 assert_eq!(q.joins[0].kind, JoinKind::RightOuter);
3518 }
3519 _ => panic!("expected query"),
3520 }
3521 }
3522
3523 #[test]
3524 fn test_parse_cross_join_has_no_on() {
3525 let stmt = parse("User cross join Order").unwrap();
3526 match stmt {
3527 Statement::Query(q) => {
3528 assert_eq!(q.joins[0].kind, JoinKind::Cross);
3529 assert!(q.joins[0].on.is_none());
3530 }
3531 _ => panic!("expected query"),
3532 }
3533 }
3534
3535 #[test]
3536 fn test_parse_multi_join_chain() {
3537 let stmt = parse(
3538 "User as u join Order as o on u.id = o.user_id \
3539 join Product as p on o.product_id = p.id",
3540 )
3541 .unwrap();
3542 match stmt {
3543 Statement::Query(q) => {
3544 assert_eq!(q.joins.len(), 2);
3545 assert_eq!(q.joins[0].source, "Order");
3546 assert_eq!(q.joins[1].source, "Product");
3547 }
3548 _ => panic!("expected query"),
3549 }
3550 }
3551
3552 #[test]
3553 fn test_parse_join_with_filter_tail() {
3554 let stmt = parse(
3556 "User as u join Order as o on u.id = o.user_id \
3557 filter o.total > 100 order .name limit 10",
3558 )
3559 .unwrap();
3560 match stmt {
3561 Statement::Query(q) => {
3562 assert_eq!(q.joins.len(), 1);
3563 assert!(q.filter.is_some());
3564 assert!(q.order.is_some());
3565 assert!(q.limit.is_some());
3566 }
3567 _ => panic!("expected query"),
3568 }
3569 }
3570
3571 #[test]
3572 fn test_parse_join_requires_on_for_inner() {
3573 let err = parse("User join Order").unwrap_err();
3575 assert!(
3576 err.message().contains("on"),
3577 "expected on-clause error, got {:?}",
3578 err.message()
3579 );
3580 }
3581
3582 #[test]
3583 fn test_parse_update_on_joined_query_errors() {
3584 let err =
3587 parse("User as u join Order as o on u.id = o.user_id update { age := 1 }").unwrap_err();
3588 assert!(err.message().contains("update"));
3589 }
3590
3591 #[test]
3592 fn test_parse_delete_on_joined_query_errors() {
3593 let err = parse("User as u join Order as o on u.id = o.user_id delete").unwrap_err();
3594 assert!(err.message().contains("delete"));
3595 }
3596
3597 #[test]
3600 fn test_parse_distinct() {
3601 let stmt = parse("User distinct { .name }").unwrap();
3602 match stmt {
3603 Statement::Query(q) => {
3604 assert!(q.distinct);
3605 assert!(q.projection.is_some());
3606 }
3607 _ => panic!("expected query"),
3608 }
3609 }
3610
3611 #[test]
3612 fn test_parse_in_list() {
3613 let stmt = parse(r#"User filter .name in ("Alice", "Bob")"#).unwrap();
3614 match stmt {
3615 Statement::Query(q) => match q.filter.unwrap() {
3616 Expr::InList {
3617 expr,
3618 list,
3619 negated,
3620 } => {
3621 assert!(!negated);
3622 assert!(matches!(*expr, Expr::Field(f) if f == "name"));
3623 assert_eq!(list.len(), 2);
3624 }
3625 other => panic!("expected InList, got {other:?}"),
3626 },
3627 _ => panic!("expected query"),
3628 }
3629 }
3630
3631 #[test]
3632 fn test_parse_not_in_list() {
3633 let stmt = parse("User filter .age not in (1, 2, 3)").unwrap();
3634 match stmt {
3635 Statement::Query(q) => match q.filter.unwrap() {
3636 Expr::InList { negated, list, .. } => {
3637 assert!(negated);
3638 assert_eq!(list.len(), 3);
3639 }
3640 other => panic!("expected InList, got {other:?}"),
3641 },
3642 _ => panic!("expected query"),
3643 }
3644 }
3645
3646 #[test]
3647 fn test_parse_between() {
3648 let stmt = parse("User filter .age between 10 and 20").unwrap();
3650 match stmt {
3651 Statement::Query(q) => {
3652 match q.filter.unwrap() {
3653 Expr::BinaryOp(_, BinOp::And, _) => {} other => panic!("expected And (desugared between), got {other:?}"),
3655 }
3656 }
3657 _ => panic!("expected query"),
3658 }
3659 }
3660
3661 #[test]
3662 fn test_parse_not_between() {
3663 let stmt = parse("User filter .age not between 10 and 20").unwrap();
3665 match stmt {
3666 Statement::Query(q) => {
3667 match q.filter.unwrap() {
3668 Expr::BinaryOp(_, BinOp::Or, _) => {} other => panic!("expected Or (desugared not between), got {other:?}"),
3670 }
3671 }
3672 _ => panic!("expected query"),
3673 }
3674 }
3675
3676 #[test]
3677 fn test_parse_like() {
3678 let stmt = parse(r#"User filter .name like "A%""#).unwrap();
3679 match stmt {
3680 Statement::Query(q) => match q.filter.unwrap() {
3681 Expr::BinaryOp(l, BinOp::Like, r) => {
3682 assert!(matches!(*l, Expr::Field(f) if f == "name"));
3683 assert!(matches!(*r, Expr::Literal(Literal::String(s)) if s == "A%"));
3684 }
3685 other => panic!("expected Like, got {other:?}"),
3686 },
3687 _ => panic!("expected query"),
3688 }
3689 }
3690
3691 #[test]
3692 fn test_parse_not_like() {
3693 let stmt = parse(r#"User filter .name not like "A%""#).unwrap();
3694 match stmt {
3695 Statement::Query(q) => match q.filter.unwrap() {
3696 Expr::UnaryOp(UnaryOp::Not, inner) => {
3697 assert!(matches!(*inner, Expr::BinaryOp(_, BinOp::Like, _)));
3698 }
3699 other => panic!("expected Not(Like), got {other:?}"),
3700 },
3701 _ => panic!("expected query"),
3702 }
3703 }
3704
3705 #[test]
3708 fn test_parse_group_by_single_key() {
3709 let stmt = parse("User group .status { .status, n: count(.name) }").unwrap();
3710 match stmt {
3711 Statement::Query(q) => {
3712 let gb = q.group_by.unwrap();
3713 assert_eq!(
3714 gb.keys,
3715 vec![GroupKey {
3716 expr: Expr::Field("status".into()),
3717 output_name: "status".into(),
3718 }]
3719 );
3720 assert!(gb.having.is_none());
3721 let proj = q.projection.unwrap();
3722 assert_eq!(proj.len(), 2);
3723 assert!(matches!(
3724 &proj[1].expr,
3725 Expr::FunctionCall(AggFunc::Count, _, _)
3726 ));
3727 assert_eq!(proj[1].alias.as_deref(), Some("n"));
3728 }
3729 _ => panic!("expected query"),
3730 }
3731 }
3732
3733 #[test]
3734 fn test_parse_group_by_multi_key() {
3735 let stmt = parse("User group .status, .age { .status, .age }").unwrap();
3736 match stmt {
3737 Statement::Query(q) => {
3738 let gb = q.group_by.unwrap();
3739 assert_eq!(
3740 gb.keys,
3741 vec![
3742 GroupKey {
3743 expr: Expr::Field("status".into()),
3744 output_name: "status".into(),
3745 },
3746 GroupKey {
3747 expr: Expr::Field("age".into()),
3748 output_name: "age".into(),
3749 }
3750 ]
3751 );
3752 }
3753 _ => panic!("expected query"),
3754 }
3755 }
3756
3757 #[test]
3758 fn test_parse_group_by_having() {
3759 let stmt = parse("User group .status having count(.name) > 1 { .status }").unwrap();
3760 match stmt {
3761 Statement::Query(q) => {
3762 let gb = q.group_by.unwrap();
3763 assert_eq!(
3764 gb.keys,
3765 vec![GroupKey {
3766 expr: Expr::Field("status".into()),
3767 output_name: "status".into(),
3768 }]
3769 );
3770 assert!(gb.having.is_some());
3771 match gb.having.unwrap() {
3773 Expr::BinaryOp(l, BinOp::Gt, _) => {
3774 assert!(matches!(*l, Expr::FunctionCall(AggFunc::Count, _, _)));
3775 }
3776 other => panic!("expected BinaryOp, got {other:?}"),
3777 }
3778 }
3779 _ => panic!("expected query"),
3780 }
3781 }
3782
3783 #[test]
3784 fn test_parse_aggregate_in_projection() {
3785 let stmt = parse("User group .status { .status, count(.name), sum(.age) }").unwrap();
3787 match stmt {
3788 Statement::Query(q) => {
3789 let proj = q.projection.unwrap();
3790 assert_eq!(proj.len(), 3);
3791 assert!(matches!(
3792 &proj[1].expr,
3793 Expr::FunctionCall(AggFunc::Count, _, _)
3794 ));
3795 assert!(matches!(
3796 &proj[2].expr,
3797 Expr::FunctionCall(AggFunc::Sum, _, _)
3798 ));
3799 }
3800 _ => panic!("expected query"),
3801 }
3802 }
3803
3804 #[test]
3805 fn test_parse_aggregate_in_aliased_projection() {
3806 let stmt = parse("User group .status { .status, total: count(.name), average: avg(.age) }")
3807 .unwrap();
3808 match stmt {
3809 Statement::Query(q) => {
3810 let proj = q.projection.unwrap();
3811 assert_eq!(proj[1].alias.as_deref(), Some("total"));
3812 assert!(matches!(
3813 &proj[1].expr,
3814 Expr::FunctionCall(AggFunc::Count, _, _)
3815 ));
3816 assert_eq!(proj[2].alias.as_deref(), Some("average"));
3817 assert!(matches!(
3818 &proj[2].expr,
3819 Expr::FunctionCall(AggFunc::Avg, _, _)
3820 ));
3821 }
3822 _ => panic!("expected query"),
3823 }
3824 }
3825
3826 #[test]
3829 fn test_parse_is_null() {
3830 let stmt = parse("User filter .age is null").unwrap();
3831 match stmt {
3832 Statement::Query(q) => {
3833 let filter = q.filter.unwrap();
3834 assert_eq!(
3835 filter,
3836 Expr::UnaryOp(UnaryOp::IsNull, Box::new(Expr::Field("age".into())))
3837 );
3838 }
3839 _ => panic!("expected query"),
3840 }
3841 }
3842
3843 #[test]
3844 fn test_parse_is_not_null() {
3845 let stmt = parse("User filter .age is not null").unwrap();
3846 match stmt {
3847 Statement::Query(q) => {
3848 let filter = q.filter.unwrap();
3849 assert_eq!(
3850 filter,
3851 Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(Expr::Field("age".into())))
3852 );
3853 }
3854 _ => panic!("expected query"),
3855 }
3856 }
3857
3858 #[test]
3859 fn test_parse_eq_null_desugars_to_is_null() {
3860 let stmt = parse("User filter .age = null").unwrap();
3861 match stmt {
3862 Statement::Query(q) => {
3863 let filter = q.filter.unwrap();
3864 assert_eq!(
3865 filter,
3866 Expr::UnaryOp(UnaryOp::IsNull, Box::new(Expr::Field("age".into())))
3867 );
3868 }
3869 _ => panic!("expected query"),
3870 }
3871 }
3872
3873 #[test]
3874 fn test_parse_neq_null_desugars_to_is_not_null() {
3875 let stmt = parse("User filter .age != null").unwrap();
3876 match stmt {
3877 Statement::Query(q) => {
3878 let filter = q.filter.unwrap();
3879 assert_eq!(
3880 filter,
3881 Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(Expr::Field("age".into())))
3882 );
3883 }
3884 _ => panic!("expected query"),
3885 }
3886 }
3887
3888 #[test]
3889 fn test_parse_null_comparisons_parse_ok() {
3890 assert!(parse("User filter .age < null").is_ok());
3894 assert!(parse("User filter .age >= null").is_ok());
3895 }
3896
3897 #[test]
3898 fn test_parse_count_star_expr() {
3899 let stmt = parse("User filter count(*) > 0").unwrap();
3900 match stmt {
3901 Statement::Query(q) => {
3902 let filter = q.filter.unwrap();
3903 match filter {
3904 Expr::BinaryOp(left, BinOp::Gt, _) => {
3905 assert_eq!(
3906 *left,
3907 Expr::FunctionCall(
3908 AggFunc::Count,
3909 Box::new(Expr::Field("*".into())),
3910 AggregateMode::Symmetric,
3911 )
3912 );
3913 }
3914 _ => panic!("expected comparison"),
3915 }
3916 }
3917 _ => panic!("expected query"),
3918 }
3919 }
3920
3921 #[test]
3924 fn test_parse_upper_in_filter() {
3925 let stmt = parse(r#"User filter upper(.name) = "ALICE""#).unwrap();
3926 match stmt {
3927 Statement::Query(q) => {
3928 let f = q.filter.unwrap();
3929 match f {
3930 Expr::BinaryOp(left, BinOp::Eq, _right) => {
3931 assert!(matches!(*left, Expr::ScalarFunc(ScalarFn::Upper, _)));
3932 }
3933 _ => panic!("expected binary op with upper"),
3934 }
3935 }
3936 _ => panic!("expected query"),
3937 }
3938 }
3939
3940 #[test]
3941 fn test_parse_substring() {
3942 let stmt = parse("User { sub: substring(.name, 1, 3) }").unwrap();
3943 match stmt {
3944 Statement::Query(q) => {
3945 let proj = q.projection.unwrap();
3946 match &proj[0].expr {
3947 Expr::ScalarFunc(ScalarFn::Substring, args) => {
3948 assert_eq!(args.len(), 3);
3949 }
3950 other => panic!("expected ScalarFunc Substring, got {other:?}"),
3951 }
3952 }
3953 _ => panic!("expected query"),
3954 }
3955 }
3956
3957 #[test]
3958 fn test_parse_concat() {
3959 let stmt = parse(r#"User { full: concat(.name, " - ", .email) }"#).unwrap();
3960 match stmt {
3961 Statement::Query(q) => {
3962 let proj = q.projection.unwrap();
3963 match &proj[0].expr {
3964 Expr::ScalarFunc(ScalarFn::Concat, args) => {
3965 assert_eq!(args.len(), 3);
3966 }
3967 other => panic!("expected ScalarFunc Concat, got {other:?}"),
3968 }
3969 }
3970 _ => panic!("expected query"),
3971 }
3972 }
3973
3974 #[test]
3977 fn test_parse_case_single_when() {
3978 let stmt = parse(r#"User filter case when .age > 30 then true else false end"#).unwrap();
3979 match stmt {
3980 Statement::Query(q) => {
3981 let filter = q.filter.unwrap();
3982 match filter {
3983 Expr::Case { whens, else_expr } => {
3984 assert_eq!(whens.len(), 1);
3985 assert!(else_expr.is_some());
3986 }
3987 other => panic!("expected Case expr, got {other:?}"),
3988 }
3989 }
3990 _ => panic!("expected query"),
3991 }
3992 }
3993
3994 #[test]
3995 fn test_parse_case_multiple_whens() {
3996 let stmt = parse(
3997 r#"User { label: case when .age > 30 then "senior" when .age > 20 then "adult" else "young" end }"#
3998 ).unwrap();
3999 match stmt {
4000 Statement::Query(q) => {
4001 let proj = q.projection.unwrap();
4002 match &proj[0].expr {
4003 Expr::Case { whens, else_expr } => {
4004 assert_eq!(whens.len(), 2);
4005 assert!(else_expr.is_some());
4006 }
4007 other => panic!("expected Case expr, got {other:?}"),
4008 }
4009 }
4010 _ => panic!("expected query"),
4011 }
4012 }
4013
4014 #[test]
4015 fn test_parse_case_without_else() {
4016 let stmt = parse(r#"User filter case when .age > 30 then true end"#).unwrap();
4017 match stmt {
4018 Statement::Query(q) => {
4019 let filter = q.filter.unwrap();
4020 match filter {
4021 Expr::Case { whens, else_expr } => {
4022 assert_eq!(whens.len(), 1);
4023 assert!(else_expr.is_none());
4024 }
4025 other => panic!("expected Case expr, got {other:?}"),
4026 }
4027 }
4028 _ => panic!("expected query"),
4029 }
4030 }
4031
4032 #[test]
4035 fn test_parse_mul_expr() {
4036 let stmt = parse("User filter .price * .quantity > 100").unwrap();
4037 match stmt {
4038 Statement::Query(q) => {
4039 let filter = q.filter.unwrap();
4040 match filter {
4041 Expr::BinaryOp(left, BinOp::Gt, _) => match *left {
4042 Expr::BinaryOp(_, BinOp::Mul, _) => {}
4043 other => panic!("expected Mul, got {other:?}"),
4044 },
4045 other => panic!("expected BinaryOp Gt, got {other:?}"),
4046 }
4047 }
4048 _ => panic!("expected query"),
4049 }
4050 }
4051
4052 #[test]
4053 fn test_parse_div_expr() {
4054 let stmt = parse("User { ratio: .total / .count }").unwrap();
4055 match stmt {
4056 Statement::Query(q) => {
4057 let proj = q.projection.unwrap();
4058 assert_eq!(proj[0].alias.as_deref(), Some("ratio"));
4059 match &proj[0].expr {
4060 Expr::BinaryOp(_, BinOp::Div, _) => {}
4061 other => panic!("expected Div, got {other:?}"),
4062 }
4063 }
4064 _ => panic!("expected query"),
4065 }
4066 }
4067
4068 #[test]
4069 fn test_parse_mul_div_precedence() {
4070 let stmt = parse("User filter .a + .b * .c > 0").unwrap();
4072 match stmt {
4073 Statement::Query(q) => {
4074 let filter = q.filter.unwrap();
4075 match filter {
4076 Expr::BinaryOp(left, BinOp::Gt, _) => match *left {
4077 Expr::BinaryOp(_, BinOp::Add, right) => {
4078 assert!(matches!(*right, Expr::BinaryOp(_, BinOp::Mul, _)));
4079 }
4080 other => panic!("expected Add, got {other:?}"),
4081 },
4082 other => panic!("expected Gt, got {other:?}"),
4083 }
4084 }
4085 _ => panic!("expected query"),
4086 }
4087 }
4088
4089 #[test]
4092 fn test_parse_multi_order() {
4093 let stmt = parse("User order .name asc, .age desc").unwrap();
4094 match stmt {
4095 Statement::Query(q) => {
4096 let order = q.order.unwrap();
4097 assert_eq!(order.keys.len(), 2);
4098 assert_eq!(order.keys[0].expr, Expr::Field("name".into()));
4099 assert!(!order.keys[0].descending);
4100 assert_eq!(order.keys[1].expr, Expr::Field("age".into()));
4101 assert!(order.keys[1].descending);
4102 }
4103 _ => panic!("expected query"),
4104 }
4105 }
4106
4107 #[test]
4108 fn test_parse_order_default_asc() {
4109 let stmt = parse("User order .name").unwrap();
4110 match stmt {
4111 Statement::Query(q) => {
4112 let order = q.order.unwrap();
4113 assert_eq!(order.keys.len(), 1);
4114 assert!(!order.keys[0].descending);
4115 }
4116 _ => panic!("expected query"),
4117 }
4118 }
4119
4120 #[test]
4123 fn test_parse_alter_add_column() {
4124 let stmt = parse("alter User add column status: str").unwrap();
4125 match stmt {
4126 Statement::AlterTable(at) => {
4127 assert_eq!(at.table, "User");
4128 match at.action {
4129 AlterAction::AddColumn {
4130 name,
4131 type_name,
4132 required,
4133 } => {
4134 assert_eq!(name, "status");
4135 assert_eq!(type_name, "str");
4136 assert!(!required);
4137 }
4138 other => panic!("expected AddColumn, got {other:?}"),
4139 }
4140 }
4141 other => panic!("expected AlterTable, got {other:?}"),
4142 }
4143 }
4144
4145 #[test]
4146 fn test_parse_alter_add_required_column() {
4147 let stmt = parse("alter User add required status: str").unwrap();
4148 match stmt {
4149 Statement::AlterTable(at) => match at.action {
4150 AlterAction::AddColumn { required, .. } => assert!(required),
4151 other => panic!("expected AddColumn, got {other:?}"),
4152 },
4153 other => panic!("expected AlterTable, got {other:?}"),
4154 }
4155 }
4156
4157 #[test]
4158 fn test_parse_type_with_unique_modifier() {
4159 let stmt = parse("type User { required unique email: str, age: int }").unwrap();
4160 match stmt {
4161 Statement::CreateType(ct) => {
4162 assert!(ct.fields[0].required && ct.fields[0].unique);
4163 assert!(!ct.fields[1].unique);
4164 }
4165 other => panic!("expected CreateType, got {other:?}"),
4166 }
4167 }
4168
4169 #[test]
4170 fn test_parse_type_unique_before_required() {
4171 let stmt = parse("type User { unique required email: str }").unwrap();
4173 match stmt {
4174 Statement::CreateType(ct) => {
4175 assert!(ct.fields[0].required && ct.fields[0].unique);
4176 }
4177 other => panic!("expected CreateType, got {other:?}"),
4178 }
4179 }
4180
4181 #[test]
4182 fn test_parse_alter_add_unique() {
4183 let stmt = parse("alter User add unique .email").unwrap();
4184 match stmt {
4185 Statement::AlterTable(at) => assert!(matches!(
4186 at.action,
4187 AlterAction::AddUnique {
4188 target: IndexTarget::Column(ref column),
4189 ..
4190 } if column == "email"
4191 )),
4192 other => panic!("expected AlterTable, got {other:?}"),
4193 }
4194 }
4195
4196 #[test]
4197 fn test_parse_alter_drop_column() {
4198 let stmt = parse("alter User drop column status").unwrap();
4199 match stmt {
4200 Statement::AlterTable(at) => {
4201 assert_eq!(at.table, "User");
4202 match at.action {
4203 AlterAction::DropColumn { name, .. } => assert_eq!(name, "status"),
4204 other => panic!("expected DropColumn, got {other:?}"),
4205 }
4206 }
4207 other => panic!("expected AlterTable, got {other:?}"),
4208 }
4209 }
4210
4211 #[test]
4212 fn test_parse_alter_drop_without_column_keyword() {
4213 let stmt = parse("alter User drop status").unwrap();
4214 match stmt {
4215 Statement::AlterTable(at) => match at.action {
4216 AlterAction::DropColumn { name, .. } => assert_eq!(name, "status"),
4217 other => panic!("expected DropColumn, got {other:?}"),
4218 },
4219 other => panic!("expected AlterTable, got {other:?}"),
4220 }
4221 }
4222
4223 #[test]
4224 fn test_parse_drop_table() {
4225 let stmt = parse("drop User").unwrap();
4226 match stmt {
4227 Statement::DropTable(dt) => assert_eq!(dt.table, "User"),
4228 other => panic!("expected DropTable, got {other:?}"),
4229 }
4230 }
4231
4232 #[test]
4235 fn test_parse_in_subquery() {
4236 let stmt = parse("User filter .name in (VIP { .name })").unwrap();
4237 match stmt {
4238 Statement::Query(q) => {
4239 let filter = q.filter.unwrap();
4240 match filter {
4241 Expr::InSubquery {
4242 expr,
4243 subquery,
4244 negated,
4245 } => {
4246 assert!(!negated);
4247 assert!(matches!(*expr, Expr::Field(ref f) if f == "name"));
4248 assert_eq!(subquery.source, "VIP");
4249 }
4250 other => panic!("expected InSubquery, got {other:?}"),
4251 }
4252 }
4253 _ => panic!("expected query"),
4254 }
4255 }
4256
4257 #[test]
4258 fn test_parse_not_in_subquery() {
4259 let stmt = parse("User filter .id not in (Order { .user_id })").unwrap();
4260 match stmt {
4261 Statement::Query(q) => match q.filter.unwrap() {
4262 Expr::InSubquery { negated, .. } => assert!(negated),
4263 other => panic!("expected InSubquery, got {other:?}"),
4264 },
4265 _ => panic!("expected query"),
4266 }
4267 }
4268
4269 #[test]
4270 fn test_parse_in_literal_list_still_works() {
4271 let stmt = parse("User filter .age in (25, 30, 35)").unwrap();
4273 match stmt {
4274 Statement::Query(q) => match q.filter.unwrap() {
4275 Expr::InList { list, negated, .. } => {
4276 assert!(!negated);
4277 assert_eq!(list.len(), 3);
4278 }
4279 other => panic!("expected InList, got {other:?}"),
4280 },
4281 _ => panic!("expected query"),
4282 }
4283 }
4284
4285 #[test]
4288 fn test_parse_create_view() {
4289 let stmt = parse("materialize OldUsers as User filter .age > 28").unwrap();
4290 match stmt {
4291 Statement::CreateView(cv) => {
4292 assert_eq!(cv.name, "OldUsers");
4293 assert_eq!(cv.query.source, "User");
4294 assert!(cv.query.filter.is_some());
4295 assert!(!cv.query_text.is_empty());
4296 }
4297 _ => panic!("expected CreateView"),
4298 }
4299 }
4300
4301 #[test]
4302 fn test_parse_create_view_with_projection() {
4303 let stmt = parse("materialize UserNames as User { .name }").unwrap();
4304 match stmt {
4305 Statement::CreateView(cv) => {
4306 assert_eq!(cv.name, "UserNames");
4307 assert!(cv.query.projection.is_some());
4308 }
4309 _ => panic!("expected CreateView"),
4310 }
4311 }
4312
4313 #[test]
4314 fn test_parse_refresh_view() {
4315 let stmt = parse("refresh OldUsers").unwrap();
4316 match stmt {
4317 Statement::RefreshView(rv) => {
4318 assert_eq!(rv.name, "OldUsers");
4319 }
4320 _ => panic!("expected RefreshView"),
4321 }
4322 }
4323
4324 #[test]
4325 fn test_parse_drop_view() {
4326 let stmt = parse("drop view OldUsers").unwrap();
4327 match stmt {
4328 Statement::DropView(dv) => {
4329 assert_eq!(dv.name, "OldUsers");
4330 }
4331 _ => panic!("expected DropView"),
4332 }
4333 }
4334
4335 #[test]
4336 fn test_parse_drop_table_still_works() {
4337 let stmt = parse("drop Users").unwrap();
4338 match stmt {
4339 Statement::DropTable(dt) => {
4340 assert_eq!(dt.table, "Users");
4341 }
4342 _ => panic!("expected DropTable"),
4343 }
4344 }
4345
4346 #[test]
4347 fn test_parse_union() {
4348 let stmt = parse("User union Order").unwrap();
4349 match stmt {
4350 Statement::Union(u) => {
4351 assert!(!u.all);
4352 match *u.left {
4353 Statement::Query(_) => {}
4354 _ => panic!("expected Query on left"),
4355 }
4356 match *u.right {
4357 Statement::Query(_) => {}
4358 _ => panic!("expected Query on right"),
4359 }
4360 }
4361 _ => panic!("expected Union"),
4362 }
4363 }
4364
4365 #[test]
4366 fn test_parse_union_all() {
4367 let stmt = parse("User union all Order").unwrap();
4368 match stmt {
4369 Statement::Union(u) => {
4370 assert!(u.all, "expected UNION ALL");
4371 match *u.left {
4372 Statement::Query(_) => {}
4373 _ => panic!("expected Query on left"),
4374 }
4375 match *u.right {
4376 Statement::Query(_) => {}
4377 _ => panic!("expected Query on right"),
4378 }
4379 }
4380 _ => panic!("expected Union"),
4381 }
4382 }
4383
4384 #[test]
4385 fn test_parse_union_chain() {
4386 let stmt = parse("User union Order union Product").unwrap();
4388 match stmt {
4389 Statement::Union(outer) => {
4390 assert!(!outer.all);
4391 match *outer.right {
4393 Statement::Query(q) => assert_eq!(q.source, "Product"),
4394 _ => panic!("expected Query(Product) on right"),
4395 }
4396 match *outer.left {
4398 Statement::Union(inner) => {
4399 assert!(!inner.all);
4400 match *inner.left {
4401 Statement::Query(q) => assert_eq!(q.source, "User"),
4402 _ => panic!("expected Query(User)"),
4403 }
4404 match *inner.right {
4405 Statement::Query(q) => assert_eq!(q.source, "Order"),
4406 _ => panic!("expected Query(Order)"),
4407 }
4408 }
4409 _ => panic!("expected inner Union"),
4410 }
4411 }
4412 _ => panic!("expected Union"),
4413 }
4414 }
4415
4416 #[test]
4417 fn test_parse_union_with_filter() {
4418 let stmt = parse("User filter .age > 10 union Order filter .total > 50").unwrap();
4419 match stmt {
4420 Statement::Union(u) => {
4421 assert!(!u.all);
4422 match *u.left {
4424 Statement::Query(q) => {
4425 assert_eq!(q.source, "User");
4426 assert!(q.filter.is_some());
4427 }
4428 _ => panic!("expected Query on left"),
4429 }
4430 match *u.right {
4431 Statement::Query(q) => {
4432 assert_eq!(q.source, "Order");
4433 assert!(q.filter.is_some());
4434 }
4435 _ => panic!("expected Query on right"),
4436 }
4437 }
4438 _ => panic!("expected Union"),
4439 }
4440 }
4441
4442 #[test]
4443 fn test_parse_count_distinct_standalone() {
4444 let stmt = parse("count(distinct User { .name })").unwrap();
4445 match stmt {
4446 Statement::Query(q) => {
4447 let agg = q.aggregation.unwrap();
4448 assert_eq!(agg.function, AggFunc::CountDistinct);
4449 assert_eq!(agg.argument, Some(Expr::Field("name".into())));
4450 }
4451 _ => panic!("expected Query"),
4452 }
4453 }
4454
4455 #[test]
4456 fn test_parse_count_distinct_in_projection() {
4457 let stmt = parse("User group .dept { .dept, count(distinct .name) }").unwrap();
4458 match stmt {
4459 Statement::Query(q) => {
4460 let proj = q.projection.unwrap();
4461 assert_eq!(proj.len(), 2);
4462 match &proj[1].expr {
4463 Expr::FunctionCall(func, _, _) => {
4464 assert_eq!(*func, AggFunc::CountDistinct);
4465 }
4466 _ => panic!("expected FunctionCall"),
4467 }
4468 }
4469 _ => panic!("expected Query"),
4470 }
4471 }
4472
4473 #[test]
4476 fn test_parse_window_row_number_order() {
4477 let stmt = parse("User { .name, rn: row_number() over (order .age) }").unwrap();
4478 match stmt {
4479 Statement::Query(q) => {
4480 let proj = q.projection.unwrap();
4481 assert_eq!(proj.len(), 2);
4482 assert_eq!(proj[1].alias.as_deref(), Some("rn"));
4483 match &proj[1].expr {
4484 Expr::Window {
4485 function,
4486 args,
4487 partition_by,
4488 order_by,
4489 ..
4490 } => {
4491 assert_eq!(*function, WindowFunc::RowNumber);
4492 assert!(args.is_empty());
4493 assert!(partition_by.is_empty());
4494 assert_eq!(order_by.len(), 1);
4495 assert_eq!(order_by[0].expr, Expr::Field("age".into()));
4496 assert!(!order_by[0].descending);
4497 }
4498 other => panic!("expected Window, got {other:?}"),
4499 }
4500 }
4501 _ => panic!("expected query"),
4502 }
4503 }
4504
4505 #[test]
4506 fn test_parse_window_sum_partition_order() {
4507 let stmt =
4508 parse("User { .name, s: sum(.salary) over (partition .dept order .salary) }").unwrap();
4509 match stmt {
4510 Statement::Query(q) => {
4511 let proj = q.projection.unwrap();
4512 assert_eq!(proj.len(), 2);
4513 assert_eq!(proj[1].alias.as_deref(), Some("s"));
4514 match &proj[1].expr {
4515 Expr::Window {
4516 function,
4517 args,
4518 partition_by,
4519 order_by,
4520 ..
4521 } => {
4522 assert_eq!(*function, WindowFunc::Sum);
4523 assert_eq!(args.len(), 1);
4524 assert!(matches!(&args[0], Expr::Field(f) if f == "salary"));
4525 assert_eq!(partition_by, &[Expr::Field("dept".into())]);
4526 assert_eq!(order_by.len(), 1);
4527 assert_eq!(order_by[0].expr, Expr::Field("salary".into()));
4528 assert!(!order_by[0].descending);
4529 }
4530 other => panic!("expected Window, got {other:?}"),
4531 }
4532 }
4533 _ => panic!("expected query"),
4534 }
4535 }
4536
4537 #[test]
4538 fn test_parse_window_rank_desc() {
4539 let stmt =
4540 parse("User { .dept, .salary, r: rank() over (partition .dept order .salary desc) }")
4541 .unwrap();
4542 match stmt {
4543 Statement::Query(q) => {
4544 let proj = q.projection.unwrap();
4545 assert_eq!(proj.len(), 3);
4546 match &proj[2].expr {
4547 Expr::Window {
4548 function,
4549 partition_by,
4550 order_by,
4551 ..
4552 } => {
4553 assert_eq!(*function, WindowFunc::Rank);
4554 assert_eq!(partition_by, &[Expr::Field("dept".into())]);
4555 assert_eq!(order_by.len(), 1);
4556 assert!(order_by[0].descending);
4557 }
4558 other => panic!("expected Window, got {other:?}"),
4559 }
4560 }
4561 _ => panic!("expected query"),
4562 }
4563 }
4564
4565 #[test]
4566 fn test_parse_window_dense_rank() {
4567 let stmt = parse("User { .name, dr: dense_rank() over (order .score desc) }").unwrap();
4568 match stmt {
4569 Statement::Query(q) => {
4570 let proj = q.projection.unwrap();
4571 assert_eq!(proj.len(), 2);
4572 match &proj[1].expr {
4573 Expr::Window { function, .. } => {
4574 assert_eq!(*function, WindowFunc::DenseRank);
4575 }
4576 other => panic!("expected Window, got {other:?}"),
4577 }
4578 }
4579 _ => panic!("expected query"),
4580 }
4581 }
4582
4583 #[test]
4584 fn test_parse_sum_without_over_is_aggregate() {
4585 let stmt = parse("User group .dept { .dept, total: sum(.salary) }").unwrap();
4587 match stmt {
4588 Statement::Query(q) => {
4589 let proj = q.projection.unwrap();
4590 assert_eq!(proj.len(), 2);
4591 match &proj[1].expr {
4592 Expr::FunctionCall(AggFunc::Sum, _, _) => {} other => panic!("expected FunctionCall(Sum), got {other:?}"),
4594 }
4595 }
4596 _ => panic!("expected query"),
4597 }
4598 }
4599
4600 #[test]
4601 fn test_nesting_depth_limit() {
4602 let mut query = String::from("User filter ");
4604 for _ in 0..70 {
4605 query.push('(');
4606 }
4607 query.push_str(".age > 1");
4608 for _ in 0..70 {
4609 query.push(')');
4610 }
4611 let result = parse(&query);
4612 assert!(result.is_err());
4613 let err = result.unwrap_err();
4614 assert!(
4615 err.message().contains("nesting depth"),
4616 "expected nesting depth error, got: {}",
4617 err.message()
4618 );
4619 }
4620
4621 #[test]
4622 fn test_unary_prefix_nesting_depth_limit() {
4623 let query = String::from("User filter ") + &"not ".repeat(5000) + ".active";
4627 let result = parse(&query);
4628 assert!(result.is_err());
4629 let err = result.unwrap_err();
4630 assert!(
4631 err.message().contains("nesting depth"),
4632 "expected nesting depth error, got: {}",
4633 err.message()
4634 );
4635 }
4636
4637 #[test]
4638 fn test_moderate_nesting_succeeds() {
4639 let mut query = String::from("User filter ");
4641 for _ in 0..10 {
4642 query.push('(');
4643 }
4644 query.push_str(".age > 1");
4645 for _ in 0..10 {
4646 query.push(')');
4647 }
4648 assert!(parse(&query).is_ok());
4649 }
4650
4651 #[test]
4655 fn test_parse_fuzz_repro_projection_eof() {
4656 let err = parse("nn{").expect_err("unterminated projection must error, not panic");
4657 let _ = err.message();
4658 }
4659
4660 #[test]
4663 fn test_parse_fuzz_repro_short_projection_eof() {
4664 let err = parse("z{").expect_err("unterminated projection must error, not panic");
4665 let _ = err.message();
4666 }
4667
4668 #[test]
4669 fn test_update_at_statement_start_gives_helpful_error() {
4670 let err =
4671 parse(r#"update User filter .name = "Alice" { age := 31 }"#).expect_err("should fail");
4672 let msg = err.message();
4673 assert!(
4674 msg.contains("pipeline syntax"),
4675 "error should mention pipeline syntax, got: {msg}"
4676 );
4677 assert!(
4678 msg.contains("update"),
4679 "error should mention 'update', got: {msg}"
4680 );
4681 }
4682
4683 #[test]
4684 fn test_delete_at_statement_start_gives_helpful_error() {
4685 let err = parse("delete User filter .age < 18").expect_err("should fail");
4686 let msg = err.message();
4687 assert!(
4688 msg.contains("pipeline syntax"),
4689 "error should mention pipeline syntax, got: {msg}"
4690 );
4691 assert!(
4692 msg.contains("delete"),
4693 "error should mention 'delete', got: {msg}"
4694 );
4695 }
4696}
4697
4698#[cfg(test)]
4699mod cleanup_parser_dx_tests {
4700 use super::*;
4701
4702 #[test]
4703 fn typoed_statement_keyword_gets_suggestion() {
4704 let err = parse("updat User set age = 1").unwrap_err();
4705 let msg = err.to_string();
4706 assert!(msg.contains("near token"), "{msg}");
4707 assert!(msg.contains("did you mean `update`"), "{msg}");
4708 }
4709}
4710
4711#[cfg(test)]
4712mod dogfood_dx_tests {
4713 use super::*;
4714
4715 #[test]
4718 fn reserved_word_field_name_gives_actionable_error() {
4719 let err = parse("type Post { type: str }").unwrap_err();
4720 let msg = err.to_string();
4721 assert!(
4722 msg.contains("'type' is a reserved word")
4723 && msg.contains("field name")
4724 && msg.contains("quote it as `type`"),
4725 "unhelpful message: {msg}"
4726 );
4727 }
4728
4729 #[test]
4730 fn reserved_modifier_word_as_field_name_gives_actionable_error() {
4731 let err = parse("type Post { required: bool }").unwrap_err();
4735 let msg = err.to_string();
4736 assert!(
4737 msg.contains("'required' is a reserved word") && msg.contains("quote it as `required`"),
4738 "unhelpful message: {msg}"
4739 );
4740 }
4741
4742 #[test]
4743 fn reserved_word_in_insert_assignment_gives_actionable_error() {
4744 let err = parse(r#"insert Post { type := "x" }"#).unwrap_err();
4745 let msg = err.to_string();
4746 assert!(msg.contains("'type' is a reserved word"), "{msg}");
4747 }
4748
4749 #[test]
4750 fn reserved_word_in_alter_column_gives_actionable_error() {
4751 let err = parse("alter Post add column order: int").unwrap_err();
4752 let msg = err.to_string();
4753 assert!(msg.contains("'order' is a reserved word"), "{msg}");
4754 }
4755
4756 #[test]
4757 fn backtick_field_name_parses_as_identifier() {
4758 let stmt = parse("type Post { `type`: str, `order`: int }").unwrap();
4759 match stmt {
4760 Statement::CreateType(ct) => {
4761 assert_eq!(ct.fields[0].name, "type");
4762 assert_eq!(ct.fields[1].name, "order");
4763 }
4764 other => panic!("expected CreateType, got {other:?}"),
4765 }
4766 }
4767
4768 #[test]
4769 fn backtick_field_still_honors_modifiers() {
4770 let stmt = parse("type Post { required `type`: str }").unwrap();
4771 match stmt {
4772 Statement::CreateType(ct) => {
4773 assert_eq!(ct.fields[0].name, "type");
4774 assert!(ct.fields[0].required);
4775 }
4776 other => panic!("expected CreateType, got {other:?}"),
4777 }
4778 }
4779
4780 #[test]
4783 fn create_type_if_not_exists_parses() {
4784 let stmt = parse("type Post if not exists { id: int }").unwrap();
4785 match stmt {
4786 Statement::CreateType(ct) => assert!(ct.if_not_exists),
4787 other => panic!("expected CreateType, got {other:?}"),
4788 }
4789 }
4790
4791 #[test]
4792 fn create_type_without_clause_defaults_false() {
4793 let stmt = parse("type Post { id: int }").unwrap();
4794 match stmt {
4795 Statement::CreateType(ct) => assert!(!ct.if_not_exists),
4796 other => panic!("expected CreateType, got {other:?}"),
4797 }
4798 }
4799
4800 #[test]
4801 fn drop_if_exists_parses() {
4802 match parse("drop if exists Post").unwrap() {
4803 Statement::DropTable(dt) => assert!(dt.if_exists),
4804 other => panic!("expected DropTable, got {other:?}"),
4805 }
4806 match parse("drop Post").unwrap() {
4807 Statement::DropTable(dt) => assert!(!dt.if_exists),
4808 other => panic!("expected DropTable, got {other:?}"),
4809 }
4810 }
4811
4812 #[test]
4813 fn drop_view_if_exists_parses() {
4814 match parse("drop view if exists ActiveUsers").unwrap() {
4815 Statement::DropView(dv) => {
4816 assert!(dv.if_exists);
4817 assert_eq!(dv.name, "ActiveUsers");
4818 }
4819 other => panic!("expected DropView, got {other:?}"),
4820 }
4821 }
4822
4823 #[test]
4824 fn add_index_and_unique_if_not_exists_parse() {
4825 match parse("alter Post add index if not exists .slug").unwrap() {
4826 Statement::AlterTable(at) => {
4827 assert!(matches!(
4828 at.action,
4829 AlterAction::AddIndex {
4830 if_not_exists: true,
4831 ..
4832 }
4833 ));
4834 }
4835 other => panic!("expected AlterTable, got {other:?}"),
4836 }
4837 match parse("alter Post add unique if not exists .slug").unwrap() {
4838 Statement::AlterTable(at) => {
4839 assert!(matches!(
4840 at.action,
4841 AlterAction::AddUnique {
4842 if_not_exists: true,
4843 ..
4844 }
4845 ));
4846 }
4847 other => panic!("expected AlterTable, got {other:?}"),
4848 }
4849 }
4850
4851 #[test]
4852 fn expression_index_targets_parse_with_stable_table_local_identity() {
4853 use powdb_storage::stored_json_path::{
4854 StoredJsonPathSegmentV1 as Segment, StoredJsonPathV1,
4855 };
4856
4857 let expected = StoredJsonPathV1::new(
4858 "data",
4859 vec![Segment::Key("author".into()), Segment::Index(0)],
4860 );
4861 for query in [
4862 "alter Post add index (.data->author->0)",
4863 "alter Post add unique if not exists (.data->\"author\"->0)",
4864 "alter Post drop index if exists (.data->author->0)",
4865 ] {
4866 let Statement::AlterTable(alter) = parse(query).unwrap() else {
4867 panic!("expected alter table for {query}");
4868 };
4869 let (target, flag) = match alter.action {
4870 AlterAction::AddIndex {
4871 target,
4872 if_not_exists,
4873 }
4874 | AlterAction::AddUnique {
4875 target,
4876 if_not_exists,
4877 } => (target, if_not_exists),
4878 AlterAction::DropIndex { target, if_exists } => (target, if_exists),
4879 other => panic!("expected index action, got {other:?}"),
4880 };
4881 assert_eq!(target, IndexTarget::JsonPath(expected.clone()));
4882 assert_eq!(
4883 flag,
4884 query.contains("if not exists") || query.contains("if exists")
4885 );
4886 }
4887 }
4888
4889 #[test]
4890 fn expression_index_target_rejects_ambiguous_or_non_path_forms() {
4891 let cases = [
4892 (
4893 "alter Post add index .data->author",
4894 "must be parenthesized",
4895 ),
4896 (
4897 "alter Post add index (p.data->author)",
4898 "qualified JSON paths",
4899 ),
4900 (
4901 "alter Post add index (.data)",
4902 "use `.column` for a stored column",
4903 ),
4904 (
4905 "alter Post add index (.data->age + 1)",
4906 "only a direct JSON path",
4907 ),
4908 (
4909 "alter Post drop index ({ value := 1 })",
4910 "expected an unqualified JSON path",
4911 ),
4912 ];
4913 for (query, expected) in cases {
4914 let error = parse(query).expect_err(query).to_string();
4915 assert!(
4916 error.contains(expected),
4917 "`{query}` should mention `{expected}`, got `{error}`"
4918 );
4919 }
4920 }
4921
4922 #[test]
4923 fn alter_drop_column_if_exists_parses() {
4924 match parse("alter Post drop column if exists status").unwrap() {
4925 Statement::AlterTable(at) => {
4926 assert!(matches!(
4927 at.action,
4928 AlterAction::DropColumn {
4929 if_exists: true,
4930 ..
4931 }
4932 ));
4933 }
4934 other => panic!("expected AlterTable, got {other:?}"),
4935 }
4936 }
4937
4938 #[test]
4941 fn schema_parses_to_list_types() {
4942 assert_eq!(parse("schema").unwrap(), Statement::ListTypes);
4943 }
4944
4945 #[test]
4946 fn describe_parses_to_describe() {
4947 assert_eq!(
4948 parse("describe Post").unwrap(),
4949 Statement::Describe("Post".to_string())
4950 );
4951 }
4952
4953 #[test]
4954 fn schema_with_type_aliases_describe() {
4955 assert_eq!(
4956 parse("schema Post").unwrap(),
4957 Statement::Describe("Post".to_string())
4958 );
4959 }
4960
4961 #[test]
4962 fn schema_links_parses_to_list_links() {
4963 assert_eq!(parse("schema links").unwrap(), Statement::ListLinks);
4964 }
4965
4966 #[test]
4967 fn describe_links_still_names_a_table() {
4968 assert_eq!(
4971 parse("describe links").unwrap(),
4972 Statement::Describe("links".to_string())
4973 );
4974 }
4975}
4976
4977#[cfg(test)]
4978mod json_path_tests {
4979 use super::*;
4980
4981 fn filter_of(src: &str) -> Expr {
4983 match parse(src).unwrap() {
4984 Statement::Query(q) => q.filter.expect("expected a filter"),
4985 other => panic!("expected a query, got {other:?}"),
4986 }
4987 }
4988
4989 #[test]
4990 fn ident_key_path() {
4991 let e = filter_of(r#"Post filter .data->author->name = "x""#);
4993 let Expr::BinaryOp(lhs, BinOp::Eq, _) = e else {
4994 panic!("expected an equality, got {e:?}");
4995 };
4996 assert_eq!(
4997 *lhs,
4998 Expr::JsonPath {
4999 base: Box::new(Expr::Field("data".into())),
5000 segments: vec![PathSeg::Key("author".into()), PathSeg::Key("name".into())],
5001 }
5002 );
5003 }
5004
5005 #[test]
5006 fn string_form_key_path() {
5007 let e = filter_of(r#"Post filter .data->"weird key!" = 1"#);
5009 let Expr::BinaryOp(lhs, _, _) = e else {
5010 panic!("expected binop");
5011 };
5012 assert_eq!(
5013 *lhs,
5014 Expr::JsonPath {
5015 base: Box::new(Expr::Field("data".into())),
5016 segments: vec![PathSeg::Key("weird key!".into())],
5017 }
5018 );
5019 }
5020
5021 #[test]
5022 fn array_index_path() {
5023 let e = filter_of(r#"Post filter .data->tags->0 = "rust""#);
5025 let Expr::BinaryOp(lhs, _, _) = e else {
5026 panic!("expected binop");
5027 };
5028 assert_eq!(
5029 *lhs,
5030 Expr::JsonPath {
5031 base: Box::new(Expr::Field("data".into())),
5032 segments: vec![PathSeg::Key("tags".into()), PathSeg::Index(0)],
5033 }
5034 );
5035 }
5036
5037 #[test]
5038 fn qualified_base_path() {
5039 let e = filter_of(r#"Post as posts filter posts.data->author = "a""#);
5041 let Expr::BinaryOp(lhs, _, _) = e else {
5042 panic!("expected binop");
5043 };
5044 assert_eq!(
5045 *lhs,
5046 Expr::JsonPath {
5047 base: Box::new(Expr::QualifiedField {
5048 qualifier: "posts".into(),
5049 field: "data".into(),
5050 }),
5051 segments: vec![PathSeg::Key("author".into())],
5052 }
5053 );
5054 }
5055
5056 #[test]
5057 fn path_binds_tighter_than_comparison_and_arithmetic() {
5058 let e = filter_of("Post filter .data->age > 21");
5060 let Expr::BinaryOp(lhs, BinOp::Gt, rhs) = e else {
5061 panic!("expected a top-level `>`, got {e:?}");
5062 };
5063 assert!(matches!(*lhs, Expr::JsonPath { .. }));
5064 assert_eq!(*rhs, Expr::Literal(Literal::Int(21)));
5065
5066 let e = filter_of("Post filter .a->b + 1 = 3");
5068 let Expr::BinaryOp(add, BinOp::Eq, _) = e else {
5069 panic!("expected eq");
5070 };
5071 let Expr::BinaryOp(lhs, BinOp::Add, _) = *add else {
5072 panic!("expected `+` under `=`, got {add:?}");
5073 };
5074 assert!(matches!(*lhs, Expr::JsonPath { .. }));
5075 }
5076
5077 #[test]
5078 fn dash_vs_arrow_lexing() {
5079 let idx = filter_of("Post filter .a->1 = 0");
5082 let Expr::BinaryOp(lhs, BinOp::Eq, _) = idx else {
5083 panic!("expected eq");
5084 };
5085 assert_eq!(
5086 *lhs,
5087 Expr::JsonPath {
5088 base: Box::new(Expr::Field("a".into())),
5089 segments: vec![PathSeg::Index(1)],
5090 }
5091 );
5092
5093 let sub = filter_of("Post filter .a - 1 = 0");
5096 let Expr::BinaryOp(lhs, BinOp::Eq, _) = sub else {
5097 panic!("expected eq");
5098 };
5099 assert!(
5100 matches!(*lhs, Expr::BinaryOp(_, BinOp::Sub, _)),
5101 "`.a - 1` should be subtraction, got {lhs:?}"
5102 );
5103
5104 assert!(
5109 parse("Post filter .a-1 = 0").is_err(),
5110 "`.a-1` should fail to parse (negative-literal gotcha)"
5111 );
5112
5113 assert!(
5115 parse("Post filter .a - > 0").is_err(),
5116 "`.a - >` should fail to parse"
5117 );
5118 }
5119
5120 #[test]
5121 fn negative_index_rejected() {
5122 let err = parse("Post filter .data->-1 = 0").unwrap_err();
5123 assert!(
5124 err.to_string().contains("array index"),
5125 "expected an index error, got: {err}"
5126 );
5127 }
5128
5129 #[test]
5130 fn path_on_literal_base_rejected() {
5131 let err = parse("Post filter 5->x = 1").unwrap_err();
5133 assert!(
5134 err.to_string().to_lowercase().contains("field base"),
5135 "expected a field-base error, got: {err}"
5136 );
5137 }
5138
5139 #[test]
5140 fn json_path_assignment_target_is_targeted_unsupported() {
5141 for stmt in [
5146 "Doc update { .data->x := 5 }",
5147 "Doc update { data->x := 5 }",
5148 ] {
5149 let err = parse(stmt).unwrap_err();
5150 assert!(
5151 matches!(err, ParseError::Unsupported { .. }),
5152 "{stmt}: expected Unsupported, got {err:?}"
5153 );
5154 let msg = err.to_string();
5155 assert!(
5156 msg.contains("JSON path assignment targets are not supported"),
5157 "{stmt}: message must state the unsupported feature: {msg}"
5158 );
5159 assert!(
5160 msg.contains("json_set"),
5161 "{stmt}: message must point at the whole-column alternative: {msg}"
5162 );
5163 }
5164 assert!(parse(r#"Doc update { data := "{}" }"#).is_ok());
5166 }
5167
5168 #[test]
5169 fn json_type_scalar_parses() {
5170 let e = filter_of(r#"Post filter json_type(.data->x) = "string""#);
5171 let Expr::BinaryOp(lhs, _, _) = e else {
5172 panic!("expected binop");
5173 };
5174 let Expr::ScalarFunc(ScalarFn::JsonType, args) = *lhs else {
5175 panic!("expected json_type call, got {lhs:?}");
5176 };
5177 assert_eq!(args.len(), 1);
5178 assert!(matches!(args[0], Expr::JsonPath { .. }));
5179 }
5180
5181 #[test]
5182 fn path_in_projection() {
5183 let stmt = parse("Post { author: .data->author }").unwrap();
5186 let Statement::Query(q) = stmt else {
5187 panic!("expected query");
5188 };
5189 let proj = q.projection.unwrap();
5190 assert_eq!(proj[0].alias.as_deref(), Some("author"));
5191 assert!(matches!(proj[0].expr, Expr::JsonPath { .. }));
5192
5193 let Statement::Query(ordered) = parse("Post order .data->author { .id }").unwrap() else {
5194 panic!("expected query");
5195 };
5196 assert!(matches!(
5197 ordered.order.unwrap().keys[0].expr,
5198 Expr::JsonPath { .. }
5199 ));
5200
5201 let Statement::Query(grouped) =
5202 parse("Post group .data->author { .data->author }").unwrap()
5203 else {
5204 panic!("expected query");
5205 };
5206 assert!(matches!(
5207 grouped.group_by.unwrap().keys[0].expr,
5208 Expr::JsonPath { .. }
5209 ));
5210 }
5211}
5212
5213#[cfg(test)]
5218mod token_text_roundtrip {
5219 use super::*;
5220 use proptest::prelude::*;
5221
5222 fn every_token() -> Vec<Token> {
5232 let mut toks = vec![
5233 Token::Ident("User".into()),
5236 Token::Ident("order".into()),
5237 Token::Ident("true".into()),
5238 Token::Ident("null".into()),
5239 Token::Ident("column name".into()),
5240 Token::Ident("1st".into()),
5241 Token::Ident("a-b".into()),
5242 Token::Ident("has#hash".into()),
5243 Token::Ident("has.dot".into()),
5244 Token::Ident("héllo".into()),
5245 Token::DotIdent("name".into()),
5246 Token::DotIdent("order".into()),
5247 Token::DotIdent("field name".into()),
5248 Token::DotIdent("1st".into()),
5249 Token::DotIdent("a-b".into()),
5250 Token::IntLit(0),
5253 Token::IntLit(-1),
5254 Token::IntLit(i64::MIN),
5255 Token::IntLit(i64::MAX),
5256 Token::FloatLit(0.0),
5257 Token::FloatLit(-0.0),
5258 Token::FloatLit(3.0),
5259 Token::FloatLit(-2.0),
5260 Token::FloatLit(1.5),
5261 Token::FloatLit(1e300),
5262 Token::FloatLit(1e-300),
5263 Token::FloatLit(f64::MIN_POSITIVE),
5264 Token::StringLit(String::new()),
5267 Token::StringLit("plain".into()),
5268 Token::StringLit("back\\slash".into()),
5269 Token::StringLit("he said \"hi\"".into()),
5270 Token::StringLit("line\nbreak".into()),
5271 Token::StringLit("tab\there".into()),
5272 Token::StringLit("carriage\rreturn".into()),
5273 Token::StringLit("`backtick`".into()),
5274 Token::StringLit("# not a comment".into()),
5275 Token::StringLit("} filter .x = 1".into()),
5276 Token::StringLit("\\\"".into()),
5277 Token::BoolLit(true),
5278 Token::BoolLit(false),
5279 Token::Param("1".into()),
5280 Token::Param("name".into()),
5281 Token::Param(String::new()),
5282 ];
5283 toks.extend([
5284 Token::Type,
5285 Token::Filter,
5286 Token::Order,
5287 Token::Limit,
5288 Token::Offset,
5289 Token::Insert,
5290 Token::Update,
5291 Token::Delete,
5292 Token::Upsert,
5293 Token::Returning,
5294 Token::Select,
5295 Token::Required,
5296 Token::Default,
5297 Token::Auto,
5298 Token::Multi,
5299 Token::Link,
5300 Token::Index,
5301 Token::Unique,
5302 Token::On,
5303 Token::Conflict,
5304 Token::Asc,
5305 Token::Desc,
5306 Token::And,
5307 Token::Or,
5308 Token::Not,
5309 Token::Exists,
5310 Token::Let,
5311 Token::As,
5312 Token::Match,
5313 Token::Group,
5314 Token::Join,
5315 Token::Inner,
5316 Token::LeftKw,
5317 Token::RightKw,
5318 Token::Outer,
5319 Token::Cross,
5320 Token::Transaction,
5321 Token::Begin,
5322 Token::Commit,
5323 Token::Rollback,
5324 Token::View,
5325 Token::Materialized,
5326 Token::Refresh,
5327 Token::Union,
5328 Token::Having,
5329 Token::Distinct,
5330 Token::In,
5331 Token::Between,
5332 Token::Like,
5333 Token::Count,
5334 Token::Avg,
5335 Token::Sum,
5336 Token::Min,
5337 Token::Max,
5338 Token::Raw,
5339 Token::Is,
5340 Token::Null,
5341 ]);
5342 toks.extend([
5343 Token::Upper,
5344 Token::Lower,
5345 Token::Length,
5346 Token::Trim,
5347 Token::Substring,
5348 Token::Concat,
5349 Token::Abs,
5350 Token::Round,
5351 Token::Ceil,
5352 Token::Floor,
5353 Token::Sqrt,
5354 Token::Pow,
5355 Token::Now,
5356 Token::Extract,
5357 Token::DateAdd,
5358 Token::DateDiff,
5359 Token::JsonType,
5360 Token::JsonText,
5361 Token::Cast,
5362 Token::Case,
5363 Token::When,
5364 Token::Then,
5365 Token::Else,
5366 Token::End,
5367 Token::Over,
5368 Token::Partition,
5369 Token::RowNumber,
5370 Token::Rank,
5371 Token::DenseRank,
5372 Token::Alter,
5373 Token::Drop,
5374 Token::Add,
5375 Token::Column,
5376 Token::Explain,
5377 Token::Schema,
5378 Token::Describe,
5379 ]);
5380 toks.extend([
5381 Token::Eq,
5382 Token::Neq,
5383 Token::Lt,
5384 Token::Gt,
5385 Token::Lte,
5386 Token::Gte,
5387 Token::Assign,
5388 Token::Arrow,
5389 Token::Pipe,
5390 Token::Coalesce,
5391 Token::Plus,
5392 Token::Minus,
5393 Token::Star,
5394 Token::Slash,
5395 Token::LBrace,
5396 Token::RBrace,
5397 Token::LParen,
5398 Token::RParen,
5399 Token::Comma,
5400 Token::Colon,
5401 Token::Dot,
5402 ]);
5403 toks
5404 }
5405
5406 fn assert_round_trips(tokens: &[Token]) {
5408 let text =
5409 tokens_to_text(tokens).unwrap_or_else(|e| panic!("no source text for {tokens:?}: {e}"));
5410 let relexed = lex(&text)
5411 .unwrap_or_else(|e| panic!("`{text}` from {tokens:?} does not lex: {}", e.message));
5412 let mut expected = tokens.to_vec();
5413 expected.push(Token::Eof);
5414 assert_eq!(relexed, expected, "`{text}` re-lexes to different tokens");
5415 }
5416
5417 #[test]
5418 fn every_token_round_trips_on_its_own() {
5419 for tok in every_token() {
5420 assert_round_trips(std::slice::from_ref(&tok));
5421 }
5422 }
5423
5424 #[test]
5425 fn eof_contributes_no_text() {
5426 assert_eq!(tokens_to_text(&[Token::Eof]).unwrap(), "");
5427 assert_eq!(tokens_to_text(&[]).unwrap(), "");
5428 }
5429
5430 #[test]
5436 fn unspellable_tokens_are_typed_errors() {
5437 for tok in [
5438 Token::Ident("has`tick".into()),
5439 Token::DotIdent("has`tick".into()),
5440 Token::Ident(String::new()),
5441 Token::FloatLit(f64::INFINITY),
5442 Token::FloatLit(f64::NAN),
5443 Token::Param("two words".into()),
5444 ] {
5445 let err = tokens_to_text(std::slice::from_ref(&tok))
5446 .expect_err("{tok:?} must not be written back wrong");
5447 assert!(
5448 matches!(err, ParseError::Unsupported { .. }),
5449 "expected a typed Unsupported error for {tok:?}, got {err:?}"
5450 );
5451 }
5452 }
5453
5454 #[test]
5457 fn unspellable_view_source_is_refused() {
5458 let huge = format!("1{}.0", "0".repeat(400)); let err = parse(&format!("materialize V as U filter .x = {huge}"))
5460 .expect_err("a view source that cannot round-trip must be refused");
5461 assert!(
5462 matches!(err, ParseError::Unsupported { .. }),
5463 "expected a typed Unsupported error, got {err:?}"
5464 );
5465 }
5466
5467 proptest! {
5468 #[test]
5471 fn any_token_stream_round_trips(
5472 tokens in proptest::collection::vec(
5473 proptest::sample::select(every_token()),
5474 0..12usize,
5475 )
5476 ) {
5477 let text = tokens_to_text(&tokens)
5478 .map_err(|e| TestCaseError::fail(format!("no source text: {e}")))?;
5479 let relexed = lex(&text)
5480 .map_err(|e| TestCaseError::fail(format!("`{text}` does not lex: {}", e.message)))?;
5481 let mut expected = tokens.clone();
5482 expected.push(Token::Eof);
5483 prop_assert_eq!(relexed, expected, "`{}` re-lexes to different tokens", text);
5484 }
5485 }
5486}