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 mut negations = 0usize;
1177 while *self.peek() == Token::Not
1178 && !matches!(self.tokens.get(self.pos + 1), Some(Token::Exists))
1179 {
1180 self.advance();
1181 negations += 1;
1182 if self.depth + negations > MAX_NESTING_DEPTH {
1183 return Err(ParseError::NestingDepthExceeded {
1184 max: MAX_NESTING_DEPTH,
1185 });
1186 }
1187 }
1188 let mut expr = self.parse_comparison_body()?;
1189 for _ in 0..negations {
1190 expr = Expr::UnaryOp(UnaryOp::Not, Box::new(expr));
1191 }
1192 Ok(expr)
1193 }
1194
1195 fn parse_comparison_body(&mut self) -> Result<Expr, ParseError> {
1196 let left = self.parse_additive()?;
1197
1198 if *self.peek() == Token::Is {
1200 self.advance();
1201 if *self.peek() == Token::Not {
1202 self.advance();
1203 self.expect(&Token::Null)?;
1204 return Ok(Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(left)));
1205 } else {
1206 self.expect(&Token::Null)?;
1207 return Ok(Expr::UnaryOp(UnaryOp::IsNull, Box::new(left)));
1208 }
1209 }
1210
1211 match self.peek() {
1214 Token::In => {
1215 self.advance();
1216 return self.parse_in_list(left, false);
1217 }
1218 Token::Like => {
1219 self.advance();
1220 let pattern = self.parse_additive()?;
1221 return Ok(Expr::BinaryOp(
1222 Box::new(left),
1223 BinOp::Like,
1224 Box::new(pattern),
1225 ));
1226 }
1227 Token::Between => {
1228 self.advance();
1229 return self.parse_between(left, false);
1230 }
1231 Token::Not => {
1232 let next = self.tokens.get(self.pos + 1);
1236 match next {
1237 Some(Token::In) => {
1238 self.advance(); self.advance(); return self.parse_in_list(left, true);
1241 }
1242 Some(Token::Like) => {
1243 self.advance(); self.advance(); let pattern = self.parse_additive()?;
1246 let like = Expr::BinaryOp(Box::new(left), BinOp::Like, Box::new(pattern));
1247 return Ok(Expr::UnaryOp(UnaryOp::Not, Box::new(like)));
1248 }
1249 Some(Token::Between) => {
1250 self.advance(); self.advance(); return self.parse_between(left, true);
1253 }
1254 _ => {}
1255 }
1256 }
1257 _ => {}
1258 }
1259
1260 let op = match self.peek() {
1261 Token::Eq => BinOp::Eq,
1262 Token::Neq => BinOp::Neq,
1263 Token::Lt => BinOp::Lt,
1264 Token::Gt => BinOp::Gt,
1265 Token::Lte => BinOp::Lte,
1266 Token::Gte => BinOp::Gte,
1267 _ => return Ok(left),
1268 };
1269 self.advance();
1270 if *self.peek() == Token::Null {
1274 match op {
1275 BinOp::Eq => {
1276 self.advance();
1277 return Ok(Expr::UnaryOp(UnaryOp::IsNull, Box::new(left)));
1278 }
1279 BinOp::Neq => {
1280 self.advance();
1281 return Ok(Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(left)));
1282 }
1283 _ => {}
1284 }
1285 }
1286 let right = self.parse_additive()?;
1287 Ok(Expr::BinaryOp(Box::new(left), op, Box::new(right)))
1288 }
1289
1290 fn parse_in_list(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParseError> {
1295 self.expect(&Token::LParen)?;
1296 if let Token::Ident(_) = self.peek() {
1298 let after = self.tokens.get(self.pos + 1);
1301 let is_subquery = !matches!(after, Some(Token::Comma) | Some(Token::RParen));
1302 if is_subquery {
1303 let source = match self.advance() {
1304 Token::Ident(name) => name,
1305 _ => unreachable!(),
1306 };
1307 let subquery = self.parse_query_tail(source)?;
1308 self.expect(&Token::RParen)?;
1309 return Ok(Expr::InSubquery {
1310 expr: Box::new(expr),
1311 subquery: Box::new(subquery),
1312 negated,
1313 });
1314 }
1315 }
1316 let mut list = Vec::new();
1317 while !matches!(self.peek(), Token::RParen | Token::Eof) {
1318 list.push(self.parse_expr()?);
1319 if *self.peek() == Token::Comma {
1320 self.advance();
1321 }
1322 }
1323 self.expect(&Token::RParen)?;
1324 Ok(Expr::InList {
1325 expr: Box::new(expr),
1326 list,
1327 negated,
1328 })
1329 }
1330
1331 fn try_parse_exists_subquery(&mut self) -> Result<Option<QueryExpr>, ParseError> {
1339 if *self.peek() != Token::LParen {
1340 return Ok(None);
1341 }
1342 let after_lparen = self.tokens.get(self.pos + 1);
1346 if !matches!(after_lparen, Some(Token::Ident(_))) {
1347 return Ok(None);
1348 }
1349 self.expect(&Token::LParen)?;
1350 let source = match self.advance() {
1351 Token::Ident(name) => name,
1352 _ => unreachable!(),
1353 };
1354 let subquery = self.parse_query_tail(source)?;
1355 self.expect(&Token::RParen)?;
1356 Ok(Some(subquery))
1357 }
1358
1359 fn parse_between(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParseError> {
1363 let low = self.parse_additive()?;
1364 self.expect(&Token::And)?;
1365 let high = self.parse_additive()?;
1366 if negated {
1367 Ok(Expr::BinaryOp(
1369 Box::new(Expr::BinaryOp(
1370 Box::new(expr.clone()),
1371 BinOp::Lt,
1372 Box::new(low),
1373 )),
1374 BinOp::Or,
1375 Box::new(Expr::BinaryOp(Box::new(expr), BinOp::Gt, Box::new(high))),
1376 ))
1377 } else {
1378 Ok(Expr::BinaryOp(
1380 Box::new(Expr::BinaryOp(
1381 Box::new(expr.clone()),
1382 BinOp::Gte,
1383 Box::new(low),
1384 )),
1385 BinOp::And,
1386 Box::new(Expr::BinaryOp(Box::new(expr), BinOp::Lte, Box::new(high))),
1387 ))
1388 }
1389 }
1390
1391 fn parse_group_by(&mut self) -> Result<GroupByClause, ParseError> {
1393 let mut keys = Vec::new();
1394 loop {
1395 let expr = self.parse_expr()?;
1396 let output_name = match &expr {
1397 Expr::Field(_) | Expr::QualifiedField { .. } | Expr::JsonPath { .. } => {
1398 expression_output_name(&expr)
1399 }
1400 _ => format!("__group_{}", keys.len()),
1401 };
1402 keys.push(GroupKey { expr, output_name });
1403 if *self.peek() == Token::Comma {
1404 self.advance();
1405 } else {
1406 break;
1407 }
1408 }
1409 if keys.is_empty() {
1410 return Err(ParseError::Syntax {
1411 message: "expected at least one group key after group".into(),
1412 });
1413 }
1414 let having = if *self.peek() == Token::Having {
1415 self.advance();
1416 Some(self.parse_expr()?)
1417 } else {
1418 None
1419 };
1420 Ok(GroupByClause { keys, having })
1421 }
1422
1423 fn parse_additive(&mut self) -> Result<Expr, ParseError> {
1424 let mut left = self.parse_multiplicative()?;
1425 loop {
1426 let op = match self.peek() {
1427 Token::Plus => BinOp::Add,
1428 Token::Minus => BinOp::Sub,
1429 Token::Coalesce => {
1430 self.advance();
1431 let right = self.parse_multiplicative()?;
1432 left = Expr::Coalesce(Box::new(left), Box::new(right));
1433 continue;
1434 }
1435 _ => break,
1436 };
1437 self.advance();
1438 let right = self.parse_multiplicative()?;
1439 left = Expr::BinaryOp(Box::new(left), op, Box::new(right));
1440 }
1441 Ok(left)
1442 }
1443
1444 fn parse_multiplicative(&mut self) -> Result<Expr, ParseError> {
1445 let mut left = self.parse_primary()?;
1446 loop {
1447 let op = match self.peek() {
1448 Token::Star => BinOp::Mul,
1449 Token::Slash => BinOp::Div,
1450 _ => break,
1451 };
1452 self.advance();
1453 let right = self.parse_primary()?;
1454 left = Expr::BinaryOp(Box::new(left), op, Box::new(right));
1455 }
1456 Ok(left)
1457 }
1458
1459 fn parse_primary(&mut self) -> Result<Expr, ParseError> {
1460 self.depth += 1;
1465 if self.depth > MAX_NESTING_DEPTH {
1466 self.depth -= 1;
1467 return Err(ParseError::NestingDepthExceeded {
1468 max: MAX_NESTING_DEPTH,
1469 });
1470 }
1471 let result = self.parse_primary_inner();
1472 self.depth -= 1;
1473 self.parse_json_path_postfix(result?)
1479 }
1480
1481 fn parse_json_path_postfix(&mut self, base: Expr) -> Result<Expr, ParseError> {
1488 if *self.peek() != Token::Arrow {
1489 return Ok(base);
1490 }
1491 match &base {
1492 Expr::Field(_) | Expr::QualifiedField { .. } | Expr::JsonPath { .. } => {}
1493 _ => {
1494 return Err(ParseError::Syntax {
1495 message: "'->' JSON path access requires a field base \
1496 (e.g. .data->key or posts.data->author)"
1497 .into(),
1498 })
1499 }
1500 }
1501 let mut segments = Vec::new();
1502 while *self.peek() == Token::Arrow {
1503 self.advance(); let seg = match self.advance() {
1505 Token::Ident(name) => PathSeg::Key(name),
1507 Token::StringLit(s) => PathSeg::Key(s),
1510 Token::IntLit(v) => {
1514 let idx = u32::try_from(v).map_err(|_| ParseError::Syntax {
1515 message: format!(
1516 "invalid JSON path array index {v}: expected a non-negative integer that fits in 32 bits"
1517 ),
1518 })?;
1519 PathSeg::Index(idx)
1520 }
1521 other => {
1522 return Err(ParseError::Syntax {
1523 message: format!(
1524 "expected a JSON path segment (object key or array index) after '->', found {}",
1525 other.display_name()
1526 ),
1527 })
1528 }
1529 };
1530 segments.push(seg);
1531 }
1532 if let Expr::JsonPath {
1535 base: inner_base,
1536 segments: mut inner_segments,
1537 } = base
1538 {
1539 inner_segments.extend(segments);
1540 return Ok(Expr::JsonPath {
1541 base: inner_base,
1542 segments: inner_segments,
1543 });
1544 }
1545 Ok(Expr::JsonPath {
1546 base: Box::new(base),
1547 segments,
1548 })
1549 }
1550
1551 fn parse_primary_inner(&mut self) -> Result<Expr, ParseError> {
1552 match self.peek().clone() {
1553 Token::DotIdent(name) => {
1554 self.advance();
1555 Ok(Expr::Field(name))
1556 }
1557 Token::IntLit(v) => {
1558 self.advance();
1559 Ok(Expr::Literal(Literal::Int(v)))
1560 }
1561 Token::FloatLit(v) => {
1562 self.advance();
1563 Ok(Expr::Literal(Literal::Float(v)))
1564 }
1565 Token::StringLit(v) => {
1566 self.advance();
1567 Ok(Expr::Literal(Literal::String(v)))
1568 }
1569 Token::BoolLit(v) => {
1570 self.advance();
1571 Ok(Expr::Literal(Literal::Bool(v)))
1572 }
1573 Token::Null => {
1580 self.advance();
1581 Ok(Expr::Null)
1582 }
1583 Token::Not => {
1584 self.advance();
1585 if *self.peek() == Token::Exists {
1586 self.advance();
1587 if let Some(sub) = self.try_parse_exists_subquery()? {
1591 return Ok(Expr::ExistsSubquery {
1592 subquery: Box::new(sub),
1593 negated: true,
1594 });
1595 }
1596 let expr = self.parse_primary()?;
1597 Ok(Expr::UnaryOp(UnaryOp::NotExists, Box::new(expr)))
1598 } else {
1599 let expr = self.parse_primary()?;
1600 Ok(Expr::UnaryOp(UnaryOp::Not, Box::new(expr)))
1601 }
1602 }
1603 Token::Exists => {
1604 self.advance();
1605 if let Some(sub) = self.try_parse_exists_subquery()? {
1609 return Ok(Expr::ExistsSubquery {
1610 subquery: Box::new(sub),
1611 negated: false,
1612 });
1613 }
1614 let expr = self.parse_primary()?;
1615 Ok(Expr::UnaryOp(UnaryOp::Exists, Box::new(expr)))
1616 }
1617 Token::LParen => {
1618 self.advance();
1619 let expr = self.parse_expr()?;
1620 self.expect(&Token::RParen)?;
1621 Ok(expr)
1622 }
1623 Token::Ident(name) => {
1624 self.advance();
1625 if *self.peek() == Token::LParen {
1630 let cast_type = match name.as_str() {
1631 "uuid" => Some(CastType::Uuid),
1632 "bytes" => Some(CastType::Bytes),
1633 _ => None,
1634 };
1635 if let Some(cast_type) = cast_type {
1636 self.advance(); let inner = self.parse_expr()?;
1638 self.expect(&Token::RParen)?;
1639 return Ok(Expr::Cast(Box::new(inner), cast_type));
1640 }
1641 }
1642 if let Token::DotIdent(field) = self.peek().clone() {
1646 self.advance();
1647 return Ok(Expr::QualifiedField {
1648 qualifier: name,
1649 field,
1650 });
1651 }
1652 Ok(Expr::Field(name))
1653 }
1654 Token::RowNumber | Token::Rank | Token::DenseRank => {
1656 let wfunc = match self.advance() {
1657 Token::RowNumber => WindowFunc::RowNumber,
1658 Token::Rank => WindowFunc::Rank,
1659 Token::DenseRank => WindowFunc::DenseRank,
1660 _ => {
1661 return Err(ParseError::Syntax {
1662 message: "unexpected window function token".into(),
1663 })
1664 }
1665 };
1666 self.expect(&Token::LParen)?;
1667 self.expect(&Token::RParen)?;
1668 let (partition_by, order_by) = self.parse_over_clause()?;
1669 Ok(Expr::Window {
1670 function: wfunc,
1671 args: vec![],
1672 mode: AggregateMode::Symmetric,
1673 partition_by,
1674 order_by,
1675 })
1676 }
1677 Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
1681 let mut func = match self.advance() {
1682 Token::Count => AggFunc::Count,
1683 Token::Avg => AggFunc::Avg,
1684 Token::Sum => AggFunc::Sum,
1685 Token::Min => AggFunc::Min,
1686 Token::Max => AggFunc::Max,
1687 _ => {
1688 return Err(ParseError::Syntax {
1689 message: "unexpected aggregate token".into(),
1690 })
1691 }
1692 };
1693 self.expect(&Token::LParen)?;
1694 let mode = if *self.peek() == Token::Raw {
1695 self.advance();
1696 AggregateMode::Raw
1697 } else {
1698 AggregateMode::Symmetric
1699 };
1700 if func == AggFunc::Count && *self.peek() == Token::Star {
1702 self.advance();
1703 self.expect(&Token::RParen)?;
1704 if *self.peek() == Token::Over {
1706 let (partition_by, order_by) = self.parse_over_clause()?;
1707 return Ok(Expr::Window {
1708 function: WindowFunc::Count,
1709 args: vec![Expr::Field("*".into())],
1710 mode,
1711 partition_by,
1712 order_by,
1713 });
1714 }
1715 return Ok(Expr::FunctionCall(
1716 AggFunc::Count,
1717 Box::new(Expr::Field("*".into())),
1718 mode,
1719 ));
1720 }
1721 if func == AggFunc::Count && *self.peek() == Token::Distinct {
1723 self.advance();
1724 func = AggFunc::CountDistinct;
1725 }
1726 let inner = self.parse_expr()?;
1727 self.expect(&Token::RParen)?;
1728 if *self.peek() == Token::Over {
1730 let wfunc = match func {
1731 AggFunc::Count => WindowFunc::Count,
1732 AggFunc::Avg => WindowFunc::Avg,
1733 AggFunc::Sum => WindowFunc::Sum,
1734 AggFunc::Min => WindowFunc::Min,
1735 AggFunc::Max => WindowFunc::Max,
1736 _ => {
1737 return Err(ParseError::Unsupported {
1738 feature: "count(distinct ...) over (...) is not supported".into(),
1739 })
1740 }
1741 };
1742 let (partition_by, order_by) = self.parse_over_clause()?;
1743 return Ok(Expr::Window {
1744 function: wfunc,
1745 args: vec![inner],
1746 mode,
1747 partition_by,
1748 order_by,
1749 });
1750 }
1751 Ok(Expr::FunctionCall(func, Box::new(inner), mode))
1752 }
1753 Token::Upper
1754 | Token::Lower
1755 | Token::Length
1756 | Token::Trim
1757 | Token::Substring
1758 | Token::Concat
1759 | Token::Abs
1760 | Token::Round
1761 | Token::Ceil
1762 | Token::Floor
1763 | Token::Sqrt
1764 | Token::Pow
1765 | Token::Now
1766 | Token::Extract
1767 | Token::DateAdd
1768 | Token::DateDiff
1769 | Token::JsonType
1770 | Token::JsonText => {
1771 let tok = self.advance();
1772 let func = token_to_scalar_fn(&tok);
1773 self.expect(&Token::LParen)?;
1774 let mut args = Vec::new();
1775 while !matches!(self.peek(), Token::RParen | Token::Eof) {
1776 args.push(self.parse_expr()?);
1777 if *self.peek() == Token::Comma {
1778 self.advance();
1779 }
1780 }
1781 self.expect(&Token::RParen)?;
1782 Ok(Expr::ScalarFunc(func, args))
1783 }
1784 Token::Cast => {
1785 self.advance();
1786 self.expect(&Token::LParen)?;
1787 let inner = self.parse_expr()?;
1788 self.expect(&Token::Comma)?;
1789 let cast_type = self.parse_cast_type()?;
1790 self.expect(&Token::RParen)?;
1791 Ok(Expr::Cast(Box::new(inner), cast_type))
1792 }
1793 Token::Case => {
1794 self.advance();
1795 let mut whens = Vec::new();
1796 while *self.peek() == Token::When {
1797 self.advance();
1798 let condition = self.parse_expr()?;
1799 self.expect(&Token::Then)?;
1800 let result = self.parse_expr()?;
1801 whens.push((Box::new(condition), Box::new(result)));
1802 }
1803 let else_expr = if *self.peek() == Token::Else {
1804 self.advance();
1805 Some(Box::new(self.parse_expr()?))
1806 } else {
1807 None
1808 };
1809 self.expect(&Token::End)?;
1810 Ok(Expr::Case { whens, else_expr })
1811 }
1812 t => Err(ParseError::Syntax {
1813 message: format!("unexpected token in expression: {}", t.display_name()),
1814 }),
1815 }
1816 }
1817
1818 fn parse_alter_table(&mut self) -> Result<Statement, ParseError> {
1821 self.expect(&Token::Alter)?;
1822 let table = match self.advance() {
1823 Token::Ident(name) => name,
1824 t => {
1825 return Err(ParseError::UnexpectedToken {
1826 expected: "table name after alter".into(),
1827 got: t.display_name(),
1828 })
1829 }
1830 };
1831 match self.peek() {
1832 Token::Add => {
1833 self.advance();
1834 if *self.peek() == Token::Index {
1836 self.advance();
1837 let if_not_exists = self.parse_optional_if_not_exists();
1838 let target = self.parse_index_target("add index")?;
1839 return Ok(Statement::AlterTable(AlterTableExpr {
1840 table,
1841 action: AlterAction::AddIndex {
1842 target,
1843 if_not_exists,
1844 },
1845 }));
1846 }
1847 if *self.peek() == Token::Unique {
1849 self.advance();
1850 let if_not_exists = self.parse_optional_if_not_exists();
1851 let target = self.parse_index_target("add unique")?;
1852 return Ok(Statement::AlterTable(AlterTableExpr {
1853 table,
1854 action: AlterAction::AddUnique {
1855 target,
1856 if_not_exists,
1857 },
1858 }));
1859 }
1860 if *self.peek() == Token::Column {
1862 self.advance();
1863 }
1864 let required = if *self.peek() == Token::Required {
1865 self.advance();
1866 true
1867 } else {
1868 false
1869 };
1870 let name = self.expect_named_ident("column name")?;
1871 self.expect(&Token::Colon)?;
1872 let type_name = match self.advance() {
1873 Token::Ident(n) => n,
1874 t => {
1875 return Err(ParseError::UnexpectedToken {
1876 expected: "type name".into(),
1877 got: t.display_name(),
1878 })
1879 }
1880 };
1881 Ok(Statement::AlterTable(AlterTableExpr {
1882 table,
1883 action: AlterAction::AddColumn {
1884 name,
1885 type_name,
1886 required,
1887 },
1888 }))
1889 }
1890 Token::Drop => {
1891 self.advance();
1892 if *self.peek() == Token::Index {
1893 self.advance();
1894 let if_exists = self.parse_optional_if_exists();
1895 let target = self.parse_index_target("drop index")?;
1896 return Ok(Statement::AlterTable(AlterTableExpr {
1897 table,
1898 action: AlterAction::DropIndex { target, if_exists },
1899 }));
1900 }
1901 if *self.peek() == Token::Column {
1903 self.advance();
1904 }
1905 let if_exists = self.parse_optional_if_exists();
1906 let name = self.expect_named_ident("column name")?;
1907 Ok(Statement::AlterTable(AlterTableExpr {
1908 table,
1909 action: AlterAction::DropColumn { name, if_exists },
1910 }))
1911 }
1912 t => Err(ParseError::UnexpectedToken {
1913 expected: "add or drop after alter <table>".into(),
1914 got: t.display_name(),
1915 }),
1916 }
1917 }
1918
1919 fn parse_index_target(&mut self, action: &str) -> Result<IndexTarget, ParseError> {
1924 match self.peek() {
1925 Token::DotIdent(_) => {
1926 if matches!(self.tokens.get(self.pos + 1), Some(Token::Arrow)) {
1927 return Err(ParseError::Syntax {
1928 message: format!(
1929 "JSON path index targets must be parenthesized after {action}; use `(.data->key)`"
1930 ),
1931 });
1932 }
1933 let Token::DotIdent(column) = self.advance() else {
1934 unreachable!("guarded by DotIdent match")
1935 };
1936 Ok(IndexTarget::Column(column))
1937 }
1938 Token::LParen => {
1939 self.advance();
1940 let expr = self.parse_expr().map_err(|error| match error {
1941 ParseError::NestingDepthExceeded { .. } => error,
1942 _ => ParseError::Syntax {
1943 message: format!(
1944 "invalid expression index target after {action}: expected an unqualified JSON path like `(.data->key)`"
1945 ),
1946 },
1947 })?;
1948 if *self.peek() != Token::RParen {
1949 return Err(ParseError::Syntax {
1950 message: format!(
1951 "invalid expression index target after {action}: only a direct JSON path is supported"
1952 ),
1953 });
1954 }
1955 self.advance();
1956
1957 match JsonPathIdentityV1::from_expr(&expr) {
1958 Some(identity) => identity.bind_table_local(None).map(IndexTarget::JsonPath).ok_or_else(|| {
1959 ParseError::Syntax {
1960 message: format!(
1961 "qualified JSON paths are not valid index targets after {action}; use an unqualified table-local path like `(.data->key)`"
1962 ),
1963 }
1964 }),
1965 None => match expr {
1966 Expr::Field(_) => Err(ParseError::Syntax {
1967 message: format!(
1968 "invalid expression index target after {action}: parentheses are reserved for a direct JSON path like `(.data->key)`; use `.column` for a stored column"
1969 ),
1970 }),
1971 Expr::QualifiedField { .. } => Err(ParseError::Syntax {
1972 message: format!(
1973 "qualified references are not valid index targets after {action}; use a table-local `.column` or `(.data->key)`"
1974 ),
1975 }),
1976 _ => Err(ParseError::Syntax {
1977 message: format!(
1978 "invalid expression index target after {action}: only a direct JSON path is supported"
1979 ),
1980 }),
1981 },
1982 }
1983 }
1984 token => Err(ParseError::UnexpectedToken {
1985 expected: format!(".<column> or parenthesized JSON path after {action}"),
1986 got: token.display_name(),
1987 }),
1988 }
1989 }
1990
1991 fn parse_drop_or_drop_view(&mut self) -> Result<Statement, ParseError> {
1993 self.expect(&Token::Drop)?;
1994 if *self.peek() == Token::View {
1995 self.advance(); let if_exists = self.parse_optional_if_exists();
1997 let name = match self.advance() {
1998 Token::Ident(name) => name,
1999 t => {
2000 return Err(ParseError::UnexpectedToken {
2001 expected: "view name after drop view".into(),
2002 got: t.display_name(),
2003 })
2004 }
2005 };
2006 return Ok(Statement::DropView(DropViewExpr { name, if_exists }));
2007 }
2008 let if_exists = self.parse_optional_if_exists();
2009 let table = match self.advance() {
2010 Token::Ident(name) => name,
2011 t => {
2012 return Err(ParseError::UnexpectedToken {
2013 expected: "table name after drop".into(),
2014 got: t.display_name(),
2015 })
2016 }
2017 };
2018 Ok(Statement::DropTable(DropTableExpr { table, if_exists }))
2019 }
2020
2021 fn parse_create_view(&mut self) -> Result<Statement, ParseError> {
2026 self.expect(&Token::Materialized)?;
2027 let name = match self.advance() {
2028 Token::Ident(name) => name,
2029 t => {
2030 return Err(ParseError::UnexpectedToken {
2031 expected: "view name after materialize".into(),
2032 got: t.display_name(),
2033 })
2034 }
2035 };
2036 self.expect(&Token::As)?;
2037 let query_start = self.pos;
2039 let source = match self.advance() {
2040 Token::Ident(s) => s,
2041 t => {
2042 return Err(ParseError::UnexpectedToken {
2043 expected: "source table name".into(),
2044 got: t.display_name(),
2045 })
2046 }
2047 };
2048 let query = self.parse_query_tail(source)?;
2049 let query_text = tokens_to_text(&self.tokens[query_start..self.pos]);
2051 Ok(Statement::CreateView(CreateViewExpr {
2052 name,
2053 query,
2054 query_text,
2055 }))
2056 }
2057
2058 fn maybe_parse_union(&mut self, left: Statement) -> Result<Statement, ParseError> {
2061 if *self.peek() != Token::Union {
2062 return Ok(left);
2063 }
2064 if !matches!(left, Statement::Query(_) | Statement::Union(_)) {
2065 return Err(ParseError::Syntax {
2066 message: "UNION requires a query on the left side".into(),
2067 });
2068 }
2069 self.advance(); let all = if let Token::Ident(s) = self.peek() {
2071 if s == "all" {
2072 self.advance();
2073 true
2074 } else {
2075 false
2076 }
2077 } else {
2078 false
2079 };
2080 let right = self.parse_single_query()?;
2082 let union = Statement::Union(UnionExpr {
2083 left: Box::new(left),
2084 right: Box::new(right),
2085 all,
2086 });
2087 self.maybe_parse_union(union)
2089 }
2090
2091 fn parse_single_query(&mut self) -> Result<Statement, ParseError> {
2093 match self.peek() {
2094 Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
2095 self.parse_aggregate_query()
2096 }
2097 Token::Ident(_) => self.parse_query_or_mutation(),
2098 _ => Err(ParseError::Syntax {
2099 message: format!(
2100 "expected query after UNION, got {}",
2101 self.peek().display_name()
2102 ),
2103 }),
2104 }
2105 }
2106
2107 fn parse_refresh_view(&mut self) -> Result<Statement, ParseError> {
2109 self.expect(&Token::Refresh)?;
2110 let name = match self.advance() {
2111 Token::Ident(name) => name,
2112 t => {
2113 return Err(ParseError::UnexpectedToken {
2114 expected: "view name after refresh".into(),
2115 got: t.display_name(),
2116 })
2117 }
2118 };
2119 Ok(Statement::RefreshView(RefreshViewExpr { name }))
2120 }
2121
2122 fn parse_create_type(&mut self) -> Result<Statement, ParseError> {
2123 self.expect(&Token::Type)?;
2124 let name = self.expect_named_ident("type name")?;
2125 let if_not_exists = self.parse_optional_if_not_exists();
2126 self.expect(&Token::LBrace)?;
2127 let mut fields = Vec::new();
2128 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
2129 let (mut required, mut unique, mut auto) = (false, false, false);
2134 loop {
2135 let is_modifier =
2136 matches!(self.peek(), Token::Required | Token::Unique | Token::Auto)
2137 && !matches!(self.tokens.get(self.pos + 1), Some(Token::Colon));
2138 if !is_modifier {
2139 break;
2140 }
2141 match self.advance() {
2142 Token::Required => required = true,
2143 Token::Unique => unique = true,
2144 Token::Auto => auto = true,
2145 _ => unreachable!("guarded by is_modifier"),
2146 }
2147 }
2148 let field_name = self.expect_named_ident("field name")?;
2149 self.expect(&Token::Colon)?;
2150 let type_name = match self.advance() {
2151 Token::Ident(n) => n,
2152 t => {
2153 return Err(ParseError::UnexpectedToken {
2154 expected: "type name".into(),
2155 got: t.display_name(),
2156 })
2157 }
2158 };
2159 let default = if *self.peek() == Token::Default {
2162 self.advance();
2163 Some(self.parse_default_literal()?)
2164 } else {
2165 None
2166 };
2167 fields.push(FieldDef {
2168 name: field_name,
2169 type_name,
2170 required,
2171 unique,
2172 default,
2173 auto,
2174 });
2175 if *self.peek() == Token::Comma {
2176 self.advance();
2177 }
2178 }
2179 self.expect(&Token::RBrace)?;
2180 Ok(Statement::CreateType(CreateTypeExpr {
2181 name,
2182 fields,
2183 if_not_exists,
2184 }))
2185 }
2186
2187 fn parse_schema(&mut self) -> Result<Statement, ParseError> {
2190 self.expect(&Token::Schema)?;
2191 if let Token::Ident(_) = self.peek() {
2192 let table = self.expect_named_ident("type name")?;
2193 return Ok(Statement::Describe(table));
2194 }
2195 Ok(Statement::ListTypes)
2196 }
2197
2198 fn parse_describe(&mut self) -> Result<Statement, ParseError> {
2200 self.expect(&Token::Describe)?;
2201 let table = self.expect_named_ident("type name")?;
2202 Ok(Statement::Describe(table))
2203 }
2204
2205 fn parse_default_literal(&mut self) -> Result<Literal, ParseError> {
2209 match self.advance() {
2210 Token::IntLit(v) => Ok(Literal::Int(v)),
2211 Token::FloatLit(v) => Ok(Literal::Float(v)),
2212 Token::StringLit(v) => Ok(Literal::String(v)),
2213 Token::BoolLit(v) => Ok(Literal::Bool(v)),
2214 t => Err(ParseError::UnexpectedToken {
2215 expected: "literal default value".into(),
2216 got: t.display_name(),
2217 }),
2218 }
2219 }
2220}
2221
2222fn tokens_to_text(tokens: &[Token]) -> String {
2226 let mut out = String::with_capacity(64);
2227 for tok in tokens {
2228 if !out.is_empty() && !matches!(tok, Token::Eof) {
2229 out.push(' ');
2230 }
2231 match tok {
2232 Token::Ident(s) => out.push_str(s),
2233 Token::DotIdent(s) => {
2234 out.push('.');
2235 out.push_str(s);
2236 }
2237 Token::IntLit(v) => out.push_str(&v.to_string()),
2238 Token::FloatLit(v) => out.push_str(&v.to_string()),
2239 Token::StringLit(s) => {
2240 out.push('"');
2241 out.push_str(s);
2242 out.push('"');
2243 }
2244 Token::BoolLit(v) => out.push_str(if *v { "true" } else { "false" }),
2245 Token::Param(s) => {
2246 out.push('$');
2247 out.push_str(s);
2248 }
2249 Token::Type => out.push_str("type"),
2250 Token::Filter => out.push_str("filter"),
2251 Token::Order => out.push_str("order"),
2252 Token::Limit => out.push_str("limit"),
2253 Token::Offset => out.push_str("offset"),
2254 Token::Insert => out.push_str("insert"),
2255 Token::Update => out.push_str("update"),
2256 Token::Delete => out.push_str("delete"),
2257 Token::Upsert => out.push_str("upsert"),
2258 Token::Returning => out.push_str("returning"),
2259 Token::Conflict => out.push_str("conflict"),
2260 Token::Select => out.push_str("select"),
2261 Token::Required => out.push_str("required"),
2262 Token::Default => out.push_str("default"),
2263 Token::Auto => out.push_str("auto"),
2264 Token::Multi => out.push_str("multi"),
2265 Token::Link => out.push_str("link"),
2266 Token::Index => out.push_str("index"),
2267 Token::Unique => out.push_str("unique"),
2268 Token::On => out.push_str("on"),
2269 Token::Asc => out.push_str("asc"),
2270 Token::Desc => out.push_str("desc"),
2271 Token::And => out.push_str("and"),
2272 Token::Or => out.push_str("or"),
2273 Token::Not => out.push_str("not"),
2274 Token::Exists => out.push_str("exists"),
2275 Token::Let => out.push_str("let"),
2276 Token::As => out.push_str("as"),
2277 Token::Match => out.push_str("match"),
2278 Token::Group => out.push_str("group"),
2279 Token::Join => out.push_str("join"),
2280 Token::Inner => out.push_str("inner"),
2281 Token::LeftKw => out.push_str("left"),
2282 Token::RightKw => out.push_str("right"),
2283 Token::Outer => out.push_str("outer"),
2284 Token::Cross => out.push_str("cross"),
2285 Token::Transaction => out.push_str("transaction"),
2286 Token::Begin => out.push_str("begin"),
2287 Token::Commit => out.push_str("commit"),
2288 Token::Rollback => out.push_str("rollback"),
2289 Token::View => out.push_str("view"),
2290 Token::Materialized => out.push_str("materialized"),
2291 Token::Refresh => out.push_str("refresh"),
2292 Token::Union => out.push_str("union"),
2293 Token::Having => out.push_str("having"),
2294 Token::Distinct => out.push_str("distinct"),
2295 Token::In => out.push_str("in"),
2296 Token::Between => out.push_str("between"),
2297 Token::Like => out.push_str("like"),
2298 Token::Count => out.push_str("count"),
2299 Token::Avg => out.push_str("avg"),
2300 Token::Sum => out.push_str("sum"),
2301 Token::Raw => out.push_str("raw"),
2302 Token::Min => out.push_str("min"),
2303 Token::Max => out.push_str("max"),
2304 Token::Is => out.push_str("is"),
2305 Token::Null => out.push_str("null"),
2306 Token::Upper => out.push_str("upper"),
2307 Token::Lower => out.push_str("lower"),
2308 Token::Length => out.push_str("length"),
2309 Token::Trim => out.push_str("trim"),
2310 Token::Substring => out.push_str("substring"),
2311 Token::Concat => out.push_str("concat"),
2312 Token::Abs => out.push_str("abs"),
2313 Token::Round => out.push_str("round"),
2314 Token::Ceil => out.push_str("ceil"),
2315 Token::Floor => out.push_str("floor"),
2316 Token::Sqrt => out.push_str("sqrt"),
2317 Token::Pow => out.push_str("pow"),
2318 Token::Now => out.push_str("now"),
2319 Token::Extract => out.push_str("extract"),
2320 Token::DateAdd => out.push_str("date_add"),
2321 Token::DateDiff => out.push_str("date_diff"),
2322 Token::JsonType => out.push_str("json_type"),
2323 Token::JsonText => out.push_str("json_text"),
2324 Token::Cast => out.push_str("cast"),
2325 Token::Case => out.push_str("case"),
2326 Token::When => out.push_str("when"),
2327 Token::Then => out.push_str("then"),
2328 Token::Else => out.push_str("else"),
2329 Token::End => out.push_str("end"),
2330 Token::Over => out.push_str("over"),
2331 Token::Partition => out.push_str("partition"),
2332 Token::RowNumber => out.push_str("row_number"),
2333 Token::Rank => out.push_str("rank"),
2334 Token::DenseRank => out.push_str("dense_rank"),
2335 Token::Alter => out.push_str("alter"),
2336 Token::Drop => out.push_str("drop"),
2337 Token::Add => out.push_str("add"),
2338 Token::Column => out.push_str("column"),
2339 Token::Eq => out.push('='),
2340 Token::Neq => out.push_str("!="),
2341 Token::Lt => out.push('<'),
2342 Token::Gt => out.push('>'),
2343 Token::Lte => out.push_str("<="),
2344 Token::Gte => out.push_str(">="),
2345 Token::Assign => out.push_str(":="),
2346 Token::Arrow => out.push_str("->"),
2347 Token::Pipe => out.push('|'),
2348 Token::Coalesce => out.push_str("??"),
2349 Token::Plus => out.push('+'),
2350 Token::Minus => out.push('-'),
2351 Token::Star => out.push('*'),
2352 Token::Slash => out.push('/'),
2353 Token::LBrace => out.push('{'),
2354 Token::RBrace => out.push('}'),
2355 Token::LParen => out.push('('),
2356 Token::RParen => out.push(')'),
2357 Token::Comma => out.push(','),
2358 Token::Colon => out.push(':'),
2359 Token::Dot => out.push('.'),
2360 Token::Explain => out.push_str("explain"),
2361 Token::Schema => out.push_str("schema"),
2362 Token::Describe => out.push_str("describe"),
2363 Token::Eof => {}
2364 }
2365 }
2366 out
2367}
2368
2369#[cfg(test)]
2370mod tests {
2371 use super::*;
2372 #[test]
2373 fn test_parse_simple_query() {
2374 let stmt = parse("User").unwrap();
2375 match stmt {
2376 Statement::Query(q) => {
2377 assert_eq!(q.source, "User");
2378 assert!(q.filter.is_none());
2379 assert!(q.projection.is_none());
2380 }
2381 _ => panic!("expected query"),
2382 }
2383 }
2384
2385 #[test]
2386 fn test_parse_filter() {
2387 let stmt = parse("User filter .age > 30").unwrap();
2388 match stmt {
2389 Statement::Query(q) => {
2390 assert_eq!(q.source, "User");
2391 assert!(q.filter.is_some());
2392 }
2393 _ => panic!("expected query"),
2394 }
2395 }
2396
2397 #[test]
2398 fn test_parse_projection() {
2399 let stmt = parse("User { name, email }").unwrap();
2400 match stmt {
2401 Statement::Query(q) => {
2402 let proj = q.projection.unwrap();
2403 assert_eq!(proj.len(), 2);
2404 }
2405 _ => panic!("expected query"),
2406 }
2407 }
2408
2409 #[test]
2410 fn test_parse_filter_order_limit() {
2411 let stmt = parse("User filter .age > 30 order .name desc limit 10").unwrap();
2412 match stmt {
2413 Statement::Query(q) => {
2414 assert!(q.filter.is_some());
2415 let order = q.order.unwrap();
2416 assert_eq!(order.keys.len(), 1);
2417 assert_eq!(order.keys[0].expr, Expr::Field("name".into()));
2418 assert!(order.keys[0].descending);
2419 assert!(q.limit.is_some());
2420 }
2421 _ => panic!("expected query"),
2422 }
2423 }
2424
2425 #[test]
2426 fn test_parse_insert() {
2427 let stmt = parse(r#"insert User { name := "Alice", age := 30 }"#).unwrap();
2428 match stmt {
2429 Statement::Insert(ins) => {
2430 assert_eq!(ins.target, "User");
2431 assert_eq!(ins.rows.len(), 1);
2432 assert_eq!(ins.rows[0].len(), 2);
2433 assert_eq!(ins.rows[0][0].field, "name");
2434 assert_eq!(ins.rows[0][1].field, "age");
2435 }
2436 _ => panic!("expected insert"),
2437 }
2438 }
2439
2440 #[test]
2441 fn test_parse_insert_multi_row() {
2442 let stmt =
2443 parse(r#"insert User { name := "Alice", age := 30 }, { name := "Bob", age := 25 }, { name := "Cy" }"#)
2444 .unwrap();
2445 match stmt {
2446 Statement::Insert(ins) => {
2447 assert_eq!(ins.target, "User");
2448 assert_eq!(ins.rows.len(), 3);
2449 assert_eq!(ins.rows[0].len(), 2);
2450 assert_eq!(ins.rows[1][0].field, "name");
2451 assert_eq!(ins.rows[2].len(), 1);
2452 assert_eq!(ins.rows[2][0].field, "name");
2453 }
2454 _ => panic!("expected insert"),
2455 }
2456 }
2457
2458 #[test]
2459 fn test_parse_update() {
2460 let stmt = parse(r#"User filter .email = "alice@ex.com" update { age := 31 }"#).unwrap();
2461 match stmt {
2462 Statement::UpdateQuery(upd) => {
2463 assert_eq!(upd.source, "User");
2464 assert!(upd.filter.is_some());
2465 assert_eq!(upd.assignments.len(), 1);
2466 assert!(!upd.returning);
2467 }
2468 _ => panic!("expected update"),
2469 }
2470 }
2471
2472 #[test]
2473 fn test_parse_update_returning() {
2474 let stmt = parse(r#"User filter .name = "Alice" update { age := 31 } returning"#).unwrap();
2475 match stmt {
2476 Statement::UpdateQuery(upd) => assert!(upd.returning),
2477 _ => panic!("expected update"),
2478 }
2479 }
2480
2481 #[test]
2482 fn test_parse_delete() {
2483 let stmt = parse("User filter .age < 18 delete").unwrap();
2484 match stmt {
2485 Statement::DeleteQuery(del) => {
2486 assert_eq!(del.source, "User");
2487 assert!(del.filter.is_some());
2488 assert!(!del.returning);
2489 }
2490 _ => panic!("expected delete"),
2491 }
2492 }
2493
2494 #[test]
2495 fn test_parse_delete_returning() {
2496 let stmt = parse("User filter .age < 18 delete returning").unwrap();
2497 match stmt {
2498 Statement::DeleteQuery(del) => assert!(del.returning),
2499 _ => panic!("expected delete"),
2500 }
2501 }
2502
2503 #[test]
2504 fn test_parse_count() {
2505 let stmt = parse("count(User)").unwrap();
2506 match stmt {
2507 Statement::Query(q) => {
2508 let agg = q.aggregation.unwrap();
2509 assert_eq!(agg.function, AggFunc::Count);
2510 assert!(q.filter.is_none());
2511 }
2512 _ => panic!("expected query with aggregation"),
2513 }
2514 }
2515
2516 #[test]
2517 fn test_parse_count_with_filter() {
2518 let stmt = parse("count(User filter .age > 30)").unwrap();
2521 match stmt {
2522 Statement::Query(q) => {
2523 assert_eq!(q.source, "User");
2524 let agg = q.aggregation.unwrap();
2525 assert_eq!(agg.function, AggFunc::Count);
2526 assert!(q.filter.is_some(), "filter should have been parsed");
2527 }
2528 _ => panic!("expected query with aggregation"),
2529 }
2530 }
2531
2532 #[test]
2533 fn test_parse_count_with_filter_and_limit() {
2534 let stmt = parse("count(User filter .age > 30 limit 100)").unwrap();
2535 match stmt {
2536 Statement::Query(q) => {
2537 assert_eq!(q.source, "User");
2538 assert!(q.filter.is_some());
2539 assert!(q.limit.is_some());
2540 assert_eq!(q.aggregation.unwrap().function, AggFunc::Count);
2541 }
2542 _ => panic!("expected query with aggregation"),
2543 }
2544 }
2545
2546 #[test]
2547 fn test_parse_create_type() {
2548 let stmt = parse("type User { required name: str, age: int }").unwrap();
2549 match stmt {
2550 Statement::CreateType(ct) => {
2551 assert_eq!(ct.name, "User");
2552 assert_eq!(ct.fields.len(), 2);
2553 assert!(ct.fields[0].required);
2554 assert!(!ct.fields[1].required);
2555 }
2556 _ => panic!("expected create type"),
2557 }
2558 }
2559
2560 #[test]
2561 fn test_parse_sum_with_field_projection() {
2562 let stmt = parse("sum(User filter .age > 30 { .age })").unwrap();
2565 match stmt {
2566 Statement::Query(q) => {
2567 let agg = q.aggregation.expect("aggregate");
2568 assert_eq!(agg.function, AggFunc::Sum);
2569 assert_eq!(agg.argument, Some(Expr::Field("age".into())));
2570 assert!(
2571 q.projection.is_none(),
2572 "projection should be lifted into agg.field"
2573 );
2574 }
2575 _ => panic!("expected query"),
2576 }
2577 }
2578
2579 #[test]
2580 fn test_parse_raw_aggregate_modes() {
2581 let Statement::Query(top_level) = parse("sum(raw User { .age })").unwrap() else {
2582 panic!("expected query");
2583 };
2584 assert_eq!(top_level.aggregation.unwrap().mode, AggregateMode::Raw);
2585
2586 let Statement::Query(grouped) = parse("User group .dept { total: sum(raw .age) }").unwrap()
2587 else {
2588 panic!("expected query");
2589 };
2590 assert!(matches!(
2591 grouped.projection.unwrap()[0].expr,
2592 Expr::FunctionCall(AggFunc::Sum, _, AggregateMode::Raw)
2593 ));
2594 }
2595
2596 #[test]
2597 fn test_parse_avg_min_max_with_field() {
2598 for (src, expected) in [
2599 ("avg(User { .age })", AggFunc::Avg),
2600 ("min(User { .age })", AggFunc::Min),
2601 ("max(User { .age })", AggFunc::Max),
2602 ] {
2603 let stmt = parse(src).unwrap();
2604 match stmt {
2605 Statement::Query(q) => {
2606 let agg = q.aggregation.unwrap();
2607 assert_eq!(agg.function, expected, "func mismatch for {src}");
2608 assert_eq!(
2609 agg.argument,
2610 Some(Expr::Field("age".into())),
2611 "field mismatch for {src}"
2612 );
2613 assert!(
2614 q.projection.is_none(),
2615 "projection should be cleared for {src}"
2616 );
2617 }
2618 _ => panic!("expected query for {src}"),
2619 }
2620 }
2621 }
2622
2623 #[test]
2624 fn test_parse_count_leaves_projection_alone() {
2625 let stmt = parse("count(User { .age })").unwrap();
2628 match stmt {
2629 Statement::Query(q) => {
2630 let agg = q.aggregation.unwrap();
2631 assert_eq!(agg.function, AggFunc::Count);
2632 assert!(agg.argument.is_none());
2633 assert!(q.projection.is_some(), "count must not eat projection");
2634 }
2635 _ => panic!("expected query"),
2636 }
2637 }
2638
2639 #[test]
2644 fn test_parse_source_alias() {
2645 let stmt = parse("User as u filter u.age > 30").unwrap();
2646 match stmt {
2647 Statement::Query(q) => {
2648 assert_eq!(q.source, "User");
2649 assert_eq!(q.alias.as_deref(), Some("u"));
2650 assert!(q.joins.is_empty());
2651 match q.filter.unwrap() {
2652 Expr::BinaryOp(l, BinOp::Gt, _) => match *l {
2653 Expr::QualifiedField { qualifier, field } => {
2654 assert_eq!(qualifier, "u");
2655 assert_eq!(field, "age");
2656 }
2657 other => panic!("expected qualified field, got {other:?}"),
2658 },
2659 other => panic!("expected >, got {other:?}"),
2660 }
2661 }
2662 _ => panic!("expected query"),
2663 }
2664 }
2665
2666 #[test]
2667 fn test_parse_inner_join_on() {
2668 let stmt = parse("User as u inner join Order as o on u.id = o.user_id").unwrap();
2669 match stmt {
2670 Statement::Query(q) => {
2671 assert_eq!(q.source, "User");
2672 assert_eq!(q.alias.as_deref(), Some("u"));
2673 assert_eq!(q.joins.len(), 1);
2674 let j = &q.joins[0];
2675 assert_eq!(j.kind, JoinKind::Inner);
2676 assert_eq!(j.source, "Order");
2677 assert_eq!(j.alias.as_deref(), Some("o"));
2678 let on = j.on.as_ref().expect("on clause");
2679 match on {
2680 Expr::BinaryOp(l, BinOp::Eq, r) => {
2681 assert!(matches!(**l, Expr::QualifiedField { .. }));
2682 assert!(matches!(**r, Expr::QualifiedField { .. }));
2683 }
2684 other => panic!("expected eq, got {other:?}"),
2685 }
2686 }
2687 _ => panic!("expected query"),
2688 }
2689 }
2690
2691 #[test]
2692 fn test_parse_bare_join_defaults_to_inner() {
2693 let stmt = parse("User join Order on User.id = Order.user_id").unwrap();
2694 match stmt {
2695 Statement::Query(q) => {
2696 assert_eq!(q.joins.len(), 1);
2697 assert_eq!(q.joins[0].kind, JoinKind::Inner);
2698 }
2699 _ => panic!("expected query"),
2700 }
2701 }
2702
2703 #[test]
2704 fn test_parse_left_outer_join() {
2705 let stmt = parse("User as u left outer join Order as o on u.id = o.user_id").unwrap();
2706 match stmt {
2707 Statement::Query(q) => {
2708 assert_eq!(q.joins.len(), 1);
2709 assert_eq!(q.joins[0].kind, JoinKind::LeftOuter);
2710 }
2711 _ => panic!("expected query"),
2712 }
2713 }
2714
2715 #[test]
2716 fn test_parse_left_join_without_outer_keyword() {
2717 let stmt = parse("User as u left join Order as o on u.id = o.user_id").unwrap();
2719 match stmt {
2720 Statement::Query(q) => {
2721 assert_eq!(q.joins[0].kind, JoinKind::LeftOuter);
2722 }
2723 _ => panic!("expected query"),
2724 }
2725 }
2726
2727 #[test]
2728 fn test_parse_right_join() {
2729 let stmt = parse("User as u right join Order as o on u.id = o.user_id").unwrap();
2730 match stmt {
2731 Statement::Query(q) => {
2732 assert_eq!(q.joins[0].kind, JoinKind::RightOuter);
2733 }
2734 _ => panic!("expected query"),
2735 }
2736 }
2737
2738 #[test]
2739 fn test_parse_cross_join_has_no_on() {
2740 let stmt = parse("User cross join Order").unwrap();
2741 match stmt {
2742 Statement::Query(q) => {
2743 assert_eq!(q.joins[0].kind, JoinKind::Cross);
2744 assert!(q.joins[0].on.is_none());
2745 }
2746 _ => panic!("expected query"),
2747 }
2748 }
2749
2750 #[test]
2751 fn test_parse_multi_join_chain() {
2752 let stmt = parse(
2753 "User as u join Order as o on u.id = o.user_id \
2754 join Product as p on o.product_id = p.id",
2755 )
2756 .unwrap();
2757 match stmt {
2758 Statement::Query(q) => {
2759 assert_eq!(q.joins.len(), 2);
2760 assert_eq!(q.joins[0].source, "Order");
2761 assert_eq!(q.joins[1].source, "Product");
2762 }
2763 _ => panic!("expected query"),
2764 }
2765 }
2766
2767 #[test]
2768 fn test_parse_join_with_filter_tail() {
2769 let stmt = parse(
2771 "User as u join Order as o on u.id = o.user_id \
2772 filter o.total > 100 order .name limit 10",
2773 )
2774 .unwrap();
2775 match stmt {
2776 Statement::Query(q) => {
2777 assert_eq!(q.joins.len(), 1);
2778 assert!(q.filter.is_some());
2779 assert!(q.order.is_some());
2780 assert!(q.limit.is_some());
2781 }
2782 _ => panic!("expected query"),
2783 }
2784 }
2785
2786 #[test]
2787 fn test_parse_join_requires_on_for_inner() {
2788 let err = parse("User join Order").unwrap_err();
2790 assert!(
2791 err.message().contains("on"),
2792 "expected on-clause error, got {:?}",
2793 err.message()
2794 );
2795 }
2796
2797 #[test]
2798 fn test_parse_update_on_joined_query_errors() {
2799 let err =
2802 parse("User as u join Order as o on u.id = o.user_id update { age := 1 }").unwrap_err();
2803 assert!(err.message().contains("update"));
2804 }
2805
2806 #[test]
2807 fn test_parse_delete_on_joined_query_errors() {
2808 let err = parse("User as u join Order as o on u.id = o.user_id delete").unwrap_err();
2809 assert!(err.message().contains("delete"));
2810 }
2811
2812 #[test]
2815 fn test_parse_distinct() {
2816 let stmt = parse("User distinct { .name }").unwrap();
2817 match stmt {
2818 Statement::Query(q) => {
2819 assert!(q.distinct);
2820 assert!(q.projection.is_some());
2821 }
2822 _ => panic!("expected query"),
2823 }
2824 }
2825
2826 #[test]
2827 fn test_parse_in_list() {
2828 let stmt = parse(r#"User filter .name in ("Alice", "Bob")"#).unwrap();
2829 match stmt {
2830 Statement::Query(q) => match q.filter.unwrap() {
2831 Expr::InList {
2832 expr,
2833 list,
2834 negated,
2835 } => {
2836 assert!(!negated);
2837 assert!(matches!(*expr, Expr::Field(f) if f == "name"));
2838 assert_eq!(list.len(), 2);
2839 }
2840 other => panic!("expected InList, got {other:?}"),
2841 },
2842 _ => panic!("expected query"),
2843 }
2844 }
2845
2846 #[test]
2847 fn test_parse_not_in_list() {
2848 let stmt = parse("User filter .age not in (1, 2, 3)").unwrap();
2849 match stmt {
2850 Statement::Query(q) => match q.filter.unwrap() {
2851 Expr::InList { negated, list, .. } => {
2852 assert!(negated);
2853 assert_eq!(list.len(), 3);
2854 }
2855 other => panic!("expected InList, got {other:?}"),
2856 },
2857 _ => panic!("expected query"),
2858 }
2859 }
2860
2861 #[test]
2862 fn test_parse_between() {
2863 let stmt = parse("User filter .age between 10 and 20").unwrap();
2865 match stmt {
2866 Statement::Query(q) => {
2867 match q.filter.unwrap() {
2868 Expr::BinaryOp(_, BinOp::And, _) => {} other => panic!("expected And (desugared between), got {other:?}"),
2870 }
2871 }
2872 _ => panic!("expected query"),
2873 }
2874 }
2875
2876 #[test]
2877 fn test_parse_not_between() {
2878 let stmt = parse("User filter .age not between 10 and 20").unwrap();
2880 match stmt {
2881 Statement::Query(q) => {
2882 match q.filter.unwrap() {
2883 Expr::BinaryOp(_, BinOp::Or, _) => {} other => panic!("expected Or (desugared not between), got {other:?}"),
2885 }
2886 }
2887 _ => panic!("expected query"),
2888 }
2889 }
2890
2891 #[test]
2892 fn test_parse_like() {
2893 let stmt = parse(r#"User filter .name like "A%""#).unwrap();
2894 match stmt {
2895 Statement::Query(q) => match q.filter.unwrap() {
2896 Expr::BinaryOp(l, BinOp::Like, r) => {
2897 assert!(matches!(*l, Expr::Field(f) if f == "name"));
2898 assert!(matches!(*r, Expr::Literal(Literal::String(s)) if s == "A%"));
2899 }
2900 other => panic!("expected Like, got {other:?}"),
2901 },
2902 _ => panic!("expected query"),
2903 }
2904 }
2905
2906 #[test]
2907 fn test_parse_not_like() {
2908 let stmt = parse(r#"User filter .name not like "A%""#).unwrap();
2909 match stmt {
2910 Statement::Query(q) => match q.filter.unwrap() {
2911 Expr::UnaryOp(UnaryOp::Not, inner) => {
2912 assert!(matches!(*inner, Expr::BinaryOp(_, BinOp::Like, _)));
2913 }
2914 other => panic!("expected Not(Like), got {other:?}"),
2915 },
2916 _ => panic!("expected query"),
2917 }
2918 }
2919
2920 #[test]
2923 fn test_parse_group_by_single_key() {
2924 let stmt = parse("User group .status { .status, n: count(.name) }").unwrap();
2925 match stmt {
2926 Statement::Query(q) => {
2927 let gb = q.group_by.unwrap();
2928 assert_eq!(
2929 gb.keys,
2930 vec![GroupKey {
2931 expr: Expr::Field("status".into()),
2932 output_name: "status".into(),
2933 }]
2934 );
2935 assert!(gb.having.is_none());
2936 let proj = q.projection.unwrap();
2937 assert_eq!(proj.len(), 2);
2938 assert!(matches!(
2939 &proj[1].expr,
2940 Expr::FunctionCall(AggFunc::Count, _, _)
2941 ));
2942 assert_eq!(proj[1].alias.as_deref(), Some("n"));
2943 }
2944 _ => panic!("expected query"),
2945 }
2946 }
2947
2948 #[test]
2949 fn test_parse_group_by_multi_key() {
2950 let stmt = parse("User group .status, .age { .status, .age }").unwrap();
2951 match stmt {
2952 Statement::Query(q) => {
2953 let gb = q.group_by.unwrap();
2954 assert_eq!(
2955 gb.keys,
2956 vec![
2957 GroupKey {
2958 expr: Expr::Field("status".into()),
2959 output_name: "status".into(),
2960 },
2961 GroupKey {
2962 expr: Expr::Field("age".into()),
2963 output_name: "age".into(),
2964 }
2965 ]
2966 );
2967 }
2968 _ => panic!("expected query"),
2969 }
2970 }
2971
2972 #[test]
2973 fn test_parse_group_by_having() {
2974 let stmt = parse("User group .status having count(.name) > 1 { .status }").unwrap();
2975 match stmt {
2976 Statement::Query(q) => {
2977 let gb = q.group_by.unwrap();
2978 assert_eq!(
2979 gb.keys,
2980 vec![GroupKey {
2981 expr: Expr::Field("status".into()),
2982 output_name: "status".into(),
2983 }]
2984 );
2985 assert!(gb.having.is_some());
2986 match gb.having.unwrap() {
2988 Expr::BinaryOp(l, BinOp::Gt, _) => {
2989 assert!(matches!(*l, Expr::FunctionCall(AggFunc::Count, _, _)));
2990 }
2991 other => panic!("expected BinaryOp, got {other:?}"),
2992 }
2993 }
2994 _ => panic!("expected query"),
2995 }
2996 }
2997
2998 #[test]
2999 fn test_parse_aggregate_in_projection() {
3000 let stmt = parse("User group .status { .status, count(.name), sum(.age) }").unwrap();
3002 match stmt {
3003 Statement::Query(q) => {
3004 let proj = q.projection.unwrap();
3005 assert_eq!(proj.len(), 3);
3006 assert!(matches!(
3007 &proj[1].expr,
3008 Expr::FunctionCall(AggFunc::Count, _, _)
3009 ));
3010 assert!(matches!(
3011 &proj[2].expr,
3012 Expr::FunctionCall(AggFunc::Sum, _, _)
3013 ));
3014 }
3015 _ => panic!("expected query"),
3016 }
3017 }
3018
3019 #[test]
3020 fn test_parse_aggregate_in_aliased_projection() {
3021 let stmt = parse("User group .status { .status, total: count(.name), average: avg(.age) }")
3022 .unwrap();
3023 match stmt {
3024 Statement::Query(q) => {
3025 let proj = q.projection.unwrap();
3026 assert_eq!(proj[1].alias.as_deref(), Some("total"));
3027 assert!(matches!(
3028 &proj[1].expr,
3029 Expr::FunctionCall(AggFunc::Count, _, _)
3030 ));
3031 assert_eq!(proj[2].alias.as_deref(), Some("average"));
3032 assert!(matches!(
3033 &proj[2].expr,
3034 Expr::FunctionCall(AggFunc::Avg, _, _)
3035 ));
3036 }
3037 _ => panic!("expected query"),
3038 }
3039 }
3040
3041 #[test]
3044 fn test_parse_is_null() {
3045 let stmt = parse("User filter .age is null").unwrap();
3046 match stmt {
3047 Statement::Query(q) => {
3048 let filter = q.filter.unwrap();
3049 assert_eq!(
3050 filter,
3051 Expr::UnaryOp(UnaryOp::IsNull, Box::new(Expr::Field("age".into())))
3052 );
3053 }
3054 _ => panic!("expected query"),
3055 }
3056 }
3057
3058 #[test]
3059 fn test_parse_is_not_null() {
3060 let stmt = parse("User filter .age is not null").unwrap();
3061 match stmt {
3062 Statement::Query(q) => {
3063 let filter = q.filter.unwrap();
3064 assert_eq!(
3065 filter,
3066 Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(Expr::Field("age".into())))
3067 );
3068 }
3069 _ => panic!("expected query"),
3070 }
3071 }
3072
3073 #[test]
3074 fn test_parse_eq_null_desugars_to_is_null() {
3075 let stmt = parse("User filter .age = null").unwrap();
3076 match stmt {
3077 Statement::Query(q) => {
3078 let filter = q.filter.unwrap();
3079 assert_eq!(
3080 filter,
3081 Expr::UnaryOp(UnaryOp::IsNull, Box::new(Expr::Field("age".into())))
3082 );
3083 }
3084 _ => panic!("expected query"),
3085 }
3086 }
3087
3088 #[test]
3089 fn test_parse_neq_null_desugars_to_is_not_null() {
3090 let stmt = parse("User filter .age != null").unwrap();
3091 match stmt {
3092 Statement::Query(q) => {
3093 let filter = q.filter.unwrap();
3094 assert_eq!(
3095 filter,
3096 Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(Expr::Field("age".into())))
3097 );
3098 }
3099 _ => panic!("expected query"),
3100 }
3101 }
3102
3103 #[test]
3104 fn test_parse_null_comparisons_parse_ok() {
3105 assert!(parse("User filter .age < null").is_ok());
3109 assert!(parse("User filter .age >= null").is_ok());
3110 }
3111
3112 #[test]
3113 fn test_parse_count_star_expr() {
3114 let stmt = parse("User filter count(*) > 0").unwrap();
3115 match stmt {
3116 Statement::Query(q) => {
3117 let filter = q.filter.unwrap();
3118 match filter {
3119 Expr::BinaryOp(left, BinOp::Gt, _) => {
3120 assert_eq!(
3121 *left,
3122 Expr::FunctionCall(
3123 AggFunc::Count,
3124 Box::new(Expr::Field("*".into())),
3125 AggregateMode::Symmetric,
3126 )
3127 );
3128 }
3129 _ => panic!("expected comparison"),
3130 }
3131 }
3132 _ => panic!("expected query"),
3133 }
3134 }
3135
3136 #[test]
3139 fn test_parse_upper_in_filter() {
3140 let stmt = parse(r#"User filter upper(.name) = "ALICE""#).unwrap();
3141 match stmt {
3142 Statement::Query(q) => {
3143 let f = q.filter.unwrap();
3144 match f {
3145 Expr::BinaryOp(left, BinOp::Eq, _right) => {
3146 assert!(matches!(*left, Expr::ScalarFunc(ScalarFn::Upper, _)));
3147 }
3148 _ => panic!("expected binary op with upper"),
3149 }
3150 }
3151 _ => panic!("expected query"),
3152 }
3153 }
3154
3155 #[test]
3156 fn test_parse_substring() {
3157 let stmt = parse("User { sub: substring(.name, 1, 3) }").unwrap();
3158 match stmt {
3159 Statement::Query(q) => {
3160 let proj = q.projection.unwrap();
3161 match &proj[0].expr {
3162 Expr::ScalarFunc(ScalarFn::Substring, args) => {
3163 assert_eq!(args.len(), 3);
3164 }
3165 other => panic!("expected ScalarFunc Substring, got {other:?}"),
3166 }
3167 }
3168 _ => panic!("expected query"),
3169 }
3170 }
3171
3172 #[test]
3173 fn test_parse_concat() {
3174 let stmt = parse(r#"User { full: concat(.name, " - ", .email) }"#).unwrap();
3175 match stmt {
3176 Statement::Query(q) => {
3177 let proj = q.projection.unwrap();
3178 match &proj[0].expr {
3179 Expr::ScalarFunc(ScalarFn::Concat, args) => {
3180 assert_eq!(args.len(), 3);
3181 }
3182 other => panic!("expected ScalarFunc Concat, got {other:?}"),
3183 }
3184 }
3185 _ => panic!("expected query"),
3186 }
3187 }
3188
3189 #[test]
3192 fn test_parse_case_single_when() {
3193 let stmt = parse(r#"User filter case when .age > 30 then true else false end"#).unwrap();
3194 match stmt {
3195 Statement::Query(q) => {
3196 let filter = q.filter.unwrap();
3197 match filter {
3198 Expr::Case { whens, else_expr } => {
3199 assert_eq!(whens.len(), 1);
3200 assert!(else_expr.is_some());
3201 }
3202 other => panic!("expected Case expr, got {other:?}"),
3203 }
3204 }
3205 _ => panic!("expected query"),
3206 }
3207 }
3208
3209 #[test]
3210 fn test_parse_case_multiple_whens() {
3211 let stmt = parse(
3212 r#"User { label: case when .age > 30 then "senior" when .age > 20 then "adult" else "young" end }"#
3213 ).unwrap();
3214 match stmt {
3215 Statement::Query(q) => {
3216 let proj = q.projection.unwrap();
3217 match &proj[0].expr {
3218 Expr::Case { whens, else_expr } => {
3219 assert_eq!(whens.len(), 2);
3220 assert!(else_expr.is_some());
3221 }
3222 other => panic!("expected Case expr, got {other:?}"),
3223 }
3224 }
3225 _ => panic!("expected query"),
3226 }
3227 }
3228
3229 #[test]
3230 fn test_parse_case_without_else() {
3231 let stmt = parse(r#"User filter case when .age > 30 then true end"#).unwrap();
3232 match stmt {
3233 Statement::Query(q) => {
3234 let filter = q.filter.unwrap();
3235 match filter {
3236 Expr::Case { whens, else_expr } => {
3237 assert_eq!(whens.len(), 1);
3238 assert!(else_expr.is_none());
3239 }
3240 other => panic!("expected Case expr, got {other:?}"),
3241 }
3242 }
3243 _ => panic!("expected query"),
3244 }
3245 }
3246
3247 #[test]
3250 fn test_parse_mul_expr() {
3251 let stmt = parse("User filter .price * .quantity > 100").unwrap();
3252 match stmt {
3253 Statement::Query(q) => {
3254 let filter = q.filter.unwrap();
3255 match filter {
3256 Expr::BinaryOp(left, BinOp::Gt, _) => match *left {
3257 Expr::BinaryOp(_, BinOp::Mul, _) => {}
3258 other => panic!("expected Mul, got {other:?}"),
3259 },
3260 other => panic!("expected BinaryOp Gt, got {other:?}"),
3261 }
3262 }
3263 _ => panic!("expected query"),
3264 }
3265 }
3266
3267 #[test]
3268 fn test_parse_div_expr() {
3269 let stmt = parse("User { ratio: .total / .count }").unwrap();
3270 match stmt {
3271 Statement::Query(q) => {
3272 let proj = q.projection.unwrap();
3273 assert_eq!(proj[0].alias.as_deref(), Some("ratio"));
3274 match &proj[0].expr {
3275 Expr::BinaryOp(_, BinOp::Div, _) => {}
3276 other => panic!("expected Div, got {other:?}"),
3277 }
3278 }
3279 _ => panic!("expected query"),
3280 }
3281 }
3282
3283 #[test]
3284 fn test_parse_mul_div_precedence() {
3285 let stmt = parse("User filter .a + .b * .c > 0").unwrap();
3287 match stmt {
3288 Statement::Query(q) => {
3289 let filter = q.filter.unwrap();
3290 match filter {
3291 Expr::BinaryOp(left, BinOp::Gt, _) => match *left {
3292 Expr::BinaryOp(_, BinOp::Add, right) => {
3293 assert!(matches!(*right, Expr::BinaryOp(_, BinOp::Mul, _)));
3294 }
3295 other => panic!("expected Add, got {other:?}"),
3296 },
3297 other => panic!("expected Gt, got {other:?}"),
3298 }
3299 }
3300 _ => panic!("expected query"),
3301 }
3302 }
3303
3304 #[test]
3307 fn test_parse_multi_order() {
3308 let stmt = parse("User order .name asc, .age desc").unwrap();
3309 match stmt {
3310 Statement::Query(q) => {
3311 let order = q.order.unwrap();
3312 assert_eq!(order.keys.len(), 2);
3313 assert_eq!(order.keys[0].expr, Expr::Field("name".into()));
3314 assert!(!order.keys[0].descending);
3315 assert_eq!(order.keys[1].expr, Expr::Field("age".into()));
3316 assert!(order.keys[1].descending);
3317 }
3318 _ => panic!("expected query"),
3319 }
3320 }
3321
3322 #[test]
3323 fn test_parse_order_default_asc() {
3324 let stmt = parse("User order .name").unwrap();
3325 match stmt {
3326 Statement::Query(q) => {
3327 let order = q.order.unwrap();
3328 assert_eq!(order.keys.len(), 1);
3329 assert!(!order.keys[0].descending);
3330 }
3331 _ => panic!("expected query"),
3332 }
3333 }
3334
3335 #[test]
3338 fn test_parse_alter_add_column() {
3339 let stmt = parse("alter User add column status: str").unwrap();
3340 match stmt {
3341 Statement::AlterTable(at) => {
3342 assert_eq!(at.table, "User");
3343 match at.action {
3344 AlterAction::AddColumn {
3345 name,
3346 type_name,
3347 required,
3348 } => {
3349 assert_eq!(name, "status");
3350 assert_eq!(type_name, "str");
3351 assert!(!required);
3352 }
3353 other => panic!("expected AddColumn, got {other:?}"),
3354 }
3355 }
3356 other => panic!("expected AlterTable, got {other:?}"),
3357 }
3358 }
3359
3360 #[test]
3361 fn test_parse_alter_add_required_column() {
3362 let stmt = parse("alter User add required status: str").unwrap();
3363 match stmt {
3364 Statement::AlterTable(at) => match at.action {
3365 AlterAction::AddColumn { required, .. } => assert!(required),
3366 other => panic!("expected AddColumn, got {other:?}"),
3367 },
3368 other => panic!("expected AlterTable, got {other:?}"),
3369 }
3370 }
3371
3372 #[test]
3373 fn test_parse_type_with_unique_modifier() {
3374 let stmt = parse("type User { required unique email: str, age: int }").unwrap();
3375 match stmt {
3376 Statement::CreateType(ct) => {
3377 assert!(ct.fields[0].required && ct.fields[0].unique);
3378 assert!(!ct.fields[1].unique);
3379 }
3380 other => panic!("expected CreateType, got {other:?}"),
3381 }
3382 }
3383
3384 #[test]
3385 fn test_parse_type_unique_before_required() {
3386 let stmt = parse("type User { unique required email: str }").unwrap();
3388 match stmt {
3389 Statement::CreateType(ct) => {
3390 assert!(ct.fields[0].required && ct.fields[0].unique);
3391 }
3392 other => panic!("expected CreateType, got {other:?}"),
3393 }
3394 }
3395
3396 #[test]
3397 fn test_parse_alter_add_unique() {
3398 let stmt = parse("alter User add unique .email").unwrap();
3399 match stmt {
3400 Statement::AlterTable(at) => assert!(matches!(
3401 at.action,
3402 AlterAction::AddUnique {
3403 target: IndexTarget::Column(ref column),
3404 ..
3405 } if column == "email"
3406 )),
3407 other => panic!("expected AlterTable, got {other:?}"),
3408 }
3409 }
3410
3411 #[test]
3412 fn test_parse_alter_drop_column() {
3413 let stmt = parse("alter User drop column status").unwrap();
3414 match stmt {
3415 Statement::AlterTable(at) => {
3416 assert_eq!(at.table, "User");
3417 match at.action {
3418 AlterAction::DropColumn { name, .. } => assert_eq!(name, "status"),
3419 other => panic!("expected DropColumn, got {other:?}"),
3420 }
3421 }
3422 other => panic!("expected AlterTable, got {other:?}"),
3423 }
3424 }
3425
3426 #[test]
3427 fn test_parse_alter_drop_without_column_keyword() {
3428 let stmt = parse("alter User drop status").unwrap();
3429 match stmt {
3430 Statement::AlterTable(at) => match at.action {
3431 AlterAction::DropColumn { name, .. } => assert_eq!(name, "status"),
3432 other => panic!("expected DropColumn, got {other:?}"),
3433 },
3434 other => panic!("expected AlterTable, got {other:?}"),
3435 }
3436 }
3437
3438 #[test]
3439 fn test_parse_drop_table() {
3440 let stmt = parse("drop User").unwrap();
3441 match stmt {
3442 Statement::DropTable(dt) => assert_eq!(dt.table, "User"),
3443 other => panic!("expected DropTable, got {other:?}"),
3444 }
3445 }
3446
3447 #[test]
3450 fn test_parse_in_subquery() {
3451 let stmt = parse("User filter .name in (VIP { .name })").unwrap();
3452 match stmt {
3453 Statement::Query(q) => {
3454 let filter = q.filter.unwrap();
3455 match filter {
3456 Expr::InSubquery {
3457 expr,
3458 subquery,
3459 negated,
3460 } => {
3461 assert!(!negated);
3462 assert!(matches!(*expr, Expr::Field(ref f) if f == "name"));
3463 assert_eq!(subquery.source, "VIP");
3464 }
3465 other => panic!("expected InSubquery, got {other:?}"),
3466 }
3467 }
3468 _ => panic!("expected query"),
3469 }
3470 }
3471
3472 #[test]
3473 fn test_parse_not_in_subquery() {
3474 let stmt = parse("User filter .id not in (Order { .user_id })").unwrap();
3475 match stmt {
3476 Statement::Query(q) => match q.filter.unwrap() {
3477 Expr::InSubquery { negated, .. } => assert!(negated),
3478 other => panic!("expected InSubquery, got {other:?}"),
3479 },
3480 _ => panic!("expected query"),
3481 }
3482 }
3483
3484 #[test]
3485 fn test_parse_in_literal_list_still_works() {
3486 let stmt = parse("User filter .age in (25, 30, 35)").unwrap();
3488 match stmt {
3489 Statement::Query(q) => match q.filter.unwrap() {
3490 Expr::InList { list, negated, .. } => {
3491 assert!(!negated);
3492 assert_eq!(list.len(), 3);
3493 }
3494 other => panic!("expected InList, got {other:?}"),
3495 },
3496 _ => panic!("expected query"),
3497 }
3498 }
3499
3500 #[test]
3503 fn test_parse_create_view() {
3504 let stmt = parse("materialize OldUsers as User filter .age > 28").unwrap();
3505 match stmt {
3506 Statement::CreateView(cv) => {
3507 assert_eq!(cv.name, "OldUsers");
3508 assert_eq!(cv.query.source, "User");
3509 assert!(cv.query.filter.is_some());
3510 assert!(!cv.query_text.is_empty());
3511 }
3512 _ => panic!("expected CreateView"),
3513 }
3514 }
3515
3516 #[test]
3517 fn test_parse_create_view_with_projection() {
3518 let stmt = parse("materialize UserNames as User { .name }").unwrap();
3519 match stmt {
3520 Statement::CreateView(cv) => {
3521 assert_eq!(cv.name, "UserNames");
3522 assert!(cv.query.projection.is_some());
3523 }
3524 _ => panic!("expected CreateView"),
3525 }
3526 }
3527
3528 #[test]
3529 fn test_parse_refresh_view() {
3530 let stmt = parse("refresh OldUsers").unwrap();
3531 match stmt {
3532 Statement::RefreshView(rv) => {
3533 assert_eq!(rv.name, "OldUsers");
3534 }
3535 _ => panic!("expected RefreshView"),
3536 }
3537 }
3538
3539 #[test]
3540 fn test_parse_drop_view() {
3541 let stmt = parse("drop view OldUsers").unwrap();
3542 match stmt {
3543 Statement::DropView(dv) => {
3544 assert_eq!(dv.name, "OldUsers");
3545 }
3546 _ => panic!("expected DropView"),
3547 }
3548 }
3549
3550 #[test]
3551 fn test_parse_drop_table_still_works() {
3552 let stmt = parse("drop Users").unwrap();
3553 match stmt {
3554 Statement::DropTable(dt) => {
3555 assert_eq!(dt.table, "Users");
3556 }
3557 _ => panic!("expected DropTable"),
3558 }
3559 }
3560
3561 #[test]
3562 fn test_parse_union() {
3563 let stmt = parse("User union Order").unwrap();
3564 match stmt {
3565 Statement::Union(u) => {
3566 assert!(!u.all);
3567 match *u.left {
3568 Statement::Query(_) => {}
3569 _ => panic!("expected Query on left"),
3570 }
3571 match *u.right {
3572 Statement::Query(_) => {}
3573 _ => panic!("expected Query on right"),
3574 }
3575 }
3576 _ => panic!("expected Union"),
3577 }
3578 }
3579
3580 #[test]
3581 fn test_parse_union_all() {
3582 let stmt = parse("User union all Order").unwrap();
3583 match stmt {
3584 Statement::Union(u) => {
3585 assert!(u.all, "expected UNION ALL");
3586 match *u.left {
3587 Statement::Query(_) => {}
3588 _ => panic!("expected Query on left"),
3589 }
3590 match *u.right {
3591 Statement::Query(_) => {}
3592 _ => panic!("expected Query on right"),
3593 }
3594 }
3595 _ => panic!("expected Union"),
3596 }
3597 }
3598
3599 #[test]
3600 fn test_parse_union_chain() {
3601 let stmt = parse("User union Order union Product").unwrap();
3603 match stmt {
3604 Statement::Union(outer) => {
3605 assert!(!outer.all);
3606 match *outer.right {
3608 Statement::Query(q) => assert_eq!(q.source, "Product"),
3609 _ => panic!("expected Query(Product) on right"),
3610 }
3611 match *outer.left {
3613 Statement::Union(inner) => {
3614 assert!(!inner.all);
3615 match *inner.left {
3616 Statement::Query(q) => assert_eq!(q.source, "User"),
3617 _ => panic!("expected Query(User)"),
3618 }
3619 match *inner.right {
3620 Statement::Query(q) => assert_eq!(q.source, "Order"),
3621 _ => panic!("expected Query(Order)"),
3622 }
3623 }
3624 _ => panic!("expected inner Union"),
3625 }
3626 }
3627 _ => panic!("expected Union"),
3628 }
3629 }
3630
3631 #[test]
3632 fn test_parse_union_with_filter() {
3633 let stmt = parse("User filter .age > 10 union Order filter .total > 50").unwrap();
3634 match stmt {
3635 Statement::Union(u) => {
3636 assert!(!u.all);
3637 match *u.left {
3639 Statement::Query(q) => {
3640 assert_eq!(q.source, "User");
3641 assert!(q.filter.is_some());
3642 }
3643 _ => panic!("expected Query on left"),
3644 }
3645 match *u.right {
3646 Statement::Query(q) => {
3647 assert_eq!(q.source, "Order");
3648 assert!(q.filter.is_some());
3649 }
3650 _ => panic!("expected Query on right"),
3651 }
3652 }
3653 _ => panic!("expected Union"),
3654 }
3655 }
3656
3657 #[test]
3658 fn test_parse_count_distinct_standalone() {
3659 let stmt = parse("count(distinct User { .name })").unwrap();
3660 match stmt {
3661 Statement::Query(q) => {
3662 let agg = q.aggregation.unwrap();
3663 assert_eq!(agg.function, AggFunc::CountDistinct);
3664 assert_eq!(agg.argument, Some(Expr::Field("name".into())));
3665 }
3666 _ => panic!("expected Query"),
3667 }
3668 }
3669
3670 #[test]
3671 fn test_parse_count_distinct_in_projection() {
3672 let stmt = parse("User group .dept { .dept, count(distinct .name) }").unwrap();
3673 match stmt {
3674 Statement::Query(q) => {
3675 let proj = q.projection.unwrap();
3676 assert_eq!(proj.len(), 2);
3677 match &proj[1].expr {
3678 Expr::FunctionCall(func, _, _) => {
3679 assert_eq!(*func, AggFunc::CountDistinct);
3680 }
3681 _ => panic!("expected FunctionCall"),
3682 }
3683 }
3684 _ => panic!("expected Query"),
3685 }
3686 }
3687
3688 #[test]
3691 fn test_parse_window_row_number_order() {
3692 let stmt = parse("User { .name, rn: row_number() over (order .age) }").unwrap();
3693 match stmt {
3694 Statement::Query(q) => {
3695 let proj = q.projection.unwrap();
3696 assert_eq!(proj.len(), 2);
3697 assert_eq!(proj[1].alias.as_deref(), Some("rn"));
3698 match &proj[1].expr {
3699 Expr::Window {
3700 function,
3701 args,
3702 partition_by,
3703 order_by,
3704 ..
3705 } => {
3706 assert_eq!(*function, WindowFunc::RowNumber);
3707 assert!(args.is_empty());
3708 assert!(partition_by.is_empty());
3709 assert_eq!(order_by.len(), 1);
3710 assert_eq!(order_by[0].expr, Expr::Field("age".into()));
3711 assert!(!order_by[0].descending);
3712 }
3713 other => panic!("expected Window, got {other:?}"),
3714 }
3715 }
3716 _ => panic!("expected query"),
3717 }
3718 }
3719
3720 #[test]
3721 fn test_parse_window_sum_partition_order() {
3722 let stmt =
3723 parse("User { .name, s: sum(.salary) over (partition .dept order .salary) }").unwrap();
3724 match stmt {
3725 Statement::Query(q) => {
3726 let proj = q.projection.unwrap();
3727 assert_eq!(proj.len(), 2);
3728 assert_eq!(proj[1].alias.as_deref(), Some("s"));
3729 match &proj[1].expr {
3730 Expr::Window {
3731 function,
3732 args,
3733 partition_by,
3734 order_by,
3735 ..
3736 } => {
3737 assert_eq!(*function, WindowFunc::Sum);
3738 assert_eq!(args.len(), 1);
3739 assert!(matches!(&args[0], Expr::Field(f) if f == "salary"));
3740 assert_eq!(partition_by, &[Expr::Field("dept".into())]);
3741 assert_eq!(order_by.len(), 1);
3742 assert_eq!(order_by[0].expr, Expr::Field("salary".into()));
3743 assert!(!order_by[0].descending);
3744 }
3745 other => panic!("expected Window, got {other:?}"),
3746 }
3747 }
3748 _ => panic!("expected query"),
3749 }
3750 }
3751
3752 #[test]
3753 fn test_parse_window_rank_desc() {
3754 let stmt =
3755 parse("User { .dept, .salary, r: rank() over (partition .dept order .salary desc) }")
3756 .unwrap();
3757 match stmt {
3758 Statement::Query(q) => {
3759 let proj = q.projection.unwrap();
3760 assert_eq!(proj.len(), 3);
3761 match &proj[2].expr {
3762 Expr::Window {
3763 function,
3764 partition_by,
3765 order_by,
3766 ..
3767 } => {
3768 assert_eq!(*function, WindowFunc::Rank);
3769 assert_eq!(partition_by, &[Expr::Field("dept".into())]);
3770 assert_eq!(order_by.len(), 1);
3771 assert!(order_by[0].descending);
3772 }
3773 other => panic!("expected Window, got {other:?}"),
3774 }
3775 }
3776 _ => panic!("expected query"),
3777 }
3778 }
3779
3780 #[test]
3781 fn test_parse_window_dense_rank() {
3782 let stmt = parse("User { .name, dr: dense_rank() over (order .score desc) }").unwrap();
3783 match stmt {
3784 Statement::Query(q) => {
3785 let proj = q.projection.unwrap();
3786 assert_eq!(proj.len(), 2);
3787 match &proj[1].expr {
3788 Expr::Window { function, .. } => {
3789 assert_eq!(*function, WindowFunc::DenseRank);
3790 }
3791 other => panic!("expected Window, got {other:?}"),
3792 }
3793 }
3794 _ => panic!("expected query"),
3795 }
3796 }
3797
3798 #[test]
3799 fn test_parse_sum_without_over_is_aggregate() {
3800 let stmt = parse("User group .dept { .dept, total: sum(.salary) }").unwrap();
3802 match stmt {
3803 Statement::Query(q) => {
3804 let proj = q.projection.unwrap();
3805 assert_eq!(proj.len(), 2);
3806 match &proj[1].expr {
3807 Expr::FunctionCall(AggFunc::Sum, _, _) => {} other => panic!("expected FunctionCall(Sum), got {other:?}"),
3809 }
3810 }
3811 _ => panic!("expected query"),
3812 }
3813 }
3814
3815 #[test]
3816 fn test_nesting_depth_limit() {
3817 let mut query = String::from("User filter ");
3819 for _ in 0..70 {
3820 query.push('(');
3821 }
3822 query.push_str(".age > 1");
3823 for _ in 0..70 {
3824 query.push(')');
3825 }
3826 let result = parse(&query);
3827 assert!(result.is_err());
3828 let err = result.unwrap_err();
3829 assert!(
3830 err.message().contains("nesting depth"),
3831 "expected nesting depth error, got: {}",
3832 err.message()
3833 );
3834 }
3835
3836 #[test]
3837 fn test_unary_prefix_nesting_depth_limit() {
3838 let query = String::from("User filter ") + &"not ".repeat(5000) + ".active";
3842 let result = parse(&query);
3843 assert!(result.is_err());
3844 let err = result.unwrap_err();
3845 assert!(
3846 err.message().contains("nesting depth"),
3847 "expected nesting depth error, got: {}",
3848 err.message()
3849 );
3850 }
3851
3852 #[test]
3853 fn test_moderate_nesting_succeeds() {
3854 let mut query = String::from("User filter ");
3856 for _ in 0..10 {
3857 query.push('(');
3858 }
3859 query.push_str(".age > 1");
3860 for _ in 0..10 {
3861 query.push(')');
3862 }
3863 assert!(parse(&query).is_ok());
3864 }
3865
3866 #[test]
3870 fn test_parse_fuzz_repro_projection_eof() {
3871 let err = parse("nn{").expect_err("unterminated projection must error, not panic");
3872 let _ = err.message();
3873 }
3874
3875 #[test]
3878 fn test_parse_fuzz_repro_short_projection_eof() {
3879 let err = parse("z{").expect_err("unterminated projection must error, not panic");
3880 let _ = err.message();
3881 }
3882
3883 #[test]
3884 fn test_update_at_statement_start_gives_helpful_error() {
3885 let err =
3886 parse(r#"update User filter .name = "Alice" { age := 31 }"#).expect_err("should fail");
3887 let msg = err.message();
3888 assert!(
3889 msg.contains("pipeline syntax"),
3890 "error should mention pipeline syntax, got: {msg}"
3891 );
3892 assert!(
3893 msg.contains("update"),
3894 "error should mention 'update', got: {msg}"
3895 );
3896 }
3897
3898 #[test]
3899 fn test_delete_at_statement_start_gives_helpful_error() {
3900 let err = parse("delete User filter .age < 18").expect_err("should fail");
3901 let msg = err.message();
3902 assert!(
3903 msg.contains("pipeline syntax"),
3904 "error should mention pipeline syntax, got: {msg}"
3905 );
3906 assert!(
3907 msg.contains("delete"),
3908 "error should mention 'delete', got: {msg}"
3909 );
3910 }
3911}
3912
3913#[cfg(test)]
3914mod cleanup_parser_dx_tests {
3915 use super::*;
3916
3917 #[test]
3918 fn typoed_statement_keyword_gets_suggestion() {
3919 let err = parse("updat User set age = 1").unwrap_err();
3920 let msg = err.to_string();
3921 assert!(msg.contains("near token"), "{msg}");
3922 assert!(msg.contains("did you mean `update`"), "{msg}");
3923 }
3924}
3925
3926#[cfg(test)]
3927mod dogfood_dx_tests {
3928 use super::*;
3929
3930 #[test]
3933 fn reserved_word_field_name_gives_actionable_error() {
3934 let err = parse("type Post { type: str }").unwrap_err();
3935 let msg = err.to_string();
3936 assert!(
3937 msg.contains("'type' is a reserved word")
3938 && msg.contains("field name")
3939 && msg.contains("quote it as `type`"),
3940 "unhelpful message: {msg}"
3941 );
3942 }
3943
3944 #[test]
3945 fn reserved_modifier_word_as_field_name_gives_actionable_error() {
3946 let err = parse("type Post { required: bool }").unwrap_err();
3950 let msg = err.to_string();
3951 assert!(
3952 msg.contains("'required' is a reserved word") && msg.contains("quote it as `required`"),
3953 "unhelpful message: {msg}"
3954 );
3955 }
3956
3957 #[test]
3958 fn reserved_word_in_insert_assignment_gives_actionable_error() {
3959 let err = parse(r#"insert Post { type := "x" }"#).unwrap_err();
3960 let msg = err.to_string();
3961 assert!(msg.contains("'type' is a reserved word"), "{msg}");
3962 }
3963
3964 #[test]
3965 fn reserved_word_in_alter_column_gives_actionable_error() {
3966 let err = parse("alter Post add column order: int").unwrap_err();
3967 let msg = err.to_string();
3968 assert!(msg.contains("'order' is a reserved word"), "{msg}");
3969 }
3970
3971 #[test]
3972 fn backtick_field_name_parses_as_identifier() {
3973 let stmt = parse("type Post { `type`: str, `order`: int }").unwrap();
3974 match stmt {
3975 Statement::CreateType(ct) => {
3976 assert_eq!(ct.fields[0].name, "type");
3977 assert_eq!(ct.fields[1].name, "order");
3978 }
3979 other => panic!("expected CreateType, got {other:?}"),
3980 }
3981 }
3982
3983 #[test]
3984 fn backtick_field_still_honors_modifiers() {
3985 let stmt = parse("type Post { required `type`: str }").unwrap();
3986 match stmt {
3987 Statement::CreateType(ct) => {
3988 assert_eq!(ct.fields[0].name, "type");
3989 assert!(ct.fields[0].required);
3990 }
3991 other => panic!("expected CreateType, got {other:?}"),
3992 }
3993 }
3994
3995 #[test]
3998 fn create_type_if_not_exists_parses() {
3999 let stmt = parse("type Post if not exists { id: int }").unwrap();
4000 match stmt {
4001 Statement::CreateType(ct) => assert!(ct.if_not_exists),
4002 other => panic!("expected CreateType, got {other:?}"),
4003 }
4004 }
4005
4006 #[test]
4007 fn create_type_without_clause_defaults_false() {
4008 let stmt = parse("type Post { id: int }").unwrap();
4009 match stmt {
4010 Statement::CreateType(ct) => assert!(!ct.if_not_exists),
4011 other => panic!("expected CreateType, got {other:?}"),
4012 }
4013 }
4014
4015 #[test]
4016 fn drop_if_exists_parses() {
4017 match parse("drop if exists Post").unwrap() {
4018 Statement::DropTable(dt) => assert!(dt.if_exists),
4019 other => panic!("expected DropTable, got {other:?}"),
4020 }
4021 match parse("drop Post").unwrap() {
4022 Statement::DropTable(dt) => assert!(!dt.if_exists),
4023 other => panic!("expected DropTable, got {other:?}"),
4024 }
4025 }
4026
4027 #[test]
4028 fn drop_view_if_exists_parses() {
4029 match parse("drop view if exists ActiveUsers").unwrap() {
4030 Statement::DropView(dv) => {
4031 assert!(dv.if_exists);
4032 assert_eq!(dv.name, "ActiveUsers");
4033 }
4034 other => panic!("expected DropView, got {other:?}"),
4035 }
4036 }
4037
4038 #[test]
4039 fn add_index_and_unique_if_not_exists_parse() {
4040 match parse("alter Post add index if not exists .slug").unwrap() {
4041 Statement::AlterTable(at) => {
4042 assert!(matches!(
4043 at.action,
4044 AlterAction::AddIndex {
4045 if_not_exists: true,
4046 ..
4047 }
4048 ));
4049 }
4050 other => panic!("expected AlterTable, got {other:?}"),
4051 }
4052 match parse("alter Post add unique if not exists .slug").unwrap() {
4053 Statement::AlterTable(at) => {
4054 assert!(matches!(
4055 at.action,
4056 AlterAction::AddUnique {
4057 if_not_exists: true,
4058 ..
4059 }
4060 ));
4061 }
4062 other => panic!("expected AlterTable, got {other:?}"),
4063 }
4064 }
4065
4066 #[test]
4067 fn expression_index_targets_parse_with_stable_table_local_identity() {
4068 use powdb_storage::stored_json_path::{
4069 StoredJsonPathSegmentV1 as Segment, StoredJsonPathV1,
4070 };
4071
4072 let expected = StoredJsonPathV1::new(
4073 "data",
4074 vec![Segment::Key("author".into()), Segment::Index(0)],
4075 );
4076 for query in [
4077 "alter Post add index (.data->author->0)",
4078 "alter Post add unique if not exists (.data->\"author\"->0)",
4079 "alter Post drop index if exists (.data->author->0)",
4080 ] {
4081 let Statement::AlterTable(alter) = parse(query).unwrap() else {
4082 panic!("expected alter table for {query}");
4083 };
4084 let (target, flag) = match alter.action {
4085 AlterAction::AddIndex {
4086 target,
4087 if_not_exists,
4088 }
4089 | AlterAction::AddUnique {
4090 target,
4091 if_not_exists,
4092 } => (target, if_not_exists),
4093 AlterAction::DropIndex { target, if_exists } => (target, if_exists),
4094 other => panic!("expected index action, got {other:?}"),
4095 };
4096 assert_eq!(target, IndexTarget::JsonPath(expected.clone()));
4097 assert_eq!(
4098 flag,
4099 query.contains("if not exists") || query.contains("if exists")
4100 );
4101 }
4102 }
4103
4104 #[test]
4105 fn expression_index_target_rejects_ambiguous_or_non_path_forms() {
4106 let cases = [
4107 (
4108 "alter Post add index .data->author",
4109 "must be parenthesized",
4110 ),
4111 (
4112 "alter Post add index (p.data->author)",
4113 "qualified JSON paths",
4114 ),
4115 (
4116 "alter Post add index (.data)",
4117 "use `.column` for a stored column",
4118 ),
4119 (
4120 "alter Post add index (.data->age + 1)",
4121 "only a direct JSON path",
4122 ),
4123 (
4124 "alter Post drop index ({ value := 1 })",
4125 "expected an unqualified JSON path",
4126 ),
4127 ];
4128 for (query, expected) in cases {
4129 let error = parse(query).expect_err(query).to_string();
4130 assert!(
4131 error.contains(expected),
4132 "`{query}` should mention `{expected}`, got `{error}`"
4133 );
4134 }
4135 }
4136
4137 #[test]
4138 fn alter_drop_column_if_exists_parses() {
4139 match parse("alter Post drop column if exists status").unwrap() {
4140 Statement::AlterTable(at) => {
4141 assert!(matches!(
4142 at.action,
4143 AlterAction::DropColumn {
4144 if_exists: true,
4145 ..
4146 }
4147 ));
4148 }
4149 other => panic!("expected AlterTable, got {other:?}"),
4150 }
4151 }
4152
4153 #[test]
4156 fn schema_parses_to_list_types() {
4157 assert_eq!(parse("schema").unwrap(), Statement::ListTypes);
4158 }
4159
4160 #[test]
4161 fn describe_parses_to_describe() {
4162 assert_eq!(
4163 parse("describe Post").unwrap(),
4164 Statement::Describe("Post".to_string())
4165 );
4166 }
4167
4168 #[test]
4169 fn schema_with_type_aliases_describe() {
4170 assert_eq!(
4171 parse("schema Post").unwrap(),
4172 Statement::Describe("Post".to_string())
4173 );
4174 }
4175}
4176
4177#[cfg(test)]
4178mod json_path_tests {
4179 use super::*;
4180
4181 fn filter_of(src: &str) -> Expr {
4183 match parse(src).unwrap() {
4184 Statement::Query(q) => q.filter.expect("expected a filter"),
4185 other => panic!("expected a query, got {other:?}"),
4186 }
4187 }
4188
4189 #[test]
4190 fn ident_key_path() {
4191 let e = filter_of(r#"Post filter .data->author->name = "x""#);
4193 let Expr::BinaryOp(lhs, BinOp::Eq, _) = e else {
4194 panic!("expected an equality, got {e:?}");
4195 };
4196 assert_eq!(
4197 *lhs,
4198 Expr::JsonPath {
4199 base: Box::new(Expr::Field("data".into())),
4200 segments: vec![PathSeg::Key("author".into()), PathSeg::Key("name".into())],
4201 }
4202 );
4203 }
4204
4205 #[test]
4206 fn string_form_key_path() {
4207 let e = filter_of(r#"Post filter .data->"weird key!" = 1"#);
4209 let Expr::BinaryOp(lhs, _, _) = e else {
4210 panic!("expected binop");
4211 };
4212 assert_eq!(
4213 *lhs,
4214 Expr::JsonPath {
4215 base: Box::new(Expr::Field("data".into())),
4216 segments: vec![PathSeg::Key("weird key!".into())],
4217 }
4218 );
4219 }
4220
4221 #[test]
4222 fn array_index_path() {
4223 let e = filter_of(r#"Post filter .data->tags->0 = "rust""#);
4225 let Expr::BinaryOp(lhs, _, _) = e else {
4226 panic!("expected binop");
4227 };
4228 assert_eq!(
4229 *lhs,
4230 Expr::JsonPath {
4231 base: Box::new(Expr::Field("data".into())),
4232 segments: vec![PathSeg::Key("tags".into()), PathSeg::Index(0)],
4233 }
4234 );
4235 }
4236
4237 #[test]
4238 fn qualified_base_path() {
4239 let e = filter_of(r#"Post as posts filter posts.data->author = "a""#);
4241 let Expr::BinaryOp(lhs, _, _) = e else {
4242 panic!("expected binop");
4243 };
4244 assert_eq!(
4245 *lhs,
4246 Expr::JsonPath {
4247 base: Box::new(Expr::QualifiedField {
4248 qualifier: "posts".into(),
4249 field: "data".into(),
4250 }),
4251 segments: vec![PathSeg::Key("author".into())],
4252 }
4253 );
4254 }
4255
4256 #[test]
4257 fn path_binds_tighter_than_comparison_and_arithmetic() {
4258 let e = filter_of("Post filter .data->age > 21");
4260 let Expr::BinaryOp(lhs, BinOp::Gt, rhs) = e else {
4261 panic!("expected a top-level `>`, got {e:?}");
4262 };
4263 assert!(matches!(*lhs, Expr::JsonPath { .. }));
4264 assert_eq!(*rhs, Expr::Literal(Literal::Int(21)));
4265
4266 let e = filter_of("Post filter .a->b + 1 = 3");
4268 let Expr::BinaryOp(add, BinOp::Eq, _) = e else {
4269 panic!("expected eq");
4270 };
4271 let Expr::BinaryOp(lhs, BinOp::Add, _) = *add else {
4272 panic!("expected `+` under `=`, got {add:?}");
4273 };
4274 assert!(matches!(*lhs, Expr::JsonPath { .. }));
4275 }
4276
4277 #[test]
4278 fn dash_vs_arrow_lexing() {
4279 let idx = filter_of("Post filter .a->1 = 0");
4282 let Expr::BinaryOp(lhs, BinOp::Eq, _) = idx else {
4283 panic!("expected eq");
4284 };
4285 assert_eq!(
4286 *lhs,
4287 Expr::JsonPath {
4288 base: Box::new(Expr::Field("a".into())),
4289 segments: vec![PathSeg::Index(1)],
4290 }
4291 );
4292
4293 let sub = filter_of("Post filter .a - 1 = 0");
4296 let Expr::BinaryOp(lhs, BinOp::Eq, _) = sub else {
4297 panic!("expected eq");
4298 };
4299 assert!(
4300 matches!(*lhs, Expr::BinaryOp(_, BinOp::Sub, _)),
4301 "`.a - 1` should be subtraction, got {lhs:?}"
4302 );
4303
4304 assert!(
4309 parse("Post filter .a-1 = 0").is_err(),
4310 "`.a-1` should fail to parse (negative-literal gotcha)"
4311 );
4312
4313 assert!(
4315 parse("Post filter .a - > 0").is_err(),
4316 "`.a - >` should fail to parse"
4317 );
4318 }
4319
4320 #[test]
4321 fn negative_index_rejected() {
4322 let err = parse("Post filter .data->-1 = 0").unwrap_err();
4323 assert!(
4324 err.to_string().contains("array index"),
4325 "expected an index error, got: {err}"
4326 );
4327 }
4328
4329 #[test]
4330 fn path_on_literal_base_rejected() {
4331 let err = parse("Post filter 5->x = 1").unwrap_err();
4333 assert!(
4334 err.to_string().to_lowercase().contains("field base"),
4335 "expected a field-base error, got: {err}"
4336 );
4337 }
4338
4339 #[test]
4340 fn json_path_assignment_target_is_targeted_unsupported() {
4341 for stmt in [
4346 "Doc update { .data->x := 5 }",
4347 "Doc update { data->x := 5 }",
4348 ] {
4349 let err = parse(stmt).unwrap_err();
4350 assert!(
4351 matches!(err, ParseError::Unsupported { .. }),
4352 "{stmt}: expected Unsupported, got {err:?}"
4353 );
4354 let msg = err.to_string();
4355 assert!(
4356 msg.contains("JSON path assignment targets are not supported"),
4357 "{stmt}: message must state the unsupported feature: {msg}"
4358 );
4359 assert!(
4360 msg.contains("json_set"),
4361 "{stmt}: message must point at the whole-column alternative: {msg}"
4362 );
4363 }
4364 assert!(parse(r#"Doc update { data := "{}" }"#).is_ok());
4366 }
4367
4368 #[test]
4369 fn json_type_scalar_parses() {
4370 let e = filter_of(r#"Post filter json_type(.data->x) = "string""#);
4371 let Expr::BinaryOp(lhs, _, _) = e else {
4372 panic!("expected binop");
4373 };
4374 let Expr::ScalarFunc(ScalarFn::JsonType, args) = *lhs else {
4375 panic!("expected json_type call, got {lhs:?}");
4376 };
4377 assert_eq!(args.len(), 1);
4378 assert!(matches!(args[0], Expr::JsonPath { .. }));
4379 }
4380
4381 #[test]
4382 fn path_in_projection() {
4383 let stmt = parse("Post { author: .data->author }").unwrap();
4386 let Statement::Query(q) = stmt else {
4387 panic!("expected query");
4388 };
4389 let proj = q.projection.unwrap();
4390 assert_eq!(proj[0].alias.as_deref(), Some("author"));
4391 assert!(matches!(proj[0].expr, Expr::JsonPath { .. }));
4392
4393 let Statement::Query(ordered) = parse("Post order .data->author { .id }").unwrap() else {
4394 panic!("expected query");
4395 };
4396 assert!(matches!(
4397 ordered.order.unwrap().keys[0].expr,
4398 Expr::JsonPath { .. }
4399 ));
4400
4401 let Statement::Query(grouped) =
4402 parse("Post group .data->author { .data->author }").unwrap()
4403 else {
4404 panic!("expected query");
4405 };
4406 assert!(matches!(
4407 grouped.group_by.unwrap().keys[0].expr,
4408 Expr::JsonPath { .. }
4409 ));
4410 }
4411}