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