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