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::Alter => self.parse_alter_table(),
365 Token::Drop => self.parse_drop_or_drop_view(),
366 Token::Materialized => self.parse_create_view(),
367 Token::Refresh => self.parse_refresh_view(),
368 Token::Begin => {
369 self.advance();
370 if *self.peek() == Token::Transaction {
372 self.advance();
373 }
374 return Ok(Statement::Begin);
375 }
376 Token::Commit => {
377 self.advance();
378 return Ok(Statement::Commit);
379 }
380 Token::Rollback => {
381 self.advance();
382 return Ok(Statement::Rollback);
383 }
384 Token::Schema => self.parse_schema(),
385 Token::Describe => self.parse_describe(),
386 Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
387 self.parse_aggregate_query()
388 }
389 Token::Ident(_) => self.parse_query_or_mutation(),
390 Token::Update => Err(ParseError::Syntax {
391 message: "'update' cannot start a statement — in PowQL, use pipeline syntax: \
392 TableName filter ... update { ... }"
393 .into(),
394 }),
395 Token::Delete => Err(ParseError::Syntax {
396 message: "'delete' cannot start a statement — in PowQL, use pipeline syntax: \
397 TableName filter ... delete"
398 .into(),
399 }),
400 _ => Err(self.unexpected("statement", self.peek())),
401 }?;
402 let result = self.maybe_parse_union(stmt);
404 self.depth -= 1;
405 result
406 }
407
408 fn parse_query_or_mutation(&mut self) -> Result<Statement, ParseError> {
409 let source = match self.advance() {
410 Token::Ident(name) => name,
411 t => {
412 return Err(ParseError::UnexpectedToken {
413 expected: "type name".into(),
414 got: t.display_name(),
415 })
416 }
417 };
418 let alias = self.try_parse_alias();
419 let joins = self.parse_joins()?;
420
421 let mut filter = None;
425 let mut order = None;
426 let mut limit = None;
427 let mut offset = None;
428 let mut projection = None;
429 let mut distinct = false;
430 let mut group_by = None;
431
432 loop {
433 match self.peek() {
434 Token::Distinct => {
435 self.advance();
436 distinct = true;
437 }
438 Token::Group => {
439 self.advance();
440 group_by = Some(self.parse_group_by()?);
441 }
442 Token::Filter => {
443 self.advance();
444 filter = Some(self.parse_expr()?);
445 }
446 Token::Order => {
447 self.advance();
448 order = Some(self.parse_order()?);
449 }
450 Token::Limit => {
451 self.advance();
452 limit = Some(self.parse_expr()?);
453 }
454 Token::Offset => {
455 self.advance();
456 offset = Some(self.parse_expr()?);
457 }
458 Token::LBrace => {
459 projection = Some(self.parse_projection()?);
460 }
461 Token::Having => {
462 self.advance();
464 let having_expr = self.parse_expr()?;
465 let group = group_by.as_mut().ok_or_else(|| ParseError::Syntax {
466 message: "having without group by".into(),
467 })?;
468 let rewritten = match projection.as_ref() {
469 Some(fields) => substitute_projection_aliases(having_expr, fields),
470 None => having_expr,
471 };
472 group.having = Some(match group.having.take() {
473 Some(existing) => {
474 Expr::BinaryOp(Box::new(existing), BinOp::And, Box::new(rewritten))
475 }
476 None => rewritten,
477 });
478 }
479 Token::Update => {
480 if !joins.is_empty() {
481 return Err(ParseError::Unsupported {
482 feature: "update on a joined query is not supported".into(),
483 });
484 }
485 self.advance();
486 let assignments = self.parse_assignments()?;
487 let returning = *self.peek() == Token::Returning;
489 if returning {
490 self.advance();
491 }
492 return Ok(Statement::UpdateQuery(UpdateExpr {
493 source,
494 alias,
495 filter,
496 assignments,
497 returning,
498 }));
499 }
500 Token::Delete => {
501 if !joins.is_empty() {
502 return Err(ParseError::Unsupported {
503 feature: "delete on a joined query is not supported".into(),
504 });
505 }
506 self.advance();
507 let returning = *self.peek() == Token::Returning;
509 if returning {
510 self.advance();
511 }
512 return Ok(Statement::DeleteQuery(DeleteExpr {
513 source,
514 alias,
515 filter,
516 returning,
517 }));
518 }
519 _ => break,
520 }
521 }
522
523 Ok(Statement::Query(QueryExpr {
524 source,
525 alias,
526 joins,
527 filter,
528 order,
529 limit,
530 offset,
531 projection,
532 aggregation: None,
533 distinct,
534 group_by,
535 }))
536 }
537
538 fn parse_query_tail(&mut self, source: String) -> Result<QueryExpr, ParseError> {
544 let alias = self.try_parse_alias();
545 let joins = self.parse_joins()?;
546 let mut filter = None;
547 let mut order = None;
548 let mut limit = None;
549 let mut offset = None;
550 let mut projection = None;
551 let mut distinct = false;
552 let mut group_by = None;
553
554 loop {
555 match self.peek() {
556 Token::Distinct => {
557 self.advance();
558 distinct = true;
559 }
560 Token::Group => {
561 self.advance();
562 group_by = Some(self.parse_group_by()?);
563 }
564 Token::Filter => {
565 self.advance();
566 filter = Some(self.parse_expr()?);
567 }
568 Token::Order => {
569 self.advance();
570 order = Some(self.parse_order()?);
571 }
572 Token::Limit => {
573 self.advance();
574 limit = Some(self.parse_expr()?);
575 }
576 Token::Offset => {
577 self.advance();
578 offset = Some(self.parse_expr()?);
579 }
580 Token::LBrace => {
581 projection = Some(self.parse_projection()?);
582 }
583 Token::Having => {
584 self.advance();
590 let having_expr = self.parse_expr()?;
591 let group = group_by.as_mut().ok_or_else(|| ParseError::Syntax {
592 message: "having without group by".into(),
593 })?;
594 let rewritten = match projection.as_ref() {
595 Some(fields) => substitute_projection_aliases(having_expr, fields),
596 None => having_expr,
597 };
598 group.having = Some(match group.having.take() {
599 Some(existing) => {
600 Expr::BinaryOp(Box::new(existing), BinOp::And, Box::new(rewritten))
601 }
602 None => rewritten,
603 });
604 }
605 _ => break,
606 }
607 }
608
609 Ok(QueryExpr {
610 source,
611 alias,
612 joins,
613 filter,
614 order,
615 limit,
616 offset,
617 projection,
618 aggregation: None,
619 distinct,
620 group_by,
621 })
622 }
623
624 fn try_parse_alias(&mut self) -> Option<String> {
628 if *self.peek() == Token::As {
629 self.advance();
630 if let Token::Ident(name) = self.peek().clone() {
631 self.advance();
632 return Some(name);
633 }
634 }
635 None
636 }
637
638 fn parse_joins(&mut self) -> Result<Vec<JoinClause>, ParseError> {
645 let mut joins = Vec::new();
646 loop {
647 let kind = match self.peek() {
648 Token::Join => {
649 self.advance();
650 JoinKind::Inner
651 }
652 Token::Inner => {
653 self.advance();
654 self.expect(&Token::Join)?;
655 JoinKind::Inner
656 }
657 Token::LeftKw => {
658 self.advance();
659 if *self.peek() == Token::Outer {
660 self.advance();
661 }
662 self.expect(&Token::Join)?;
663 JoinKind::LeftOuter
664 }
665 Token::RightKw => {
666 self.advance();
667 if *self.peek() == Token::Outer {
668 self.advance();
669 }
670 self.expect(&Token::Join)?;
671 JoinKind::RightOuter
672 }
673 Token::Cross => {
674 self.advance();
675 self.expect(&Token::Join)?;
676 JoinKind::Cross
677 }
678 _ => break,
679 };
680
681 let source = match self.advance() {
682 Token::Ident(name) => name,
683 t => {
684 return Err(ParseError::UnexpectedToken {
685 expected: "type name after join".into(),
686 got: t.display_name(),
687 });
688 }
689 };
690 let alias = self.try_parse_alias();
691 let on = if kind == JoinKind::Cross {
692 None
693 } else if *self.peek() == Token::On {
694 self.advance();
695 Some(self.parse_expr()?)
696 } else {
697 return Err(ParseError::Syntax {
698 message: format!("expected `on <expr>` after join {source}"),
699 });
700 };
701
702 joins.push(JoinClause {
703 kind,
704 source,
705 alias,
706 on,
707 });
708 }
709 Ok(joins)
710 }
711
712 fn parse_insert(&mut self) -> Result<Statement, ParseError> {
713 self.expect(&Token::Insert)?;
714 let target = match self.advance() {
715 Token::Ident(name) => name,
716 t => {
717 return Err(ParseError::UnexpectedToken {
718 expected: "type name".into(),
719 got: t.display_name(),
720 })
721 }
722 };
723 let mut rows = vec![self.parse_assignments()?];
727 while *self.peek() == Token::Comma {
728 self.advance(); rows.push(self.parse_assignments()?);
730 }
731 let returning = *self.peek() == Token::Returning;
733 if returning {
734 self.advance();
735 }
736 Ok(Statement::Insert(InsertExpr {
737 target,
738 rows,
739 returning,
740 }))
741 }
742
743 fn parse_upsert(&mut self) -> Result<Statement, ParseError> {
745 self.expect(&Token::Upsert)?;
746 let target = match self.advance() {
747 Token::Ident(name) => name,
748 t => {
749 return Err(ParseError::UnexpectedToken {
750 expected: "type name".into(),
751 got: t.display_name(),
752 })
753 }
754 };
755 self.expect(&Token::On)?;
756 let key_column = match self.advance() {
757 Token::DotIdent(name) => name,
758 t => {
759 return Err(ParseError::UnexpectedToken {
760 expected: ".key_column".into(),
761 got: t.display_name(),
762 })
763 }
764 };
765 let assignments = self.parse_assignments()?;
766 let on_conflict = if *self.peek() == Token::On {
767 self.advance(); self.expect(&Token::Conflict)?;
769 self.parse_assignments()?
770 } else {
771 Vec::new()
772 };
773 Ok(Statement::Upsert(UpsertExpr {
774 target,
775 key_column,
776 assignments,
777 on_conflict,
778 }))
779 }
780
781 fn parse_assignments(&mut self) -> Result<Vec<Assignment>, ParseError> {
782 self.expect(&Token::LBrace)?;
783 let mut assignments = Vec::new();
784 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
785 if matches!(self.peek(), Token::DotIdent(_) | Token::Ident(_))
792 && matches!(self.tokens.get(self.pos + 1), Some(Token::Arrow))
793 {
794 let field = match self.peek() {
795 Token::DotIdent(n) | Token::Ident(n) => n.clone(),
796 _ => unreachable!("guarded by the matches! above"),
797 };
798 return Err(ParseError::Unsupported {
799 feature: format!(
800 "cannot assign to a JSON path target `.{field}->...` (at token {pos}): \
801 JSON path assignment targets are not supported; write the whole JSON \
802 column instead (path mutation such as json_set is not yet available)",
803 pos = self.pos
804 ),
805 });
806 }
807 let field = self.expect_named_ident("field name")?;
808 self.expect(&Token::Assign)?;
809 let value = self.parse_expr()?;
810 assignments.push(Assignment { field, value });
811 if *self.peek() == Token::Comma {
812 self.advance();
813 }
814 }
815 self.expect(&Token::RBrace)?;
816 Ok(assignments)
817 }
818
819 fn parse_projection(&mut self) -> Result<Vec<ProjectionField>, ParseError> {
820 self.expect(&Token::LBrace)?;
821 let mut fields = Vec::new();
822 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
823 if matches!(self.peek(), Token::Ident(_))
831 && matches!(self.tokens.get(self.pos + 1), Some(Token::Colon))
832 {
833 let alias = match self.advance() {
834 Token::Ident(name) => name,
835 _ => unreachable!("guarded by the matches! above"),
836 };
837 self.advance(); let expr = if matches!(self.peek(), Token::Ident(_))
841 && matches!(self.tokens.get(self.pos + 1), Some(Token::As))
842 {
843 Expr::NestedQuery(Box::new(self.parse_nested_query()?))
844 } else {
845 self.parse_expr()?
846 };
847 fields.push(ProjectionField {
848 alias: Some(alias),
849 expr,
850 });
851 } else {
852 if matches!(self.peek(), Token::Ident(_))
853 && matches!(self.tokens.get(self.pos + 1), Some(Token::As))
854 {
855 return Err(ParseError::Syntax {
856 message: "a nested projection needs a field name: \
857 `<name>: <Table> as <alias> filter ... { ... }`"
858 .into(),
859 });
860 }
861 let expr = self.parse_expr()?;
862 fields.push(ProjectionField { alias: None, expr });
863 }
864 if *self.peek() == Token::Comma {
865 self.advance();
866 }
867 }
868 self.expect(&Token::RBrace)?;
869 Ok(fields)
870 }
871
872 fn parse_nested_query(&mut self) -> Result<NestedQuery, ParseError> {
877 self.depth += 1;
880 if self.depth > MAX_NESTING_DEPTH {
881 self.depth -= 1;
882 return Err(ParseError::NestingDepthExceeded {
883 max: MAX_NESTING_DEPTH,
884 });
885 }
886 let result = self.parse_nested_query_inner();
887 self.depth -= 1;
888 result
889 }
890
891 fn parse_nested_query_inner(&mut self) -> Result<NestedQuery, ParseError> {
892 let source = self.expect_named_ident("nested source type")?;
893 self.expect(&Token::As)?;
894 let alias = self.expect_named_ident("nested source alias")?;
895 self.expect(&Token::Filter)?;
896 let filter = self.parse_expr()?;
897 let mut order = None;
898 let mut limit = None;
899 let mut offset = None;
900 let mut offset_before_limit = false;
901 loop {
902 match self.peek() {
903 Token::Order => {
904 self.advance();
905 order = Some(self.parse_order()?);
906 }
907 Token::Limit => {
908 self.advance();
909 if offset.is_some() {
910 offset_before_limit = true;
911 }
912 limit = Some(self.parse_expr()?);
913 }
914 Token::Offset => {
915 self.advance();
916 offset = Some(self.parse_expr()?);
917 }
918 _ => break,
919 }
920 }
921 self.expect(&Token::LBrace)?;
922 let mut fields = Vec::new();
923 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
924 let alias = if matches!(self.peek(), Token::Ident(_))
925 && matches!(self.tokens.get(self.pos + 1), Some(Token::Colon))
926 {
927 let alias = self.expect_named_ident("field alias")?;
928 self.advance(); Some(alias)
930 } else {
931 None
932 };
933 let expr = if matches!(self.peek(), Token::Ident(_))
934 && matches!(self.tokens.get(self.pos + 1), Some(Token::As))
935 {
936 if alias.is_none() {
937 return Err(ParseError::Syntax {
938 message: "a nested projection needs a field name: \
939 `<name>: <Table> as <alias> filter ... { ... }`"
940 .into(),
941 });
942 }
943 Expr::NestedQuery(Box::new(self.parse_nested_query()?))
944 } else {
945 self.parse_expr()?
946 };
947 fields.push(ProjectionField { alias, expr });
948 if *self.peek() == Token::Comma {
949 self.advance();
950 }
951 }
952 self.expect(&Token::RBrace)?;
953 if fields.is_empty() {
954 return Err(ParseError::Syntax {
955 message: "nested projection requires at least one field".into(),
956 });
957 }
958 Ok(NestedQuery {
959 source,
960 alias,
961 filter,
962 order,
963 limit,
964 offset,
965 offset_before_limit,
966 fields,
967 })
968 }
969
970 fn parse_over_clause(&mut self) -> Result<(Vec<Expr>, Vec<OrderKey>), ParseError> {
973 self.expect(&Token::Over)?;
974 self.expect(&Token::LParen)?;
975 let mut partition_by = Vec::new();
976 let mut order_by = Vec::new();
977 if *self.peek() == Token::Partition {
978 self.advance();
979 loop {
980 partition_by.push(self.parse_expr()?);
981 if *self.peek() == Token::Comma {
982 if !matches!(
983 self.tokens.get(self.pos + 1),
984 Some(Token::Order | Token::RParen)
985 ) {
986 self.advance();
987 } else {
988 break;
989 }
990 } else {
991 break;
992 }
993 }
994 }
995 if *self.peek() == Token::Order {
996 self.advance();
997 loop {
998 let expr = self.parse_expr()?;
999 let descending = match self.peek() {
1000 Token::Desc => {
1001 self.advance();
1002 true
1003 }
1004 Token::Asc => {
1005 self.advance();
1006 false
1007 }
1008 _ => false,
1009 };
1010 order_by.push(OrderKey { expr, descending });
1011 if *self.peek() == Token::Comma {
1012 self.advance();
1013 } else {
1014 break;
1015 }
1016 }
1017 }
1018 self.expect(&Token::RParen)?;
1019 Ok((partition_by, order_by))
1020 }
1021
1022 fn parse_cast_type(&mut self) -> Result<CastType, ParseError> {
1024 match self.advance() {
1025 Token::StringLit(s) => match s.as_str() {
1026 "int" | "Int" | "INT" => Ok(CastType::Int),
1027 "float" | "Float" | "FLOAT" => Ok(CastType::Float),
1028 "str" | "Str" | "STR" | "string" | "String" => Ok(CastType::Str),
1029 "bool" | "Bool" | "BOOL" | "boolean" => Ok(CastType::Bool),
1030 "datetime" | "DateTime" | "DATETIME" => Ok(CastType::DateTime),
1031 "uuid" | "Uuid" | "UUID" => Ok(CastType::Uuid),
1032 "bytes" | "Bytes" | "BYTES" | "bytea" => Ok(CastType::Bytes),
1033 other => Err(ParseError::Syntax {
1034 message: format!("invalid cast type: \"{other}\""),
1035 }),
1036 },
1037 t => Err(ParseError::UnexpectedToken {
1038 expected: "string literal for cast type".into(),
1039 got: t.display_name(),
1040 }),
1041 }
1042 }
1043
1044 fn parse_order(&mut self) -> Result<OrderClause, ParseError> {
1045 let mut keys = Vec::new();
1046 loop {
1047 let expr = self.parse_expr()?;
1048 let descending = match self.peek() {
1049 Token::Desc => {
1050 self.advance();
1051 true
1052 }
1053 Token::Asc => {
1054 self.advance();
1055 false
1056 }
1057 _ => false,
1058 };
1059 keys.push(OrderKey { expr, descending });
1060 if *self.peek() == Token::Comma {
1061 self.advance();
1062 } else {
1063 break;
1064 }
1065 }
1066 Ok(OrderClause { keys })
1067 }
1068
1069 fn parse_aggregate_query(&mut self) -> Result<Statement, ParseError> {
1070 let mut func = match self.advance() {
1071 Token::Count => AggFunc::Count,
1072 Token::Avg => AggFunc::Avg,
1073 Token::Sum => AggFunc::Sum,
1074 Token::Min => AggFunc::Min,
1075 Token::Max => AggFunc::Max,
1076 t => {
1077 return Err(ParseError::UnexpectedToken {
1078 expected: "aggregate function".into(),
1079 got: t.display_name(),
1080 })
1081 }
1082 };
1083 self.expect(&Token::LParen)?;
1084 let mode = if *self.peek() == Token::Raw {
1085 self.advance();
1086 AggregateMode::Raw
1087 } else {
1088 AggregateMode::Symmetric
1089 };
1090 if func == AggFunc::Count && *self.peek() == Token::Distinct {
1092 self.advance();
1093 func = AggFunc::CountDistinct;
1094 }
1095 let source = match self.advance() {
1096 Token::Ident(name) => name,
1097 t => {
1098 return Err(ParseError::UnexpectedToken {
1099 expected: "type name".into(),
1100 got: t.display_name(),
1101 })
1102 }
1103 };
1104 let mut query = self.parse_query_tail(source)?;
1108 self.expect(&Token::RParen)?;
1109
1110 let mut argument: Option<Expr> = None;
1117 if func != AggFunc::Count {
1118 if let Some(proj) = &query.projection {
1119 if proj.len() == 1 && proj[0].alias.is_none() {
1120 argument = Some(proj[0].expr.clone());
1121 }
1122 }
1123 if argument.is_some() {
1124 query.projection = None;
1125 }
1126 }
1127 query.aggregation = Some(AggregateExpr {
1128 function: func,
1129 argument,
1130 mode,
1131 });
1132 Ok(Statement::Query(query))
1133 }
1134
1135 fn parse_expr(&mut self) -> Result<Expr, ParseError> {
1136 self.depth += 1;
1137 if self.depth > MAX_NESTING_DEPTH {
1138 self.depth -= 1;
1139 return Err(ParseError::NestingDepthExceeded {
1140 max: MAX_NESTING_DEPTH,
1141 });
1142 }
1143 let result = self.parse_or_expr();
1144 self.depth -= 1;
1145 result
1146 }
1147
1148 fn parse_or_expr(&mut self) -> Result<Expr, ParseError> {
1149 let mut left = self.parse_and_expr()?;
1150 while *self.peek() == Token::Or {
1151 self.advance();
1152 let right = self.parse_and_expr()?;
1153 left = Expr::BinaryOp(Box::new(left), BinOp::Or, Box::new(right));
1154 }
1155 Ok(left)
1156 }
1157
1158 fn parse_and_expr(&mut self) -> Result<Expr, ParseError> {
1159 let mut left = self.parse_comparison()?;
1160 while *self.peek() == Token::And {
1161 self.advance();
1162 let right = self.parse_comparison()?;
1163 left = Expr::BinaryOp(Box::new(left), BinOp::And, Box::new(right));
1164 }
1165 Ok(left)
1166 }
1167
1168 fn parse_comparison(&mut self) -> Result<Expr, ParseError> {
1169 let mut negations = 0usize;
1179 while *self.peek() == Token::Not
1180 && !matches!(self.tokens.get(self.pos + 1), Some(Token::Exists))
1181 {
1182 self.advance();
1183 negations += 1;
1184 if self.depth + negations > MAX_NESTING_DEPTH {
1185 return Err(ParseError::NestingDepthExceeded {
1186 max: MAX_NESTING_DEPTH,
1187 });
1188 }
1189 }
1190 let mut expr = self.parse_comparison_body()?;
1191 for _ in 0..negations {
1192 expr = Expr::UnaryOp(UnaryOp::Not, Box::new(expr));
1193 }
1194 Ok(expr)
1195 }
1196
1197 fn parse_comparison_body(&mut self) -> Result<Expr, ParseError> {
1198 let left = self.parse_additive()?;
1199
1200 if *self.peek() == Token::Is {
1202 self.advance();
1203 if *self.peek() == Token::Not {
1204 self.advance();
1205 self.expect(&Token::Null)?;
1206 return Ok(Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(left)));
1207 } else {
1208 self.expect(&Token::Null)?;
1209 return Ok(Expr::UnaryOp(UnaryOp::IsNull, Box::new(left)));
1210 }
1211 }
1212
1213 match self.peek() {
1216 Token::In => {
1217 self.advance();
1218 return self.parse_in_list(left, false);
1219 }
1220 Token::Like => {
1221 self.advance();
1222 let pattern = self.parse_additive()?;
1223 return Ok(Expr::BinaryOp(
1224 Box::new(left),
1225 BinOp::Like,
1226 Box::new(pattern),
1227 ));
1228 }
1229 Token::Between => {
1230 self.advance();
1231 return self.parse_between(left, false);
1232 }
1233 Token::Not => {
1234 let next = self.tokens.get(self.pos + 1);
1238 match next {
1239 Some(Token::In) => {
1240 self.advance(); self.advance(); return self.parse_in_list(left, true);
1243 }
1244 Some(Token::Like) => {
1245 self.advance(); self.advance(); let pattern = self.parse_additive()?;
1248 let like = Expr::BinaryOp(Box::new(left), BinOp::Like, Box::new(pattern));
1249 return Ok(Expr::UnaryOp(UnaryOp::Not, Box::new(like)));
1250 }
1251 Some(Token::Between) => {
1252 self.advance(); self.advance(); return self.parse_between(left, true);
1255 }
1256 _ => {}
1257 }
1258 }
1259 _ => {}
1260 }
1261
1262 let op = match self.peek() {
1263 Token::Eq => BinOp::Eq,
1264 Token::Neq => BinOp::Neq,
1265 Token::Lt => BinOp::Lt,
1266 Token::Gt => BinOp::Gt,
1267 Token::Lte => BinOp::Lte,
1268 Token::Gte => BinOp::Gte,
1269 _ => return Ok(left),
1270 };
1271 self.advance();
1272 if *self.peek() == Token::Null {
1276 match op {
1277 BinOp::Eq => {
1278 self.advance();
1279 return Ok(Expr::UnaryOp(UnaryOp::IsNull, Box::new(left)));
1280 }
1281 BinOp::Neq => {
1282 self.advance();
1283 return Ok(Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(left)));
1284 }
1285 _ => {}
1286 }
1287 }
1288 let right = self.parse_additive()?;
1289 Ok(Expr::BinaryOp(Box::new(left), op, Box::new(right)))
1290 }
1291
1292 fn parse_in_list(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParseError> {
1297 self.expect(&Token::LParen)?;
1298 if let Token::Ident(_) = self.peek() {
1300 let after = self.tokens.get(self.pos + 1);
1303 let is_subquery = !matches!(after, Some(Token::Comma) | Some(Token::RParen));
1304 if is_subquery {
1305 let source = match self.advance() {
1306 Token::Ident(name) => name,
1307 _ => unreachable!(),
1308 };
1309 let subquery = self.parse_query_tail(source)?;
1310 self.expect(&Token::RParen)?;
1311 return Ok(Expr::InSubquery {
1312 expr: Box::new(expr),
1313 subquery: Box::new(subquery),
1314 negated,
1315 });
1316 }
1317 }
1318 let mut list = Vec::new();
1319 while !matches!(self.peek(), Token::RParen | Token::Eof) {
1320 list.push(self.parse_expr()?);
1321 if *self.peek() == Token::Comma {
1322 self.advance();
1323 }
1324 }
1325 self.expect(&Token::RParen)?;
1326 Ok(Expr::InList {
1327 expr: Box::new(expr),
1328 list,
1329 negated,
1330 })
1331 }
1332
1333 fn try_parse_exists_subquery(&mut self) -> Result<Option<QueryExpr>, ParseError> {
1341 if *self.peek() != Token::LParen {
1342 return Ok(None);
1343 }
1344 let after_lparen = self.tokens.get(self.pos + 1);
1348 if !matches!(after_lparen, Some(Token::Ident(_))) {
1349 return Ok(None);
1350 }
1351 self.expect(&Token::LParen)?;
1352 let source = match self.advance() {
1353 Token::Ident(name) => name,
1354 _ => unreachable!(),
1355 };
1356 let subquery = self.parse_query_tail(source)?;
1357 self.expect(&Token::RParen)?;
1358 Ok(Some(subquery))
1359 }
1360
1361 fn parse_between(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParseError> {
1365 let low = self.parse_additive()?;
1366 self.expect(&Token::And)?;
1367 let high = self.parse_additive()?;
1368 if negated {
1369 Ok(Expr::BinaryOp(
1371 Box::new(Expr::BinaryOp(
1372 Box::new(expr.clone()),
1373 BinOp::Lt,
1374 Box::new(low),
1375 )),
1376 BinOp::Or,
1377 Box::new(Expr::BinaryOp(Box::new(expr), BinOp::Gt, Box::new(high))),
1378 ))
1379 } else {
1380 Ok(Expr::BinaryOp(
1382 Box::new(Expr::BinaryOp(
1383 Box::new(expr.clone()),
1384 BinOp::Gte,
1385 Box::new(low),
1386 )),
1387 BinOp::And,
1388 Box::new(Expr::BinaryOp(Box::new(expr), BinOp::Lte, Box::new(high))),
1389 ))
1390 }
1391 }
1392
1393 fn parse_group_by(&mut self) -> Result<GroupByClause, ParseError> {
1395 let mut keys = Vec::new();
1396 loop {
1397 let expr = self.parse_expr()?;
1398 let output_name = match &expr {
1399 Expr::Field(_) | Expr::QualifiedField { .. } | Expr::JsonPath { .. } => {
1400 expression_output_name(&expr)
1401 }
1402 _ => format!("__group_{}", keys.len()),
1403 };
1404 keys.push(GroupKey { expr, output_name });
1405 if *self.peek() == Token::Comma {
1406 self.advance();
1407 } else {
1408 break;
1409 }
1410 }
1411 if keys.is_empty() {
1412 return Err(ParseError::Syntax {
1413 message: "expected at least one group key after group".into(),
1414 });
1415 }
1416 let having = if *self.peek() == Token::Having {
1417 self.advance();
1418 Some(self.parse_expr()?)
1419 } else {
1420 None
1421 };
1422 Ok(GroupByClause { keys, having })
1423 }
1424
1425 fn parse_additive(&mut self) -> Result<Expr, ParseError> {
1426 let mut left = self.parse_multiplicative()?;
1427 loop {
1428 let op = match self.peek() {
1429 Token::Plus => BinOp::Add,
1430 Token::Minus => BinOp::Sub,
1431 Token::Coalesce => {
1432 self.advance();
1433 let right = self.parse_multiplicative()?;
1434 left = Expr::Coalesce(Box::new(left), Box::new(right));
1435 continue;
1436 }
1437 _ => break,
1438 };
1439 self.advance();
1440 let right = self.parse_multiplicative()?;
1441 left = Expr::BinaryOp(Box::new(left), op, Box::new(right));
1442 }
1443 Ok(left)
1444 }
1445
1446 fn parse_multiplicative(&mut self) -> Result<Expr, ParseError> {
1447 let mut left = self.parse_primary()?;
1448 loop {
1449 let op = match self.peek() {
1450 Token::Star => BinOp::Mul,
1451 Token::Slash => BinOp::Div,
1452 _ => break,
1453 };
1454 self.advance();
1455 let right = self.parse_primary()?;
1456 left = Expr::BinaryOp(Box::new(left), op, Box::new(right));
1457 }
1458 Ok(left)
1459 }
1460
1461 fn parse_primary(&mut self) -> Result<Expr, ParseError> {
1462 self.depth += 1;
1467 if self.depth > MAX_NESTING_DEPTH {
1468 self.depth -= 1;
1469 return Err(ParseError::NestingDepthExceeded {
1470 max: MAX_NESTING_DEPTH,
1471 });
1472 }
1473 let result = self.parse_primary_inner();
1474 self.depth -= 1;
1475 self.parse_json_path_postfix(result?)
1481 }
1482
1483 fn parse_json_path_postfix(&mut self, base: Expr) -> Result<Expr, ParseError> {
1490 if *self.peek() != Token::Arrow {
1491 return Ok(base);
1492 }
1493 match &base {
1494 Expr::Field(_) | Expr::QualifiedField { .. } | Expr::JsonPath { .. } => {}
1495 _ => {
1496 return Err(ParseError::Syntax {
1497 message: "'->' JSON path access requires a field base \
1498 (e.g. .data->key or posts.data->author)"
1499 .into(),
1500 })
1501 }
1502 }
1503 let mut segments = Vec::new();
1504 while *self.peek() == Token::Arrow {
1505 self.advance(); let seg = match self.advance() {
1507 Token::Ident(name) => PathSeg::Key(name),
1509 Token::StringLit(s) => PathSeg::Key(s),
1512 Token::IntLit(v) => {
1516 let idx = u32::try_from(v).map_err(|_| ParseError::Syntax {
1517 message: format!(
1518 "invalid JSON path array index {v}: expected a non-negative integer that fits in 32 bits"
1519 ),
1520 })?;
1521 PathSeg::Index(idx)
1522 }
1523 other => {
1524 return Err(ParseError::Syntax {
1525 message: format!(
1526 "expected a JSON path segment (object key or array index) after '->', found {}",
1527 other.display_name()
1528 ),
1529 })
1530 }
1531 };
1532 segments.push(seg);
1533 }
1534 if let Expr::JsonPath {
1537 base: inner_base,
1538 segments: mut inner_segments,
1539 } = base
1540 {
1541 inner_segments.extend(segments);
1542 return Ok(Expr::JsonPath {
1543 base: inner_base,
1544 segments: inner_segments,
1545 });
1546 }
1547 Ok(Expr::JsonPath {
1548 base: Box::new(base),
1549 segments,
1550 })
1551 }
1552
1553 fn parse_primary_inner(&mut self) -> Result<Expr, ParseError> {
1554 match self.peek().clone() {
1555 Token::DotIdent(name) => {
1556 self.advance();
1557 Ok(Expr::Field(name))
1558 }
1559 Token::IntLit(v) => {
1560 self.advance();
1561 Ok(Expr::Literal(Literal::Int(v)))
1562 }
1563 Token::FloatLit(v) => {
1564 self.advance();
1565 Ok(Expr::Literal(Literal::Float(v)))
1566 }
1567 Token::StringLit(v) => {
1568 self.advance();
1569 Ok(Expr::Literal(Literal::String(v)))
1570 }
1571 Token::BoolLit(v) => {
1572 self.advance();
1573 Ok(Expr::Literal(Literal::Bool(v)))
1574 }
1575 Token::Null => {
1582 self.advance();
1583 Ok(Expr::Null)
1584 }
1585 Token::Not => {
1586 self.advance();
1587 if *self.peek() == Token::Exists {
1588 self.advance();
1589 if let Some(sub) = self.try_parse_exists_subquery()? {
1593 return Ok(Expr::ExistsSubquery {
1594 subquery: Box::new(sub),
1595 negated: true,
1596 });
1597 }
1598 let expr = self.parse_primary()?;
1599 Ok(Expr::UnaryOp(UnaryOp::NotExists, Box::new(expr)))
1600 } else {
1601 let expr = self.parse_primary()?;
1602 Ok(Expr::UnaryOp(UnaryOp::Not, Box::new(expr)))
1603 }
1604 }
1605 Token::Exists => {
1606 self.advance();
1607 if let Some(sub) = self.try_parse_exists_subquery()? {
1611 return Ok(Expr::ExistsSubquery {
1612 subquery: Box::new(sub),
1613 negated: false,
1614 });
1615 }
1616 let expr = self.parse_primary()?;
1617 Ok(Expr::UnaryOp(UnaryOp::Exists, Box::new(expr)))
1618 }
1619 Token::LParen => {
1620 self.advance();
1621 let expr = self.parse_expr()?;
1622 self.expect(&Token::RParen)?;
1623 Ok(expr)
1624 }
1625 Token::Ident(name) => {
1626 self.advance();
1627 if *self.peek() == Token::LParen {
1632 let cast_type = match name.as_str() {
1633 "uuid" => Some(CastType::Uuid),
1634 "bytes" => Some(CastType::Bytes),
1635 _ => None,
1636 };
1637 if let Some(cast_type) = cast_type {
1638 self.advance(); let inner = self.parse_expr()?;
1640 self.expect(&Token::RParen)?;
1641 return Ok(Expr::Cast(Box::new(inner), cast_type));
1642 }
1643 }
1644 if let Token::DotIdent(field) = self.peek().clone() {
1648 self.advance();
1649 return Ok(Expr::QualifiedField {
1650 qualifier: name,
1651 field,
1652 });
1653 }
1654 Ok(Expr::Field(name))
1655 }
1656 Token::RowNumber | Token::Rank | Token::DenseRank => {
1658 let wfunc = match self.advance() {
1659 Token::RowNumber => WindowFunc::RowNumber,
1660 Token::Rank => WindowFunc::Rank,
1661 Token::DenseRank => WindowFunc::DenseRank,
1662 _ => {
1663 return Err(ParseError::Syntax {
1664 message: "unexpected window function token".into(),
1665 })
1666 }
1667 };
1668 self.expect(&Token::LParen)?;
1669 self.expect(&Token::RParen)?;
1670 let (partition_by, order_by) = self.parse_over_clause()?;
1671 Ok(Expr::Window {
1672 function: wfunc,
1673 args: vec![],
1674 mode: AggregateMode::Symmetric,
1675 partition_by,
1676 order_by,
1677 })
1678 }
1679 Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
1683 let mut func = match self.advance() {
1684 Token::Count => AggFunc::Count,
1685 Token::Avg => AggFunc::Avg,
1686 Token::Sum => AggFunc::Sum,
1687 Token::Min => AggFunc::Min,
1688 Token::Max => AggFunc::Max,
1689 _ => {
1690 return Err(ParseError::Syntax {
1691 message: "unexpected aggregate token".into(),
1692 })
1693 }
1694 };
1695 self.expect(&Token::LParen)?;
1696 let mode = if *self.peek() == Token::Raw {
1697 self.advance();
1698 AggregateMode::Raw
1699 } else {
1700 AggregateMode::Symmetric
1701 };
1702 if func == AggFunc::Count && *self.peek() == Token::Star {
1704 self.advance();
1705 self.expect(&Token::RParen)?;
1706 if *self.peek() == Token::Over {
1708 let (partition_by, order_by) = self.parse_over_clause()?;
1709 return Ok(Expr::Window {
1710 function: WindowFunc::Count,
1711 args: vec![Expr::Field("*".into())],
1712 mode,
1713 partition_by,
1714 order_by,
1715 });
1716 }
1717 return Ok(Expr::FunctionCall(
1718 AggFunc::Count,
1719 Box::new(Expr::Field("*".into())),
1720 mode,
1721 ));
1722 }
1723 if func == AggFunc::Count && *self.peek() == Token::Distinct {
1725 self.advance();
1726 func = AggFunc::CountDistinct;
1727 }
1728 let inner = self.parse_expr()?;
1729 self.expect(&Token::RParen)?;
1730 if *self.peek() == Token::Over {
1732 let wfunc = match func {
1733 AggFunc::Count => WindowFunc::Count,
1734 AggFunc::Avg => WindowFunc::Avg,
1735 AggFunc::Sum => WindowFunc::Sum,
1736 AggFunc::Min => WindowFunc::Min,
1737 AggFunc::Max => WindowFunc::Max,
1738 _ => {
1739 return Err(ParseError::Unsupported {
1740 feature: "count(distinct ...) over (...) is not supported".into(),
1741 })
1742 }
1743 };
1744 let (partition_by, order_by) = self.parse_over_clause()?;
1745 return Ok(Expr::Window {
1746 function: wfunc,
1747 args: vec![inner],
1748 mode,
1749 partition_by,
1750 order_by,
1751 });
1752 }
1753 Ok(Expr::FunctionCall(func, Box::new(inner), mode))
1754 }
1755 Token::Upper
1756 | Token::Lower
1757 | Token::Length
1758 | Token::Trim
1759 | Token::Substring
1760 | Token::Concat
1761 | Token::Abs
1762 | Token::Round
1763 | Token::Ceil
1764 | Token::Floor
1765 | Token::Sqrt
1766 | Token::Pow
1767 | Token::Now
1768 | Token::Extract
1769 | Token::DateAdd
1770 | Token::DateDiff
1771 | Token::JsonType
1772 | Token::JsonText => {
1773 let tok = self.advance();
1774 let func = token_to_scalar_fn(&tok);
1775 self.expect(&Token::LParen)?;
1776 let mut args = Vec::new();
1777 while !matches!(self.peek(), Token::RParen | Token::Eof) {
1778 args.push(self.parse_expr()?);
1779 if *self.peek() == Token::Comma {
1780 self.advance();
1781 }
1782 }
1783 self.expect(&Token::RParen)?;
1784 Ok(Expr::ScalarFunc(func, args))
1785 }
1786 Token::Cast => {
1787 self.advance();
1788 self.expect(&Token::LParen)?;
1789 let inner = self.parse_expr()?;
1790 self.expect(&Token::Comma)?;
1791 let cast_type = self.parse_cast_type()?;
1792 self.expect(&Token::RParen)?;
1793 Ok(Expr::Cast(Box::new(inner), cast_type))
1794 }
1795 Token::Case => {
1796 self.advance();
1797 let mut whens = Vec::new();
1798 while *self.peek() == Token::When {
1799 self.advance();
1800 let condition = self.parse_expr()?;
1801 self.expect(&Token::Then)?;
1802 let result = self.parse_expr()?;
1803 whens.push((Box::new(condition), Box::new(result)));
1804 }
1805 let else_expr = if *self.peek() == Token::Else {
1806 self.advance();
1807 Some(Box::new(self.parse_expr()?))
1808 } else {
1809 None
1810 };
1811 self.expect(&Token::End)?;
1812 Ok(Expr::Case { whens, else_expr })
1813 }
1814 t => Err(ParseError::Syntax {
1815 message: format!("unexpected token in expression: {}", t.display_name()),
1816 }),
1817 }
1818 }
1819
1820 fn parse_alter_table(&mut self) -> Result<Statement, ParseError> {
1823 self.expect(&Token::Alter)?;
1824 let table = match self.advance() {
1825 Token::Ident(name) => name,
1826 t => {
1827 return Err(ParseError::UnexpectedToken {
1828 expected: "table name after alter".into(),
1829 got: t.display_name(),
1830 })
1831 }
1832 };
1833 match self.peek() {
1834 Token::Add => {
1835 self.advance();
1836 if *self.peek() == Token::Index {
1838 self.advance();
1839 let if_not_exists = self.parse_optional_if_not_exists();
1840 let target = self.parse_index_target("add index")?;
1841 return Ok(Statement::AlterTable(AlterTableExpr {
1842 table,
1843 action: AlterAction::AddIndex {
1844 target,
1845 if_not_exists,
1846 },
1847 }));
1848 }
1849 if *self.peek() == Token::Unique {
1851 self.advance();
1852 let if_not_exists = self.parse_optional_if_not_exists();
1853 let target = self.parse_index_target("add unique")?;
1854 return Ok(Statement::AlterTable(AlterTableExpr {
1855 table,
1856 action: AlterAction::AddUnique {
1857 target,
1858 if_not_exists,
1859 },
1860 }));
1861 }
1862 if *self.peek() == Token::Column {
1864 self.advance();
1865 }
1866 let required = if *self.peek() == Token::Required {
1867 self.advance();
1868 true
1869 } else {
1870 false
1871 };
1872 let name = self.expect_named_ident("column name")?;
1873 self.expect(&Token::Colon)?;
1874 let type_name = match self.advance() {
1875 Token::Ident(n) => n,
1876 t => {
1877 return Err(ParseError::UnexpectedToken {
1878 expected: "type name".into(),
1879 got: t.display_name(),
1880 })
1881 }
1882 };
1883 Ok(Statement::AlterTable(AlterTableExpr {
1884 table,
1885 action: AlterAction::AddColumn {
1886 name,
1887 type_name,
1888 required,
1889 },
1890 }))
1891 }
1892 Token::Drop => {
1893 self.advance();
1894 if *self.peek() == Token::Index {
1895 self.advance();
1896 let if_exists = self.parse_optional_if_exists();
1897 let target = self.parse_index_target("drop index")?;
1898 return Ok(Statement::AlterTable(AlterTableExpr {
1899 table,
1900 action: AlterAction::DropIndex { target, if_exists },
1901 }));
1902 }
1903 if *self.peek() == Token::Column {
1905 self.advance();
1906 }
1907 let if_exists = self.parse_optional_if_exists();
1908 let name = self.expect_named_ident("column name")?;
1909 Ok(Statement::AlterTable(AlterTableExpr {
1910 table,
1911 action: AlterAction::DropColumn { name, if_exists },
1912 }))
1913 }
1914 t => Err(ParseError::UnexpectedToken {
1915 expected: "add or drop after alter <table>".into(),
1916 got: t.display_name(),
1917 }),
1918 }
1919 }
1920
1921 fn parse_index_target(&mut self, action: &str) -> Result<IndexTarget, ParseError> {
1926 match self.peek() {
1927 Token::DotIdent(_) => {
1928 if matches!(self.tokens.get(self.pos + 1), Some(Token::Arrow)) {
1929 return Err(ParseError::Syntax {
1930 message: format!(
1931 "JSON path index targets must be parenthesized after {action}; use `(.data->key)`"
1932 ),
1933 });
1934 }
1935 let Token::DotIdent(column) = self.advance() else {
1936 unreachable!("guarded by DotIdent match")
1937 };
1938 Ok(IndexTarget::Column(column))
1939 }
1940 Token::LParen => {
1941 self.advance();
1942 let expr = self.parse_expr().map_err(|error| match error {
1943 ParseError::NestingDepthExceeded { .. } => error,
1944 _ => ParseError::Syntax {
1945 message: format!(
1946 "invalid expression index target after {action}: expected an unqualified JSON path like `(.data->key)`"
1947 ),
1948 },
1949 })?;
1950 if *self.peek() != Token::RParen {
1951 return Err(ParseError::Syntax {
1952 message: format!(
1953 "invalid expression index target after {action}: only a direct JSON path is supported"
1954 ),
1955 });
1956 }
1957 self.advance();
1958
1959 match JsonPathIdentityV1::from_expr(&expr) {
1960 Some(identity) => identity.bind_table_local(None).map(IndexTarget::JsonPath).ok_or_else(|| {
1961 ParseError::Syntax {
1962 message: format!(
1963 "qualified JSON paths are not valid index targets after {action}; use an unqualified table-local path like `(.data->key)`"
1964 ),
1965 }
1966 }),
1967 None => match expr {
1968 Expr::Field(_) => Err(ParseError::Syntax {
1969 message: format!(
1970 "invalid expression index target after {action}: parentheses are reserved for a direct JSON path like `(.data->key)`; use `.column` for a stored column"
1971 ),
1972 }),
1973 Expr::QualifiedField { .. } => Err(ParseError::Syntax {
1974 message: format!(
1975 "qualified references are not valid index targets after {action}; use a table-local `.column` or `(.data->key)`"
1976 ),
1977 }),
1978 _ => Err(ParseError::Syntax {
1979 message: format!(
1980 "invalid expression index target after {action}: only a direct JSON path is supported"
1981 ),
1982 }),
1983 },
1984 }
1985 }
1986 token => Err(ParseError::UnexpectedToken {
1987 expected: format!(".<column> or parenthesized JSON path after {action}"),
1988 got: token.display_name(),
1989 }),
1990 }
1991 }
1992
1993 fn parse_drop_or_drop_view(&mut self) -> Result<Statement, ParseError> {
1995 self.expect(&Token::Drop)?;
1996 if *self.peek() == Token::View {
1997 self.advance(); let if_exists = self.parse_optional_if_exists();
1999 let name = match self.advance() {
2000 Token::Ident(name) => name,
2001 t => {
2002 return Err(ParseError::UnexpectedToken {
2003 expected: "view name after drop view".into(),
2004 got: t.display_name(),
2005 })
2006 }
2007 };
2008 return Ok(Statement::DropView(DropViewExpr { name, if_exists }));
2009 }
2010 let if_exists = self.parse_optional_if_exists();
2011 let table = match self.advance() {
2012 Token::Ident(name) => name,
2013 t => {
2014 return Err(ParseError::UnexpectedToken {
2015 expected: "table name after drop".into(),
2016 got: t.display_name(),
2017 })
2018 }
2019 };
2020 Ok(Statement::DropTable(DropTableExpr { table, if_exists }))
2021 }
2022
2023 fn parse_create_view(&mut self) -> Result<Statement, ParseError> {
2028 self.expect(&Token::Materialized)?;
2029 let name = match self.advance() {
2030 Token::Ident(name) => name,
2031 t => {
2032 return Err(ParseError::UnexpectedToken {
2033 expected: "view name after materialize".into(),
2034 got: t.display_name(),
2035 })
2036 }
2037 };
2038 self.expect(&Token::As)?;
2039 let query_start = self.pos;
2041 let source = match self.advance() {
2042 Token::Ident(s) => s,
2043 t => {
2044 return Err(ParseError::UnexpectedToken {
2045 expected: "source table name".into(),
2046 got: t.display_name(),
2047 })
2048 }
2049 };
2050 let query = self.parse_query_tail(source)?;
2051 let query_text = tokens_to_text(&self.tokens[query_start..self.pos]);
2053 Ok(Statement::CreateView(CreateViewExpr {
2054 name,
2055 query,
2056 query_text,
2057 }))
2058 }
2059
2060 fn maybe_parse_union(&mut self, left: Statement) -> Result<Statement, ParseError> {
2063 if *self.peek() != Token::Union {
2064 return Ok(left);
2065 }
2066 if !matches!(left, Statement::Query(_) | Statement::Union(_)) {
2067 return Err(ParseError::Syntax {
2068 message: "UNION requires a query on the left side".into(),
2069 });
2070 }
2071 self.advance(); let all = if let Token::Ident(s) = self.peek() {
2073 if s == "all" {
2074 self.advance();
2075 true
2076 } else {
2077 false
2078 }
2079 } else {
2080 false
2081 };
2082 let right = self.parse_single_query()?;
2084 let union = Statement::Union(UnionExpr {
2085 left: Box::new(left),
2086 right: Box::new(right),
2087 all,
2088 });
2089 self.maybe_parse_union(union)
2091 }
2092
2093 fn parse_single_query(&mut self) -> Result<Statement, ParseError> {
2095 match self.peek() {
2096 Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
2097 self.parse_aggregate_query()
2098 }
2099 Token::Ident(_) => self.parse_query_or_mutation(),
2100 _ => Err(ParseError::Syntax {
2101 message: format!(
2102 "expected query after UNION, got {}",
2103 self.peek().display_name()
2104 ),
2105 }),
2106 }
2107 }
2108
2109 fn parse_refresh_view(&mut self) -> Result<Statement, ParseError> {
2111 self.expect(&Token::Refresh)?;
2112 let name = match self.advance() {
2113 Token::Ident(name) => name,
2114 t => {
2115 return Err(ParseError::UnexpectedToken {
2116 expected: "view name after refresh".into(),
2117 got: t.display_name(),
2118 })
2119 }
2120 };
2121 Ok(Statement::RefreshView(RefreshViewExpr { name }))
2122 }
2123
2124 fn parse_create_type(&mut self) -> Result<Statement, ParseError> {
2125 self.expect(&Token::Type)?;
2126 let name = self.expect_named_ident("type name")?;
2127 let if_not_exists = self.parse_optional_if_not_exists();
2128 self.expect(&Token::LBrace)?;
2129 let mut fields = Vec::new();
2130 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
2131 let (mut required, mut unique, mut auto) = (false, false, false);
2136 loop {
2137 let is_modifier =
2138 matches!(self.peek(), Token::Required | Token::Unique | Token::Auto)
2139 && !matches!(self.tokens.get(self.pos + 1), Some(Token::Colon));
2140 if !is_modifier {
2141 break;
2142 }
2143 match self.advance() {
2144 Token::Required => required = true,
2145 Token::Unique => unique = true,
2146 Token::Auto => auto = true,
2147 _ => unreachable!("guarded by is_modifier"),
2148 }
2149 }
2150 let field_name = self.expect_named_ident("field name")?;
2151 self.expect(&Token::Colon)?;
2152 let type_name = match self.advance() {
2153 Token::Ident(n) => n,
2154 t => {
2155 return Err(ParseError::UnexpectedToken {
2156 expected: "type name".into(),
2157 got: t.display_name(),
2158 })
2159 }
2160 };
2161 let default = if *self.peek() == Token::Default {
2164 self.advance();
2165 Some(self.parse_default_literal()?)
2166 } else {
2167 None
2168 };
2169 fields.push(FieldDef {
2170 name: field_name,
2171 type_name,
2172 required,
2173 unique,
2174 default,
2175 auto,
2176 });
2177 if *self.peek() == Token::Comma {
2178 self.advance();
2179 }
2180 }
2181 self.expect(&Token::RBrace)?;
2182 Ok(Statement::CreateType(CreateTypeExpr {
2183 name,
2184 fields,
2185 if_not_exists,
2186 }))
2187 }
2188
2189 fn parse_schema(&mut self) -> Result<Statement, ParseError> {
2192 self.expect(&Token::Schema)?;
2193 if let Token::Ident(_) = self.peek() {
2194 let table = self.expect_named_ident("type name")?;
2195 return Ok(Statement::Describe(table));
2196 }
2197 Ok(Statement::ListTypes)
2198 }
2199
2200 fn parse_describe(&mut self) -> Result<Statement, ParseError> {
2202 self.expect(&Token::Describe)?;
2203 let table = self.expect_named_ident("type name")?;
2204 Ok(Statement::Describe(table))
2205 }
2206
2207 fn parse_default_literal(&mut self) -> Result<Literal, ParseError> {
2211 match self.advance() {
2212 Token::IntLit(v) => Ok(Literal::Int(v)),
2213 Token::FloatLit(v) => Ok(Literal::Float(v)),
2214 Token::StringLit(v) => Ok(Literal::String(v)),
2215 Token::BoolLit(v) => Ok(Literal::Bool(v)),
2216 t => Err(ParseError::UnexpectedToken {
2217 expected: "literal default value".into(),
2218 got: t.display_name(),
2219 }),
2220 }
2221 }
2222}
2223
2224fn tokens_to_text(tokens: &[Token]) -> String {
2228 let mut out = String::with_capacity(64);
2229 for tok in tokens {
2230 if !out.is_empty() && !matches!(tok, Token::Eof) {
2231 out.push(' ');
2232 }
2233 match tok {
2234 Token::Ident(s) => out.push_str(s),
2235 Token::DotIdent(s) => {
2236 out.push('.');
2237 out.push_str(s);
2238 }
2239 Token::IntLit(v) => out.push_str(&v.to_string()),
2240 Token::FloatLit(v) => out.push_str(&v.to_string()),
2241 Token::StringLit(s) => {
2242 out.push('"');
2243 out.push_str(s);
2244 out.push('"');
2245 }
2246 Token::BoolLit(v) => out.push_str(if *v { "true" } else { "false" }),
2247 Token::Param(s) => {
2248 out.push('$');
2249 out.push_str(s);
2250 }
2251 Token::Type => out.push_str("type"),
2252 Token::Filter => out.push_str("filter"),
2253 Token::Order => out.push_str("order"),
2254 Token::Limit => out.push_str("limit"),
2255 Token::Offset => out.push_str("offset"),
2256 Token::Insert => out.push_str("insert"),
2257 Token::Update => out.push_str("update"),
2258 Token::Delete => out.push_str("delete"),
2259 Token::Upsert => out.push_str("upsert"),
2260 Token::Returning => out.push_str("returning"),
2261 Token::Conflict => out.push_str("conflict"),
2262 Token::Select => out.push_str("select"),
2263 Token::Required => out.push_str("required"),
2264 Token::Default => out.push_str("default"),
2265 Token::Auto => out.push_str("auto"),
2266 Token::Multi => out.push_str("multi"),
2267 Token::Link => out.push_str("link"),
2268 Token::Index => out.push_str("index"),
2269 Token::Unique => out.push_str("unique"),
2270 Token::On => out.push_str("on"),
2271 Token::Asc => out.push_str("asc"),
2272 Token::Desc => out.push_str("desc"),
2273 Token::And => out.push_str("and"),
2274 Token::Or => out.push_str("or"),
2275 Token::Not => out.push_str("not"),
2276 Token::Exists => out.push_str("exists"),
2277 Token::Let => out.push_str("let"),
2278 Token::As => out.push_str("as"),
2279 Token::Match => out.push_str("match"),
2280 Token::Group => out.push_str("group"),
2281 Token::Join => out.push_str("join"),
2282 Token::Inner => out.push_str("inner"),
2283 Token::LeftKw => out.push_str("left"),
2284 Token::RightKw => out.push_str("right"),
2285 Token::Outer => out.push_str("outer"),
2286 Token::Cross => out.push_str("cross"),
2287 Token::Transaction => out.push_str("transaction"),
2288 Token::Begin => out.push_str("begin"),
2289 Token::Commit => out.push_str("commit"),
2290 Token::Rollback => out.push_str("rollback"),
2291 Token::View => out.push_str("view"),
2292 Token::Materialized => out.push_str("materialized"),
2293 Token::Refresh => out.push_str("refresh"),
2294 Token::Union => out.push_str("union"),
2295 Token::Having => out.push_str("having"),
2296 Token::Distinct => out.push_str("distinct"),
2297 Token::In => out.push_str("in"),
2298 Token::Between => out.push_str("between"),
2299 Token::Like => out.push_str("like"),
2300 Token::Count => out.push_str("count"),
2301 Token::Avg => out.push_str("avg"),
2302 Token::Sum => out.push_str("sum"),
2303 Token::Raw => out.push_str("raw"),
2304 Token::Min => out.push_str("min"),
2305 Token::Max => out.push_str("max"),
2306 Token::Is => out.push_str("is"),
2307 Token::Null => out.push_str("null"),
2308 Token::Upper => out.push_str("upper"),
2309 Token::Lower => out.push_str("lower"),
2310 Token::Length => out.push_str("length"),
2311 Token::Trim => out.push_str("trim"),
2312 Token::Substring => out.push_str("substring"),
2313 Token::Concat => out.push_str("concat"),
2314 Token::Abs => out.push_str("abs"),
2315 Token::Round => out.push_str("round"),
2316 Token::Ceil => out.push_str("ceil"),
2317 Token::Floor => out.push_str("floor"),
2318 Token::Sqrt => out.push_str("sqrt"),
2319 Token::Pow => out.push_str("pow"),
2320 Token::Now => out.push_str("now"),
2321 Token::Extract => out.push_str("extract"),
2322 Token::DateAdd => out.push_str("date_add"),
2323 Token::DateDiff => out.push_str("date_diff"),
2324 Token::JsonType => out.push_str("json_type"),
2325 Token::JsonText => out.push_str("json_text"),
2326 Token::Cast => out.push_str("cast"),
2327 Token::Case => out.push_str("case"),
2328 Token::When => out.push_str("when"),
2329 Token::Then => out.push_str("then"),
2330 Token::Else => out.push_str("else"),
2331 Token::End => out.push_str("end"),
2332 Token::Over => out.push_str("over"),
2333 Token::Partition => out.push_str("partition"),
2334 Token::RowNumber => out.push_str("row_number"),
2335 Token::Rank => out.push_str("rank"),
2336 Token::DenseRank => out.push_str("dense_rank"),
2337 Token::Alter => out.push_str("alter"),
2338 Token::Drop => out.push_str("drop"),
2339 Token::Add => out.push_str("add"),
2340 Token::Column => out.push_str("column"),
2341 Token::Eq => out.push('='),
2342 Token::Neq => out.push_str("!="),
2343 Token::Lt => out.push('<'),
2344 Token::Gt => out.push('>'),
2345 Token::Lte => out.push_str("<="),
2346 Token::Gte => out.push_str(">="),
2347 Token::Assign => out.push_str(":="),
2348 Token::Arrow => out.push_str("->"),
2349 Token::Pipe => out.push('|'),
2350 Token::Coalesce => out.push_str("??"),
2351 Token::Plus => out.push('+'),
2352 Token::Minus => out.push('-'),
2353 Token::Star => out.push('*'),
2354 Token::Slash => out.push('/'),
2355 Token::LBrace => out.push('{'),
2356 Token::RBrace => out.push('}'),
2357 Token::LParen => out.push('('),
2358 Token::RParen => out.push(')'),
2359 Token::Comma => out.push(','),
2360 Token::Colon => out.push(':'),
2361 Token::Dot => out.push('.'),
2362 Token::Explain => out.push_str("explain"),
2363 Token::Schema => out.push_str("schema"),
2364 Token::Describe => out.push_str("describe"),
2365 Token::Eof => {}
2366 }
2367 }
2368 out
2369}
2370
2371#[cfg(test)]
2372mod tests {
2373 use super::*;
2374 #[test]
2375 fn test_parse_simple_query() {
2376 let stmt = parse("User").unwrap();
2377 match stmt {
2378 Statement::Query(q) => {
2379 assert_eq!(q.source, "User");
2380 assert!(q.filter.is_none());
2381 assert!(q.projection.is_none());
2382 }
2383 _ => panic!("expected query"),
2384 }
2385 }
2386
2387 #[test]
2388 fn test_parse_filter() {
2389 let stmt = parse("User filter .age > 30").unwrap();
2390 match stmt {
2391 Statement::Query(q) => {
2392 assert_eq!(q.source, "User");
2393 assert!(q.filter.is_some());
2394 }
2395 _ => panic!("expected query"),
2396 }
2397 }
2398
2399 #[test]
2400 fn test_parse_projection() {
2401 let stmt = parse("User { name, email }").unwrap();
2402 match stmt {
2403 Statement::Query(q) => {
2404 let proj = q.projection.unwrap();
2405 assert_eq!(proj.len(), 2);
2406 }
2407 _ => panic!("expected query"),
2408 }
2409 }
2410
2411 #[test]
2412 fn test_parse_filter_order_limit() {
2413 let stmt = parse("User filter .age > 30 order .name desc limit 10").unwrap();
2414 match stmt {
2415 Statement::Query(q) => {
2416 assert!(q.filter.is_some());
2417 let order = q.order.unwrap();
2418 assert_eq!(order.keys.len(), 1);
2419 assert_eq!(order.keys[0].expr, Expr::Field("name".into()));
2420 assert!(order.keys[0].descending);
2421 assert!(q.limit.is_some());
2422 }
2423 _ => panic!("expected query"),
2424 }
2425 }
2426
2427 #[test]
2428 fn test_parse_insert() {
2429 let stmt = parse(r#"insert User { name := "Alice", age := 30 }"#).unwrap();
2430 match stmt {
2431 Statement::Insert(ins) => {
2432 assert_eq!(ins.target, "User");
2433 assert_eq!(ins.rows.len(), 1);
2434 assert_eq!(ins.rows[0].len(), 2);
2435 assert_eq!(ins.rows[0][0].field, "name");
2436 assert_eq!(ins.rows[0][1].field, "age");
2437 }
2438 _ => panic!("expected insert"),
2439 }
2440 }
2441
2442 #[test]
2443 fn test_parse_insert_multi_row() {
2444 let stmt =
2445 parse(r#"insert User { name := "Alice", age := 30 }, { name := "Bob", age := 25 }, { name := "Cy" }"#)
2446 .unwrap();
2447 match stmt {
2448 Statement::Insert(ins) => {
2449 assert_eq!(ins.target, "User");
2450 assert_eq!(ins.rows.len(), 3);
2451 assert_eq!(ins.rows[0].len(), 2);
2452 assert_eq!(ins.rows[1][0].field, "name");
2453 assert_eq!(ins.rows[2].len(), 1);
2454 assert_eq!(ins.rows[2][0].field, "name");
2455 }
2456 _ => panic!("expected insert"),
2457 }
2458 }
2459
2460 #[test]
2461 fn test_parse_update() {
2462 let stmt = parse(r#"User filter .email = "alice@ex.com" update { age := 31 }"#).unwrap();
2463 match stmt {
2464 Statement::UpdateQuery(upd) => {
2465 assert_eq!(upd.source, "User");
2466 assert!(upd.filter.is_some());
2467 assert_eq!(upd.assignments.len(), 1);
2468 assert!(!upd.returning);
2469 }
2470 _ => panic!("expected update"),
2471 }
2472 }
2473
2474 #[test]
2475 fn test_parse_update_returning() {
2476 let stmt = parse(r#"User filter .name = "Alice" update { age := 31 } returning"#).unwrap();
2477 match stmt {
2478 Statement::UpdateQuery(upd) => assert!(upd.returning),
2479 _ => panic!("expected update"),
2480 }
2481 }
2482
2483 #[test]
2484 fn test_parse_delete() {
2485 let stmt = parse("User filter .age < 18 delete").unwrap();
2486 match stmt {
2487 Statement::DeleteQuery(del) => {
2488 assert_eq!(del.source, "User");
2489 assert!(del.filter.is_some());
2490 assert!(!del.returning);
2491 }
2492 _ => panic!("expected delete"),
2493 }
2494 }
2495
2496 #[test]
2497 fn test_parse_delete_returning() {
2498 let stmt = parse("User filter .age < 18 delete returning").unwrap();
2499 match stmt {
2500 Statement::DeleteQuery(del) => assert!(del.returning),
2501 _ => panic!("expected delete"),
2502 }
2503 }
2504
2505 #[test]
2506 fn test_parse_count() {
2507 let stmt = parse("count(User)").unwrap();
2508 match stmt {
2509 Statement::Query(q) => {
2510 let agg = q.aggregation.unwrap();
2511 assert_eq!(agg.function, AggFunc::Count);
2512 assert!(q.filter.is_none());
2513 }
2514 _ => panic!("expected query with aggregation"),
2515 }
2516 }
2517
2518 #[test]
2519 fn test_parse_count_with_filter() {
2520 let stmt = parse("count(User filter .age > 30)").unwrap();
2523 match stmt {
2524 Statement::Query(q) => {
2525 assert_eq!(q.source, "User");
2526 let agg = q.aggregation.unwrap();
2527 assert_eq!(agg.function, AggFunc::Count);
2528 assert!(q.filter.is_some(), "filter should have been parsed");
2529 }
2530 _ => panic!("expected query with aggregation"),
2531 }
2532 }
2533
2534 #[test]
2535 fn test_parse_count_with_filter_and_limit() {
2536 let stmt = parse("count(User filter .age > 30 limit 100)").unwrap();
2537 match stmt {
2538 Statement::Query(q) => {
2539 assert_eq!(q.source, "User");
2540 assert!(q.filter.is_some());
2541 assert!(q.limit.is_some());
2542 assert_eq!(q.aggregation.unwrap().function, AggFunc::Count);
2543 }
2544 _ => panic!("expected query with aggregation"),
2545 }
2546 }
2547
2548 #[test]
2549 fn test_parse_create_type() {
2550 let stmt = parse("type User { required name: str, age: int }").unwrap();
2551 match stmt {
2552 Statement::CreateType(ct) => {
2553 assert_eq!(ct.name, "User");
2554 assert_eq!(ct.fields.len(), 2);
2555 assert!(ct.fields[0].required);
2556 assert!(!ct.fields[1].required);
2557 }
2558 _ => panic!("expected create type"),
2559 }
2560 }
2561
2562 #[test]
2563 fn test_parse_sum_with_field_projection() {
2564 let stmt = parse("sum(User filter .age > 30 { .age })").unwrap();
2567 match stmt {
2568 Statement::Query(q) => {
2569 let agg = q.aggregation.expect("aggregate");
2570 assert_eq!(agg.function, AggFunc::Sum);
2571 assert_eq!(agg.argument, Some(Expr::Field("age".into())));
2572 assert!(
2573 q.projection.is_none(),
2574 "projection should be lifted into agg.field"
2575 );
2576 }
2577 _ => panic!("expected query"),
2578 }
2579 }
2580
2581 #[test]
2582 fn test_parse_raw_aggregate_modes() {
2583 let Statement::Query(top_level) = parse("sum(raw User { .age })").unwrap() else {
2584 panic!("expected query");
2585 };
2586 assert_eq!(top_level.aggregation.unwrap().mode, AggregateMode::Raw);
2587
2588 let Statement::Query(grouped) = parse("User group .dept { total: sum(raw .age) }").unwrap()
2589 else {
2590 panic!("expected query");
2591 };
2592 assert!(matches!(
2593 grouped.projection.unwrap()[0].expr,
2594 Expr::FunctionCall(AggFunc::Sum, _, AggregateMode::Raw)
2595 ));
2596 }
2597
2598 #[test]
2599 fn test_parse_avg_min_max_with_field() {
2600 for (src, expected) in [
2601 ("avg(User { .age })", AggFunc::Avg),
2602 ("min(User { .age })", AggFunc::Min),
2603 ("max(User { .age })", AggFunc::Max),
2604 ] {
2605 let stmt = parse(src).unwrap();
2606 match stmt {
2607 Statement::Query(q) => {
2608 let agg = q.aggregation.unwrap();
2609 assert_eq!(agg.function, expected, "func mismatch for {src}");
2610 assert_eq!(
2611 agg.argument,
2612 Some(Expr::Field("age".into())),
2613 "field mismatch for {src}"
2614 );
2615 assert!(
2616 q.projection.is_none(),
2617 "projection should be cleared for {src}"
2618 );
2619 }
2620 _ => panic!("expected query for {src}"),
2621 }
2622 }
2623 }
2624
2625 #[test]
2626 fn test_parse_count_leaves_projection_alone() {
2627 let stmt = parse("count(User { .age })").unwrap();
2630 match stmt {
2631 Statement::Query(q) => {
2632 let agg = q.aggregation.unwrap();
2633 assert_eq!(agg.function, AggFunc::Count);
2634 assert!(agg.argument.is_none());
2635 assert!(q.projection.is_some(), "count must not eat projection");
2636 }
2637 _ => panic!("expected query"),
2638 }
2639 }
2640
2641 #[test]
2646 fn test_parse_source_alias() {
2647 let stmt = parse("User as u filter u.age > 30").unwrap();
2648 match stmt {
2649 Statement::Query(q) => {
2650 assert_eq!(q.source, "User");
2651 assert_eq!(q.alias.as_deref(), Some("u"));
2652 assert!(q.joins.is_empty());
2653 match q.filter.unwrap() {
2654 Expr::BinaryOp(l, BinOp::Gt, _) => match *l {
2655 Expr::QualifiedField { qualifier, field } => {
2656 assert_eq!(qualifier, "u");
2657 assert_eq!(field, "age");
2658 }
2659 other => panic!("expected qualified field, got {other:?}"),
2660 },
2661 other => panic!("expected >, got {other:?}"),
2662 }
2663 }
2664 _ => panic!("expected query"),
2665 }
2666 }
2667
2668 #[test]
2669 fn test_parse_inner_join_on() {
2670 let stmt = parse("User as u inner join Order as o on u.id = o.user_id").unwrap();
2671 match stmt {
2672 Statement::Query(q) => {
2673 assert_eq!(q.source, "User");
2674 assert_eq!(q.alias.as_deref(), Some("u"));
2675 assert_eq!(q.joins.len(), 1);
2676 let j = &q.joins[0];
2677 assert_eq!(j.kind, JoinKind::Inner);
2678 assert_eq!(j.source, "Order");
2679 assert_eq!(j.alias.as_deref(), Some("o"));
2680 let on = j.on.as_ref().expect("on clause");
2681 match on {
2682 Expr::BinaryOp(l, BinOp::Eq, r) => {
2683 assert!(matches!(**l, Expr::QualifiedField { .. }));
2684 assert!(matches!(**r, Expr::QualifiedField { .. }));
2685 }
2686 other => panic!("expected eq, got {other:?}"),
2687 }
2688 }
2689 _ => panic!("expected query"),
2690 }
2691 }
2692
2693 #[test]
2694 fn test_parse_bare_join_defaults_to_inner() {
2695 let stmt = parse("User join Order on User.id = Order.user_id").unwrap();
2696 match stmt {
2697 Statement::Query(q) => {
2698 assert_eq!(q.joins.len(), 1);
2699 assert_eq!(q.joins[0].kind, JoinKind::Inner);
2700 }
2701 _ => panic!("expected query"),
2702 }
2703 }
2704
2705 #[test]
2706 fn test_parse_left_outer_join() {
2707 let stmt = parse("User as u left outer join Order as o on u.id = o.user_id").unwrap();
2708 match stmt {
2709 Statement::Query(q) => {
2710 assert_eq!(q.joins.len(), 1);
2711 assert_eq!(q.joins[0].kind, JoinKind::LeftOuter);
2712 }
2713 _ => panic!("expected query"),
2714 }
2715 }
2716
2717 #[test]
2718 fn test_parse_left_join_without_outer_keyword() {
2719 let stmt = parse("User as u left join Order as o on u.id = o.user_id").unwrap();
2721 match stmt {
2722 Statement::Query(q) => {
2723 assert_eq!(q.joins[0].kind, JoinKind::LeftOuter);
2724 }
2725 _ => panic!("expected query"),
2726 }
2727 }
2728
2729 #[test]
2730 fn test_parse_right_join() {
2731 let stmt = parse("User as u right join Order as o on u.id = o.user_id").unwrap();
2732 match stmt {
2733 Statement::Query(q) => {
2734 assert_eq!(q.joins[0].kind, JoinKind::RightOuter);
2735 }
2736 _ => panic!("expected query"),
2737 }
2738 }
2739
2740 #[test]
2741 fn test_parse_cross_join_has_no_on() {
2742 let stmt = parse("User cross join Order").unwrap();
2743 match stmt {
2744 Statement::Query(q) => {
2745 assert_eq!(q.joins[0].kind, JoinKind::Cross);
2746 assert!(q.joins[0].on.is_none());
2747 }
2748 _ => panic!("expected query"),
2749 }
2750 }
2751
2752 #[test]
2753 fn test_parse_multi_join_chain() {
2754 let stmt = parse(
2755 "User as u join Order as o on u.id = o.user_id \
2756 join Product as p on o.product_id = p.id",
2757 )
2758 .unwrap();
2759 match stmt {
2760 Statement::Query(q) => {
2761 assert_eq!(q.joins.len(), 2);
2762 assert_eq!(q.joins[0].source, "Order");
2763 assert_eq!(q.joins[1].source, "Product");
2764 }
2765 _ => panic!("expected query"),
2766 }
2767 }
2768
2769 #[test]
2770 fn test_parse_join_with_filter_tail() {
2771 let stmt = parse(
2773 "User as u join Order as o on u.id = o.user_id \
2774 filter o.total > 100 order .name limit 10",
2775 )
2776 .unwrap();
2777 match stmt {
2778 Statement::Query(q) => {
2779 assert_eq!(q.joins.len(), 1);
2780 assert!(q.filter.is_some());
2781 assert!(q.order.is_some());
2782 assert!(q.limit.is_some());
2783 }
2784 _ => panic!("expected query"),
2785 }
2786 }
2787
2788 #[test]
2789 fn test_parse_join_requires_on_for_inner() {
2790 let err = parse("User join Order").unwrap_err();
2792 assert!(
2793 err.message().contains("on"),
2794 "expected on-clause error, got {:?}",
2795 err.message()
2796 );
2797 }
2798
2799 #[test]
2800 fn test_parse_update_on_joined_query_errors() {
2801 let err =
2804 parse("User as u join Order as o on u.id = o.user_id update { age := 1 }").unwrap_err();
2805 assert!(err.message().contains("update"));
2806 }
2807
2808 #[test]
2809 fn test_parse_delete_on_joined_query_errors() {
2810 let err = parse("User as u join Order as o on u.id = o.user_id delete").unwrap_err();
2811 assert!(err.message().contains("delete"));
2812 }
2813
2814 #[test]
2817 fn test_parse_distinct() {
2818 let stmt = parse("User distinct { .name }").unwrap();
2819 match stmt {
2820 Statement::Query(q) => {
2821 assert!(q.distinct);
2822 assert!(q.projection.is_some());
2823 }
2824 _ => panic!("expected query"),
2825 }
2826 }
2827
2828 #[test]
2829 fn test_parse_in_list() {
2830 let stmt = parse(r#"User filter .name in ("Alice", "Bob")"#).unwrap();
2831 match stmt {
2832 Statement::Query(q) => match q.filter.unwrap() {
2833 Expr::InList {
2834 expr,
2835 list,
2836 negated,
2837 } => {
2838 assert!(!negated);
2839 assert!(matches!(*expr, Expr::Field(f) if f == "name"));
2840 assert_eq!(list.len(), 2);
2841 }
2842 other => panic!("expected InList, got {other:?}"),
2843 },
2844 _ => panic!("expected query"),
2845 }
2846 }
2847
2848 #[test]
2849 fn test_parse_not_in_list() {
2850 let stmt = parse("User filter .age not in (1, 2, 3)").unwrap();
2851 match stmt {
2852 Statement::Query(q) => match q.filter.unwrap() {
2853 Expr::InList { negated, list, .. } => {
2854 assert!(negated);
2855 assert_eq!(list.len(), 3);
2856 }
2857 other => panic!("expected InList, got {other:?}"),
2858 },
2859 _ => panic!("expected query"),
2860 }
2861 }
2862
2863 #[test]
2864 fn test_parse_between() {
2865 let stmt = parse("User filter .age between 10 and 20").unwrap();
2867 match stmt {
2868 Statement::Query(q) => {
2869 match q.filter.unwrap() {
2870 Expr::BinaryOp(_, BinOp::And, _) => {} other => panic!("expected And (desugared between), got {other:?}"),
2872 }
2873 }
2874 _ => panic!("expected query"),
2875 }
2876 }
2877
2878 #[test]
2879 fn test_parse_not_between() {
2880 let stmt = parse("User filter .age not between 10 and 20").unwrap();
2882 match stmt {
2883 Statement::Query(q) => {
2884 match q.filter.unwrap() {
2885 Expr::BinaryOp(_, BinOp::Or, _) => {} other => panic!("expected Or (desugared not between), got {other:?}"),
2887 }
2888 }
2889 _ => panic!("expected query"),
2890 }
2891 }
2892
2893 #[test]
2894 fn test_parse_like() {
2895 let stmt = parse(r#"User filter .name like "A%""#).unwrap();
2896 match stmt {
2897 Statement::Query(q) => match q.filter.unwrap() {
2898 Expr::BinaryOp(l, BinOp::Like, r) => {
2899 assert!(matches!(*l, Expr::Field(f) if f == "name"));
2900 assert!(matches!(*r, Expr::Literal(Literal::String(s)) if s == "A%"));
2901 }
2902 other => panic!("expected Like, got {other:?}"),
2903 },
2904 _ => panic!("expected query"),
2905 }
2906 }
2907
2908 #[test]
2909 fn test_parse_not_like() {
2910 let stmt = parse(r#"User filter .name not like "A%""#).unwrap();
2911 match stmt {
2912 Statement::Query(q) => match q.filter.unwrap() {
2913 Expr::UnaryOp(UnaryOp::Not, inner) => {
2914 assert!(matches!(*inner, Expr::BinaryOp(_, BinOp::Like, _)));
2915 }
2916 other => panic!("expected Not(Like), got {other:?}"),
2917 },
2918 _ => panic!("expected query"),
2919 }
2920 }
2921
2922 #[test]
2925 fn test_parse_group_by_single_key() {
2926 let stmt = parse("User group .status { .status, n: count(.name) }").unwrap();
2927 match stmt {
2928 Statement::Query(q) => {
2929 let gb = q.group_by.unwrap();
2930 assert_eq!(
2931 gb.keys,
2932 vec![GroupKey {
2933 expr: Expr::Field("status".into()),
2934 output_name: "status".into(),
2935 }]
2936 );
2937 assert!(gb.having.is_none());
2938 let proj = q.projection.unwrap();
2939 assert_eq!(proj.len(), 2);
2940 assert!(matches!(
2941 &proj[1].expr,
2942 Expr::FunctionCall(AggFunc::Count, _, _)
2943 ));
2944 assert_eq!(proj[1].alias.as_deref(), Some("n"));
2945 }
2946 _ => panic!("expected query"),
2947 }
2948 }
2949
2950 #[test]
2951 fn test_parse_group_by_multi_key() {
2952 let stmt = parse("User group .status, .age { .status, .age }").unwrap();
2953 match stmt {
2954 Statement::Query(q) => {
2955 let gb = q.group_by.unwrap();
2956 assert_eq!(
2957 gb.keys,
2958 vec![
2959 GroupKey {
2960 expr: Expr::Field("status".into()),
2961 output_name: "status".into(),
2962 },
2963 GroupKey {
2964 expr: Expr::Field("age".into()),
2965 output_name: "age".into(),
2966 }
2967 ]
2968 );
2969 }
2970 _ => panic!("expected query"),
2971 }
2972 }
2973
2974 #[test]
2975 fn test_parse_group_by_having() {
2976 let stmt = parse("User group .status having count(.name) > 1 { .status }").unwrap();
2977 match stmt {
2978 Statement::Query(q) => {
2979 let gb = q.group_by.unwrap();
2980 assert_eq!(
2981 gb.keys,
2982 vec![GroupKey {
2983 expr: Expr::Field("status".into()),
2984 output_name: "status".into(),
2985 }]
2986 );
2987 assert!(gb.having.is_some());
2988 match gb.having.unwrap() {
2990 Expr::BinaryOp(l, BinOp::Gt, _) => {
2991 assert!(matches!(*l, Expr::FunctionCall(AggFunc::Count, _, _)));
2992 }
2993 other => panic!("expected BinaryOp, got {other:?}"),
2994 }
2995 }
2996 _ => panic!("expected query"),
2997 }
2998 }
2999
3000 #[test]
3001 fn test_parse_aggregate_in_projection() {
3002 let stmt = parse("User group .status { .status, count(.name), sum(.age) }").unwrap();
3004 match stmt {
3005 Statement::Query(q) => {
3006 let proj = q.projection.unwrap();
3007 assert_eq!(proj.len(), 3);
3008 assert!(matches!(
3009 &proj[1].expr,
3010 Expr::FunctionCall(AggFunc::Count, _, _)
3011 ));
3012 assert!(matches!(
3013 &proj[2].expr,
3014 Expr::FunctionCall(AggFunc::Sum, _, _)
3015 ));
3016 }
3017 _ => panic!("expected query"),
3018 }
3019 }
3020
3021 #[test]
3022 fn test_parse_aggregate_in_aliased_projection() {
3023 let stmt = parse("User group .status { .status, total: count(.name), average: avg(.age) }")
3024 .unwrap();
3025 match stmt {
3026 Statement::Query(q) => {
3027 let proj = q.projection.unwrap();
3028 assert_eq!(proj[1].alias.as_deref(), Some("total"));
3029 assert!(matches!(
3030 &proj[1].expr,
3031 Expr::FunctionCall(AggFunc::Count, _, _)
3032 ));
3033 assert_eq!(proj[2].alias.as_deref(), Some("average"));
3034 assert!(matches!(
3035 &proj[2].expr,
3036 Expr::FunctionCall(AggFunc::Avg, _, _)
3037 ));
3038 }
3039 _ => panic!("expected query"),
3040 }
3041 }
3042
3043 #[test]
3046 fn test_parse_is_null() {
3047 let stmt = parse("User filter .age is null").unwrap();
3048 match stmt {
3049 Statement::Query(q) => {
3050 let filter = q.filter.unwrap();
3051 assert_eq!(
3052 filter,
3053 Expr::UnaryOp(UnaryOp::IsNull, Box::new(Expr::Field("age".into())))
3054 );
3055 }
3056 _ => panic!("expected query"),
3057 }
3058 }
3059
3060 #[test]
3061 fn test_parse_is_not_null() {
3062 let stmt = parse("User filter .age is not null").unwrap();
3063 match stmt {
3064 Statement::Query(q) => {
3065 let filter = q.filter.unwrap();
3066 assert_eq!(
3067 filter,
3068 Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(Expr::Field("age".into())))
3069 );
3070 }
3071 _ => panic!("expected query"),
3072 }
3073 }
3074
3075 #[test]
3076 fn test_parse_eq_null_desugars_to_is_null() {
3077 let stmt = parse("User filter .age = null").unwrap();
3078 match stmt {
3079 Statement::Query(q) => {
3080 let filter = q.filter.unwrap();
3081 assert_eq!(
3082 filter,
3083 Expr::UnaryOp(UnaryOp::IsNull, Box::new(Expr::Field("age".into())))
3084 );
3085 }
3086 _ => panic!("expected query"),
3087 }
3088 }
3089
3090 #[test]
3091 fn test_parse_neq_null_desugars_to_is_not_null() {
3092 let stmt = parse("User filter .age != null").unwrap();
3093 match stmt {
3094 Statement::Query(q) => {
3095 let filter = q.filter.unwrap();
3096 assert_eq!(
3097 filter,
3098 Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(Expr::Field("age".into())))
3099 );
3100 }
3101 _ => panic!("expected query"),
3102 }
3103 }
3104
3105 #[test]
3106 fn test_parse_null_comparisons_parse_ok() {
3107 assert!(parse("User filter .age < null").is_ok());
3111 assert!(parse("User filter .age >= null").is_ok());
3112 }
3113
3114 #[test]
3115 fn test_parse_count_star_expr() {
3116 let stmt = parse("User filter count(*) > 0").unwrap();
3117 match stmt {
3118 Statement::Query(q) => {
3119 let filter = q.filter.unwrap();
3120 match filter {
3121 Expr::BinaryOp(left, BinOp::Gt, _) => {
3122 assert_eq!(
3123 *left,
3124 Expr::FunctionCall(
3125 AggFunc::Count,
3126 Box::new(Expr::Field("*".into())),
3127 AggregateMode::Symmetric,
3128 )
3129 );
3130 }
3131 _ => panic!("expected comparison"),
3132 }
3133 }
3134 _ => panic!("expected query"),
3135 }
3136 }
3137
3138 #[test]
3141 fn test_parse_upper_in_filter() {
3142 let stmt = parse(r#"User filter upper(.name) = "ALICE""#).unwrap();
3143 match stmt {
3144 Statement::Query(q) => {
3145 let f = q.filter.unwrap();
3146 match f {
3147 Expr::BinaryOp(left, BinOp::Eq, _right) => {
3148 assert!(matches!(*left, Expr::ScalarFunc(ScalarFn::Upper, _)));
3149 }
3150 _ => panic!("expected binary op with upper"),
3151 }
3152 }
3153 _ => panic!("expected query"),
3154 }
3155 }
3156
3157 #[test]
3158 fn test_parse_substring() {
3159 let stmt = parse("User { sub: substring(.name, 1, 3) }").unwrap();
3160 match stmt {
3161 Statement::Query(q) => {
3162 let proj = q.projection.unwrap();
3163 match &proj[0].expr {
3164 Expr::ScalarFunc(ScalarFn::Substring, args) => {
3165 assert_eq!(args.len(), 3);
3166 }
3167 other => panic!("expected ScalarFunc Substring, got {other:?}"),
3168 }
3169 }
3170 _ => panic!("expected query"),
3171 }
3172 }
3173
3174 #[test]
3175 fn test_parse_concat() {
3176 let stmt = parse(r#"User { full: concat(.name, " - ", .email) }"#).unwrap();
3177 match stmt {
3178 Statement::Query(q) => {
3179 let proj = q.projection.unwrap();
3180 match &proj[0].expr {
3181 Expr::ScalarFunc(ScalarFn::Concat, args) => {
3182 assert_eq!(args.len(), 3);
3183 }
3184 other => panic!("expected ScalarFunc Concat, got {other:?}"),
3185 }
3186 }
3187 _ => panic!("expected query"),
3188 }
3189 }
3190
3191 #[test]
3194 fn test_parse_case_single_when() {
3195 let stmt = parse(r#"User filter case when .age > 30 then true else false end"#).unwrap();
3196 match stmt {
3197 Statement::Query(q) => {
3198 let filter = q.filter.unwrap();
3199 match filter {
3200 Expr::Case { whens, else_expr } => {
3201 assert_eq!(whens.len(), 1);
3202 assert!(else_expr.is_some());
3203 }
3204 other => panic!("expected Case expr, got {other:?}"),
3205 }
3206 }
3207 _ => panic!("expected query"),
3208 }
3209 }
3210
3211 #[test]
3212 fn test_parse_case_multiple_whens() {
3213 let stmt = parse(
3214 r#"User { label: case when .age > 30 then "senior" when .age > 20 then "adult" else "young" end }"#
3215 ).unwrap();
3216 match stmt {
3217 Statement::Query(q) => {
3218 let proj = q.projection.unwrap();
3219 match &proj[0].expr {
3220 Expr::Case { whens, else_expr } => {
3221 assert_eq!(whens.len(), 2);
3222 assert!(else_expr.is_some());
3223 }
3224 other => panic!("expected Case expr, got {other:?}"),
3225 }
3226 }
3227 _ => panic!("expected query"),
3228 }
3229 }
3230
3231 #[test]
3232 fn test_parse_case_without_else() {
3233 let stmt = parse(r#"User filter case when .age > 30 then true end"#).unwrap();
3234 match stmt {
3235 Statement::Query(q) => {
3236 let filter = q.filter.unwrap();
3237 match filter {
3238 Expr::Case { whens, else_expr } => {
3239 assert_eq!(whens.len(), 1);
3240 assert!(else_expr.is_none());
3241 }
3242 other => panic!("expected Case expr, got {other:?}"),
3243 }
3244 }
3245 _ => panic!("expected query"),
3246 }
3247 }
3248
3249 #[test]
3252 fn test_parse_mul_expr() {
3253 let stmt = parse("User filter .price * .quantity > 100").unwrap();
3254 match stmt {
3255 Statement::Query(q) => {
3256 let filter = q.filter.unwrap();
3257 match filter {
3258 Expr::BinaryOp(left, BinOp::Gt, _) => match *left {
3259 Expr::BinaryOp(_, BinOp::Mul, _) => {}
3260 other => panic!("expected Mul, got {other:?}"),
3261 },
3262 other => panic!("expected BinaryOp Gt, got {other:?}"),
3263 }
3264 }
3265 _ => panic!("expected query"),
3266 }
3267 }
3268
3269 #[test]
3270 fn test_parse_div_expr() {
3271 let stmt = parse("User { ratio: .total / .count }").unwrap();
3272 match stmt {
3273 Statement::Query(q) => {
3274 let proj = q.projection.unwrap();
3275 assert_eq!(proj[0].alias.as_deref(), Some("ratio"));
3276 match &proj[0].expr {
3277 Expr::BinaryOp(_, BinOp::Div, _) => {}
3278 other => panic!("expected Div, got {other:?}"),
3279 }
3280 }
3281 _ => panic!("expected query"),
3282 }
3283 }
3284
3285 #[test]
3286 fn test_parse_mul_div_precedence() {
3287 let stmt = parse("User filter .a + .b * .c > 0").unwrap();
3289 match stmt {
3290 Statement::Query(q) => {
3291 let filter = q.filter.unwrap();
3292 match filter {
3293 Expr::BinaryOp(left, BinOp::Gt, _) => match *left {
3294 Expr::BinaryOp(_, BinOp::Add, right) => {
3295 assert!(matches!(*right, Expr::BinaryOp(_, BinOp::Mul, _)));
3296 }
3297 other => panic!("expected Add, got {other:?}"),
3298 },
3299 other => panic!("expected Gt, got {other:?}"),
3300 }
3301 }
3302 _ => panic!("expected query"),
3303 }
3304 }
3305
3306 #[test]
3309 fn test_parse_multi_order() {
3310 let stmt = parse("User order .name asc, .age desc").unwrap();
3311 match stmt {
3312 Statement::Query(q) => {
3313 let order = q.order.unwrap();
3314 assert_eq!(order.keys.len(), 2);
3315 assert_eq!(order.keys[0].expr, Expr::Field("name".into()));
3316 assert!(!order.keys[0].descending);
3317 assert_eq!(order.keys[1].expr, Expr::Field("age".into()));
3318 assert!(order.keys[1].descending);
3319 }
3320 _ => panic!("expected query"),
3321 }
3322 }
3323
3324 #[test]
3325 fn test_parse_order_default_asc() {
3326 let stmt = parse("User order .name").unwrap();
3327 match stmt {
3328 Statement::Query(q) => {
3329 let order = q.order.unwrap();
3330 assert_eq!(order.keys.len(), 1);
3331 assert!(!order.keys[0].descending);
3332 }
3333 _ => panic!("expected query"),
3334 }
3335 }
3336
3337 #[test]
3340 fn test_parse_alter_add_column() {
3341 let stmt = parse("alter User add column status: str").unwrap();
3342 match stmt {
3343 Statement::AlterTable(at) => {
3344 assert_eq!(at.table, "User");
3345 match at.action {
3346 AlterAction::AddColumn {
3347 name,
3348 type_name,
3349 required,
3350 } => {
3351 assert_eq!(name, "status");
3352 assert_eq!(type_name, "str");
3353 assert!(!required);
3354 }
3355 other => panic!("expected AddColumn, got {other:?}"),
3356 }
3357 }
3358 other => panic!("expected AlterTable, got {other:?}"),
3359 }
3360 }
3361
3362 #[test]
3363 fn test_parse_alter_add_required_column() {
3364 let stmt = parse("alter User add required status: str").unwrap();
3365 match stmt {
3366 Statement::AlterTable(at) => match at.action {
3367 AlterAction::AddColumn { required, .. } => assert!(required),
3368 other => panic!("expected AddColumn, got {other:?}"),
3369 },
3370 other => panic!("expected AlterTable, got {other:?}"),
3371 }
3372 }
3373
3374 #[test]
3375 fn test_parse_type_with_unique_modifier() {
3376 let stmt = parse("type User { required unique email: str, age: int }").unwrap();
3377 match stmt {
3378 Statement::CreateType(ct) => {
3379 assert!(ct.fields[0].required && ct.fields[0].unique);
3380 assert!(!ct.fields[1].unique);
3381 }
3382 other => panic!("expected CreateType, got {other:?}"),
3383 }
3384 }
3385
3386 #[test]
3387 fn test_parse_type_unique_before_required() {
3388 let stmt = parse("type User { unique required email: str }").unwrap();
3390 match stmt {
3391 Statement::CreateType(ct) => {
3392 assert!(ct.fields[0].required && ct.fields[0].unique);
3393 }
3394 other => panic!("expected CreateType, got {other:?}"),
3395 }
3396 }
3397
3398 #[test]
3399 fn test_parse_alter_add_unique() {
3400 let stmt = parse("alter User add unique .email").unwrap();
3401 match stmt {
3402 Statement::AlterTable(at) => assert!(matches!(
3403 at.action,
3404 AlterAction::AddUnique {
3405 target: IndexTarget::Column(ref column),
3406 ..
3407 } if column == "email"
3408 )),
3409 other => panic!("expected AlterTable, got {other:?}"),
3410 }
3411 }
3412
3413 #[test]
3414 fn test_parse_alter_drop_column() {
3415 let stmt = parse("alter User drop column status").unwrap();
3416 match stmt {
3417 Statement::AlterTable(at) => {
3418 assert_eq!(at.table, "User");
3419 match at.action {
3420 AlterAction::DropColumn { name, .. } => assert_eq!(name, "status"),
3421 other => panic!("expected DropColumn, got {other:?}"),
3422 }
3423 }
3424 other => panic!("expected AlterTable, got {other:?}"),
3425 }
3426 }
3427
3428 #[test]
3429 fn test_parse_alter_drop_without_column_keyword() {
3430 let stmt = parse("alter User drop status").unwrap();
3431 match stmt {
3432 Statement::AlterTable(at) => match at.action {
3433 AlterAction::DropColumn { name, .. } => assert_eq!(name, "status"),
3434 other => panic!("expected DropColumn, got {other:?}"),
3435 },
3436 other => panic!("expected AlterTable, got {other:?}"),
3437 }
3438 }
3439
3440 #[test]
3441 fn test_parse_drop_table() {
3442 let stmt = parse("drop User").unwrap();
3443 match stmt {
3444 Statement::DropTable(dt) => assert_eq!(dt.table, "User"),
3445 other => panic!("expected DropTable, got {other:?}"),
3446 }
3447 }
3448
3449 #[test]
3452 fn test_parse_in_subquery() {
3453 let stmt = parse("User filter .name in (VIP { .name })").unwrap();
3454 match stmt {
3455 Statement::Query(q) => {
3456 let filter = q.filter.unwrap();
3457 match filter {
3458 Expr::InSubquery {
3459 expr,
3460 subquery,
3461 negated,
3462 } => {
3463 assert!(!negated);
3464 assert!(matches!(*expr, Expr::Field(ref f) if f == "name"));
3465 assert_eq!(subquery.source, "VIP");
3466 }
3467 other => panic!("expected InSubquery, got {other:?}"),
3468 }
3469 }
3470 _ => panic!("expected query"),
3471 }
3472 }
3473
3474 #[test]
3475 fn test_parse_not_in_subquery() {
3476 let stmt = parse("User filter .id not in (Order { .user_id })").unwrap();
3477 match stmt {
3478 Statement::Query(q) => match q.filter.unwrap() {
3479 Expr::InSubquery { negated, .. } => assert!(negated),
3480 other => panic!("expected InSubquery, got {other:?}"),
3481 },
3482 _ => panic!("expected query"),
3483 }
3484 }
3485
3486 #[test]
3487 fn test_parse_in_literal_list_still_works() {
3488 let stmt = parse("User filter .age in (25, 30, 35)").unwrap();
3490 match stmt {
3491 Statement::Query(q) => match q.filter.unwrap() {
3492 Expr::InList { list, negated, .. } => {
3493 assert!(!negated);
3494 assert_eq!(list.len(), 3);
3495 }
3496 other => panic!("expected InList, got {other:?}"),
3497 },
3498 _ => panic!("expected query"),
3499 }
3500 }
3501
3502 #[test]
3505 fn test_parse_create_view() {
3506 let stmt = parse("materialize OldUsers as User filter .age > 28").unwrap();
3507 match stmt {
3508 Statement::CreateView(cv) => {
3509 assert_eq!(cv.name, "OldUsers");
3510 assert_eq!(cv.query.source, "User");
3511 assert!(cv.query.filter.is_some());
3512 assert!(!cv.query_text.is_empty());
3513 }
3514 _ => panic!("expected CreateView"),
3515 }
3516 }
3517
3518 #[test]
3519 fn test_parse_create_view_with_projection() {
3520 let stmt = parse("materialize UserNames as User { .name }").unwrap();
3521 match stmt {
3522 Statement::CreateView(cv) => {
3523 assert_eq!(cv.name, "UserNames");
3524 assert!(cv.query.projection.is_some());
3525 }
3526 _ => panic!("expected CreateView"),
3527 }
3528 }
3529
3530 #[test]
3531 fn test_parse_refresh_view() {
3532 let stmt = parse("refresh OldUsers").unwrap();
3533 match stmt {
3534 Statement::RefreshView(rv) => {
3535 assert_eq!(rv.name, "OldUsers");
3536 }
3537 _ => panic!("expected RefreshView"),
3538 }
3539 }
3540
3541 #[test]
3542 fn test_parse_drop_view() {
3543 let stmt = parse("drop view OldUsers").unwrap();
3544 match stmt {
3545 Statement::DropView(dv) => {
3546 assert_eq!(dv.name, "OldUsers");
3547 }
3548 _ => panic!("expected DropView"),
3549 }
3550 }
3551
3552 #[test]
3553 fn test_parse_drop_table_still_works() {
3554 let stmt = parse("drop Users").unwrap();
3555 match stmt {
3556 Statement::DropTable(dt) => {
3557 assert_eq!(dt.table, "Users");
3558 }
3559 _ => panic!("expected DropTable"),
3560 }
3561 }
3562
3563 #[test]
3564 fn test_parse_union() {
3565 let stmt = parse("User union Order").unwrap();
3566 match stmt {
3567 Statement::Union(u) => {
3568 assert!(!u.all);
3569 match *u.left {
3570 Statement::Query(_) => {}
3571 _ => panic!("expected Query on left"),
3572 }
3573 match *u.right {
3574 Statement::Query(_) => {}
3575 _ => panic!("expected Query on right"),
3576 }
3577 }
3578 _ => panic!("expected Union"),
3579 }
3580 }
3581
3582 #[test]
3583 fn test_parse_union_all() {
3584 let stmt = parse("User union all Order").unwrap();
3585 match stmt {
3586 Statement::Union(u) => {
3587 assert!(u.all, "expected UNION ALL");
3588 match *u.left {
3589 Statement::Query(_) => {}
3590 _ => panic!("expected Query on left"),
3591 }
3592 match *u.right {
3593 Statement::Query(_) => {}
3594 _ => panic!("expected Query on right"),
3595 }
3596 }
3597 _ => panic!("expected Union"),
3598 }
3599 }
3600
3601 #[test]
3602 fn test_parse_union_chain() {
3603 let stmt = parse("User union Order union Product").unwrap();
3605 match stmt {
3606 Statement::Union(outer) => {
3607 assert!(!outer.all);
3608 match *outer.right {
3610 Statement::Query(q) => assert_eq!(q.source, "Product"),
3611 _ => panic!("expected Query(Product) on right"),
3612 }
3613 match *outer.left {
3615 Statement::Union(inner) => {
3616 assert!(!inner.all);
3617 match *inner.left {
3618 Statement::Query(q) => assert_eq!(q.source, "User"),
3619 _ => panic!("expected Query(User)"),
3620 }
3621 match *inner.right {
3622 Statement::Query(q) => assert_eq!(q.source, "Order"),
3623 _ => panic!("expected Query(Order)"),
3624 }
3625 }
3626 _ => panic!("expected inner Union"),
3627 }
3628 }
3629 _ => panic!("expected Union"),
3630 }
3631 }
3632
3633 #[test]
3634 fn test_parse_union_with_filter() {
3635 let stmt = parse("User filter .age > 10 union Order filter .total > 50").unwrap();
3636 match stmt {
3637 Statement::Union(u) => {
3638 assert!(!u.all);
3639 match *u.left {
3641 Statement::Query(q) => {
3642 assert_eq!(q.source, "User");
3643 assert!(q.filter.is_some());
3644 }
3645 _ => panic!("expected Query on left"),
3646 }
3647 match *u.right {
3648 Statement::Query(q) => {
3649 assert_eq!(q.source, "Order");
3650 assert!(q.filter.is_some());
3651 }
3652 _ => panic!("expected Query on right"),
3653 }
3654 }
3655 _ => panic!("expected Union"),
3656 }
3657 }
3658
3659 #[test]
3660 fn test_parse_count_distinct_standalone() {
3661 let stmt = parse("count(distinct User { .name })").unwrap();
3662 match stmt {
3663 Statement::Query(q) => {
3664 let agg = q.aggregation.unwrap();
3665 assert_eq!(agg.function, AggFunc::CountDistinct);
3666 assert_eq!(agg.argument, Some(Expr::Field("name".into())));
3667 }
3668 _ => panic!("expected Query"),
3669 }
3670 }
3671
3672 #[test]
3673 fn test_parse_count_distinct_in_projection() {
3674 let stmt = parse("User group .dept { .dept, count(distinct .name) }").unwrap();
3675 match stmt {
3676 Statement::Query(q) => {
3677 let proj = q.projection.unwrap();
3678 assert_eq!(proj.len(), 2);
3679 match &proj[1].expr {
3680 Expr::FunctionCall(func, _, _) => {
3681 assert_eq!(*func, AggFunc::CountDistinct);
3682 }
3683 _ => panic!("expected FunctionCall"),
3684 }
3685 }
3686 _ => panic!("expected Query"),
3687 }
3688 }
3689
3690 #[test]
3693 fn test_parse_window_row_number_order() {
3694 let stmt = parse("User { .name, rn: row_number() over (order .age) }").unwrap();
3695 match stmt {
3696 Statement::Query(q) => {
3697 let proj = q.projection.unwrap();
3698 assert_eq!(proj.len(), 2);
3699 assert_eq!(proj[1].alias.as_deref(), Some("rn"));
3700 match &proj[1].expr {
3701 Expr::Window {
3702 function,
3703 args,
3704 partition_by,
3705 order_by,
3706 ..
3707 } => {
3708 assert_eq!(*function, WindowFunc::RowNumber);
3709 assert!(args.is_empty());
3710 assert!(partition_by.is_empty());
3711 assert_eq!(order_by.len(), 1);
3712 assert_eq!(order_by[0].expr, Expr::Field("age".into()));
3713 assert!(!order_by[0].descending);
3714 }
3715 other => panic!("expected Window, got {other:?}"),
3716 }
3717 }
3718 _ => panic!("expected query"),
3719 }
3720 }
3721
3722 #[test]
3723 fn test_parse_window_sum_partition_order() {
3724 let stmt =
3725 parse("User { .name, s: sum(.salary) over (partition .dept order .salary) }").unwrap();
3726 match stmt {
3727 Statement::Query(q) => {
3728 let proj = q.projection.unwrap();
3729 assert_eq!(proj.len(), 2);
3730 assert_eq!(proj[1].alias.as_deref(), Some("s"));
3731 match &proj[1].expr {
3732 Expr::Window {
3733 function,
3734 args,
3735 partition_by,
3736 order_by,
3737 ..
3738 } => {
3739 assert_eq!(*function, WindowFunc::Sum);
3740 assert_eq!(args.len(), 1);
3741 assert!(matches!(&args[0], Expr::Field(f) if f == "salary"));
3742 assert_eq!(partition_by, &[Expr::Field("dept".into())]);
3743 assert_eq!(order_by.len(), 1);
3744 assert_eq!(order_by[0].expr, Expr::Field("salary".into()));
3745 assert!(!order_by[0].descending);
3746 }
3747 other => panic!("expected Window, got {other:?}"),
3748 }
3749 }
3750 _ => panic!("expected query"),
3751 }
3752 }
3753
3754 #[test]
3755 fn test_parse_window_rank_desc() {
3756 let stmt =
3757 parse("User { .dept, .salary, r: rank() over (partition .dept order .salary desc) }")
3758 .unwrap();
3759 match stmt {
3760 Statement::Query(q) => {
3761 let proj = q.projection.unwrap();
3762 assert_eq!(proj.len(), 3);
3763 match &proj[2].expr {
3764 Expr::Window {
3765 function,
3766 partition_by,
3767 order_by,
3768 ..
3769 } => {
3770 assert_eq!(*function, WindowFunc::Rank);
3771 assert_eq!(partition_by, &[Expr::Field("dept".into())]);
3772 assert_eq!(order_by.len(), 1);
3773 assert!(order_by[0].descending);
3774 }
3775 other => panic!("expected Window, got {other:?}"),
3776 }
3777 }
3778 _ => panic!("expected query"),
3779 }
3780 }
3781
3782 #[test]
3783 fn test_parse_window_dense_rank() {
3784 let stmt = parse("User { .name, dr: dense_rank() over (order .score desc) }").unwrap();
3785 match stmt {
3786 Statement::Query(q) => {
3787 let proj = q.projection.unwrap();
3788 assert_eq!(proj.len(), 2);
3789 match &proj[1].expr {
3790 Expr::Window { function, .. } => {
3791 assert_eq!(*function, WindowFunc::DenseRank);
3792 }
3793 other => panic!("expected Window, got {other:?}"),
3794 }
3795 }
3796 _ => panic!("expected query"),
3797 }
3798 }
3799
3800 #[test]
3801 fn test_parse_sum_without_over_is_aggregate() {
3802 let stmt = parse("User group .dept { .dept, total: sum(.salary) }").unwrap();
3804 match stmt {
3805 Statement::Query(q) => {
3806 let proj = q.projection.unwrap();
3807 assert_eq!(proj.len(), 2);
3808 match &proj[1].expr {
3809 Expr::FunctionCall(AggFunc::Sum, _, _) => {} other => panic!("expected FunctionCall(Sum), got {other:?}"),
3811 }
3812 }
3813 _ => panic!("expected query"),
3814 }
3815 }
3816
3817 #[test]
3818 fn test_nesting_depth_limit() {
3819 let mut query = String::from("User filter ");
3821 for _ in 0..70 {
3822 query.push('(');
3823 }
3824 query.push_str(".age > 1");
3825 for _ in 0..70 {
3826 query.push(')');
3827 }
3828 let result = parse(&query);
3829 assert!(result.is_err());
3830 let err = result.unwrap_err();
3831 assert!(
3832 err.message().contains("nesting depth"),
3833 "expected nesting depth error, got: {}",
3834 err.message()
3835 );
3836 }
3837
3838 #[test]
3839 fn test_unary_prefix_nesting_depth_limit() {
3840 let query = String::from("User filter ") + &"not ".repeat(5000) + ".active";
3844 let result = parse(&query);
3845 assert!(result.is_err());
3846 let err = result.unwrap_err();
3847 assert!(
3848 err.message().contains("nesting depth"),
3849 "expected nesting depth error, got: {}",
3850 err.message()
3851 );
3852 }
3853
3854 #[test]
3855 fn test_moderate_nesting_succeeds() {
3856 let mut query = String::from("User filter ");
3858 for _ in 0..10 {
3859 query.push('(');
3860 }
3861 query.push_str(".age > 1");
3862 for _ in 0..10 {
3863 query.push(')');
3864 }
3865 assert!(parse(&query).is_ok());
3866 }
3867
3868 #[test]
3872 fn test_parse_fuzz_repro_projection_eof() {
3873 let err = parse("nn{").expect_err("unterminated projection must error, not panic");
3874 let _ = err.message();
3875 }
3876
3877 #[test]
3880 fn test_parse_fuzz_repro_short_projection_eof() {
3881 let err = parse("z{").expect_err("unterminated projection must error, not panic");
3882 let _ = err.message();
3883 }
3884
3885 #[test]
3886 fn test_update_at_statement_start_gives_helpful_error() {
3887 let err =
3888 parse(r#"update User filter .name = "Alice" { age := 31 }"#).expect_err("should fail");
3889 let msg = err.message();
3890 assert!(
3891 msg.contains("pipeline syntax"),
3892 "error should mention pipeline syntax, got: {msg}"
3893 );
3894 assert!(
3895 msg.contains("update"),
3896 "error should mention 'update', got: {msg}"
3897 );
3898 }
3899
3900 #[test]
3901 fn test_delete_at_statement_start_gives_helpful_error() {
3902 let err = parse("delete User filter .age < 18").expect_err("should fail");
3903 let msg = err.message();
3904 assert!(
3905 msg.contains("pipeline syntax"),
3906 "error should mention pipeline syntax, got: {msg}"
3907 );
3908 assert!(
3909 msg.contains("delete"),
3910 "error should mention 'delete', got: {msg}"
3911 );
3912 }
3913}
3914
3915#[cfg(test)]
3916mod cleanup_parser_dx_tests {
3917 use super::*;
3918
3919 #[test]
3920 fn typoed_statement_keyword_gets_suggestion() {
3921 let err = parse("updat User set age = 1").unwrap_err();
3922 let msg = err.to_string();
3923 assert!(msg.contains("near token"), "{msg}");
3924 assert!(msg.contains("did you mean `update`"), "{msg}");
3925 }
3926}
3927
3928#[cfg(test)]
3929mod dogfood_dx_tests {
3930 use super::*;
3931
3932 #[test]
3935 fn reserved_word_field_name_gives_actionable_error() {
3936 let err = parse("type Post { type: str }").unwrap_err();
3937 let msg = err.to_string();
3938 assert!(
3939 msg.contains("'type' is a reserved word")
3940 && msg.contains("field name")
3941 && msg.contains("quote it as `type`"),
3942 "unhelpful message: {msg}"
3943 );
3944 }
3945
3946 #[test]
3947 fn reserved_modifier_word_as_field_name_gives_actionable_error() {
3948 let err = parse("type Post { required: bool }").unwrap_err();
3952 let msg = err.to_string();
3953 assert!(
3954 msg.contains("'required' is a reserved word") && msg.contains("quote it as `required`"),
3955 "unhelpful message: {msg}"
3956 );
3957 }
3958
3959 #[test]
3960 fn reserved_word_in_insert_assignment_gives_actionable_error() {
3961 let err = parse(r#"insert Post { type := "x" }"#).unwrap_err();
3962 let msg = err.to_string();
3963 assert!(msg.contains("'type' is a reserved word"), "{msg}");
3964 }
3965
3966 #[test]
3967 fn reserved_word_in_alter_column_gives_actionable_error() {
3968 let err = parse("alter Post add column order: int").unwrap_err();
3969 let msg = err.to_string();
3970 assert!(msg.contains("'order' is a reserved word"), "{msg}");
3971 }
3972
3973 #[test]
3974 fn backtick_field_name_parses_as_identifier() {
3975 let stmt = parse("type Post { `type`: str, `order`: int }").unwrap();
3976 match stmt {
3977 Statement::CreateType(ct) => {
3978 assert_eq!(ct.fields[0].name, "type");
3979 assert_eq!(ct.fields[1].name, "order");
3980 }
3981 other => panic!("expected CreateType, got {other:?}"),
3982 }
3983 }
3984
3985 #[test]
3986 fn backtick_field_still_honors_modifiers() {
3987 let stmt = parse("type Post { required `type`: str }").unwrap();
3988 match stmt {
3989 Statement::CreateType(ct) => {
3990 assert_eq!(ct.fields[0].name, "type");
3991 assert!(ct.fields[0].required);
3992 }
3993 other => panic!("expected CreateType, got {other:?}"),
3994 }
3995 }
3996
3997 #[test]
4000 fn create_type_if_not_exists_parses() {
4001 let stmt = parse("type Post if not exists { id: int }").unwrap();
4002 match stmt {
4003 Statement::CreateType(ct) => assert!(ct.if_not_exists),
4004 other => panic!("expected CreateType, got {other:?}"),
4005 }
4006 }
4007
4008 #[test]
4009 fn create_type_without_clause_defaults_false() {
4010 let stmt = parse("type Post { id: int }").unwrap();
4011 match stmt {
4012 Statement::CreateType(ct) => assert!(!ct.if_not_exists),
4013 other => panic!("expected CreateType, got {other:?}"),
4014 }
4015 }
4016
4017 #[test]
4018 fn drop_if_exists_parses() {
4019 match parse("drop if exists Post").unwrap() {
4020 Statement::DropTable(dt) => assert!(dt.if_exists),
4021 other => panic!("expected DropTable, got {other:?}"),
4022 }
4023 match parse("drop Post").unwrap() {
4024 Statement::DropTable(dt) => assert!(!dt.if_exists),
4025 other => panic!("expected DropTable, got {other:?}"),
4026 }
4027 }
4028
4029 #[test]
4030 fn drop_view_if_exists_parses() {
4031 match parse("drop view if exists ActiveUsers").unwrap() {
4032 Statement::DropView(dv) => {
4033 assert!(dv.if_exists);
4034 assert_eq!(dv.name, "ActiveUsers");
4035 }
4036 other => panic!("expected DropView, got {other:?}"),
4037 }
4038 }
4039
4040 #[test]
4041 fn add_index_and_unique_if_not_exists_parse() {
4042 match parse("alter Post add index if not exists .slug").unwrap() {
4043 Statement::AlterTable(at) => {
4044 assert!(matches!(
4045 at.action,
4046 AlterAction::AddIndex {
4047 if_not_exists: true,
4048 ..
4049 }
4050 ));
4051 }
4052 other => panic!("expected AlterTable, got {other:?}"),
4053 }
4054 match parse("alter Post add unique if not exists .slug").unwrap() {
4055 Statement::AlterTable(at) => {
4056 assert!(matches!(
4057 at.action,
4058 AlterAction::AddUnique {
4059 if_not_exists: true,
4060 ..
4061 }
4062 ));
4063 }
4064 other => panic!("expected AlterTable, got {other:?}"),
4065 }
4066 }
4067
4068 #[test]
4069 fn expression_index_targets_parse_with_stable_table_local_identity() {
4070 use powdb_storage::stored_json_path::{
4071 StoredJsonPathSegmentV1 as Segment, StoredJsonPathV1,
4072 };
4073
4074 let expected = StoredJsonPathV1::new(
4075 "data",
4076 vec![Segment::Key("author".into()), Segment::Index(0)],
4077 );
4078 for query in [
4079 "alter Post add index (.data->author->0)",
4080 "alter Post add unique if not exists (.data->\"author\"->0)",
4081 "alter Post drop index if exists (.data->author->0)",
4082 ] {
4083 let Statement::AlterTable(alter) = parse(query).unwrap() else {
4084 panic!("expected alter table for {query}");
4085 };
4086 let (target, flag) = match alter.action {
4087 AlterAction::AddIndex {
4088 target,
4089 if_not_exists,
4090 }
4091 | AlterAction::AddUnique {
4092 target,
4093 if_not_exists,
4094 } => (target, if_not_exists),
4095 AlterAction::DropIndex { target, if_exists } => (target, if_exists),
4096 other => panic!("expected index action, got {other:?}"),
4097 };
4098 assert_eq!(target, IndexTarget::JsonPath(expected.clone()));
4099 assert_eq!(
4100 flag,
4101 query.contains("if not exists") || query.contains("if exists")
4102 );
4103 }
4104 }
4105
4106 #[test]
4107 fn expression_index_target_rejects_ambiguous_or_non_path_forms() {
4108 let cases = [
4109 (
4110 "alter Post add index .data->author",
4111 "must be parenthesized",
4112 ),
4113 (
4114 "alter Post add index (p.data->author)",
4115 "qualified JSON paths",
4116 ),
4117 (
4118 "alter Post add index (.data)",
4119 "use `.column` for a stored column",
4120 ),
4121 (
4122 "alter Post add index (.data->age + 1)",
4123 "only a direct JSON path",
4124 ),
4125 (
4126 "alter Post drop index ({ value := 1 })",
4127 "expected an unqualified JSON path",
4128 ),
4129 ];
4130 for (query, expected) in cases {
4131 let error = parse(query).expect_err(query).to_string();
4132 assert!(
4133 error.contains(expected),
4134 "`{query}` should mention `{expected}`, got `{error}`"
4135 );
4136 }
4137 }
4138
4139 #[test]
4140 fn alter_drop_column_if_exists_parses() {
4141 match parse("alter Post drop column if exists status").unwrap() {
4142 Statement::AlterTable(at) => {
4143 assert!(matches!(
4144 at.action,
4145 AlterAction::DropColumn {
4146 if_exists: true,
4147 ..
4148 }
4149 ));
4150 }
4151 other => panic!("expected AlterTable, got {other:?}"),
4152 }
4153 }
4154
4155 #[test]
4158 fn schema_parses_to_list_types() {
4159 assert_eq!(parse("schema").unwrap(), Statement::ListTypes);
4160 }
4161
4162 #[test]
4163 fn describe_parses_to_describe() {
4164 assert_eq!(
4165 parse("describe Post").unwrap(),
4166 Statement::Describe("Post".to_string())
4167 );
4168 }
4169
4170 #[test]
4171 fn schema_with_type_aliases_describe() {
4172 assert_eq!(
4173 parse("schema Post").unwrap(),
4174 Statement::Describe("Post".to_string())
4175 );
4176 }
4177}
4178
4179#[cfg(test)]
4180mod json_path_tests {
4181 use super::*;
4182
4183 fn filter_of(src: &str) -> Expr {
4185 match parse(src).unwrap() {
4186 Statement::Query(q) => q.filter.expect("expected a filter"),
4187 other => panic!("expected a query, got {other:?}"),
4188 }
4189 }
4190
4191 #[test]
4192 fn ident_key_path() {
4193 let e = filter_of(r#"Post filter .data->author->name = "x""#);
4195 let Expr::BinaryOp(lhs, BinOp::Eq, _) = e else {
4196 panic!("expected an equality, got {e:?}");
4197 };
4198 assert_eq!(
4199 *lhs,
4200 Expr::JsonPath {
4201 base: Box::new(Expr::Field("data".into())),
4202 segments: vec![PathSeg::Key("author".into()), PathSeg::Key("name".into())],
4203 }
4204 );
4205 }
4206
4207 #[test]
4208 fn string_form_key_path() {
4209 let e = filter_of(r#"Post filter .data->"weird key!" = 1"#);
4211 let Expr::BinaryOp(lhs, _, _) = e else {
4212 panic!("expected binop");
4213 };
4214 assert_eq!(
4215 *lhs,
4216 Expr::JsonPath {
4217 base: Box::new(Expr::Field("data".into())),
4218 segments: vec![PathSeg::Key("weird key!".into())],
4219 }
4220 );
4221 }
4222
4223 #[test]
4224 fn array_index_path() {
4225 let e = filter_of(r#"Post filter .data->tags->0 = "rust""#);
4227 let Expr::BinaryOp(lhs, _, _) = e else {
4228 panic!("expected binop");
4229 };
4230 assert_eq!(
4231 *lhs,
4232 Expr::JsonPath {
4233 base: Box::new(Expr::Field("data".into())),
4234 segments: vec![PathSeg::Key("tags".into()), PathSeg::Index(0)],
4235 }
4236 );
4237 }
4238
4239 #[test]
4240 fn qualified_base_path() {
4241 let e = filter_of(r#"Post as posts filter posts.data->author = "a""#);
4243 let Expr::BinaryOp(lhs, _, _) = e else {
4244 panic!("expected binop");
4245 };
4246 assert_eq!(
4247 *lhs,
4248 Expr::JsonPath {
4249 base: Box::new(Expr::QualifiedField {
4250 qualifier: "posts".into(),
4251 field: "data".into(),
4252 }),
4253 segments: vec![PathSeg::Key("author".into())],
4254 }
4255 );
4256 }
4257
4258 #[test]
4259 fn path_binds_tighter_than_comparison_and_arithmetic() {
4260 let e = filter_of("Post filter .data->age > 21");
4262 let Expr::BinaryOp(lhs, BinOp::Gt, rhs) = e else {
4263 panic!("expected a top-level `>`, got {e:?}");
4264 };
4265 assert!(matches!(*lhs, Expr::JsonPath { .. }));
4266 assert_eq!(*rhs, Expr::Literal(Literal::Int(21)));
4267
4268 let e = filter_of("Post filter .a->b + 1 = 3");
4270 let Expr::BinaryOp(add, BinOp::Eq, _) = e else {
4271 panic!("expected eq");
4272 };
4273 let Expr::BinaryOp(lhs, BinOp::Add, _) = *add else {
4274 panic!("expected `+` under `=`, got {add:?}");
4275 };
4276 assert!(matches!(*lhs, Expr::JsonPath { .. }));
4277 }
4278
4279 #[test]
4280 fn dash_vs_arrow_lexing() {
4281 let idx = filter_of("Post filter .a->1 = 0");
4284 let Expr::BinaryOp(lhs, BinOp::Eq, _) = idx else {
4285 panic!("expected eq");
4286 };
4287 assert_eq!(
4288 *lhs,
4289 Expr::JsonPath {
4290 base: Box::new(Expr::Field("a".into())),
4291 segments: vec![PathSeg::Index(1)],
4292 }
4293 );
4294
4295 let sub = filter_of("Post filter .a - 1 = 0");
4298 let Expr::BinaryOp(lhs, BinOp::Eq, _) = sub else {
4299 panic!("expected eq");
4300 };
4301 assert!(
4302 matches!(*lhs, Expr::BinaryOp(_, BinOp::Sub, _)),
4303 "`.a - 1` should be subtraction, got {lhs:?}"
4304 );
4305
4306 assert!(
4311 parse("Post filter .a-1 = 0").is_err(),
4312 "`.a-1` should fail to parse (negative-literal gotcha)"
4313 );
4314
4315 assert!(
4317 parse("Post filter .a - > 0").is_err(),
4318 "`.a - >` should fail to parse"
4319 );
4320 }
4321
4322 #[test]
4323 fn negative_index_rejected() {
4324 let err = parse("Post filter .data->-1 = 0").unwrap_err();
4325 assert!(
4326 err.to_string().contains("array index"),
4327 "expected an index error, got: {err}"
4328 );
4329 }
4330
4331 #[test]
4332 fn path_on_literal_base_rejected() {
4333 let err = parse("Post filter 5->x = 1").unwrap_err();
4335 assert!(
4336 err.to_string().to_lowercase().contains("field base"),
4337 "expected a field-base error, got: {err}"
4338 );
4339 }
4340
4341 #[test]
4342 fn json_path_assignment_target_is_targeted_unsupported() {
4343 for stmt in [
4348 "Doc update { .data->x := 5 }",
4349 "Doc update { data->x := 5 }",
4350 ] {
4351 let err = parse(stmt).unwrap_err();
4352 assert!(
4353 matches!(err, ParseError::Unsupported { .. }),
4354 "{stmt}: expected Unsupported, got {err:?}"
4355 );
4356 let msg = err.to_string();
4357 assert!(
4358 msg.contains("JSON path assignment targets are not supported"),
4359 "{stmt}: message must state the unsupported feature: {msg}"
4360 );
4361 assert!(
4362 msg.contains("json_set"),
4363 "{stmt}: message must point at the whole-column alternative: {msg}"
4364 );
4365 }
4366 assert!(parse(r#"Doc update { data := "{}" }"#).is_ok());
4368 }
4369
4370 #[test]
4371 fn json_type_scalar_parses() {
4372 let e = filter_of(r#"Post filter json_type(.data->x) = "string""#);
4373 let Expr::BinaryOp(lhs, _, _) = e else {
4374 panic!("expected binop");
4375 };
4376 let Expr::ScalarFunc(ScalarFn::JsonType, args) = *lhs else {
4377 panic!("expected json_type call, got {lhs:?}");
4378 };
4379 assert_eq!(args.len(), 1);
4380 assert!(matches!(args[0], Expr::JsonPath { .. }));
4381 }
4382
4383 #[test]
4384 fn path_in_projection() {
4385 let stmt = parse("Post { author: .data->author }").unwrap();
4388 let Statement::Query(q) = stmt else {
4389 panic!("expected query");
4390 };
4391 let proj = q.projection.unwrap();
4392 assert_eq!(proj[0].alias.as_deref(), Some("author"));
4393 assert!(matches!(proj[0].expr, Expr::JsonPath { .. }));
4394
4395 let Statement::Query(ordered) = parse("Post order .data->author { .id }").unwrap() else {
4396 panic!("expected query");
4397 };
4398 assert!(matches!(
4399 ordered.order.unwrap().keys[0].expr,
4400 Expr::JsonPath { .. }
4401 ));
4402
4403 let Statement::Query(grouped) =
4404 parse("Post group .data->author { .data->author }").unwrap()
4405 else {
4406 panic!("expected query");
4407 };
4408 assert!(matches!(
4409 grouped.group_by.unwrap().keys[0].expr,
4410 Expr::JsonPath { .. }
4411 ));
4412 }
4413}