1use crate::ast::*;
2use crate::lexer::lex;
3use crate::token::Token;
4
5const MAX_NESTING_DEPTH: usize = 64;
19
20#[derive(Debug, thiserror::Error)]
27pub enum ParseError {
28 #[error("at position {position}: {message}")]
30 Lex { message: String, position: usize },
31 #[error("expected {expected}, got {got}")]
33 UnexpectedToken { expected: String, got: String },
34 #[error("query nesting depth exceeds maximum of {max}")]
36 NestingDepthExceeded { max: usize },
37 #[error("{feature}")]
39 Unsupported { feature: String },
40 #[error("{message}")]
42 Syntax { message: String },
43}
44
45impl ParseError {
46 pub fn message(&self) -> String {
48 self.to_string()
49 }
50}
51
52fn token_to_scalar_fn(tok: &Token) -> ScalarFn {
53 match tok {
54 Token::Upper => ScalarFn::Upper,
55 Token::Lower => ScalarFn::Lower,
56 Token::Length => ScalarFn::Length,
57 Token::Trim => ScalarFn::Trim,
58 Token::Substring => ScalarFn::Substring,
59 Token::Concat => ScalarFn::Concat,
60 Token::Abs => ScalarFn::Abs,
61 Token::Round => ScalarFn::Round,
62 Token::Ceil => ScalarFn::Ceil,
63 Token::Floor => ScalarFn::Floor,
64 Token::Sqrt => ScalarFn::Sqrt,
65 Token::Pow => ScalarFn::Pow,
66 Token::Now => ScalarFn::Now,
67 Token::Extract => ScalarFn::Extract,
68 Token::DateAdd => ScalarFn::DateAdd,
69 Token::DateDiff => ScalarFn::DateDiff,
70 Token::JsonType => ScalarFn::JsonType,
71 Token::JsonText => ScalarFn::JsonText,
72 _ => unreachable!(),
73 }
74}
75
76struct Parser {
77 tokens: Vec<Token>,
78 pos: usize,
79 depth: usize,
80}
81
82pub fn parse(input: &str) -> Result<Statement, ParseError> {
104 let tokens = lex(input).map_err(|e| ParseError::Lex {
105 message: e.message,
106 position: e.position,
107 })?;
108 parse_tokens(tokens)
109}
110
111pub fn parse_with_params(input: &str, params: &[ParamValue]) -> Result<Statement, ParseError> {
126 let mut tokens = lex(input).map_err(|e| ParseError::Lex {
127 message: e.message,
128 position: e.position,
129 })?;
130 for tok in tokens.iter_mut() {
131 if let Token::Param(name) = tok {
132 let n: usize = name.parse().map_err(|_| ParseError::Syntax {
133 message: format!(
134 "positional parameters must be numeric (`$1`, `$2`, …); got `${name}`"
135 ),
136 })?;
137 if n == 0 {
138 return Err(ParseError::Syntax {
139 message: "parameter placeholders are 1-based; `$0` is invalid".into(),
140 });
141 }
142 let p = params.get(n - 1).ok_or_else(|| ParseError::Syntax {
143 message: format!(
144 "query references ${n} but only {} parameter(s) were supplied",
145 params.len()
146 ),
147 })?;
148 *tok = match p {
149 ParamValue::Null => Token::Null,
150 ParamValue::Int(v) => Token::IntLit(*v),
151 ParamValue::Float(v) => Token::FloatLit(*v),
152 ParamValue::Bool(v) => Token::BoolLit(*v),
153 ParamValue::Str(s) => Token::StringLit(s.clone()),
154 };
155 }
156 }
157 parse_tokens(tokens)
158}
159
160fn edit_distance(a: &str, b: &str) -> usize {
161 let mut prev: Vec<usize> = (0..=b.len()).collect();
162 let mut curr = vec![0; b.len() + 1];
163 for (i, ca) in a.bytes().enumerate() {
164 curr[0] = i + 1;
165 for (j, cb) in b.bytes().enumerate() {
166 let cost = usize::from(ca != cb);
167 curr[j + 1] = (prev[j + 1] + 1).min(curr[j] + 1).min(prev[j] + cost);
168 }
169 std::mem::swap(&mut prev, &mut curr);
170 }
171 prev[b.len()]
172}
173
174fn keyword_suggestion(word: &str) -> Option<&'static str> {
175 const STATEMENT_KEYWORDS: &[&str] = &[
176 "alter", "begin", "commit", "delete", "drop", "explain", "insert", "refresh", "rollback",
177 "select", "type", "update", "upsert",
178 ];
179 let lower = word.to_ascii_lowercase();
180 STATEMENT_KEYWORDS
181 .iter()
182 .copied()
183 .filter(|kw| edit_distance(&lower, kw) <= 2)
184 .min_by_key(|kw| edit_distance(&lower, kw))
185}
186
187fn parse_tokens(tokens: Vec<Token>) -> Result<Statement, ParseError> {
191 let mut parser = Parser {
192 tokens,
193 pos: 0,
194 depth: 0,
195 };
196 let stmt = parser.parse_statement()?;
197 if !matches!(parser.peek(), Token::Eof) {
203 let mut message = format!(
204 "unexpected trailing token near token {}: {}",
205 parser.pos,
206 parser.peek().display_name()
207 );
208 if let Some(Token::Ident(first)) = parser.tokens.first() {
209 if let Some(suggestion) = keyword_suggestion(first) {
210 message.push_str(&format!("; did you mean `{suggestion}`?"));
211 }
212 }
213 return Err(ParseError::Syntax { message });
214 }
215 Ok(stmt)
216}
217
218fn substitute_projection_aliases(expr: Expr, fields: &[ProjectionField]) -> Expr {
223 match expr {
224 Expr::Field(ref name) => {
225 for f in fields {
226 if f.alias.as_deref() == Some(name.as_str()) {
227 return f.expr.clone();
228 }
229 }
230 expr
231 }
232 Expr::BinaryOp(l, op, r) => Expr::BinaryOp(
233 Box::new(substitute_projection_aliases(*l, fields)),
234 op,
235 Box::new(substitute_projection_aliases(*r, fields)),
236 ),
237 Expr::UnaryOp(op, inner) => {
238 Expr::UnaryOp(op, Box::new(substitute_projection_aliases(*inner, fields)))
239 }
240 Expr::Coalesce(l, r) => Expr::Coalesce(
241 Box::new(substitute_projection_aliases(*l, fields)),
242 Box::new(substitute_projection_aliases(*r, fields)),
243 ),
244 Expr::InList {
245 expr: e,
246 list,
247 negated,
248 } => Expr::InList {
249 expr: Box::new(substitute_projection_aliases(*e, fields)),
250 list: list
251 .into_iter()
252 .map(|i| substitute_projection_aliases(i, fields))
253 .collect(),
254 negated,
255 },
256 Expr::ScalarFunc(f, args) => Expr::ScalarFunc(
257 f,
258 args.into_iter()
259 .map(|a| substitute_projection_aliases(a, fields))
260 .collect(),
261 ),
262 other => other,
263 }
264}
265
266impl Parser {
267 fn peek(&self) -> &Token {
268 &self.tokens[self.pos]
269 }
270
271 fn advance(&mut self) -> Token {
272 let t = self.tokens[self.pos].clone();
273 self.pos += 1;
274 t
275 }
276
277 fn expect(&mut self, expected: &Token) -> Result<(), ParseError> {
278 let t = self.advance();
279 if &t == expected {
280 Ok(())
281 } else {
282 Err(ParseError::UnexpectedToken {
283 expected: expected.display_name(),
284 got: t.display_name(),
285 })
286 }
287 }
288
289 fn unexpected(&self, expected: &str, got: &Token) -> ParseError {
291 ParseError::UnexpectedToken {
292 expected: expected.into(),
293 got: got.display_name(),
294 }
295 }
296
297 fn expect_named_ident(&mut self, context: &str) -> Result<String, ParseError> {
302 match self.advance() {
303 Token::Ident(n) => Ok(n),
304 t => Err(self.named_ident_error(context, &t)),
305 }
306 }
307
308 fn named_ident_error(&self, context: &str, got: &Token) -> ParseError {
312 if let Some(kw) = got.keyword_str() {
313 ParseError::Syntax {
314 message: format!(
318 "syntax error: '{kw}' is a reserved word and cannot be used as a {context}; \
319 rename it or quote it as `{kw}`"
320 ),
321 }
322 } else {
323 ParseError::UnexpectedToken {
324 expected: context.into(),
325 got: got.display_name(),
326 }
327 }
328 }
329
330 fn parse_optional_if_not_exists(&mut self) -> bool {
333 if matches!(self.peek(), Token::Ident(w) if w == "if")
334 && matches!(self.tokens.get(self.pos + 1), Some(Token::Not))
335 && matches!(self.tokens.get(self.pos + 2), Some(Token::Exists))
336 {
337 self.pos += 3;
338 true
339 } else {
340 false
341 }
342 }
343
344 fn parse_optional_if_exists(&mut self) -> bool {
346 if matches!(self.peek(), Token::Ident(w) if w == "if")
347 && matches!(self.tokens.get(self.pos + 1), Some(Token::Exists))
348 {
349 self.pos += 2;
350 true
351 } else {
352 false
353 }
354 }
355
356 fn parse_statement(&mut self) -> Result<Statement, ParseError> {
357 self.depth += 1;
358 if self.depth > MAX_NESTING_DEPTH {
359 self.depth -= 1;
360 return Err(ParseError::NestingDepthExceeded {
361 max: MAX_NESTING_DEPTH,
362 });
363 }
364 if matches!(self.peek(), Token::Explain) {
365 self.advance();
366 let inner = self.parse_statement()?;
367 self.depth -= 1;
368 return Ok(Statement::Explain(Box::new(inner)));
369 }
370 let stmt = match self.peek() {
371 Token::Insert => self.parse_insert(),
372 Token::Upsert => self.parse_upsert(),
373 Token::Type => self.parse_create_type(),
374 Token::Link => self.parse_create_link(),
375 Token::Alter => self.parse_alter_table(),
376 Token::Drop => self.parse_drop_or_drop_view(),
377 Token::Materialized => self.parse_create_view(),
378 Token::Refresh => self.parse_refresh_view(),
379 Token::Begin => {
380 self.advance();
381 if *self.peek() == Token::Transaction {
383 self.advance();
384 }
385 return Ok(Statement::Begin);
386 }
387 Token::Commit => {
388 self.advance();
389 return Ok(Statement::Commit);
390 }
391 Token::Rollback => {
392 self.advance();
393 return Ok(Statement::Rollback);
394 }
395 Token::Schema => self.parse_schema(),
396 Token::Describe => self.parse_describe(),
397 Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
398 self.parse_aggregate_query()
399 }
400 Token::Ident(_) => self.parse_query_or_mutation(),
401 Token::Update => Err(ParseError::Syntax {
402 message: "'update' cannot start a statement — in PowQL, use pipeline syntax: \
403 TableName filter ... update { ... }"
404 .into(),
405 }),
406 Token::Delete => Err(ParseError::Syntax {
407 message: "'delete' cannot start a statement — in PowQL, use pipeline syntax: \
408 TableName filter ... delete"
409 .into(),
410 }),
411 _ => Err(self.unexpected("statement", self.peek())),
412 }?;
413 let result = self.maybe_parse_union(stmt);
415 self.depth -= 1;
416 result
417 }
418
419 fn parse_query_or_mutation(&mut self) -> Result<Statement, ParseError> {
420 let source = match self.advance() {
421 Token::Ident(name) => name,
422 t => {
423 return Err(ParseError::UnexpectedToken {
424 expected: "type name".into(),
425 got: t.display_name(),
426 })
427 }
428 };
429 let alias = self.try_parse_alias();
430 let joins = self.parse_joins()?;
431
432 let mut filter = None;
436 let mut order = None;
437 let mut limit = None;
438 let mut offset = None;
439 let mut projection = None;
440 let mut distinct = false;
441 let mut group_by = None;
442 let mut having_chain = 0usize;
446
447 loop {
448 match self.peek() {
449 Token::Distinct => {
450 self.advance();
451 distinct = true;
452 }
453 Token::Group => {
454 self.advance();
455 group_by = Some(self.parse_group_by()?);
456 }
457 Token::Filter => {
458 self.advance();
459 filter = Some(self.parse_expr()?);
460 }
461 Token::Order => {
462 self.advance();
463 order = Some(self.parse_order()?);
464 }
465 Token::Limit => {
466 self.advance();
467 limit = Some(self.parse_expr()?);
468 }
469 Token::Offset => {
470 self.advance();
471 offset = Some(self.parse_expr()?);
472 }
473 Token::LBrace => {
474 projection = Some(self.parse_projection()?);
475 }
476 Token::Having => {
477 self.advance();
479 let having_expr = self.parse_expr()?;
480 let group = group_by.as_mut().ok_or_else(|| ParseError::Syntax {
481 message: "having without group by".into(),
482 })?;
483 let rewritten = match projection.as_ref() {
484 Some(fields) => substitute_projection_aliases(having_expr, fields),
485 None => having_expr,
486 };
487 group.having = Some(match group.having.take() {
488 Some(existing) => {
489 having_chain += 1;
490 self.check_chain_depth(having_chain)?;
491 Expr::BinaryOp(Box::new(existing), BinOp::And, Box::new(rewritten))
492 }
493 None => rewritten,
494 });
495 }
496 Token::Update => {
497 if !joins.is_empty() {
498 return Err(ParseError::Unsupported {
499 feature: "update on a joined query is not supported".into(),
500 });
501 }
502 self.advance();
503 let assignments = self.parse_assignments()?;
504 let returning = *self.peek() == Token::Returning;
506 if returning {
507 self.advance();
508 }
509 return Ok(Statement::UpdateQuery(UpdateExpr {
510 source,
511 alias,
512 filter,
513 assignments,
514 returning,
515 }));
516 }
517 Token::Delete => {
518 if !joins.is_empty() {
519 return Err(ParseError::Unsupported {
520 feature: "delete on a joined query is not supported".into(),
521 });
522 }
523 self.advance();
524 let returning = *self.peek() == Token::Returning;
526 if returning {
527 self.advance();
528 }
529 return Ok(Statement::DeleteQuery(DeleteExpr {
530 source,
531 alias,
532 filter,
533 returning,
534 }));
535 }
536 _ => break,
537 }
538 }
539
540 Ok(Statement::Query(QueryExpr {
541 source,
542 alias,
543 joins,
544 filter,
545 order,
546 limit,
547 offset,
548 projection,
549 aggregation: None,
550 distinct,
551 group_by,
552 }))
553 }
554
555 fn parse_query_tail(&mut self, source: String) -> Result<QueryExpr, ParseError> {
561 let alias = self.try_parse_alias();
562 let joins = self.parse_joins()?;
563 let mut filter = None;
564 let mut order = None;
565 let mut limit = None;
566 let mut offset = None;
567 let mut projection = None;
568 let mut distinct = false;
569 let mut group_by = None;
570 let mut having_chain = 0usize;
574
575 loop {
576 match self.peek() {
577 Token::Distinct => {
578 self.advance();
579 distinct = true;
580 }
581 Token::Group => {
582 self.advance();
583 group_by = Some(self.parse_group_by()?);
584 }
585 Token::Filter => {
586 self.advance();
587 filter = Some(self.parse_expr()?);
588 }
589 Token::Order => {
590 self.advance();
591 order = Some(self.parse_order()?);
592 }
593 Token::Limit => {
594 self.advance();
595 limit = Some(self.parse_expr()?);
596 }
597 Token::Offset => {
598 self.advance();
599 offset = Some(self.parse_expr()?);
600 }
601 Token::LBrace => {
602 projection = Some(self.parse_projection()?);
603 }
604 Token::Having => {
605 self.advance();
611 let having_expr = self.parse_expr()?;
612 let group = group_by.as_mut().ok_or_else(|| ParseError::Syntax {
613 message: "having without group by".into(),
614 })?;
615 let rewritten = match projection.as_ref() {
616 Some(fields) => substitute_projection_aliases(having_expr, fields),
617 None => having_expr,
618 };
619 group.having = Some(match group.having.take() {
620 Some(existing) => {
621 having_chain += 1;
622 self.check_chain_depth(having_chain)?;
623 Expr::BinaryOp(Box::new(existing), BinOp::And, Box::new(rewritten))
624 }
625 None => rewritten,
626 });
627 }
628 _ => break,
629 }
630 }
631
632 Ok(QueryExpr {
633 source,
634 alias,
635 joins,
636 filter,
637 order,
638 limit,
639 offset,
640 projection,
641 aggregation: None,
642 distinct,
643 group_by,
644 })
645 }
646
647 fn try_parse_alias(&mut self) -> Option<String> {
651 if *self.peek() == Token::As {
652 self.advance();
653 if let Token::Ident(name) = self.peek().clone() {
654 self.advance();
655 return Some(name);
656 }
657 }
658 None
659 }
660
661 fn parse_joins(&mut self) -> Result<Vec<JoinClause>, ParseError> {
668 let mut joins = Vec::new();
669 loop {
670 let kind = match self.peek() {
671 Token::Join => {
672 self.advance();
673 JoinKind::Inner
674 }
675 Token::Inner => {
676 self.advance();
677 self.expect(&Token::Join)?;
678 JoinKind::Inner
679 }
680 Token::LeftKw => {
681 self.advance();
682 if *self.peek() == Token::Outer {
683 self.advance();
684 }
685 self.expect(&Token::Join)?;
686 JoinKind::LeftOuter
687 }
688 Token::RightKw => {
689 self.advance();
690 if *self.peek() == Token::Outer {
691 self.advance();
692 }
693 self.expect(&Token::Join)?;
694 JoinKind::RightOuter
695 }
696 Token::Cross => {
697 self.advance();
698 self.expect(&Token::Join)?;
699 JoinKind::Cross
700 }
701 _ => break,
702 };
703
704 let source = match self.advance() {
705 Token::Ident(name) => name,
706 t => {
707 return Err(ParseError::UnexpectedToken {
708 expected: "type name after join".into(),
709 got: t.display_name(),
710 });
711 }
712 };
713 let alias = self.try_parse_alias();
714 let on = if kind == JoinKind::Cross {
715 None
716 } else if *self.peek() == Token::On {
717 self.advance();
718 Some(self.parse_expr()?)
719 } else {
720 return Err(ParseError::Syntax {
721 message: format!("expected `on <expr>` after join {source}"),
722 });
723 };
724
725 joins.push(JoinClause {
726 kind,
727 source,
728 alias,
729 on,
730 });
731 }
732 Ok(joins)
733 }
734
735 fn parse_insert(&mut self) -> Result<Statement, ParseError> {
736 self.expect(&Token::Insert)?;
737 let target = match self.advance() {
738 Token::Ident(name) => name,
739 t => {
740 return Err(ParseError::UnexpectedToken {
741 expected: "type name".into(),
742 got: t.display_name(),
743 })
744 }
745 };
746 let mut rows = vec![self.parse_assignments()?];
750 while *self.peek() == Token::Comma {
751 self.advance(); rows.push(self.parse_assignments()?);
753 }
754 let returning = *self.peek() == Token::Returning;
756 if returning {
757 self.advance();
758 }
759 Ok(Statement::Insert(InsertExpr {
760 target,
761 rows,
762 returning,
763 }))
764 }
765
766 fn parse_upsert(&mut self) -> Result<Statement, ParseError> {
768 self.expect(&Token::Upsert)?;
769 let target = match self.advance() {
770 Token::Ident(name) => name,
771 t => {
772 return Err(ParseError::UnexpectedToken {
773 expected: "type name".into(),
774 got: t.display_name(),
775 })
776 }
777 };
778 self.expect(&Token::On)?;
779 let key_column = match self.advance() {
780 Token::DotIdent(name) => name,
781 t => {
782 return Err(ParseError::UnexpectedToken {
783 expected: ".key_column".into(),
784 got: t.display_name(),
785 })
786 }
787 };
788 let assignments = self.parse_assignments()?;
789 let on_conflict = if *self.peek() == Token::On {
790 self.advance(); self.expect(&Token::Conflict)?;
792 self.parse_assignments()?
793 } else {
794 Vec::new()
795 };
796 Ok(Statement::Upsert(UpsertExpr {
797 target,
798 key_column,
799 assignments,
800 on_conflict,
801 }))
802 }
803
804 fn parse_assignments(&mut self) -> Result<Vec<Assignment>, ParseError> {
805 self.expect(&Token::LBrace)?;
806 let mut assignments = Vec::new();
807 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
808 if matches!(self.peek(), Token::DotIdent(_) | Token::Ident(_))
815 && matches!(self.tokens.get(self.pos + 1), Some(Token::Arrow))
816 {
817 let field = match self.peek() {
818 Token::DotIdent(n) | Token::Ident(n) => n.clone(),
819 _ => unreachable!("guarded by the matches! above"),
820 };
821 return Err(ParseError::Unsupported {
822 feature: format!(
823 "cannot assign to a JSON path target `.{field}->...` (at token {pos}): \
824 JSON path assignment targets are not supported; write the whole JSON \
825 column instead (path mutation such as json_set is not yet available)",
826 pos = self.pos
827 ),
828 });
829 }
830 let field = self.expect_named_ident("field name")?;
831 self.expect(&Token::Assign)?;
832 let value = self.parse_expr()?;
833 assignments.push(Assignment { field, value });
834 if *self.peek() == Token::Comma {
835 self.advance();
836 }
837 }
838 self.expect(&Token::RBrace)?;
839 Ok(assignments)
840 }
841
842 fn parse_projection(&mut self) -> Result<Vec<ProjectionField>, ParseError> {
843 self.expect(&Token::LBrace)?;
844 let mut fields = Vec::new();
845 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
846 if matches!(self.peek(), Token::Ident(_))
854 && matches!(self.tokens.get(self.pos + 1), Some(Token::Colon))
855 {
856 let alias = match self.advance() {
857 Token::Ident(name) => name,
858 _ => unreachable!("guarded by the matches! above"),
859 };
860 self.advance(); self.reject_bare_dotted_path()?;
862 let expr = if matches!(self.peek(), Token::Ident(_))
865 && matches!(self.tokens.get(self.pos + 1), Some(Token::As))
866 {
867 Expr::NestedQuery(Box::new(self.parse_nested_query()?))
868 } else if self.at_link_traversal() {
869 Expr::NestedQuery(Box::new(self.parse_link_traversal()?))
875 } else if self.at_scalar_link_path() {
876 self.parse_scalar_link_path()?
878 } else {
879 self.parse_expr()?
880 };
881 fields.push(ProjectionField {
882 alias: Some(alias),
883 expr,
884 });
885 } else {
886 if matches!(self.peek(), Token::Ident(_))
887 && matches!(self.tokens.get(self.pos + 1), Some(Token::As))
888 {
889 return Err(ParseError::Syntax {
890 message: "a nested projection needs a field name: \
891 `<name>: <Table> as <alias> filter ... { ... }`"
892 .into(),
893 });
894 }
895 self.reject_bare_dotted_path()?;
896 let expr = if self.at_scalar_link_path() {
897 self.parse_scalar_link_path()?
900 } else {
901 self.parse_expr()?
902 };
903 fields.push(ProjectionField { alias: None, expr });
904 }
905 if *self.peek() == Token::Comma {
906 self.advance();
907 }
908 }
909 self.expect(&Token::RBrace)?;
910 Ok(fields)
911 }
912
913 fn reject_bare_dotted_path(&self) -> Result<(), ParseError> {
920 if let (Token::DotIdent(first), Some(Token::DotIdent(second))) =
921 (self.peek(), self.tokens.get(self.pos + 1))
922 {
923 return Err(ParseError::Syntax {
924 message: format!(
925 "`.{first}.{second}` is ambiguous in a projection: for a link \
926 path, alias the table and qualify the path \
927 (`Order as o {{ o.{first}.{second} }}`); for separate fields, \
928 separate them with commas (`.{first}, .{second}`)"
929 ),
930 });
931 }
932 Ok(())
933 }
934
935 fn at_link_traversal(&self) -> bool {
941 matches!(self.peek(), Token::Ident(_))
942 && matches!(self.tokens.get(self.pos + 1), Some(Token::DotIdent(_)))
943 && matches!(
944 self.tokens.get(self.pos + 2),
945 Some(Token::LBrace | Token::Filter | Token::Order | Token::Limit | Token::Offset)
946 )
947 }
948
949 fn at_scalar_link_path(&self) -> bool {
956 matches!(self.peek(), Token::Ident(_))
957 && matches!(self.tokens.get(self.pos + 1), Some(Token::DotIdent(_)))
958 && matches!(self.tokens.get(self.pos + 2), Some(Token::DotIdent(_)))
959 }
960
961 fn parse_scalar_link_path(&mut self) -> Result<Expr, ParseError> {
967 let outer_alias = match self.advance() {
968 Token::Ident(n) => n,
969 t => return Err(self.named_ident_error("link path outer alias", &t)),
970 };
971 let mut parts = Vec::new();
972 while let Some(Token::DotIdent(_)) = self.tokens.get(self.pos) {
973 match self.advance() {
974 Token::DotIdent(n) => parts.push(n),
975 _ => unreachable!("peeked DotIdent"),
976 }
977 }
978 debug_assert!(parts.len() >= 2, "guarded by at_scalar_link_path");
979 let column = parts.pop().expect("at least two parts");
980 Ok(Expr::LinkPath {
981 outer_alias,
982 links: parts,
983 column,
984 })
985 }
986
987 fn parse_link_traversal(&mut self) -> Result<NestedQuery, ParseError> {
997 self.depth += 1;
998 if self.depth > MAX_NESTING_DEPTH {
999 self.depth -= 1;
1000 return Err(ParseError::NestingDepthExceeded {
1001 max: MAX_NESTING_DEPTH,
1002 });
1003 }
1004 let result = self.parse_link_traversal_inner();
1005 self.depth -= 1;
1006 result
1007 }
1008
1009 fn parse_link_traversal_inner(&mut self) -> Result<NestedQuery, ParseError> {
1010 let outer_alias = match self.advance() {
1011 Token::Ident(n) => n,
1012 t => return Err(self.named_ident_error("link outer alias", &t)),
1013 };
1014 let link_name = match self.advance() {
1015 Token::DotIdent(n) => n,
1016 t => {
1017 return Err(ParseError::UnexpectedToken {
1018 expected: "link name".into(),
1019 got: t.display_name(),
1020 })
1021 }
1022 };
1023 let child_alias = link_name.clone();
1026
1027 let mut residual = None;
1031 if *self.peek() == Token::Filter {
1032 self.advance();
1033 residual = Some(qualify_bare_fields(self.parse_expr()?, &child_alias));
1034 }
1035 let mut order = None;
1036 let mut limit = None;
1037 let mut offset = None;
1038 let mut offset_before_limit = false;
1039 loop {
1040 match self.peek() {
1041 Token::Order => {
1042 self.advance();
1043 let mut clause = self.parse_order()?;
1044 for key in &mut clause.keys {
1045 key.expr = qualify_bare_fields(
1046 std::mem::replace(&mut key.expr, Expr::Null),
1047 &child_alias,
1048 );
1049 }
1050 order = Some(clause);
1051 }
1052 Token::Limit => {
1053 self.advance();
1054 if offset.is_some() {
1055 offset_before_limit = true;
1056 }
1057 limit = Some(self.parse_expr()?);
1058 }
1059 Token::Offset => {
1060 self.advance();
1061 offset = Some(self.parse_expr()?);
1062 }
1063 _ => break,
1064 }
1065 }
1066 self.expect(&Token::LBrace)?;
1067 let mut fields = Vec::new();
1068 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1069 let alias = if matches!(self.peek(), Token::Ident(_))
1070 && matches!(self.tokens.get(self.pos + 1), Some(Token::Colon))
1071 {
1072 let alias = self.expect_named_ident("field alias")?;
1073 self.advance(); Some(alias)
1075 } else {
1076 None
1077 };
1078 self.reject_bare_dotted_path()?;
1079 let expr = qualify_bare_fields(self.parse_expr()?, &child_alias);
1080 fields.push(ProjectionField { alias, expr });
1081 if *self.peek() == Token::Comma {
1082 self.advance();
1083 }
1084 }
1085 self.expect(&Token::RBrace)?;
1086 if fields.is_empty() {
1087 return Err(ParseError::Syntax {
1088 message: "link traversal requires at least one field".into(),
1089 });
1090 }
1091 Ok(NestedQuery {
1092 source: String::new(),
1094 alias: child_alias,
1095 via_link: Some(ViaLink {
1096 outer_alias,
1097 link_name,
1098 }),
1099 filter: residual.unwrap_or(Expr::Literal(Literal::Bool(true))),
1102 order,
1103 limit,
1104 offset,
1105 offset_before_limit,
1106 fields,
1107 })
1108 }
1109
1110 fn parse_nested_query(&mut self) -> Result<NestedQuery, ParseError> {
1115 self.depth += 1;
1118 if self.depth > MAX_NESTING_DEPTH {
1119 self.depth -= 1;
1120 return Err(ParseError::NestingDepthExceeded {
1121 max: MAX_NESTING_DEPTH,
1122 });
1123 }
1124 let result = self.parse_nested_query_inner();
1125 self.depth -= 1;
1126 result
1127 }
1128
1129 fn parse_nested_query_inner(&mut self) -> Result<NestedQuery, ParseError> {
1130 let source = self.expect_named_ident("nested source type")?;
1131 self.expect(&Token::As)?;
1132 let alias = self.expect_named_ident("nested source alias")?;
1133 self.expect(&Token::Filter)?;
1134 let filter = self.parse_expr()?;
1135 let mut order = None;
1136 let mut limit = None;
1137 let mut offset = None;
1138 let mut offset_before_limit = false;
1139 loop {
1140 match self.peek() {
1141 Token::Order => {
1142 self.advance();
1143 order = Some(self.parse_order()?);
1144 }
1145 Token::Limit => {
1146 self.advance();
1147 if offset.is_some() {
1148 offset_before_limit = true;
1149 }
1150 limit = Some(self.parse_expr()?);
1151 }
1152 Token::Offset => {
1153 self.advance();
1154 offset = Some(self.parse_expr()?);
1155 }
1156 _ => break,
1157 }
1158 }
1159 self.expect(&Token::LBrace)?;
1160 let mut fields = Vec::new();
1161 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
1162 let alias = if matches!(self.peek(), Token::Ident(_))
1163 && matches!(self.tokens.get(self.pos + 1), Some(Token::Colon))
1164 {
1165 let alias = self.expect_named_ident("field alias")?;
1166 self.advance(); Some(alias)
1168 } else {
1169 None
1170 };
1171 let expr = if matches!(self.peek(), Token::Ident(_))
1172 && matches!(self.tokens.get(self.pos + 1), Some(Token::As))
1173 {
1174 if alias.is_none() {
1175 return Err(ParseError::Syntax {
1176 message: "a nested projection needs a field name: \
1177 `<name>: <Table> as <alias> filter ... { ... }`"
1178 .into(),
1179 });
1180 }
1181 Expr::NestedQuery(Box::new(self.parse_nested_query()?))
1182 } else {
1183 self.reject_bare_dotted_path()?;
1184 self.parse_expr()?
1185 };
1186 fields.push(ProjectionField { alias, expr });
1187 if *self.peek() == Token::Comma {
1188 self.advance();
1189 }
1190 }
1191 self.expect(&Token::RBrace)?;
1192 if fields.is_empty() {
1193 return Err(ParseError::Syntax {
1194 message: "nested projection requires at least one field".into(),
1195 });
1196 }
1197 Ok(NestedQuery {
1198 source,
1199 alias,
1200 via_link: None,
1201 filter,
1202 order,
1203 limit,
1204 offset,
1205 offset_before_limit,
1206 fields,
1207 })
1208 }
1209
1210 fn parse_over_clause(&mut self) -> Result<(Vec<Expr>, Vec<OrderKey>), ParseError> {
1213 self.expect(&Token::Over)?;
1214 self.expect(&Token::LParen)?;
1215 let mut partition_by = Vec::new();
1216 let mut order_by = Vec::new();
1217 if *self.peek() == Token::Partition {
1218 self.advance();
1219 loop {
1220 partition_by.push(self.parse_expr()?);
1221 if *self.peek() == Token::Comma {
1222 if !matches!(
1223 self.tokens.get(self.pos + 1),
1224 Some(Token::Order | Token::RParen)
1225 ) {
1226 self.advance();
1227 } else {
1228 break;
1229 }
1230 } else {
1231 break;
1232 }
1233 }
1234 }
1235 if *self.peek() == Token::Order {
1236 self.advance();
1237 loop {
1238 let expr = self.parse_expr()?;
1239 let descending = match self.peek() {
1240 Token::Desc => {
1241 self.advance();
1242 true
1243 }
1244 Token::Asc => {
1245 self.advance();
1246 false
1247 }
1248 _ => false,
1249 };
1250 order_by.push(OrderKey { expr, descending });
1251 if *self.peek() == Token::Comma {
1252 self.advance();
1253 } else {
1254 break;
1255 }
1256 }
1257 }
1258 self.expect(&Token::RParen)?;
1259 Ok((partition_by, order_by))
1260 }
1261
1262 fn parse_cast_type(&mut self) -> Result<CastType, ParseError> {
1264 match self.advance() {
1265 Token::StringLit(s) => match s.as_str() {
1266 "int" | "Int" | "INT" => Ok(CastType::Int),
1267 "float" | "Float" | "FLOAT" => Ok(CastType::Float),
1268 "str" | "Str" | "STR" | "string" | "String" => Ok(CastType::Str),
1269 "bool" | "Bool" | "BOOL" | "boolean" => Ok(CastType::Bool),
1270 "datetime" | "DateTime" | "DATETIME" => Ok(CastType::DateTime),
1271 "uuid" | "Uuid" | "UUID" => Ok(CastType::Uuid),
1272 "bytes" | "Bytes" | "BYTES" | "bytea" => Ok(CastType::Bytes),
1273 other => Err(ParseError::Syntax {
1274 message: format!("invalid cast type: \"{other}\""),
1275 }),
1276 },
1277 t => Err(ParseError::UnexpectedToken {
1278 expected: "string literal for cast type".into(),
1279 got: t.display_name(),
1280 }),
1281 }
1282 }
1283
1284 fn parse_order(&mut self) -> Result<OrderClause, ParseError> {
1285 let mut keys = Vec::new();
1286 loop {
1287 let expr = self.parse_expr()?;
1288 let descending = match self.peek() {
1289 Token::Desc => {
1290 self.advance();
1291 true
1292 }
1293 Token::Asc => {
1294 self.advance();
1295 false
1296 }
1297 _ => false,
1298 };
1299 keys.push(OrderKey { expr, descending });
1300 if *self.peek() == Token::Comma {
1301 self.advance();
1302 } else {
1303 break;
1304 }
1305 }
1306 Ok(OrderClause { keys })
1307 }
1308
1309 fn parse_aggregate_query(&mut self) -> Result<Statement, ParseError> {
1310 let mut func = match self.advance() {
1311 Token::Count => AggFunc::Count,
1312 Token::Avg => AggFunc::Avg,
1313 Token::Sum => AggFunc::Sum,
1314 Token::Min => AggFunc::Min,
1315 Token::Max => AggFunc::Max,
1316 t => {
1317 return Err(ParseError::UnexpectedToken {
1318 expected: "aggregate function".into(),
1319 got: t.display_name(),
1320 })
1321 }
1322 };
1323 self.expect(&Token::LParen)?;
1324 let mode = if *self.peek() == Token::Raw {
1325 self.advance();
1326 AggregateMode::Raw
1327 } else {
1328 AggregateMode::Symmetric
1329 };
1330 if func == AggFunc::Count && *self.peek() == Token::Distinct {
1332 self.advance();
1333 func = AggFunc::CountDistinct;
1334 }
1335 let source = match self.advance() {
1336 Token::Ident(name) => name,
1337 t => {
1338 return Err(ParseError::UnexpectedToken {
1339 expected: "type name".into(),
1340 got: t.display_name(),
1341 })
1342 }
1343 };
1344 let mut query = self.parse_query_tail(source)?;
1348 self.expect(&Token::RParen)?;
1349
1350 let mut argument: Option<Expr> = None;
1363 if let Some(proj) = &query.projection {
1364 if proj.len() == 1 && proj[0].alias.is_none() {
1365 argument = Some(proj[0].expr.clone());
1366 }
1367 }
1368 if argument.is_some() {
1369 query.projection = None;
1370 }
1371 query.aggregation = Some(AggregateExpr {
1372 function: func,
1373 argument,
1374 mode,
1375 });
1376 Ok(Statement::Query(query))
1377 }
1378
1379 fn parse_expr(&mut self) -> Result<Expr, ParseError> {
1380 self.depth += 1;
1381 if self.depth > MAX_NESTING_DEPTH {
1382 self.depth -= 1;
1383 return Err(ParseError::NestingDepthExceeded {
1384 max: MAX_NESTING_DEPTH,
1385 });
1386 }
1387 let result = self.parse_or_expr();
1388 self.depth -= 1;
1389 result
1390 }
1391
1392 fn check_chain_depth(&self, chain: usize) -> Result<(), ParseError> {
1397 if self.depth + chain > MAX_NESTING_DEPTH {
1398 return Err(ParseError::NestingDepthExceeded {
1399 max: MAX_NESTING_DEPTH,
1400 });
1401 }
1402 Ok(())
1403 }
1404
1405 fn parse_or_expr(&mut self) -> Result<Expr, ParseError> {
1406 let mut left = self.parse_and_expr()?;
1407 let mut chain = 0usize;
1408 while *self.peek() == Token::Or {
1409 chain += 1;
1410 self.check_chain_depth(chain)?;
1411 self.advance();
1412 let right = self.parse_and_expr()?;
1413 left = Expr::BinaryOp(Box::new(left), BinOp::Or, Box::new(right));
1414 }
1415 Ok(left)
1416 }
1417
1418 fn parse_and_expr(&mut self) -> Result<Expr, ParseError> {
1419 let mut left = self.parse_comparison()?;
1420 let mut chain = 0usize;
1421 while *self.peek() == Token::And {
1422 chain += 1;
1423 self.check_chain_depth(chain)?;
1424 self.advance();
1425 let right = self.parse_comparison()?;
1426 left = Expr::BinaryOp(Box::new(left), BinOp::And, Box::new(right));
1427 }
1428 Ok(left)
1429 }
1430
1431 fn parse_comparison(&mut self) -> Result<Expr, ParseError> {
1432 let mut negations = 0usize;
1442 while *self.peek() == Token::Not
1443 && !matches!(self.tokens.get(self.pos + 1), Some(Token::Exists))
1444 {
1445 self.advance();
1446 negations += 1;
1447 self.check_chain_depth(negations)?;
1448 }
1449 let mut expr = self.parse_comparison_body()?;
1450 for _ in 0..negations {
1451 expr = Expr::UnaryOp(UnaryOp::Not, Box::new(expr));
1452 }
1453 Ok(expr)
1454 }
1455
1456 fn parse_comparison_body(&mut self) -> Result<Expr, ParseError> {
1457 let left = self.parse_additive()?;
1458
1459 if *self.peek() == Token::Is {
1461 self.advance();
1462 if *self.peek() == Token::Not {
1463 self.advance();
1464 self.expect(&Token::Null)?;
1465 return Ok(Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(left)));
1466 } else {
1467 self.expect(&Token::Null)?;
1468 return Ok(Expr::UnaryOp(UnaryOp::IsNull, Box::new(left)));
1469 }
1470 }
1471
1472 match self.peek() {
1475 Token::In => {
1476 self.advance();
1477 return self.parse_in_list(left, false);
1478 }
1479 Token::Like => {
1480 self.advance();
1481 let pattern = self.parse_additive()?;
1482 return Ok(Expr::BinaryOp(
1483 Box::new(left),
1484 BinOp::Like,
1485 Box::new(pattern),
1486 ));
1487 }
1488 Token::Between => {
1489 self.advance();
1490 return self.parse_between(left, false);
1491 }
1492 Token::Not => {
1493 let next = self.tokens.get(self.pos + 1);
1497 match next {
1498 Some(Token::In) => {
1499 self.advance(); self.advance(); return self.parse_in_list(left, true);
1502 }
1503 Some(Token::Like) => {
1504 self.advance(); self.advance(); let pattern = self.parse_additive()?;
1507 let like = Expr::BinaryOp(Box::new(left), BinOp::Like, Box::new(pattern));
1508 return Ok(Expr::UnaryOp(UnaryOp::Not, Box::new(like)));
1509 }
1510 Some(Token::Between) => {
1511 self.advance(); self.advance(); return self.parse_between(left, true);
1514 }
1515 _ => {}
1516 }
1517 }
1518 _ => {}
1519 }
1520
1521 let op = match self.peek() {
1522 Token::Eq => BinOp::Eq,
1523 Token::Neq => BinOp::Neq,
1524 Token::Lt => BinOp::Lt,
1525 Token::Gt => BinOp::Gt,
1526 Token::Lte => BinOp::Lte,
1527 Token::Gte => BinOp::Gte,
1528 _ => return Ok(left),
1529 };
1530 self.advance();
1531 if *self.peek() == Token::Null {
1535 match op {
1536 BinOp::Eq => {
1537 self.advance();
1538 return Ok(Expr::UnaryOp(UnaryOp::IsNull, Box::new(left)));
1539 }
1540 BinOp::Neq => {
1541 self.advance();
1542 return Ok(Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(left)));
1543 }
1544 _ => {}
1545 }
1546 }
1547 let right = self.parse_additive()?;
1548 Ok(Expr::BinaryOp(Box::new(left), op, Box::new(right)))
1549 }
1550
1551 fn parse_in_list(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParseError> {
1556 self.expect(&Token::LParen)?;
1557 if let Token::Ident(_) = self.peek() {
1559 let after = self.tokens.get(self.pos + 1);
1562 let is_subquery = !matches!(after, Some(Token::Comma) | Some(Token::RParen));
1563 if is_subquery {
1564 let source = match self.advance() {
1565 Token::Ident(name) => name,
1566 _ => unreachable!(),
1567 };
1568 let subquery = self.parse_query_tail(source)?;
1569 self.expect(&Token::RParen)?;
1570 return Ok(Expr::InSubquery {
1571 expr: Box::new(expr),
1572 subquery: Box::new(subquery),
1573 negated,
1574 });
1575 }
1576 }
1577 let mut list = Vec::new();
1578 while !matches!(self.peek(), Token::RParen | Token::Eof) {
1579 list.push(self.parse_expr()?);
1580 if *self.peek() == Token::Comma {
1581 self.advance();
1582 }
1583 }
1584 self.expect(&Token::RParen)?;
1585 Ok(Expr::InList {
1586 expr: Box::new(expr),
1587 list,
1588 negated,
1589 })
1590 }
1591
1592 fn try_parse_exists_subquery(&mut self) -> Result<Option<QueryExpr>, ParseError> {
1600 if *self.peek() != Token::LParen {
1601 return Ok(None);
1602 }
1603 let after_lparen = self.tokens.get(self.pos + 1);
1607 if !matches!(after_lparen, Some(Token::Ident(_))) {
1608 return Ok(None);
1609 }
1610 self.expect(&Token::LParen)?;
1611 let source = match self.advance() {
1612 Token::Ident(name) => name,
1613 _ => unreachable!(),
1614 };
1615 let subquery = self.parse_query_tail(source)?;
1616 self.expect(&Token::RParen)?;
1617 Ok(Some(subquery))
1618 }
1619
1620 fn parse_between(&mut self, expr: Expr, negated: bool) -> Result<Expr, ParseError> {
1624 let low = self.parse_additive()?;
1625 self.expect(&Token::And)?;
1626 let high = self.parse_additive()?;
1627 if negated {
1628 Ok(Expr::BinaryOp(
1630 Box::new(Expr::BinaryOp(
1631 Box::new(expr.clone()),
1632 BinOp::Lt,
1633 Box::new(low),
1634 )),
1635 BinOp::Or,
1636 Box::new(Expr::BinaryOp(Box::new(expr), BinOp::Gt, Box::new(high))),
1637 ))
1638 } else {
1639 Ok(Expr::BinaryOp(
1641 Box::new(Expr::BinaryOp(
1642 Box::new(expr.clone()),
1643 BinOp::Gte,
1644 Box::new(low),
1645 )),
1646 BinOp::And,
1647 Box::new(Expr::BinaryOp(Box::new(expr), BinOp::Lte, Box::new(high))),
1648 ))
1649 }
1650 }
1651
1652 fn parse_group_by(&mut self) -> Result<GroupByClause, ParseError> {
1654 let mut keys = Vec::new();
1655 loop {
1656 let expr = self.parse_expr()?;
1657 let output_name = match &expr {
1658 Expr::Field(_) | Expr::QualifiedField { .. } | Expr::JsonPath { .. } => {
1659 expression_output_name(&expr)
1660 }
1661 _ => format!("__group_{}", keys.len()),
1662 };
1663 keys.push(GroupKey { expr, output_name });
1664 if *self.peek() == Token::Comma {
1665 self.advance();
1666 } else {
1667 break;
1668 }
1669 }
1670 if keys.is_empty() {
1671 return Err(ParseError::Syntax {
1672 message: "expected at least one group key after group".into(),
1673 });
1674 }
1675 let having = if *self.peek() == Token::Having {
1676 self.advance();
1677 Some(self.parse_expr()?)
1678 } else {
1679 None
1680 };
1681 Ok(GroupByClause { keys, having })
1682 }
1683
1684 fn parse_additive(&mut self) -> Result<Expr, ParseError> {
1685 let mut left = self.parse_multiplicative()?;
1686 let mut chain = 0usize;
1687 loop {
1688 let op = match self.peek() {
1689 Token::Plus => BinOp::Add,
1690 Token::Minus => BinOp::Sub,
1691 Token::Coalesce => {
1692 chain += 1;
1693 self.check_chain_depth(chain)?;
1694 self.advance();
1695 let right = self.parse_multiplicative()?;
1696 left = Expr::Coalesce(Box::new(left), Box::new(right));
1697 continue;
1698 }
1699 _ => break,
1700 };
1701 chain += 1;
1702 self.check_chain_depth(chain)?;
1703 self.advance();
1704 let right = self.parse_multiplicative()?;
1705 left = Expr::BinaryOp(Box::new(left), op, Box::new(right));
1706 }
1707 Ok(left)
1708 }
1709
1710 fn parse_multiplicative(&mut self) -> Result<Expr, ParseError> {
1711 let mut left = self.parse_primary()?;
1712 let mut chain = 0usize;
1713 loop {
1714 let op = match self.peek() {
1715 Token::Star => BinOp::Mul,
1716 Token::Slash => BinOp::Div,
1717 _ => break,
1718 };
1719 chain += 1;
1720 self.check_chain_depth(chain)?;
1721 self.advance();
1722 let right = self.parse_primary()?;
1723 left = Expr::BinaryOp(Box::new(left), op, Box::new(right));
1724 }
1725 Ok(left)
1726 }
1727
1728 fn parse_primary(&mut self) -> Result<Expr, ParseError> {
1729 self.depth += 1;
1734 if self.depth > MAX_NESTING_DEPTH {
1735 self.depth -= 1;
1736 return Err(ParseError::NestingDepthExceeded {
1737 max: MAX_NESTING_DEPTH,
1738 });
1739 }
1740 let result = self.parse_primary_inner();
1741 self.depth -= 1;
1742 self.parse_json_path_postfix(result?)
1748 }
1749
1750 fn parse_json_path_postfix(&mut self, base: Expr) -> Result<Expr, ParseError> {
1757 if *self.peek() != Token::Arrow {
1758 return Ok(base);
1759 }
1760 match &base {
1761 Expr::Field(_) | Expr::QualifiedField { .. } | Expr::JsonPath { .. } => {}
1762 _ => {
1763 return Err(ParseError::Syntax {
1764 message: "'->' JSON path access requires a field base \
1765 (e.g. .data->key or posts.data->author)"
1766 .into(),
1767 })
1768 }
1769 }
1770 let mut segments = Vec::new();
1771 while *self.peek() == Token::Arrow {
1772 self.advance(); let seg = match self.advance() {
1774 Token::Ident(name) => PathSeg::Key(name),
1776 Token::StringLit(s) => PathSeg::Key(s),
1779 Token::IntLit(v) => {
1783 let idx = u32::try_from(v).map_err(|_| ParseError::Syntax {
1784 message: format!(
1785 "invalid JSON path array index {v}: expected a non-negative integer that fits in 32 bits"
1786 ),
1787 })?;
1788 PathSeg::Index(idx)
1789 }
1790 other => {
1791 return Err(ParseError::Syntax {
1792 message: format!(
1793 "expected a JSON path segment (object key or array index) after '->', found {}",
1794 other.display_name()
1795 ),
1796 })
1797 }
1798 };
1799 segments.push(seg);
1800 }
1801 if let Expr::JsonPath {
1804 base: inner_base,
1805 segments: mut inner_segments,
1806 } = base
1807 {
1808 inner_segments.extend(segments);
1809 return Ok(Expr::JsonPath {
1810 base: inner_base,
1811 segments: inner_segments,
1812 });
1813 }
1814 Ok(Expr::JsonPath {
1815 base: Box::new(base),
1816 segments,
1817 })
1818 }
1819
1820 fn parse_primary_inner(&mut self) -> Result<Expr, ParseError> {
1821 match self.peek().clone() {
1822 Token::DotIdent(name) => {
1823 self.advance();
1824 Ok(Expr::Field(name))
1825 }
1826 Token::IntLit(v) => {
1827 self.advance();
1828 Ok(Expr::Literal(Literal::Int(v)))
1829 }
1830 Token::FloatLit(v) => {
1831 self.advance();
1832 Ok(Expr::Literal(Literal::Float(v)))
1833 }
1834 Token::StringLit(v) => {
1835 self.advance();
1836 Ok(Expr::Literal(Literal::String(v)))
1837 }
1838 Token::BoolLit(v) => {
1839 self.advance();
1840 Ok(Expr::Literal(Literal::Bool(v)))
1841 }
1842 Token::Null => {
1849 self.advance();
1850 Ok(Expr::Null)
1851 }
1852 Token::Not => {
1853 self.advance();
1854 if *self.peek() == Token::Exists {
1855 self.advance();
1856 if let Some(sub) = self.try_parse_exists_subquery()? {
1860 return Ok(Expr::ExistsSubquery {
1861 subquery: Box::new(sub),
1862 negated: true,
1863 });
1864 }
1865 let expr = self.parse_primary()?;
1866 Ok(Expr::UnaryOp(UnaryOp::NotExists, Box::new(expr)))
1867 } else {
1868 let expr = self.parse_primary()?;
1869 Ok(Expr::UnaryOp(UnaryOp::Not, Box::new(expr)))
1870 }
1871 }
1872 Token::Exists => {
1873 self.advance();
1874 if let Some(sub) = self.try_parse_exists_subquery()? {
1878 return Ok(Expr::ExistsSubquery {
1879 subquery: Box::new(sub),
1880 negated: false,
1881 });
1882 }
1883 let expr = self.parse_primary()?;
1884 Ok(Expr::UnaryOp(UnaryOp::Exists, Box::new(expr)))
1885 }
1886 Token::LParen => {
1887 self.advance();
1888 let expr = self.parse_expr()?;
1889 self.expect(&Token::RParen)?;
1890 Ok(expr)
1891 }
1892 Token::Ident(name) => {
1893 self.advance();
1894 if *self.peek() == Token::LParen {
1899 let cast_type = match name.as_str() {
1900 "uuid" => Some(CastType::Uuid),
1901 "bytes" => Some(CastType::Bytes),
1902 _ => None,
1903 };
1904 if let Some(cast_type) = cast_type {
1905 self.advance(); let inner = self.parse_expr()?;
1907 self.expect(&Token::RParen)?;
1908 return Ok(Expr::Cast(Box::new(inner), cast_type));
1909 }
1910 }
1911 if let Token::DotIdent(field) = self.peek().clone() {
1915 self.advance();
1916 return Ok(Expr::QualifiedField {
1917 qualifier: name,
1918 field,
1919 });
1920 }
1921 Ok(Expr::Field(name))
1922 }
1923 Token::RowNumber | Token::Rank | Token::DenseRank => {
1925 let wfunc = match self.advance() {
1926 Token::RowNumber => WindowFunc::RowNumber,
1927 Token::Rank => WindowFunc::Rank,
1928 Token::DenseRank => WindowFunc::DenseRank,
1929 _ => {
1930 return Err(ParseError::Syntax {
1931 message: "unexpected window function token".into(),
1932 })
1933 }
1934 };
1935 self.expect(&Token::LParen)?;
1936 self.expect(&Token::RParen)?;
1937 let (partition_by, order_by) = self.parse_over_clause()?;
1938 Ok(Expr::Window {
1939 function: wfunc,
1940 args: vec![],
1941 mode: AggregateMode::Symmetric,
1942 partition_by,
1943 order_by,
1944 })
1945 }
1946 Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
1950 let mut func = match self.advance() {
1951 Token::Count => AggFunc::Count,
1952 Token::Avg => AggFunc::Avg,
1953 Token::Sum => AggFunc::Sum,
1954 Token::Min => AggFunc::Min,
1955 Token::Max => AggFunc::Max,
1956 _ => {
1957 return Err(ParseError::Syntax {
1958 message: "unexpected aggregate token".into(),
1959 })
1960 }
1961 };
1962 self.expect(&Token::LParen)?;
1963 let mode = if *self.peek() == Token::Raw {
1964 self.advance();
1965 AggregateMode::Raw
1966 } else {
1967 AggregateMode::Symmetric
1968 };
1969 if func == AggFunc::Count && *self.peek() == Token::Star {
1971 self.advance();
1972 self.expect(&Token::RParen)?;
1973 if *self.peek() == Token::Over {
1975 let (partition_by, order_by) = self.parse_over_clause()?;
1976 return Ok(Expr::Window {
1977 function: WindowFunc::Count,
1978 args: vec![Expr::Field("*".into())],
1979 mode,
1980 partition_by,
1981 order_by,
1982 });
1983 }
1984 return Ok(Expr::FunctionCall(
1985 AggFunc::Count,
1986 Box::new(Expr::Field("*".into())),
1987 mode,
1988 ));
1989 }
1990 if func == AggFunc::Count && *self.peek() == Token::Distinct {
1992 self.advance();
1993 func = AggFunc::CountDistinct;
1994 }
1995 let inner = self.parse_expr()?;
1996 self.expect(&Token::RParen)?;
1997 if *self.peek() == Token::Over {
1999 let wfunc = match func {
2000 AggFunc::Count => WindowFunc::Count,
2001 AggFunc::Avg => WindowFunc::Avg,
2002 AggFunc::Sum => WindowFunc::Sum,
2003 AggFunc::Min => WindowFunc::Min,
2004 AggFunc::Max => WindowFunc::Max,
2005 _ => {
2006 return Err(ParseError::Unsupported {
2007 feature: "count(distinct ...) over (...) is not supported".into(),
2008 })
2009 }
2010 };
2011 let (partition_by, order_by) = self.parse_over_clause()?;
2012 return Ok(Expr::Window {
2013 function: wfunc,
2014 args: vec![inner],
2015 mode,
2016 partition_by,
2017 order_by,
2018 });
2019 }
2020 Ok(Expr::FunctionCall(func, Box::new(inner), mode))
2021 }
2022 Token::Upper
2023 | Token::Lower
2024 | Token::Length
2025 | Token::Trim
2026 | Token::Substring
2027 | Token::Concat
2028 | Token::Abs
2029 | Token::Round
2030 | Token::Ceil
2031 | Token::Floor
2032 | Token::Sqrt
2033 | Token::Pow
2034 | Token::Now
2035 | Token::Extract
2036 | Token::DateAdd
2037 | Token::DateDiff
2038 | Token::JsonType
2039 | Token::JsonText => {
2040 let tok = self.advance();
2041 let func = token_to_scalar_fn(&tok);
2042 self.expect(&Token::LParen)?;
2043 let mut args = Vec::new();
2044 while !matches!(self.peek(), Token::RParen | Token::Eof) {
2045 args.push(self.parse_expr()?);
2046 if *self.peek() == Token::Comma {
2047 self.advance();
2048 }
2049 }
2050 self.expect(&Token::RParen)?;
2051 Ok(Expr::ScalarFunc(func, args))
2052 }
2053 Token::Cast => {
2054 self.advance();
2055 self.expect(&Token::LParen)?;
2056 let inner = self.parse_expr()?;
2057 self.expect(&Token::Comma)?;
2058 let cast_type = self.parse_cast_type()?;
2059 self.expect(&Token::RParen)?;
2060 Ok(Expr::Cast(Box::new(inner), cast_type))
2061 }
2062 Token::Case => {
2063 self.advance();
2064 let mut whens = Vec::new();
2065 while *self.peek() == Token::When {
2066 self.advance();
2067 let condition = self.parse_expr()?;
2068 self.expect(&Token::Then)?;
2069 let result = self.parse_expr()?;
2070 whens.push((Box::new(condition), Box::new(result)));
2071 }
2072 let else_expr = if *self.peek() == Token::Else {
2073 self.advance();
2074 Some(Box::new(self.parse_expr()?))
2075 } else {
2076 None
2077 };
2078 self.expect(&Token::End)?;
2079 Ok(Expr::Case { whens, else_expr })
2080 }
2081 t => Err(ParseError::Syntax {
2082 message: format!("unexpected token in expression: {}", t.display_name()),
2083 }),
2084 }
2085 }
2086
2087 fn parse_create_link(&mut self) -> Result<Statement, ParseError> {
2095 self.expect(&Token::Link)?;
2096 let owner = self.expect_named_ident("link owner type")?;
2097 let name = match self.advance() {
2098 Token::DotIdent(n) => n,
2099 t => {
2100 return Err(ParseError::UnexpectedToken {
2101 expected: "`.<name>` after the owner type (link <Owner>.<name> -> ...)".into(),
2102 got: t.display_name(),
2103 })
2104 }
2105 };
2106 let (target, local_key, target_key) = self.parse_link_tail()?;
2107 Ok(Statement::CreateLink(CreateLinkExpr {
2108 owner,
2109 name,
2110 target,
2111 local_key,
2112 target_key,
2113 }))
2114 }
2115
2116 fn parse_link_tail(&mut self) -> Result<(String, String, String), ParseError> {
2119 self.expect(&Token::Arrow)?;
2120 let target = self.expect_named_ident("link target type")?;
2121 self.expect(&Token::On)?;
2122 let local_key = self.parse_link_column("link local key")?;
2123 self.expect(&Token::Eq)?;
2124 let target_key = self.parse_link_column("link target key")?;
2125 Ok((target, local_key, target_key))
2126 }
2127
2128 fn parse_link_column(&mut self, context: &str) -> Result<String, ParseError> {
2131 match self.advance() {
2132 Token::Ident(n) | Token::DotIdent(n) => Ok(n),
2133 t => Err(self.named_ident_error(context, &t)),
2134 }
2135 }
2136
2137 fn parse_alter_table(&mut self) -> Result<Statement, ParseError> {
2138 self.expect(&Token::Alter)?;
2139 let table = match self.advance() {
2140 Token::Ident(name) => name,
2141 t => {
2142 return Err(ParseError::UnexpectedToken {
2143 expected: "table name after alter".into(),
2144 got: t.display_name(),
2145 })
2146 }
2147 };
2148 match self.peek() {
2149 Token::Add => {
2150 self.advance();
2151 if *self.peek() == Token::Index {
2153 self.advance();
2154 let if_not_exists = self.parse_optional_if_not_exists();
2155 let target = self.parse_index_target("add index")?;
2156 return Ok(Statement::AlterTable(AlterTableExpr {
2157 table,
2158 action: AlterAction::AddIndex {
2159 target,
2160 if_not_exists,
2161 },
2162 }));
2163 }
2164 if *self.peek() == Token::Unique {
2166 self.advance();
2167 let if_not_exists = self.parse_optional_if_not_exists();
2168 let target = self.parse_index_target("add unique")?;
2169 return Ok(Statement::AlterTable(AlterTableExpr {
2170 table,
2171 action: AlterAction::AddUnique {
2172 target,
2173 if_not_exists,
2174 },
2175 }));
2176 }
2177 if *self.peek() == Token::Link {
2179 self.advance();
2180 let name = self.expect_named_ident("link name")?;
2181 let (target, local_key, target_key) = self.parse_link_tail()?;
2182 return Ok(Statement::AlterTable(AlterTableExpr {
2183 table,
2184 action: AlterAction::AddLink {
2185 name,
2186 target,
2187 local_key,
2188 target_key,
2189 },
2190 }));
2191 }
2192 if *self.peek() == Token::Column {
2194 self.advance();
2195 }
2196 let required = if *self.peek() == Token::Required {
2197 self.advance();
2198 true
2199 } else {
2200 false
2201 };
2202 let name = self.expect_named_ident("column name")?;
2203 self.expect(&Token::Colon)?;
2204 let type_name = match self.advance() {
2205 Token::Ident(n) => n,
2206 t => {
2207 return Err(ParseError::UnexpectedToken {
2208 expected: "type name".into(),
2209 got: t.display_name(),
2210 })
2211 }
2212 };
2213 Ok(Statement::AlterTable(AlterTableExpr {
2214 table,
2215 action: AlterAction::AddColumn {
2216 name,
2217 type_name,
2218 required,
2219 },
2220 }))
2221 }
2222 Token::Drop => {
2223 self.advance();
2224 if *self.peek() == Token::Index {
2225 self.advance();
2226 let if_exists = self.parse_optional_if_exists();
2227 let target = self.parse_index_target("drop index")?;
2228 return Ok(Statement::AlterTable(AlterTableExpr {
2229 table,
2230 action: AlterAction::DropIndex { target, if_exists },
2231 }));
2232 }
2233 if *self.peek() == Token::Column {
2235 self.advance();
2236 }
2237 let if_exists = self.parse_optional_if_exists();
2238 let name = self.expect_named_ident("column name")?;
2239 Ok(Statement::AlterTable(AlterTableExpr {
2240 table,
2241 action: AlterAction::DropColumn { name, if_exists },
2242 }))
2243 }
2244 t => Err(ParseError::UnexpectedToken {
2245 expected: "add or drop after alter <table>".into(),
2246 got: t.display_name(),
2247 }),
2248 }
2249 }
2250
2251 fn parse_index_target(&mut self, action: &str) -> Result<IndexTarget, ParseError> {
2256 match self.peek() {
2257 Token::DotIdent(_) => {
2258 if matches!(self.tokens.get(self.pos + 1), Some(Token::Arrow)) {
2259 return Err(ParseError::Syntax {
2260 message: format!(
2261 "JSON path index targets must be parenthesized after {action}; use `(.data->key)`"
2262 ),
2263 });
2264 }
2265 let Token::DotIdent(column) = self.advance() else {
2266 unreachable!("guarded by DotIdent match")
2267 };
2268 Ok(IndexTarget::Column(column))
2269 }
2270 Token::LParen => {
2271 self.advance();
2272 let expr = self.parse_expr().map_err(|error| match error {
2273 ParseError::NestingDepthExceeded { .. } => error,
2274 _ => ParseError::Syntax {
2275 message: format!(
2276 "invalid expression index target after {action}: expected an unqualified JSON path like `(.data->key)`"
2277 ),
2278 },
2279 })?;
2280 if *self.peek() != Token::RParen {
2281 return Err(ParseError::Syntax {
2282 message: format!(
2283 "invalid expression index target after {action}: only a direct JSON path is supported"
2284 ),
2285 });
2286 }
2287 self.advance();
2288
2289 match JsonPathIdentityV1::from_expr(&expr) {
2290 Some(identity) => identity.bind_table_local(None).map(IndexTarget::JsonPath).ok_or_else(|| {
2291 ParseError::Syntax {
2292 message: format!(
2293 "qualified JSON paths are not valid index targets after {action}; use an unqualified table-local path like `(.data->key)`"
2294 ),
2295 }
2296 }),
2297 None => match expr {
2298 Expr::Field(_) => Err(ParseError::Syntax {
2299 message: format!(
2300 "invalid expression index target after {action}: parentheses are reserved for a direct JSON path like `(.data->key)`; use `.column` for a stored column"
2301 ),
2302 }),
2303 Expr::QualifiedField { .. } => Err(ParseError::Syntax {
2304 message: format!(
2305 "qualified references are not valid index targets after {action}; use a table-local `.column` or `(.data->key)`"
2306 ),
2307 }),
2308 _ => Err(ParseError::Syntax {
2309 message: format!(
2310 "invalid expression index target after {action}: only a direct JSON path is supported"
2311 ),
2312 }),
2313 },
2314 }
2315 }
2316 token => Err(ParseError::UnexpectedToken {
2317 expected: format!(".<column> or parenthesized JSON path after {action}"),
2318 got: token.display_name(),
2319 }),
2320 }
2321 }
2322
2323 fn parse_drop_or_drop_view(&mut self) -> Result<Statement, ParseError> {
2325 self.expect(&Token::Drop)?;
2326 if *self.peek() == Token::View {
2327 self.advance(); let if_exists = self.parse_optional_if_exists();
2329 let name = match self.advance() {
2330 Token::Ident(name) => name,
2331 t => {
2332 return Err(ParseError::UnexpectedToken {
2333 expected: "view name after drop view".into(),
2334 got: t.display_name(),
2335 })
2336 }
2337 };
2338 return Ok(Statement::DropView(DropViewExpr { name, if_exists }));
2339 }
2340 let if_exists = self.parse_optional_if_exists();
2341 let table = match self.advance() {
2342 Token::Ident(name) => name,
2343 t => {
2344 return Err(ParseError::UnexpectedToken {
2345 expected: "table name after drop".into(),
2346 got: t.display_name(),
2347 })
2348 }
2349 };
2350 Ok(Statement::DropTable(DropTableExpr { table, if_exists }))
2351 }
2352
2353 fn parse_create_view(&mut self) -> Result<Statement, ParseError> {
2358 self.expect(&Token::Materialized)?;
2359 let name = match self.advance() {
2360 Token::Ident(name) => name,
2361 t => {
2362 return Err(ParseError::UnexpectedToken {
2363 expected: "view name after materialize".into(),
2364 got: t.display_name(),
2365 })
2366 }
2367 };
2368 self.expect(&Token::As)?;
2369 let query_start = self.pos;
2371 let source = match self.advance() {
2372 Token::Ident(s) => s,
2373 t => {
2374 return Err(ParseError::UnexpectedToken {
2375 expected: "source table name".into(),
2376 got: t.display_name(),
2377 })
2378 }
2379 };
2380 let query = self.parse_query_tail(source)?;
2381 let query_text = tokens_to_text(&self.tokens[query_start..self.pos])?;
2383 Ok(Statement::CreateView(CreateViewExpr {
2384 name,
2385 query,
2386 query_text,
2387 }))
2388 }
2389
2390 fn maybe_parse_union(&mut self, left: Statement) -> Result<Statement, ParseError> {
2393 if *self.peek() != Token::Union {
2394 return Ok(left);
2395 }
2396 if !matches!(left, Statement::Query(_) | Statement::Union(_)) {
2397 return Err(ParseError::Syntax {
2398 message: "UNION requires a query on the left side".into(),
2399 });
2400 }
2401 self.advance(); let all = if let Token::Ident(s) = self.peek() {
2403 if s == "all" {
2404 self.advance();
2405 true
2406 } else {
2407 false
2408 }
2409 } else {
2410 false
2411 };
2412 let right = self.parse_single_query()?;
2414 let union = Statement::Union(UnionExpr {
2415 left: Box::new(left),
2416 right: Box::new(right),
2417 all,
2418 });
2419 self.maybe_parse_union(union)
2421 }
2422
2423 fn parse_single_query(&mut self) -> Result<Statement, ParseError> {
2425 match self.peek() {
2426 Token::Count | Token::Avg | Token::Sum | Token::Min | Token::Max => {
2427 self.parse_aggregate_query()
2428 }
2429 Token::Ident(_) => self.parse_query_or_mutation(),
2430 _ => Err(ParseError::Syntax {
2431 message: format!(
2432 "expected query after UNION, got {}",
2433 self.peek().display_name()
2434 ),
2435 }),
2436 }
2437 }
2438
2439 fn parse_refresh_view(&mut self) -> Result<Statement, ParseError> {
2441 self.expect(&Token::Refresh)?;
2442 let name = match self.advance() {
2443 Token::Ident(name) => name,
2444 t => {
2445 return Err(ParseError::UnexpectedToken {
2446 expected: "view name after refresh".into(),
2447 got: t.display_name(),
2448 })
2449 }
2450 };
2451 Ok(Statement::RefreshView(RefreshViewExpr { name }))
2452 }
2453
2454 fn parse_create_type(&mut self) -> Result<Statement, ParseError> {
2455 self.expect(&Token::Type)?;
2456 let name = self.expect_named_ident("type name")?;
2457 let if_not_exists = self.parse_optional_if_not_exists();
2458 self.expect(&Token::LBrace)?;
2459 let mut fields = Vec::new();
2460 while !matches!(self.peek(), Token::RBrace | Token::Eof) {
2461 let (mut required, mut unique, mut auto) = (false, false, false);
2466 loop {
2467 let is_modifier =
2468 matches!(self.peek(), Token::Required | Token::Unique | Token::Auto)
2469 && !matches!(self.tokens.get(self.pos + 1), Some(Token::Colon));
2470 if !is_modifier {
2471 break;
2472 }
2473 match self.advance() {
2474 Token::Required => required = true,
2475 Token::Unique => unique = true,
2476 Token::Auto => auto = true,
2477 _ => unreachable!("guarded by is_modifier"),
2478 }
2479 }
2480 let field_name = self.expect_named_ident("field name")?;
2481 self.expect(&Token::Colon)?;
2482 let type_name = match self.advance() {
2483 Token::Ident(n) => n,
2484 t => {
2485 return Err(ParseError::UnexpectedToken {
2486 expected: "type name".into(),
2487 got: t.display_name(),
2488 })
2489 }
2490 };
2491 let default = if *self.peek() == Token::Default {
2494 self.advance();
2495 Some(self.parse_default_literal()?)
2496 } else {
2497 None
2498 };
2499 fields.push(FieldDef {
2500 name: field_name,
2501 type_name,
2502 required,
2503 unique,
2504 default,
2505 auto,
2506 });
2507 if *self.peek() == Token::Comma {
2508 self.advance();
2509 }
2510 }
2511 self.expect(&Token::RBrace)?;
2512 Ok(Statement::CreateType(CreateTypeExpr {
2513 name,
2514 fields,
2515 if_not_exists,
2516 }))
2517 }
2518
2519 fn parse_schema(&mut self) -> Result<Statement, ParseError> {
2522 self.expect(&Token::Schema)?;
2523 if let Token::Ident(name) = self.peek() {
2524 if name == "links" {
2525 self.advance();
2526 return Ok(Statement::ListLinks);
2527 }
2528 let table = self.expect_named_ident("type name")?;
2529 return Ok(Statement::Describe(table));
2530 }
2531 Ok(Statement::ListTypes)
2532 }
2533
2534 fn parse_describe(&mut self) -> Result<Statement, ParseError> {
2536 self.expect(&Token::Describe)?;
2537 let table = self.expect_named_ident("type name")?;
2538 Ok(Statement::Describe(table))
2539 }
2540
2541 fn parse_default_literal(&mut self) -> Result<Literal, ParseError> {
2545 match self.advance() {
2546 Token::IntLit(v) => Ok(Literal::Int(v)),
2547 Token::FloatLit(v) => Ok(Literal::Float(v)),
2548 Token::StringLit(v) => Ok(Literal::String(v)),
2549 Token::BoolLit(v) => Ok(Literal::Bool(v)),
2550 t => Err(ParseError::UnexpectedToken {
2551 expected: "literal default value".into(),
2552 got: t.display_name(),
2553 }),
2554 }
2555 }
2556}
2557
2558fn qualify_bare_fields(expr: Expr, alias: &str) -> Expr {
2565 let recur = |e: Expr| Box::new(qualify_bare_fields(e, alias));
2566 match expr {
2567 Expr::Field(field) => Expr::QualifiedField {
2568 qualifier: alias.to_string(),
2569 field,
2570 },
2571 Expr::BinaryOp(l, op, r) => Expr::BinaryOp(recur(*l), op, recur(*r)),
2572 Expr::UnaryOp(op, inner) => Expr::UnaryOp(op, recur(*inner)),
2573 Expr::Coalesce(l, r) => Expr::Coalesce(recur(*l), recur(*r)),
2574 Expr::Cast(inner, ty) => Expr::Cast(recur(*inner), ty),
2575 Expr::ScalarFunc(func, args) => Expr::ScalarFunc(
2576 func,
2577 args.into_iter()
2578 .map(|a| qualify_bare_fields(a, alias))
2579 .collect(),
2580 ),
2581 Expr::InList {
2582 expr,
2583 list,
2584 negated,
2585 } => Expr::InList {
2586 expr: recur(*expr),
2587 list: list
2588 .into_iter()
2589 .map(|a| qualify_bare_fields(a, alias))
2590 .collect(),
2591 negated,
2592 },
2593 Expr::Case { whens, else_expr } => Expr::Case {
2594 whens: whens
2595 .into_iter()
2596 .map(|(c, r)| (recur(*c), recur(*r)))
2597 .collect(),
2598 else_expr: else_expr.map(|e| recur(*e)),
2599 },
2600 Expr::JsonPath { base, segments } => Expr::JsonPath {
2601 base: recur(*base),
2602 segments,
2603 },
2604 other => other,
2606 }
2607}
2608
2609fn relexes_to(text: &str, tok: &Token) -> bool {
2614 match lex(text) {
2615 Ok(toks) => matches!(toks.as_slice(), [t, Token::Eof] if t == tok),
2616 Err(_) => false,
2617 }
2618}
2619
2620fn push_string_literal(out: &mut String, s: &str) {
2628 out.push('"');
2629 for c in s.chars() {
2630 match c {
2631 '"' => out.push_str("\\\""),
2632 '\\' => out.push_str("\\\\"),
2633 '\n' => out.push_str("\\n"),
2634 '\t' => out.push_str("\\t"),
2635 other => out.push(other),
2636 }
2637 }
2638 out.push('"');
2639}
2640
2641fn push_ident(out: &mut String, prefix: &str, name: &str, tok: &Token) -> Result<(), ParseError> {
2649 let bare = format!("{prefix}{name}");
2650 if relexes_to(&bare, tok) {
2651 out.push_str(&bare);
2652 return Ok(());
2653 }
2654 let quoted = format!("{prefix}`{name}`");
2655 if relexes_to("ed, tok) {
2656 out.push_str("ed);
2657 return Ok(());
2658 }
2659 Err(ParseError::Unsupported {
2660 feature: format!(
2661 "cannot store view source: identifier '{name}' has no PowQL spelling that reads back unchanged"
2662 ),
2663 })
2664}
2665
2666fn tokens_to_text(tokens: &[Token]) -> Result<String, ParseError> {
2676 let mut out = String::with_capacity(64);
2677 for tok in tokens {
2678 if !out.is_empty() && !matches!(tok, Token::Eof) {
2679 out.push(' ');
2680 }
2681 match tok {
2682 Token::Ident(s) => push_ident(&mut out, "", s, tok)?,
2683 Token::DotIdent(s) => push_ident(&mut out, ".", s, tok)?,
2684 Token::IntLit(v) => out.push_str(&v.to_string()),
2685 Token::FloatLit(v) => {
2686 if !v.is_finite() {
2687 return Err(ParseError::Unsupported {
2688 feature: "cannot store view source: non-finite number literal".into(),
2689 });
2690 }
2691 let rendered = v.to_string();
2692 out.push_str(&rendered);
2693 if !rendered.contains('.') {
2698 out.push_str(".0");
2699 }
2700 }
2701 Token::StringLit(s) => push_string_literal(&mut out, s),
2702 Token::BoolLit(v) => out.push_str(if *v { "true" } else { "false" }),
2703 Token::Param(s) => {
2704 out.push('$');
2705 out.push_str(s);
2706 }
2707 Token::Type => out.push_str("type"),
2708 Token::Filter => out.push_str("filter"),
2709 Token::Order => out.push_str("order"),
2710 Token::Limit => out.push_str("limit"),
2711 Token::Offset => out.push_str("offset"),
2712 Token::Insert => out.push_str("insert"),
2713 Token::Update => out.push_str("update"),
2714 Token::Delete => out.push_str("delete"),
2715 Token::Upsert => out.push_str("upsert"),
2716 Token::Returning => out.push_str("returning"),
2717 Token::Conflict => out.push_str("conflict"),
2718 Token::Select => out.push_str("select"),
2719 Token::Required => out.push_str("required"),
2720 Token::Default => out.push_str("default"),
2721 Token::Auto => out.push_str("auto"),
2722 Token::Multi => out.push_str("multi"),
2723 Token::Link => out.push_str("link"),
2724 Token::Index => out.push_str("index"),
2725 Token::Unique => out.push_str("unique"),
2726 Token::On => out.push_str("on"),
2727 Token::Asc => out.push_str("asc"),
2728 Token::Desc => out.push_str("desc"),
2729 Token::And => out.push_str("and"),
2730 Token::Or => out.push_str("or"),
2731 Token::Not => out.push_str("not"),
2732 Token::Exists => out.push_str("exists"),
2733 Token::Let => out.push_str("let"),
2734 Token::As => out.push_str("as"),
2735 Token::Match => out.push_str("match"),
2736 Token::Group => out.push_str("group"),
2737 Token::Join => out.push_str("join"),
2738 Token::Inner => out.push_str("inner"),
2739 Token::LeftKw => out.push_str("left"),
2740 Token::RightKw => out.push_str("right"),
2741 Token::Outer => out.push_str("outer"),
2742 Token::Cross => out.push_str("cross"),
2743 Token::Transaction => out.push_str("transaction"),
2744 Token::Begin => out.push_str("begin"),
2745 Token::Commit => out.push_str("commit"),
2746 Token::Rollback => out.push_str("rollback"),
2747 Token::View => out.push_str("view"),
2748 Token::Materialized => out.push_str("materialized"),
2749 Token::Refresh => out.push_str("refresh"),
2750 Token::Union => out.push_str("union"),
2751 Token::Having => out.push_str("having"),
2752 Token::Distinct => out.push_str("distinct"),
2753 Token::In => out.push_str("in"),
2754 Token::Between => out.push_str("between"),
2755 Token::Like => out.push_str("like"),
2756 Token::Count => out.push_str("count"),
2757 Token::Avg => out.push_str("avg"),
2758 Token::Sum => out.push_str("sum"),
2759 Token::Raw => out.push_str("raw"),
2760 Token::Min => out.push_str("min"),
2761 Token::Max => out.push_str("max"),
2762 Token::Is => out.push_str("is"),
2763 Token::Null => out.push_str("null"),
2764 Token::Upper => out.push_str("upper"),
2765 Token::Lower => out.push_str("lower"),
2766 Token::Length => out.push_str("length"),
2767 Token::Trim => out.push_str("trim"),
2768 Token::Substring => out.push_str("substring"),
2769 Token::Concat => out.push_str("concat"),
2770 Token::Abs => out.push_str("abs"),
2771 Token::Round => out.push_str("round"),
2772 Token::Ceil => out.push_str("ceil"),
2773 Token::Floor => out.push_str("floor"),
2774 Token::Sqrt => out.push_str("sqrt"),
2775 Token::Pow => out.push_str("pow"),
2776 Token::Now => out.push_str("now"),
2777 Token::Extract => out.push_str("extract"),
2778 Token::DateAdd => out.push_str("date_add"),
2779 Token::DateDiff => out.push_str("date_diff"),
2780 Token::JsonType => out.push_str("json_type"),
2781 Token::JsonText => out.push_str("json_text"),
2782 Token::Cast => out.push_str("cast"),
2783 Token::Case => out.push_str("case"),
2784 Token::When => out.push_str("when"),
2785 Token::Then => out.push_str("then"),
2786 Token::Else => out.push_str("else"),
2787 Token::End => out.push_str("end"),
2788 Token::Over => out.push_str("over"),
2789 Token::Partition => out.push_str("partition"),
2790 Token::RowNumber => out.push_str("row_number"),
2791 Token::Rank => out.push_str("rank"),
2792 Token::DenseRank => out.push_str("dense_rank"),
2793 Token::Alter => out.push_str("alter"),
2794 Token::Drop => out.push_str("drop"),
2795 Token::Add => out.push_str("add"),
2796 Token::Column => out.push_str("column"),
2797 Token::Eq => out.push('='),
2798 Token::Neq => out.push_str("!="),
2799 Token::Lt => out.push('<'),
2800 Token::Gt => out.push('>'),
2801 Token::Lte => out.push_str("<="),
2802 Token::Gte => out.push_str(">="),
2803 Token::Assign => out.push_str(":="),
2804 Token::Arrow => out.push_str("->"),
2805 Token::Pipe => out.push('|'),
2806 Token::Coalesce => out.push_str("??"),
2807 Token::Plus => out.push('+'),
2808 Token::Minus => out.push('-'),
2809 Token::Star => out.push('*'),
2810 Token::Slash => out.push('/'),
2811 Token::LBrace => out.push('{'),
2812 Token::RBrace => out.push('}'),
2813 Token::LParen => out.push('('),
2814 Token::RParen => out.push(')'),
2815 Token::Comma => out.push(','),
2816 Token::Colon => out.push(':'),
2817 Token::Dot => out.push('.'),
2818 Token::Explain => out.push_str("explain"),
2819 Token::Schema => out.push_str("schema"),
2820 Token::Describe => out.push_str("describe"),
2821 Token::Eof => {}
2822 }
2823 }
2824 let mut expected: Vec<Token> = tokens
2831 .iter()
2832 .filter(|t| **t != Token::Eof)
2833 .cloned()
2834 .collect();
2835 expected.push(Token::Eof);
2836 match lex(&out) {
2837 Ok(round_tripped) if round_tripped == expected => Ok(out),
2838 _ => Err(ParseError::Unsupported {
2839 feature: "cannot store view source: query text does not read back unchanged".into(),
2840 }),
2841 }
2842}
2843
2844#[cfg(test)]
2845mod tests {
2846 use super::*;
2847 #[test]
2848 fn test_parse_simple_query() {
2849 let stmt = parse("User").unwrap();
2850 match stmt {
2851 Statement::Query(q) => {
2852 assert_eq!(q.source, "User");
2853 assert!(q.filter.is_none());
2854 assert!(q.projection.is_none());
2855 }
2856 _ => panic!("expected query"),
2857 }
2858 }
2859
2860 #[test]
2861 fn test_parse_filter() {
2862 let stmt = parse("User filter .age > 30").unwrap();
2863 match stmt {
2864 Statement::Query(q) => {
2865 assert_eq!(q.source, "User");
2866 assert!(q.filter.is_some());
2867 }
2868 _ => panic!("expected query"),
2869 }
2870 }
2871
2872 #[test]
2873 fn test_parse_projection() {
2874 let stmt = parse("User { name, email }").unwrap();
2875 match stmt {
2876 Statement::Query(q) => {
2877 let proj = q.projection.unwrap();
2878 assert_eq!(proj.len(), 2);
2879 }
2880 _ => panic!("expected query"),
2881 }
2882 }
2883
2884 #[test]
2885 fn test_bare_dotted_path_projection_is_a_parse_error() {
2886 for q in [
2890 "Order { .user.name }",
2891 "Order { .id, uname: .user.name }",
2892 "Order { uname: .user.company.name }",
2893 ] {
2894 let err = parse(q).unwrap_err();
2895 let msg = err.to_string();
2896 assert!(
2897 msg.contains("alias the table"),
2898 "`{q}` should error with alias guidance, got: {msg}"
2899 );
2900 }
2901 }
2902
2903 #[test]
2904 fn test_aliased_scalar_link_path_parses_as_one_field() {
2905 let stmt = parse("Order as o { uname: o.user.name }").unwrap();
2906 match stmt {
2907 Statement::Query(q) => {
2908 let proj = q.projection.unwrap();
2909 assert_eq!(proj.len(), 1);
2910 match &proj[0].expr {
2911 Expr::LinkPath {
2912 outer_alias,
2913 links,
2914 column,
2915 } => {
2916 assert_eq!(outer_alias, "o");
2917 assert_eq!(links, &["user".to_string()]);
2918 assert_eq!(column, "name");
2919 }
2920 other => panic!("expected LinkPath, got {other:?}"),
2921 }
2922 }
2923 _ => panic!("expected query"),
2924 }
2925 }
2926
2927 #[test]
2928 fn test_parse_filter_order_limit() {
2929 let stmt = parse("User filter .age > 30 order .name desc limit 10").unwrap();
2930 match stmt {
2931 Statement::Query(q) => {
2932 assert!(q.filter.is_some());
2933 let order = q.order.unwrap();
2934 assert_eq!(order.keys.len(), 1);
2935 assert_eq!(order.keys[0].expr, Expr::Field("name".into()));
2936 assert!(order.keys[0].descending);
2937 assert!(q.limit.is_some());
2938 }
2939 _ => panic!("expected query"),
2940 }
2941 }
2942
2943 #[test]
2944 fn test_parse_insert() {
2945 let stmt = parse(r#"insert User { name := "Alice", age := 30 }"#).unwrap();
2946 match stmt {
2947 Statement::Insert(ins) => {
2948 assert_eq!(ins.target, "User");
2949 assert_eq!(ins.rows.len(), 1);
2950 assert_eq!(ins.rows[0].len(), 2);
2951 assert_eq!(ins.rows[0][0].field, "name");
2952 assert_eq!(ins.rows[0][1].field, "age");
2953 }
2954 _ => panic!("expected insert"),
2955 }
2956 }
2957
2958 #[test]
2959 fn test_parse_insert_multi_row() {
2960 let stmt =
2961 parse(r#"insert User { name := "Alice", age := 30 }, { name := "Bob", age := 25 }, { name := "Cy" }"#)
2962 .unwrap();
2963 match stmt {
2964 Statement::Insert(ins) => {
2965 assert_eq!(ins.target, "User");
2966 assert_eq!(ins.rows.len(), 3);
2967 assert_eq!(ins.rows[0].len(), 2);
2968 assert_eq!(ins.rows[1][0].field, "name");
2969 assert_eq!(ins.rows[2].len(), 1);
2970 assert_eq!(ins.rows[2][0].field, "name");
2971 }
2972 _ => panic!("expected insert"),
2973 }
2974 }
2975
2976 #[test]
2977 fn test_parse_update() {
2978 let stmt = parse(r#"User filter .email = "alice@ex.com" update { age := 31 }"#).unwrap();
2979 match stmt {
2980 Statement::UpdateQuery(upd) => {
2981 assert_eq!(upd.source, "User");
2982 assert!(upd.filter.is_some());
2983 assert_eq!(upd.assignments.len(), 1);
2984 assert!(!upd.returning);
2985 }
2986 _ => panic!("expected update"),
2987 }
2988 }
2989
2990 #[test]
2991 fn test_parse_update_returning() {
2992 let stmt = parse(r#"User filter .name = "Alice" update { age := 31 } returning"#).unwrap();
2993 match stmt {
2994 Statement::UpdateQuery(upd) => assert!(upd.returning),
2995 _ => panic!("expected update"),
2996 }
2997 }
2998
2999 #[test]
3000 fn test_parse_delete() {
3001 let stmt = parse("User filter .age < 18 delete").unwrap();
3002 match stmt {
3003 Statement::DeleteQuery(del) => {
3004 assert_eq!(del.source, "User");
3005 assert!(del.filter.is_some());
3006 assert!(!del.returning);
3007 }
3008 _ => panic!("expected delete"),
3009 }
3010 }
3011
3012 #[test]
3013 fn test_parse_delete_returning() {
3014 let stmt = parse("User filter .age < 18 delete returning").unwrap();
3015 match stmt {
3016 Statement::DeleteQuery(del) => assert!(del.returning),
3017 _ => panic!("expected delete"),
3018 }
3019 }
3020
3021 #[test]
3022 fn test_parse_count() {
3023 let stmt = parse("count(User)").unwrap();
3024 match stmt {
3025 Statement::Query(q) => {
3026 let agg = q.aggregation.unwrap();
3027 assert_eq!(agg.function, AggFunc::Count);
3028 assert!(q.filter.is_none());
3029 }
3030 _ => panic!("expected query with aggregation"),
3031 }
3032 }
3033
3034 #[test]
3035 fn test_parse_count_with_filter() {
3036 let stmt = parse("count(User filter .age > 30)").unwrap();
3039 match stmt {
3040 Statement::Query(q) => {
3041 assert_eq!(q.source, "User");
3042 let agg = q.aggregation.unwrap();
3043 assert_eq!(agg.function, AggFunc::Count);
3044 assert!(q.filter.is_some(), "filter should have been parsed");
3045 }
3046 _ => panic!("expected query with aggregation"),
3047 }
3048 }
3049
3050 #[test]
3051 fn test_parse_count_with_filter_and_limit() {
3052 let stmt = parse("count(User filter .age > 30 limit 100)").unwrap();
3053 match stmt {
3054 Statement::Query(q) => {
3055 assert_eq!(q.source, "User");
3056 assert!(q.filter.is_some());
3057 assert!(q.limit.is_some());
3058 assert_eq!(q.aggregation.unwrap().function, AggFunc::Count);
3059 }
3060 _ => panic!("expected query with aggregation"),
3061 }
3062 }
3063
3064 #[test]
3065 fn test_parse_create_type() {
3066 let stmt = parse("type User { required name: str, age: int }").unwrap();
3067 match stmt {
3068 Statement::CreateType(ct) => {
3069 assert_eq!(ct.name, "User");
3070 assert_eq!(ct.fields.len(), 2);
3071 assert!(ct.fields[0].required);
3072 assert!(!ct.fields[1].required);
3073 }
3074 _ => panic!("expected create type"),
3075 }
3076 }
3077
3078 #[test]
3079 fn test_parse_sum_with_field_projection() {
3080 let stmt = parse("sum(User filter .age > 30 { .age })").unwrap();
3083 match stmt {
3084 Statement::Query(q) => {
3085 let agg = q.aggregation.expect("aggregate");
3086 assert_eq!(agg.function, AggFunc::Sum);
3087 assert_eq!(agg.argument, Some(Expr::Field("age".into())));
3088 assert!(
3089 q.projection.is_none(),
3090 "projection should be lifted into agg.field"
3091 );
3092 }
3093 _ => panic!("expected query"),
3094 }
3095 }
3096
3097 #[test]
3098 fn test_parse_raw_aggregate_modes() {
3099 let Statement::Query(top_level) = parse("sum(raw User { .age })").unwrap() else {
3100 panic!("expected query");
3101 };
3102 assert_eq!(top_level.aggregation.unwrap().mode, AggregateMode::Raw);
3103
3104 let Statement::Query(grouped) = parse("User group .dept { total: sum(raw .age) }").unwrap()
3105 else {
3106 panic!("expected query");
3107 };
3108 assert!(matches!(
3109 grouped.projection.unwrap()[0].expr,
3110 Expr::FunctionCall(AggFunc::Sum, _, AggregateMode::Raw)
3111 ));
3112 }
3113
3114 #[test]
3115 fn test_parse_avg_min_max_with_field() {
3116 for (src, expected) in [
3117 ("avg(User { .age })", AggFunc::Avg),
3118 ("min(User { .age })", AggFunc::Min),
3119 ("max(User { .age })", AggFunc::Max),
3120 ] {
3121 let stmt = parse(src).unwrap();
3122 match stmt {
3123 Statement::Query(q) => {
3124 let agg = q.aggregation.unwrap();
3125 assert_eq!(agg.function, expected, "func mismatch for {src}");
3126 assert_eq!(
3127 agg.argument,
3128 Some(Expr::Field("age".into())),
3129 "field mismatch for {src}"
3130 );
3131 assert!(
3132 q.projection.is_none(),
3133 "projection should be cleared for {src}"
3134 );
3135 }
3136 _ => panic!("expected query for {src}"),
3137 }
3138 }
3139 }
3140
3141 #[test]
3142 fn test_parse_count_lifts_projection_into_argument() {
3143 let stmt = parse("count(User { .age })").unwrap();
3148 match stmt {
3149 Statement::Query(q) => {
3150 let agg = q.aggregation.unwrap();
3151 assert_eq!(agg.function, AggFunc::Count);
3152 assert_eq!(agg.argument, Some(Expr::Field("age".into())));
3153 assert!(q.projection.is_none(), "projection should be lifted");
3154 }
3155 _ => panic!("expected query"),
3156 }
3157 }
3158
3159 #[test]
3160 fn test_parse_count_without_projection_has_no_argument() {
3161 let stmt = parse("count(User)").unwrap();
3163 match stmt {
3164 Statement::Query(q) => {
3165 let agg = q.aggregation.unwrap();
3166 assert_eq!(agg.function, AggFunc::Count);
3167 assert!(agg.argument.is_none());
3168 }
3169 _ => panic!("expected query"),
3170 }
3171 }
3172
3173 #[test]
3178 fn test_parse_source_alias() {
3179 let stmt = parse("User as u filter u.age > 30").unwrap();
3180 match stmt {
3181 Statement::Query(q) => {
3182 assert_eq!(q.source, "User");
3183 assert_eq!(q.alias.as_deref(), Some("u"));
3184 assert!(q.joins.is_empty());
3185 match q.filter.unwrap() {
3186 Expr::BinaryOp(l, BinOp::Gt, _) => match *l {
3187 Expr::QualifiedField { qualifier, field } => {
3188 assert_eq!(qualifier, "u");
3189 assert_eq!(field, "age");
3190 }
3191 other => panic!("expected qualified field, got {other:?}"),
3192 },
3193 other => panic!("expected >, got {other:?}"),
3194 }
3195 }
3196 _ => panic!("expected query"),
3197 }
3198 }
3199
3200 #[test]
3201 fn test_parse_inner_join_on() {
3202 let stmt = parse("User as u inner join Order as o on u.id = o.user_id").unwrap();
3203 match stmt {
3204 Statement::Query(q) => {
3205 assert_eq!(q.source, "User");
3206 assert_eq!(q.alias.as_deref(), Some("u"));
3207 assert_eq!(q.joins.len(), 1);
3208 let j = &q.joins[0];
3209 assert_eq!(j.kind, JoinKind::Inner);
3210 assert_eq!(j.source, "Order");
3211 assert_eq!(j.alias.as_deref(), Some("o"));
3212 let on = j.on.as_ref().expect("on clause");
3213 match on {
3214 Expr::BinaryOp(l, BinOp::Eq, r) => {
3215 assert!(matches!(**l, Expr::QualifiedField { .. }));
3216 assert!(matches!(**r, Expr::QualifiedField { .. }));
3217 }
3218 other => panic!("expected eq, got {other:?}"),
3219 }
3220 }
3221 _ => panic!("expected query"),
3222 }
3223 }
3224
3225 #[test]
3226 fn test_parse_bare_join_defaults_to_inner() {
3227 let stmt = parse("User join Order on User.id = Order.user_id").unwrap();
3228 match stmt {
3229 Statement::Query(q) => {
3230 assert_eq!(q.joins.len(), 1);
3231 assert_eq!(q.joins[0].kind, JoinKind::Inner);
3232 }
3233 _ => panic!("expected query"),
3234 }
3235 }
3236
3237 #[test]
3238 fn test_parse_left_outer_join() {
3239 let stmt = parse("User as u left outer join Order as o on u.id = o.user_id").unwrap();
3240 match stmt {
3241 Statement::Query(q) => {
3242 assert_eq!(q.joins.len(), 1);
3243 assert_eq!(q.joins[0].kind, JoinKind::LeftOuter);
3244 }
3245 _ => panic!("expected query"),
3246 }
3247 }
3248
3249 #[test]
3250 fn test_parse_left_join_without_outer_keyword() {
3251 let stmt = parse("User as u left join Order as o on u.id = o.user_id").unwrap();
3253 match stmt {
3254 Statement::Query(q) => {
3255 assert_eq!(q.joins[0].kind, JoinKind::LeftOuter);
3256 }
3257 _ => panic!("expected query"),
3258 }
3259 }
3260
3261 #[test]
3262 fn test_parse_right_join() {
3263 let stmt = parse("User as u right join Order as o on u.id = o.user_id").unwrap();
3264 match stmt {
3265 Statement::Query(q) => {
3266 assert_eq!(q.joins[0].kind, JoinKind::RightOuter);
3267 }
3268 _ => panic!("expected query"),
3269 }
3270 }
3271
3272 #[test]
3273 fn test_parse_cross_join_has_no_on() {
3274 let stmt = parse("User cross join Order").unwrap();
3275 match stmt {
3276 Statement::Query(q) => {
3277 assert_eq!(q.joins[0].kind, JoinKind::Cross);
3278 assert!(q.joins[0].on.is_none());
3279 }
3280 _ => panic!("expected query"),
3281 }
3282 }
3283
3284 #[test]
3285 fn test_parse_multi_join_chain() {
3286 let stmt = parse(
3287 "User as u join Order as o on u.id = o.user_id \
3288 join Product as p on o.product_id = p.id",
3289 )
3290 .unwrap();
3291 match stmt {
3292 Statement::Query(q) => {
3293 assert_eq!(q.joins.len(), 2);
3294 assert_eq!(q.joins[0].source, "Order");
3295 assert_eq!(q.joins[1].source, "Product");
3296 }
3297 _ => panic!("expected query"),
3298 }
3299 }
3300
3301 #[test]
3302 fn test_parse_join_with_filter_tail() {
3303 let stmt = parse(
3305 "User as u join Order as o on u.id = o.user_id \
3306 filter o.total > 100 order .name limit 10",
3307 )
3308 .unwrap();
3309 match stmt {
3310 Statement::Query(q) => {
3311 assert_eq!(q.joins.len(), 1);
3312 assert!(q.filter.is_some());
3313 assert!(q.order.is_some());
3314 assert!(q.limit.is_some());
3315 }
3316 _ => panic!("expected query"),
3317 }
3318 }
3319
3320 #[test]
3321 fn test_parse_join_requires_on_for_inner() {
3322 let err = parse("User join Order").unwrap_err();
3324 assert!(
3325 err.message().contains("on"),
3326 "expected on-clause error, got {:?}",
3327 err.message()
3328 );
3329 }
3330
3331 #[test]
3332 fn test_parse_update_on_joined_query_errors() {
3333 let err =
3336 parse("User as u join Order as o on u.id = o.user_id update { age := 1 }").unwrap_err();
3337 assert!(err.message().contains("update"));
3338 }
3339
3340 #[test]
3341 fn test_parse_delete_on_joined_query_errors() {
3342 let err = parse("User as u join Order as o on u.id = o.user_id delete").unwrap_err();
3343 assert!(err.message().contains("delete"));
3344 }
3345
3346 #[test]
3349 fn test_parse_distinct() {
3350 let stmt = parse("User distinct { .name }").unwrap();
3351 match stmt {
3352 Statement::Query(q) => {
3353 assert!(q.distinct);
3354 assert!(q.projection.is_some());
3355 }
3356 _ => panic!("expected query"),
3357 }
3358 }
3359
3360 #[test]
3361 fn test_parse_in_list() {
3362 let stmt = parse(r#"User filter .name in ("Alice", "Bob")"#).unwrap();
3363 match stmt {
3364 Statement::Query(q) => match q.filter.unwrap() {
3365 Expr::InList {
3366 expr,
3367 list,
3368 negated,
3369 } => {
3370 assert!(!negated);
3371 assert!(matches!(*expr, Expr::Field(f) if f == "name"));
3372 assert_eq!(list.len(), 2);
3373 }
3374 other => panic!("expected InList, got {other:?}"),
3375 },
3376 _ => panic!("expected query"),
3377 }
3378 }
3379
3380 #[test]
3381 fn test_parse_not_in_list() {
3382 let stmt = parse("User filter .age not in (1, 2, 3)").unwrap();
3383 match stmt {
3384 Statement::Query(q) => match q.filter.unwrap() {
3385 Expr::InList { negated, list, .. } => {
3386 assert!(negated);
3387 assert_eq!(list.len(), 3);
3388 }
3389 other => panic!("expected InList, got {other:?}"),
3390 },
3391 _ => panic!("expected query"),
3392 }
3393 }
3394
3395 #[test]
3396 fn test_parse_between() {
3397 let stmt = parse("User filter .age between 10 and 20").unwrap();
3399 match stmt {
3400 Statement::Query(q) => {
3401 match q.filter.unwrap() {
3402 Expr::BinaryOp(_, BinOp::And, _) => {} other => panic!("expected And (desugared between), got {other:?}"),
3404 }
3405 }
3406 _ => panic!("expected query"),
3407 }
3408 }
3409
3410 #[test]
3411 fn test_parse_not_between() {
3412 let stmt = parse("User filter .age not between 10 and 20").unwrap();
3414 match stmt {
3415 Statement::Query(q) => {
3416 match q.filter.unwrap() {
3417 Expr::BinaryOp(_, BinOp::Or, _) => {} other => panic!("expected Or (desugared not between), got {other:?}"),
3419 }
3420 }
3421 _ => panic!("expected query"),
3422 }
3423 }
3424
3425 #[test]
3426 fn test_parse_like() {
3427 let stmt = parse(r#"User filter .name like "A%""#).unwrap();
3428 match stmt {
3429 Statement::Query(q) => match q.filter.unwrap() {
3430 Expr::BinaryOp(l, BinOp::Like, r) => {
3431 assert!(matches!(*l, Expr::Field(f) if f == "name"));
3432 assert!(matches!(*r, Expr::Literal(Literal::String(s)) if s == "A%"));
3433 }
3434 other => panic!("expected Like, got {other:?}"),
3435 },
3436 _ => panic!("expected query"),
3437 }
3438 }
3439
3440 #[test]
3441 fn test_parse_not_like() {
3442 let stmt = parse(r#"User filter .name not like "A%""#).unwrap();
3443 match stmt {
3444 Statement::Query(q) => match q.filter.unwrap() {
3445 Expr::UnaryOp(UnaryOp::Not, inner) => {
3446 assert!(matches!(*inner, Expr::BinaryOp(_, BinOp::Like, _)));
3447 }
3448 other => panic!("expected Not(Like), got {other:?}"),
3449 },
3450 _ => panic!("expected query"),
3451 }
3452 }
3453
3454 #[test]
3457 fn test_parse_group_by_single_key() {
3458 let stmt = parse("User group .status { .status, n: count(.name) }").unwrap();
3459 match stmt {
3460 Statement::Query(q) => {
3461 let gb = q.group_by.unwrap();
3462 assert_eq!(
3463 gb.keys,
3464 vec![GroupKey {
3465 expr: Expr::Field("status".into()),
3466 output_name: "status".into(),
3467 }]
3468 );
3469 assert!(gb.having.is_none());
3470 let proj = q.projection.unwrap();
3471 assert_eq!(proj.len(), 2);
3472 assert!(matches!(
3473 &proj[1].expr,
3474 Expr::FunctionCall(AggFunc::Count, _, _)
3475 ));
3476 assert_eq!(proj[1].alias.as_deref(), Some("n"));
3477 }
3478 _ => panic!("expected query"),
3479 }
3480 }
3481
3482 #[test]
3483 fn test_parse_group_by_multi_key() {
3484 let stmt = parse("User group .status, .age { .status, .age }").unwrap();
3485 match stmt {
3486 Statement::Query(q) => {
3487 let gb = q.group_by.unwrap();
3488 assert_eq!(
3489 gb.keys,
3490 vec![
3491 GroupKey {
3492 expr: Expr::Field("status".into()),
3493 output_name: "status".into(),
3494 },
3495 GroupKey {
3496 expr: Expr::Field("age".into()),
3497 output_name: "age".into(),
3498 }
3499 ]
3500 );
3501 }
3502 _ => panic!("expected query"),
3503 }
3504 }
3505
3506 #[test]
3507 fn test_parse_group_by_having() {
3508 let stmt = parse("User group .status having count(.name) > 1 { .status }").unwrap();
3509 match stmt {
3510 Statement::Query(q) => {
3511 let gb = q.group_by.unwrap();
3512 assert_eq!(
3513 gb.keys,
3514 vec![GroupKey {
3515 expr: Expr::Field("status".into()),
3516 output_name: "status".into(),
3517 }]
3518 );
3519 assert!(gb.having.is_some());
3520 match gb.having.unwrap() {
3522 Expr::BinaryOp(l, BinOp::Gt, _) => {
3523 assert!(matches!(*l, Expr::FunctionCall(AggFunc::Count, _, _)));
3524 }
3525 other => panic!("expected BinaryOp, got {other:?}"),
3526 }
3527 }
3528 _ => panic!("expected query"),
3529 }
3530 }
3531
3532 #[test]
3533 fn test_parse_aggregate_in_projection() {
3534 let stmt = parse("User group .status { .status, count(.name), sum(.age) }").unwrap();
3536 match stmt {
3537 Statement::Query(q) => {
3538 let proj = q.projection.unwrap();
3539 assert_eq!(proj.len(), 3);
3540 assert!(matches!(
3541 &proj[1].expr,
3542 Expr::FunctionCall(AggFunc::Count, _, _)
3543 ));
3544 assert!(matches!(
3545 &proj[2].expr,
3546 Expr::FunctionCall(AggFunc::Sum, _, _)
3547 ));
3548 }
3549 _ => panic!("expected query"),
3550 }
3551 }
3552
3553 #[test]
3554 fn test_parse_aggregate_in_aliased_projection() {
3555 let stmt = parse("User group .status { .status, total: count(.name), average: avg(.age) }")
3556 .unwrap();
3557 match stmt {
3558 Statement::Query(q) => {
3559 let proj = q.projection.unwrap();
3560 assert_eq!(proj[1].alias.as_deref(), Some("total"));
3561 assert!(matches!(
3562 &proj[1].expr,
3563 Expr::FunctionCall(AggFunc::Count, _, _)
3564 ));
3565 assert_eq!(proj[2].alias.as_deref(), Some("average"));
3566 assert!(matches!(
3567 &proj[2].expr,
3568 Expr::FunctionCall(AggFunc::Avg, _, _)
3569 ));
3570 }
3571 _ => panic!("expected query"),
3572 }
3573 }
3574
3575 #[test]
3578 fn test_parse_is_null() {
3579 let stmt = parse("User filter .age is null").unwrap();
3580 match stmt {
3581 Statement::Query(q) => {
3582 let filter = q.filter.unwrap();
3583 assert_eq!(
3584 filter,
3585 Expr::UnaryOp(UnaryOp::IsNull, Box::new(Expr::Field("age".into())))
3586 );
3587 }
3588 _ => panic!("expected query"),
3589 }
3590 }
3591
3592 #[test]
3593 fn test_parse_is_not_null() {
3594 let stmt = parse("User filter .age is not null").unwrap();
3595 match stmt {
3596 Statement::Query(q) => {
3597 let filter = q.filter.unwrap();
3598 assert_eq!(
3599 filter,
3600 Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(Expr::Field("age".into())))
3601 );
3602 }
3603 _ => panic!("expected query"),
3604 }
3605 }
3606
3607 #[test]
3608 fn test_parse_eq_null_desugars_to_is_null() {
3609 let stmt = parse("User filter .age = null").unwrap();
3610 match stmt {
3611 Statement::Query(q) => {
3612 let filter = q.filter.unwrap();
3613 assert_eq!(
3614 filter,
3615 Expr::UnaryOp(UnaryOp::IsNull, Box::new(Expr::Field("age".into())))
3616 );
3617 }
3618 _ => panic!("expected query"),
3619 }
3620 }
3621
3622 #[test]
3623 fn test_parse_neq_null_desugars_to_is_not_null() {
3624 let stmt = parse("User filter .age != null").unwrap();
3625 match stmt {
3626 Statement::Query(q) => {
3627 let filter = q.filter.unwrap();
3628 assert_eq!(
3629 filter,
3630 Expr::UnaryOp(UnaryOp::IsNotNull, Box::new(Expr::Field("age".into())))
3631 );
3632 }
3633 _ => panic!("expected query"),
3634 }
3635 }
3636
3637 #[test]
3638 fn test_parse_null_comparisons_parse_ok() {
3639 assert!(parse("User filter .age < null").is_ok());
3643 assert!(parse("User filter .age >= null").is_ok());
3644 }
3645
3646 #[test]
3647 fn test_parse_count_star_expr() {
3648 let stmt = parse("User filter count(*) > 0").unwrap();
3649 match stmt {
3650 Statement::Query(q) => {
3651 let filter = q.filter.unwrap();
3652 match filter {
3653 Expr::BinaryOp(left, BinOp::Gt, _) => {
3654 assert_eq!(
3655 *left,
3656 Expr::FunctionCall(
3657 AggFunc::Count,
3658 Box::new(Expr::Field("*".into())),
3659 AggregateMode::Symmetric,
3660 )
3661 );
3662 }
3663 _ => panic!("expected comparison"),
3664 }
3665 }
3666 _ => panic!("expected query"),
3667 }
3668 }
3669
3670 #[test]
3673 fn test_parse_upper_in_filter() {
3674 let stmt = parse(r#"User filter upper(.name) = "ALICE""#).unwrap();
3675 match stmt {
3676 Statement::Query(q) => {
3677 let f = q.filter.unwrap();
3678 match f {
3679 Expr::BinaryOp(left, BinOp::Eq, _right) => {
3680 assert!(matches!(*left, Expr::ScalarFunc(ScalarFn::Upper, _)));
3681 }
3682 _ => panic!("expected binary op with upper"),
3683 }
3684 }
3685 _ => panic!("expected query"),
3686 }
3687 }
3688
3689 #[test]
3690 fn test_parse_substring() {
3691 let stmt = parse("User { sub: substring(.name, 1, 3) }").unwrap();
3692 match stmt {
3693 Statement::Query(q) => {
3694 let proj = q.projection.unwrap();
3695 match &proj[0].expr {
3696 Expr::ScalarFunc(ScalarFn::Substring, args) => {
3697 assert_eq!(args.len(), 3);
3698 }
3699 other => panic!("expected ScalarFunc Substring, got {other:?}"),
3700 }
3701 }
3702 _ => panic!("expected query"),
3703 }
3704 }
3705
3706 #[test]
3707 fn test_parse_concat() {
3708 let stmt = parse(r#"User { full: concat(.name, " - ", .email) }"#).unwrap();
3709 match stmt {
3710 Statement::Query(q) => {
3711 let proj = q.projection.unwrap();
3712 match &proj[0].expr {
3713 Expr::ScalarFunc(ScalarFn::Concat, args) => {
3714 assert_eq!(args.len(), 3);
3715 }
3716 other => panic!("expected ScalarFunc Concat, got {other:?}"),
3717 }
3718 }
3719 _ => panic!("expected query"),
3720 }
3721 }
3722
3723 #[test]
3726 fn test_parse_case_single_when() {
3727 let stmt = parse(r#"User filter case when .age > 30 then true else false end"#).unwrap();
3728 match stmt {
3729 Statement::Query(q) => {
3730 let filter = q.filter.unwrap();
3731 match filter {
3732 Expr::Case { whens, else_expr } => {
3733 assert_eq!(whens.len(), 1);
3734 assert!(else_expr.is_some());
3735 }
3736 other => panic!("expected Case expr, got {other:?}"),
3737 }
3738 }
3739 _ => panic!("expected query"),
3740 }
3741 }
3742
3743 #[test]
3744 fn test_parse_case_multiple_whens() {
3745 let stmt = parse(
3746 r#"User { label: case when .age > 30 then "senior" when .age > 20 then "adult" else "young" end }"#
3747 ).unwrap();
3748 match stmt {
3749 Statement::Query(q) => {
3750 let proj = q.projection.unwrap();
3751 match &proj[0].expr {
3752 Expr::Case { whens, else_expr } => {
3753 assert_eq!(whens.len(), 2);
3754 assert!(else_expr.is_some());
3755 }
3756 other => panic!("expected Case expr, got {other:?}"),
3757 }
3758 }
3759 _ => panic!("expected query"),
3760 }
3761 }
3762
3763 #[test]
3764 fn test_parse_case_without_else() {
3765 let stmt = parse(r#"User filter case when .age > 30 then true end"#).unwrap();
3766 match stmt {
3767 Statement::Query(q) => {
3768 let filter = q.filter.unwrap();
3769 match filter {
3770 Expr::Case { whens, else_expr } => {
3771 assert_eq!(whens.len(), 1);
3772 assert!(else_expr.is_none());
3773 }
3774 other => panic!("expected Case expr, got {other:?}"),
3775 }
3776 }
3777 _ => panic!("expected query"),
3778 }
3779 }
3780
3781 #[test]
3784 fn test_parse_mul_expr() {
3785 let stmt = parse("User filter .price * .quantity > 100").unwrap();
3786 match stmt {
3787 Statement::Query(q) => {
3788 let filter = q.filter.unwrap();
3789 match filter {
3790 Expr::BinaryOp(left, BinOp::Gt, _) => match *left {
3791 Expr::BinaryOp(_, BinOp::Mul, _) => {}
3792 other => panic!("expected Mul, got {other:?}"),
3793 },
3794 other => panic!("expected BinaryOp Gt, got {other:?}"),
3795 }
3796 }
3797 _ => panic!("expected query"),
3798 }
3799 }
3800
3801 #[test]
3802 fn test_parse_div_expr() {
3803 let stmt = parse("User { ratio: .total / .count }").unwrap();
3804 match stmt {
3805 Statement::Query(q) => {
3806 let proj = q.projection.unwrap();
3807 assert_eq!(proj[0].alias.as_deref(), Some("ratio"));
3808 match &proj[0].expr {
3809 Expr::BinaryOp(_, BinOp::Div, _) => {}
3810 other => panic!("expected Div, got {other:?}"),
3811 }
3812 }
3813 _ => panic!("expected query"),
3814 }
3815 }
3816
3817 #[test]
3818 fn test_parse_mul_div_precedence() {
3819 let stmt = parse("User filter .a + .b * .c > 0").unwrap();
3821 match stmt {
3822 Statement::Query(q) => {
3823 let filter = q.filter.unwrap();
3824 match filter {
3825 Expr::BinaryOp(left, BinOp::Gt, _) => match *left {
3826 Expr::BinaryOp(_, BinOp::Add, right) => {
3827 assert!(matches!(*right, Expr::BinaryOp(_, BinOp::Mul, _)));
3828 }
3829 other => panic!("expected Add, got {other:?}"),
3830 },
3831 other => panic!("expected Gt, got {other:?}"),
3832 }
3833 }
3834 _ => panic!("expected query"),
3835 }
3836 }
3837
3838 #[test]
3841 fn test_parse_multi_order() {
3842 let stmt = parse("User order .name asc, .age desc").unwrap();
3843 match stmt {
3844 Statement::Query(q) => {
3845 let order = q.order.unwrap();
3846 assert_eq!(order.keys.len(), 2);
3847 assert_eq!(order.keys[0].expr, Expr::Field("name".into()));
3848 assert!(!order.keys[0].descending);
3849 assert_eq!(order.keys[1].expr, Expr::Field("age".into()));
3850 assert!(order.keys[1].descending);
3851 }
3852 _ => panic!("expected query"),
3853 }
3854 }
3855
3856 #[test]
3857 fn test_parse_order_default_asc() {
3858 let stmt = parse("User order .name").unwrap();
3859 match stmt {
3860 Statement::Query(q) => {
3861 let order = q.order.unwrap();
3862 assert_eq!(order.keys.len(), 1);
3863 assert!(!order.keys[0].descending);
3864 }
3865 _ => panic!("expected query"),
3866 }
3867 }
3868
3869 #[test]
3872 fn test_parse_alter_add_column() {
3873 let stmt = parse("alter User add column status: str").unwrap();
3874 match stmt {
3875 Statement::AlterTable(at) => {
3876 assert_eq!(at.table, "User");
3877 match at.action {
3878 AlterAction::AddColumn {
3879 name,
3880 type_name,
3881 required,
3882 } => {
3883 assert_eq!(name, "status");
3884 assert_eq!(type_name, "str");
3885 assert!(!required);
3886 }
3887 other => panic!("expected AddColumn, got {other:?}"),
3888 }
3889 }
3890 other => panic!("expected AlterTable, got {other:?}"),
3891 }
3892 }
3893
3894 #[test]
3895 fn test_parse_alter_add_required_column() {
3896 let stmt = parse("alter User add required status: str").unwrap();
3897 match stmt {
3898 Statement::AlterTable(at) => match at.action {
3899 AlterAction::AddColumn { required, .. } => assert!(required),
3900 other => panic!("expected AddColumn, got {other:?}"),
3901 },
3902 other => panic!("expected AlterTable, got {other:?}"),
3903 }
3904 }
3905
3906 #[test]
3907 fn test_parse_type_with_unique_modifier() {
3908 let stmt = parse("type User { required unique email: str, age: int }").unwrap();
3909 match stmt {
3910 Statement::CreateType(ct) => {
3911 assert!(ct.fields[0].required && ct.fields[0].unique);
3912 assert!(!ct.fields[1].unique);
3913 }
3914 other => panic!("expected CreateType, got {other:?}"),
3915 }
3916 }
3917
3918 #[test]
3919 fn test_parse_type_unique_before_required() {
3920 let stmt = parse("type User { unique required email: str }").unwrap();
3922 match stmt {
3923 Statement::CreateType(ct) => {
3924 assert!(ct.fields[0].required && ct.fields[0].unique);
3925 }
3926 other => panic!("expected CreateType, got {other:?}"),
3927 }
3928 }
3929
3930 #[test]
3931 fn test_parse_alter_add_unique() {
3932 let stmt = parse("alter User add unique .email").unwrap();
3933 match stmt {
3934 Statement::AlterTable(at) => assert!(matches!(
3935 at.action,
3936 AlterAction::AddUnique {
3937 target: IndexTarget::Column(ref column),
3938 ..
3939 } if column == "email"
3940 )),
3941 other => panic!("expected AlterTable, got {other:?}"),
3942 }
3943 }
3944
3945 #[test]
3946 fn test_parse_alter_drop_column() {
3947 let stmt = parse("alter User drop column status").unwrap();
3948 match stmt {
3949 Statement::AlterTable(at) => {
3950 assert_eq!(at.table, "User");
3951 match at.action {
3952 AlterAction::DropColumn { name, .. } => assert_eq!(name, "status"),
3953 other => panic!("expected DropColumn, got {other:?}"),
3954 }
3955 }
3956 other => panic!("expected AlterTable, got {other:?}"),
3957 }
3958 }
3959
3960 #[test]
3961 fn test_parse_alter_drop_without_column_keyword() {
3962 let stmt = parse("alter User drop status").unwrap();
3963 match stmt {
3964 Statement::AlterTable(at) => match at.action {
3965 AlterAction::DropColumn { name, .. } => assert_eq!(name, "status"),
3966 other => panic!("expected DropColumn, got {other:?}"),
3967 },
3968 other => panic!("expected AlterTable, got {other:?}"),
3969 }
3970 }
3971
3972 #[test]
3973 fn test_parse_drop_table() {
3974 let stmt = parse("drop User").unwrap();
3975 match stmt {
3976 Statement::DropTable(dt) => assert_eq!(dt.table, "User"),
3977 other => panic!("expected DropTable, got {other:?}"),
3978 }
3979 }
3980
3981 #[test]
3984 fn test_parse_in_subquery() {
3985 let stmt = parse("User filter .name in (VIP { .name })").unwrap();
3986 match stmt {
3987 Statement::Query(q) => {
3988 let filter = q.filter.unwrap();
3989 match filter {
3990 Expr::InSubquery {
3991 expr,
3992 subquery,
3993 negated,
3994 } => {
3995 assert!(!negated);
3996 assert!(matches!(*expr, Expr::Field(ref f) if f == "name"));
3997 assert_eq!(subquery.source, "VIP");
3998 }
3999 other => panic!("expected InSubquery, got {other:?}"),
4000 }
4001 }
4002 _ => panic!("expected query"),
4003 }
4004 }
4005
4006 #[test]
4007 fn test_parse_not_in_subquery() {
4008 let stmt = parse("User filter .id not in (Order { .user_id })").unwrap();
4009 match stmt {
4010 Statement::Query(q) => match q.filter.unwrap() {
4011 Expr::InSubquery { negated, .. } => assert!(negated),
4012 other => panic!("expected InSubquery, got {other:?}"),
4013 },
4014 _ => panic!("expected query"),
4015 }
4016 }
4017
4018 #[test]
4019 fn test_parse_in_literal_list_still_works() {
4020 let stmt = parse("User filter .age in (25, 30, 35)").unwrap();
4022 match stmt {
4023 Statement::Query(q) => match q.filter.unwrap() {
4024 Expr::InList { list, negated, .. } => {
4025 assert!(!negated);
4026 assert_eq!(list.len(), 3);
4027 }
4028 other => panic!("expected InList, got {other:?}"),
4029 },
4030 _ => panic!("expected query"),
4031 }
4032 }
4033
4034 #[test]
4037 fn test_parse_create_view() {
4038 let stmt = parse("materialize OldUsers as User filter .age > 28").unwrap();
4039 match stmt {
4040 Statement::CreateView(cv) => {
4041 assert_eq!(cv.name, "OldUsers");
4042 assert_eq!(cv.query.source, "User");
4043 assert!(cv.query.filter.is_some());
4044 assert!(!cv.query_text.is_empty());
4045 }
4046 _ => panic!("expected CreateView"),
4047 }
4048 }
4049
4050 #[test]
4051 fn test_parse_create_view_with_projection() {
4052 let stmt = parse("materialize UserNames as User { .name }").unwrap();
4053 match stmt {
4054 Statement::CreateView(cv) => {
4055 assert_eq!(cv.name, "UserNames");
4056 assert!(cv.query.projection.is_some());
4057 }
4058 _ => panic!("expected CreateView"),
4059 }
4060 }
4061
4062 #[test]
4063 fn test_parse_refresh_view() {
4064 let stmt = parse("refresh OldUsers").unwrap();
4065 match stmt {
4066 Statement::RefreshView(rv) => {
4067 assert_eq!(rv.name, "OldUsers");
4068 }
4069 _ => panic!("expected RefreshView"),
4070 }
4071 }
4072
4073 #[test]
4074 fn test_parse_drop_view() {
4075 let stmt = parse("drop view OldUsers").unwrap();
4076 match stmt {
4077 Statement::DropView(dv) => {
4078 assert_eq!(dv.name, "OldUsers");
4079 }
4080 _ => panic!("expected DropView"),
4081 }
4082 }
4083
4084 #[test]
4085 fn test_parse_drop_table_still_works() {
4086 let stmt = parse("drop Users").unwrap();
4087 match stmt {
4088 Statement::DropTable(dt) => {
4089 assert_eq!(dt.table, "Users");
4090 }
4091 _ => panic!("expected DropTable"),
4092 }
4093 }
4094
4095 #[test]
4096 fn test_parse_union() {
4097 let stmt = parse("User union Order").unwrap();
4098 match stmt {
4099 Statement::Union(u) => {
4100 assert!(!u.all);
4101 match *u.left {
4102 Statement::Query(_) => {}
4103 _ => panic!("expected Query on left"),
4104 }
4105 match *u.right {
4106 Statement::Query(_) => {}
4107 _ => panic!("expected Query on right"),
4108 }
4109 }
4110 _ => panic!("expected Union"),
4111 }
4112 }
4113
4114 #[test]
4115 fn test_parse_union_all() {
4116 let stmt = parse("User union all Order").unwrap();
4117 match stmt {
4118 Statement::Union(u) => {
4119 assert!(u.all, "expected UNION ALL");
4120 match *u.left {
4121 Statement::Query(_) => {}
4122 _ => panic!("expected Query on left"),
4123 }
4124 match *u.right {
4125 Statement::Query(_) => {}
4126 _ => panic!("expected Query on right"),
4127 }
4128 }
4129 _ => panic!("expected Union"),
4130 }
4131 }
4132
4133 #[test]
4134 fn test_parse_union_chain() {
4135 let stmt = parse("User union Order union Product").unwrap();
4137 match stmt {
4138 Statement::Union(outer) => {
4139 assert!(!outer.all);
4140 match *outer.right {
4142 Statement::Query(q) => assert_eq!(q.source, "Product"),
4143 _ => panic!("expected Query(Product) on right"),
4144 }
4145 match *outer.left {
4147 Statement::Union(inner) => {
4148 assert!(!inner.all);
4149 match *inner.left {
4150 Statement::Query(q) => assert_eq!(q.source, "User"),
4151 _ => panic!("expected Query(User)"),
4152 }
4153 match *inner.right {
4154 Statement::Query(q) => assert_eq!(q.source, "Order"),
4155 _ => panic!("expected Query(Order)"),
4156 }
4157 }
4158 _ => panic!("expected inner Union"),
4159 }
4160 }
4161 _ => panic!("expected Union"),
4162 }
4163 }
4164
4165 #[test]
4166 fn test_parse_union_with_filter() {
4167 let stmt = parse("User filter .age > 10 union Order filter .total > 50").unwrap();
4168 match stmt {
4169 Statement::Union(u) => {
4170 assert!(!u.all);
4171 match *u.left {
4173 Statement::Query(q) => {
4174 assert_eq!(q.source, "User");
4175 assert!(q.filter.is_some());
4176 }
4177 _ => panic!("expected Query on left"),
4178 }
4179 match *u.right {
4180 Statement::Query(q) => {
4181 assert_eq!(q.source, "Order");
4182 assert!(q.filter.is_some());
4183 }
4184 _ => panic!("expected Query on right"),
4185 }
4186 }
4187 _ => panic!("expected Union"),
4188 }
4189 }
4190
4191 #[test]
4192 fn test_parse_count_distinct_standalone() {
4193 let stmt = parse("count(distinct User { .name })").unwrap();
4194 match stmt {
4195 Statement::Query(q) => {
4196 let agg = q.aggregation.unwrap();
4197 assert_eq!(agg.function, AggFunc::CountDistinct);
4198 assert_eq!(agg.argument, Some(Expr::Field("name".into())));
4199 }
4200 _ => panic!("expected Query"),
4201 }
4202 }
4203
4204 #[test]
4205 fn test_parse_count_distinct_in_projection() {
4206 let stmt = parse("User group .dept { .dept, count(distinct .name) }").unwrap();
4207 match stmt {
4208 Statement::Query(q) => {
4209 let proj = q.projection.unwrap();
4210 assert_eq!(proj.len(), 2);
4211 match &proj[1].expr {
4212 Expr::FunctionCall(func, _, _) => {
4213 assert_eq!(*func, AggFunc::CountDistinct);
4214 }
4215 _ => panic!("expected FunctionCall"),
4216 }
4217 }
4218 _ => panic!("expected Query"),
4219 }
4220 }
4221
4222 #[test]
4225 fn test_parse_window_row_number_order() {
4226 let stmt = parse("User { .name, rn: row_number() over (order .age) }").unwrap();
4227 match stmt {
4228 Statement::Query(q) => {
4229 let proj = q.projection.unwrap();
4230 assert_eq!(proj.len(), 2);
4231 assert_eq!(proj[1].alias.as_deref(), Some("rn"));
4232 match &proj[1].expr {
4233 Expr::Window {
4234 function,
4235 args,
4236 partition_by,
4237 order_by,
4238 ..
4239 } => {
4240 assert_eq!(*function, WindowFunc::RowNumber);
4241 assert!(args.is_empty());
4242 assert!(partition_by.is_empty());
4243 assert_eq!(order_by.len(), 1);
4244 assert_eq!(order_by[0].expr, Expr::Field("age".into()));
4245 assert!(!order_by[0].descending);
4246 }
4247 other => panic!("expected Window, got {other:?}"),
4248 }
4249 }
4250 _ => panic!("expected query"),
4251 }
4252 }
4253
4254 #[test]
4255 fn test_parse_window_sum_partition_order() {
4256 let stmt =
4257 parse("User { .name, s: sum(.salary) over (partition .dept order .salary) }").unwrap();
4258 match stmt {
4259 Statement::Query(q) => {
4260 let proj = q.projection.unwrap();
4261 assert_eq!(proj.len(), 2);
4262 assert_eq!(proj[1].alias.as_deref(), Some("s"));
4263 match &proj[1].expr {
4264 Expr::Window {
4265 function,
4266 args,
4267 partition_by,
4268 order_by,
4269 ..
4270 } => {
4271 assert_eq!(*function, WindowFunc::Sum);
4272 assert_eq!(args.len(), 1);
4273 assert!(matches!(&args[0], Expr::Field(f) if f == "salary"));
4274 assert_eq!(partition_by, &[Expr::Field("dept".into())]);
4275 assert_eq!(order_by.len(), 1);
4276 assert_eq!(order_by[0].expr, Expr::Field("salary".into()));
4277 assert!(!order_by[0].descending);
4278 }
4279 other => panic!("expected Window, got {other:?}"),
4280 }
4281 }
4282 _ => panic!("expected query"),
4283 }
4284 }
4285
4286 #[test]
4287 fn test_parse_window_rank_desc() {
4288 let stmt =
4289 parse("User { .dept, .salary, r: rank() over (partition .dept order .salary desc) }")
4290 .unwrap();
4291 match stmt {
4292 Statement::Query(q) => {
4293 let proj = q.projection.unwrap();
4294 assert_eq!(proj.len(), 3);
4295 match &proj[2].expr {
4296 Expr::Window {
4297 function,
4298 partition_by,
4299 order_by,
4300 ..
4301 } => {
4302 assert_eq!(*function, WindowFunc::Rank);
4303 assert_eq!(partition_by, &[Expr::Field("dept".into())]);
4304 assert_eq!(order_by.len(), 1);
4305 assert!(order_by[0].descending);
4306 }
4307 other => panic!("expected Window, got {other:?}"),
4308 }
4309 }
4310 _ => panic!("expected query"),
4311 }
4312 }
4313
4314 #[test]
4315 fn test_parse_window_dense_rank() {
4316 let stmt = parse("User { .name, dr: dense_rank() over (order .score desc) }").unwrap();
4317 match stmt {
4318 Statement::Query(q) => {
4319 let proj = q.projection.unwrap();
4320 assert_eq!(proj.len(), 2);
4321 match &proj[1].expr {
4322 Expr::Window { function, .. } => {
4323 assert_eq!(*function, WindowFunc::DenseRank);
4324 }
4325 other => panic!("expected Window, got {other:?}"),
4326 }
4327 }
4328 _ => panic!("expected query"),
4329 }
4330 }
4331
4332 #[test]
4333 fn test_parse_sum_without_over_is_aggregate() {
4334 let stmt = parse("User group .dept { .dept, total: sum(.salary) }").unwrap();
4336 match stmt {
4337 Statement::Query(q) => {
4338 let proj = q.projection.unwrap();
4339 assert_eq!(proj.len(), 2);
4340 match &proj[1].expr {
4341 Expr::FunctionCall(AggFunc::Sum, _, _) => {} other => panic!("expected FunctionCall(Sum), got {other:?}"),
4343 }
4344 }
4345 _ => panic!("expected query"),
4346 }
4347 }
4348
4349 #[test]
4350 fn test_nesting_depth_limit() {
4351 let mut query = String::from("User filter ");
4353 for _ in 0..70 {
4354 query.push('(');
4355 }
4356 query.push_str(".age > 1");
4357 for _ in 0..70 {
4358 query.push(')');
4359 }
4360 let result = parse(&query);
4361 assert!(result.is_err());
4362 let err = result.unwrap_err();
4363 assert!(
4364 err.message().contains("nesting depth"),
4365 "expected nesting depth error, got: {}",
4366 err.message()
4367 );
4368 }
4369
4370 #[test]
4371 fn test_unary_prefix_nesting_depth_limit() {
4372 let query = String::from("User filter ") + &"not ".repeat(5000) + ".active";
4376 let result = parse(&query);
4377 assert!(result.is_err());
4378 let err = result.unwrap_err();
4379 assert!(
4380 err.message().contains("nesting depth"),
4381 "expected nesting depth error, got: {}",
4382 err.message()
4383 );
4384 }
4385
4386 #[test]
4387 fn test_moderate_nesting_succeeds() {
4388 let mut query = String::from("User filter ");
4390 for _ in 0..10 {
4391 query.push('(');
4392 }
4393 query.push_str(".age > 1");
4394 for _ in 0..10 {
4395 query.push(')');
4396 }
4397 assert!(parse(&query).is_ok());
4398 }
4399
4400 #[test]
4404 fn test_parse_fuzz_repro_projection_eof() {
4405 let err = parse("nn{").expect_err("unterminated projection must error, not panic");
4406 let _ = err.message();
4407 }
4408
4409 #[test]
4412 fn test_parse_fuzz_repro_short_projection_eof() {
4413 let err = parse("z{").expect_err("unterminated projection must error, not panic");
4414 let _ = err.message();
4415 }
4416
4417 #[test]
4418 fn test_update_at_statement_start_gives_helpful_error() {
4419 let err =
4420 parse(r#"update User filter .name = "Alice" { age := 31 }"#).expect_err("should fail");
4421 let msg = err.message();
4422 assert!(
4423 msg.contains("pipeline syntax"),
4424 "error should mention pipeline syntax, got: {msg}"
4425 );
4426 assert!(
4427 msg.contains("update"),
4428 "error should mention 'update', got: {msg}"
4429 );
4430 }
4431
4432 #[test]
4433 fn test_delete_at_statement_start_gives_helpful_error() {
4434 let err = parse("delete User filter .age < 18").expect_err("should fail");
4435 let msg = err.message();
4436 assert!(
4437 msg.contains("pipeline syntax"),
4438 "error should mention pipeline syntax, got: {msg}"
4439 );
4440 assert!(
4441 msg.contains("delete"),
4442 "error should mention 'delete', got: {msg}"
4443 );
4444 }
4445}
4446
4447#[cfg(test)]
4448mod cleanup_parser_dx_tests {
4449 use super::*;
4450
4451 #[test]
4452 fn typoed_statement_keyword_gets_suggestion() {
4453 let err = parse("updat User set age = 1").unwrap_err();
4454 let msg = err.to_string();
4455 assert!(msg.contains("near token"), "{msg}");
4456 assert!(msg.contains("did you mean `update`"), "{msg}");
4457 }
4458}
4459
4460#[cfg(test)]
4461mod dogfood_dx_tests {
4462 use super::*;
4463
4464 #[test]
4467 fn reserved_word_field_name_gives_actionable_error() {
4468 let err = parse("type Post { type: str }").unwrap_err();
4469 let msg = err.to_string();
4470 assert!(
4471 msg.contains("'type' is a reserved word")
4472 && msg.contains("field name")
4473 && msg.contains("quote it as `type`"),
4474 "unhelpful message: {msg}"
4475 );
4476 }
4477
4478 #[test]
4479 fn reserved_modifier_word_as_field_name_gives_actionable_error() {
4480 let err = parse("type Post { required: bool }").unwrap_err();
4484 let msg = err.to_string();
4485 assert!(
4486 msg.contains("'required' is a reserved word") && msg.contains("quote it as `required`"),
4487 "unhelpful message: {msg}"
4488 );
4489 }
4490
4491 #[test]
4492 fn reserved_word_in_insert_assignment_gives_actionable_error() {
4493 let err = parse(r#"insert Post { type := "x" }"#).unwrap_err();
4494 let msg = err.to_string();
4495 assert!(msg.contains("'type' is a reserved word"), "{msg}");
4496 }
4497
4498 #[test]
4499 fn reserved_word_in_alter_column_gives_actionable_error() {
4500 let err = parse("alter Post add column order: int").unwrap_err();
4501 let msg = err.to_string();
4502 assert!(msg.contains("'order' is a reserved word"), "{msg}");
4503 }
4504
4505 #[test]
4506 fn backtick_field_name_parses_as_identifier() {
4507 let stmt = parse("type Post { `type`: str, `order`: int }").unwrap();
4508 match stmt {
4509 Statement::CreateType(ct) => {
4510 assert_eq!(ct.fields[0].name, "type");
4511 assert_eq!(ct.fields[1].name, "order");
4512 }
4513 other => panic!("expected CreateType, got {other:?}"),
4514 }
4515 }
4516
4517 #[test]
4518 fn backtick_field_still_honors_modifiers() {
4519 let stmt = parse("type Post { required `type`: str }").unwrap();
4520 match stmt {
4521 Statement::CreateType(ct) => {
4522 assert_eq!(ct.fields[0].name, "type");
4523 assert!(ct.fields[0].required);
4524 }
4525 other => panic!("expected CreateType, got {other:?}"),
4526 }
4527 }
4528
4529 #[test]
4532 fn create_type_if_not_exists_parses() {
4533 let stmt = parse("type Post if not exists { id: int }").unwrap();
4534 match stmt {
4535 Statement::CreateType(ct) => assert!(ct.if_not_exists),
4536 other => panic!("expected CreateType, got {other:?}"),
4537 }
4538 }
4539
4540 #[test]
4541 fn create_type_without_clause_defaults_false() {
4542 let stmt = parse("type Post { id: int }").unwrap();
4543 match stmt {
4544 Statement::CreateType(ct) => assert!(!ct.if_not_exists),
4545 other => panic!("expected CreateType, got {other:?}"),
4546 }
4547 }
4548
4549 #[test]
4550 fn drop_if_exists_parses() {
4551 match parse("drop if exists Post").unwrap() {
4552 Statement::DropTable(dt) => assert!(dt.if_exists),
4553 other => panic!("expected DropTable, got {other:?}"),
4554 }
4555 match parse("drop Post").unwrap() {
4556 Statement::DropTable(dt) => assert!(!dt.if_exists),
4557 other => panic!("expected DropTable, got {other:?}"),
4558 }
4559 }
4560
4561 #[test]
4562 fn drop_view_if_exists_parses() {
4563 match parse("drop view if exists ActiveUsers").unwrap() {
4564 Statement::DropView(dv) => {
4565 assert!(dv.if_exists);
4566 assert_eq!(dv.name, "ActiveUsers");
4567 }
4568 other => panic!("expected DropView, got {other:?}"),
4569 }
4570 }
4571
4572 #[test]
4573 fn add_index_and_unique_if_not_exists_parse() {
4574 match parse("alter Post add index if not exists .slug").unwrap() {
4575 Statement::AlterTable(at) => {
4576 assert!(matches!(
4577 at.action,
4578 AlterAction::AddIndex {
4579 if_not_exists: true,
4580 ..
4581 }
4582 ));
4583 }
4584 other => panic!("expected AlterTable, got {other:?}"),
4585 }
4586 match parse("alter Post add unique if not exists .slug").unwrap() {
4587 Statement::AlterTable(at) => {
4588 assert!(matches!(
4589 at.action,
4590 AlterAction::AddUnique {
4591 if_not_exists: true,
4592 ..
4593 }
4594 ));
4595 }
4596 other => panic!("expected AlterTable, got {other:?}"),
4597 }
4598 }
4599
4600 #[test]
4601 fn expression_index_targets_parse_with_stable_table_local_identity() {
4602 use powdb_storage::stored_json_path::{
4603 StoredJsonPathSegmentV1 as Segment, StoredJsonPathV1,
4604 };
4605
4606 let expected = StoredJsonPathV1::new(
4607 "data",
4608 vec![Segment::Key("author".into()), Segment::Index(0)],
4609 );
4610 for query in [
4611 "alter Post add index (.data->author->0)",
4612 "alter Post add unique if not exists (.data->\"author\"->0)",
4613 "alter Post drop index if exists (.data->author->0)",
4614 ] {
4615 let Statement::AlterTable(alter) = parse(query).unwrap() else {
4616 panic!("expected alter table for {query}");
4617 };
4618 let (target, flag) = match alter.action {
4619 AlterAction::AddIndex {
4620 target,
4621 if_not_exists,
4622 }
4623 | AlterAction::AddUnique {
4624 target,
4625 if_not_exists,
4626 } => (target, if_not_exists),
4627 AlterAction::DropIndex { target, if_exists } => (target, if_exists),
4628 other => panic!("expected index action, got {other:?}"),
4629 };
4630 assert_eq!(target, IndexTarget::JsonPath(expected.clone()));
4631 assert_eq!(
4632 flag,
4633 query.contains("if not exists") || query.contains("if exists")
4634 );
4635 }
4636 }
4637
4638 #[test]
4639 fn expression_index_target_rejects_ambiguous_or_non_path_forms() {
4640 let cases = [
4641 (
4642 "alter Post add index .data->author",
4643 "must be parenthesized",
4644 ),
4645 (
4646 "alter Post add index (p.data->author)",
4647 "qualified JSON paths",
4648 ),
4649 (
4650 "alter Post add index (.data)",
4651 "use `.column` for a stored column",
4652 ),
4653 (
4654 "alter Post add index (.data->age + 1)",
4655 "only a direct JSON path",
4656 ),
4657 (
4658 "alter Post drop index ({ value := 1 })",
4659 "expected an unqualified JSON path",
4660 ),
4661 ];
4662 for (query, expected) in cases {
4663 let error = parse(query).expect_err(query).to_string();
4664 assert!(
4665 error.contains(expected),
4666 "`{query}` should mention `{expected}`, got `{error}`"
4667 );
4668 }
4669 }
4670
4671 #[test]
4672 fn alter_drop_column_if_exists_parses() {
4673 match parse("alter Post drop column if exists status").unwrap() {
4674 Statement::AlterTable(at) => {
4675 assert!(matches!(
4676 at.action,
4677 AlterAction::DropColumn {
4678 if_exists: true,
4679 ..
4680 }
4681 ));
4682 }
4683 other => panic!("expected AlterTable, got {other:?}"),
4684 }
4685 }
4686
4687 #[test]
4690 fn schema_parses_to_list_types() {
4691 assert_eq!(parse("schema").unwrap(), Statement::ListTypes);
4692 }
4693
4694 #[test]
4695 fn describe_parses_to_describe() {
4696 assert_eq!(
4697 parse("describe Post").unwrap(),
4698 Statement::Describe("Post".to_string())
4699 );
4700 }
4701
4702 #[test]
4703 fn schema_with_type_aliases_describe() {
4704 assert_eq!(
4705 parse("schema Post").unwrap(),
4706 Statement::Describe("Post".to_string())
4707 );
4708 }
4709
4710 #[test]
4711 fn schema_links_parses_to_list_links() {
4712 assert_eq!(parse("schema links").unwrap(), Statement::ListLinks);
4713 }
4714
4715 #[test]
4716 fn describe_links_still_names_a_table() {
4717 assert_eq!(
4720 parse("describe links").unwrap(),
4721 Statement::Describe("links".to_string())
4722 );
4723 }
4724}
4725
4726#[cfg(test)]
4727mod json_path_tests {
4728 use super::*;
4729
4730 fn filter_of(src: &str) -> Expr {
4732 match parse(src).unwrap() {
4733 Statement::Query(q) => q.filter.expect("expected a filter"),
4734 other => panic!("expected a query, got {other:?}"),
4735 }
4736 }
4737
4738 #[test]
4739 fn ident_key_path() {
4740 let e = filter_of(r#"Post filter .data->author->name = "x""#);
4742 let Expr::BinaryOp(lhs, BinOp::Eq, _) = e else {
4743 panic!("expected an equality, got {e:?}");
4744 };
4745 assert_eq!(
4746 *lhs,
4747 Expr::JsonPath {
4748 base: Box::new(Expr::Field("data".into())),
4749 segments: vec![PathSeg::Key("author".into()), PathSeg::Key("name".into())],
4750 }
4751 );
4752 }
4753
4754 #[test]
4755 fn string_form_key_path() {
4756 let e = filter_of(r#"Post filter .data->"weird key!" = 1"#);
4758 let Expr::BinaryOp(lhs, _, _) = e else {
4759 panic!("expected binop");
4760 };
4761 assert_eq!(
4762 *lhs,
4763 Expr::JsonPath {
4764 base: Box::new(Expr::Field("data".into())),
4765 segments: vec![PathSeg::Key("weird key!".into())],
4766 }
4767 );
4768 }
4769
4770 #[test]
4771 fn array_index_path() {
4772 let e = filter_of(r#"Post filter .data->tags->0 = "rust""#);
4774 let Expr::BinaryOp(lhs, _, _) = e else {
4775 panic!("expected binop");
4776 };
4777 assert_eq!(
4778 *lhs,
4779 Expr::JsonPath {
4780 base: Box::new(Expr::Field("data".into())),
4781 segments: vec![PathSeg::Key("tags".into()), PathSeg::Index(0)],
4782 }
4783 );
4784 }
4785
4786 #[test]
4787 fn qualified_base_path() {
4788 let e = filter_of(r#"Post as posts filter posts.data->author = "a""#);
4790 let Expr::BinaryOp(lhs, _, _) = e else {
4791 panic!("expected binop");
4792 };
4793 assert_eq!(
4794 *lhs,
4795 Expr::JsonPath {
4796 base: Box::new(Expr::QualifiedField {
4797 qualifier: "posts".into(),
4798 field: "data".into(),
4799 }),
4800 segments: vec![PathSeg::Key("author".into())],
4801 }
4802 );
4803 }
4804
4805 #[test]
4806 fn path_binds_tighter_than_comparison_and_arithmetic() {
4807 let e = filter_of("Post filter .data->age > 21");
4809 let Expr::BinaryOp(lhs, BinOp::Gt, rhs) = e else {
4810 panic!("expected a top-level `>`, got {e:?}");
4811 };
4812 assert!(matches!(*lhs, Expr::JsonPath { .. }));
4813 assert_eq!(*rhs, Expr::Literal(Literal::Int(21)));
4814
4815 let e = filter_of("Post filter .a->b + 1 = 3");
4817 let Expr::BinaryOp(add, BinOp::Eq, _) = e else {
4818 panic!("expected eq");
4819 };
4820 let Expr::BinaryOp(lhs, BinOp::Add, _) = *add else {
4821 panic!("expected `+` under `=`, got {add:?}");
4822 };
4823 assert!(matches!(*lhs, Expr::JsonPath { .. }));
4824 }
4825
4826 #[test]
4827 fn dash_vs_arrow_lexing() {
4828 let idx = filter_of("Post filter .a->1 = 0");
4831 let Expr::BinaryOp(lhs, BinOp::Eq, _) = idx else {
4832 panic!("expected eq");
4833 };
4834 assert_eq!(
4835 *lhs,
4836 Expr::JsonPath {
4837 base: Box::new(Expr::Field("a".into())),
4838 segments: vec![PathSeg::Index(1)],
4839 }
4840 );
4841
4842 let sub = filter_of("Post filter .a - 1 = 0");
4845 let Expr::BinaryOp(lhs, BinOp::Eq, _) = sub else {
4846 panic!("expected eq");
4847 };
4848 assert!(
4849 matches!(*lhs, Expr::BinaryOp(_, BinOp::Sub, _)),
4850 "`.a - 1` should be subtraction, got {lhs:?}"
4851 );
4852
4853 assert!(
4858 parse("Post filter .a-1 = 0").is_err(),
4859 "`.a-1` should fail to parse (negative-literal gotcha)"
4860 );
4861
4862 assert!(
4864 parse("Post filter .a - > 0").is_err(),
4865 "`.a - >` should fail to parse"
4866 );
4867 }
4868
4869 #[test]
4870 fn negative_index_rejected() {
4871 let err = parse("Post filter .data->-1 = 0").unwrap_err();
4872 assert!(
4873 err.to_string().contains("array index"),
4874 "expected an index error, got: {err}"
4875 );
4876 }
4877
4878 #[test]
4879 fn path_on_literal_base_rejected() {
4880 let err = parse("Post filter 5->x = 1").unwrap_err();
4882 assert!(
4883 err.to_string().to_lowercase().contains("field base"),
4884 "expected a field-base error, got: {err}"
4885 );
4886 }
4887
4888 #[test]
4889 fn json_path_assignment_target_is_targeted_unsupported() {
4890 for stmt in [
4895 "Doc update { .data->x := 5 }",
4896 "Doc update { data->x := 5 }",
4897 ] {
4898 let err = parse(stmt).unwrap_err();
4899 assert!(
4900 matches!(err, ParseError::Unsupported { .. }),
4901 "{stmt}: expected Unsupported, got {err:?}"
4902 );
4903 let msg = err.to_string();
4904 assert!(
4905 msg.contains("JSON path assignment targets are not supported"),
4906 "{stmt}: message must state the unsupported feature: {msg}"
4907 );
4908 assert!(
4909 msg.contains("json_set"),
4910 "{stmt}: message must point at the whole-column alternative: {msg}"
4911 );
4912 }
4913 assert!(parse(r#"Doc update { data := "{}" }"#).is_ok());
4915 }
4916
4917 #[test]
4918 fn json_type_scalar_parses() {
4919 let e = filter_of(r#"Post filter json_type(.data->x) = "string""#);
4920 let Expr::BinaryOp(lhs, _, _) = e else {
4921 panic!("expected binop");
4922 };
4923 let Expr::ScalarFunc(ScalarFn::JsonType, args) = *lhs else {
4924 panic!("expected json_type call, got {lhs:?}");
4925 };
4926 assert_eq!(args.len(), 1);
4927 assert!(matches!(args[0], Expr::JsonPath { .. }));
4928 }
4929
4930 #[test]
4931 fn path_in_projection() {
4932 let stmt = parse("Post { author: .data->author }").unwrap();
4935 let Statement::Query(q) = stmt else {
4936 panic!("expected query");
4937 };
4938 let proj = q.projection.unwrap();
4939 assert_eq!(proj[0].alias.as_deref(), Some("author"));
4940 assert!(matches!(proj[0].expr, Expr::JsonPath { .. }));
4941
4942 let Statement::Query(ordered) = parse("Post order .data->author { .id }").unwrap() else {
4943 panic!("expected query");
4944 };
4945 assert!(matches!(
4946 ordered.order.unwrap().keys[0].expr,
4947 Expr::JsonPath { .. }
4948 ));
4949
4950 let Statement::Query(grouped) =
4951 parse("Post group .data->author { .data->author }").unwrap()
4952 else {
4953 panic!("expected query");
4954 };
4955 assert!(matches!(
4956 grouped.group_by.unwrap().keys[0].expr,
4957 Expr::JsonPath { .. }
4958 ));
4959 }
4960}
4961
4962#[cfg(test)]
4967mod token_text_roundtrip {
4968 use super::*;
4969 use proptest::prelude::*;
4970
4971 fn every_token() -> Vec<Token> {
4981 let mut toks = vec![
4982 Token::Ident("User".into()),
4985 Token::Ident("order".into()),
4986 Token::Ident("true".into()),
4987 Token::Ident("null".into()),
4988 Token::Ident("column name".into()),
4989 Token::Ident("1st".into()),
4990 Token::Ident("a-b".into()),
4991 Token::Ident("has#hash".into()),
4992 Token::Ident("has.dot".into()),
4993 Token::Ident("héllo".into()),
4994 Token::DotIdent("name".into()),
4995 Token::DotIdent("order".into()),
4996 Token::DotIdent("field name".into()),
4997 Token::DotIdent("1st".into()),
4998 Token::DotIdent("a-b".into()),
4999 Token::IntLit(0),
5002 Token::IntLit(-1),
5003 Token::IntLit(i64::MIN),
5004 Token::IntLit(i64::MAX),
5005 Token::FloatLit(0.0),
5006 Token::FloatLit(-0.0),
5007 Token::FloatLit(3.0),
5008 Token::FloatLit(-2.0),
5009 Token::FloatLit(1.5),
5010 Token::FloatLit(1e300),
5011 Token::FloatLit(1e-300),
5012 Token::FloatLit(f64::MIN_POSITIVE),
5013 Token::StringLit(String::new()),
5016 Token::StringLit("plain".into()),
5017 Token::StringLit("back\\slash".into()),
5018 Token::StringLit("he said \"hi\"".into()),
5019 Token::StringLit("line\nbreak".into()),
5020 Token::StringLit("tab\there".into()),
5021 Token::StringLit("carriage\rreturn".into()),
5022 Token::StringLit("`backtick`".into()),
5023 Token::StringLit("# not a comment".into()),
5024 Token::StringLit("} filter .x = 1".into()),
5025 Token::StringLit("\\\"".into()),
5026 Token::BoolLit(true),
5027 Token::BoolLit(false),
5028 Token::Param("1".into()),
5029 Token::Param("name".into()),
5030 Token::Param(String::new()),
5031 ];
5032 toks.extend([
5033 Token::Type,
5034 Token::Filter,
5035 Token::Order,
5036 Token::Limit,
5037 Token::Offset,
5038 Token::Insert,
5039 Token::Update,
5040 Token::Delete,
5041 Token::Upsert,
5042 Token::Returning,
5043 Token::Select,
5044 Token::Required,
5045 Token::Default,
5046 Token::Auto,
5047 Token::Multi,
5048 Token::Link,
5049 Token::Index,
5050 Token::Unique,
5051 Token::On,
5052 Token::Conflict,
5053 Token::Asc,
5054 Token::Desc,
5055 Token::And,
5056 Token::Or,
5057 Token::Not,
5058 Token::Exists,
5059 Token::Let,
5060 Token::As,
5061 Token::Match,
5062 Token::Group,
5063 Token::Join,
5064 Token::Inner,
5065 Token::LeftKw,
5066 Token::RightKw,
5067 Token::Outer,
5068 Token::Cross,
5069 Token::Transaction,
5070 Token::Begin,
5071 Token::Commit,
5072 Token::Rollback,
5073 Token::View,
5074 Token::Materialized,
5075 Token::Refresh,
5076 Token::Union,
5077 Token::Having,
5078 Token::Distinct,
5079 Token::In,
5080 Token::Between,
5081 Token::Like,
5082 Token::Count,
5083 Token::Avg,
5084 Token::Sum,
5085 Token::Min,
5086 Token::Max,
5087 Token::Raw,
5088 Token::Is,
5089 Token::Null,
5090 ]);
5091 toks.extend([
5092 Token::Upper,
5093 Token::Lower,
5094 Token::Length,
5095 Token::Trim,
5096 Token::Substring,
5097 Token::Concat,
5098 Token::Abs,
5099 Token::Round,
5100 Token::Ceil,
5101 Token::Floor,
5102 Token::Sqrt,
5103 Token::Pow,
5104 Token::Now,
5105 Token::Extract,
5106 Token::DateAdd,
5107 Token::DateDiff,
5108 Token::JsonType,
5109 Token::JsonText,
5110 Token::Cast,
5111 Token::Case,
5112 Token::When,
5113 Token::Then,
5114 Token::Else,
5115 Token::End,
5116 Token::Over,
5117 Token::Partition,
5118 Token::RowNumber,
5119 Token::Rank,
5120 Token::DenseRank,
5121 Token::Alter,
5122 Token::Drop,
5123 Token::Add,
5124 Token::Column,
5125 Token::Explain,
5126 Token::Schema,
5127 Token::Describe,
5128 ]);
5129 toks.extend([
5130 Token::Eq,
5131 Token::Neq,
5132 Token::Lt,
5133 Token::Gt,
5134 Token::Lte,
5135 Token::Gte,
5136 Token::Assign,
5137 Token::Arrow,
5138 Token::Pipe,
5139 Token::Coalesce,
5140 Token::Plus,
5141 Token::Minus,
5142 Token::Star,
5143 Token::Slash,
5144 Token::LBrace,
5145 Token::RBrace,
5146 Token::LParen,
5147 Token::RParen,
5148 Token::Comma,
5149 Token::Colon,
5150 Token::Dot,
5151 ]);
5152 toks
5153 }
5154
5155 fn assert_round_trips(tokens: &[Token]) {
5157 let text =
5158 tokens_to_text(tokens).unwrap_or_else(|e| panic!("no source text for {tokens:?}: {e}"));
5159 let relexed = lex(&text)
5160 .unwrap_or_else(|e| panic!("`{text}` from {tokens:?} does not lex: {}", e.message));
5161 let mut expected = tokens.to_vec();
5162 expected.push(Token::Eof);
5163 assert_eq!(relexed, expected, "`{text}` re-lexes to different tokens");
5164 }
5165
5166 #[test]
5167 fn every_token_round_trips_on_its_own() {
5168 for tok in every_token() {
5169 assert_round_trips(std::slice::from_ref(&tok));
5170 }
5171 }
5172
5173 #[test]
5174 fn eof_contributes_no_text() {
5175 assert_eq!(tokens_to_text(&[Token::Eof]).unwrap(), "");
5176 assert_eq!(tokens_to_text(&[]).unwrap(), "");
5177 }
5178
5179 #[test]
5185 fn unspellable_tokens_are_typed_errors() {
5186 for tok in [
5187 Token::Ident("has`tick".into()),
5188 Token::DotIdent("has`tick".into()),
5189 Token::Ident(String::new()),
5190 Token::FloatLit(f64::INFINITY),
5191 Token::FloatLit(f64::NAN),
5192 Token::Param("two words".into()),
5193 ] {
5194 let err = tokens_to_text(std::slice::from_ref(&tok))
5195 .expect_err("{tok:?} must not be written back wrong");
5196 assert!(
5197 matches!(err, ParseError::Unsupported { .. }),
5198 "expected a typed Unsupported error for {tok:?}, got {err:?}"
5199 );
5200 }
5201 }
5202
5203 #[test]
5206 fn unspellable_view_source_is_refused() {
5207 let huge = format!("1{}.0", "0".repeat(400)); let err = parse(&format!("materialize V as U filter .x = {huge}"))
5209 .expect_err("a view source that cannot round-trip must be refused");
5210 assert!(
5211 matches!(err, ParseError::Unsupported { .. }),
5212 "expected a typed Unsupported error, got {err:?}"
5213 );
5214 }
5215
5216 proptest! {
5217 #[test]
5220 fn any_token_stream_round_trips(
5221 tokens in proptest::collection::vec(
5222 proptest::sample::select(every_token()),
5223 0..12usize,
5224 )
5225 ) {
5226 let text = tokens_to_text(&tokens)
5227 .map_err(|e| TestCaseError::fail(format!("no source text: {e}")))?;
5228 let relexed = lex(&text)
5229 .map_err(|e| TestCaseError::fail(format!("`{text}` does not lex: {}", e.message)))?;
5230 let mut expected = tokens.clone();
5231 expected.push(Token::Eof);
5232 prop_assert_eq!(relexed, expected, "`{}` re-lexes to different tokens", text);
5233 }
5234 }
5235}