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("expected {expected}, got {got}")]
33 UnexpectedToken { expected: String, got: String },
34 #[error("query nesting depth exceeds maximum of {max}")]
36 NestingDepthExceeded { max: usize },
37 #[error("{feature}")]
39 Unsupported { feature: String },
40 #[error("{message}")]
42 Syntax { message: String },
43}
44
45impl ParseError {
46 pub fn message(&self) -> String {
48 self.to_string()
49 }
50}
51
52fn token_to_scalar_fn(tok: &Token) -> ScalarFn {
53 match tok {
54 Token::Upper => ScalarFn::Upper,
55 Token::Lower => ScalarFn::Lower,
56 Token::Length => ScalarFn::Length,
57 Token::Trim => ScalarFn::Trim,
58 Token::Substring => ScalarFn::Substring,
59 Token::Concat => ScalarFn::Concat,
60 Token::Abs => ScalarFn::Abs,
61 Token::Round => ScalarFn::Round,
62 Token::Ceil => ScalarFn::Ceil,
63 Token::Floor => ScalarFn::Floor,
64 Token::Sqrt => ScalarFn::Sqrt,
65 Token::Pow => ScalarFn::Pow,
66 Token::Now => ScalarFn::Now,
67 Token::Extract => ScalarFn::Extract,
68 Token::DateAdd => ScalarFn::DateAdd,
69 Token::DateDiff => ScalarFn::DateDiff,
70 Token::JsonType => ScalarFn::JsonType,
71 Token::JsonText => ScalarFn::JsonText,
72 _ => unreachable!(),
73 }
74}
75
76struct Parser {
77 tokens: Vec<Token>,
78 pos: usize,
79 depth: usize,
80}
81
82pub fn parse(input: &str) -> Result<Statement, ParseError> {
104 let tokens = lex(input).map_err(|e| ParseError::Lex {
105 message: e.message,
106 position: e.position,
107 })?;
108 parse_tokens(tokens)
109}
110
111pub fn parse_with_params(input: &str, params: &[ParamValue]) -> Result<Statement, ParseError> {
126 let mut tokens = lex(input).map_err(|e| ParseError::Lex {
127 message: e.message,
128 position: e.position,
129 })?;
130 for tok in tokens.iter_mut() {
131 if let Token::Param(name) = tok {
132 let n: usize = name.parse().map_err(|_| ParseError::Syntax {
133 message: format!(
134 "positional parameters must be numeric (`$1`, `$2`, …); got `${name}`"
135 ),
136 })?;
137 if n == 0 {
138 return Err(ParseError::Syntax {
139 message: "parameter placeholders are 1-based; `$0` is invalid".into(),
140 });
141 }
142 let p = params.get(n - 1).ok_or_else(|| ParseError::Syntax {
143 message: format!(
144 "query references ${n} but only {} parameter(s) were supplied",
145 params.len()
146 ),
147 })?;
148 *tok = match p {
149 ParamValue::Null => Token::Null,
150 ParamValue::Int(v) => Token::IntLit(*v),
151 ParamValue::Float(v) => Token::FloatLit(*v),
152 ParamValue::Bool(v) => Token::BoolLit(*v),
153 ParamValue::Str(s) => Token::StringLit(s.clone()),
154 };
155 }
156 }
157 parse_tokens(tokens)
158}
159
160fn edit_distance(a: &str, b: &str) -> usize {
161 let mut prev: Vec<usize> = (0..=b.len()).collect();
162 let mut curr = vec![0; b.len() + 1];
163 for (i, ca) in a.bytes().enumerate() {
164 curr[0] = i + 1;
165 for (j, cb) in b.bytes().enumerate() {
166 let cost = usize::from(ca != cb);
167 curr[j + 1] = (prev[j + 1] + 1).min(curr[j] + 1).min(prev[j] + cost);
168 }
169 std::mem::swap(&mut prev, &mut curr);
170 }
171 prev[b.len()]
172}
173
174fn keyword_suggestion(word: &str) -> Option<&'static str> {
175 const STATEMENT_KEYWORDS: &[&str] = &[
176 "alter", "begin", "commit", "delete", "drop", "explain", "insert", "refresh", "rollback",
177 "select", "type", "update", "upsert",
178 ];
179 let lower = word.to_ascii_lowercase();
180 STATEMENT_KEYWORDS
181 .iter()
182 .copied()
183 .filter(|kw| edit_distance(&lower, kw) <= 2)
184 .min_by_key(|kw| edit_distance(&lower, kw))
185}
186
187fn parse_tokens(tokens: Vec<Token>) -> Result<Statement, ParseError> {
191 let mut parser = Parser {
192 tokens,
193 pos: 0,
194 depth: 0,
195 };
196 let stmt = parser.parse_statement()?;
197 if !matches!(parser.peek(), Token::Eof) {
203 let mut message = format!(
204 "unexpected trailing token near token {}: {}",
205 parser.pos,
206 parser.peek().display_name()
207 );
208 if let Some(Token::Ident(first)) = parser.tokens.first() {
209 if let Some(suggestion) = keyword_suggestion(first) {
210 message.push_str(&format!("; did you mean `{suggestion}`?"));
211 }
212 }
213 return Err(ParseError::Syntax { message });
214 }
215 Ok(stmt)
216}
217
218fn substitute_projection_aliases(expr: Expr, fields: &[ProjectionField]) -> Expr {
223 match expr {
224 Expr::Field(ref name) => {
225 for f in fields {
226 if f.alias.as_deref() == Some(name.as_str()) {
227 return f.expr.clone();
228 }
229 }
230 expr
231 }
232 Expr::BinaryOp(l, op, r) => Expr::BinaryOp(
233 Box::new(substitute_projection_aliases(*l, fields)),
234 op,
235 Box::new(substitute_projection_aliases(*r, fields)),
236 ),
237 Expr::UnaryOp(op, inner) => {
238 Expr::UnaryOp(op, Box::new(substitute_projection_aliases(*inner, fields)))
239 }
240 Expr::Coalesce(l, r) => Expr::Coalesce(
241 Box::new(substitute_projection_aliases(*l, fields)),
242 Box::new(substitute_projection_aliases(*r, fields)),
243 ),
244 Expr::InList {
245 expr: e,
246 list,
247 negated,
248 } => Expr::InList {
249 expr: Box::new(substitute_projection_aliases(*e, fields)),
250 list: list
251 .into_iter()
252 .map(|i| substitute_projection_aliases(i, fields))
253 .collect(),
254 negated,
255 },
256 Expr::ScalarFunc(f, args) => Expr::ScalarFunc(
257 f,
258 args.into_iter()
259 .map(|a| substitute_projection_aliases(a, fields))
260 .collect(),
261 ),
262 other => other,
263 }
264}
265
266impl Parser {
267 fn peek(&self) -> &Token {
268 &self.tokens[self.pos]
269 }
270
271 fn advance(&mut self) -> Token {
272 let t = self.tokens[self.pos].clone();
273 self.pos += 1;
274 t
275 }
276
277 fn expect(&mut self, expected: &Token) -> Result<(), ParseError> {
278 let t = self.advance();
279 if &t == expected {
280 Ok(())
281 } else {
282 Err(ParseError::UnexpectedToken {
283 expected: expected.display_name(),
284 got: t.display_name(),
285 })
286 }
287 }
288
289 fn unexpected(&self, expected: &str, got: &Token) -> ParseError {
291 ParseError::UnexpectedToken {
292 expected: expected.into(),
293 got: got.display_name(),
294 }
295 }
296
297 fn expect_named_ident(&mut self, context: &str) -> Result<String, ParseError> {
302 match self.advance() {
303 Token::Ident(n) => Ok(n),
304 t => Err(self.named_ident_error(context, &t)),
305 }
306 }
307
308 fn named_ident_error(&self, context: &str, got: &Token) -> ParseError {
312 if let Some(kw) = got.keyword_str() {
313 ParseError::Syntax {
314 message: format!(
318 "syntax error: '{kw}' is a reserved word and cannot be used as a {context}; \
319 rename it or quote it as `{kw}`"
320 ),
321 }
322 } else {
323 ParseError::UnexpectedToken {
324 expected: context.into(),
325 got: got.display_name(),
326 }
327 }
328 }
329
330 fn parse_optional_if_not_exists(&mut self) -> bool {
333 if matches!(self.peek(), Token::Ident(w) if w == "if")
334 && matches!(self.tokens.get(self.pos + 1), Some(Token::Not))
335 && matches!(self.tokens.get(self.pos + 2), Some(Token::Exists))
336 {
337 self.pos += 3;
338 true
339 } else {
340 false
341 }
342 }
343
344 fn parse_optional_if_exists(&mut self) -> bool {
346 if matches!(self.peek(), Token::Ident(w) if w == "if")
347 && matches!(self.tokens.get(self.pos + 1), Some(Token::Exists))
348 {
349 self.pos += 2;
350 true
351 } else {
352 false
353 }
354 }
355
356 fn parse_statement(&mut self) -> Result<Statement, ParseError> {
357 self.depth += 1;
358 if self.depth > MAX_NESTING_DEPTH {
359 self.depth -= 1;
360 return Err(ParseError::NestingDepthExceeded {
361 max: MAX_NESTING_DEPTH,
362 });
363 }
364 if matches!(self.peek(), Token::Explain) {
365 self.advance();
366 let inner = self.parse_statement()?;
367 self.depth -= 1;
368 return Ok(Statement::Explain(Box::new(inner)));
369 }
370 let stmt = match self.peek() {
371 Token::Insert => self.parse_insert(),
372 Token::Upsert => self.parse_upsert(),
373 Token::Type => self.parse_create_type(),
374 Token::Link => self.parse_create_link(),
375 Token::Alter => self.parse_alter_table(),
376 Token::Drop => self.parse_drop_or_drop_view(),
377 Token::Materialized => self.parse_create_view(),
378 Token::Refresh => self.parse_refresh_view(),
379 Token::Begin => {
380 self.advance();
381 if *self.peek() == Token::Transaction {
383 self.advance();
384 }
385 return Ok(Statement::Begin);
386 }
387 Token::Commit => {
388 self.advance();
389 return Ok(Statement::Commit);
390 }
391 Token::Rollback => {
392 self.advance();
393 return Ok(Statement::Rollback);
394 }
395 Token::Schema => self.parse_schema(),
396 Token::Describe => self.parse_describe(),
397 Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
398 self.parse_aggregate_query()
399 }
400 Token::Ident(_) => self.parse_query_or_mutation(),
401 Token::Update => Err(ParseError::Syntax {
402 message: "'update' cannot start a statement — in PowQL, use pipeline syntax: \
403 TableName filter ... update { ... }"
404 .into(),
405 }),
406 Token::Delete => Err(ParseError::Syntax {
407 message: "'delete' cannot start a statement — in PowQL, use pipeline syntax: \
408 TableName filter ... delete"
409 .into(),
410 }),
411 _ => Err(self.unexpected("statement", self.peek())),
412 }?;
413 let result = self.maybe_parse_union(stmt);
415 self.depth -= 1;
416 result
417 }
418
419 fn parse_query_or_mutation(&mut self) -> Result<Statement, ParseError> {
420 let source = match self.advance() {
421 Token::Ident(name) => name,
422 t => {
423 return Err(ParseError::UnexpectedToken {
424 expected: "type name".into(),
425 got: t.display_name(),
426 })
427 }
428 };
429 let alias = self.try_parse_alias();
430 let joins = self.parse_joins()?;
431
432 let mut filter = None;
436 let mut order = None;
437 let mut limit = None;
438 let mut offset = None;
439 let mut projection = None;
440 let mut distinct = false;
441 let mut group_by = None;
442 let mut having_chain = 0usize;
446
447 loop {
448 match self.peek() {
449 Token::Distinct => {
450 self.advance();
451 distinct = true;
452 }
453 Token::Group => {
454 self.advance();
455 group_by = Some(self.parse_group_by()?);
456 }
457 Token::Filter => {
458 self.advance();
459 filter = Some(self.parse_expr()?);
460 }
461 Token::Order => {
462 self.advance();
463 order = Some(self.parse_order()?);
464 }
465 Token::Limit => {
466 self.advance();
467 limit = Some(self.parse_expr()?);
468 }
469 Token::Offset => {
470 self.advance();
471 offset = Some(self.parse_expr()?);
472 }
473 Token::LBrace => {
474 projection = Some(self.parse_projection()?);
475 }
476 Token::Having => {
477 self.advance();
479 let having_expr = self.parse_expr()?;
480 let group = group_by.as_mut().ok_or_else(|| ParseError::Syntax {
481 message: "having without group by".into(),
482 })?;
483 let rewritten = match projection.as_ref() {
484 Some(fields) => substitute_projection_aliases(having_expr, fields),
485 None => having_expr,
486 };
487 group.having = Some(match group.having.take() {
488 Some(existing) => {
489 having_chain += 1;
490 self.check_chain_depth(having_chain)?;
491 Expr::BinaryOp(Box::new(existing), BinOp::And, Box::new(rewritten))
492 }
493 None => rewritten,
494 });
495 }
496 Token::Update => {
497 if !joins.is_empty() {
498 return Err(ParseError::Unsupported {
499 feature: "update on a joined query is not supported".into(),
500 });
501 }
502 self.advance();
503 let assignments = self.parse_assignments()?;
504 let returning = *self.peek() == Token::Returning;
506 if returning {
507 self.advance();
508 }
509 return Ok(Statement::UpdateQuery(UpdateExpr {
510 source,
511 alias,
512 filter,
513 assignments,
514 returning,
515 }));
516 }
517 Token::Delete => {
518 if !joins.is_empty() {
519 return Err(ParseError::Unsupported {
520 feature: "delete on a joined query is not supported".into(),
521 });
522 }
523 self.advance();
524 let returning = *self.peek() == Token::Returning;
526 if returning {
527 self.advance();
528 }
529 return Ok(Statement::DeleteQuery(DeleteExpr {
530 source,
531 alias,
532 filter,
533 returning,
534 }));
535 }
536 _ => break,
537 }
538 }
539
540 Ok(Statement::Query(QueryExpr {
541 source,
542 alias,
543 joins,
544 filter,
545 order,
546 limit,
547 offset,
548 projection,
549 aggregation: None,
550 distinct,
551 group_by,
552 }))
553 }
554
555 fn parse_query_tail(&mut self, source: String) -> Result<QueryExpr, ParseError> {
561 let alias = self.try_parse_alias();
562 let joins = self.parse_joins()?;
563 let mut filter = None;
564 let mut order = None;
565 let mut limit = None;
566 let mut offset = None;
567 let mut projection = None;
568 let mut distinct = false;
569 let mut group_by = None;
570 let mut having_chain = 0usize;
574
575 loop {
576 match self.peek() {
577 Token::Distinct => {
578 self.advance();
579 distinct = true;
580 }
581 Token::Group => {
582 self.advance();
583 group_by = Some(self.parse_group_by()?);
584 }
585 Token::Filter => {
586 self.advance();
587 filter = Some(self.parse_expr()?);
588 }
589 Token::Order => {
590 self.advance();
591 order = Some(self.parse_order()?);
592 }
593 Token::Limit => {
594 self.advance();
595 limit = Some(self.parse_expr()?);
596 }
597 Token::Offset => {
598 self.advance();
599 offset = Some(self.parse_expr()?);
600 }
601 Token::LBrace => {
602 projection = Some(self.parse_projection()?);
603 }
604 Token::Having => {
605 self.advance();
611 let having_expr = self.parse_expr()?;
612 let group = group_by.as_mut().ok_or_else(|| ParseError::Syntax {
613 message: "having without group by".into(),
614 })?;
615 let rewritten = match projection.as_ref() {
616 Some(fields) => substitute_projection_aliases(having_expr, fields),
617 None => having_expr,
618 };
619 group.having = Some(match group.having.take() {
620 Some(existing) => {
621 having_chain += 1;
622 self.check_chain_depth(having_chain)?;
623 Expr::BinaryOp(Box::new(existing), BinOp::And, Box::new(rewritten))
624 }
625 None => rewritten,
626 });
627 }
628 _ => break,
629 }
630 }
631
632 Ok(QueryExpr {
633 source,
634 alias,
635 joins,
636 filter,
637 order,
638 limit,
639 offset,
640 projection,
641 aggregation: None,
642 distinct,
643 group_by,
644 })
645 }
646
647 fn try_parse_alias(&mut self) -> Option<String> {
651 if *self.peek() == Token::As {
652 self.advance();
653 if let Token::Ident(name) = self.peek().clone() {
654 self.advance();
655 return Some(name);
656 }
657 }
658 None
659 }
660
661 fn parse_joins(&mut self) -> Result<Vec<JoinClause>, ParseError> {
668 let mut joins = Vec::new();
669 loop {
670 let kind = match self.peek() {
671 Token::Join => {
672 self.advance();
673 JoinKind::Inner
674 }
675 Token::Inner => {
676 self.advance();
677 self.expect(&Token::Join)?;
678 JoinKind::Inner
679 }
680 Token::LeftKw => {
681 self.advance();
682 if *self.peek() == Token::Outer {
683 self.advance();
684 }
685 self.expect(&Token::Join)?;
686 JoinKind::LeftOuter
687 }
688 Token::RightKw => {
689 self.advance();
690 if *self.peek() == Token::Outer {
691 self.advance();
692 }
693 self.expect(&Token::Join)?;
694 JoinKind::RightOuter
695 }
696 Token::Cross => {
697 self.advance();
698 self.expect(&Token::Join)?;
699 JoinKind::Cross
700 }
701 _ => break,
702 };
703
704 let source = match self.advance() {
705 Token::Ident(name) => name,
706 t => {
707 return Err(ParseError::UnexpectedToken {
708 expected: "type name after join".into(),
709 got: t.display_name(),
710 });
711 }
712 };
713 let alias = self.try_parse_alias();
714 let on = if kind == JoinKind::Cross {
715 None
716 } else if *self.peek() == Token::On {
717 self.advance();
718 Some(self.parse_expr()?)
719 } else {
720 return Err(ParseError::Syntax {
721 message: format!("expected `on <expr>` after join {source}"),
722 });
723 };
724
725 joins.push(JoinClause {
726 kind,
727 source,
728 alias,
729 on,
730 });
731 }
732 Ok(joins)
733 }
734
735 fn parse_insert(&mut self) -> Result<Statement, ParseError> {
736 self.expect(&Token::Insert)?;
737 let target = match self.advance() {
738 Token::Ident(name) => name,
739 t => {
740 return Err(ParseError::UnexpectedToken {
741 expected: "type name".into(),
742 got: t.display_name(),
743 })
744 }
745 };
746 let mut rows = vec![self.parse_assignments()?];
750 while *self.peek() == Token::Comma {
751 self.advance(); rows.push(self.parse_assignments()?);
753 }
754 let returning = *self.peek() == Token::Returning;
756 if returning {
757 self.advance();
758 }
759 Ok(Statement::Insert(InsertExpr {
760 target,
761 rows,
762 returning,
763 }))
764 }
765
766 fn parse_upsert(&mut self) -> Result<Statement, ParseError> {
768 self.expect(&Token::Upsert)?;
769 let target = match self.advance() {
770 Token::Ident(name) => name,
771 t => {
772 return Err(ParseError::UnexpectedToken {
773 expected: "type name".into(),
774 got: t.display_name(),
775 })
776 }
777 };
778 self.expect(&Token::On)?;
779 let key_column = match self.advance() {
780 Token::DotIdent(name) => name,
781 t => {
782 return Err(ParseError::UnexpectedToken {
783 expected: ".key_column".into(),
784 got: t.display_name(),
785 })
786 }
787 };
788 let assignments = self.parse_assignments()?;
789 let on_conflict = if *self.peek() == Token::On {
790 self.advance(); self.expect(&Token::Conflict)?;
792 self.parse_assignments()?
793 } else {
794 Vec::new()
795 };
796 Ok(Statement::Upsert(UpsertExpr {
797 target,
798 key_column,
799 assignments,
800 on_conflict,
801 }))
802 }
803
804 fn parse_assignments(&mut self) -> Result<Vec<Assignment>, ParseError> {
805 self.expect(&Token::LBrace)?;
806 let mut assignments = Vec::new();
807 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
808 if matches!(self.peek(), Token::DotIdent(_) | Token::Ident(_))
815 && matches!(self.tokens.get(self.pos + 1), Some(Token::Arrow))
816 {
817 let field = match self.peek() {
818 Token::DotIdent(n) | Token::Ident(n) => n.clone(),
819 _ => unreachable!("guarded by the matches! above"),
820 };
821 return Err(ParseError::Unsupported {
822 feature: format!(
823 "cannot assign to a JSON path target `.{field}->...` (at token {pos}): \
824 JSON path assignment targets are not supported; write the whole JSON \
825 column instead (path mutation such as json_set is not yet available)",
826 pos = self.pos
827 ),
828 });
829 }
830 let field = self.expect_named_ident("field name")?;
831 self.expect(&Token::Assign)?;
832 let value = self.parse_expr()?;
833 assignments.push(Assignment { field, value });
834 if *self.peek() == Token::Comma {
835 self.advance();
836 }
837 }
838 self.expect(&Token::RBrace)?;
839 Ok(assignments)
840 }
841
842 fn parse_projection(&mut self) -> Result<Vec<ProjectionField>, ParseError> {
843 self.expect(&Token::LBrace)?;
844 let mut fields = Vec::new();
845 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
846 if matches!(self.peek(), Token::Ident(_))
854 && matches!(self.tokens.get(self.pos + 1), Some(Token::Colon))
855 {
856 let alias = match self.advance() {
857 Token::Ident(name) => name,
858 _ => unreachable!("guarded by the matches! above"),
859 };
860 self.advance(); self.reject_bare_dotted_path()?;
862 let expr = if matches!(self.peek(), Token::Ident(_))
865 && matches!(self.tokens.get(self.pos + 1), Some(Token::As))
866 {
867 Expr::NestedQuery(Box::new(self.parse_nested_query()?))
868 } else if self.at_link_traversal() {
869 Expr::NestedQuery(Box::new(self.parse_link_traversal()?))
875 } else if self.at_scalar_link_path() {
876 self.parse_scalar_link_path()?
878 } else {
879 self.parse_expr()?
880 };
881 fields.push(ProjectionField {
882 alias: Some(alias),
883 expr,
884 });
885 } else {
886 if matches!(self.peek(), Token::Ident(_))
887 && matches!(self.tokens.get(self.pos + 1), Some(Token::As))
888 {
889 return Err(ParseError::Syntax {
890 message: "a nested projection needs a field name: \
891 `<name>: <Table> as <alias> filter ... { ... }`"
892 .into(),
893 });
894 }
895 self.reject_bare_dotted_path()?;
896 let expr = if self.at_scalar_link_path() {
897 self.parse_scalar_link_path()?
900 } else {
901 self.parse_expr()?
902 };
903 fields.push(ProjectionField { alias: None, expr });
904 }
905 if *self.peek() == Token::Comma {
906 self.advance();
907 }
908 }
909 self.expect(&Token::RBrace)?;
910 Ok(fields)
911 }
912
913 fn reject_bare_dotted_path(&self) -> Result<(), ParseError> {
920 if let (Token::DotIdent(first), Some(Token::DotIdent(second))) =
921 (self.peek(), self.tokens.get(self.pos + 1))
922 {
923 return Err(ParseError::Syntax {
924 message: format!(
925 "`.{first}.{second}` is ambiguous in a projection: for a link \
926 path, alias the table and qualify the path \
927 (`Order as o {{ o.{first}.{second} }}`); for separate fields, \
928 separate them with commas (`.{first}, .{second}`)"
929 ),
930 });
931 }
932 Ok(())
933 }
934
935 fn at_link_traversal(&self) -> bool {
941 matches!(self.peek(), Token::Ident(_))
942 && matches!(self.tokens.get(self.pos + 1), Some(Token::DotIdent(_)))
943 && matches!(
944 self.tokens.get(self.pos + 2),
945 Some(Token::LBrace | Token::Filter | Token::Order | Token::Limit | Token::Offset)
946 )
947 }
948
949 fn at_scalar_link_path(&self) -> bool {
956 matches!(self.peek(), Token::Ident(_))
957 && matches!(self.tokens.get(self.pos + 1), Some(Token::DotIdent(_)))
958 && matches!(self.tokens.get(self.pos + 2), Some(Token::DotIdent(_)))
959 }
960
961 fn parse_scalar_link_path(&mut self) -> Result<Expr, ParseError> {
967 let outer_alias = match self.advance() {
968 Token::Ident(n) => n,
969 t => return Err(self.named_ident_error("link path outer alias", &t)),
970 };
971 let mut parts = Vec::new();
972 while let Some(Token::DotIdent(_)) = self.tokens.get(self.pos) {
973 match self.advance() {
974 Token::DotIdent(n) => parts.push(n),
975 _ => unreachable!("peeked DotIdent"),
976 }
977 }
978 debug_assert!(parts.len() >= 2, "guarded by at_scalar_link_path");
979 let column = parts.pop().expect("at least two parts");
980 Ok(Expr::LinkPath {
981 outer_alias,
982 links: parts,
983 column,
984 })
985 }
986
987 fn parse_link_traversal(&mut self) -> Result<NestedQuery, ParseError> {
997 self.depth += 1;
998 if self.depth > MAX_NESTING_DEPTH {
999 self.depth -= 1;
1000 return Err(ParseError::NestingDepthExceeded {
1001 max: MAX_NESTING_DEPTH,
1002 });
1003 }
1004 let result = self.parse_link_traversal_inner();
1005 self.depth -= 1;
1006 result
1007 }
1008
1009 fn parse_link_traversal_inner(&mut self) -> Result<NestedQuery, ParseError> {
1010 let outer_alias = match self.advance() {
1011 Token::Ident(n) => n,
1012 t => return Err(self.named_ident_error("link outer alias", &t)),
1013 };
1014 let link_name = match self.advance() {
1015 Token::DotIdent(n) => n,
1016 t => {
1017 return Err(ParseError::UnexpectedToken {
1018 expected: "link name".into(),
1019 got: t.display_name(),
1020 })
1021 }
1022 };
1023 let child_alias = link_name.clone();
1026
1027 let mut residual = None;
1031 if *self.peek() == Token::Filter {
1032 self.advance();
1033 residual = Some(qualify_bare_fields(self.parse_expr()?, &child_alias));
1034 }
1035 let mut order = None;
1036 let mut limit = None;
1037 let mut offset = None;
1038 let mut offset_before_limit = false;
1039 loop {
1040 match self.peek() {
1041 Token::Order => {
1042 self.advance();
1043 let mut clause = self.parse_order()?;
1044 for key in &mut clause.keys {
1045 key.expr = qualify_bare_fields(
1046 std::mem::replace(&mut key.expr, Expr::Null),
1047 &child_alias,
1048 );
1049 }
1050 order = Some(clause);
1051 }
1052 Token::Limit => {
1053 self.advance();
1054 if offset.is_some() {
1055 offset_before_limit = true;
1056 }
1057 limit = Some(self.parse_expr()?);
1058 }
1059 Token::Offset => {
1060 self.advance();
1061 offset = Some(self.parse_expr()?);
1062 }
1063 _ => break,
1064 }
1065 }
1066 self.expect(&Token::LBrace)?;
1067 let mut fields = Vec::new();
1068 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1069 let alias = if matches!(self.peek(), Token::Ident(_))
1070 && matches!(self.tokens.get(self.pos + 1), Some(Token::Colon))
1071 {
1072 let alias = self.expect_named_ident("field alias")?;
1073 self.advance(); Some(alias)
1075 } else {
1076 None
1077 };
1078 self.reject_bare_dotted_path()?;
1079 let expr = qualify_bare_fields(self.parse_expr()?, &child_alias);
1080 fields.push(ProjectionField { alias, expr });
1081 if *self.peek() == Token::Comma {
1082 self.advance();
1083 }
1084 }
1085 self.expect(&Token::RBrace)?;
1086 if fields.is_empty() {
1087 return Err(ParseError::Syntax {
1088 message: "link traversal requires at least one field".into(),
1089 });
1090 }
1091 Ok(NestedQuery {
1092 source: String::new(),
1094 alias: child_alias,
1095 via_link: Some(ViaLink {
1096 outer_alias,
1097 link_name,
1098 }),
1099 filter: residual.unwrap_or(Expr::Literal(Literal::Bool(true))),
1102 order,
1103 limit,
1104 offset,
1105 offset_before_limit,
1106 fields,
1107 })
1108 }
1109
1110 fn parse_nested_query(&mut self) -> Result<NestedQuery, ParseError> {
1115 self.depth += 1;
1118 if self.depth > MAX_NESTING_DEPTH {
1119 self.depth -= 1;
1120 return Err(ParseError::NestingDepthExceeded {
1121 max: MAX_NESTING_DEPTH,
1122 });
1123 }
1124 let result = self.parse_nested_query_inner();
1125 self.depth -= 1;
1126 result
1127 }
1128
1129 fn parse_nested_query_inner(&mut self) -> Result<NestedQuery, ParseError> {
1130 let source = self.expect_named_ident("nested source type")?;
1131 self.expect(&Token::As)?;
1132 let alias = self.expect_named_ident("nested source alias")?;
1133 self.expect(&Token::Filter)?;
1134 let filter = self.parse_expr()?;
1135 let mut order = None;
1136 let mut limit = None;
1137 let mut offset = None;
1138 let mut offset_before_limit = false;
1139 loop {
1140 match self.peek() {
1141 Token::Order => {
1142 self.advance();
1143 order = Some(self.parse_order()?);
1144 }
1145 Token::Limit => {
1146 self.advance();
1147 if offset.is_some() {
1148 offset_before_limit = true;
1149 }
1150 limit = Some(self.parse_expr()?);
1151 }
1152 Token::Offset => {
1153 self.advance();
1154 offset = Some(self.parse_expr()?);
1155 }
1156 _ => break,
1157 }
1158 }
1159 self.expect(&Token::LBrace)?;
1160 let mut fields = Vec::new();
1161 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1162 let alias = if matches!(self.peek(), Token::Ident(_))
1163 && matches!(self.tokens.get(self.pos + 1), Some(Token::Colon))
1164 {
1165 let alias = self.expect_named_ident("field alias")?;
1166 self.advance(); Some(alias)
1168 } else {
1169 None
1170 };
1171 let expr = if matches!(self.peek(), Token::Ident(_))
1172 && matches!(self.tokens.get(self.pos + 1), Some(Token::As))
1173 {
1174 if alias.is_none() {
1175 return Err(ParseError::Syntax {
1176 message: "a nested projection needs a field name: \
1177 `<name>: <Table> as <alias> filter ... { ... }`"
1178 .into(),
1179 });
1180 }
1181 Expr::NestedQuery(Box::new(self.parse_nested_query()?))
1182 } else {
1183 self.reject_bare_dotted_path()?;
1184 self.parse_expr()?
1185 };
1186 fields.push(ProjectionField { alias, expr });
1187 if *self.peek() == Token::Comma {
1188 self.advance();
1189 }
1190 }
1191 self.expect(&Token::RBrace)?;
1192 if fields.is_empty() {
1193 return Err(ParseError::Syntax {
1194 message: "nested projection requires at least one field".into(),
1195 });
1196 }
1197 Ok(NestedQuery {
1198 source,
1199 alias,
1200 via_link: None,
1201 filter,
1202 order,
1203 limit,
1204 offset,
1205 offset_before_limit,
1206 fields,
1207 })
1208 }
1209
1210 fn parse_over_clause(&mut self) -> Result<(Vec<Expr>, Vec<OrderKey>), ParseError> {
1213 self.expect(&Token::Over)?;
1214 self.expect(&Token::LParen)?;
1215 let mut partition_by = Vec::new();
1216 let mut order_by = Vec::new();
1217 if *self.peek() == Token::Partition {
1218 self.advance();
1219 loop {
1220 partition_by.push(self.parse_expr()?);
1221 if *self.peek() == Token::Comma {
1222 if !matches!(
1223 self.tokens.get(self.pos + 1),
1224 Some(Token::Order | Token::RParen)
1225 ) {
1226 self.advance();
1227 } else {
1228 break;
1229 }
1230 } else {
1231 break;
1232 }
1233 }
1234 }
1235 if *self.peek() == Token::Order {
1236 self.advance();
1237 loop {
1238 let expr = self.parse_expr()?;
1239 let descending = match self.peek() {
1240 Token::Desc => {
1241 self.advance();
1242 true
1243 }
1244 Token::Asc => {
1245 self.advance();
1246 false
1247 }
1248 _ => false,
1249 };
1250 order_by.push(OrderKey { expr, descending });
1251 if *self.peek() == Token::Comma {
1252 self.advance();
1253 } else {
1254 break;
1255 }
1256 }
1257 }
1258 self.expect(&Token::RParen)?;
1259 Ok((partition_by, order_by))
1260 }
1261
1262 fn parse_cast_type(&mut self) -> Result<CastType, ParseError> {
1264 match self.advance() {
1265 Token::StringLit(s) => match s.as_str() {
1266 "int" | "Int" | "INT" => Ok(CastType::Int),
1267 "float" | "Float" | "FLOAT" => Ok(CastType::Float),
1268 "str" | "Str" | "STR" | "string" | "String" => Ok(CastType::Str),
1269 "bool" | "Bool" | "BOOL" | "boolean" => Ok(CastType::Bool),
1270 "datetime" | "DateTime" | "DATETIME" => Ok(CastType::DateTime),
1271 "uuid" | "Uuid" | "UUID" => Ok(CastType::Uuid),
1272 "bytes" | "Bytes" | "BYTES" | "bytea" => Ok(CastType::Bytes),
1273 other => Err(ParseError::Syntax {
1274 message: format!("invalid cast type: \"{other}\""),
1275 }),
1276 },
1277 t => Err(ParseError::UnexpectedToken {
1278 expected: "string literal for cast type".into(),
1279 got: t.display_name(),
1280 }),
1281 }
1282 }
1283
1284 fn parse_order(&mut self) -> Result<OrderClause, ParseError> {
1285 let mut keys = Vec::new();
1286 loop {
1287 let expr = self.parse_expr()?;
1288 let descending = match self.peek() {
1289 Token::Desc => {
1290 self.advance();
1291 true
1292 }
1293 Token::Asc => {
1294 self.advance();
1295 false
1296 }
1297 _ => false,
1298 };
1299 keys.push(OrderKey { expr, descending });
1300 if *self.peek() == Token::Comma {
1301 self.advance();
1302 } else {
1303 break;
1304 }
1305 }
1306 Ok(OrderClause { keys })
1307 }
1308
1309 fn parse_aggregate_query(&mut self) -> Result<Statement, ParseError> {
1310 let mut func = match self.advance() {
1311 Token::Count => AggFunc::Count,
1312 Token::Avg => AggFunc::Avg,
1313 Token::Sum => AggFunc::Sum,
1314 Token::Min => AggFunc::Min,
1315 Token::Max => AggFunc::Max,
1316 t => {
1317 return Err(ParseError::UnexpectedToken {
1318 expected: "aggregate function".into(),
1319 got: t.display_name(),
1320 })
1321 }
1322 };
1323 self.expect(&Token::LParen)?;
1324 let mode = if *self.peek() == Token::Raw {
1325 self.advance();
1326 AggregateMode::Raw
1327 } else {
1328 AggregateMode::Symmetric
1329 };
1330 if func == AggFunc::Count && *self.peek() == Token::Distinct {
1332 self.advance();
1333 func = AggFunc::CountDistinct;
1334 }
1335 let source = match self.advance() {
1336 Token::Ident(name) => name,
1337 t => {
1338 return Err(ParseError::UnexpectedToken {
1339 expected: "type name".into(),
1340 got: t.display_name(),
1341 })
1342 }
1343 };
1344 let mut query = self.parse_query_tail(source)?;
1348 self.expect(&Token::RParen)?;
1349
1350 let mut argument: Option<Expr> = None;
1363 if let Some(proj) = &query.projection {
1364 if proj.len() == 1 && proj[0].alias.is_none() {
1365 argument = Some(proj[0].expr.clone());
1366 }
1367 }
1368 if argument.is_some() {
1369 query.projection = None;
1370 }
1371 query.aggregation = Some(AggregateExpr {
1372 function: func,
1373 argument,
1374 mode,
1375 });
1376 Ok(Statement::Query(query))
1377 }
1378
1379 fn parse_expr(&mut self) -> Result<Expr, ParseError> {
1380 self.depth += 1;
1381 if self.depth > MAX_NESTING_DEPTH {
1382 self.depth -= 1;
1383 return Err(ParseError::NestingDepthExceeded {
1384 max: MAX_NESTING_DEPTH,
1385 });
1386 }
1387 let result = self.parse_or_expr();
1388 self.depth -= 1;
1389 result
1390 }
1391
1392 fn check_chain_depth(&self, chain: usize) -> Result<(), ParseError> {
1397 if self.depth + chain > MAX_NESTING_DEPTH {
1398 return Err(ParseError::NestingDepthExceeded {
1399 max: MAX_NESTING_DEPTH,
1400 });
1401 }
1402 Ok(())
1403 }
1404
1405 fn parse_or_expr(&mut self) -> Result<Expr, ParseError> {
1406 let mut left = self.parse_and_expr()?;
1407 let mut chain = 0usize;
1408 while *self.peek() == Token::Or {
1409 chain += 1;
1410 self.check_chain_depth(chain)?;
1411 self.advance();
1412 let right = self.parse_and_expr()?;
1413 left = Expr::BinaryOp(Box::new(left), BinOp::Or, Box::new(right));
1414 }
1415 Ok(left)
1416 }
1417
1418 fn parse_and_expr(&mut self) -> Result<Expr, ParseError> {
1419 let mut left = self.parse_comparison()?;
1420 let mut chain = 0usize;
1421 while *self.peek() == Token::And {
1422 chain += 1;
1423 self.check_chain_depth(chain)?;
1424 self.advance();
1425 let right = self.parse_comparison()?;
1426 left = Expr::BinaryOp(Box::new(left), BinOp::And, Box::new(right));
1427 }
1428 Ok(left)
1429 }
1430
1431 fn parse_comparison(&mut self) -> Result<Expr, ParseError> {
1432 let mut negations = 0usize;
1442 while *self.peek() == Token::Not
1443 && !matches!(self.tokens.get(self.pos + 1), Some(Token::Exists))
1444 {
1445 self.advance();
1446 negations += 1;
1447 self.check_chain_depth(negations)?;
1448 }
1449 let mut expr = self.parse_comparison_body()?;
1450 for _ in 0..negations {
1451 expr = Expr::UnaryOp(UnaryOp::Not, Box::new(expr));
1452 }
1453 Ok(expr)
1454 }
1455
1456 fn parse_comparison_body(&mut self) -> Result<Expr, ParseError> {
1457 let left = self.parse_additive()?;
1458
1459 if *self.peek() == Token::Is {
1461 self.advance();
1462 if *self.peek() == Token::Not {
1463 self.advance();
1464 self.expect(&Token::Null)?;
1465 return Ok(Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(left)));
1466 } else {
1467 self.expect(&Token::Null)?;
1468 return Ok(Expr::UnaryOp(UnaryOp::IsNull, Box::new(left)));
1469 }
1470 }
1471
1472 match self.peek() {
1475 Token::In => {
1476 self.advance();
1477 return self.parse_in_list(left, false);
1478 }
1479 Token::Like => {
1480 self.advance();
1481 let pattern = self.parse_additive()?;
1482 return Ok(Expr::BinaryOp(
1483 Box::new(left),
1484 BinOp::Like,
1485 Box::new(pattern),
1486 ));
1487 }
1488 Token::Between => {
1489 self.advance();
1490 return self.parse_between(left, false);
1491 }
1492 Token::Not => {
1493 let next = self.tokens.get(self.pos + 1);
1497 match next {
1498 Some(Token::In) => {
1499 self.advance(); self.advance(); return self.parse_in_list(left, true);
1502 }
1503 Some(Token::Like) => {
1504 self.advance(); self.advance(); let pattern = self.parse_additive()?;
1507 let like = Expr::BinaryOp(Box::new(left), BinOp::Like, Box::new(pattern));
1508 return Ok(Expr::UnaryOp(UnaryOp::Not, Box::new(like)));
1509 }
1510 Some(Token::Between) => {
1511 self.advance(); self.advance(); return self.parse_between(left, true);
1514 }
1515 _ => {}
1516 }
1517 }
1518 _ => {}
1519 }
1520
1521 let op = match self.peek() {
1522 Token::Eq => BinOp::Eq,
1523 Token::Neq => BinOp::Neq,
1524 Token::Lt => BinOp::Lt,
1525 Token::Gt => BinOp::Gt,
1526 Token::Lte => BinOp::Lte,
1527 Token::Gte => BinOp::Gte,
1528 _ => return Ok(left),
1529 };
1530 self.advance();
1531 if *self.peek() == Token::Null {
1535 match op {
1536 BinOp::Eq => {
1537 self.advance();
1538 return Ok(Expr::UnaryOp(UnaryOp::IsNull, Box::new(left)));
1539 }
1540 BinOp::Neq => {
1541 self.advance();
1542 return Ok(Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(left)));
1543 }
1544 _ => {}
1545 }
1546 }
1547 let right = self.parse_additive()?;
1548 Ok(Expr::BinaryOp(Box::new(left), op, Box::new(right)))
1549 }
1550
1551 fn parse_in_list(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParseError> {
1556 self.expect(&Token::LParen)?;
1557 if let Token::Ident(_) = self.peek() {
1559 let after = self.tokens.get(self.pos + 1);
1562 let is_subquery = !matches!(after, Some(Token::Comma) | Some(Token::RParen));
1563 if is_subquery {
1564 let source = match self.advance() {
1565 Token::Ident(name) => name,
1566 _ => unreachable!(),
1567 };
1568 let subquery = self.parse_query_tail(source)?;
1569 self.expect(&Token::RParen)?;
1570 return Ok(Expr::InSubquery {
1571 expr: Box::new(expr),
1572 subquery: Box::new(subquery),
1573 negated,
1574 });
1575 }
1576 }
1577 let mut list = Vec::new();
1578 while !matches!(self.peek(), Token::RParen | Token::Eof) {
1579 list.push(self.parse_expr()?);
1580 if *self.peek() == Token::Comma {
1581 self.advance();
1582 }
1583 }
1584 self.expect(&Token::RParen)?;
1585 Ok(Expr::InList {
1586 expr: Box::new(expr),
1587 list,
1588 negated,
1589 })
1590 }
1591
1592 fn try_parse_exists_subquery(&mut self) -> Result<Option<QueryExpr>, ParseError> {
1600 if *self.peek() != Token::LParen {
1601 return Ok(None);
1602 }
1603 let after_lparen = self.tokens.get(self.pos + 1);
1607 if !matches!(after_lparen, Some(Token::Ident(_))) {
1608 return Ok(None);
1609 }
1610 self.expect(&Token::LParen)?;
1611 let source = match self.advance() {
1612 Token::Ident(name) => name,
1613 _ => unreachable!(),
1614 };
1615 let subquery = self.parse_query_tail(source)?;
1616 self.expect(&Token::RParen)?;
1617 Ok(Some(subquery))
1618 }
1619
1620 fn parse_between(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParseError> {
1624 let low = self.parse_additive()?;
1625 self.expect(&Token::And)?;
1626 let high = self.parse_additive()?;
1627 if negated {
1628 Ok(Expr::BinaryOp(
1630 Box::new(Expr::BinaryOp(
1631 Box::new(expr.clone()),
1632 BinOp::Lt,
1633 Box::new(low),
1634 )),
1635 BinOp::Or,
1636 Box::new(Expr::BinaryOp(Box::new(expr), BinOp::Gt, Box::new(high))),
1637 ))
1638 } else {
1639 Ok(Expr::BinaryOp(
1641 Box::new(Expr::BinaryOp(
1642 Box::new(expr.clone()),
1643 BinOp::Gte,
1644 Box::new(low),
1645 )),
1646 BinOp::And,
1647 Box::new(Expr::BinaryOp(Box::new(expr), BinOp::Lte, Box::new(high))),
1648 ))
1649 }
1650 }
1651
1652 fn parse_group_by(&mut self) -> Result<GroupByClause, ParseError> {
1654 let mut keys = Vec::new();
1655 loop {
1656 let expr = self.parse_expr()?;
1657 let output_name = match &expr {
1658 Expr::Field(_) | Expr::QualifiedField { .. } | Expr::JsonPath { .. } => {
1659 expression_output_name(&expr)
1660 }
1661 _ => format!("__group_{}", keys.len()),
1662 };
1663 keys.push(GroupKey { expr, output_name });
1664 if *self.peek() == Token::Comma {
1665 self.advance();
1666 } else {
1667 break;
1668 }
1669 }
1670 if keys.is_empty() {
1671 return Err(ParseError::Syntax {
1672 message: "expected at least one group key after group".into(),
1673 });
1674 }
1675 let having = if *self.peek() == Token::Having {
1676 self.advance();
1677 Some(self.parse_expr()?)
1678 } else {
1679 None
1680 };
1681 Ok(GroupByClause { keys, having })
1682 }
1683
1684 fn parse_additive(&mut self) -> Result<Expr, ParseError> {
1685 let mut left = self.parse_multiplicative()?;
1686 let mut chain = 0usize;
1687 loop {
1688 let op = match self.peek() {
1689 Token::Plus => BinOp::Add,
1690 Token::Minus => BinOp::Sub,
1691 Token::Coalesce => {
1692 chain += 1;
1693 self.check_chain_depth(chain)?;
1694 self.advance();
1695 let right = self.parse_multiplicative()?;
1696 left = Expr::Coalesce(Box::new(left), Box::new(right));
1697 continue;
1698 }
1699 _ => break,
1700 };
1701 chain += 1;
1702 self.check_chain_depth(chain)?;
1703 self.advance();
1704 let right = self.parse_multiplicative()?;
1705 left = Expr::BinaryOp(Box::new(left), op, Box::new(right));
1706 }
1707 Ok(left)
1708 }
1709
1710 fn parse_multiplicative(&mut self) -> Result<Expr, ParseError> {
1711 let mut left = self.parse_primary()?;
1712 let mut chain = 0usize;
1713 loop {
1714 let op = match self.peek() {
1715 Token::Star => BinOp::Mul,
1716 Token::Slash => BinOp::Div,
1717 _ => break,
1718 };
1719 chain += 1;
1720 self.check_chain_depth(chain)?;
1721 self.advance();
1722 let right = self.parse_primary()?;
1723 left = Expr::BinaryOp(Box::new(left), op, Box::new(right));
1724 }
1725 Ok(left)
1726 }
1727
1728 fn parse_primary(&mut self) -> Result<Expr, ParseError> {
1729 self.depth += 1;
1734 if self.depth > MAX_NESTING_DEPTH {
1735 self.depth -= 1;
1736 return Err(ParseError::NestingDepthExceeded {
1737 max: MAX_NESTING_DEPTH,
1738 });
1739 }
1740 let result = self.parse_primary_inner();
1741 self.depth -= 1;
1742 self.parse_json_path_postfix(result?)
1748 }
1749
1750 fn parse_json_path_postfix(&mut self, base: Expr) -> Result<Expr, ParseError> {
1757 if *self.peek() != Token::Arrow {
1758 return Ok(base);
1759 }
1760 match &base {
1761 Expr::Field(_) | Expr::QualifiedField { .. } | Expr::JsonPath { .. } => {}
1762 _ => {
1763 return Err(ParseError::Syntax {
1764 message: "'->' JSON path access requires a field base \
1765 (e.g. .data->key or posts.data->author)"
1766 .into(),
1767 })
1768 }
1769 }
1770 let mut segments = Vec::new();
1771 while *self.peek() == Token::Arrow {
1772 self.advance(); let seg = match self.advance() {
1774 Token::Ident(name) => PathSeg::Key(name),
1776 Token::StringLit(s) => PathSeg::Key(s),
1779 Token::IntLit(v) => {
1783 let idx = u32::try_from(v).map_err(|_| ParseError::Syntax {
1784 message: format!(
1785 "invalid JSON path array index {v}: expected a non-negative integer that fits in 32 bits"
1786 ),
1787 })?;
1788 PathSeg::Index(idx)
1789 }
1790 other => {
1791 return Err(ParseError::Syntax {
1792 message: format!(
1793 "expected a JSON path segment (object key or array index) after '->', found {}",
1794 other.display_name()
1795 ),
1796 })
1797 }
1798 };
1799 segments.push(seg);
1800 }
1801 if let Expr::JsonPath {
1804 base: inner_base,
1805 segments: mut inner_segments,
1806 } = base
1807 {
1808 inner_segments.extend(segments);
1809 return Ok(Expr::JsonPath {
1810 base: inner_base,
1811 segments: inner_segments,
1812 });
1813 }
1814 Ok(Expr::JsonPath {
1815 base: Box::new(base),
1816 segments,
1817 })
1818 }
1819
1820 fn parse_primary_inner(&mut self) -> Result<Expr, ParseError> {
1821 match self.peek().clone() {
1822 Token::DotIdent(name) => {
1823 self.advance();
1824 Ok(Expr::Field(name))
1825 }
1826 Token::IntLit(v) => {
1827 self.advance();
1828 Ok(Expr::Literal(Literal::Int(v)))
1829 }
1830 Token::FloatLit(v) => {
1831 self.advance();
1832 Ok(Expr::Literal(Literal::Float(v)))
1833 }
1834 Token::StringLit(v) => {
1835 self.advance();
1836 Ok(Expr::Literal(Literal::String(v)))
1837 }
1838 Token::BoolLit(v) => {
1839 self.advance();
1840 Ok(Expr::Literal(Literal::Bool(v)))
1841 }
1842 Token::Null => {
1849 self.advance();
1850 Ok(Expr::Null)
1851 }
1852 Token::Not => {
1853 self.advance();
1854 if *self.peek() == Token::Exists {
1855 self.advance();
1856 if let Some(sub) = self.try_parse_exists_subquery()? {
1860 return Ok(Expr::ExistsSubquery {
1861 subquery: Box::new(sub),
1862 negated: true,
1863 });
1864 }
1865 let expr = self.parse_primary()?;
1866 Ok(Expr::UnaryOp(UnaryOp::NotExists, Box::new(expr)))
1867 } else {
1868 let expr = self.parse_primary()?;
1869 Ok(Expr::UnaryOp(UnaryOp::Not, Box::new(expr)))
1870 }
1871 }
1872 Token::Exists => {
1873 self.advance();
1874 if let Some(sub) = self.try_parse_exists_subquery()? {
1878 return Ok(Expr::ExistsSubquery {
1879 subquery: Box::new(sub),
1880 negated: false,
1881 });
1882 }
1883 let expr = self.parse_primary()?;
1884 Ok(Expr::UnaryOp(UnaryOp::Exists, Box::new(expr)))
1885 }
1886 Token::LParen => {
1887 self.advance();
1888 let expr = self.parse_expr()?;
1889 self.expect(&Token::RParen)?;
1890 Ok(expr)
1891 }
1892 Token::Ident(name) => {
1893 self.advance();
1894 if *self.peek() == Token::LParen {
1899 let cast_type = match name.as_str() {
1900 "uuid" => Some(CastType::Uuid),
1901 "bytes" => Some(CastType::Bytes),
1902 _ => None,
1903 };
1904 if let Some(cast_type) = cast_type {
1905 self.advance(); let inner = self.parse_expr()?;
1907 self.expect(&Token::RParen)?;
1908 return Ok(Expr::Cast(Box::new(inner), cast_type));
1909 }
1910 }
1911 if let Token::DotIdent(field) = self.peek().clone() {
1915 self.advance();
1916 return Ok(Expr::QualifiedField {
1917 qualifier: name,
1918 field,
1919 });
1920 }
1921 Ok(Expr::Field(name))
1922 }
1923 Token::RowNumber | Token::Rank | Token::DenseRank => {
1925 let wfunc = match self.advance() {
1926 Token::RowNumber => WindowFunc::RowNumber,
1927 Token::Rank => WindowFunc::Rank,
1928 Token::DenseRank => WindowFunc::DenseRank,
1929 _ => {
1930 return Err(ParseError::Syntax {
1931 message: "unexpected window function token".into(),
1932 })
1933 }
1934 };
1935 self.expect(&Token::LParen)?;
1936 self.expect(&Token::RParen)?;
1937 let (partition_by, order_by) = self.parse_over_clause()?;
1938 Ok(Expr::Window {
1939 function: wfunc,
1940 args: vec![],
1941 mode: AggregateMode::Symmetric,
1942 partition_by,
1943 order_by,
1944 })
1945 }
1946 Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
1950 let mut func = match self.advance() {
1951 Token::Count => AggFunc::Count,
1952 Token::Avg => AggFunc::Avg,
1953 Token::Sum => AggFunc::Sum,
1954 Token::Min => AggFunc::Min,
1955 Token::Max => AggFunc::Max,
1956 _ => {
1957 return Err(ParseError::Syntax {
1958 message: "unexpected aggregate token".into(),
1959 })
1960 }
1961 };
1962 self.expect(&Token::LParen)?;
1963 let mode = if *self.peek() == Token::Raw {
1964 self.advance();
1965 AggregateMode::Raw
1966 } else {
1967 AggregateMode::Symmetric
1968 };
1969 if func == AggFunc::Count && *self.peek() == Token::Star {
1971 self.advance();
1972 self.expect(&Token::RParen)?;
1973 if *self.peek() == Token::Over {
1975 let (partition_by, order_by) = self.parse_over_clause()?;
1976 return Ok(Expr::Window {
1977 function: WindowFunc::Count,
1978 args: vec![Expr::Field("*".into())],
1979 mode,
1980 partition_by,
1981 order_by,
1982 });
1983 }
1984 return Ok(Expr::FunctionCall(
1985 AggFunc::Count,
1986 Box::new(Expr::Field("*".into())),
1987 mode,
1988 ));
1989 }
1990 if func == AggFunc::Count && *self.peek() == Token::Distinct {
1992 self.advance();
1993 func = AggFunc::CountDistinct;
1994 }
1995 let inner = self.parse_expr()?;
1996 self.expect(&Token::RParen)?;
1997 if *self.peek() == Token::Over {
1999 let wfunc = match func {
2000 AggFunc::Count => WindowFunc::Count,
2001 AggFunc::Avg => WindowFunc::Avg,
2002 AggFunc::Sum => WindowFunc::Sum,
2003 AggFunc::Min => WindowFunc::Min,
2004 AggFunc::Max => WindowFunc::Max,
2005 _ => {
2006 return Err(ParseError::Unsupported {
2007 feature: "count(distinct ...) over (...) is not supported".into(),
2008 })
2009 }
2010 };
2011 let (partition_by, order_by) = self.parse_over_clause()?;
2012 return Ok(Expr::Window {
2013 function: wfunc,
2014 args: vec![inner],
2015 mode,
2016 partition_by,
2017 order_by,
2018 });
2019 }
2020 Ok(Expr::FunctionCall(func, Box::new(inner), mode))
2021 }
2022 Token::Upper
2023 | Token::Lower
2024 | Token::Length
2025 | Token::Trim
2026 | Token::Substring
2027 | Token::Concat
2028 | Token::Abs
2029 | Token::Round
2030 | Token::Ceil
2031 | Token::Floor
2032 | Token::Sqrt
2033 | Token::Pow
2034 | Token::Now
2035 | Token::Extract
2036 | Token::DateAdd
2037 | Token::DateDiff
2038 | Token::JsonType
2039 | Token::JsonText => {
2040 let tok = self.advance();
2041 let func = token_to_scalar_fn(&tok);
2042 self.expect(&Token::LParen)?;
2043 let mut args = Vec::new();
2044 while !matches!(self.peek(), Token::RParen | Token::Eof) {
2045 args.push(self.parse_expr()?);
2046 if *self.peek() == Token::Comma {
2047 self.advance();
2048 }
2049 }
2050 self.expect(&Token::RParen)?;
2051 Ok(Expr::ScalarFunc(func, args))
2052 }
2053 Token::Cast => {
2054 self.advance();
2055 self.expect(&Token::LParen)?;
2056 let inner = self.parse_expr()?;
2057 self.expect(&Token::Comma)?;
2058 let cast_type = self.parse_cast_type()?;
2059 self.expect(&Token::RParen)?;
2060 Ok(Expr::Cast(Box::new(inner), cast_type))
2061 }
2062 Token::Case => {
2063 self.advance();
2064 let mut whens = Vec::new();
2065 while *self.peek() == Token::When {
2066 self.advance();
2067 let condition = self.parse_expr()?;
2068 self.expect(&Token::Then)?;
2069 let result = self.parse_expr()?;
2070 whens.push((Box::new(condition), Box::new(result)));
2071 }
2072 let else_expr = if *self.peek() == Token::Else {
2073 self.advance();
2074 Some(Box::new(self.parse_expr()?))
2075 } else {
2076 None
2077 };
2078 self.expect(&Token::End)?;
2079 Ok(Expr::Case { whens, else_expr })
2080 }
2081 t => Err(ParseError::Syntax {
2082 message: format!("unexpected token in expression: {}", t.display_name()),
2083 }),
2084 }
2085 }
2086
2087 fn parse_create_link(&mut self) -> Result<Statement, ParseError> {
2095 self.expect(&Token::Link)?;
2096 let owner = self.expect_named_ident("link owner type")?;
2097 let name = match self.advance() {
2098 Token::DotIdent(n) => n,
2099 t => {
2100 return Err(ParseError::UnexpectedToken {
2101 expected: "`.<name>` after the owner type (link <Owner>.<name> -> ...)".into(),
2102 got: t.display_name(),
2103 })
2104 }
2105 };
2106 let (target, local_key, target_key) = self.parse_link_tail()?;
2107 Ok(Statement::CreateLink(CreateLinkExpr {
2108 owner,
2109 name,
2110 target,
2111 local_key,
2112 target_key,
2113 }))
2114 }
2115
2116 fn parse_link_tail(&mut self) -> Result<(String, String, String), ParseError> {
2119 self.expect(&Token::Arrow)?;
2120 let target = self.expect_named_ident("link target type")?;
2121 self.expect(&Token::On)?;
2122 let local_key = self.parse_link_column("link local key")?;
2123 self.expect(&Token::Eq)?;
2124 let target_key = self.parse_link_column("link target key")?;
2125 Ok((target, local_key, target_key))
2126 }
2127
2128 fn parse_link_column(&mut self, context: &str) -> Result<String, ParseError> {
2131 match self.advance() {
2132 Token::Ident(n) | Token::DotIdent(n) => Ok(n),
2133 t => Err(self.named_ident_error(context, &t)),
2134 }
2135 }
2136
2137 fn parse_alter_table(&mut self) -> Result<Statement, ParseError> {
2138 self.expect(&Token::Alter)?;
2139 let table = match self.advance() {
2140 Token::Ident(name) => name,
2141 t => {
2142 return Err(ParseError::UnexpectedToken {
2143 expected: "table name after alter".into(),
2144 got: t.display_name(),
2145 })
2146 }
2147 };
2148 match self.peek() {
2149 Token::Add => {
2150 self.advance();
2151 if *self.peek() == Token::Index {
2153 self.advance();
2154 let if_not_exists = self.parse_optional_if_not_exists();
2155 let target = self.parse_index_target("add index")?;
2156 return Ok(Statement::AlterTable(AlterTableExpr {
2157 table,
2158 action: AlterAction::AddIndex {
2159 target,
2160 if_not_exists,
2161 },
2162 }));
2163 }
2164 if *self.peek() == Token::Unique {
2166 self.advance();
2167 let if_not_exists = self.parse_optional_if_not_exists();
2168 let target = self.parse_index_target("add unique")?;
2169 return Ok(Statement::AlterTable(AlterTableExpr {
2170 table,
2171 action: AlterAction::AddUnique {
2172 target,
2173 if_not_exists,
2174 },
2175 }));
2176 }
2177 if *self.peek() == Token::Link {
2179 self.advance();
2180 let name = self.expect_named_ident("link name")?;
2181 let (target, local_key, target_key) = self.parse_link_tail()?;
2182 return Ok(Statement::AlterTable(AlterTableExpr {
2183 table,
2184 action: AlterAction::AddLink {
2185 name,
2186 target,
2187 local_key,
2188 target_key,
2189 },
2190 }));
2191 }
2192 if *self.peek() == Token::Column {
2194 self.advance();
2195 }
2196 let required = if *self.peek() == Token::Required {
2197 self.advance();
2198 true
2199 } else {
2200 false
2201 };
2202 let name = self.expect_named_ident("column name")?;
2203 self.expect(&Token::Colon)?;
2204 let type_name = match self.advance() {
2205 Token::Ident(n) => n,
2206 t => {
2207 return Err(ParseError::UnexpectedToken {
2208 expected: "type name".into(),
2209 got: t.display_name(),
2210 })
2211 }
2212 };
2213 Ok(Statement::AlterTable(AlterTableExpr {
2214 table,
2215 action: AlterAction::AddColumn {
2216 name,
2217 type_name,
2218 required,
2219 },
2220 }))
2221 }
2222 Token::Drop => {
2223 self.advance();
2224 if *self.peek() == Token::Index {
2225 self.advance();
2226 let if_exists = self.parse_optional_if_exists();
2227 let target = self.parse_index_target("drop index")?;
2228 return Ok(Statement::AlterTable(AlterTableExpr {
2229 table,
2230 action: AlterAction::DropIndex { target, if_exists },
2231 }));
2232 }
2233 if *self.peek() == Token::Column {
2235 self.advance();
2236 }
2237 let if_exists = self.parse_optional_if_exists();
2238 let name = self.expect_named_ident("column name")?;
2239 Ok(Statement::AlterTable(AlterTableExpr {
2240 table,
2241 action: AlterAction::DropColumn { name, if_exists },
2242 }))
2243 }
2244 t => Err(ParseError::UnexpectedToken {
2245 expected: "add or drop after alter <table>".into(),
2246 got: t.display_name(),
2247 }),
2248 }
2249 }
2250
2251 fn parse_index_target(&mut self, action: &str) -> Result<IndexTarget, ParseError> {
2256 match self.peek() {
2257 Token::DotIdent(_) => {
2258 if matches!(self.tokens.get(self.pos + 1), Some(Token::Arrow)) {
2259 return Err(ParseError::Syntax {
2260 message: format!(
2261 "JSON path index targets must be parenthesized after {action}; use `(.data->key)`"
2262 ),
2263 });
2264 }
2265 let Token::DotIdent(column) = self.advance() else {
2266 unreachable!("guarded by DotIdent match")
2267 };
2268 Ok(IndexTarget::Column(column))
2269 }
2270 Token::LParen => {
2271 self.advance();
2272 let expr = self.parse_expr().map_err(|error| match error {
2273 ParseError::NestingDepthExceeded { .. } => error,
2274 _ => ParseError::Syntax {
2275 message: format!(
2276 "invalid expression index target after {action}: expected an unqualified JSON path like `(.data->key)`"
2277 ),
2278 },
2279 })?;
2280 if *self.peek() != Token::RParen {
2281 return Err(ParseError::Syntax {
2282 message: format!(
2283 "invalid expression index target after {action}: only a direct JSON path is supported"
2284 ),
2285 });
2286 }
2287 self.advance();
2288
2289 match JsonPathIdentityV1::from_expr(&expr) {
2290 Some(identity) => identity.bind_table_local(None).map(IndexTarget::JsonPath).ok_or_else(|| {
2291 ParseError::Syntax {
2292 message: format!(
2293 "qualified JSON paths are not valid index targets after {action}; use an unqualified table-local path like `(.data->key)`"
2294 ),
2295 }
2296 }),
2297 None => match expr {
2298 Expr::Field(_) => Err(ParseError::Syntax {
2299 message: format!(
2300 "invalid expression index target after {action}: parentheses are reserved for a direct JSON path like `(.data->key)`; use `.column` for a stored column"
2301 ),
2302 }),
2303 Expr::QualifiedField { .. } => Err(ParseError::Syntax {
2304 message: format!(
2305 "qualified references are not valid index targets after {action}; use a table-local `.column` or `(.data->key)`"
2306 ),
2307 }),
2308 _ => Err(ParseError::Syntax {
2309 message: format!(
2310 "invalid expression index target after {action}: only a direct JSON path is supported"
2311 ),
2312 }),
2313 },
2314 }
2315 }
2316 token => Err(ParseError::UnexpectedToken {
2317 expected: format!(".<column> or parenthesized JSON path after {action}"),
2318 got: token.display_name(),
2319 }),
2320 }
2321 }
2322
2323 fn parse_drop_or_drop_view(&mut self) -> Result<Statement, ParseError> {
2325 self.expect(&Token::Drop)?;
2326 if *self.peek() == Token::View {
2327 self.advance(); let if_exists = self.parse_optional_if_exists();
2329 let name = match self.advance() {
2330 Token::Ident(name) => name,
2331 t => {
2332 return Err(ParseError::UnexpectedToken {
2333 expected: "view name after drop view".into(),
2334 got: t.display_name(),
2335 })
2336 }
2337 };
2338 return Ok(Statement::DropView(DropViewExpr { name, if_exists }));
2339 }
2340 let if_exists = self.parse_optional_if_exists();
2341 let table = match self.advance() {
2342 Token::Ident(name) => name,
2343 t => {
2344 return Err(ParseError::UnexpectedToken {
2345 expected: "table name after drop".into(),
2346 got: t.display_name(),
2347 })
2348 }
2349 };
2350 Ok(Statement::DropTable(DropTableExpr { table, if_exists }))
2351 }
2352
2353 fn parse_create_view(&mut self) -> Result<Statement, ParseError> {
2358 self.expect(&Token::Materialized)?;
2359 let name = match self.advance() {
2360 Token::Ident(name) => name,
2361 t => {
2362 return Err(ParseError::UnexpectedToken {
2363 expected: "view name after materialize".into(),
2364 got: t.display_name(),
2365 })
2366 }
2367 };
2368 self.expect(&Token::As)?;
2369 let query_start = self.pos;
2371 let source = match self.advance() {
2372 Token::Ident(s) => s,
2373 t => {
2374 return Err(ParseError::UnexpectedToken {
2375 expected: "source table name".into(),
2376 got: t.display_name(),
2377 })
2378 }
2379 };
2380 let query = self.parse_query_tail(source)?;
2381 let query_text = tokens_to_text(&self.tokens[query_start..self.pos]);
2383 Ok(Statement::CreateView(CreateViewExpr {
2384 name,
2385 query,
2386 query_text,
2387 }))
2388 }
2389
2390 fn maybe_parse_union(&mut self, left: Statement) -> Result<Statement, ParseError> {
2393 if *self.peek() != Token::Union {
2394 return Ok(left);
2395 }
2396 if !matches!(left, Statement::Query(_) | Statement::Union(_)) {
2397 return Err(ParseError::Syntax {
2398 message: "UNION requires a query on the left side".into(),
2399 });
2400 }
2401 self.advance(); let all = if let Token::Ident(s) = self.peek() {
2403 if s == "all" {
2404 self.advance();
2405 true
2406 } else {
2407 false
2408 }
2409 } else {
2410 false
2411 };
2412 let right = self.parse_single_query()?;
2414 let union = Statement::Union(UnionExpr {
2415 left: Box::new(left),
2416 right: Box::new(right),
2417 all,
2418 });
2419 self.maybe_parse_union(union)
2421 }
2422
2423 fn parse_single_query(&mut self) -> Result<Statement, ParseError> {
2425 match self.peek() {
2426 Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
2427 self.parse_aggregate_query()
2428 }
2429 Token::Ident(_) => self.parse_query_or_mutation(),
2430 _ => Err(ParseError::Syntax {
2431 message: format!(
2432 "expected query after UNION, got {}",
2433 self.peek().display_name()
2434 ),
2435 }),
2436 }
2437 }
2438
2439 fn parse_refresh_view(&mut self) -> Result<Statement, ParseError> {
2441 self.expect(&Token::Refresh)?;
2442 let name = match self.advance() {
2443 Token::Ident(name) => name,
2444 t => {
2445 return Err(ParseError::UnexpectedToken {
2446 expected: "view name after refresh".into(),
2447 got: t.display_name(),
2448 })
2449 }
2450 };
2451 Ok(Statement::RefreshView(RefreshViewExpr { name }))
2452 }
2453
2454 fn parse_create_type(&mut self) -> Result<Statement, ParseError> {
2455 self.expect(&Token::Type)?;
2456 let name = self.expect_named_ident("type name")?;
2457 let if_not_exists = self.parse_optional_if_not_exists();
2458 self.expect(&Token::LBrace)?;
2459 let mut fields = Vec::new();
2460 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
2461 let (mut required, mut unique, mut auto) = (false, false, false);
2466 loop {
2467 let is_modifier =
2468 matches!(self.peek(), Token::Required | Token::Unique | Token::Auto)
2469 && !matches!(self.tokens.get(self.pos + 1), Some(Token::Colon));
2470 if !is_modifier {
2471 break;
2472 }
2473 match self.advance() {
2474 Token::Required => required = true,
2475 Token::Unique => unique = true,
2476 Token::Auto => auto = true,
2477 _ => unreachable!("guarded by is_modifier"),
2478 }
2479 }
2480 let field_name = self.expect_named_ident("field name")?;
2481 self.expect(&Token::Colon)?;
2482 let type_name = match self.advance() {
2483 Token::Ident(n) => n,
2484 t => {
2485 return Err(ParseError::UnexpectedToken {
2486 expected: "type name".into(),
2487 got: t.display_name(),
2488 })
2489 }
2490 };
2491 let default = if *self.peek() == Token::Default {
2494 self.advance();
2495 Some(self.parse_default_literal()?)
2496 } else {
2497 None
2498 };
2499 fields.push(FieldDef {
2500 name: field_name,
2501 type_name,
2502 required,
2503 unique,
2504 default,
2505 auto,
2506 });
2507 if *self.peek() == Token::Comma {
2508 self.advance();
2509 }
2510 }
2511 self.expect(&Token::RBrace)?;
2512 Ok(Statement::CreateType(CreateTypeExpr {
2513 name,
2514 fields,
2515 if_not_exists,
2516 }))
2517 }
2518
2519 fn parse_schema(&mut self) -> Result<Statement, ParseError> {
2522 self.expect(&Token::Schema)?;
2523 if let Token::Ident(name) = self.peek() {
2524 if name == "links" {
2525 self.advance();
2526 return Ok(Statement::ListLinks);
2527 }
2528 let table = self.expect_named_ident("type name")?;
2529 return Ok(Statement::Describe(table));
2530 }
2531 Ok(Statement::ListTypes)
2532 }
2533
2534 fn parse_describe(&mut self) -> Result<Statement, ParseError> {
2536 self.expect(&Token::Describe)?;
2537 let table = self.expect_named_ident("type name")?;
2538 Ok(Statement::Describe(table))
2539 }
2540
2541 fn parse_default_literal(&mut self) -> Result<Literal, ParseError> {
2545 match self.advance() {
2546 Token::IntLit(v) => Ok(Literal::Int(v)),
2547 Token::FloatLit(v) => Ok(Literal::Float(v)),
2548 Token::StringLit(v) => Ok(Literal::String(v)),
2549 Token::BoolLit(v) => Ok(Literal::Bool(v)),
2550 t => Err(ParseError::UnexpectedToken {
2551 expected: "literal default value".into(),
2552 got: t.display_name(),
2553 }),
2554 }
2555 }
2556}
2557
2558fn qualify_bare_fields(expr: Expr, alias: &str) -> Expr {
2565 let recur = |e: Expr| Box::new(qualify_bare_fields(e, alias));
2566 match expr {
2567 Expr::Field(field) => Expr::QualifiedField {
2568 qualifier: alias.to_string(),
2569 field,
2570 },
2571 Expr::BinaryOp(l, op, r) => Expr::BinaryOp(recur(*l), op, recur(*r)),
2572 Expr::UnaryOp(op, inner) => Expr::UnaryOp(op, recur(*inner)),
2573 Expr::Coalesce(l, r) => Expr::Coalesce(recur(*l), recur(*r)),
2574 Expr::Cast(inner, ty) => Expr::Cast(recur(*inner), ty),
2575 Expr::ScalarFunc(func, args) => Expr::ScalarFunc(
2576 func,
2577 args.into_iter()
2578 .map(|a| qualify_bare_fields(a, alias))
2579 .collect(),
2580 ),
2581 Expr::InList {
2582 expr,
2583 list,
2584 negated,
2585 } => Expr::InList {
2586 expr: recur(*expr),
2587 list: list
2588 .into_iter()
2589 .map(|a| qualify_bare_fields(a, alias))
2590 .collect(),
2591 negated,
2592 },
2593 Expr::Case { whens, else_expr } => Expr::Case {
2594 whens: whens
2595 .into_iter()
2596 .map(|(c, r)| (recur(*c), recur(*r)))
2597 .collect(),
2598 else_expr: else_expr.map(|e| recur(*e)),
2599 },
2600 Expr::JsonPath { base, segments } => Expr::JsonPath {
2601 base: recur(*base),
2602 segments,
2603 },
2604 other => other,
2606 }
2607}
2608
2609fn tokens_to_text(tokens: &[Token]) -> String {
2613 let mut out = String::with_capacity(64);
2614 for tok in tokens {
2615 if !out.is_empty() && !matches!(tok, Token::Eof) {
2616 out.push(' ');
2617 }
2618 match tok {
2619 Token::Ident(s) => out.push_str(s),
2620 Token::DotIdent(s) => {
2621 out.push('.');
2622 out.push_str(s);
2623 }
2624 Token::IntLit(v) => out.push_str(&v.to_string()),
2625 Token::FloatLit(v) => out.push_str(&v.to_string()),
2626 Token::StringLit(s) => {
2627 out.push('"');
2628 out.push_str(s);
2629 out.push('"');
2630 }
2631 Token::BoolLit(v) => out.push_str(if *v { "true" } else { "false" }),
2632 Token::Param(s) => {
2633 out.push('$');
2634 out.push_str(s);
2635 }
2636 Token::Type => out.push_str("type"),
2637 Token::Filter => out.push_str("filter"),
2638 Token::Order => out.push_str("order"),
2639 Token::Limit => out.push_str("limit"),
2640 Token::Offset => out.push_str("offset"),
2641 Token::Insert => out.push_str("insert"),
2642 Token::Update => out.push_str("update"),
2643 Token::Delete => out.push_str("delete"),
2644 Token::Upsert => out.push_str("upsert"),
2645 Token::Returning => out.push_str("returning"),
2646 Token::Conflict => out.push_str("conflict"),
2647 Token::Select => out.push_str("select"),
2648 Token::Required => out.push_str("required"),
2649 Token::Default => out.push_str("default"),
2650 Token::Auto => out.push_str("auto"),
2651 Token::Multi => out.push_str("multi"),
2652 Token::Link => out.push_str("link"),
2653 Token::Index => out.push_str("index"),
2654 Token::Unique => out.push_str("unique"),
2655 Token::On => out.push_str("on"),
2656 Token::Asc => out.push_str("asc"),
2657 Token::Desc => out.push_str("desc"),
2658 Token::And => out.push_str("and"),
2659 Token::Or => out.push_str("or"),
2660 Token::Not => out.push_str("not"),
2661 Token::Exists => out.push_str("exists"),
2662 Token::Let => out.push_str("let"),
2663 Token::As => out.push_str("as"),
2664 Token::Match => out.push_str("match"),
2665 Token::Group => out.push_str("group"),
2666 Token::Join => out.push_str("join"),
2667 Token::Inner => out.push_str("inner"),
2668 Token::LeftKw => out.push_str("left"),
2669 Token::RightKw => out.push_str("right"),
2670 Token::Outer => out.push_str("outer"),
2671 Token::Cross => out.push_str("cross"),
2672 Token::Transaction => out.push_str("transaction"),
2673 Token::Begin => out.push_str("begin"),
2674 Token::Commit => out.push_str("commit"),
2675 Token::Rollback => out.push_str("rollback"),
2676 Token::View => out.push_str("view"),
2677 Token::Materialized => out.push_str("materialized"),
2678 Token::Refresh => out.push_str("refresh"),
2679 Token::Union => out.push_str("union"),
2680 Token::Having => out.push_str("having"),
2681 Token::Distinct => out.push_str("distinct"),
2682 Token::In => out.push_str("in"),
2683 Token::Between => out.push_str("between"),
2684 Token::Like => out.push_str("like"),
2685 Token::Count => out.push_str("count"),
2686 Token::Avg => out.push_str("avg"),
2687 Token::Sum => out.push_str("sum"),
2688 Token::Raw => out.push_str("raw"),
2689 Token::Min => out.push_str("min"),
2690 Token::Max => out.push_str("max"),
2691 Token::Is => out.push_str("is"),
2692 Token::Null => out.push_str("null"),
2693 Token::Upper => out.push_str("upper"),
2694 Token::Lower => out.push_str("lower"),
2695 Token::Length => out.push_str("length"),
2696 Token::Trim => out.push_str("trim"),
2697 Token::Substring => out.push_str("substring"),
2698 Token::Concat => out.push_str("concat"),
2699 Token::Abs => out.push_str("abs"),
2700 Token::Round => out.push_str("round"),
2701 Token::Ceil => out.push_str("ceil"),
2702 Token::Floor => out.push_str("floor"),
2703 Token::Sqrt => out.push_str("sqrt"),
2704 Token::Pow => out.push_str("pow"),
2705 Token::Now => out.push_str("now"),
2706 Token::Extract => out.push_str("extract"),
2707 Token::DateAdd => out.push_str("date_add"),
2708 Token::DateDiff => out.push_str("date_diff"),
2709 Token::JsonType => out.push_str("json_type"),
2710 Token::JsonText => out.push_str("json_text"),
2711 Token::Cast => out.push_str("cast"),
2712 Token::Case => out.push_str("case"),
2713 Token::When => out.push_str("when"),
2714 Token::Then => out.push_str("then"),
2715 Token::Else => out.push_str("else"),
2716 Token::End => out.push_str("end"),
2717 Token::Over => out.push_str("over"),
2718 Token::Partition => out.push_str("partition"),
2719 Token::RowNumber => out.push_str("row_number"),
2720 Token::Rank => out.push_str("rank"),
2721 Token::DenseRank => out.push_str("dense_rank"),
2722 Token::Alter => out.push_str("alter"),
2723 Token::Drop => out.push_str("drop"),
2724 Token::Add => out.push_str("add"),
2725 Token::Column => out.push_str("column"),
2726 Token::Eq => out.push('='),
2727 Token::Neq => out.push_str("!="),
2728 Token::Lt => out.push('<'),
2729 Token::Gt => out.push('>'),
2730 Token::Lte => out.push_str("<="),
2731 Token::Gte => out.push_str(">="),
2732 Token::Assign => out.push_str(":="),
2733 Token::Arrow => out.push_str("->"),
2734 Token::Pipe => out.push('|'),
2735 Token::Coalesce => out.push_str("??"),
2736 Token::Plus => out.push('+'),
2737 Token::Minus => out.push('-'),
2738 Token::Star => out.push('*'),
2739 Token::Slash => out.push('/'),
2740 Token::LBrace => out.push('{'),
2741 Token::RBrace => out.push('}'),
2742 Token::LParen => out.push('('),
2743 Token::RParen => out.push(')'),
2744 Token::Comma => out.push(','),
2745 Token::Colon => out.push(':'),
2746 Token::Dot => out.push('.'),
2747 Token::Explain => out.push_str("explain"),
2748 Token::Schema => out.push_str("schema"),
2749 Token::Describe => out.push_str("describe"),
2750 Token::Eof => {}
2751 }
2752 }
2753 out
2754}
2755
2756#[cfg(test)]
2757mod tests {
2758 use super::*;
2759 #[test]
2760 fn test_parse_simple_query() {
2761 let stmt = parse("User").unwrap();
2762 match stmt {
2763 Statement::Query(q) => {
2764 assert_eq!(q.source, "User");
2765 assert!(q.filter.is_none());
2766 assert!(q.projection.is_none());
2767 }
2768 _ => panic!("expected query"),
2769 }
2770 }
2771
2772 #[test]
2773 fn test_parse_filter() {
2774 let stmt = parse("User filter .age > 30").unwrap();
2775 match stmt {
2776 Statement::Query(q) => {
2777 assert_eq!(q.source, "User");
2778 assert!(q.filter.is_some());
2779 }
2780 _ => panic!("expected query"),
2781 }
2782 }
2783
2784 #[test]
2785 fn test_parse_projection() {
2786 let stmt = parse("User { name, email }").unwrap();
2787 match stmt {
2788 Statement::Query(q) => {
2789 let proj = q.projection.unwrap();
2790 assert_eq!(proj.len(), 2);
2791 }
2792 _ => panic!("expected query"),
2793 }
2794 }
2795
2796 #[test]
2797 fn test_bare_dotted_path_projection_is_a_parse_error() {
2798 for q in [
2802 "Order { .user.name }",
2803 "Order { .id, uname: .user.name }",
2804 "Order { uname: .user.company.name }",
2805 ] {
2806 let err = parse(q).unwrap_err();
2807 let msg = err.to_string();
2808 assert!(
2809 msg.contains("alias the table"),
2810 "`{q}` should error with alias guidance, got: {msg}"
2811 );
2812 }
2813 }
2814
2815 #[test]
2816 fn test_aliased_scalar_link_path_parses_as_one_field() {
2817 let stmt = parse("Order as o { uname: o.user.name }").unwrap();
2818 match stmt {
2819 Statement::Query(q) => {
2820 let proj = q.projection.unwrap();
2821 assert_eq!(proj.len(), 1);
2822 match &proj[0].expr {
2823 Expr::LinkPath {
2824 outer_alias,
2825 links,
2826 column,
2827 } => {
2828 assert_eq!(outer_alias, "o");
2829 assert_eq!(links, &["user".to_string()]);
2830 assert_eq!(column, "name");
2831 }
2832 other => panic!("expected LinkPath, got {other:?}"),
2833 }
2834 }
2835 _ => panic!("expected query"),
2836 }
2837 }
2838
2839 #[test]
2840 fn test_parse_filter_order_limit() {
2841 let stmt = parse("User filter .age > 30 order .name desc limit 10").unwrap();
2842 match stmt {
2843 Statement::Query(q) => {
2844 assert!(q.filter.is_some());
2845 let order = q.order.unwrap();
2846 assert_eq!(order.keys.len(), 1);
2847 assert_eq!(order.keys[0].expr, Expr::Field("name".into()));
2848 assert!(order.keys[0].descending);
2849 assert!(q.limit.is_some());
2850 }
2851 _ => panic!("expected query"),
2852 }
2853 }
2854
2855 #[test]
2856 fn test_parse_insert() {
2857 let stmt = parse(r#"insert User { name := "Alice", age := 30 }"#).unwrap();
2858 match stmt {
2859 Statement::Insert(ins) => {
2860 assert_eq!(ins.target, "User");
2861 assert_eq!(ins.rows.len(), 1);
2862 assert_eq!(ins.rows[0].len(), 2);
2863 assert_eq!(ins.rows[0][0].field, "name");
2864 assert_eq!(ins.rows[0][1].field, "age");
2865 }
2866 _ => panic!("expected insert"),
2867 }
2868 }
2869
2870 #[test]
2871 fn test_parse_insert_multi_row() {
2872 let stmt =
2873 parse(r#"insert User { name := "Alice", age := 30 }, { name := "Bob", age := 25 }, { name := "Cy" }"#)
2874 .unwrap();
2875 match stmt {
2876 Statement::Insert(ins) => {
2877 assert_eq!(ins.target, "User");
2878 assert_eq!(ins.rows.len(), 3);
2879 assert_eq!(ins.rows[0].len(), 2);
2880 assert_eq!(ins.rows[1][0].field, "name");
2881 assert_eq!(ins.rows[2].len(), 1);
2882 assert_eq!(ins.rows[2][0].field, "name");
2883 }
2884 _ => panic!("expected insert"),
2885 }
2886 }
2887
2888 #[test]
2889 fn test_parse_update() {
2890 let stmt = parse(r#"User filter .email = "alice@ex.com" update { age := 31 }"#).unwrap();
2891 match stmt {
2892 Statement::UpdateQuery(upd) => {
2893 assert_eq!(upd.source, "User");
2894 assert!(upd.filter.is_some());
2895 assert_eq!(upd.assignments.len(), 1);
2896 assert!(!upd.returning);
2897 }
2898 _ => panic!("expected update"),
2899 }
2900 }
2901
2902 #[test]
2903 fn test_parse_update_returning() {
2904 let stmt = parse(r#"User filter .name = "Alice" update { age := 31 } returning"#).unwrap();
2905 match stmt {
2906 Statement::UpdateQuery(upd) => assert!(upd.returning),
2907 _ => panic!("expected update"),
2908 }
2909 }
2910
2911 #[test]
2912 fn test_parse_delete() {
2913 let stmt = parse("User filter .age < 18 delete").unwrap();
2914 match stmt {
2915 Statement::DeleteQuery(del) => {
2916 assert_eq!(del.source, "User");
2917 assert!(del.filter.is_some());
2918 assert!(!del.returning);
2919 }
2920 _ => panic!("expected delete"),
2921 }
2922 }
2923
2924 #[test]
2925 fn test_parse_delete_returning() {
2926 let stmt = parse("User filter .age < 18 delete returning").unwrap();
2927 match stmt {
2928 Statement::DeleteQuery(del) => assert!(del.returning),
2929 _ => panic!("expected delete"),
2930 }
2931 }
2932
2933 #[test]
2934 fn test_parse_count() {
2935 let stmt = parse("count(User)").unwrap();
2936 match stmt {
2937 Statement::Query(q) => {
2938 let agg = q.aggregation.unwrap();
2939 assert_eq!(agg.function, AggFunc::Count);
2940 assert!(q.filter.is_none());
2941 }
2942 _ => panic!("expected query with aggregation"),
2943 }
2944 }
2945
2946 #[test]
2947 fn test_parse_count_with_filter() {
2948 let stmt = parse("count(User filter .age > 30)").unwrap();
2951 match stmt {
2952 Statement::Query(q) => {
2953 assert_eq!(q.source, "User");
2954 let agg = q.aggregation.unwrap();
2955 assert_eq!(agg.function, AggFunc::Count);
2956 assert!(q.filter.is_some(), "filter should have been parsed");
2957 }
2958 _ => panic!("expected query with aggregation"),
2959 }
2960 }
2961
2962 #[test]
2963 fn test_parse_count_with_filter_and_limit() {
2964 let stmt = parse("count(User filter .age > 30 limit 100)").unwrap();
2965 match stmt {
2966 Statement::Query(q) => {
2967 assert_eq!(q.source, "User");
2968 assert!(q.filter.is_some());
2969 assert!(q.limit.is_some());
2970 assert_eq!(q.aggregation.unwrap().function, AggFunc::Count);
2971 }
2972 _ => panic!("expected query with aggregation"),
2973 }
2974 }
2975
2976 #[test]
2977 fn test_parse_create_type() {
2978 let stmt = parse("type User { required name: str, age: int }").unwrap();
2979 match stmt {
2980 Statement::CreateType(ct) => {
2981 assert_eq!(ct.name, "User");
2982 assert_eq!(ct.fields.len(), 2);
2983 assert!(ct.fields[0].required);
2984 assert!(!ct.fields[1].required);
2985 }
2986 _ => panic!("expected create type"),
2987 }
2988 }
2989
2990 #[test]
2991 fn test_parse_sum_with_field_projection() {
2992 let stmt = parse("sum(User filter .age > 30 { .age })").unwrap();
2995 match stmt {
2996 Statement::Query(q) => {
2997 let agg = q.aggregation.expect("aggregate");
2998 assert_eq!(agg.function, AggFunc::Sum);
2999 assert_eq!(agg.argument, Some(Expr::Field("age".into())));
3000 assert!(
3001 q.projection.is_none(),
3002 "projection should be lifted into agg.field"
3003 );
3004 }
3005 _ => panic!("expected query"),
3006 }
3007 }
3008
3009 #[test]
3010 fn test_parse_raw_aggregate_modes() {
3011 let Statement::Query(top_level) = parse("sum(raw User { .age })").unwrap() else {
3012 panic!("expected query");
3013 };
3014 assert_eq!(top_level.aggregation.unwrap().mode, AggregateMode::Raw);
3015
3016 let Statement::Query(grouped) = parse("User group .dept { total: sum(raw .age) }").unwrap()
3017 else {
3018 panic!("expected query");
3019 };
3020 assert!(matches!(
3021 grouped.projection.unwrap()[0].expr,
3022 Expr::FunctionCall(AggFunc::Sum, _, AggregateMode::Raw)
3023 ));
3024 }
3025
3026 #[test]
3027 fn test_parse_avg_min_max_with_field() {
3028 for (src, expected) in [
3029 ("avg(User { .age })", AggFunc::Avg),
3030 ("min(User { .age })", AggFunc::Min),
3031 ("max(User { .age })", AggFunc::Max),
3032 ] {
3033 let stmt = parse(src).unwrap();
3034 match stmt {
3035 Statement::Query(q) => {
3036 let agg = q.aggregation.unwrap();
3037 assert_eq!(agg.function, expected, "func mismatch for {src}");
3038 assert_eq!(
3039 agg.argument,
3040 Some(Expr::Field("age".into())),
3041 "field mismatch for {src}"
3042 );
3043 assert!(
3044 q.projection.is_none(),
3045 "projection should be cleared for {src}"
3046 );
3047 }
3048 _ => panic!("expected query for {src}"),
3049 }
3050 }
3051 }
3052
3053 #[test]
3054 fn test_parse_count_lifts_projection_into_argument() {
3055 let stmt = parse("count(User { .age })").unwrap();
3060 match stmt {
3061 Statement::Query(q) => {
3062 let agg = q.aggregation.unwrap();
3063 assert_eq!(agg.function, AggFunc::Count);
3064 assert_eq!(agg.argument, Some(Expr::Field("age".into())));
3065 assert!(q.projection.is_none(), "projection should be lifted");
3066 }
3067 _ => panic!("expected query"),
3068 }
3069 }
3070
3071 #[test]
3072 fn test_parse_count_without_projection_has_no_argument() {
3073 let stmt = parse("count(User)").unwrap();
3075 match stmt {
3076 Statement::Query(q) => {
3077 let agg = q.aggregation.unwrap();
3078 assert_eq!(agg.function, AggFunc::Count);
3079 assert!(agg.argument.is_none());
3080 }
3081 _ => panic!("expected query"),
3082 }
3083 }
3084
3085 #[test]
3090 fn test_parse_source_alias() {
3091 let stmt = parse("User as u filter u.age > 30").unwrap();
3092 match stmt {
3093 Statement::Query(q) => {
3094 assert_eq!(q.source, "User");
3095 assert_eq!(q.alias.as_deref(), Some("u"));
3096 assert!(q.joins.is_empty());
3097 match q.filter.unwrap() {
3098 Expr::BinaryOp(l, BinOp::Gt, _) => match *l {
3099 Expr::QualifiedField { qualifier, field } => {
3100 assert_eq!(qualifier, "u");
3101 assert_eq!(field, "age");
3102 }
3103 other => panic!("expected qualified field, got {other:?}"),
3104 },
3105 other => panic!("expected >, got {other:?}"),
3106 }
3107 }
3108 _ => panic!("expected query"),
3109 }
3110 }
3111
3112 #[test]
3113 fn test_parse_inner_join_on() {
3114 let stmt = parse("User as u inner join Order as o on u.id = o.user_id").unwrap();
3115 match stmt {
3116 Statement::Query(q) => {
3117 assert_eq!(q.source, "User");
3118 assert_eq!(q.alias.as_deref(), Some("u"));
3119 assert_eq!(q.joins.len(), 1);
3120 let j = &q.joins[0];
3121 assert_eq!(j.kind, JoinKind::Inner);
3122 assert_eq!(j.source, "Order");
3123 assert_eq!(j.alias.as_deref(), Some("o"));
3124 let on = j.on.as_ref().expect("on clause");
3125 match on {
3126 Expr::BinaryOp(l, BinOp::Eq, r) => {
3127 assert!(matches!(**l, Expr::QualifiedField { .. }));
3128 assert!(matches!(**r, Expr::QualifiedField { .. }));
3129 }
3130 other => panic!("expected eq, got {other:?}"),
3131 }
3132 }
3133 _ => panic!("expected query"),
3134 }
3135 }
3136
3137 #[test]
3138 fn test_parse_bare_join_defaults_to_inner() {
3139 let stmt = parse("User join Order on User.id = Order.user_id").unwrap();
3140 match stmt {
3141 Statement::Query(q) => {
3142 assert_eq!(q.joins.len(), 1);
3143 assert_eq!(q.joins[0].kind, JoinKind::Inner);
3144 }
3145 _ => panic!("expected query"),
3146 }
3147 }
3148
3149 #[test]
3150 fn test_parse_left_outer_join() {
3151 let stmt = parse("User as u left outer join Order as o on u.id = o.user_id").unwrap();
3152 match stmt {
3153 Statement::Query(q) => {
3154 assert_eq!(q.joins.len(), 1);
3155 assert_eq!(q.joins[0].kind, JoinKind::LeftOuter);
3156 }
3157 _ => panic!("expected query"),
3158 }
3159 }
3160
3161 #[test]
3162 fn test_parse_left_join_without_outer_keyword() {
3163 let stmt = parse("User as u left join Order as o on u.id = o.user_id").unwrap();
3165 match stmt {
3166 Statement::Query(q) => {
3167 assert_eq!(q.joins[0].kind, JoinKind::LeftOuter);
3168 }
3169 _ => panic!("expected query"),
3170 }
3171 }
3172
3173 #[test]
3174 fn test_parse_right_join() {
3175 let stmt = parse("User as u right join Order as o on u.id = o.user_id").unwrap();
3176 match stmt {
3177 Statement::Query(q) => {
3178 assert_eq!(q.joins[0].kind, JoinKind::RightOuter);
3179 }
3180 _ => panic!("expected query"),
3181 }
3182 }
3183
3184 #[test]
3185 fn test_parse_cross_join_has_no_on() {
3186 let stmt = parse("User cross join Order").unwrap();
3187 match stmt {
3188 Statement::Query(q) => {
3189 assert_eq!(q.joins[0].kind, JoinKind::Cross);
3190 assert!(q.joins[0].on.is_none());
3191 }
3192 _ => panic!("expected query"),
3193 }
3194 }
3195
3196 #[test]
3197 fn test_parse_multi_join_chain() {
3198 let stmt = parse(
3199 "User as u join Order as o on u.id = o.user_id \
3200 join Product as p on o.product_id = p.id",
3201 )
3202 .unwrap();
3203 match stmt {
3204 Statement::Query(q) => {
3205 assert_eq!(q.joins.len(), 2);
3206 assert_eq!(q.joins[0].source, "Order");
3207 assert_eq!(q.joins[1].source, "Product");
3208 }
3209 _ => panic!("expected query"),
3210 }
3211 }
3212
3213 #[test]
3214 fn test_parse_join_with_filter_tail() {
3215 let stmt = parse(
3217 "User as u join Order as o on u.id = o.user_id \
3218 filter o.total > 100 order .name limit 10",
3219 )
3220 .unwrap();
3221 match stmt {
3222 Statement::Query(q) => {
3223 assert_eq!(q.joins.len(), 1);
3224 assert!(q.filter.is_some());
3225 assert!(q.order.is_some());
3226 assert!(q.limit.is_some());
3227 }
3228 _ => panic!("expected query"),
3229 }
3230 }
3231
3232 #[test]
3233 fn test_parse_join_requires_on_for_inner() {
3234 let err = parse("User join Order").unwrap_err();
3236 assert!(
3237 err.message().contains("on"),
3238 "expected on-clause error, got {:?}",
3239 err.message()
3240 );
3241 }
3242
3243 #[test]
3244 fn test_parse_update_on_joined_query_errors() {
3245 let err =
3248 parse("User as u join Order as o on u.id = o.user_id update { age := 1 }").unwrap_err();
3249 assert!(err.message().contains("update"));
3250 }
3251
3252 #[test]
3253 fn test_parse_delete_on_joined_query_errors() {
3254 let err = parse("User as u join Order as o on u.id = o.user_id delete").unwrap_err();
3255 assert!(err.message().contains("delete"));
3256 }
3257
3258 #[test]
3261 fn test_parse_distinct() {
3262 let stmt = parse("User distinct { .name }").unwrap();
3263 match stmt {
3264 Statement::Query(q) => {
3265 assert!(q.distinct);
3266 assert!(q.projection.is_some());
3267 }
3268 _ => panic!("expected query"),
3269 }
3270 }
3271
3272 #[test]
3273 fn test_parse_in_list() {
3274 let stmt = parse(r#"User filter .name in ("Alice", "Bob")"#).unwrap();
3275 match stmt {
3276 Statement::Query(q) => match q.filter.unwrap() {
3277 Expr::InList {
3278 expr,
3279 list,
3280 negated,
3281 } => {
3282 assert!(!negated);
3283 assert!(matches!(*expr, Expr::Field(f) if f == "name"));
3284 assert_eq!(list.len(), 2);
3285 }
3286 other => panic!("expected InList, got {other:?}"),
3287 },
3288 _ => panic!("expected query"),
3289 }
3290 }
3291
3292 #[test]
3293 fn test_parse_not_in_list() {
3294 let stmt = parse("User filter .age not in (1, 2, 3)").unwrap();
3295 match stmt {
3296 Statement::Query(q) => match q.filter.unwrap() {
3297 Expr::InList { negated, list, .. } => {
3298 assert!(negated);
3299 assert_eq!(list.len(), 3);
3300 }
3301 other => panic!("expected InList, got {other:?}"),
3302 },
3303 _ => panic!("expected query"),
3304 }
3305 }
3306
3307 #[test]
3308 fn test_parse_between() {
3309 let stmt = parse("User filter .age between 10 and 20").unwrap();
3311 match stmt {
3312 Statement::Query(q) => {
3313 match q.filter.unwrap() {
3314 Expr::BinaryOp(_, BinOp::And, _) => {} other => panic!("expected And (desugared between), got {other:?}"),
3316 }
3317 }
3318 _ => panic!("expected query"),
3319 }
3320 }
3321
3322 #[test]
3323 fn test_parse_not_between() {
3324 let stmt = parse("User filter .age not between 10 and 20").unwrap();
3326 match stmt {
3327 Statement::Query(q) => {
3328 match q.filter.unwrap() {
3329 Expr::BinaryOp(_, BinOp::Or, _) => {} other => panic!("expected Or (desugared not between), got {other:?}"),
3331 }
3332 }
3333 _ => panic!("expected query"),
3334 }
3335 }
3336
3337 #[test]
3338 fn test_parse_like() {
3339 let stmt = parse(r#"User filter .name like "A%""#).unwrap();
3340 match stmt {
3341 Statement::Query(q) => match q.filter.unwrap() {
3342 Expr::BinaryOp(l, BinOp::Like, r) => {
3343 assert!(matches!(*l, Expr::Field(f) if f == "name"));
3344 assert!(matches!(*r, Expr::Literal(Literal::String(s)) if s == "A%"));
3345 }
3346 other => panic!("expected Like, got {other:?}"),
3347 },
3348 _ => panic!("expected query"),
3349 }
3350 }
3351
3352 #[test]
3353 fn test_parse_not_like() {
3354 let stmt = parse(r#"User filter .name not like "A%""#).unwrap();
3355 match stmt {
3356 Statement::Query(q) => match q.filter.unwrap() {
3357 Expr::UnaryOp(UnaryOp::Not, inner) => {
3358 assert!(matches!(*inner, Expr::BinaryOp(_, BinOp::Like, _)));
3359 }
3360 other => panic!("expected Not(Like), got {other:?}"),
3361 },
3362 _ => panic!("expected query"),
3363 }
3364 }
3365
3366 #[test]
3369 fn test_parse_group_by_single_key() {
3370 let stmt = parse("User group .status { .status, n: count(.name) }").unwrap();
3371 match stmt {
3372 Statement::Query(q) => {
3373 let gb = q.group_by.unwrap();
3374 assert_eq!(
3375 gb.keys,
3376 vec![GroupKey {
3377 expr: Expr::Field("status".into()),
3378 output_name: "status".into(),
3379 }]
3380 );
3381 assert!(gb.having.is_none());
3382 let proj = q.projection.unwrap();
3383 assert_eq!(proj.len(), 2);
3384 assert!(matches!(
3385 &proj[1].expr,
3386 Expr::FunctionCall(AggFunc::Count, _, _)
3387 ));
3388 assert_eq!(proj[1].alias.as_deref(), Some("n"));
3389 }
3390 _ => panic!("expected query"),
3391 }
3392 }
3393
3394 #[test]
3395 fn test_parse_group_by_multi_key() {
3396 let stmt = parse("User group .status, .age { .status, .age }").unwrap();
3397 match stmt {
3398 Statement::Query(q) => {
3399 let gb = q.group_by.unwrap();
3400 assert_eq!(
3401 gb.keys,
3402 vec![
3403 GroupKey {
3404 expr: Expr::Field("status".into()),
3405 output_name: "status".into(),
3406 },
3407 GroupKey {
3408 expr: Expr::Field("age".into()),
3409 output_name: "age".into(),
3410 }
3411 ]
3412 );
3413 }
3414 _ => panic!("expected query"),
3415 }
3416 }
3417
3418 #[test]
3419 fn test_parse_group_by_having() {
3420 let stmt = parse("User group .status having count(.name) > 1 { .status }").unwrap();
3421 match stmt {
3422 Statement::Query(q) => {
3423 let gb = q.group_by.unwrap();
3424 assert_eq!(
3425 gb.keys,
3426 vec![GroupKey {
3427 expr: Expr::Field("status".into()),
3428 output_name: "status".into(),
3429 }]
3430 );
3431 assert!(gb.having.is_some());
3432 match gb.having.unwrap() {
3434 Expr::BinaryOp(l, BinOp::Gt, _) => {
3435 assert!(matches!(*l, Expr::FunctionCall(AggFunc::Count, _, _)));
3436 }
3437 other => panic!("expected BinaryOp, got {other:?}"),
3438 }
3439 }
3440 _ => panic!("expected query"),
3441 }
3442 }
3443
3444 #[test]
3445 fn test_parse_aggregate_in_projection() {
3446 let stmt = parse("User group .status { .status, count(.name), sum(.age) }").unwrap();
3448 match stmt {
3449 Statement::Query(q) => {
3450 let proj = q.projection.unwrap();
3451 assert_eq!(proj.len(), 3);
3452 assert!(matches!(
3453 &proj[1].expr,
3454 Expr::FunctionCall(AggFunc::Count, _, _)
3455 ));
3456 assert!(matches!(
3457 &proj[2].expr,
3458 Expr::FunctionCall(AggFunc::Sum, _, _)
3459 ));
3460 }
3461 _ => panic!("expected query"),
3462 }
3463 }
3464
3465 #[test]
3466 fn test_parse_aggregate_in_aliased_projection() {
3467 let stmt = parse("User group .status { .status, total: count(.name), average: avg(.age) }")
3468 .unwrap();
3469 match stmt {
3470 Statement::Query(q) => {
3471 let proj = q.projection.unwrap();
3472 assert_eq!(proj[1].alias.as_deref(), Some("total"));
3473 assert!(matches!(
3474 &proj[1].expr,
3475 Expr::FunctionCall(AggFunc::Count, _, _)
3476 ));
3477 assert_eq!(proj[2].alias.as_deref(), Some("average"));
3478 assert!(matches!(
3479 &proj[2].expr,
3480 Expr::FunctionCall(AggFunc::Avg, _, _)
3481 ));
3482 }
3483 _ => panic!("expected query"),
3484 }
3485 }
3486
3487 #[test]
3490 fn test_parse_is_null() {
3491 let stmt = parse("User filter .age is null").unwrap();
3492 match stmt {
3493 Statement::Query(q) => {
3494 let filter = q.filter.unwrap();
3495 assert_eq!(
3496 filter,
3497 Expr::UnaryOp(UnaryOp::IsNull, Box::new(Expr::Field("age".into())))
3498 );
3499 }
3500 _ => panic!("expected query"),
3501 }
3502 }
3503
3504 #[test]
3505 fn test_parse_is_not_null() {
3506 let stmt = parse("User filter .age is not null").unwrap();
3507 match stmt {
3508 Statement::Query(q) => {
3509 let filter = q.filter.unwrap();
3510 assert_eq!(
3511 filter,
3512 Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(Expr::Field("age".into())))
3513 );
3514 }
3515 _ => panic!("expected query"),
3516 }
3517 }
3518
3519 #[test]
3520 fn test_parse_eq_null_desugars_to_is_null() {
3521 let stmt = parse("User filter .age = null").unwrap();
3522 match stmt {
3523 Statement::Query(q) => {
3524 let filter = q.filter.unwrap();
3525 assert_eq!(
3526 filter,
3527 Expr::UnaryOp(UnaryOp::IsNull, Box::new(Expr::Field("age".into())))
3528 );
3529 }
3530 _ => panic!("expected query"),
3531 }
3532 }
3533
3534 #[test]
3535 fn test_parse_neq_null_desugars_to_is_not_null() {
3536 let stmt = parse("User filter .age != null").unwrap();
3537 match stmt {
3538 Statement::Query(q) => {
3539 let filter = q.filter.unwrap();
3540 assert_eq!(
3541 filter,
3542 Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(Expr::Field("age".into())))
3543 );
3544 }
3545 _ => panic!("expected query"),
3546 }
3547 }
3548
3549 #[test]
3550 fn test_parse_null_comparisons_parse_ok() {
3551 assert!(parse("User filter .age < null").is_ok());
3555 assert!(parse("User filter .age >= null").is_ok());
3556 }
3557
3558 #[test]
3559 fn test_parse_count_star_expr() {
3560 let stmt = parse("User filter count(*) > 0").unwrap();
3561 match stmt {
3562 Statement::Query(q) => {
3563 let filter = q.filter.unwrap();
3564 match filter {
3565 Expr::BinaryOp(left, BinOp::Gt, _) => {
3566 assert_eq!(
3567 *left,
3568 Expr::FunctionCall(
3569 AggFunc::Count,
3570 Box::new(Expr::Field("*".into())),
3571 AggregateMode::Symmetric,
3572 )
3573 );
3574 }
3575 _ => panic!("expected comparison"),
3576 }
3577 }
3578 _ => panic!("expected query"),
3579 }
3580 }
3581
3582 #[test]
3585 fn test_parse_upper_in_filter() {
3586 let stmt = parse(r#"User filter upper(.name) = "ALICE""#).unwrap();
3587 match stmt {
3588 Statement::Query(q) => {
3589 let f = q.filter.unwrap();
3590 match f {
3591 Expr::BinaryOp(left, BinOp::Eq, _right) => {
3592 assert!(matches!(*left, Expr::ScalarFunc(ScalarFn::Upper, _)));
3593 }
3594 _ => panic!("expected binary op with upper"),
3595 }
3596 }
3597 _ => panic!("expected query"),
3598 }
3599 }
3600
3601 #[test]
3602 fn test_parse_substring() {
3603 let stmt = parse("User { sub: substring(.name, 1, 3) }").unwrap();
3604 match stmt {
3605 Statement::Query(q) => {
3606 let proj = q.projection.unwrap();
3607 match &proj[0].expr {
3608 Expr::ScalarFunc(ScalarFn::Substring, args) => {
3609 assert_eq!(args.len(), 3);
3610 }
3611 other => panic!("expected ScalarFunc Substring, got {other:?}"),
3612 }
3613 }
3614 _ => panic!("expected query"),
3615 }
3616 }
3617
3618 #[test]
3619 fn test_parse_concat() {
3620 let stmt = parse(r#"User { full: concat(.name, " - ", .email) }"#).unwrap();
3621 match stmt {
3622 Statement::Query(q) => {
3623 let proj = q.projection.unwrap();
3624 match &proj[0].expr {
3625 Expr::ScalarFunc(ScalarFn::Concat, args) => {
3626 assert_eq!(args.len(), 3);
3627 }
3628 other => panic!("expected ScalarFunc Concat, got {other:?}"),
3629 }
3630 }
3631 _ => panic!("expected query"),
3632 }
3633 }
3634
3635 #[test]
3638 fn test_parse_case_single_when() {
3639 let stmt = parse(r#"User filter case when .age > 30 then true else false end"#).unwrap();
3640 match stmt {
3641 Statement::Query(q) => {
3642 let filter = q.filter.unwrap();
3643 match filter {
3644 Expr::Case { whens, else_expr } => {
3645 assert_eq!(whens.len(), 1);
3646 assert!(else_expr.is_some());
3647 }
3648 other => panic!("expected Case expr, got {other:?}"),
3649 }
3650 }
3651 _ => panic!("expected query"),
3652 }
3653 }
3654
3655 #[test]
3656 fn test_parse_case_multiple_whens() {
3657 let stmt = parse(
3658 r#"User { label: case when .age > 30 then "senior" when .age > 20 then "adult" else "young" end }"#
3659 ).unwrap();
3660 match stmt {
3661 Statement::Query(q) => {
3662 let proj = q.projection.unwrap();
3663 match &proj[0].expr {
3664 Expr::Case { whens, else_expr } => {
3665 assert_eq!(whens.len(), 2);
3666 assert!(else_expr.is_some());
3667 }
3668 other => panic!("expected Case expr, got {other:?}"),
3669 }
3670 }
3671 _ => panic!("expected query"),
3672 }
3673 }
3674
3675 #[test]
3676 fn test_parse_case_without_else() {
3677 let stmt = parse(r#"User filter case when .age > 30 then true end"#).unwrap();
3678 match stmt {
3679 Statement::Query(q) => {
3680 let filter = q.filter.unwrap();
3681 match filter {
3682 Expr::Case { whens, else_expr } => {
3683 assert_eq!(whens.len(), 1);
3684 assert!(else_expr.is_none());
3685 }
3686 other => panic!("expected Case expr, got {other:?}"),
3687 }
3688 }
3689 _ => panic!("expected query"),
3690 }
3691 }
3692
3693 #[test]
3696 fn test_parse_mul_expr() {
3697 let stmt = parse("User filter .price * .quantity > 100").unwrap();
3698 match stmt {
3699 Statement::Query(q) => {
3700 let filter = q.filter.unwrap();
3701 match filter {
3702 Expr::BinaryOp(left, BinOp::Gt, _) => match *left {
3703 Expr::BinaryOp(_, BinOp::Mul, _) => {}
3704 other => panic!("expected Mul, got {other:?}"),
3705 },
3706 other => panic!("expected BinaryOp Gt, got {other:?}"),
3707 }
3708 }
3709 _ => panic!("expected query"),
3710 }
3711 }
3712
3713 #[test]
3714 fn test_parse_div_expr() {
3715 let stmt = parse("User { ratio: .total / .count }").unwrap();
3716 match stmt {
3717 Statement::Query(q) => {
3718 let proj = q.projection.unwrap();
3719 assert_eq!(proj[0].alias.as_deref(), Some("ratio"));
3720 match &proj[0].expr {
3721 Expr::BinaryOp(_, BinOp::Div, _) => {}
3722 other => panic!("expected Div, got {other:?}"),
3723 }
3724 }
3725 _ => panic!("expected query"),
3726 }
3727 }
3728
3729 #[test]
3730 fn test_parse_mul_div_precedence() {
3731 let stmt = parse("User filter .a + .b * .c > 0").unwrap();
3733 match stmt {
3734 Statement::Query(q) => {
3735 let filter = q.filter.unwrap();
3736 match filter {
3737 Expr::BinaryOp(left, BinOp::Gt, _) => match *left {
3738 Expr::BinaryOp(_, BinOp::Add, right) => {
3739 assert!(matches!(*right, Expr::BinaryOp(_, BinOp::Mul, _)));
3740 }
3741 other => panic!("expected Add, got {other:?}"),
3742 },
3743 other => panic!("expected Gt, got {other:?}"),
3744 }
3745 }
3746 _ => panic!("expected query"),
3747 }
3748 }
3749
3750 #[test]
3753 fn test_parse_multi_order() {
3754 let stmt = parse("User order .name asc, .age desc").unwrap();
3755 match stmt {
3756 Statement::Query(q) => {
3757 let order = q.order.unwrap();
3758 assert_eq!(order.keys.len(), 2);
3759 assert_eq!(order.keys[0].expr, Expr::Field("name".into()));
3760 assert!(!order.keys[0].descending);
3761 assert_eq!(order.keys[1].expr, Expr::Field("age".into()));
3762 assert!(order.keys[1].descending);
3763 }
3764 _ => panic!("expected query"),
3765 }
3766 }
3767
3768 #[test]
3769 fn test_parse_order_default_asc() {
3770 let stmt = parse("User order .name").unwrap();
3771 match stmt {
3772 Statement::Query(q) => {
3773 let order = q.order.unwrap();
3774 assert_eq!(order.keys.len(), 1);
3775 assert!(!order.keys[0].descending);
3776 }
3777 _ => panic!("expected query"),
3778 }
3779 }
3780
3781 #[test]
3784 fn test_parse_alter_add_column() {
3785 let stmt = parse("alter User add column status: str").unwrap();
3786 match stmt {
3787 Statement::AlterTable(at) => {
3788 assert_eq!(at.table, "User");
3789 match at.action {
3790 AlterAction::AddColumn {
3791 name,
3792 type_name,
3793 required,
3794 } => {
3795 assert_eq!(name, "status");
3796 assert_eq!(type_name, "str");
3797 assert!(!required);
3798 }
3799 other => panic!("expected AddColumn, got {other:?}"),
3800 }
3801 }
3802 other => panic!("expected AlterTable, got {other:?}"),
3803 }
3804 }
3805
3806 #[test]
3807 fn test_parse_alter_add_required_column() {
3808 let stmt = parse("alter User add required status: str").unwrap();
3809 match stmt {
3810 Statement::AlterTable(at) => match at.action {
3811 AlterAction::AddColumn { required, .. } => assert!(required),
3812 other => panic!("expected AddColumn, got {other:?}"),
3813 },
3814 other => panic!("expected AlterTable, got {other:?}"),
3815 }
3816 }
3817
3818 #[test]
3819 fn test_parse_type_with_unique_modifier() {
3820 let stmt = parse("type User { required unique email: str, age: int }").unwrap();
3821 match stmt {
3822 Statement::CreateType(ct) => {
3823 assert!(ct.fields[0].required && ct.fields[0].unique);
3824 assert!(!ct.fields[1].unique);
3825 }
3826 other => panic!("expected CreateType, got {other:?}"),
3827 }
3828 }
3829
3830 #[test]
3831 fn test_parse_type_unique_before_required() {
3832 let stmt = parse("type User { unique required email: str }").unwrap();
3834 match stmt {
3835 Statement::CreateType(ct) => {
3836 assert!(ct.fields[0].required && ct.fields[0].unique);
3837 }
3838 other => panic!("expected CreateType, got {other:?}"),
3839 }
3840 }
3841
3842 #[test]
3843 fn test_parse_alter_add_unique() {
3844 let stmt = parse("alter User add unique .email").unwrap();
3845 match stmt {
3846 Statement::AlterTable(at) => assert!(matches!(
3847 at.action,
3848 AlterAction::AddUnique {
3849 target: IndexTarget::Column(ref column),
3850 ..
3851 } if column == "email"
3852 )),
3853 other => panic!("expected AlterTable, got {other:?}"),
3854 }
3855 }
3856
3857 #[test]
3858 fn test_parse_alter_drop_column() {
3859 let stmt = parse("alter User drop column status").unwrap();
3860 match stmt {
3861 Statement::AlterTable(at) => {
3862 assert_eq!(at.table, "User");
3863 match at.action {
3864 AlterAction::DropColumn { name, .. } => assert_eq!(name, "status"),
3865 other => panic!("expected DropColumn, got {other:?}"),
3866 }
3867 }
3868 other => panic!("expected AlterTable, got {other:?}"),
3869 }
3870 }
3871
3872 #[test]
3873 fn test_parse_alter_drop_without_column_keyword() {
3874 let stmt = parse("alter User drop status").unwrap();
3875 match stmt {
3876 Statement::AlterTable(at) => match at.action {
3877 AlterAction::DropColumn { name, .. } => assert_eq!(name, "status"),
3878 other => panic!("expected DropColumn, got {other:?}"),
3879 },
3880 other => panic!("expected AlterTable, got {other:?}"),
3881 }
3882 }
3883
3884 #[test]
3885 fn test_parse_drop_table() {
3886 let stmt = parse("drop User").unwrap();
3887 match stmt {
3888 Statement::DropTable(dt) => assert_eq!(dt.table, "User"),
3889 other => panic!("expected DropTable, got {other:?}"),
3890 }
3891 }
3892
3893 #[test]
3896 fn test_parse_in_subquery() {
3897 let stmt = parse("User filter .name in (VIP { .name })").unwrap();
3898 match stmt {
3899 Statement::Query(q) => {
3900 let filter = q.filter.unwrap();
3901 match filter {
3902 Expr::InSubquery {
3903 expr,
3904 subquery,
3905 negated,
3906 } => {
3907 assert!(!negated);
3908 assert!(matches!(*expr, Expr::Field(ref f) if f == "name"));
3909 assert_eq!(subquery.source, "VIP");
3910 }
3911 other => panic!("expected InSubquery, got {other:?}"),
3912 }
3913 }
3914 _ => panic!("expected query"),
3915 }
3916 }
3917
3918 #[test]
3919 fn test_parse_not_in_subquery() {
3920 let stmt = parse("User filter .id not in (Order { .user_id })").unwrap();
3921 match stmt {
3922 Statement::Query(q) => match q.filter.unwrap() {
3923 Expr::InSubquery { negated, .. } => assert!(negated),
3924 other => panic!("expected InSubquery, got {other:?}"),
3925 },
3926 _ => panic!("expected query"),
3927 }
3928 }
3929
3930 #[test]
3931 fn test_parse_in_literal_list_still_works() {
3932 let stmt = parse("User filter .age in (25, 30, 35)").unwrap();
3934 match stmt {
3935 Statement::Query(q) => match q.filter.unwrap() {
3936 Expr::InList { list, negated, .. } => {
3937 assert!(!negated);
3938 assert_eq!(list.len(), 3);
3939 }
3940 other => panic!("expected InList, got {other:?}"),
3941 },
3942 _ => panic!("expected query"),
3943 }
3944 }
3945
3946 #[test]
3949 fn test_parse_create_view() {
3950 let stmt = parse("materialize OldUsers as User filter .age > 28").unwrap();
3951 match stmt {
3952 Statement::CreateView(cv) => {
3953 assert_eq!(cv.name, "OldUsers");
3954 assert_eq!(cv.query.source, "User");
3955 assert!(cv.query.filter.is_some());
3956 assert!(!cv.query_text.is_empty());
3957 }
3958 _ => panic!("expected CreateView"),
3959 }
3960 }
3961
3962 #[test]
3963 fn test_parse_create_view_with_projection() {
3964 let stmt = parse("materialize UserNames as User { .name }").unwrap();
3965 match stmt {
3966 Statement::CreateView(cv) => {
3967 assert_eq!(cv.name, "UserNames");
3968 assert!(cv.query.projection.is_some());
3969 }
3970 _ => panic!("expected CreateView"),
3971 }
3972 }
3973
3974 #[test]
3975 fn test_parse_refresh_view() {
3976 let stmt = parse("refresh OldUsers").unwrap();
3977 match stmt {
3978 Statement::RefreshView(rv) => {
3979 assert_eq!(rv.name, "OldUsers");
3980 }
3981 _ => panic!("expected RefreshView"),
3982 }
3983 }
3984
3985 #[test]
3986 fn test_parse_drop_view() {
3987 let stmt = parse("drop view OldUsers").unwrap();
3988 match stmt {
3989 Statement::DropView(dv) => {
3990 assert_eq!(dv.name, "OldUsers");
3991 }
3992 _ => panic!("expected DropView"),
3993 }
3994 }
3995
3996 #[test]
3997 fn test_parse_drop_table_still_works() {
3998 let stmt = parse("drop Users").unwrap();
3999 match stmt {
4000 Statement::DropTable(dt) => {
4001 assert_eq!(dt.table, "Users");
4002 }
4003 _ => panic!("expected DropTable"),
4004 }
4005 }
4006
4007 #[test]
4008 fn test_parse_union() {
4009 let stmt = parse("User union Order").unwrap();
4010 match stmt {
4011 Statement::Union(u) => {
4012 assert!(!u.all);
4013 match *u.left {
4014 Statement::Query(_) => {}
4015 _ => panic!("expected Query on left"),
4016 }
4017 match *u.right {
4018 Statement::Query(_) => {}
4019 _ => panic!("expected Query on right"),
4020 }
4021 }
4022 _ => panic!("expected Union"),
4023 }
4024 }
4025
4026 #[test]
4027 fn test_parse_union_all() {
4028 let stmt = parse("User union all Order").unwrap();
4029 match stmt {
4030 Statement::Union(u) => {
4031 assert!(u.all, "expected UNION ALL");
4032 match *u.left {
4033 Statement::Query(_) => {}
4034 _ => panic!("expected Query on left"),
4035 }
4036 match *u.right {
4037 Statement::Query(_) => {}
4038 _ => panic!("expected Query on right"),
4039 }
4040 }
4041 _ => panic!("expected Union"),
4042 }
4043 }
4044
4045 #[test]
4046 fn test_parse_union_chain() {
4047 let stmt = parse("User union Order union Product").unwrap();
4049 match stmt {
4050 Statement::Union(outer) => {
4051 assert!(!outer.all);
4052 match *outer.right {
4054 Statement::Query(q) => assert_eq!(q.source, "Product"),
4055 _ => panic!("expected Query(Product) on right"),
4056 }
4057 match *outer.left {
4059 Statement::Union(inner) => {
4060 assert!(!inner.all);
4061 match *inner.left {
4062 Statement::Query(q) => assert_eq!(q.source, "User"),
4063 _ => panic!("expected Query(User)"),
4064 }
4065 match *inner.right {
4066 Statement::Query(q) => assert_eq!(q.source, "Order"),
4067 _ => panic!("expected Query(Order)"),
4068 }
4069 }
4070 _ => panic!("expected inner Union"),
4071 }
4072 }
4073 _ => panic!("expected Union"),
4074 }
4075 }
4076
4077 #[test]
4078 fn test_parse_union_with_filter() {
4079 let stmt = parse("User filter .age > 10 union Order filter .total > 50").unwrap();
4080 match stmt {
4081 Statement::Union(u) => {
4082 assert!(!u.all);
4083 match *u.left {
4085 Statement::Query(q) => {
4086 assert_eq!(q.source, "User");
4087 assert!(q.filter.is_some());
4088 }
4089 _ => panic!("expected Query on left"),
4090 }
4091 match *u.right {
4092 Statement::Query(q) => {
4093 assert_eq!(q.source, "Order");
4094 assert!(q.filter.is_some());
4095 }
4096 _ => panic!("expected Query on right"),
4097 }
4098 }
4099 _ => panic!("expected Union"),
4100 }
4101 }
4102
4103 #[test]
4104 fn test_parse_count_distinct_standalone() {
4105 let stmt = parse("count(distinct User { .name })").unwrap();
4106 match stmt {
4107 Statement::Query(q) => {
4108 let agg = q.aggregation.unwrap();
4109 assert_eq!(agg.function, AggFunc::CountDistinct);
4110 assert_eq!(agg.argument, Some(Expr::Field("name".into())));
4111 }
4112 _ => panic!("expected Query"),
4113 }
4114 }
4115
4116 #[test]
4117 fn test_parse_count_distinct_in_projection() {
4118 let stmt = parse("User group .dept { .dept, count(distinct .name) }").unwrap();
4119 match stmt {
4120 Statement::Query(q) => {
4121 let proj = q.projection.unwrap();
4122 assert_eq!(proj.len(), 2);
4123 match &proj[1].expr {
4124 Expr::FunctionCall(func, _, _) => {
4125 assert_eq!(*func, AggFunc::CountDistinct);
4126 }
4127 _ => panic!("expected FunctionCall"),
4128 }
4129 }
4130 _ => panic!("expected Query"),
4131 }
4132 }
4133
4134 #[test]
4137 fn test_parse_window_row_number_order() {
4138 let stmt = parse("User { .name, rn: row_number() over (order .age) }").unwrap();
4139 match stmt {
4140 Statement::Query(q) => {
4141 let proj = q.projection.unwrap();
4142 assert_eq!(proj.len(), 2);
4143 assert_eq!(proj[1].alias.as_deref(), Some("rn"));
4144 match &proj[1].expr {
4145 Expr::Window {
4146 function,
4147 args,
4148 partition_by,
4149 order_by,
4150 ..
4151 } => {
4152 assert_eq!(*function, WindowFunc::RowNumber);
4153 assert!(args.is_empty());
4154 assert!(partition_by.is_empty());
4155 assert_eq!(order_by.len(), 1);
4156 assert_eq!(order_by[0].expr, Expr::Field("age".into()));
4157 assert!(!order_by[0].descending);
4158 }
4159 other => panic!("expected Window, got {other:?}"),
4160 }
4161 }
4162 _ => panic!("expected query"),
4163 }
4164 }
4165
4166 #[test]
4167 fn test_parse_window_sum_partition_order() {
4168 let stmt =
4169 parse("User { .name, s: sum(.salary) over (partition .dept order .salary) }").unwrap();
4170 match stmt {
4171 Statement::Query(q) => {
4172 let proj = q.projection.unwrap();
4173 assert_eq!(proj.len(), 2);
4174 assert_eq!(proj[1].alias.as_deref(), Some("s"));
4175 match &proj[1].expr {
4176 Expr::Window {
4177 function,
4178 args,
4179 partition_by,
4180 order_by,
4181 ..
4182 } => {
4183 assert_eq!(*function, WindowFunc::Sum);
4184 assert_eq!(args.len(), 1);
4185 assert!(matches!(&args[0], Expr::Field(f) if f == "salary"));
4186 assert_eq!(partition_by, &[Expr::Field("dept".into())]);
4187 assert_eq!(order_by.len(), 1);
4188 assert_eq!(order_by[0].expr, Expr::Field("salary".into()));
4189 assert!(!order_by[0].descending);
4190 }
4191 other => panic!("expected Window, got {other:?}"),
4192 }
4193 }
4194 _ => panic!("expected query"),
4195 }
4196 }
4197
4198 #[test]
4199 fn test_parse_window_rank_desc() {
4200 let stmt =
4201 parse("User { .dept, .salary, r: rank() over (partition .dept order .salary desc) }")
4202 .unwrap();
4203 match stmt {
4204 Statement::Query(q) => {
4205 let proj = q.projection.unwrap();
4206 assert_eq!(proj.len(), 3);
4207 match &proj[2].expr {
4208 Expr::Window {
4209 function,
4210 partition_by,
4211 order_by,
4212 ..
4213 } => {
4214 assert_eq!(*function, WindowFunc::Rank);
4215 assert_eq!(partition_by, &[Expr::Field("dept".into())]);
4216 assert_eq!(order_by.len(), 1);
4217 assert!(order_by[0].descending);
4218 }
4219 other => panic!("expected Window, got {other:?}"),
4220 }
4221 }
4222 _ => panic!("expected query"),
4223 }
4224 }
4225
4226 #[test]
4227 fn test_parse_window_dense_rank() {
4228 let stmt = parse("User { .name, dr: dense_rank() over (order .score desc) }").unwrap();
4229 match stmt {
4230 Statement::Query(q) => {
4231 let proj = q.projection.unwrap();
4232 assert_eq!(proj.len(), 2);
4233 match &proj[1].expr {
4234 Expr::Window { function, .. } => {
4235 assert_eq!(*function, WindowFunc::DenseRank);
4236 }
4237 other => panic!("expected Window, got {other:?}"),
4238 }
4239 }
4240 _ => panic!("expected query"),
4241 }
4242 }
4243
4244 #[test]
4245 fn test_parse_sum_without_over_is_aggregate() {
4246 let stmt = parse("User group .dept { .dept, total: sum(.salary) }").unwrap();
4248 match stmt {
4249 Statement::Query(q) => {
4250 let proj = q.projection.unwrap();
4251 assert_eq!(proj.len(), 2);
4252 match &proj[1].expr {
4253 Expr::FunctionCall(AggFunc::Sum, _, _) => {} other => panic!("expected FunctionCall(Sum), got {other:?}"),
4255 }
4256 }
4257 _ => panic!("expected query"),
4258 }
4259 }
4260
4261 #[test]
4262 fn test_nesting_depth_limit() {
4263 let mut query = String::from("User filter ");
4265 for _ in 0..70 {
4266 query.push('(');
4267 }
4268 query.push_str(".age > 1");
4269 for _ in 0..70 {
4270 query.push(')');
4271 }
4272 let result = parse(&query);
4273 assert!(result.is_err());
4274 let err = result.unwrap_err();
4275 assert!(
4276 err.message().contains("nesting depth"),
4277 "expected nesting depth error, got: {}",
4278 err.message()
4279 );
4280 }
4281
4282 #[test]
4283 fn test_unary_prefix_nesting_depth_limit() {
4284 let query = String::from("User filter ") + &"not ".repeat(5000) + ".active";
4288 let result = parse(&query);
4289 assert!(result.is_err());
4290 let err = result.unwrap_err();
4291 assert!(
4292 err.message().contains("nesting depth"),
4293 "expected nesting depth error, got: {}",
4294 err.message()
4295 );
4296 }
4297
4298 #[test]
4299 fn test_moderate_nesting_succeeds() {
4300 let mut query = String::from("User filter ");
4302 for _ in 0..10 {
4303 query.push('(');
4304 }
4305 query.push_str(".age > 1");
4306 for _ in 0..10 {
4307 query.push(')');
4308 }
4309 assert!(parse(&query).is_ok());
4310 }
4311
4312 #[test]
4316 fn test_parse_fuzz_repro_projection_eof() {
4317 let err = parse("nn{").expect_err("unterminated projection must error, not panic");
4318 let _ = err.message();
4319 }
4320
4321 #[test]
4324 fn test_parse_fuzz_repro_short_projection_eof() {
4325 let err = parse("z{").expect_err("unterminated projection must error, not panic");
4326 let _ = err.message();
4327 }
4328
4329 #[test]
4330 fn test_update_at_statement_start_gives_helpful_error() {
4331 let err =
4332 parse(r#"update User filter .name = "Alice" { age := 31 }"#).expect_err("should fail");
4333 let msg = err.message();
4334 assert!(
4335 msg.contains("pipeline syntax"),
4336 "error should mention pipeline syntax, got: {msg}"
4337 );
4338 assert!(
4339 msg.contains("update"),
4340 "error should mention 'update', got: {msg}"
4341 );
4342 }
4343
4344 #[test]
4345 fn test_delete_at_statement_start_gives_helpful_error() {
4346 let err = parse("delete User filter .age < 18").expect_err("should fail");
4347 let msg = err.message();
4348 assert!(
4349 msg.contains("pipeline syntax"),
4350 "error should mention pipeline syntax, got: {msg}"
4351 );
4352 assert!(
4353 msg.contains("delete"),
4354 "error should mention 'delete', got: {msg}"
4355 );
4356 }
4357}
4358
4359#[cfg(test)]
4360mod cleanup_parser_dx_tests {
4361 use super::*;
4362
4363 #[test]
4364 fn typoed_statement_keyword_gets_suggestion() {
4365 let err = parse("updat User set age = 1").unwrap_err();
4366 let msg = err.to_string();
4367 assert!(msg.contains("near token"), "{msg}");
4368 assert!(msg.contains("did you mean `update`"), "{msg}");
4369 }
4370}
4371
4372#[cfg(test)]
4373mod dogfood_dx_tests {
4374 use super::*;
4375
4376 #[test]
4379 fn reserved_word_field_name_gives_actionable_error() {
4380 let err = parse("type Post { type: str }").unwrap_err();
4381 let msg = err.to_string();
4382 assert!(
4383 msg.contains("'type' is a reserved word")
4384 && msg.contains("field name")
4385 && msg.contains("quote it as `type`"),
4386 "unhelpful message: {msg}"
4387 );
4388 }
4389
4390 #[test]
4391 fn reserved_modifier_word_as_field_name_gives_actionable_error() {
4392 let err = parse("type Post { required: bool }").unwrap_err();
4396 let msg = err.to_string();
4397 assert!(
4398 msg.contains("'required' is a reserved word") && msg.contains("quote it as `required`"),
4399 "unhelpful message: {msg}"
4400 );
4401 }
4402
4403 #[test]
4404 fn reserved_word_in_insert_assignment_gives_actionable_error() {
4405 let err = parse(r#"insert Post { type := "x" }"#).unwrap_err();
4406 let msg = err.to_string();
4407 assert!(msg.contains("'type' is a reserved word"), "{msg}");
4408 }
4409
4410 #[test]
4411 fn reserved_word_in_alter_column_gives_actionable_error() {
4412 let err = parse("alter Post add column order: int").unwrap_err();
4413 let msg = err.to_string();
4414 assert!(msg.contains("'order' is a reserved word"), "{msg}");
4415 }
4416
4417 #[test]
4418 fn backtick_field_name_parses_as_identifier() {
4419 let stmt = parse("type Post { `type`: str, `order`: int }").unwrap();
4420 match stmt {
4421 Statement::CreateType(ct) => {
4422 assert_eq!(ct.fields[0].name, "type");
4423 assert_eq!(ct.fields[1].name, "order");
4424 }
4425 other => panic!("expected CreateType, got {other:?}"),
4426 }
4427 }
4428
4429 #[test]
4430 fn backtick_field_still_honors_modifiers() {
4431 let stmt = parse("type Post { required `type`: str }").unwrap();
4432 match stmt {
4433 Statement::CreateType(ct) => {
4434 assert_eq!(ct.fields[0].name, "type");
4435 assert!(ct.fields[0].required);
4436 }
4437 other => panic!("expected CreateType, got {other:?}"),
4438 }
4439 }
4440
4441 #[test]
4444 fn create_type_if_not_exists_parses() {
4445 let stmt = parse("type Post if not exists { id: int }").unwrap();
4446 match stmt {
4447 Statement::CreateType(ct) => assert!(ct.if_not_exists),
4448 other => panic!("expected CreateType, got {other:?}"),
4449 }
4450 }
4451
4452 #[test]
4453 fn create_type_without_clause_defaults_false() {
4454 let stmt = parse("type Post { id: int }").unwrap();
4455 match stmt {
4456 Statement::CreateType(ct) => assert!(!ct.if_not_exists),
4457 other => panic!("expected CreateType, got {other:?}"),
4458 }
4459 }
4460
4461 #[test]
4462 fn drop_if_exists_parses() {
4463 match parse("drop if exists Post").unwrap() {
4464 Statement::DropTable(dt) => assert!(dt.if_exists),
4465 other => panic!("expected DropTable, got {other:?}"),
4466 }
4467 match parse("drop Post").unwrap() {
4468 Statement::DropTable(dt) => assert!(!dt.if_exists),
4469 other => panic!("expected DropTable, got {other:?}"),
4470 }
4471 }
4472
4473 #[test]
4474 fn drop_view_if_exists_parses() {
4475 match parse("drop view if exists ActiveUsers").unwrap() {
4476 Statement::DropView(dv) => {
4477 assert!(dv.if_exists);
4478 assert_eq!(dv.name, "ActiveUsers");
4479 }
4480 other => panic!("expected DropView, got {other:?}"),
4481 }
4482 }
4483
4484 #[test]
4485 fn add_index_and_unique_if_not_exists_parse() {
4486 match parse("alter Post add index if not exists .slug").unwrap() {
4487 Statement::AlterTable(at) => {
4488 assert!(matches!(
4489 at.action,
4490 AlterAction::AddIndex {
4491 if_not_exists: true,
4492 ..
4493 }
4494 ));
4495 }
4496 other => panic!("expected AlterTable, got {other:?}"),
4497 }
4498 match parse("alter Post add unique if not exists .slug").unwrap() {
4499 Statement::AlterTable(at) => {
4500 assert!(matches!(
4501 at.action,
4502 AlterAction::AddUnique {
4503 if_not_exists: true,
4504 ..
4505 }
4506 ));
4507 }
4508 other => panic!("expected AlterTable, got {other:?}"),
4509 }
4510 }
4511
4512 #[test]
4513 fn expression_index_targets_parse_with_stable_table_local_identity() {
4514 use powdb_storage::stored_json_path::{
4515 StoredJsonPathSegmentV1 as Segment, StoredJsonPathV1,
4516 };
4517
4518 let expected = StoredJsonPathV1::new(
4519 "data",
4520 vec![Segment::Key("author".into()), Segment::Index(0)],
4521 );
4522 for query in [
4523 "alter Post add index (.data->author->0)",
4524 "alter Post add unique if not exists (.data->\"author\"->0)",
4525 "alter Post drop index if exists (.data->author->0)",
4526 ] {
4527 let Statement::AlterTable(alter) = parse(query).unwrap() else {
4528 panic!("expected alter table for {query}");
4529 };
4530 let (target, flag) = match alter.action {
4531 AlterAction::AddIndex {
4532 target,
4533 if_not_exists,
4534 }
4535 | AlterAction::AddUnique {
4536 target,
4537 if_not_exists,
4538 } => (target, if_not_exists),
4539 AlterAction::DropIndex { target, if_exists } => (target, if_exists),
4540 other => panic!("expected index action, got {other:?}"),
4541 };
4542 assert_eq!(target, IndexTarget::JsonPath(expected.clone()));
4543 assert_eq!(
4544 flag,
4545 query.contains("if not exists") || query.contains("if exists")
4546 );
4547 }
4548 }
4549
4550 #[test]
4551 fn expression_index_target_rejects_ambiguous_or_non_path_forms() {
4552 let cases = [
4553 (
4554 "alter Post add index .data->author",
4555 "must be parenthesized",
4556 ),
4557 (
4558 "alter Post add index (p.data->author)",
4559 "qualified JSON paths",
4560 ),
4561 (
4562 "alter Post add index (.data)",
4563 "use `.column` for a stored column",
4564 ),
4565 (
4566 "alter Post add index (.data->age + 1)",
4567 "only a direct JSON path",
4568 ),
4569 (
4570 "alter Post drop index ({ value := 1 })",
4571 "expected an unqualified JSON path",
4572 ),
4573 ];
4574 for (query, expected) in cases {
4575 let error = parse(query).expect_err(query).to_string();
4576 assert!(
4577 error.contains(expected),
4578 "`{query}` should mention `{expected}`, got `{error}`"
4579 );
4580 }
4581 }
4582
4583 #[test]
4584 fn alter_drop_column_if_exists_parses() {
4585 match parse("alter Post drop column if exists status").unwrap() {
4586 Statement::AlterTable(at) => {
4587 assert!(matches!(
4588 at.action,
4589 AlterAction::DropColumn {
4590 if_exists: true,
4591 ..
4592 }
4593 ));
4594 }
4595 other => panic!("expected AlterTable, got {other:?}"),
4596 }
4597 }
4598
4599 #[test]
4602 fn schema_parses_to_list_types() {
4603 assert_eq!(parse("schema").unwrap(), Statement::ListTypes);
4604 }
4605
4606 #[test]
4607 fn describe_parses_to_describe() {
4608 assert_eq!(
4609 parse("describe Post").unwrap(),
4610 Statement::Describe("Post".to_string())
4611 );
4612 }
4613
4614 #[test]
4615 fn schema_with_type_aliases_describe() {
4616 assert_eq!(
4617 parse("schema Post").unwrap(),
4618 Statement::Describe("Post".to_string())
4619 );
4620 }
4621
4622 #[test]
4623 fn schema_links_parses_to_list_links() {
4624 assert_eq!(parse("schema links").unwrap(), Statement::ListLinks);
4625 }
4626
4627 #[test]
4628 fn describe_links_still_names_a_table() {
4629 assert_eq!(
4632 parse("describe links").unwrap(),
4633 Statement::Describe("links".to_string())
4634 );
4635 }
4636}
4637
4638#[cfg(test)]
4639mod json_path_tests {
4640 use super::*;
4641
4642 fn filter_of(src: &str) -> Expr {
4644 match parse(src).unwrap() {
4645 Statement::Query(q) => q.filter.expect("expected a filter"),
4646 other => panic!("expected a query, got {other:?}"),
4647 }
4648 }
4649
4650 #[test]
4651 fn ident_key_path() {
4652 let e = filter_of(r#"Post filter .data->author->name = "x""#);
4654 let Expr::BinaryOp(lhs, BinOp::Eq, _) = e else {
4655 panic!("expected an equality, got {e:?}");
4656 };
4657 assert_eq!(
4658 *lhs,
4659 Expr::JsonPath {
4660 base: Box::new(Expr::Field("data".into())),
4661 segments: vec![PathSeg::Key("author".into()), PathSeg::Key("name".into())],
4662 }
4663 );
4664 }
4665
4666 #[test]
4667 fn string_form_key_path() {
4668 let e = filter_of(r#"Post filter .data->"weird key!" = 1"#);
4670 let Expr::BinaryOp(lhs, _, _) = e else {
4671 panic!("expected binop");
4672 };
4673 assert_eq!(
4674 *lhs,
4675 Expr::JsonPath {
4676 base: Box::new(Expr::Field("data".into())),
4677 segments: vec![PathSeg::Key("weird key!".into())],
4678 }
4679 );
4680 }
4681
4682 #[test]
4683 fn array_index_path() {
4684 let e = filter_of(r#"Post filter .data->tags->0 = "rust""#);
4686 let Expr::BinaryOp(lhs, _, _) = e else {
4687 panic!("expected binop");
4688 };
4689 assert_eq!(
4690 *lhs,
4691 Expr::JsonPath {
4692 base: Box::new(Expr::Field("data".into())),
4693 segments: vec![PathSeg::Key("tags".into()), PathSeg::Index(0)],
4694 }
4695 );
4696 }
4697
4698 #[test]
4699 fn qualified_base_path() {
4700 let e = filter_of(r#"Post as posts filter posts.data->author = "a""#);
4702 let Expr::BinaryOp(lhs, _, _) = e else {
4703 panic!("expected binop");
4704 };
4705 assert_eq!(
4706 *lhs,
4707 Expr::JsonPath {
4708 base: Box::new(Expr::QualifiedField {
4709 qualifier: "posts".into(),
4710 field: "data".into(),
4711 }),
4712 segments: vec![PathSeg::Key("author".into())],
4713 }
4714 );
4715 }
4716
4717 #[test]
4718 fn path_binds_tighter_than_comparison_and_arithmetic() {
4719 let e = filter_of("Post filter .data->age > 21");
4721 let Expr::BinaryOp(lhs, BinOp::Gt, rhs) = e else {
4722 panic!("expected a top-level `>`, got {e:?}");
4723 };
4724 assert!(matches!(*lhs, Expr::JsonPath { .. }));
4725 assert_eq!(*rhs, Expr::Literal(Literal::Int(21)));
4726
4727 let e = filter_of("Post filter .a->b + 1 = 3");
4729 let Expr::BinaryOp(add, BinOp::Eq, _) = e else {
4730 panic!("expected eq");
4731 };
4732 let Expr::BinaryOp(lhs, BinOp::Add, _) = *add else {
4733 panic!("expected `+` under `=`, got {add:?}");
4734 };
4735 assert!(matches!(*lhs, Expr::JsonPath { .. }));
4736 }
4737
4738 #[test]
4739 fn dash_vs_arrow_lexing() {
4740 let idx = filter_of("Post filter .a->1 = 0");
4743 let Expr::BinaryOp(lhs, BinOp::Eq, _) = idx else {
4744 panic!("expected eq");
4745 };
4746 assert_eq!(
4747 *lhs,
4748 Expr::JsonPath {
4749 base: Box::new(Expr::Field("a".into())),
4750 segments: vec![PathSeg::Index(1)],
4751 }
4752 );
4753
4754 let sub = filter_of("Post filter .a - 1 = 0");
4757 let Expr::BinaryOp(lhs, BinOp::Eq, _) = sub else {
4758 panic!("expected eq");
4759 };
4760 assert!(
4761 matches!(*lhs, Expr::BinaryOp(_, BinOp::Sub, _)),
4762 "`.a - 1` should be subtraction, got {lhs:?}"
4763 );
4764
4765 assert!(
4770 parse("Post filter .a-1 = 0").is_err(),
4771 "`.a-1` should fail to parse (negative-literal gotcha)"
4772 );
4773
4774 assert!(
4776 parse("Post filter .a - > 0").is_err(),
4777 "`.a - >` should fail to parse"
4778 );
4779 }
4780
4781 #[test]
4782 fn negative_index_rejected() {
4783 let err = parse("Post filter .data->-1 = 0").unwrap_err();
4784 assert!(
4785 err.to_string().contains("array index"),
4786 "expected an index error, got: {err}"
4787 );
4788 }
4789
4790 #[test]
4791 fn path_on_literal_base_rejected() {
4792 let err = parse("Post filter 5->x = 1").unwrap_err();
4794 assert!(
4795 err.to_string().to_lowercase().contains("field base"),
4796 "expected a field-base error, got: {err}"
4797 );
4798 }
4799
4800 #[test]
4801 fn json_path_assignment_target_is_targeted_unsupported() {
4802 for stmt in [
4807 "Doc update { .data->x := 5 }",
4808 "Doc update { data->x := 5 }",
4809 ] {
4810 let err = parse(stmt).unwrap_err();
4811 assert!(
4812 matches!(err, ParseError::Unsupported { .. }),
4813 "{stmt}: expected Unsupported, got {err:?}"
4814 );
4815 let msg = err.to_string();
4816 assert!(
4817 msg.contains("JSON path assignment targets are not supported"),
4818 "{stmt}: message must state the unsupported feature: {msg}"
4819 );
4820 assert!(
4821 msg.contains("json_set"),
4822 "{stmt}: message must point at the whole-column alternative: {msg}"
4823 );
4824 }
4825 assert!(parse(r#"Doc update { data := "{}" }"#).is_ok());
4827 }
4828
4829 #[test]
4830 fn json_type_scalar_parses() {
4831 let e = filter_of(r#"Post filter json_type(.data->x) = "string""#);
4832 let Expr::BinaryOp(lhs, _, _) = e else {
4833 panic!("expected binop");
4834 };
4835 let Expr::ScalarFunc(ScalarFn::JsonType, args) = *lhs else {
4836 panic!("expected json_type call, got {lhs:?}");
4837 };
4838 assert_eq!(args.len(), 1);
4839 assert!(matches!(args[0], Expr::JsonPath { .. }));
4840 }
4841
4842 #[test]
4843 fn path_in_projection() {
4844 let stmt = parse("Post { author: .data->author }").unwrap();
4847 let Statement::Query(q) = stmt else {
4848 panic!("expected query");
4849 };
4850 let proj = q.projection.unwrap();
4851 assert_eq!(proj[0].alias.as_deref(), Some("author"));
4852 assert!(matches!(proj[0].expr, Expr::JsonPath { .. }));
4853
4854 let Statement::Query(ordered) = parse("Post order .data->author { .id }").unwrap() else {
4855 panic!("expected query");
4856 };
4857 assert!(matches!(
4858 ordered.order.unwrap().keys[0].expr,
4859 Expr::JsonPath { .. }
4860 ));
4861
4862 let Statement::Query(grouped) =
4863 parse("Post group .data->author { .data->author }").unwrap()
4864 else {
4865 panic!("expected query");
4866 };
4867 assert!(matches!(
4868 grouped.group_by.unwrap().keys[0].expr,
4869 Expr::JsonPath { .. }
4870 ));
4871 }
4872}