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(); self.reject_bare_dotted_path()?;
840 let expr = if matches!(self.peek(), Token::Ident(_))
843 && matches!(self.tokens.get(self.pos + 1), Some(Token::As))
844 {
845 Expr::NestedQuery(Box::new(self.parse_nested_query()?))
846 } else if self.at_link_traversal() {
847 Expr::NestedQuery(Box::new(self.parse_link_traversal()?))
853 } else if self.at_scalar_link_path() {
854 self.parse_scalar_link_path()?
856 } else {
857 self.parse_expr()?
858 };
859 fields.push(ProjectionField {
860 alias: Some(alias),
861 expr,
862 });
863 } else {
864 if matches!(self.peek(), Token::Ident(_))
865 && matches!(self.tokens.get(self.pos + 1), Some(Token::As))
866 {
867 return Err(ParseError::Syntax {
868 message: "a nested projection needs a field name: \
869 `<name>: <Table> as <alias> filter ... { ... }`"
870 .into(),
871 });
872 }
873 self.reject_bare_dotted_path()?;
874 let expr = if self.at_scalar_link_path() {
875 self.parse_scalar_link_path()?
878 } else {
879 self.parse_expr()?
880 };
881 fields.push(ProjectionField { alias: None, expr });
882 }
883 if *self.peek() == Token::Comma {
884 self.advance();
885 }
886 }
887 self.expect(&Token::RBrace)?;
888 Ok(fields)
889 }
890
891 fn reject_bare_dotted_path(&self) -> Result<(), ParseError> {
898 if let (Token::DotIdent(first), Some(Token::DotIdent(second))) =
899 (self.peek(), self.tokens.get(self.pos + 1))
900 {
901 return Err(ParseError::Syntax {
902 message: format!(
903 "`.{first}.{second}` is ambiguous in a projection: for a link \
904 path, alias the table and qualify the path \
905 (`Order as o {{ o.{first}.{second} }}`); for separate fields, \
906 separate them with commas (`.{first}, .{second}`)"
907 ),
908 });
909 }
910 Ok(())
911 }
912
913 fn at_link_traversal(&self) -> bool {
919 matches!(self.peek(), Token::Ident(_))
920 && matches!(self.tokens.get(self.pos + 1), Some(Token::DotIdent(_)))
921 && matches!(
922 self.tokens.get(self.pos + 2),
923 Some(Token::LBrace | Token::Filter | Token::Order | Token::Limit | Token::Offset)
924 )
925 }
926
927 fn at_scalar_link_path(&self) -> bool {
934 matches!(self.peek(), Token::Ident(_))
935 && matches!(self.tokens.get(self.pos + 1), Some(Token::DotIdent(_)))
936 && matches!(self.tokens.get(self.pos + 2), Some(Token::DotIdent(_)))
937 }
938
939 fn parse_scalar_link_path(&mut self) -> Result<Expr, ParseError> {
945 let outer_alias = match self.advance() {
946 Token::Ident(n) => n,
947 t => return Err(self.named_ident_error("link path outer alias", &t)),
948 };
949 let mut parts = Vec::new();
950 while let Some(Token::DotIdent(_)) = self.tokens.get(self.pos) {
951 match self.advance() {
952 Token::DotIdent(n) => parts.push(n),
953 _ => unreachable!("peeked DotIdent"),
954 }
955 }
956 debug_assert!(parts.len() >= 2, "guarded by at_scalar_link_path");
957 let column = parts.pop().expect("at least two parts");
958 Ok(Expr::LinkPath {
959 outer_alias,
960 links: parts,
961 column,
962 })
963 }
964
965 fn parse_link_traversal(&mut self) -> Result<NestedQuery, ParseError> {
975 self.depth += 1;
976 if self.depth > MAX_NESTING_DEPTH {
977 self.depth -= 1;
978 return Err(ParseError::NestingDepthExceeded {
979 max: MAX_NESTING_DEPTH,
980 });
981 }
982 let result = self.parse_link_traversal_inner();
983 self.depth -= 1;
984 result
985 }
986
987 fn parse_link_traversal_inner(&mut self) -> Result<NestedQuery, ParseError> {
988 let outer_alias = match self.advance() {
989 Token::Ident(n) => n,
990 t => return Err(self.named_ident_error("link outer alias", &t)),
991 };
992 let link_name = match self.advance() {
993 Token::DotIdent(n) => n,
994 t => {
995 return Err(ParseError::UnexpectedToken {
996 expected: "link name".into(),
997 got: t.display_name(),
998 })
999 }
1000 };
1001 let child_alias = link_name.clone();
1004
1005 let mut residual = None;
1009 if *self.peek() == Token::Filter {
1010 self.advance();
1011 residual = Some(qualify_bare_fields(self.parse_expr()?, &child_alias));
1012 }
1013 let mut order = None;
1014 let mut limit = None;
1015 let mut offset = None;
1016 let mut offset_before_limit = false;
1017 loop {
1018 match self.peek() {
1019 Token::Order => {
1020 self.advance();
1021 let mut clause = self.parse_order()?;
1022 for key in &mut clause.keys {
1023 key.expr = qualify_bare_fields(
1024 std::mem::replace(&mut key.expr, Expr::Null),
1025 &child_alias,
1026 );
1027 }
1028 order = Some(clause);
1029 }
1030 Token::Limit => {
1031 self.advance();
1032 if offset.is_some() {
1033 offset_before_limit = true;
1034 }
1035 limit = Some(self.parse_expr()?);
1036 }
1037 Token::Offset => {
1038 self.advance();
1039 offset = Some(self.parse_expr()?);
1040 }
1041 _ => break,
1042 }
1043 }
1044 self.expect(&Token::LBrace)?;
1045 let mut fields = Vec::new();
1046 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1047 let alias = if matches!(self.peek(), Token::Ident(_))
1048 && matches!(self.tokens.get(self.pos + 1), Some(Token::Colon))
1049 {
1050 let alias = self.expect_named_ident("field alias")?;
1051 self.advance(); Some(alias)
1053 } else {
1054 None
1055 };
1056 self.reject_bare_dotted_path()?;
1057 let expr = qualify_bare_fields(self.parse_expr()?, &child_alias);
1058 fields.push(ProjectionField { alias, expr });
1059 if *self.peek() == Token::Comma {
1060 self.advance();
1061 }
1062 }
1063 self.expect(&Token::RBrace)?;
1064 if fields.is_empty() {
1065 return Err(ParseError::Syntax {
1066 message: "link traversal requires at least one field".into(),
1067 });
1068 }
1069 Ok(NestedQuery {
1070 source: String::new(),
1072 alias: child_alias,
1073 via_link: Some(ViaLink {
1074 outer_alias,
1075 link_name,
1076 }),
1077 filter: residual.unwrap_or(Expr::Literal(Literal::Bool(true))),
1080 order,
1081 limit,
1082 offset,
1083 offset_before_limit,
1084 fields,
1085 })
1086 }
1087
1088 fn parse_nested_query(&mut self) -> Result<NestedQuery, ParseError> {
1093 self.depth += 1;
1096 if self.depth > MAX_NESTING_DEPTH {
1097 self.depth -= 1;
1098 return Err(ParseError::NestingDepthExceeded {
1099 max: MAX_NESTING_DEPTH,
1100 });
1101 }
1102 let result = self.parse_nested_query_inner();
1103 self.depth -= 1;
1104 result
1105 }
1106
1107 fn parse_nested_query_inner(&mut self) -> Result<NestedQuery, ParseError> {
1108 let source = self.expect_named_ident("nested source type")?;
1109 self.expect(&Token::As)?;
1110 let alias = self.expect_named_ident("nested source alias")?;
1111 self.expect(&Token::Filter)?;
1112 let filter = self.parse_expr()?;
1113 let mut order = None;
1114 let mut limit = None;
1115 let mut offset = None;
1116 let mut offset_before_limit = false;
1117 loop {
1118 match self.peek() {
1119 Token::Order => {
1120 self.advance();
1121 order = Some(self.parse_order()?);
1122 }
1123 Token::Limit => {
1124 self.advance();
1125 if offset.is_some() {
1126 offset_before_limit = true;
1127 }
1128 limit = Some(self.parse_expr()?);
1129 }
1130 Token::Offset => {
1131 self.advance();
1132 offset = Some(self.parse_expr()?);
1133 }
1134 _ => break,
1135 }
1136 }
1137 self.expect(&Token::LBrace)?;
1138 let mut fields = Vec::new();
1139 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1140 let alias = if matches!(self.peek(), Token::Ident(_))
1141 && matches!(self.tokens.get(self.pos + 1), Some(Token::Colon))
1142 {
1143 let alias = self.expect_named_ident("field alias")?;
1144 self.advance(); Some(alias)
1146 } else {
1147 None
1148 };
1149 let expr = if matches!(self.peek(), Token::Ident(_))
1150 && matches!(self.tokens.get(self.pos + 1), Some(Token::As))
1151 {
1152 if alias.is_none() {
1153 return Err(ParseError::Syntax {
1154 message: "a nested projection needs a field name: \
1155 `<name>: <Table> as <alias> filter ... { ... }`"
1156 .into(),
1157 });
1158 }
1159 Expr::NestedQuery(Box::new(self.parse_nested_query()?))
1160 } else {
1161 self.reject_bare_dotted_path()?;
1162 self.parse_expr()?
1163 };
1164 fields.push(ProjectionField { alias, expr });
1165 if *self.peek() == Token::Comma {
1166 self.advance();
1167 }
1168 }
1169 self.expect(&Token::RBrace)?;
1170 if fields.is_empty() {
1171 return Err(ParseError::Syntax {
1172 message: "nested projection requires at least one field".into(),
1173 });
1174 }
1175 Ok(NestedQuery {
1176 source,
1177 alias,
1178 via_link: None,
1179 filter,
1180 order,
1181 limit,
1182 offset,
1183 offset_before_limit,
1184 fields,
1185 })
1186 }
1187
1188 fn parse_over_clause(&mut self) -> Result<(Vec<Expr>, Vec<OrderKey>), ParseError> {
1191 self.expect(&Token::Over)?;
1192 self.expect(&Token::LParen)?;
1193 let mut partition_by = Vec::new();
1194 let mut order_by = Vec::new();
1195 if *self.peek() == Token::Partition {
1196 self.advance();
1197 loop {
1198 partition_by.push(self.parse_expr()?);
1199 if *self.peek() == Token::Comma {
1200 if !matches!(
1201 self.tokens.get(self.pos + 1),
1202 Some(Token::Order | Token::RParen)
1203 ) {
1204 self.advance();
1205 } else {
1206 break;
1207 }
1208 } else {
1209 break;
1210 }
1211 }
1212 }
1213 if *self.peek() == Token::Order {
1214 self.advance();
1215 loop {
1216 let expr = self.parse_expr()?;
1217 let descending = match self.peek() {
1218 Token::Desc => {
1219 self.advance();
1220 true
1221 }
1222 Token::Asc => {
1223 self.advance();
1224 false
1225 }
1226 _ => false,
1227 };
1228 order_by.push(OrderKey { expr, descending });
1229 if *self.peek() == Token::Comma {
1230 self.advance();
1231 } else {
1232 break;
1233 }
1234 }
1235 }
1236 self.expect(&Token::RParen)?;
1237 Ok((partition_by, order_by))
1238 }
1239
1240 fn parse_cast_type(&mut self) -> Result<CastType, ParseError> {
1242 match self.advance() {
1243 Token::StringLit(s) => match s.as_str() {
1244 "int" | "Int" | "INT" => Ok(CastType::Int),
1245 "float" | "Float" | "FLOAT" => Ok(CastType::Float),
1246 "str" | "Str" | "STR" | "string" | "String" => Ok(CastType::Str),
1247 "bool" | "Bool" | "BOOL" | "boolean" => Ok(CastType::Bool),
1248 "datetime" | "DateTime" | "DATETIME" => Ok(CastType::DateTime),
1249 "uuid" | "Uuid" | "UUID" => Ok(CastType::Uuid),
1250 "bytes" | "Bytes" | "BYTES" | "bytea" => Ok(CastType::Bytes),
1251 other => Err(ParseError::Syntax {
1252 message: format!("invalid cast type: \"{other}\""),
1253 }),
1254 },
1255 t => Err(ParseError::UnexpectedToken {
1256 expected: "string literal for cast type".into(),
1257 got: t.display_name(),
1258 }),
1259 }
1260 }
1261
1262 fn parse_order(&mut self) -> Result<OrderClause, ParseError> {
1263 let mut keys = Vec::new();
1264 loop {
1265 let expr = self.parse_expr()?;
1266 let descending = match self.peek() {
1267 Token::Desc => {
1268 self.advance();
1269 true
1270 }
1271 Token::Asc => {
1272 self.advance();
1273 false
1274 }
1275 _ => false,
1276 };
1277 keys.push(OrderKey { expr, descending });
1278 if *self.peek() == Token::Comma {
1279 self.advance();
1280 } else {
1281 break;
1282 }
1283 }
1284 Ok(OrderClause { keys })
1285 }
1286
1287 fn parse_aggregate_query(&mut self) -> Result<Statement, ParseError> {
1288 let mut func = match self.advance() {
1289 Token::Count => AggFunc::Count,
1290 Token::Avg => AggFunc::Avg,
1291 Token::Sum => AggFunc::Sum,
1292 Token::Min => AggFunc::Min,
1293 Token::Max => AggFunc::Max,
1294 t => {
1295 return Err(ParseError::UnexpectedToken {
1296 expected: "aggregate function".into(),
1297 got: t.display_name(),
1298 })
1299 }
1300 };
1301 self.expect(&Token::LParen)?;
1302 let mode = if *self.peek() == Token::Raw {
1303 self.advance();
1304 AggregateMode::Raw
1305 } else {
1306 AggregateMode::Symmetric
1307 };
1308 if func == AggFunc::Count && *self.peek() == Token::Distinct {
1310 self.advance();
1311 func = AggFunc::CountDistinct;
1312 }
1313 let source = match self.advance() {
1314 Token::Ident(name) => name,
1315 t => {
1316 return Err(ParseError::UnexpectedToken {
1317 expected: "type name".into(),
1318 got: t.display_name(),
1319 })
1320 }
1321 };
1322 let mut query = self.parse_query_tail(source)?;
1326 self.expect(&Token::RParen)?;
1327
1328 let mut argument: Option<Expr> = None;
1335 if func != AggFunc::Count {
1336 if let Some(proj) = &query.projection {
1337 if proj.len() == 1 && proj[0].alias.is_none() {
1338 argument = Some(proj[0].expr.clone());
1339 }
1340 }
1341 if argument.is_some() {
1342 query.projection = None;
1343 }
1344 }
1345 query.aggregation = Some(AggregateExpr {
1346 function: func,
1347 argument,
1348 mode,
1349 });
1350 Ok(Statement::Query(query))
1351 }
1352
1353 fn parse_expr(&mut self) -> Result<Expr, ParseError> {
1354 self.depth += 1;
1355 if self.depth > MAX_NESTING_DEPTH {
1356 self.depth -= 1;
1357 return Err(ParseError::NestingDepthExceeded {
1358 max: MAX_NESTING_DEPTH,
1359 });
1360 }
1361 let result = self.parse_or_expr();
1362 self.depth -= 1;
1363 result
1364 }
1365
1366 fn parse_or_expr(&mut self) -> Result<Expr, ParseError> {
1367 let mut left = self.parse_and_expr()?;
1368 while *self.peek() == Token::Or {
1369 self.advance();
1370 let right = self.parse_and_expr()?;
1371 left = Expr::BinaryOp(Box::new(left), BinOp::Or, Box::new(right));
1372 }
1373 Ok(left)
1374 }
1375
1376 fn parse_and_expr(&mut self) -> Result<Expr, ParseError> {
1377 let mut left = self.parse_comparison()?;
1378 while *self.peek() == Token::And {
1379 self.advance();
1380 let right = self.parse_comparison()?;
1381 left = Expr::BinaryOp(Box::new(left), BinOp::And, Box::new(right));
1382 }
1383 Ok(left)
1384 }
1385
1386 fn parse_comparison(&mut self) -> Result<Expr, ParseError> {
1387 let mut negations = 0usize;
1397 while *self.peek() == Token::Not
1398 && !matches!(self.tokens.get(self.pos + 1), Some(Token::Exists))
1399 {
1400 self.advance();
1401 negations += 1;
1402 if self.depth + negations > MAX_NESTING_DEPTH {
1403 return Err(ParseError::NestingDepthExceeded {
1404 max: MAX_NESTING_DEPTH,
1405 });
1406 }
1407 }
1408 let mut expr = self.parse_comparison_body()?;
1409 for _ in 0..negations {
1410 expr = Expr::UnaryOp(UnaryOp::Not, Box::new(expr));
1411 }
1412 Ok(expr)
1413 }
1414
1415 fn parse_comparison_body(&mut self) -> Result<Expr, ParseError> {
1416 let left = self.parse_additive()?;
1417
1418 if *self.peek() == Token::Is {
1420 self.advance();
1421 if *self.peek() == Token::Not {
1422 self.advance();
1423 self.expect(&Token::Null)?;
1424 return Ok(Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(left)));
1425 } else {
1426 self.expect(&Token::Null)?;
1427 return Ok(Expr::UnaryOp(UnaryOp::IsNull, Box::new(left)));
1428 }
1429 }
1430
1431 match self.peek() {
1434 Token::In => {
1435 self.advance();
1436 return self.parse_in_list(left, false);
1437 }
1438 Token::Like => {
1439 self.advance();
1440 let pattern = self.parse_additive()?;
1441 return Ok(Expr::BinaryOp(
1442 Box::new(left),
1443 BinOp::Like,
1444 Box::new(pattern),
1445 ));
1446 }
1447 Token::Between => {
1448 self.advance();
1449 return self.parse_between(left, false);
1450 }
1451 Token::Not => {
1452 let next = self.tokens.get(self.pos + 1);
1456 match next {
1457 Some(Token::In) => {
1458 self.advance(); self.advance(); return self.parse_in_list(left, true);
1461 }
1462 Some(Token::Like) => {
1463 self.advance(); self.advance(); let pattern = self.parse_additive()?;
1466 let like = Expr::BinaryOp(Box::new(left), BinOp::Like, Box::new(pattern));
1467 return Ok(Expr::UnaryOp(UnaryOp::Not, Box::new(like)));
1468 }
1469 Some(Token::Between) => {
1470 self.advance(); self.advance(); return self.parse_between(left, true);
1473 }
1474 _ => {}
1475 }
1476 }
1477 _ => {}
1478 }
1479
1480 let op = match self.peek() {
1481 Token::Eq => BinOp::Eq,
1482 Token::Neq => BinOp::Neq,
1483 Token::Lt => BinOp::Lt,
1484 Token::Gt => BinOp::Gt,
1485 Token::Lte => BinOp::Lte,
1486 Token::Gte => BinOp::Gte,
1487 _ => return Ok(left),
1488 };
1489 self.advance();
1490 if *self.peek() == Token::Null {
1494 match op {
1495 BinOp::Eq => {
1496 self.advance();
1497 return Ok(Expr::UnaryOp(UnaryOp::IsNull, Box::new(left)));
1498 }
1499 BinOp::Neq => {
1500 self.advance();
1501 return Ok(Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(left)));
1502 }
1503 _ => {}
1504 }
1505 }
1506 let right = self.parse_additive()?;
1507 Ok(Expr::BinaryOp(Box::new(left), op, Box::new(right)))
1508 }
1509
1510 fn parse_in_list(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParseError> {
1515 self.expect(&Token::LParen)?;
1516 if let Token::Ident(_) = self.peek() {
1518 let after = self.tokens.get(self.pos + 1);
1521 let is_subquery = !matches!(after, Some(Token::Comma) | Some(Token::RParen));
1522 if is_subquery {
1523 let source = match self.advance() {
1524 Token::Ident(name) => name,
1525 _ => unreachable!(),
1526 };
1527 let subquery = self.parse_query_tail(source)?;
1528 self.expect(&Token::RParen)?;
1529 return Ok(Expr::InSubquery {
1530 expr: Box::new(expr),
1531 subquery: Box::new(subquery),
1532 negated,
1533 });
1534 }
1535 }
1536 let mut list = Vec::new();
1537 while !matches!(self.peek(), Token::RParen | Token::Eof) {
1538 list.push(self.parse_expr()?);
1539 if *self.peek() == Token::Comma {
1540 self.advance();
1541 }
1542 }
1543 self.expect(&Token::RParen)?;
1544 Ok(Expr::InList {
1545 expr: Box::new(expr),
1546 list,
1547 negated,
1548 })
1549 }
1550
1551 fn try_parse_exists_subquery(&mut self) -> Result<Option<QueryExpr>, ParseError> {
1559 if *self.peek() != Token::LParen {
1560 return Ok(None);
1561 }
1562 let after_lparen = self.tokens.get(self.pos + 1);
1566 if !matches!(after_lparen, Some(Token::Ident(_))) {
1567 return Ok(None);
1568 }
1569 self.expect(&Token::LParen)?;
1570 let source = match self.advance() {
1571 Token::Ident(name) => name,
1572 _ => unreachable!(),
1573 };
1574 let subquery = self.parse_query_tail(source)?;
1575 self.expect(&Token::RParen)?;
1576 Ok(Some(subquery))
1577 }
1578
1579 fn parse_between(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParseError> {
1583 let low = self.parse_additive()?;
1584 self.expect(&Token::And)?;
1585 let high = self.parse_additive()?;
1586 if negated {
1587 Ok(Expr::BinaryOp(
1589 Box::new(Expr::BinaryOp(
1590 Box::new(expr.clone()),
1591 BinOp::Lt,
1592 Box::new(low),
1593 )),
1594 BinOp::Or,
1595 Box::new(Expr::BinaryOp(Box::new(expr), BinOp::Gt, Box::new(high))),
1596 ))
1597 } else {
1598 Ok(Expr::BinaryOp(
1600 Box::new(Expr::BinaryOp(
1601 Box::new(expr.clone()),
1602 BinOp::Gte,
1603 Box::new(low),
1604 )),
1605 BinOp::And,
1606 Box::new(Expr::BinaryOp(Box::new(expr), BinOp::Lte, Box::new(high))),
1607 ))
1608 }
1609 }
1610
1611 fn parse_group_by(&mut self) -> Result<GroupByClause, ParseError> {
1613 let mut keys = Vec::new();
1614 loop {
1615 let expr = self.parse_expr()?;
1616 let output_name = match &expr {
1617 Expr::Field(_) | Expr::QualifiedField { .. } | Expr::JsonPath { .. } => {
1618 expression_output_name(&expr)
1619 }
1620 _ => format!("__group_{}", keys.len()),
1621 };
1622 keys.push(GroupKey { expr, output_name });
1623 if *self.peek() == Token::Comma {
1624 self.advance();
1625 } else {
1626 break;
1627 }
1628 }
1629 if keys.is_empty() {
1630 return Err(ParseError::Syntax {
1631 message: "expected at least one group key after group".into(),
1632 });
1633 }
1634 let having = if *self.peek() == Token::Having {
1635 self.advance();
1636 Some(self.parse_expr()?)
1637 } else {
1638 None
1639 };
1640 Ok(GroupByClause { keys, having })
1641 }
1642
1643 fn parse_additive(&mut self) -> Result<Expr, ParseError> {
1644 let mut left = self.parse_multiplicative()?;
1645 loop {
1646 let op = match self.peek() {
1647 Token::Plus => BinOp::Add,
1648 Token::Minus => BinOp::Sub,
1649 Token::Coalesce => {
1650 self.advance();
1651 let right = self.parse_multiplicative()?;
1652 left = Expr::Coalesce(Box::new(left), Box::new(right));
1653 continue;
1654 }
1655 _ => break,
1656 };
1657 self.advance();
1658 let right = self.parse_multiplicative()?;
1659 left = Expr::BinaryOp(Box::new(left), op, Box::new(right));
1660 }
1661 Ok(left)
1662 }
1663
1664 fn parse_multiplicative(&mut self) -> Result<Expr, ParseError> {
1665 let mut left = self.parse_primary()?;
1666 loop {
1667 let op = match self.peek() {
1668 Token::Star => BinOp::Mul,
1669 Token::Slash => BinOp::Div,
1670 _ => break,
1671 };
1672 self.advance();
1673 let right = self.parse_primary()?;
1674 left = Expr::BinaryOp(Box::new(left), op, Box::new(right));
1675 }
1676 Ok(left)
1677 }
1678
1679 fn parse_primary(&mut self) -> Result<Expr, ParseError> {
1680 self.depth += 1;
1685 if self.depth > MAX_NESTING_DEPTH {
1686 self.depth -= 1;
1687 return Err(ParseError::NestingDepthExceeded {
1688 max: MAX_NESTING_DEPTH,
1689 });
1690 }
1691 let result = self.parse_primary_inner();
1692 self.depth -= 1;
1693 self.parse_json_path_postfix(result?)
1699 }
1700
1701 fn parse_json_path_postfix(&mut self, base: Expr) -> Result<Expr, ParseError> {
1708 if *self.peek() != Token::Arrow {
1709 return Ok(base);
1710 }
1711 match &base {
1712 Expr::Field(_) | Expr::QualifiedField { .. } | Expr::JsonPath { .. } => {}
1713 _ => {
1714 return Err(ParseError::Syntax {
1715 message: "'->' JSON path access requires a field base \
1716 (e.g. .data->key or posts.data->author)"
1717 .into(),
1718 })
1719 }
1720 }
1721 let mut segments = Vec::new();
1722 while *self.peek() == Token::Arrow {
1723 self.advance(); let seg = match self.advance() {
1725 Token::Ident(name) => PathSeg::Key(name),
1727 Token::StringLit(s) => PathSeg::Key(s),
1730 Token::IntLit(v) => {
1734 let idx = u32::try_from(v).map_err(|_| ParseError::Syntax {
1735 message: format!(
1736 "invalid JSON path array index {v}: expected a non-negative integer that fits in 32 bits"
1737 ),
1738 })?;
1739 PathSeg::Index(idx)
1740 }
1741 other => {
1742 return Err(ParseError::Syntax {
1743 message: format!(
1744 "expected a JSON path segment (object key or array index) after '->', found {}",
1745 other.display_name()
1746 ),
1747 })
1748 }
1749 };
1750 segments.push(seg);
1751 }
1752 if let Expr::JsonPath {
1755 base: inner_base,
1756 segments: mut inner_segments,
1757 } = base
1758 {
1759 inner_segments.extend(segments);
1760 return Ok(Expr::JsonPath {
1761 base: inner_base,
1762 segments: inner_segments,
1763 });
1764 }
1765 Ok(Expr::JsonPath {
1766 base: Box::new(base),
1767 segments,
1768 })
1769 }
1770
1771 fn parse_primary_inner(&mut self) -> Result<Expr, ParseError> {
1772 match self.peek().clone() {
1773 Token::DotIdent(name) => {
1774 self.advance();
1775 Ok(Expr::Field(name))
1776 }
1777 Token::IntLit(v) => {
1778 self.advance();
1779 Ok(Expr::Literal(Literal::Int(v)))
1780 }
1781 Token::FloatLit(v) => {
1782 self.advance();
1783 Ok(Expr::Literal(Literal::Float(v)))
1784 }
1785 Token::StringLit(v) => {
1786 self.advance();
1787 Ok(Expr::Literal(Literal::String(v)))
1788 }
1789 Token::BoolLit(v) => {
1790 self.advance();
1791 Ok(Expr::Literal(Literal::Bool(v)))
1792 }
1793 Token::Null => {
1800 self.advance();
1801 Ok(Expr::Null)
1802 }
1803 Token::Not => {
1804 self.advance();
1805 if *self.peek() == Token::Exists {
1806 self.advance();
1807 if let Some(sub) = self.try_parse_exists_subquery()? {
1811 return Ok(Expr::ExistsSubquery {
1812 subquery: Box::new(sub),
1813 negated: true,
1814 });
1815 }
1816 let expr = self.parse_primary()?;
1817 Ok(Expr::UnaryOp(UnaryOp::NotExists, Box::new(expr)))
1818 } else {
1819 let expr = self.parse_primary()?;
1820 Ok(Expr::UnaryOp(UnaryOp::Not, Box::new(expr)))
1821 }
1822 }
1823 Token::Exists => {
1824 self.advance();
1825 if let Some(sub) = self.try_parse_exists_subquery()? {
1829 return Ok(Expr::ExistsSubquery {
1830 subquery: Box::new(sub),
1831 negated: false,
1832 });
1833 }
1834 let expr = self.parse_primary()?;
1835 Ok(Expr::UnaryOp(UnaryOp::Exists, Box::new(expr)))
1836 }
1837 Token::LParen => {
1838 self.advance();
1839 let expr = self.parse_expr()?;
1840 self.expect(&Token::RParen)?;
1841 Ok(expr)
1842 }
1843 Token::Ident(name) => {
1844 self.advance();
1845 if *self.peek() == Token::LParen {
1850 let cast_type = match name.as_str() {
1851 "uuid" => Some(CastType::Uuid),
1852 "bytes" => Some(CastType::Bytes),
1853 _ => None,
1854 };
1855 if let Some(cast_type) = cast_type {
1856 self.advance(); let inner = self.parse_expr()?;
1858 self.expect(&Token::RParen)?;
1859 return Ok(Expr::Cast(Box::new(inner), cast_type));
1860 }
1861 }
1862 if let Token::DotIdent(field) = self.peek().clone() {
1866 self.advance();
1867 return Ok(Expr::QualifiedField {
1868 qualifier: name,
1869 field,
1870 });
1871 }
1872 Ok(Expr::Field(name))
1873 }
1874 Token::RowNumber | Token::Rank | Token::DenseRank => {
1876 let wfunc = match self.advance() {
1877 Token::RowNumber => WindowFunc::RowNumber,
1878 Token::Rank => WindowFunc::Rank,
1879 Token::DenseRank => WindowFunc::DenseRank,
1880 _ => {
1881 return Err(ParseError::Syntax {
1882 message: "unexpected window function token".into(),
1883 })
1884 }
1885 };
1886 self.expect(&Token::LParen)?;
1887 self.expect(&Token::RParen)?;
1888 let (partition_by, order_by) = self.parse_over_clause()?;
1889 Ok(Expr::Window {
1890 function: wfunc,
1891 args: vec![],
1892 mode: AggregateMode::Symmetric,
1893 partition_by,
1894 order_by,
1895 })
1896 }
1897 Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
1901 let mut func = match self.advance() {
1902 Token::Count => AggFunc::Count,
1903 Token::Avg => AggFunc::Avg,
1904 Token::Sum => AggFunc::Sum,
1905 Token::Min => AggFunc::Min,
1906 Token::Max => AggFunc::Max,
1907 _ => {
1908 return Err(ParseError::Syntax {
1909 message: "unexpected aggregate token".into(),
1910 })
1911 }
1912 };
1913 self.expect(&Token::LParen)?;
1914 let mode = if *self.peek() == Token::Raw {
1915 self.advance();
1916 AggregateMode::Raw
1917 } else {
1918 AggregateMode::Symmetric
1919 };
1920 if func == AggFunc::Count && *self.peek() == Token::Star {
1922 self.advance();
1923 self.expect(&Token::RParen)?;
1924 if *self.peek() == Token::Over {
1926 let (partition_by, order_by) = self.parse_over_clause()?;
1927 return Ok(Expr::Window {
1928 function: WindowFunc::Count,
1929 args: vec![Expr::Field("*".into())],
1930 mode,
1931 partition_by,
1932 order_by,
1933 });
1934 }
1935 return Ok(Expr::FunctionCall(
1936 AggFunc::Count,
1937 Box::new(Expr::Field("*".into())),
1938 mode,
1939 ));
1940 }
1941 if func == AggFunc::Count && *self.peek() == Token::Distinct {
1943 self.advance();
1944 func = AggFunc::CountDistinct;
1945 }
1946 let inner = self.parse_expr()?;
1947 self.expect(&Token::RParen)?;
1948 if *self.peek() == Token::Over {
1950 let wfunc = match func {
1951 AggFunc::Count => WindowFunc::Count,
1952 AggFunc::Avg => WindowFunc::Avg,
1953 AggFunc::Sum => WindowFunc::Sum,
1954 AggFunc::Min => WindowFunc::Min,
1955 AggFunc::Max => WindowFunc::Max,
1956 _ => {
1957 return Err(ParseError::Unsupported {
1958 feature: "count(distinct ...) over (...) is not supported".into(),
1959 })
1960 }
1961 };
1962 let (partition_by, order_by) = self.parse_over_clause()?;
1963 return Ok(Expr::Window {
1964 function: wfunc,
1965 args: vec![inner],
1966 mode,
1967 partition_by,
1968 order_by,
1969 });
1970 }
1971 Ok(Expr::FunctionCall(func, Box::new(inner), mode))
1972 }
1973 Token::Upper
1974 | Token::Lower
1975 | Token::Length
1976 | Token::Trim
1977 | Token::Substring
1978 | Token::Concat
1979 | Token::Abs
1980 | Token::Round
1981 | Token::Ceil
1982 | Token::Floor
1983 | Token::Sqrt
1984 | Token::Pow
1985 | Token::Now
1986 | Token::Extract
1987 | Token::DateAdd
1988 | Token::DateDiff
1989 | Token::JsonType
1990 | Token::JsonText => {
1991 let tok = self.advance();
1992 let func = token_to_scalar_fn(&tok);
1993 self.expect(&Token::LParen)?;
1994 let mut args = Vec::new();
1995 while !matches!(self.peek(), Token::RParen | Token::Eof) {
1996 args.push(self.parse_expr()?);
1997 if *self.peek() == Token::Comma {
1998 self.advance();
1999 }
2000 }
2001 self.expect(&Token::RParen)?;
2002 Ok(Expr::ScalarFunc(func, args))
2003 }
2004 Token::Cast => {
2005 self.advance();
2006 self.expect(&Token::LParen)?;
2007 let inner = self.parse_expr()?;
2008 self.expect(&Token::Comma)?;
2009 let cast_type = self.parse_cast_type()?;
2010 self.expect(&Token::RParen)?;
2011 Ok(Expr::Cast(Box::new(inner), cast_type))
2012 }
2013 Token::Case => {
2014 self.advance();
2015 let mut whens = Vec::new();
2016 while *self.peek() == Token::When {
2017 self.advance();
2018 let condition = self.parse_expr()?;
2019 self.expect(&Token::Then)?;
2020 let result = self.parse_expr()?;
2021 whens.push((Box::new(condition), Box::new(result)));
2022 }
2023 let else_expr = if *self.peek() == Token::Else {
2024 self.advance();
2025 Some(Box::new(self.parse_expr()?))
2026 } else {
2027 None
2028 };
2029 self.expect(&Token::End)?;
2030 Ok(Expr::Case { whens, else_expr })
2031 }
2032 t => Err(ParseError::Syntax {
2033 message: format!("unexpected token in expression: {}", t.display_name()),
2034 }),
2035 }
2036 }
2037
2038 fn parse_create_link(&mut self) -> Result<Statement, ParseError> {
2046 self.expect(&Token::Link)?;
2047 let owner = self.expect_named_ident("link owner type")?;
2048 let name = match self.advance() {
2049 Token::DotIdent(n) => n,
2050 t => {
2051 return Err(ParseError::UnexpectedToken {
2052 expected: "`.<name>` after the owner type (link <Owner>.<name> -> ...)".into(),
2053 got: t.display_name(),
2054 })
2055 }
2056 };
2057 let (target, local_key, target_key) = self.parse_link_tail()?;
2058 Ok(Statement::CreateLink(CreateLinkExpr {
2059 owner,
2060 name,
2061 target,
2062 local_key,
2063 target_key,
2064 }))
2065 }
2066
2067 fn parse_link_tail(&mut self) -> Result<(String, String, String), ParseError> {
2070 self.expect(&Token::Arrow)?;
2071 let target = self.expect_named_ident("link target type")?;
2072 self.expect(&Token::On)?;
2073 let local_key = self.parse_link_column("link local key")?;
2074 self.expect(&Token::Eq)?;
2075 let target_key = self.parse_link_column("link target key")?;
2076 Ok((target, local_key, target_key))
2077 }
2078
2079 fn parse_link_column(&mut self, context: &str) -> Result<String, ParseError> {
2082 match self.advance() {
2083 Token::Ident(n) | Token::DotIdent(n) => Ok(n),
2084 t => Err(self.named_ident_error(context, &t)),
2085 }
2086 }
2087
2088 fn parse_alter_table(&mut self) -> Result<Statement, ParseError> {
2089 self.expect(&Token::Alter)?;
2090 let table = match self.advance() {
2091 Token::Ident(name) => name,
2092 t => {
2093 return Err(ParseError::UnexpectedToken {
2094 expected: "table name after alter".into(),
2095 got: t.display_name(),
2096 })
2097 }
2098 };
2099 match self.peek() {
2100 Token::Add => {
2101 self.advance();
2102 if *self.peek() == Token::Index {
2104 self.advance();
2105 let if_not_exists = self.parse_optional_if_not_exists();
2106 let target = self.parse_index_target("add index")?;
2107 return Ok(Statement::AlterTable(AlterTableExpr {
2108 table,
2109 action: AlterAction::AddIndex {
2110 target,
2111 if_not_exists,
2112 },
2113 }));
2114 }
2115 if *self.peek() == Token::Unique {
2117 self.advance();
2118 let if_not_exists = self.parse_optional_if_not_exists();
2119 let target = self.parse_index_target("add unique")?;
2120 return Ok(Statement::AlterTable(AlterTableExpr {
2121 table,
2122 action: AlterAction::AddUnique {
2123 target,
2124 if_not_exists,
2125 },
2126 }));
2127 }
2128 if *self.peek() == Token::Link {
2130 self.advance();
2131 let name = self.expect_named_ident("link name")?;
2132 let (target, local_key, target_key) = self.parse_link_tail()?;
2133 return Ok(Statement::AlterTable(AlterTableExpr {
2134 table,
2135 action: AlterAction::AddLink {
2136 name,
2137 target,
2138 local_key,
2139 target_key,
2140 },
2141 }));
2142 }
2143 if *self.peek() == Token::Column {
2145 self.advance();
2146 }
2147 let required = if *self.peek() == Token::Required {
2148 self.advance();
2149 true
2150 } else {
2151 false
2152 };
2153 let name = self.expect_named_ident("column name")?;
2154 self.expect(&Token::Colon)?;
2155 let type_name = match self.advance() {
2156 Token::Ident(n) => n,
2157 t => {
2158 return Err(ParseError::UnexpectedToken {
2159 expected: "type name".into(),
2160 got: t.display_name(),
2161 })
2162 }
2163 };
2164 Ok(Statement::AlterTable(AlterTableExpr {
2165 table,
2166 action: AlterAction::AddColumn {
2167 name,
2168 type_name,
2169 required,
2170 },
2171 }))
2172 }
2173 Token::Drop => {
2174 self.advance();
2175 if *self.peek() == Token::Index {
2176 self.advance();
2177 let if_exists = self.parse_optional_if_exists();
2178 let target = self.parse_index_target("drop index")?;
2179 return Ok(Statement::AlterTable(AlterTableExpr {
2180 table,
2181 action: AlterAction::DropIndex { target, if_exists },
2182 }));
2183 }
2184 if *self.peek() == Token::Column {
2186 self.advance();
2187 }
2188 let if_exists = self.parse_optional_if_exists();
2189 let name = self.expect_named_ident("column name")?;
2190 Ok(Statement::AlterTable(AlterTableExpr {
2191 table,
2192 action: AlterAction::DropColumn { name, if_exists },
2193 }))
2194 }
2195 t => Err(ParseError::UnexpectedToken {
2196 expected: "add or drop after alter <table>".into(),
2197 got: t.display_name(),
2198 }),
2199 }
2200 }
2201
2202 fn parse_index_target(&mut self, action: &str) -> Result<IndexTarget, ParseError> {
2207 match self.peek() {
2208 Token::DotIdent(_) => {
2209 if matches!(self.tokens.get(self.pos + 1), Some(Token::Arrow)) {
2210 return Err(ParseError::Syntax {
2211 message: format!(
2212 "JSON path index targets must be parenthesized after {action}; use `(.data->key)`"
2213 ),
2214 });
2215 }
2216 let Token::DotIdent(column) = self.advance() else {
2217 unreachable!("guarded by DotIdent match")
2218 };
2219 Ok(IndexTarget::Column(column))
2220 }
2221 Token::LParen => {
2222 self.advance();
2223 let expr = self.parse_expr().map_err(|error| match error {
2224 ParseError::NestingDepthExceeded { .. } => error,
2225 _ => ParseError::Syntax {
2226 message: format!(
2227 "invalid expression index target after {action}: expected an unqualified JSON path like `(.data->key)`"
2228 ),
2229 },
2230 })?;
2231 if *self.peek() != Token::RParen {
2232 return Err(ParseError::Syntax {
2233 message: format!(
2234 "invalid expression index target after {action}: only a direct JSON path is supported"
2235 ),
2236 });
2237 }
2238 self.advance();
2239
2240 match JsonPathIdentityV1::from_expr(&expr) {
2241 Some(identity) => identity.bind_table_local(None).map(IndexTarget::JsonPath).ok_or_else(|| {
2242 ParseError::Syntax {
2243 message: format!(
2244 "qualified JSON paths are not valid index targets after {action}; use an unqualified table-local path like `(.data->key)`"
2245 ),
2246 }
2247 }),
2248 None => match expr {
2249 Expr::Field(_) => Err(ParseError::Syntax {
2250 message: format!(
2251 "invalid expression index target after {action}: parentheses are reserved for a direct JSON path like `(.data->key)`; use `.column` for a stored column"
2252 ),
2253 }),
2254 Expr::QualifiedField { .. } => Err(ParseError::Syntax {
2255 message: format!(
2256 "qualified references are not valid index targets after {action}; use a table-local `.column` or `(.data->key)`"
2257 ),
2258 }),
2259 _ => Err(ParseError::Syntax {
2260 message: format!(
2261 "invalid expression index target after {action}: only a direct JSON path is supported"
2262 ),
2263 }),
2264 },
2265 }
2266 }
2267 token => Err(ParseError::UnexpectedToken {
2268 expected: format!(".<column> or parenthesized JSON path after {action}"),
2269 got: token.display_name(),
2270 }),
2271 }
2272 }
2273
2274 fn parse_drop_or_drop_view(&mut self) -> Result<Statement, ParseError> {
2276 self.expect(&Token::Drop)?;
2277 if *self.peek() == Token::View {
2278 self.advance(); let if_exists = self.parse_optional_if_exists();
2280 let name = match self.advance() {
2281 Token::Ident(name) => name,
2282 t => {
2283 return Err(ParseError::UnexpectedToken {
2284 expected: "view name after drop view".into(),
2285 got: t.display_name(),
2286 })
2287 }
2288 };
2289 return Ok(Statement::DropView(DropViewExpr { name, if_exists }));
2290 }
2291 let if_exists = self.parse_optional_if_exists();
2292 let table = match self.advance() {
2293 Token::Ident(name) => name,
2294 t => {
2295 return Err(ParseError::UnexpectedToken {
2296 expected: "table name after drop".into(),
2297 got: t.display_name(),
2298 })
2299 }
2300 };
2301 Ok(Statement::DropTable(DropTableExpr { table, if_exists }))
2302 }
2303
2304 fn parse_create_view(&mut self) -> Result<Statement, ParseError> {
2309 self.expect(&Token::Materialized)?;
2310 let name = match self.advance() {
2311 Token::Ident(name) => name,
2312 t => {
2313 return Err(ParseError::UnexpectedToken {
2314 expected: "view name after materialize".into(),
2315 got: t.display_name(),
2316 })
2317 }
2318 };
2319 self.expect(&Token::As)?;
2320 let query_start = self.pos;
2322 let source = match self.advance() {
2323 Token::Ident(s) => s,
2324 t => {
2325 return Err(ParseError::UnexpectedToken {
2326 expected: "source table name".into(),
2327 got: t.display_name(),
2328 })
2329 }
2330 };
2331 let query = self.parse_query_tail(source)?;
2332 let query_text = tokens_to_text(&self.tokens[query_start..self.pos]);
2334 Ok(Statement::CreateView(CreateViewExpr {
2335 name,
2336 query,
2337 query_text,
2338 }))
2339 }
2340
2341 fn maybe_parse_union(&mut self, left: Statement) -> Result<Statement, ParseError> {
2344 if *self.peek() != Token::Union {
2345 return Ok(left);
2346 }
2347 if !matches!(left, Statement::Query(_) | Statement::Union(_)) {
2348 return Err(ParseError::Syntax {
2349 message: "UNION requires a query on the left side".into(),
2350 });
2351 }
2352 self.advance(); let all = if let Token::Ident(s) = self.peek() {
2354 if s == "all" {
2355 self.advance();
2356 true
2357 } else {
2358 false
2359 }
2360 } else {
2361 false
2362 };
2363 let right = self.parse_single_query()?;
2365 let union = Statement::Union(UnionExpr {
2366 left: Box::new(left),
2367 right: Box::new(right),
2368 all,
2369 });
2370 self.maybe_parse_union(union)
2372 }
2373
2374 fn parse_single_query(&mut self) -> Result<Statement, ParseError> {
2376 match self.peek() {
2377 Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
2378 self.parse_aggregate_query()
2379 }
2380 Token::Ident(_) => self.parse_query_or_mutation(),
2381 _ => Err(ParseError::Syntax {
2382 message: format!(
2383 "expected query after UNION, got {}",
2384 self.peek().display_name()
2385 ),
2386 }),
2387 }
2388 }
2389
2390 fn parse_refresh_view(&mut self) -> Result<Statement, ParseError> {
2392 self.expect(&Token::Refresh)?;
2393 let name = match self.advance() {
2394 Token::Ident(name) => name,
2395 t => {
2396 return Err(ParseError::UnexpectedToken {
2397 expected: "view name after refresh".into(),
2398 got: t.display_name(),
2399 })
2400 }
2401 };
2402 Ok(Statement::RefreshView(RefreshViewExpr { name }))
2403 }
2404
2405 fn parse_create_type(&mut self) -> Result<Statement, ParseError> {
2406 self.expect(&Token::Type)?;
2407 let name = self.expect_named_ident("type name")?;
2408 let if_not_exists = self.parse_optional_if_not_exists();
2409 self.expect(&Token::LBrace)?;
2410 let mut fields = Vec::new();
2411 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
2412 let (mut required, mut unique, mut auto) = (false, false, false);
2417 loop {
2418 let is_modifier =
2419 matches!(self.peek(), Token::Required | Token::Unique | Token::Auto)
2420 && !matches!(self.tokens.get(self.pos + 1), Some(Token::Colon));
2421 if !is_modifier {
2422 break;
2423 }
2424 match self.advance() {
2425 Token::Required => required = true,
2426 Token::Unique => unique = true,
2427 Token::Auto => auto = true,
2428 _ => unreachable!("guarded by is_modifier"),
2429 }
2430 }
2431 let field_name = self.expect_named_ident("field name")?;
2432 self.expect(&Token::Colon)?;
2433 let type_name = match self.advance() {
2434 Token::Ident(n) => n,
2435 t => {
2436 return Err(ParseError::UnexpectedToken {
2437 expected: "type name".into(),
2438 got: t.display_name(),
2439 })
2440 }
2441 };
2442 let default = if *self.peek() == Token::Default {
2445 self.advance();
2446 Some(self.parse_default_literal()?)
2447 } else {
2448 None
2449 };
2450 fields.push(FieldDef {
2451 name: field_name,
2452 type_name,
2453 required,
2454 unique,
2455 default,
2456 auto,
2457 });
2458 if *self.peek() == Token::Comma {
2459 self.advance();
2460 }
2461 }
2462 self.expect(&Token::RBrace)?;
2463 Ok(Statement::CreateType(CreateTypeExpr {
2464 name,
2465 fields,
2466 if_not_exists,
2467 }))
2468 }
2469
2470 fn parse_schema(&mut self) -> Result<Statement, ParseError> {
2473 self.expect(&Token::Schema)?;
2474 if let Token::Ident(name) = self.peek() {
2475 if name == "links" {
2476 self.advance();
2477 return Ok(Statement::ListLinks);
2478 }
2479 let table = self.expect_named_ident("type name")?;
2480 return Ok(Statement::Describe(table));
2481 }
2482 Ok(Statement::ListTypes)
2483 }
2484
2485 fn parse_describe(&mut self) -> Result<Statement, ParseError> {
2487 self.expect(&Token::Describe)?;
2488 let table = self.expect_named_ident("type name")?;
2489 Ok(Statement::Describe(table))
2490 }
2491
2492 fn parse_default_literal(&mut self) -> Result<Literal, ParseError> {
2496 match self.advance() {
2497 Token::IntLit(v) => Ok(Literal::Int(v)),
2498 Token::FloatLit(v) => Ok(Literal::Float(v)),
2499 Token::StringLit(v) => Ok(Literal::String(v)),
2500 Token::BoolLit(v) => Ok(Literal::Bool(v)),
2501 t => Err(ParseError::UnexpectedToken {
2502 expected: "literal default value".into(),
2503 got: t.display_name(),
2504 }),
2505 }
2506 }
2507}
2508
2509fn qualify_bare_fields(expr: Expr, alias: &str) -> Expr {
2516 let recur = |e: Expr| Box::new(qualify_bare_fields(e, alias));
2517 match expr {
2518 Expr::Field(field) => Expr::QualifiedField {
2519 qualifier: alias.to_string(),
2520 field,
2521 },
2522 Expr::BinaryOp(l, op, r) => Expr::BinaryOp(recur(*l), op, recur(*r)),
2523 Expr::UnaryOp(op, inner) => Expr::UnaryOp(op, recur(*inner)),
2524 Expr::Coalesce(l, r) => Expr::Coalesce(recur(*l), recur(*r)),
2525 Expr::Cast(inner, ty) => Expr::Cast(recur(*inner), ty),
2526 Expr::ScalarFunc(func, args) => Expr::ScalarFunc(
2527 func,
2528 args.into_iter()
2529 .map(|a| qualify_bare_fields(a, alias))
2530 .collect(),
2531 ),
2532 Expr::InList {
2533 expr,
2534 list,
2535 negated,
2536 } => Expr::InList {
2537 expr: recur(*expr),
2538 list: list
2539 .into_iter()
2540 .map(|a| qualify_bare_fields(a, alias))
2541 .collect(),
2542 negated,
2543 },
2544 Expr::Case { whens, else_expr } => Expr::Case {
2545 whens: whens
2546 .into_iter()
2547 .map(|(c, r)| (recur(*c), recur(*r)))
2548 .collect(),
2549 else_expr: else_expr.map(|e| recur(*e)),
2550 },
2551 Expr::JsonPath { base, segments } => Expr::JsonPath {
2552 base: recur(*base),
2553 segments,
2554 },
2555 other => other,
2557 }
2558}
2559
2560fn tokens_to_text(tokens: &[Token]) -> String {
2564 let mut out = String::with_capacity(64);
2565 for tok in tokens {
2566 if !out.is_empty() && !matches!(tok, Token::Eof) {
2567 out.push(' ');
2568 }
2569 match tok {
2570 Token::Ident(s) => out.push_str(s),
2571 Token::DotIdent(s) => {
2572 out.push('.');
2573 out.push_str(s);
2574 }
2575 Token::IntLit(v) => out.push_str(&v.to_string()),
2576 Token::FloatLit(v) => out.push_str(&v.to_string()),
2577 Token::StringLit(s) => {
2578 out.push('"');
2579 out.push_str(s);
2580 out.push('"');
2581 }
2582 Token::BoolLit(v) => out.push_str(if *v { "true" } else { "false" }),
2583 Token::Param(s) => {
2584 out.push('$');
2585 out.push_str(s);
2586 }
2587 Token::Type => out.push_str("type"),
2588 Token::Filter => out.push_str("filter"),
2589 Token::Order => out.push_str("order"),
2590 Token::Limit => out.push_str("limit"),
2591 Token::Offset => out.push_str("offset"),
2592 Token::Insert => out.push_str("insert"),
2593 Token::Update => out.push_str("update"),
2594 Token::Delete => out.push_str("delete"),
2595 Token::Upsert => out.push_str("upsert"),
2596 Token::Returning => out.push_str("returning"),
2597 Token::Conflict => out.push_str("conflict"),
2598 Token::Select => out.push_str("select"),
2599 Token::Required => out.push_str("required"),
2600 Token::Default => out.push_str("default"),
2601 Token::Auto => out.push_str("auto"),
2602 Token::Multi => out.push_str("multi"),
2603 Token::Link => out.push_str("link"),
2604 Token::Index => out.push_str("index"),
2605 Token::Unique => out.push_str("unique"),
2606 Token::On => out.push_str("on"),
2607 Token::Asc => out.push_str("asc"),
2608 Token::Desc => out.push_str("desc"),
2609 Token::And => out.push_str("and"),
2610 Token::Or => out.push_str("or"),
2611 Token::Not => out.push_str("not"),
2612 Token::Exists => out.push_str("exists"),
2613 Token::Let => out.push_str("let"),
2614 Token::As => out.push_str("as"),
2615 Token::Match => out.push_str("match"),
2616 Token::Group => out.push_str("group"),
2617 Token::Join => out.push_str("join"),
2618 Token::Inner => out.push_str("inner"),
2619 Token::LeftKw => out.push_str("left"),
2620 Token::RightKw => out.push_str("right"),
2621 Token::Outer => out.push_str("outer"),
2622 Token::Cross => out.push_str("cross"),
2623 Token::Transaction => out.push_str("transaction"),
2624 Token::Begin => out.push_str("begin"),
2625 Token::Commit => out.push_str("commit"),
2626 Token::Rollback => out.push_str("rollback"),
2627 Token::View => out.push_str("view"),
2628 Token::Materialized => out.push_str("materialized"),
2629 Token::Refresh => out.push_str("refresh"),
2630 Token::Union => out.push_str("union"),
2631 Token::Having => out.push_str("having"),
2632 Token::Distinct => out.push_str("distinct"),
2633 Token::In => out.push_str("in"),
2634 Token::Between => out.push_str("between"),
2635 Token::Like => out.push_str("like"),
2636 Token::Count => out.push_str("count"),
2637 Token::Avg => out.push_str("avg"),
2638 Token::Sum => out.push_str("sum"),
2639 Token::Raw => out.push_str("raw"),
2640 Token::Min => out.push_str("min"),
2641 Token::Max => out.push_str("max"),
2642 Token::Is => out.push_str("is"),
2643 Token::Null => out.push_str("null"),
2644 Token::Upper => out.push_str("upper"),
2645 Token::Lower => out.push_str("lower"),
2646 Token::Length => out.push_str("length"),
2647 Token::Trim => out.push_str("trim"),
2648 Token::Substring => out.push_str("substring"),
2649 Token::Concat => out.push_str("concat"),
2650 Token::Abs => out.push_str("abs"),
2651 Token::Round => out.push_str("round"),
2652 Token::Ceil => out.push_str("ceil"),
2653 Token::Floor => out.push_str("floor"),
2654 Token::Sqrt => out.push_str("sqrt"),
2655 Token::Pow => out.push_str("pow"),
2656 Token::Now => out.push_str("now"),
2657 Token::Extract => out.push_str("extract"),
2658 Token::DateAdd => out.push_str("date_add"),
2659 Token::DateDiff => out.push_str("date_diff"),
2660 Token::JsonType => out.push_str("json_type"),
2661 Token::JsonText => out.push_str("json_text"),
2662 Token::Cast => out.push_str("cast"),
2663 Token::Case => out.push_str("case"),
2664 Token::When => out.push_str("when"),
2665 Token::Then => out.push_str("then"),
2666 Token::Else => out.push_str("else"),
2667 Token::End => out.push_str("end"),
2668 Token::Over => out.push_str("over"),
2669 Token::Partition => out.push_str("partition"),
2670 Token::RowNumber => out.push_str("row_number"),
2671 Token::Rank => out.push_str("rank"),
2672 Token::DenseRank => out.push_str("dense_rank"),
2673 Token::Alter => out.push_str("alter"),
2674 Token::Drop => out.push_str("drop"),
2675 Token::Add => out.push_str("add"),
2676 Token::Column => out.push_str("column"),
2677 Token::Eq => out.push('='),
2678 Token::Neq => out.push_str("!="),
2679 Token::Lt => out.push('<'),
2680 Token::Gt => out.push('>'),
2681 Token::Lte => out.push_str("<="),
2682 Token::Gte => out.push_str(">="),
2683 Token::Assign => out.push_str(":="),
2684 Token::Arrow => out.push_str("->"),
2685 Token::Pipe => out.push('|'),
2686 Token::Coalesce => out.push_str("??"),
2687 Token::Plus => out.push('+'),
2688 Token::Minus => out.push('-'),
2689 Token::Star => out.push('*'),
2690 Token::Slash => out.push('/'),
2691 Token::LBrace => out.push('{'),
2692 Token::RBrace => out.push('}'),
2693 Token::LParen => out.push('('),
2694 Token::RParen => out.push(')'),
2695 Token::Comma => out.push(','),
2696 Token::Colon => out.push(':'),
2697 Token::Dot => out.push('.'),
2698 Token::Explain => out.push_str("explain"),
2699 Token::Schema => out.push_str("schema"),
2700 Token::Describe => out.push_str("describe"),
2701 Token::Eof => {}
2702 }
2703 }
2704 out
2705}
2706
2707#[cfg(test)]
2708mod tests {
2709 use super::*;
2710 #[test]
2711 fn test_parse_simple_query() {
2712 let stmt = parse("User").unwrap();
2713 match stmt {
2714 Statement::Query(q) => {
2715 assert_eq!(q.source, "User");
2716 assert!(q.filter.is_none());
2717 assert!(q.projection.is_none());
2718 }
2719 _ => panic!("expected query"),
2720 }
2721 }
2722
2723 #[test]
2724 fn test_parse_filter() {
2725 let stmt = parse("User filter .age > 30").unwrap();
2726 match stmt {
2727 Statement::Query(q) => {
2728 assert_eq!(q.source, "User");
2729 assert!(q.filter.is_some());
2730 }
2731 _ => panic!("expected query"),
2732 }
2733 }
2734
2735 #[test]
2736 fn test_parse_projection() {
2737 let stmt = parse("User { name, email }").unwrap();
2738 match stmt {
2739 Statement::Query(q) => {
2740 let proj = q.projection.unwrap();
2741 assert_eq!(proj.len(), 2);
2742 }
2743 _ => panic!("expected query"),
2744 }
2745 }
2746
2747 #[test]
2748 fn test_bare_dotted_path_projection_is_a_parse_error() {
2749 for q in [
2753 "Order { .user.name }",
2754 "Order { .id, uname: .user.name }",
2755 "Order { uname: .user.company.name }",
2756 ] {
2757 let err = parse(q).unwrap_err();
2758 let msg = err.to_string();
2759 assert!(
2760 msg.contains("alias the table"),
2761 "`{q}` should error with alias guidance, got: {msg}"
2762 );
2763 }
2764 }
2765
2766 #[test]
2767 fn test_aliased_scalar_link_path_parses_as_one_field() {
2768 let stmt = parse("Order as o { uname: o.user.name }").unwrap();
2769 match stmt {
2770 Statement::Query(q) => {
2771 let proj = q.projection.unwrap();
2772 assert_eq!(proj.len(), 1);
2773 match &proj[0].expr {
2774 Expr::LinkPath {
2775 outer_alias,
2776 links,
2777 column,
2778 } => {
2779 assert_eq!(outer_alias, "o");
2780 assert_eq!(links, &["user".to_string()]);
2781 assert_eq!(column, "name");
2782 }
2783 other => panic!("expected LinkPath, got {other:?}"),
2784 }
2785 }
2786 _ => panic!("expected query"),
2787 }
2788 }
2789
2790 #[test]
2791 fn test_parse_filter_order_limit() {
2792 let stmt = parse("User filter .age > 30 order .name desc limit 10").unwrap();
2793 match stmt {
2794 Statement::Query(q) => {
2795 assert!(q.filter.is_some());
2796 let order = q.order.unwrap();
2797 assert_eq!(order.keys.len(), 1);
2798 assert_eq!(order.keys[0].expr, Expr::Field("name".into()));
2799 assert!(order.keys[0].descending);
2800 assert!(q.limit.is_some());
2801 }
2802 _ => panic!("expected query"),
2803 }
2804 }
2805
2806 #[test]
2807 fn test_parse_insert() {
2808 let stmt = parse(r#"insert User { name := "Alice", age := 30 }"#).unwrap();
2809 match stmt {
2810 Statement::Insert(ins) => {
2811 assert_eq!(ins.target, "User");
2812 assert_eq!(ins.rows.len(), 1);
2813 assert_eq!(ins.rows[0].len(), 2);
2814 assert_eq!(ins.rows[0][0].field, "name");
2815 assert_eq!(ins.rows[0][1].field, "age");
2816 }
2817 _ => panic!("expected insert"),
2818 }
2819 }
2820
2821 #[test]
2822 fn test_parse_insert_multi_row() {
2823 let stmt =
2824 parse(r#"insert User { name := "Alice", age := 30 }, { name := "Bob", age := 25 }, { name := "Cy" }"#)
2825 .unwrap();
2826 match stmt {
2827 Statement::Insert(ins) => {
2828 assert_eq!(ins.target, "User");
2829 assert_eq!(ins.rows.len(), 3);
2830 assert_eq!(ins.rows[0].len(), 2);
2831 assert_eq!(ins.rows[1][0].field, "name");
2832 assert_eq!(ins.rows[2].len(), 1);
2833 assert_eq!(ins.rows[2][0].field, "name");
2834 }
2835 _ => panic!("expected insert"),
2836 }
2837 }
2838
2839 #[test]
2840 fn test_parse_update() {
2841 let stmt = parse(r#"User filter .email = "alice@ex.com" update { age := 31 }"#).unwrap();
2842 match stmt {
2843 Statement::UpdateQuery(upd) => {
2844 assert_eq!(upd.source, "User");
2845 assert!(upd.filter.is_some());
2846 assert_eq!(upd.assignments.len(), 1);
2847 assert!(!upd.returning);
2848 }
2849 _ => panic!("expected update"),
2850 }
2851 }
2852
2853 #[test]
2854 fn test_parse_update_returning() {
2855 let stmt = parse(r#"User filter .name = "Alice" update { age := 31 } returning"#).unwrap();
2856 match stmt {
2857 Statement::UpdateQuery(upd) => assert!(upd.returning),
2858 _ => panic!("expected update"),
2859 }
2860 }
2861
2862 #[test]
2863 fn test_parse_delete() {
2864 let stmt = parse("User filter .age < 18 delete").unwrap();
2865 match stmt {
2866 Statement::DeleteQuery(del) => {
2867 assert_eq!(del.source, "User");
2868 assert!(del.filter.is_some());
2869 assert!(!del.returning);
2870 }
2871 _ => panic!("expected delete"),
2872 }
2873 }
2874
2875 #[test]
2876 fn test_parse_delete_returning() {
2877 let stmt = parse("User filter .age < 18 delete returning").unwrap();
2878 match stmt {
2879 Statement::DeleteQuery(del) => assert!(del.returning),
2880 _ => panic!("expected delete"),
2881 }
2882 }
2883
2884 #[test]
2885 fn test_parse_count() {
2886 let stmt = parse("count(User)").unwrap();
2887 match stmt {
2888 Statement::Query(q) => {
2889 let agg = q.aggregation.unwrap();
2890 assert_eq!(agg.function, AggFunc::Count);
2891 assert!(q.filter.is_none());
2892 }
2893 _ => panic!("expected query with aggregation"),
2894 }
2895 }
2896
2897 #[test]
2898 fn test_parse_count_with_filter() {
2899 let stmt = parse("count(User filter .age > 30)").unwrap();
2902 match stmt {
2903 Statement::Query(q) => {
2904 assert_eq!(q.source, "User");
2905 let agg = q.aggregation.unwrap();
2906 assert_eq!(agg.function, AggFunc::Count);
2907 assert!(q.filter.is_some(), "filter should have been parsed");
2908 }
2909 _ => panic!("expected query with aggregation"),
2910 }
2911 }
2912
2913 #[test]
2914 fn test_parse_count_with_filter_and_limit() {
2915 let stmt = parse("count(User filter .age > 30 limit 100)").unwrap();
2916 match stmt {
2917 Statement::Query(q) => {
2918 assert_eq!(q.source, "User");
2919 assert!(q.filter.is_some());
2920 assert!(q.limit.is_some());
2921 assert_eq!(q.aggregation.unwrap().function, AggFunc::Count);
2922 }
2923 _ => panic!("expected query with aggregation"),
2924 }
2925 }
2926
2927 #[test]
2928 fn test_parse_create_type() {
2929 let stmt = parse("type User { required name: str, age: int }").unwrap();
2930 match stmt {
2931 Statement::CreateType(ct) => {
2932 assert_eq!(ct.name, "User");
2933 assert_eq!(ct.fields.len(), 2);
2934 assert!(ct.fields[0].required);
2935 assert!(!ct.fields[1].required);
2936 }
2937 _ => panic!("expected create type"),
2938 }
2939 }
2940
2941 #[test]
2942 fn test_parse_sum_with_field_projection() {
2943 let stmt = parse("sum(User filter .age > 30 { .age })").unwrap();
2946 match stmt {
2947 Statement::Query(q) => {
2948 let agg = q.aggregation.expect("aggregate");
2949 assert_eq!(agg.function, AggFunc::Sum);
2950 assert_eq!(agg.argument, Some(Expr::Field("age".into())));
2951 assert!(
2952 q.projection.is_none(),
2953 "projection should be lifted into agg.field"
2954 );
2955 }
2956 _ => panic!("expected query"),
2957 }
2958 }
2959
2960 #[test]
2961 fn test_parse_raw_aggregate_modes() {
2962 let Statement::Query(top_level) = parse("sum(raw User { .age })").unwrap() else {
2963 panic!("expected query");
2964 };
2965 assert_eq!(top_level.aggregation.unwrap().mode, AggregateMode::Raw);
2966
2967 let Statement::Query(grouped) = parse("User group .dept { total: sum(raw .age) }").unwrap()
2968 else {
2969 panic!("expected query");
2970 };
2971 assert!(matches!(
2972 grouped.projection.unwrap()[0].expr,
2973 Expr::FunctionCall(AggFunc::Sum, _, AggregateMode::Raw)
2974 ));
2975 }
2976
2977 #[test]
2978 fn test_parse_avg_min_max_with_field() {
2979 for (src, expected) in [
2980 ("avg(User { .age })", AggFunc::Avg),
2981 ("min(User { .age })", AggFunc::Min),
2982 ("max(User { .age })", AggFunc::Max),
2983 ] {
2984 let stmt = parse(src).unwrap();
2985 match stmt {
2986 Statement::Query(q) => {
2987 let agg = q.aggregation.unwrap();
2988 assert_eq!(agg.function, expected, "func mismatch for {src}");
2989 assert_eq!(
2990 agg.argument,
2991 Some(Expr::Field("age".into())),
2992 "field mismatch for {src}"
2993 );
2994 assert!(
2995 q.projection.is_none(),
2996 "projection should be cleared for {src}"
2997 );
2998 }
2999 _ => panic!("expected query for {src}"),
3000 }
3001 }
3002 }
3003
3004 #[test]
3005 fn test_parse_count_leaves_projection_alone() {
3006 let stmt = parse("count(User { .age })").unwrap();
3009 match stmt {
3010 Statement::Query(q) => {
3011 let agg = q.aggregation.unwrap();
3012 assert_eq!(agg.function, AggFunc::Count);
3013 assert!(agg.argument.is_none());
3014 assert!(q.projection.is_some(), "count must not eat projection");
3015 }
3016 _ => panic!("expected query"),
3017 }
3018 }
3019
3020 #[test]
3025 fn test_parse_source_alias() {
3026 let stmt = parse("User as u filter u.age > 30").unwrap();
3027 match stmt {
3028 Statement::Query(q) => {
3029 assert_eq!(q.source, "User");
3030 assert_eq!(q.alias.as_deref(), Some("u"));
3031 assert!(q.joins.is_empty());
3032 match q.filter.unwrap() {
3033 Expr::BinaryOp(l, BinOp::Gt, _) => match *l {
3034 Expr::QualifiedField { qualifier, field } => {
3035 assert_eq!(qualifier, "u");
3036 assert_eq!(field, "age");
3037 }
3038 other => panic!("expected qualified field, got {other:?}"),
3039 },
3040 other => panic!("expected >, got {other:?}"),
3041 }
3042 }
3043 _ => panic!("expected query"),
3044 }
3045 }
3046
3047 #[test]
3048 fn test_parse_inner_join_on() {
3049 let stmt = parse("User as u inner join Order as o on u.id = o.user_id").unwrap();
3050 match stmt {
3051 Statement::Query(q) => {
3052 assert_eq!(q.source, "User");
3053 assert_eq!(q.alias.as_deref(), Some("u"));
3054 assert_eq!(q.joins.len(), 1);
3055 let j = &q.joins[0];
3056 assert_eq!(j.kind, JoinKind::Inner);
3057 assert_eq!(j.source, "Order");
3058 assert_eq!(j.alias.as_deref(), Some("o"));
3059 let on = j.on.as_ref().expect("on clause");
3060 match on {
3061 Expr::BinaryOp(l, BinOp::Eq, r) => {
3062 assert!(matches!(**l, Expr::QualifiedField { .. }));
3063 assert!(matches!(**r, Expr::QualifiedField { .. }));
3064 }
3065 other => panic!("expected eq, got {other:?}"),
3066 }
3067 }
3068 _ => panic!("expected query"),
3069 }
3070 }
3071
3072 #[test]
3073 fn test_parse_bare_join_defaults_to_inner() {
3074 let stmt = parse("User join Order on User.id = Order.user_id").unwrap();
3075 match stmt {
3076 Statement::Query(q) => {
3077 assert_eq!(q.joins.len(), 1);
3078 assert_eq!(q.joins[0].kind, JoinKind::Inner);
3079 }
3080 _ => panic!("expected query"),
3081 }
3082 }
3083
3084 #[test]
3085 fn test_parse_left_outer_join() {
3086 let stmt = parse("User as u left outer join Order as o on u.id = o.user_id").unwrap();
3087 match stmt {
3088 Statement::Query(q) => {
3089 assert_eq!(q.joins.len(), 1);
3090 assert_eq!(q.joins[0].kind, JoinKind::LeftOuter);
3091 }
3092 _ => panic!("expected query"),
3093 }
3094 }
3095
3096 #[test]
3097 fn test_parse_left_join_without_outer_keyword() {
3098 let stmt = parse("User as u left join Order as o on u.id = o.user_id").unwrap();
3100 match stmt {
3101 Statement::Query(q) => {
3102 assert_eq!(q.joins[0].kind, JoinKind::LeftOuter);
3103 }
3104 _ => panic!("expected query"),
3105 }
3106 }
3107
3108 #[test]
3109 fn test_parse_right_join() {
3110 let stmt = parse("User as u right join Order as o on u.id = o.user_id").unwrap();
3111 match stmt {
3112 Statement::Query(q) => {
3113 assert_eq!(q.joins[0].kind, JoinKind::RightOuter);
3114 }
3115 _ => panic!("expected query"),
3116 }
3117 }
3118
3119 #[test]
3120 fn test_parse_cross_join_has_no_on() {
3121 let stmt = parse("User cross join Order").unwrap();
3122 match stmt {
3123 Statement::Query(q) => {
3124 assert_eq!(q.joins[0].kind, JoinKind::Cross);
3125 assert!(q.joins[0].on.is_none());
3126 }
3127 _ => panic!("expected query"),
3128 }
3129 }
3130
3131 #[test]
3132 fn test_parse_multi_join_chain() {
3133 let stmt = parse(
3134 "User as u join Order as o on u.id = o.user_id \
3135 join Product as p on o.product_id = p.id",
3136 )
3137 .unwrap();
3138 match stmt {
3139 Statement::Query(q) => {
3140 assert_eq!(q.joins.len(), 2);
3141 assert_eq!(q.joins[0].source, "Order");
3142 assert_eq!(q.joins[1].source, "Product");
3143 }
3144 _ => panic!("expected query"),
3145 }
3146 }
3147
3148 #[test]
3149 fn test_parse_join_with_filter_tail() {
3150 let stmt = parse(
3152 "User as u join Order as o on u.id = o.user_id \
3153 filter o.total > 100 order .name limit 10",
3154 )
3155 .unwrap();
3156 match stmt {
3157 Statement::Query(q) => {
3158 assert_eq!(q.joins.len(), 1);
3159 assert!(q.filter.is_some());
3160 assert!(q.order.is_some());
3161 assert!(q.limit.is_some());
3162 }
3163 _ => panic!("expected query"),
3164 }
3165 }
3166
3167 #[test]
3168 fn test_parse_join_requires_on_for_inner() {
3169 let err = parse("User join Order").unwrap_err();
3171 assert!(
3172 err.message().contains("on"),
3173 "expected on-clause error, got {:?}",
3174 err.message()
3175 );
3176 }
3177
3178 #[test]
3179 fn test_parse_update_on_joined_query_errors() {
3180 let err =
3183 parse("User as u join Order as o on u.id = o.user_id update { age := 1 }").unwrap_err();
3184 assert!(err.message().contains("update"));
3185 }
3186
3187 #[test]
3188 fn test_parse_delete_on_joined_query_errors() {
3189 let err = parse("User as u join Order as o on u.id = o.user_id delete").unwrap_err();
3190 assert!(err.message().contains("delete"));
3191 }
3192
3193 #[test]
3196 fn test_parse_distinct() {
3197 let stmt = parse("User distinct { .name }").unwrap();
3198 match stmt {
3199 Statement::Query(q) => {
3200 assert!(q.distinct);
3201 assert!(q.projection.is_some());
3202 }
3203 _ => panic!("expected query"),
3204 }
3205 }
3206
3207 #[test]
3208 fn test_parse_in_list() {
3209 let stmt = parse(r#"User filter .name in ("Alice", "Bob")"#).unwrap();
3210 match stmt {
3211 Statement::Query(q) => match q.filter.unwrap() {
3212 Expr::InList {
3213 expr,
3214 list,
3215 negated,
3216 } => {
3217 assert!(!negated);
3218 assert!(matches!(*expr, Expr::Field(f) if f == "name"));
3219 assert_eq!(list.len(), 2);
3220 }
3221 other => panic!("expected InList, got {other:?}"),
3222 },
3223 _ => panic!("expected query"),
3224 }
3225 }
3226
3227 #[test]
3228 fn test_parse_not_in_list() {
3229 let stmt = parse("User filter .age not in (1, 2, 3)").unwrap();
3230 match stmt {
3231 Statement::Query(q) => match q.filter.unwrap() {
3232 Expr::InList { negated, list, .. } => {
3233 assert!(negated);
3234 assert_eq!(list.len(), 3);
3235 }
3236 other => panic!("expected InList, got {other:?}"),
3237 },
3238 _ => panic!("expected query"),
3239 }
3240 }
3241
3242 #[test]
3243 fn test_parse_between() {
3244 let stmt = parse("User filter .age between 10 and 20").unwrap();
3246 match stmt {
3247 Statement::Query(q) => {
3248 match q.filter.unwrap() {
3249 Expr::BinaryOp(_, BinOp::And, _) => {} other => panic!("expected And (desugared between), got {other:?}"),
3251 }
3252 }
3253 _ => panic!("expected query"),
3254 }
3255 }
3256
3257 #[test]
3258 fn test_parse_not_between() {
3259 let stmt = parse("User filter .age not between 10 and 20").unwrap();
3261 match stmt {
3262 Statement::Query(q) => {
3263 match q.filter.unwrap() {
3264 Expr::BinaryOp(_, BinOp::Or, _) => {} other => panic!("expected Or (desugared not between), got {other:?}"),
3266 }
3267 }
3268 _ => panic!("expected query"),
3269 }
3270 }
3271
3272 #[test]
3273 fn test_parse_like() {
3274 let stmt = parse(r#"User filter .name like "A%""#).unwrap();
3275 match stmt {
3276 Statement::Query(q) => match q.filter.unwrap() {
3277 Expr::BinaryOp(l, BinOp::Like, r) => {
3278 assert!(matches!(*l, Expr::Field(f) if f == "name"));
3279 assert!(matches!(*r, Expr::Literal(Literal::String(s)) if s == "A%"));
3280 }
3281 other => panic!("expected Like, got {other:?}"),
3282 },
3283 _ => panic!("expected query"),
3284 }
3285 }
3286
3287 #[test]
3288 fn test_parse_not_like() {
3289 let stmt = parse(r#"User filter .name not like "A%""#).unwrap();
3290 match stmt {
3291 Statement::Query(q) => match q.filter.unwrap() {
3292 Expr::UnaryOp(UnaryOp::Not, inner) => {
3293 assert!(matches!(*inner, Expr::BinaryOp(_, BinOp::Like, _)));
3294 }
3295 other => panic!("expected Not(Like), got {other:?}"),
3296 },
3297 _ => panic!("expected query"),
3298 }
3299 }
3300
3301 #[test]
3304 fn test_parse_group_by_single_key() {
3305 let stmt = parse("User group .status { .status, n: count(.name) }").unwrap();
3306 match stmt {
3307 Statement::Query(q) => {
3308 let gb = q.group_by.unwrap();
3309 assert_eq!(
3310 gb.keys,
3311 vec![GroupKey {
3312 expr: Expr::Field("status".into()),
3313 output_name: "status".into(),
3314 }]
3315 );
3316 assert!(gb.having.is_none());
3317 let proj = q.projection.unwrap();
3318 assert_eq!(proj.len(), 2);
3319 assert!(matches!(
3320 &proj[1].expr,
3321 Expr::FunctionCall(AggFunc::Count, _, _)
3322 ));
3323 assert_eq!(proj[1].alias.as_deref(), Some("n"));
3324 }
3325 _ => panic!("expected query"),
3326 }
3327 }
3328
3329 #[test]
3330 fn test_parse_group_by_multi_key() {
3331 let stmt = parse("User group .status, .age { .status, .age }").unwrap();
3332 match stmt {
3333 Statement::Query(q) => {
3334 let gb = q.group_by.unwrap();
3335 assert_eq!(
3336 gb.keys,
3337 vec![
3338 GroupKey {
3339 expr: Expr::Field("status".into()),
3340 output_name: "status".into(),
3341 },
3342 GroupKey {
3343 expr: Expr::Field("age".into()),
3344 output_name: "age".into(),
3345 }
3346 ]
3347 );
3348 }
3349 _ => panic!("expected query"),
3350 }
3351 }
3352
3353 #[test]
3354 fn test_parse_group_by_having() {
3355 let stmt = parse("User group .status having count(.name) > 1 { .status }").unwrap();
3356 match stmt {
3357 Statement::Query(q) => {
3358 let gb = q.group_by.unwrap();
3359 assert_eq!(
3360 gb.keys,
3361 vec![GroupKey {
3362 expr: Expr::Field("status".into()),
3363 output_name: "status".into(),
3364 }]
3365 );
3366 assert!(gb.having.is_some());
3367 match gb.having.unwrap() {
3369 Expr::BinaryOp(l, BinOp::Gt, _) => {
3370 assert!(matches!(*l, Expr::FunctionCall(AggFunc::Count, _, _)));
3371 }
3372 other => panic!("expected BinaryOp, got {other:?}"),
3373 }
3374 }
3375 _ => panic!("expected query"),
3376 }
3377 }
3378
3379 #[test]
3380 fn test_parse_aggregate_in_projection() {
3381 let stmt = parse("User group .status { .status, count(.name), sum(.age) }").unwrap();
3383 match stmt {
3384 Statement::Query(q) => {
3385 let proj = q.projection.unwrap();
3386 assert_eq!(proj.len(), 3);
3387 assert!(matches!(
3388 &proj[1].expr,
3389 Expr::FunctionCall(AggFunc::Count, _, _)
3390 ));
3391 assert!(matches!(
3392 &proj[2].expr,
3393 Expr::FunctionCall(AggFunc::Sum, _, _)
3394 ));
3395 }
3396 _ => panic!("expected query"),
3397 }
3398 }
3399
3400 #[test]
3401 fn test_parse_aggregate_in_aliased_projection() {
3402 let stmt = parse("User group .status { .status, total: count(.name), average: avg(.age) }")
3403 .unwrap();
3404 match stmt {
3405 Statement::Query(q) => {
3406 let proj = q.projection.unwrap();
3407 assert_eq!(proj[1].alias.as_deref(), Some("total"));
3408 assert!(matches!(
3409 &proj[1].expr,
3410 Expr::FunctionCall(AggFunc::Count, _, _)
3411 ));
3412 assert_eq!(proj[2].alias.as_deref(), Some("average"));
3413 assert!(matches!(
3414 &proj[2].expr,
3415 Expr::FunctionCall(AggFunc::Avg, _, _)
3416 ));
3417 }
3418 _ => panic!("expected query"),
3419 }
3420 }
3421
3422 #[test]
3425 fn test_parse_is_null() {
3426 let stmt = parse("User filter .age is null").unwrap();
3427 match stmt {
3428 Statement::Query(q) => {
3429 let filter = q.filter.unwrap();
3430 assert_eq!(
3431 filter,
3432 Expr::UnaryOp(UnaryOp::IsNull, Box::new(Expr::Field("age".into())))
3433 );
3434 }
3435 _ => panic!("expected query"),
3436 }
3437 }
3438
3439 #[test]
3440 fn test_parse_is_not_null() {
3441 let stmt = parse("User filter .age is not null").unwrap();
3442 match stmt {
3443 Statement::Query(q) => {
3444 let filter = q.filter.unwrap();
3445 assert_eq!(
3446 filter,
3447 Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(Expr::Field("age".into())))
3448 );
3449 }
3450 _ => panic!("expected query"),
3451 }
3452 }
3453
3454 #[test]
3455 fn test_parse_eq_null_desugars_to_is_null() {
3456 let stmt = parse("User filter .age = null").unwrap();
3457 match stmt {
3458 Statement::Query(q) => {
3459 let filter = q.filter.unwrap();
3460 assert_eq!(
3461 filter,
3462 Expr::UnaryOp(UnaryOp::IsNull, Box::new(Expr::Field("age".into())))
3463 );
3464 }
3465 _ => panic!("expected query"),
3466 }
3467 }
3468
3469 #[test]
3470 fn test_parse_neq_null_desugars_to_is_not_null() {
3471 let stmt = parse("User filter .age != null").unwrap();
3472 match stmt {
3473 Statement::Query(q) => {
3474 let filter = q.filter.unwrap();
3475 assert_eq!(
3476 filter,
3477 Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(Expr::Field("age".into())))
3478 );
3479 }
3480 _ => panic!("expected query"),
3481 }
3482 }
3483
3484 #[test]
3485 fn test_parse_null_comparisons_parse_ok() {
3486 assert!(parse("User filter .age < null").is_ok());
3490 assert!(parse("User filter .age >= null").is_ok());
3491 }
3492
3493 #[test]
3494 fn test_parse_count_star_expr() {
3495 let stmt = parse("User filter count(*) > 0").unwrap();
3496 match stmt {
3497 Statement::Query(q) => {
3498 let filter = q.filter.unwrap();
3499 match filter {
3500 Expr::BinaryOp(left, BinOp::Gt, _) => {
3501 assert_eq!(
3502 *left,
3503 Expr::FunctionCall(
3504 AggFunc::Count,
3505 Box::new(Expr::Field("*".into())),
3506 AggregateMode::Symmetric,
3507 )
3508 );
3509 }
3510 _ => panic!("expected comparison"),
3511 }
3512 }
3513 _ => panic!("expected query"),
3514 }
3515 }
3516
3517 #[test]
3520 fn test_parse_upper_in_filter() {
3521 let stmt = parse(r#"User filter upper(.name) = "ALICE""#).unwrap();
3522 match stmt {
3523 Statement::Query(q) => {
3524 let f = q.filter.unwrap();
3525 match f {
3526 Expr::BinaryOp(left, BinOp::Eq, _right) => {
3527 assert!(matches!(*left, Expr::ScalarFunc(ScalarFn::Upper, _)));
3528 }
3529 _ => panic!("expected binary op with upper"),
3530 }
3531 }
3532 _ => panic!("expected query"),
3533 }
3534 }
3535
3536 #[test]
3537 fn test_parse_substring() {
3538 let stmt = parse("User { sub: substring(.name, 1, 3) }").unwrap();
3539 match stmt {
3540 Statement::Query(q) => {
3541 let proj = q.projection.unwrap();
3542 match &proj[0].expr {
3543 Expr::ScalarFunc(ScalarFn::Substring, args) => {
3544 assert_eq!(args.len(), 3);
3545 }
3546 other => panic!("expected ScalarFunc Substring, got {other:?}"),
3547 }
3548 }
3549 _ => panic!("expected query"),
3550 }
3551 }
3552
3553 #[test]
3554 fn test_parse_concat() {
3555 let stmt = parse(r#"User { full: concat(.name, " - ", .email) }"#).unwrap();
3556 match stmt {
3557 Statement::Query(q) => {
3558 let proj = q.projection.unwrap();
3559 match &proj[0].expr {
3560 Expr::ScalarFunc(ScalarFn::Concat, args) => {
3561 assert_eq!(args.len(), 3);
3562 }
3563 other => panic!("expected ScalarFunc Concat, got {other:?}"),
3564 }
3565 }
3566 _ => panic!("expected query"),
3567 }
3568 }
3569
3570 #[test]
3573 fn test_parse_case_single_when() {
3574 let stmt = parse(r#"User filter case when .age > 30 then true else false end"#).unwrap();
3575 match stmt {
3576 Statement::Query(q) => {
3577 let filter = q.filter.unwrap();
3578 match filter {
3579 Expr::Case { whens, else_expr } => {
3580 assert_eq!(whens.len(), 1);
3581 assert!(else_expr.is_some());
3582 }
3583 other => panic!("expected Case expr, got {other:?}"),
3584 }
3585 }
3586 _ => panic!("expected query"),
3587 }
3588 }
3589
3590 #[test]
3591 fn test_parse_case_multiple_whens() {
3592 let stmt = parse(
3593 r#"User { label: case when .age > 30 then "senior" when .age > 20 then "adult" else "young" end }"#
3594 ).unwrap();
3595 match stmt {
3596 Statement::Query(q) => {
3597 let proj = q.projection.unwrap();
3598 match &proj[0].expr {
3599 Expr::Case { whens, else_expr } => {
3600 assert_eq!(whens.len(), 2);
3601 assert!(else_expr.is_some());
3602 }
3603 other => panic!("expected Case expr, got {other:?}"),
3604 }
3605 }
3606 _ => panic!("expected query"),
3607 }
3608 }
3609
3610 #[test]
3611 fn test_parse_case_without_else() {
3612 let stmt = parse(r#"User filter case when .age > 30 then true end"#).unwrap();
3613 match stmt {
3614 Statement::Query(q) => {
3615 let filter = q.filter.unwrap();
3616 match filter {
3617 Expr::Case { whens, else_expr } => {
3618 assert_eq!(whens.len(), 1);
3619 assert!(else_expr.is_none());
3620 }
3621 other => panic!("expected Case expr, got {other:?}"),
3622 }
3623 }
3624 _ => panic!("expected query"),
3625 }
3626 }
3627
3628 #[test]
3631 fn test_parse_mul_expr() {
3632 let stmt = parse("User filter .price * .quantity > 100").unwrap();
3633 match stmt {
3634 Statement::Query(q) => {
3635 let filter = q.filter.unwrap();
3636 match filter {
3637 Expr::BinaryOp(left, BinOp::Gt, _) => match *left {
3638 Expr::BinaryOp(_, BinOp::Mul, _) => {}
3639 other => panic!("expected Mul, got {other:?}"),
3640 },
3641 other => panic!("expected BinaryOp Gt, got {other:?}"),
3642 }
3643 }
3644 _ => panic!("expected query"),
3645 }
3646 }
3647
3648 #[test]
3649 fn test_parse_div_expr() {
3650 let stmt = parse("User { ratio: .total / .count }").unwrap();
3651 match stmt {
3652 Statement::Query(q) => {
3653 let proj = q.projection.unwrap();
3654 assert_eq!(proj[0].alias.as_deref(), Some("ratio"));
3655 match &proj[0].expr {
3656 Expr::BinaryOp(_, BinOp::Div, _) => {}
3657 other => panic!("expected Div, got {other:?}"),
3658 }
3659 }
3660 _ => panic!("expected query"),
3661 }
3662 }
3663
3664 #[test]
3665 fn test_parse_mul_div_precedence() {
3666 let stmt = parse("User filter .a + .b * .c > 0").unwrap();
3668 match stmt {
3669 Statement::Query(q) => {
3670 let filter = q.filter.unwrap();
3671 match filter {
3672 Expr::BinaryOp(left, BinOp::Gt, _) => match *left {
3673 Expr::BinaryOp(_, BinOp::Add, right) => {
3674 assert!(matches!(*right, Expr::BinaryOp(_, BinOp::Mul, _)));
3675 }
3676 other => panic!("expected Add, got {other:?}"),
3677 },
3678 other => panic!("expected Gt, got {other:?}"),
3679 }
3680 }
3681 _ => panic!("expected query"),
3682 }
3683 }
3684
3685 #[test]
3688 fn test_parse_multi_order() {
3689 let stmt = parse("User order .name asc, .age desc").unwrap();
3690 match stmt {
3691 Statement::Query(q) => {
3692 let order = q.order.unwrap();
3693 assert_eq!(order.keys.len(), 2);
3694 assert_eq!(order.keys[0].expr, Expr::Field("name".into()));
3695 assert!(!order.keys[0].descending);
3696 assert_eq!(order.keys[1].expr, Expr::Field("age".into()));
3697 assert!(order.keys[1].descending);
3698 }
3699 _ => panic!("expected query"),
3700 }
3701 }
3702
3703 #[test]
3704 fn test_parse_order_default_asc() {
3705 let stmt = parse("User order .name").unwrap();
3706 match stmt {
3707 Statement::Query(q) => {
3708 let order = q.order.unwrap();
3709 assert_eq!(order.keys.len(), 1);
3710 assert!(!order.keys[0].descending);
3711 }
3712 _ => panic!("expected query"),
3713 }
3714 }
3715
3716 #[test]
3719 fn test_parse_alter_add_column() {
3720 let stmt = parse("alter User add column status: str").unwrap();
3721 match stmt {
3722 Statement::AlterTable(at) => {
3723 assert_eq!(at.table, "User");
3724 match at.action {
3725 AlterAction::AddColumn {
3726 name,
3727 type_name,
3728 required,
3729 } => {
3730 assert_eq!(name, "status");
3731 assert_eq!(type_name, "str");
3732 assert!(!required);
3733 }
3734 other => panic!("expected AddColumn, got {other:?}"),
3735 }
3736 }
3737 other => panic!("expected AlterTable, got {other:?}"),
3738 }
3739 }
3740
3741 #[test]
3742 fn test_parse_alter_add_required_column() {
3743 let stmt = parse("alter User add required status: str").unwrap();
3744 match stmt {
3745 Statement::AlterTable(at) => match at.action {
3746 AlterAction::AddColumn { required, .. } => assert!(required),
3747 other => panic!("expected AddColumn, got {other:?}"),
3748 },
3749 other => panic!("expected AlterTable, got {other:?}"),
3750 }
3751 }
3752
3753 #[test]
3754 fn test_parse_type_with_unique_modifier() {
3755 let stmt = parse("type User { required unique email: str, age: int }").unwrap();
3756 match stmt {
3757 Statement::CreateType(ct) => {
3758 assert!(ct.fields[0].required && ct.fields[0].unique);
3759 assert!(!ct.fields[1].unique);
3760 }
3761 other => panic!("expected CreateType, got {other:?}"),
3762 }
3763 }
3764
3765 #[test]
3766 fn test_parse_type_unique_before_required() {
3767 let stmt = parse("type User { unique required email: str }").unwrap();
3769 match stmt {
3770 Statement::CreateType(ct) => {
3771 assert!(ct.fields[0].required && ct.fields[0].unique);
3772 }
3773 other => panic!("expected CreateType, got {other:?}"),
3774 }
3775 }
3776
3777 #[test]
3778 fn test_parse_alter_add_unique() {
3779 let stmt = parse("alter User add unique .email").unwrap();
3780 match stmt {
3781 Statement::AlterTable(at) => assert!(matches!(
3782 at.action,
3783 AlterAction::AddUnique {
3784 target: IndexTarget::Column(ref column),
3785 ..
3786 } if column == "email"
3787 )),
3788 other => panic!("expected AlterTable, got {other:?}"),
3789 }
3790 }
3791
3792 #[test]
3793 fn test_parse_alter_drop_column() {
3794 let stmt = parse("alter User drop column status").unwrap();
3795 match stmt {
3796 Statement::AlterTable(at) => {
3797 assert_eq!(at.table, "User");
3798 match at.action {
3799 AlterAction::DropColumn { name, .. } => assert_eq!(name, "status"),
3800 other => panic!("expected DropColumn, got {other:?}"),
3801 }
3802 }
3803 other => panic!("expected AlterTable, got {other:?}"),
3804 }
3805 }
3806
3807 #[test]
3808 fn test_parse_alter_drop_without_column_keyword() {
3809 let stmt = parse("alter User drop status").unwrap();
3810 match stmt {
3811 Statement::AlterTable(at) => match at.action {
3812 AlterAction::DropColumn { name, .. } => assert_eq!(name, "status"),
3813 other => panic!("expected DropColumn, got {other:?}"),
3814 },
3815 other => panic!("expected AlterTable, got {other:?}"),
3816 }
3817 }
3818
3819 #[test]
3820 fn test_parse_drop_table() {
3821 let stmt = parse("drop User").unwrap();
3822 match stmt {
3823 Statement::DropTable(dt) => assert_eq!(dt.table, "User"),
3824 other => panic!("expected DropTable, got {other:?}"),
3825 }
3826 }
3827
3828 #[test]
3831 fn test_parse_in_subquery() {
3832 let stmt = parse("User filter .name in (VIP { .name })").unwrap();
3833 match stmt {
3834 Statement::Query(q) => {
3835 let filter = q.filter.unwrap();
3836 match filter {
3837 Expr::InSubquery {
3838 expr,
3839 subquery,
3840 negated,
3841 } => {
3842 assert!(!negated);
3843 assert!(matches!(*expr, Expr::Field(ref f) if f == "name"));
3844 assert_eq!(subquery.source, "VIP");
3845 }
3846 other => panic!("expected InSubquery, got {other:?}"),
3847 }
3848 }
3849 _ => panic!("expected query"),
3850 }
3851 }
3852
3853 #[test]
3854 fn test_parse_not_in_subquery() {
3855 let stmt = parse("User filter .id not in (Order { .user_id })").unwrap();
3856 match stmt {
3857 Statement::Query(q) => match q.filter.unwrap() {
3858 Expr::InSubquery { negated, .. } => assert!(negated),
3859 other => panic!("expected InSubquery, got {other:?}"),
3860 },
3861 _ => panic!("expected query"),
3862 }
3863 }
3864
3865 #[test]
3866 fn test_parse_in_literal_list_still_works() {
3867 let stmt = parse("User filter .age in (25, 30, 35)").unwrap();
3869 match stmt {
3870 Statement::Query(q) => match q.filter.unwrap() {
3871 Expr::InList { list, negated, .. } => {
3872 assert!(!negated);
3873 assert_eq!(list.len(), 3);
3874 }
3875 other => panic!("expected InList, got {other:?}"),
3876 },
3877 _ => panic!("expected query"),
3878 }
3879 }
3880
3881 #[test]
3884 fn test_parse_create_view() {
3885 let stmt = parse("materialize OldUsers as User filter .age > 28").unwrap();
3886 match stmt {
3887 Statement::CreateView(cv) => {
3888 assert_eq!(cv.name, "OldUsers");
3889 assert_eq!(cv.query.source, "User");
3890 assert!(cv.query.filter.is_some());
3891 assert!(!cv.query_text.is_empty());
3892 }
3893 _ => panic!("expected CreateView"),
3894 }
3895 }
3896
3897 #[test]
3898 fn test_parse_create_view_with_projection() {
3899 let stmt = parse("materialize UserNames as User { .name }").unwrap();
3900 match stmt {
3901 Statement::CreateView(cv) => {
3902 assert_eq!(cv.name, "UserNames");
3903 assert!(cv.query.projection.is_some());
3904 }
3905 _ => panic!("expected CreateView"),
3906 }
3907 }
3908
3909 #[test]
3910 fn test_parse_refresh_view() {
3911 let stmt = parse("refresh OldUsers").unwrap();
3912 match stmt {
3913 Statement::RefreshView(rv) => {
3914 assert_eq!(rv.name, "OldUsers");
3915 }
3916 _ => panic!("expected RefreshView"),
3917 }
3918 }
3919
3920 #[test]
3921 fn test_parse_drop_view() {
3922 let stmt = parse("drop view OldUsers").unwrap();
3923 match stmt {
3924 Statement::DropView(dv) => {
3925 assert_eq!(dv.name, "OldUsers");
3926 }
3927 _ => panic!("expected DropView"),
3928 }
3929 }
3930
3931 #[test]
3932 fn test_parse_drop_table_still_works() {
3933 let stmt = parse("drop Users").unwrap();
3934 match stmt {
3935 Statement::DropTable(dt) => {
3936 assert_eq!(dt.table, "Users");
3937 }
3938 _ => panic!("expected DropTable"),
3939 }
3940 }
3941
3942 #[test]
3943 fn test_parse_union() {
3944 let stmt = parse("User union Order").unwrap();
3945 match stmt {
3946 Statement::Union(u) => {
3947 assert!(!u.all);
3948 match *u.left {
3949 Statement::Query(_) => {}
3950 _ => panic!("expected Query on left"),
3951 }
3952 match *u.right {
3953 Statement::Query(_) => {}
3954 _ => panic!("expected Query on right"),
3955 }
3956 }
3957 _ => panic!("expected Union"),
3958 }
3959 }
3960
3961 #[test]
3962 fn test_parse_union_all() {
3963 let stmt = parse("User union all Order").unwrap();
3964 match stmt {
3965 Statement::Union(u) => {
3966 assert!(u.all, "expected UNION ALL");
3967 match *u.left {
3968 Statement::Query(_) => {}
3969 _ => panic!("expected Query on left"),
3970 }
3971 match *u.right {
3972 Statement::Query(_) => {}
3973 _ => panic!("expected Query on right"),
3974 }
3975 }
3976 _ => panic!("expected Union"),
3977 }
3978 }
3979
3980 #[test]
3981 fn test_parse_union_chain() {
3982 let stmt = parse("User union Order union Product").unwrap();
3984 match stmt {
3985 Statement::Union(outer) => {
3986 assert!(!outer.all);
3987 match *outer.right {
3989 Statement::Query(q) => assert_eq!(q.source, "Product"),
3990 _ => panic!("expected Query(Product) on right"),
3991 }
3992 match *outer.left {
3994 Statement::Union(inner) => {
3995 assert!(!inner.all);
3996 match *inner.left {
3997 Statement::Query(q) => assert_eq!(q.source, "User"),
3998 _ => panic!("expected Query(User)"),
3999 }
4000 match *inner.right {
4001 Statement::Query(q) => assert_eq!(q.source, "Order"),
4002 _ => panic!("expected Query(Order)"),
4003 }
4004 }
4005 _ => panic!("expected inner Union"),
4006 }
4007 }
4008 _ => panic!("expected Union"),
4009 }
4010 }
4011
4012 #[test]
4013 fn test_parse_union_with_filter() {
4014 let stmt = parse("User filter .age > 10 union Order filter .total > 50").unwrap();
4015 match stmt {
4016 Statement::Union(u) => {
4017 assert!(!u.all);
4018 match *u.left {
4020 Statement::Query(q) => {
4021 assert_eq!(q.source, "User");
4022 assert!(q.filter.is_some());
4023 }
4024 _ => panic!("expected Query on left"),
4025 }
4026 match *u.right {
4027 Statement::Query(q) => {
4028 assert_eq!(q.source, "Order");
4029 assert!(q.filter.is_some());
4030 }
4031 _ => panic!("expected Query on right"),
4032 }
4033 }
4034 _ => panic!("expected Union"),
4035 }
4036 }
4037
4038 #[test]
4039 fn test_parse_count_distinct_standalone() {
4040 let stmt = parse("count(distinct User { .name })").unwrap();
4041 match stmt {
4042 Statement::Query(q) => {
4043 let agg = q.aggregation.unwrap();
4044 assert_eq!(agg.function, AggFunc::CountDistinct);
4045 assert_eq!(agg.argument, Some(Expr::Field("name".into())));
4046 }
4047 _ => panic!("expected Query"),
4048 }
4049 }
4050
4051 #[test]
4052 fn test_parse_count_distinct_in_projection() {
4053 let stmt = parse("User group .dept { .dept, count(distinct .name) }").unwrap();
4054 match stmt {
4055 Statement::Query(q) => {
4056 let proj = q.projection.unwrap();
4057 assert_eq!(proj.len(), 2);
4058 match &proj[1].expr {
4059 Expr::FunctionCall(func, _, _) => {
4060 assert_eq!(*func, AggFunc::CountDistinct);
4061 }
4062 _ => panic!("expected FunctionCall"),
4063 }
4064 }
4065 _ => panic!("expected Query"),
4066 }
4067 }
4068
4069 #[test]
4072 fn test_parse_window_row_number_order() {
4073 let stmt = parse("User { .name, rn: row_number() over (order .age) }").unwrap();
4074 match stmt {
4075 Statement::Query(q) => {
4076 let proj = q.projection.unwrap();
4077 assert_eq!(proj.len(), 2);
4078 assert_eq!(proj[1].alias.as_deref(), Some("rn"));
4079 match &proj[1].expr {
4080 Expr::Window {
4081 function,
4082 args,
4083 partition_by,
4084 order_by,
4085 ..
4086 } => {
4087 assert_eq!(*function, WindowFunc::RowNumber);
4088 assert!(args.is_empty());
4089 assert!(partition_by.is_empty());
4090 assert_eq!(order_by.len(), 1);
4091 assert_eq!(order_by[0].expr, Expr::Field("age".into()));
4092 assert!(!order_by[0].descending);
4093 }
4094 other => panic!("expected Window, got {other:?}"),
4095 }
4096 }
4097 _ => panic!("expected query"),
4098 }
4099 }
4100
4101 #[test]
4102 fn test_parse_window_sum_partition_order() {
4103 let stmt =
4104 parse("User { .name, s: sum(.salary) over (partition .dept order .salary) }").unwrap();
4105 match stmt {
4106 Statement::Query(q) => {
4107 let proj = q.projection.unwrap();
4108 assert_eq!(proj.len(), 2);
4109 assert_eq!(proj[1].alias.as_deref(), Some("s"));
4110 match &proj[1].expr {
4111 Expr::Window {
4112 function,
4113 args,
4114 partition_by,
4115 order_by,
4116 ..
4117 } => {
4118 assert_eq!(*function, WindowFunc::Sum);
4119 assert_eq!(args.len(), 1);
4120 assert!(matches!(&args[0], Expr::Field(f) if f == "salary"));
4121 assert_eq!(partition_by, &[Expr::Field("dept".into())]);
4122 assert_eq!(order_by.len(), 1);
4123 assert_eq!(order_by[0].expr, Expr::Field("salary".into()));
4124 assert!(!order_by[0].descending);
4125 }
4126 other => panic!("expected Window, got {other:?}"),
4127 }
4128 }
4129 _ => panic!("expected query"),
4130 }
4131 }
4132
4133 #[test]
4134 fn test_parse_window_rank_desc() {
4135 let stmt =
4136 parse("User { .dept, .salary, r: rank() over (partition .dept order .salary desc) }")
4137 .unwrap();
4138 match stmt {
4139 Statement::Query(q) => {
4140 let proj = q.projection.unwrap();
4141 assert_eq!(proj.len(), 3);
4142 match &proj[2].expr {
4143 Expr::Window {
4144 function,
4145 partition_by,
4146 order_by,
4147 ..
4148 } => {
4149 assert_eq!(*function, WindowFunc::Rank);
4150 assert_eq!(partition_by, &[Expr::Field("dept".into())]);
4151 assert_eq!(order_by.len(), 1);
4152 assert!(order_by[0].descending);
4153 }
4154 other => panic!("expected Window, got {other:?}"),
4155 }
4156 }
4157 _ => panic!("expected query"),
4158 }
4159 }
4160
4161 #[test]
4162 fn test_parse_window_dense_rank() {
4163 let stmt = parse("User { .name, dr: dense_rank() over (order .score desc) }").unwrap();
4164 match stmt {
4165 Statement::Query(q) => {
4166 let proj = q.projection.unwrap();
4167 assert_eq!(proj.len(), 2);
4168 match &proj[1].expr {
4169 Expr::Window { function, .. } => {
4170 assert_eq!(*function, WindowFunc::DenseRank);
4171 }
4172 other => panic!("expected Window, got {other:?}"),
4173 }
4174 }
4175 _ => panic!("expected query"),
4176 }
4177 }
4178
4179 #[test]
4180 fn test_parse_sum_without_over_is_aggregate() {
4181 let stmt = parse("User group .dept { .dept, total: sum(.salary) }").unwrap();
4183 match stmt {
4184 Statement::Query(q) => {
4185 let proj = q.projection.unwrap();
4186 assert_eq!(proj.len(), 2);
4187 match &proj[1].expr {
4188 Expr::FunctionCall(AggFunc::Sum, _, _) => {} other => panic!("expected FunctionCall(Sum), got {other:?}"),
4190 }
4191 }
4192 _ => panic!("expected query"),
4193 }
4194 }
4195
4196 #[test]
4197 fn test_nesting_depth_limit() {
4198 let mut query = String::from("User filter ");
4200 for _ in 0..70 {
4201 query.push('(');
4202 }
4203 query.push_str(".age > 1");
4204 for _ in 0..70 {
4205 query.push(')');
4206 }
4207 let result = parse(&query);
4208 assert!(result.is_err());
4209 let err = result.unwrap_err();
4210 assert!(
4211 err.message().contains("nesting depth"),
4212 "expected nesting depth error, got: {}",
4213 err.message()
4214 );
4215 }
4216
4217 #[test]
4218 fn test_unary_prefix_nesting_depth_limit() {
4219 let query = String::from("User filter ") + &"not ".repeat(5000) + ".active";
4223 let result = parse(&query);
4224 assert!(result.is_err());
4225 let err = result.unwrap_err();
4226 assert!(
4227 err.message().contains("nesting depth"),
4228 "expected nesting depth error, got: {}",
4229 err.message()
4230 );
4231 }
4232
4233 #[test]
4234 fn test_moderate_nesting_succeeds() {
4235 let mut query = String::from("User filter ");
4237 for _ in 0..10 {
4238 query.push('(');
4239 }
4240 query.push_str(".age > 1");
4241 for _ in 0..10 {
4242 query.push(')');
4243 }
4244 assert!(parse(&query).is_ok());
4245 }
4246
4247 #[test]
4251 fn test_parse_fuzz_repro_projection_eof() {
4252 let err = parse("nn{").expect_err("unterminated projection must error, not panic");
4253 let _ = err.message();
4254 }
4255
4256 #[test]
4259 fn test_parse_fuzz_repro_short_projection_eof() {
4260 let err = parse("z{").expect_err("unterminated projection must error, not panic");
4261 let _ = err.message();
4262 }
4263
4264 #[test]
4265 fn test_update_at_statement_start_gives_helpful_error() {
4266 let err =
4267 parse(r#"update User filter .name = "Alice" { age := 31 }"#).expect_err("should fail");
4268 let msg = err.message();
4269 assert!(
4270 msg.contains("pipeline syntax"),
4271 "error should mention pipeline syntax, got: {msg}"
4272 );
4273 assert!(
4274 msg.contains("update"),
4275 "error should mention 'update', got: {msg}"
4276 );
4277 }
4278
4279 #[test]
4280 fn test_delete_at_statement_start_gives_helpful_error() {
4281 let err = parse("delete User filter .age < 18").expect_err("should fail");
4282 let msg = err.message();
4283 assert!(
4284 msg.contains("pipeline syntax"),
4285 "error should mention pipeline syntax, got: {msg}"
4286 );
4287 assert!(
4288 msg.contains("delete"),
4289 "error should mention 'delete', got: {msg}"
4290 );
4291 }
4292}
4293
4294#[cfg(test)]
4295mod cleanup_parser_dx_tests {
4296 use super::*;
4297
4298 #[test]
4299 fn typoed_statement_keyword_gets_suggestion() {
4300 let err = parse("updat User set age = 1").unwrap_err();
4301 let msg = err.to_string();
4302 assert!(msg.contains("near token"), "{msg}");
4303 assert!(msg.contains("did you mean `update`"), "{msg}");
4304 }
4305}
4306
4307#[cfg(test)]
4308mod dogfood_dx_tests {
4309 use super::*;
4310
4311 #[test]
4314 fn reserved_word_field_name_gives_actionable_error() {
4315 let err = parse("type Post { type: str }").unwrap_err();
4316 let msg = err.to_string();
4317 assert!(
4318 msg.contains("'type' is a reserved word")
4319 && msg.contains("field name")
4320 && msg.contains("quote it as `type`"),
4321 "unhelpful message: {msg}"
4322 );
4323 }
4324
4325 #[test]
4326 fn reserved_modifier_word_as_field_name_gives_actionable_error() {
4327 let err = parse("type Post { required: bool }").unwrap_err();
4331 let msg = err.to_string();
4332 assert!(
4333 msg.contains("'required' is a reserved word") && msg.contains("quote it as `required`"),
4334 "unhelpful message: {msg}"
4335 );
4336 }
4337
4338 #[test]
4339 fn reserved_word_in_insert_assignment_gives_actionable_error() {
4340 let err = parse(r#"insert Post { type := "x" }"#).unwrap_err();
4341 let msg = err.to_string();
4342 assert!(msg.contains("'type' is a reserved word"), "{msg}");
4343 }
4344
4345 #[test]
4346 fn reserved_word_in_alter_column_gives_actionable_error() {
4347 let err = parse("alter Post add column order: int").unwrap_err();
4348 let msg = err.to_string();
4349 assert!(msg.contains("'order' is a reserved word"), "{msg}");
4350 }
4351
4352 #[test]
4353 fn backtick_field_name_parses_as_identifier() {
4354 let stmt = parse("type Post { `type`: str, `order`: int }").unwrap();
4355 match stmt {
4356 Statement::CreateType(ct) => {
4357 assert_eq!(ct.fields[0].name, "type");
4358 assert_eq!(ct.fields[1].name, "order");
4359 }
4360 other => panic!("expected CreateType, got {other:?}"),
4361 }
4362 }
4363
4364 #[test]
4365 fn backtick_field_still_honors_modifiers() {
4366 let stmt = parse("type Post { required `type`: str }").unwrap();
4367 match stmt {
4368 Statement::CreateType(ct) => {
4369 assert_eq!(ct.fields[0].name, "type");
4370 assert!(ct.fields[0].required);
4371 }
4372 other => panic!("expected CreateType, got {other:?}"),
4373 }
4374 }
4375
4376 #[test]
4379 fn create_type_if_not_exists_parses() {
4380 let stmt = parse("type Post if not exists { id: int }").unwrap();
4381 match stmt {
4382 Statement::CreateType(ct) => assert!(ct.if_not_exists),
4383 other => panic!("expected CreateType, got {other:?}"),
4384 }
4385 }
4386
4387 #[test]
4388 fn create_type_without_clause_defaults_false() {
4389 let stmt = parse("type Post { id: int }").unwrap();
4390 match stmt {
4391 Statement::CreateType(ct) => assert!(!ct.if_not_exists),
4392 other => panic!("expected CreateType, got {other:?}"),
4393 }
4394 }
4395
4396 #[test]
4397 fn drop_if_exists_parses() {
4398 match parse("drop if exists Post").unwrap() {
4399 Statement::DropTable(dt) => assert!(dt.if_exists),
4400 other => panic!("expected DropTable, got {other:?}"),
4401 }
4402 match parse("drop Post").unwrap() {
4403 Statement::DropTable(dt) => assert!(!dt.if_exists),
4404 other => panic!("expected DropTable, got {other:?}"),
4405 }
4406 }
4407
4408 #[test]
4409 fn drop_view_if_exists_parses() {
4410 match parse("drop view if exists ActiveUsers").unwrap() {
4411 Statement::DropView(dv) => {
4412 assert!(dv.if_exists);
4413 assert_eq!(dv.name, "ActiveUsers");
4414 }
4415 other => panic!("expected DropView, got {other:?}"),
4416 }
4417 }
4418
4419 #[test]
4420 fn add_index_and_unique_if_not_exists_parse() {
4421 match parse("alter Post add index if not exists .slug").unwrap() {
4422 Statement::AlterTable(at) => {
4423 assert!(matches!(
4424 at.action,
4425 AlterAction::AddIndex {
4426 if_not_exists: true,
4427 ..
4428 }
4429 ));
4430 }
4431 other => panic!("expected AlterTable, got {other:?}"),
4432 }
4433 match parse("alter Post add unique if not exists .slug").unwrap() {
4434 Statement::AlterTable(at) => {
4435 assert!(matches!(
4436 at.action,
4437 AlterAction::AddUnique {
4438 if_not_exists: true,
4439 ..
4440 }
4441 ));
4442 }
4443 other => panic!("expected AlterTable, got {other:?}"),
4444 }
4445 }
4446
4447 #[test]
4448 fn expression_index_targets_parse_with_stable_table_local_identity() {
4449 use powdb_storage::stored_json_path::{
4450 StoredJsonPathSegmentV1 as Segment, StoredJsonPathV1,
4451 };
4452
4453 let expected = StoredJsonPathV1::new(
4454 "data",
4455 vec![Segment::Key("author".into()), Segment::Index(0)],
4456 );
4457 for query in [
4458 "alter Post add index (.data->author->0)",
4459 "alter Post add unique if not exists (.data->\"author\"->0)",
4460 "alter Post drop index if exists (.data->author->0)",
4461 ] {
4462 let Statement::AlterTable(alter) = parse(query).unwrap() else {
4463 panic!("expected alter table for {query}");
4464 };
4465 let (target, flag) = match alter.action {
4466 AlterAction::AddIndex {
4467 target,
4468 if_not_exists,
4469 }
4470 | AlterAction::AddUnique {
4471 target,
4472 if_not_exists,
4473 } => (target, if_not_exists),
4474 AlterAction::DropIndex { target, if_exists } => (target, if_exists),
4475 other => panic!("expected index action, got {other:?}"),
4476 };
4477 assert_eq!(target, IndexTarget::JsonPath(expected.clone()));
4478 assert_eq!(
4479 flag,
4480 query.contains("if not exists") || query.contains("if exists")
4481 );
4482 }
4483 }
4484
4485 #[test]
4486 fn expression_index_target_rejects_ambiguous_or_non_path_forms() {
4487 let cases = [
4488 (
4489 "alter Post add index .data->author",
4490 "must be parenthesized",
4491 ),
4492 (
4493 "alter Post add index (p.data->author)",
4494 "qualified JSON paths",
4495 ),
4496 (
4497 "alter Post add index (.data)",
4498 "use `.column` for a stored column",
4499 ),
4500 (
4501 "alter Post add index (.data->age + 1)",
4502 "only a direct JSON path",
4503 ),
4504 (
4505 "alter Post drop index ({ value := 1 })",
4506 "expected an unqualified JSON path",
4507 ),
4508 ];
4509 for (query, expected) in cases {
4510 let error = parse(query).expect_err(query).to_string();
4511 assert!(
4512 error.contains(expected),
4513 "`{query}` should mention `{expected}`, got `{error}`"
4514 );
4515 }
4516 }
4517
4518 #[test]
4519 fn alter_drop_column_if_exists_parses() {
4520 match parse("alter Post drop column if exists status").unwrap() {
4521 Statement::AlterTable(at) => {
4522 assert!(matches!(
4523 at.action,
4524 AlterAction::DropColumn {
4525 if_exists: true,
4526 ..
4527 }
4528 ));
4529 }
4530 other => panic!("expected AlterTable, got {other:?}"),
4531 }
4532 }
4533
4534 #[test]
4537 fn schema_parses_to_list_types() {
4538 assert_eq!(parse("schema").unwrap(), Statement::ListTypes);
4539 }
4540
4541 #[test]
4542 fn describe_parses_to_describe() {
4543 assert_eq!(
4544 parse("describe Post").unwrap(),
4545 Statement::Describe("Post".to_string())
4546 );
4547 }
4548
4549 #[test]
4550 fn schema_with_type_aliases_describe() {
4551 assert_eq!(
4552 parse("schema Post").unwrap(),
4553 Statement::Describe("Post".to_string())
4554 );
4555 }
4556
4557 #[test]
4558 fn schema_links_parses_to_list_links() {
4559 assert_eq!(parse("schema links").unwrap(), Statement::ListLinks);
4560 }
4561
4562 #[test]
4563 fn describe_links_still_names_a_table() {
4564 assert_eq!(
4567 parse("describe links").unwrap(),
4568 Statement::Describe("links".to_string())
4569 );
4570 }
4571}
4572
4573#[cfg(test)]
4574mod json_path_tests {
4575 use super::*;
4576
4577 fn filter_of(src: &str) -> Expr {
4579 match parse(src).unwrap() {
4580 Statement::Query(q) => q.filter.expect("expected a filter"),
4581 other => panic!("expected a query, got {other:?}"),
4582 }
4583 }
4584
4585 #[test]
4586 fn ident_key_path() {
4587 let e = filter_of(r#"Post filter .data->author->name = "x""#);
4589 let Expr::BinaryOp(lhs, BinOp::Eq, _) = e else {
4590 panic!("expected an equality, got {e:?}");
4591 };
4592 assert_eq!(
4593 *lhs,
4594 Expr::JsonPath {
4595 base: Box::new(Expr::Field("data".into())),
4596 segments: vec![PathSeg::Key("author".into()), PathSeg::Key("name".into())],
4597 }
4598 );
4599 }
4600
4601 #[test]
4602 fn string_form_key_path() {
4603 let e = filter_of(r#"Post filter .data->"weird key!" = 1"#);
4605 let Expr::BinaryOp(lhs, _, _) = e else {
4606 panic!("expected binop");
4607 };
4608 assert_eq!(
4609 *lhs,
4610 Expr::JsonPath {
4611 base: Box::new(Expr::Field("data".into())),
4612 segments: vec![PathSeg::Key("weird key!".into())],
4613 }
4614 );
4615 }
4616
4617 #[test]
4618 fn array_index_path() {
4619 let e = filter_of(r#"Post filter .data->tags->0 = "rust""#);
4621 let Expr::BinaryOp(lhs, _, _) = e else {
4622 panic!("expected binop");
4623 };
4624 assert_eq!(
4625 *lhs,
4626 Expr::JsonPath {
4627 base: Box::new(Expr::Field("data".into())),
4628 segments: vec![PathSeg::Key("tags".into()), PathSeg::Index(0)],
4629 }
4630 );
4631 }
4632
4633 #[test]
4634 fn qualified_base_path() {
4635 let e = filter_of(r#"Post as posts filter posts.data->author = "a""#);
4637 let Expr::BinaryOp(lhs, _, _) = e else {
4638 panic!("expected binop");
4639 };
4640 assert_eq!(
4641 *lhs,
4642 Expr::JsonPath {
4643 base: Box::new(Expr::QualifiedField {
4644 qualifier: "posts".into(),
4645 field: "data".into(),
4646 }),
4647 segments: vec![PathSeg::Key("author".into())],
4648 }
4649 );
4650 }
4651
4652 #[test]
4653 fn path_binds_tighter_than_comparison_and_arithmetic() {
4654 let e = filter_of("Post filter .data->age > 21");
4656 let Expr::BinaryOp(lhs, BinOp::Gt, rhs) = e else {
4657 panic!("expected a top-level `>`, got {e:?}");
4658 };
4659 assert!(matches!(*lhs, Expr::JsonPath { .. }));
4660 assert_eq!(*rhs, Expr::Literal(Literal::Int(21)));
4661
4662 let e = filter_of("Post filter .a->b + 1 = 3");
4664 let Expr::BinaryOp(add, BinOp::Eq, _) = e else {
4665 panic!("expected eq");
4666 };
4667 let Expr::BinaryOp(lhs, BinOp::Add, _) = *add else {
4668 panic!("expected `+` under `=`, got {add:?}");
4669 };
4670 assert!(matches!(*lhs, Expr::JsonPath { .. }));
4671 }
4672
4673 #[test]
4674 fn dash_vs_arrow_lexing() {
4675 let idx = filter_of("Post filter .a->1 = 0");
4678 let Expr::BinaryOp(lhs, BinOp::Eq, _) = idx else {
4679 panic!("expected eq");
4680 };
4681 assert_eq!(
4682 *lhs,
4683 Expr::JsonPath {
4684 base: Box::new(Expr::Field("a".into())),
4685 segments: vec![PathSeg::Index(1)],
4686 }
4687 );
4688
4689 let sub = filter_of("Post filter .a - 1 = 0");
4692 let Expr::BinaryOp(lhs, BinOp::Eq, _) = sub else {
4693 panic!("expected eq");
4694 };
4695 assert!(
4696 matches!(*lhs, Expr::BinaryOp(_, BinOp::Sub, _)),
4697 "`.a - 1` should be subtraction, got {lhs:?}"
4698 );
4699
4700 assert!(
4705 parse("Post filter .a-1 = 0").is_err(),
4706 "`.a-1` should fail to parse (negative-literal gotcha)"
4707 );
4708
4709 assert!(
4711 parse("Post filter .a - > 0").is_err(),
4712 "`.a - >` should fail to parse"
4713 );
4714 }
4715
4716 #[test]
4717 fn negative_index_rejected() {
4718 let err = parse("Post filter .data->-1 = 0").unwrap_err();
4719 assert!(
4720 err.to_string().contains("array index"),
4721 "expected an index error, got: {err}"
4722 );
4723 }
4724
4725 #[test]
4726 fn path_on_literal_base_rejected() {
4727 let err = parse("Post filter 5->x = 1").unwrap_err();
4729 assert!(
4730 err.to_string().to_lowercase().contains("field base"),
4731 "expected a field-base error, got: {err}"
4732 );
4733 }
4734
4735 #[test]
4736 fn json_path_assignment_target_is_targeted_unsupported() {
4737 for stmt in [
4742 "Doc update { .data->x := 5 }",
4743 "Doc update { data->x := 5 }",
4744 ] {
4745 let err = parse(stmt).unwrap_err();
4746 assert!(
4747 matches!(err, ParseError::Unsupported { .. }),
4748 "{stmt}: expected Unsupported, got {err:?}"
4749 );
4750 let msg = err.to_string();
4751 assert!(
4752 msg.contains("JSON path assignment targets are not supported"),
4753 "{stmt}: message must state the unsupported feature: {msg}"
4754 );
4755 assert!(
4756 msg.contains("json_set"),
4757 "{stmt}: message must point at the whole-column alternative: {msg}"
4758 );
4759 }
4760 assert!(parse(r#"Doc update { data := "{}" }"#).is_ok());
4762 }
4763
4764 #[test]
4765 fn json_type_scalar_parses() {
4766 let e = filter_of(r#"Post filter json_type(.data->x) = "string""#);
4767 let Expr::BinaryOp(lhs, _, _) = e else {
4768 panic!("expected binop");
4769 };
4770 let Expr::ScalarFunc(ScalarFn::JsonType, args) = *lhs else {
4771 panic!("expected json_type call, got {lhs:?}");
4772 };
4773 assert_eq!(args.len(), 1);
4774 assert!(matches!(args[0], Expr::JsonPath { .. }));
4775 }
4776
4777 #[test]
4778 fn path_in_projection() {
4779 let stmt = parse("Post { author: .data->author }").unwrap();
4782 let Statement::Query(q) = stmt else {
4783 panic!("expected query");
4784 };
4785 let proj = q.projection.unwrap();
4786 assert_eq!(proj[0].alias.as_deref(), Some("author"));
4787 assert!(matches!(proj[0].expr, Expr::JsonPath { .. }));
4788
4789 let Statement::Query(ordered) = parse("Post order .data->author { .id }").unwrap() else {
4790 panic!("expected query");
4791 };
4792 assert!(matches!(
4793 ordered.order.unwrap().keys[0].expr,
4794 Expr::JsonPath { .. }
4795 ));
4796
4797 let Statement::Query(grouped) =
4798 parse("Post group .data->author { .data->author }").unwrap()
4799 else {
4800 panic!("expected query");
4801 };
4802 assert!(matches!(
4803 grouped.group_by.unwrap().keys[0].expr,
4804 Expr::JsonPath { .. }
4805 ));
4806 }
4807}