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