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