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