1use crate::types::AttributeValue;
10use std::collections::HashMap;
11
12#[derive(Debug, Clone)]
20pub enum Statement {
21 Select {
22 table_name: String,
23 projections: Vec<String>, where_clause: Option<WhereClause>,
25 },
26 Insert {
27 table_name: String,
28 item: HashMap<String, PartiqlValue>,
29 if_not_exists: bool,
30 },
31 #[non_exhaustive]
32 Update {
33 table_name: String,
34 set_clauses: Vec<SetClause>,
35 remove_paths: Vec<String>,
36 where_clause: Option<WhereClause>,
37 returning: Option<ReturningVariant>,
40 },
41 #[non_exhaustive]
42 Delete {
43 table_name: String,
44 where_clause: Option<WhereClause>,
45 returning: Option<ReturningVariant>,
49 },
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum ReturningVariant {
61 AllOld,
62 ModifiedOld,
63 AllNew,
64 ModifiedNew,
65}
66
67impl ReturningVariant {
68 pub fn as_sql(self) -> &'static str {
71 match self {
72 ReturningVariant::AllOld => "ALL OLD",
73 ReturningVariant::ModifiedOld => "MODIFIED OLD",
74 ReturningVariant::AllNew => "ALL NEW",
75 ReturningVariant::ModifiedNew => "MODIFIED NEW",
76 }
77 }
78}
79
80pub fn table_name(stmt: &Statement) -> Option<&str> {
82 match stmt {
83 Statement::Select { table_name, .. }
84 | Statement::Insert { table_name, .. }
85 | Statement::Update { table_name, .. }
86 | Statement::Delete { table_name, .. } => Some(table_name),
87 }
88}
89
90pub fn returning_variant(stmt: &Statement) -> Option<ReturningVariant> {
93 match stmt {
94 Statement::Update { returning, .. } | Statement::Delete { returning, .. } => *returning,
95 _ => None,
96 }
97}
98
99pub(crate) fn count_projection_rejection(statement: &str) -> Option<String> {
116 fn advance(c: char, line: &mut usize, col: &mut usize) {
117 if c == '\n' {
118 *line += 1;
119 *col = 1;
120 } else {
121 *col += 1;
122 }
123 }
124
125 let chars: Vec<char> = statement.chars().collect();
126 let len = chars.len();
127 let mut i = 0;
128 let mut line = 1usize;
129 let mut col = 1usize;
130 let mut first_word_seen = false;
131
132 while i < len {
133 let c = chars[i];
134
135 if c == '\'' || c == '"' {
138 let quote = c;
139 advance(c, &mut line, &mut col);
140 i += 1;
141 while i < len {
142 let ch = chars[i];
143 advance(ch, &mut line, &mut col);
144 i += 1;
145 if ch == quote {
146 if i < len && chars[i] == quote {
147 advance(chars[i], &mut line, &mut col);
148 i += 1; } else {
150 break;
151 }
152 }
153 }
154 continue;
155 }
156
157 if c.is_ascii_alphabetic() || c == '_' {
158 let word_line = line;
159 let word_col = col;
160 let start = i;
161 while i < len && (chars[i].is_ascii_alphanumeric() || chars[i] == '_') {
162 advance(chars[i], &mut line, &mut col);
163 i += 1;
164 }
165 let word: String = chars[start..i].iter().collect();
166
167 if !first_word_seen {
168 first_word_seen = true;
169 if !word.eq_ignore_ascii_case("SELECT") {
170 return None;
171 }
172 continue;
173 }
174 if word.eq_ignore_ascii_case("FROM") {
175 return None;
176 }
177 if word.eq_ignore_ascii_case("COUNT") {
178 let mut j = i;
179 while j < len && chars[j].is_ascii_whitespace() {
180 j += 1;
181 }
182 if j < len && chars[j] == '(' {
183 return Some(format!(
184 "Unexpected path component at {word_line}:{word_col}:5"
185 ));
186 }
187 }
188 continue;
189 }
190
191 advance(c, &mut line, &mut col);
192 i += 1;
193 }
194
195 None
196}
197
198#[derive(Debug, Clone)]
200pub struct SetClause {
201 pub path: String,
202 pub value: SetValue,
203}
204
205#[derive(Debug, Clone, PartialEq)]
208pub enum SetValue {
209 Simple(PartiqlValue),
211 Add(String, PartiqlValue),
213 Sub(String, PartiqlValue),
215 ListAppend(PartiqlValue, PartiqlValue),
217}
218
219#[derive(Debug, Clone)]
224pub struct WhereClause {
225 pub groups: Vec<Vec<WhereCondition>>,
227}
228
229impl WhereClause {
230 pub fn from_conditions(conditions: Vec<WhereCondition>) -> Self {
232 Self {
233 groups: vec![conditions],
234 }
235 }
236
237 pub fn from_groups(groups: Vec<Vec<WhereCondition>>) -> Self {
239 Self { groups }
240 }
241}
242
243#[derive(Debug, Clone)]
245pub enum WhereCondition {
246 Comparison(Condition),
247 Exists(String),
248 NotExists(String),
249 BeginsWith(String, PartiqlValue),
250 NotBeginsWith(String, PartiqlValue),
251 Between(String, PartiqlValue, PartiqlValue),
252 In(String, Vec<PartiqlValue>),
253 Contains(String, PartiqlValue),
254 IsMissing(String),
255 IsNotMissing(String),
256}
257
258#[derive(Debug, Clone)]
260pub struct Condition {
261 pub path: String,
262 pub op: CompOp,
263 pub value: PartiqlValue,
264}
265
266#[derive(Debug, Clone, PartialEq)]
268pub enum CompOp {
269 Eq,
270 Ne,
271 Lt,
272 Le,
273 Gt,
274 Ge,
275}
276
277#[derive(Debug, Clone, PartialEq)]
279pub enum PartiqlValue {
280 Literal(AttributeValue),
281 Parameter(usize), }
283
284pub fn parse(input: &str) -> Result<Statement, String> {
286 let mut tokenizer = Tokenizer::new(input)?;
287 let first = tokenizer
288 .next_token()?
289 .ok_or("Empty statement")?
290 .to_uppercase();
291
292 match first.as_str() {
293 "SELECT" => parse_select(&mut tokenizer),
294 "INSERT" => parse_insert(&mut tokenizer),
295 "UPDATE" => parse_update(&mut tokenizer),
296 "DELETE" => parse_delete(&mut tokenizer),
297 _ => Err("Expected data manipulation".to_string()),
300 }
301}
302
303fn parse_select(t: &mut Tokenizer) -> Result<Statement, String> {
304 let projections = parse_projections(t)?;
306
307 expect_keyword(t, "FROM")?;
309
310 let table_name = parse_table_name(t)?;
312
313 let where_clause = parse_optional_where(t)?;
315
316 Ok(Statement::Select {
317 table_name,
318 projections,
319 where_clause,
320 })
321}
322
323fn parse_projections(t: &mut Tokenizer) -> Result<Vec<String>, String> {
324 let tok = t.peek_token()?.ok_or("Expected projection")?;
325
326 if tok == "*" {
327 t.next_token()?; return Ok(Vec::new());
329 }
330
331 let mut projections = Vec::new();
336 loop {
337 let name = t
338 .next_token()?
339 .ok_or("Expected projection attribute name")?;
340 let mut path = unquote(&name);
341
342 loop {
344 match t.peek_token()? {
345 Some(ref s) if s == "." => {
346 t.next_token()?; let segment = t.next_token()?.ok_or("Expected attribute name after '.'")?;
348 path.push('.');
349 path.push_str(&unquote(&segment));
350 }
351 Some(ref s) if s == "[" => {
352 t.next_token()?; let idx = t.next_token()?.ok_or("Expected index in '[]'")?;
354 let close = t.next_token()?.ok_or("Expected ']'")?;
355 if close != "]" {
356 return Err(format!("Expected ']' but got '{close}'"));
357 }
358 path.push('[');
359 path.push_str(&idx);
360 path.push(']');
361 }
362 _ => break,
363 }
364 }
365
366 projections.push(path);
367
368 match t.peek_token()? {
369 Some(ref s) if s == "," => {
370 t.next_token()?; }
372 _ => break,
373 }
374 }
375
376 Ok(projections)
377}
378
379fn parse_insert(t: &mut Tokenizer) -> Result<Statement, String> {
380 expect_keyword(t, "INTO")?;
381 let table_name = parse_table_name(t)?;
382 expect_keyword(t, "VALUE")?;
383
384 let item = parse_item_literal_partiql(t)?;
386
387 let if_not_exists = if let Some(ref tok) = t.peek_token()? {
389 if tok.eq_ignore_ascii_case("IF") {
390 t.next_token()?; expect_keyword(t, "NOT")?;
392 expect_keyword(t, "EXISTS")?;
393 true
394 } else {
395 false
396 }
397 } else {
398 false
399 };
400
401 Ok(Statement::Insert {
402 table_name,
403 item,
404 if_not_exists,
405 })
406}
407
408fn parse_update(t: &mut Tokenizer) -> Result<Statement, String> {
409 let table_name = parse_table_name(t)?;
410
411 let mut set_clauses = Vec::new();
414 let mut remove_paths = Vec::new();
415
416 if let Some(ref tok) = t.peek_token()? {
417 if tok.eq_ignore_ascii_case("SET") {
418 t.next_token()?; loop {
420 let path_tok = t.next_token()?.ok_or("Expected attribute path in SET")?;
421 let path = parse_dotted_path_from_token(&path_tok, t)?;
422
423 let eq = t.next_token()?.ok_or("Expected '='")?;
424 if eq != "=" {
425 return Err(format!("Expected '=' but got '{eq}'"));
426 }
427
428 let value = parse_set_value(t)?;
429 set_clauses.push(SetClause { path, value });
430
431 match t.peek_token()? {
432 Some(ref s) if s == "," => {
433 t.next_token()?; }
435 _ => break,
436 }
437 }
438 }
439 }
440
441 if let Some(ref tok) = t.peek_token()? {
443 if tok.eq_ignore_ascii_case("REMOVE") {
444 t.next_token()?; loop {
446 let path_tok = t.next_token()?.ok_or("Expected attribute path in REMOVE")?;
447 let path = parse_dotted_path_from_token(&path_tok, t)?;
448 remove_paths.push(path);
449 match t.peek_token()? {
450 Some(ref s) if s == "," => {
451 t.next_token()?;
452 }
453 _ => break,
454 }
455 }
456 }
457 }
458
459 if set_clauses.is_empty() && remove_paths.is_empty() {
460 return Err("UPDATE requires at least one SET or REMOVE clause".to_string());
461 }
462
463 let where_clause = parse_optional_where(t)?;
464 let returning = parse_optional_returning(t)?;
465
466 Ok(Statement::Update {
467 table_name,
468 set_clauses,
469 remove_paths,
470 where_clause,
471 returning,
472 })
473}
474
475fn parse_set_value(t: &mut Tokenizer) -> Result<SetValue, String> {
478 if let Some(ref tok) = t.peek_token()? {
480 if tok.eq_ignore_ascii_case("list_append") {
481 t.next_token()?; expect_char(t, "(")?;
483 let first = parse_value(t)?;
484 let comma = t.next_token()?.ok_or("Expected ',' in list_append")?;
485 if comma != "," {
486 return Err(format!("Expected ',' but got '{comma}'"));
487 }
488 let second = parse_value(t)?;
489 expect_char(t, ")")?;
490 return Ok(SetValue::ListAppend(first, second));
491 }
492 }
493
494 let first = parse_value(t)?;
495
496 match t.peek_token()? {
498 Some(ref s) if s == "+" => {
499 t.next_token()?; let second = parse_value(t)?;
501 let attr_path = match &first {
504 PartiqlValue::Literal(AttributeValue::S(s)) => s.clone(),
505 _ => {
510 return Err(
511 "Expected attribute path on left side of '+' expression".to_string()
512 );
513 }
514 };
515 Ok(SetValue::Add(attr_path, second))
516 }
517 Some(ref s) if s == "-" => {
518 t.next_token()?; let second = parse_value(t)?;
520 let attr_path = match &first {
521 PartiqlValue::Literal(AttributeValue::S(s)) => s.clone(),
522 _ => {
523 return Err(
524 "Expected attribute path on left side of '-' expression".to_string()
525 );
526 }
527 };
528 Ok(SetValue::Sub(attr_path, second))
529 }
530 _ => Ok(SetValue::Simple(first)),
531 }
532}
533
534fn parse_delete(t: &mut Tokenizer) -> Result<Statement, String> {
535 expect_keyword(t, "FROM")?;
536 let table_name = parse_table_name(t)?;
537 let where_clause = parse_optional_where(t)?;
538 let returning = parse_optional_returning(t)?;
539
540 Ok(Statement::Delete {
541 table_name,
542 where_clause,
543 returning,
544 })
545}
546
547fn parse_optional_returning(t: &mut Tokenizer) -> Result<Option<ReturningVariant>, String> {
554 match t.peek_token()? {
555 Some(ref s) if s.eq_ignore_ascii_case("RETURNING") => {
556 t.next_token()?; let first = t
558 .next_token()?
559 .ok_or("Expected returning variant after RETURNING")?;
560 let second = t
561 .next_token()?
562 .ok_or("Expected returning variant after RETURNING")?;
563 let star = t.next_token()?.ok_or("Expected '*' in RETURNING clause")?;
564 if star != "*" || t.peek_token()?.is_some() {
565 return Err(
566 "Malformed RETURNING clause; expected RETURNING <ALL|MODIFIED> <OLD|NEW> *"
567 .to_string(),
568 );
569 }
570 let variant = match (
571 first.to_ascii_uppercase().as_str(),
572 second.to_ascii_uppercase().as_str(),
573 ) {
574 ("ALL", "OLD") => ReturningVariant::AllOld,
575 ("MODIFIED", "OLD") => ReturningVariant::ModifiedOld,
576 ("ALL", "NEW") => ReturningVariant::AllNew,
577 ("MODIFIED", "NEW") => ReturningVariant::ModifiedNew,
578 _ => {
579 return Err(format!(
580 "Unsupported RETURNING clause: RETURNING {} {} *",
581 first.to_ascii_uppercase(),
582 second.to_ascii_uppercase()
583 ));
584 }
585 };
586 Ok(Some(variant))
587 }
588 _ => Ok(None),
589 }
590}
591
592fn parse_table_name(t: &mut Tokenizer) -> Result<String, String> {
593 let name = t.next_token()?.ok_or("Expected table name")?;
594 Ok(unquote(&name))
595}
596
597fn parse_optional_where(t: &mut Tokenizer) -> Result<Option<WhereClause>, String> {
598 match t.peek_token()? {
599 Some(ref s) if s.eq_ignore_ascii_case("WHERE") => {
600 t.next_token()?; let groups = parse_conditions_with_or(t)?;
602 Ok(Some(WhereClause::from_groups(groups)))
603 }
604 _ => Ok(None),
605 }
606}
607
608fn parse_conditions_with_or(t: &mut Tokenizer) -> Result<Vec<Vec<WhereCondition>>, String> {
611 let mut groups: Vec<Vec<WhereCondition>> = Vec::new();
612 let mut current_group: Vec<WhereCondition> = Vec::new();
613
614 loop {
615 let condition = parse_single_condition(t)?;
616 current_group.push(condition);
617
618 match t.peek_token()? {
619 Some(ref s) if s.eq_ignore_ascii_case("AND") => {
620 t.next_token()?; }
622 Some(ref s) if s.eq_ignore_ascii_case("OR") => {
623 t.next_token()?; groups.push(current_group);
625 current_group = Vec::new();
626 }
627 _ => break,
628 }
629 }
630
631 groups.push(current_group);
632 Ok(groups)
633}
634
635fn parse_single_condition(t: &mut Tokenizer) -> Result<WhereCondition, String> {
637 let tok = t.next_token()?.ok_or("Expected condition in WHERE")?;
638 let tok_upper = tok.to_uppercase();
639
640 match tok_upper.as_str() {
641 "EXISTS" => {
642 expect_char(t, "(")?;
643 let path = parse_function_path(t)?;
644 expect_char(t, ")")?;
645 Ok(WhereCondition::Exists(path))
646 }
647 "BEGINS_WITH" => {
648 expect_char(t, "(")?;
649 let path = parse_function_path(t)?;
650 let comma = t.next_token()?.ok_or("Expected ',' in BEGINS_WITH")?;
651 if comma != "," {
652 return Err(format!("Expected ',' but got '{comma}'"));
653 }
654 let value = parse_value(t)?;
655 expect_char(t, ")")?;
656 Ok(WhereCondition::BeginsWith(path, value))
657 }
658 "CONTAINS" => {
659 expect_char(t, "(")?;
660 let path = parse_function_path(t)?;
661 let comma = t.next_token()?.ok_or("Expected ',' in CONTAINS")?;
662 if comma != "," {
663 return Err(format!("Expected ',' but got '{comma}'"));
664 }
665 let value = parse_value(t)?;
666 expect_char(t, ")")?;
667 Ok(WhereCondition::Contains(path, value))
668 }
669 "NOT" => {
670 let func = t.next_token()?.ok_or("Expected function name after NOT")?;
671 if func.eq_ignore_ascii_case("EXISTS") {
672 expect_char(t, "(")?;
673 let path = parse_function_path(t)?;
674 expect_char(t, ")")?;
675 Ok(WhereCondition::NotExists(path))
676 } else if func.eq_ignore_ascii_case("BEGINS_WITH") {
677 expect_char(t, "(")?;
678 let path = parse_function_path(t)?;
679 let comma = t.next_token()?.ok_or("Expected ',' in NOT BEGINS_WITH")?;
680 if comma != "," {
681 return Err(format!("Expected ',' but got '{comma}'"));
682 }
683 let value = parse_value(t)?;
684 expect_char(t, ")")?;
685 Ok(WhereCondition::NotBeginsWith(path, value))
686 } else {
687 Err(format!("Unsupported NOT function: {func}"))
688 }
689 }
690 _ => {
691 let path = parse_dotted_path_from_token(&tok, t)?;
694
695 let next = t
697 .peek_token()?
698 .ok_or("Expected operator after attribute path")?;
699 let next_upper = next.to_uppercase();
700
701 match next_upper.as_str() {
702 "BETWEEN" => {
703 t.next_token()?; let low = parse_value(t)?;
705 expect_keyword(t, "AND")?;
706 let high = parse_value(t)?;
707 Ok(WhereCondition::Between(path, low, high))
708 }
709 "IN" => {
710 t.next_token()?; let open = t.next_token()?.ok_or("Expected '(' or '[' after IN")?;
715 let close_char = match open.as_str() {
716 "(" => ")",
717 "[" => "]",
718 other => {
719 return Err(format!("Expected '(' or '[' after IN, got '{other}'"));
720 }
721 };
722 let mut values = Vec::new();
723 loop {
724 let peek = t.peek_token()?.ok_or("Unexpected end of IN list")?;
725 if peek == close_char {
726 t.next_token()?; break;
728 }
729 if peek == "," {
730 t.next_token()?; continue;
732 }
733 values.push(parse_value(t)?);
734 }
735 Ok(WhereCondition::In(path, values))
736 }
737 "IS" => {
738 t.next_token()?; let kw = t.next_token()?.ok_or("Expected MISSING or NOT after IS")?;
740 let kw_upper = kw.to_uppercase();
741 match kw_upper.as_str() {
742 "MISSING" => Ok(WhereCondition::IsMissing(path)),
743 "NOT" => {
744 expect_keyword(t, "MISSING")?;
745 Ok(WhereCondition::IsNotMissing(path))
746 }
747 other => Err(format!(
748 "Expected MISSING or NOT MISSING after IS, got '{other}'"
749 )),
750 }
751 }
752 _ => {
753 let op_tok = t.next_token()?.ok_or("Expected comparison operator")?;
755 let op = match op_tok.as_str() {
756 "=" => CompOp::Eq,
757 "<>" | "!=" => CompOp::Ne,
758 "<" => CompOp::Lt,
759 "<=" => CompOp::Le,
760 ">" => CompOp::Gt,
761 ">=" => CompOp::Ge,
762 other => return Err(format!("Unknown operator: {other}")),
763 };
764 let value = parse_value(t)?;
765 Ok(WhereCondition::Comparison(Condition { path, op, value }))
766 }
767 }
768 }
769 }
770}
771
772fn parse_dotted_path_from_token(first_tok: &str, t: &mut Tokenizer) -> Result<String, String> {
775 let mut path = unquote(first_tok);
776 while let Some(ref next) = t.peek_token()? {
777 if next == "." {
778 t.next_token()?; let seg = t.next_token()?.ok_or("Expected attribute name after '.'")?;
780 path.push('.');
781 path.push_str(&unquote(&seg));
782 } else if next == "[" {
783 t.next_token()?; let idx = t.next_token()?.ok_or("Expected index in '[]'")?;
785 let close = t.next_token()?.ok_or("Expected ']'")?;
786 if close != "]" {
787 return Err(format!("Expected ']' but got '{close}'"));
788 }
789 path.push('[');
790 path.push_str(&idx);
791 path.push(']');
792 } else {
793 break;
794 }
795 }
796 Ok(path)
797}
798
799fn parse_function_path(t: &mut Tokenizer) -> Result<String, String> {
802 let tok = t.next_token()?.ok_or("Expected path in function")?;
803 parse_dotted_path_from_token(&tok, t)
804}
805
806fn expect_char(t: &mut Tokenizer, expected: &str) -> Result<(), String> {
807 let tok = t.next_token()?.ok_or(format!("Expected '{expected}'"))?;
808 if tok != expected {
809 return Err(format!("Expected '{expected}' but got '{tok}'"));
810 }
811 Ok(())
812}
813
814fn parse_value(t: &mut Tokenizer) -> Result<PartiqlValue, String> {
815 let tok = t.next_token()?.ok_or("Expected value")?;
816
817 if tok == "?" {
818 let idx = t.next_param_index();
819 return Ok(PartiqlValue::Parameter(idx));
820 }
821
822 if tok.starts_with('\'') && tok.ends_with('\'') && tok.len() >= 2 {
824 let s = tok[1..tok.len() - 1].to_string();
825 return Ok(PartiqlValue::Literal(AttributeValue::S(s)));
826 }
827
828 if tok == "<" {
830 if let Some(ref next) = t.peek_token()? {
831 if next == "<" {
832 t.next_token()?; let mut elements = Vec::new();
834 loop {
835 let peek = t.peek_token()?.ok_or("Unexpected end of set literal")?;
836 if peek == ">" {
837 t.next_token()?; let next_close = t.peek_token()?;
840 if next_close.as_deref() == Some(">") {
841 t.next_token()?;
842 }
843 break;
844 }
845 if peek == "," {
846 t.next_token()?;
847 continue;
848 }
849 elements.push(parse_value(t)?);
850 }
851 return set_literal_to_value(elements);
852 }
853 }
854 }
855
856 if tok == "[" {
858 let mut items = Vec::new();
859 loop {
860 let peek = t.peek_token()?.ok_or("Unexpected end of list")?;
861 if peek == "]" {
862 t.next_token()?;
863 break;
864 }
865 if peek == "," {
866 t.next_token()?;
867 continue;
868 }
869 items.push(parse_value(t)?);
870 }
871 let mut avs = Vec::new();
873 for item in items {
874 match item {
875 PartiqlValue::Literal(av) => avs.push(av),
876 PartiqlValue::Parameter(_) => {
877 return Err(
881 "Parameter placeholders inside list literals are not yet supported"
882 .to_string(),
883 );
884 }
885 }
886 }
887 return Ok(PartiqlValue::Literal(AttributeValue::L(avs)));
888 }
889
890 if tok == "{" {
892 let mut map = HashMap::new();
893 loop {
894 let peek = t.peek_token()?.ok_or("Unexpected end of map literal")?;
895 if peek == "}" {
896 t.next_token()?;
897 break;
898 }
899 if peek == "," {
900 t.next_token()?;
901 continue;
902 }
903 let key_tok = t.next_token()?.ok_or("Expected key in map literal")?;
904 let key = unquote(&key_tok);
905 let colon = t.next_token()?.ok_or("Expected ':'")?;
906 if colon != ":" {
907 return Err(format!("Expected ':' but got '{colon}'"));
908 }
909 let val = parse_value(t)?;
910 match val {
911 PartiqlValue::Literal(av) => {
912 map.insert(key, av);
913 }
914 PartiqlValue::Parameter(_) => {
915 return Err(
916 "Parameter placeholders inside map literals are not yet supported"
917 .to_string(),
918 );
919 }
920 }
921 }
922 return Ok(PartiqlValue::Literal(AttributeValue::M(map)));
923 }
924
925 if tok == "-" || tok == "+" {
927 if let Some(ref next) = t.peek_token()? {
928 if next.starts_with(|c: char| c.is_ascii_digit()) {
929 let num = t.next_token()?.unwrap();
930 return Ok(PartiqlValue::Literal(AttributeValue::N(format!(
931 "{tok}{num}"
932 ))));
933 }
934 }
935 }
936
937 if tok.starts_with(|c: char| c.is_ascii_digit()) {
939 return Ok(PartiqlValue::Literal(AttributeValue::N(tok)));
940 }
941
942 match tok.to_uppercase().as_str() {
944 "TRUE" => return Ok(PartiqlValue::Literal(AttributeValue::BOOL(true))),
945 "FALSE" => return Ok(PartiqlValue::Literal(AttributeValue::BOOL(false))),
946 "NULL" => return Ok(PartiqlValue::Literal(AttributeValue::NULL(true))),
947 _ => {}
948 }
949
950 if tok
953 .chars()
954 .next()
955 .is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
956 {
957 let mut path = tok.clone();
959 while let Some(ref next) = t.peek_token()? {
960 if next == "." {
961 t.next_token()?;
962 let seg = t.next_token()?.ok_or("Expected attribute name after '.'")?;
963 path.push('.');
964 path.push_str(&unquote(&seg));
965 } else {
966 break;
967 }
968 }
969 return Ok(PartiqlValue::Literal(AttributeValue::S(path)));
970 }
971
972 Err(format!("Unexpected value token: {tok}"))
973}
974
975fn set_literal_to_value(elements: Vec<PartiqlValue>) -> Result<PartiqlValue, String> {
977 if elements.is_empty() {
978 return Err("Set literals cannot be empty".to_string());
979 }
980
981 let first = match &elements[0] {
983 PartiqlValue::Literal(av) => av,
984 PartiqlValue::Parameter(_) => {
985 return Err("Parameter placeholders in set literals are not supported".to_string());
986 }
987 };
988
989 match first {
990 AttributeValue::S(_) => {
991 let mut ss = Vec::new();
992 for elem in &elements {
993 match elem {
994 PartiqlValue::Literal(AttributeValue::S(s)) => ss.push(s.clone()),
995 _ => return Err("Mixed types in string set literal".to_string()),
996 }
997 }
998 Ok(PartiqlValue::Literal(AttributeValue::SS(ss)))
999 }
1000 AttributeValue::N(_) => {
1001 let mut ns = Vec::new();
1002 for elem in &elements {
1003 match elem {
1004 PartiqlValue::Literal(AttributeValue::N(n)) => ns.push(n.clone()),
1005 _ => return Err("Mixed types in number set literal".to_string()),
1006 }
1007 }
1008 Ok(PartiqlValue::Literal(AttributeValue::NS(ns)))
1009 }
1010 _ => Err(format!(
1011 "Unsupported element type in set literal: {first:?}"
1012 )),
1013 }
1014}
1015
1016fn parse_item_literal(t: &mut Tokenizer) -> Result<HashMap<String, AttributeValue>, String> {
1018 let open = t.next_token()?.ok_or("Expected '{'")?;
1019 if open != "{" {
1020 return Err(format!("Expected '{{' but got '{open}'"));
1021 }
1022
1023 let mut item = HashMap::new();
1024
1025 loop {
1026 let tok = t.peek_token()?.ok_or("Unexpected end of item literal")?;
1027 if tok == "}" {
1028 t.next_token()?; break;
1030 }
1031
1032 if tok == "," {
1034 t.next_token()?;
1035 continue;
1036 }
1037
1038 let key_tok = t.next_token()?.ok_or("Expected key in item literal")?;
1040 let key = unquote(&key_tok);
1041
1042 let colon = t.next_token()?.ok_or("Expected ':'")?;
1043 if colon != ":" {
1044 return Err(format!("Expected ':' but got '{colon}'"));
1045 }
1046
1047 let val = parse_item_value(t)?;
1049 item.insert(key, val);
1050 }
1051
1052 Ok(item)
1053}
1054
1055fn parse_item_value(t: &mut Tokenizer) -> Result<AttributeValue, String> {
1057 let tok = t.peek_token()?.ok_or("Expected value")?;
1058
1059 if tok == "{" {
1060 let inner = parse_item_literal(t)?;
1062 return Ok(AttributeValue::M(inner));
1063 }
1064
1065 if tok == "[" {
1066 t.next_token()?; let mut items = Vec::new();
1069 loop {
1070 let peek = t.peek_token()?.ok_or("Unexpected end of list")?;
1071 if peek == "]" {
1072 t.next_token()?;
1073 break;
1074 }
1075 if peek == "," {
1076 t.next_token()?;
1077 continue;
1078 }
1079 items.push(parse_item_value(t)?);
1080 }
1081 return Ok(AttributeValue::L(items));
1082 }
1083
1084 if tok == "<" {
1086 if let Some(ref next_tok) = t.peek_token_at(1)? {
1087 if next_tok == "<" {
1088 t.next_token()?; t.next_token()?; let mut elements = Vec::new();
1091 loop {
1092 let peek = t.peek_token()?.ok_or("Unexpected end of set literal")?;
1093 if peek == ">" {
1094 t.next_token()?; if t.peek_token()?.as_deref() == Some(">") {
1096 t.next_token()?; }
1098 break;
1099 }
1100 if peek == "," {
1101 t.next_token()?;
1102 continue;
1103 }
1104 elements.push(parse_item_value(t)?);
1105 }
1106 return item_value_set_literal(elements);
1107 }
1108 }
1109 }
1110
1111 let tok = t.next_token()?.ok_or("Expected value")?;
1113
1114 if tok.starts_with('\'') && tok.ends_with('\'') && tok.len() >= 2 {
1116 return Ok(AttributeValue::S(tok[1..tok.len() - 1].to_string()));
1117 }
1118
1119 if tok == "-" || tok == "+" {
1121 if let Some(ref next) = t.peek_token()? {
1122 if next.starts_with(|c: char| c.is_ascii_digit()) {
1123 let num = t.next_token()?.unwrap();
1124 return Ok(AttributeValue::N(format!("{tok}{num}")));
1125 }
1126 }
1127 }
1128
1129 if tok.starts_with(|c: char| c.is_ascii_digit()) {
1131 return Ok(AttributeValue::N(tok));
1132 }
1133
1134 match tok.to_uppercase().as_str() {
1135 "TRUE" => Ok(AttributeValue::BOOL(true)),
1136 "FALSE" => Ok(AttributeValue::BOOL(false)),
1137 "NULL" => Ok(AttributeValue::NULL(true)),
1138 _ => Err(format!("Unexpected value in item literal: {tok}")),
1139 }
1140}
1141
1142fn item_value_set_literal(elements: Vec<AttributeValue>) -> Result<AttributeValue, String> {
1144 if elements.is_empty() {
1145 return Err("Set literals cannot be empty".to_string());
1146 }
1147 match &elements[0] {
1148 AttributeValue::S(_) => {
1149 let mut ss = Vec::new();
1150 for e in elements {
1151 match e {
1152 AttributeValue::S(s) => ss.push(s),
1153 _ => return Err("Mixed types in string set literal".to_string()),
1154 }
1155 }
1156 Ok(AttributeValue::SS(ss))
1157 }
1158 AttributeValue::N(_) => {
1159 let mut ns = Vec::new();
1160 for e in elements {
1161 match e {
1162 AttributeValue::N(n) => ns.push(n),
1163 _ => return Err("Mixed types in number set literal".to_string()),
1164 }
1165 }
1166 Ok(AttributeValue::NS(ns))
1167 }
1168 _ => Err(format!(
1169 "Unsupported element type in set literal: {:?}",
1170 elements[0]
1171 )),
1172 }
1173}
1174
1175fn parse_item_literal_partiql(t: &mut Tokenizer) -> Result<HashMap<String, PartiqlValue>, String> {
1178 let open = t.next_token()?.ok_or("Expected '{'")?;
1179 if open != "{" {
1180 return Err(format!("Expected '{{' but got '{open}'"));
1181 }
1182
1183 let mut item = HashMap::new();
1184
1185 loop {
1186 let tok = t.peek_token()?.ok_or("Unexpected end of item literal")?;
1187 if tok == "}" {
1188 t.next_token()?; break;
1190 }
1191
1192 if tok == "," {
1194 t.next_token()?;
1195 continue;
1196 }
1197
1198 let key_tok = t.next_token()?.ok_or("Expected key in item literal")?;
1200 let key = unquote(&key_tok);
1201
1202 let colon = t.next_token()?.ok_or("Expected ':'")?;
1203 if colon != ":" {
1204 return Err(format!("Expected ':' but got '{colon}'"));
1205 }
1206
1207 let val = parse_item_value_partiql(t)?;
1209 item.insert(key, val);
1210 }
1211
1212 Ok(item)
1213}
1214
1215fn parse_item_value_partiql(t: &mut Tokenizer) -> Result<PartiqlValue, String> {
1218 let tok = t.peek_token()?.ok_or("Expected value")?;
1219
1220 if tok == "?" {
1221 t.next_token()?; let idx = t.next_param_index();
1223 return Ok(PartiqlValue::Parameter(idx));
1224 }
1225
1226 if tok == "[" {
1228 return parse_value(t);
1229 }
1230
1231 if tok == "{" {
1233 let inner = parse_item_literal_partiql(t)?;
1235 let mut map = HashMap::new();
1237 for (k, v) in inner {
1238 match v {
1239 PartiqlValue::Literal(av) => {
1240 map.insert(k, av);
1241 }
1242 PartiqlValue::Parameter(_) => {
1243 return Err(
1246 "Parameter placeholders inside nested map literals are not yet fully supported"
1247 .to_string(),
1248 );
1249 }
1250 }
1251 }
1252 return Ok(PartiqlValue::Literal(AttributeValue::M(map)));
1253 }
1254
1255 let av = parse_item_value(t)?;
1258 Ok(PartiqlValue::Literal(av))
1259}
1260
1261fn unquote(s: &str) -> String {
1263 if (s.starts_with('"') && s.ends_with('"')) || (s.starts_with('\'') && s.ends_with('\'')) {
1264 s[1..s.len() - 1].to_string()
1265 } else {
1266 s.to_string()
1267 }
1268}
1269
1270fn expect_keyword(t: &mut Tokenizer, kw: &str) -> Result<(), String> {
1271 let tok = t.next_token()?.ok_or(format!("Expected '{kw}'"))?;
1272 if !tok.eq_ignore_ascii_case(kw) {
1273 return Err(format!("Expected '{kw}' but got '{tok}'"));
1274 }
1275 Ok(())
1276}
1277
1278struct Tokenizer {
1283 tokens: Vec<String>,
1284 pos: usize,
1285 param_counter: usize,
1286}
1287
1288impl Tokenizer {
1289 fn new(input: &str) -> Result<Self, String> {
1290 let tokens = tokenize(input)?;
1291 Ok(Self {
1292 tokens,
1293 pos: 0,
1294 param_counter: 0,
1295 })
1296 }
1297
1298 fn next_token(&mut self) -> Result<Option<String>, String> {
1299 if self.pos >= self.tokens.len() {
1300 return Ok(None);
1301 }
1302 let tok = self.tokens[self.pos].clone();
1303 self.pos += 1;
1304 Ok(Some(tok))
1305 }
1306
1307 fn peek_token(&self) -> Result<Option<String>, String> {
1308 if self.pos >= self.tokens.len() {
1309 return Ok(None);
1310 }
1311 Ok(Some(self.tokens[self.pos].clone()))
1312 }
1313
1314 fn peek_token_at(&self, offset: usize) -> Result<Option<String>, String> {
1316 let idx = self.pos + offset;
1317 if idx >= self.tokens.len() {
1318 return Ok(None);
1319 }
1320 Ok(Some(self.tokens[idx].clone()))
1321 }
1322
1323 fn next_param_index(&mut self) -> usize {
1324 let idx = self.param_counter;
1325 self.param_counter += 1;
1326 idx
1327 }
1328}
1329
1330fn tokenize(input: &str) -> Result<Vec<String>, String> {
1332 let mut tokens = Vec::new();
1333 let chars: Vec<char> = input.chars().collect();
1334 let len = chars.len();
1335 let mut i = 0;
1336
1337 while i < len {
1338 if chars[i].is_ascii_whitespace() {
1340 i += 1;
1341 continue;
1342 }
1343
1344 match chars[i] {
1346 '{' | '}' | '[' | ']' | '(' | ')' | ',' | ':' | '*' | '?' | '+' | '-' | '.' => {
1347 tokens.push(chars[i].to_string());
1349 i += 1;
1350 continue;
1351 }
1352 _ => {}
1353 }
1354
1355 if i + 1 < len {
1357 let two = format!("{}{}", chars[i], chars[i + 1]);
1358 match two.as_str() {
1359 "<>" | "<=" | ">=" | "!=" => {
1360 tokens.push(two);
1361 i += 2;
1362 continue;
1363 }
1364 _ => {}
1365 }
1366 }
1367
1368 if matches!(chars[i], '=' | '<' | '>') {
1370 tokens.push(chars[i].to_string());
1371 i += 1;
1372 continue;
1373 }
1374
1375 if chars[i] == '\'' {
1377 let mut s = String::from('\'');
1378 i += 1;
1379 while i < len {
1380 if chars[i] == '\'' {
1381 if i + 1 < len && chars[i + 1] == '\'' {
1383 s.push('\'');
1384 i += 2;
1385 } else {
1386 break; }
1388 } else {
1389 s.push(chars[i]);
1390 i += 1;
1391 }
1392 }
1393 if i < len {
1394 s.push('\'');
1395 i += 1;
1396 }
1397 tokens.push(s);
1398 continue;
1399 }
1400
1401 if chars[i] == '"' {
1403 let mut s = String::from('"');
1404 i += 1;
1405 while i < len {
1406 if chars[i] == '"' {
1407 if i + 1 < len && chars[i + 1] == '"' {
1409 s.push('"');
1410 i += 2;
1411 } else {
1412 break; }
1414 } else {
1415 s.push(chars[i]);
1416 i += 1;
1417 }
1418 }
1419 if i < len {
1420 s.push('"');
1421 i += 1;
1422 }
1423 tokens.push(s);
1424 continue;
1425 }
1426
1427 if chars[i].is_ascii_digit() {
1429 let mut s = String::new();
1430 while i < len && (chars[i].is_ascii_digit() || chars[i] == '.') {
1431 s.push(chars[i]);
1432 i += 1;
1433 }
1434 tokens.push(s);
1435 continue;
1436 }
1437
1438 if chars[i].is_ascii_alphabetic() || chars[i] == '_' {
1440 let mut s = String::new();
1441 while i < len && (chars[i].is_ascii_alphanumeric() || chars[i] == '_') {
1442 s.push(chars[i]);
1443 i += 1;
1444 }
1445 tokens.push(s);
1446 continue;
1447 }
1448
1449 return Err(format!("Unexpected character: '{}'", chars[i]));
1451 }
1452
1453 Ok(tokens)
1454}
1455
1456#[cfg(test)]
1457mod tests {
1458 use super::*;
1459
1460 #[test]
1461 fn test_parse_select_star() {
1462 let stmt = parse("SELECT * FROM \"TestTable\"").unwrap();
1463 match stmt {
1464 Statement::Select {
1465 table_name,
1466 projections,
1467 where_clause,
1468 } => {
1469 assert_eq!(table_name, "TestTable");
1470 assert!(projections.is_empty());
1471 assert!(where_clause.is_none());
1472 }
1473 _ => panic!("Expected SELECT"),
1474 }
1475 }
1476
1477 #[test]
1478 fn test_parse_select_with_where() {
1479 let stmt = parse("SELECT * FROM \"T\" WHERE pk = 'hello'").unwrap();
1480 match stmt {
1481 Statement::Select {
1482 where_clause: Some(wc),
1483 ..
1484 } => {
1485 assert_eq!(wc.groups[0].len(), 1);
1486 match &wc.groups[0][0] {
1487 WhereCondition::Comparison(c) => {
1488 assert_eq!(c.path, "pk");
1489 assert_eq!(c.op, CompOp::Eq);
1490 }
1491 _ => panic!("Expected Comparison"),
1492 }
1493 }
1494 _ => panic!("Expected SELECT with WHERE"),
1495 }
1496 }
1497
1498 #[test]
1499 fn test_parse_select_with_projection() {
1500 let stmt = parse("SELECT name, age FROM \"Users\"").unwrap();
1501 match stmt {
1502 Statement::Select { projections, .. } => {
1503 assert_eq!(projections, vec!["name", "age"]);
1504 }
1505 _ => panic!("Expected SELECT"),
1506 }
1507 }
1508
1509 #[test]
1510 fn test_parse_insert() {
1511 let stmt =
1512 parse("INSERT INTO \"TestTable\" VALUE {'pk': 'key1', 'data': 'hello'}").unwrap();
1513 match stmt {
1514 Statement::Insert {
1515 table_name, item, ..
1516 } => {
1517 assert_eq!(table_name, "TestTable");
1518 assert_eq!(
1519 item.get("pk"),
1520 Some(&PartiqlValue::Literal(AttributeValue::S(
1521 "key1".to_string()
1522 )))
1523 );
1524 assert_eq!(
1525 item.get("data"),
1526 Some(&PartiqlValue::Literal(AttributeValue::S(
1527 "hello".to_string()
1528 )))
1529 );
1530 }
1531 _ => panic!("Expected INSERT"),
1532 }
1533 }
1534
1535 #[test]
1536 fn test_parse_update() {
1537 let stmt = parse("UPDATE \"T\" SET name = 'Bob' WHERE pk = 'k1'").unwrap();
1538 match stmt {
1539 Statement::Update {
1540 table_name,
1541 set_clauses,
1542 where_clause,
1543 ..
1544 } => {
1545 assert_eq!(table_name, "T");
1546 assert_eq!(set_clauses.len(), 1);
1547 assert_eq!(set_clauses[0].path, "name");
1548 assert!(where_clause.is_some());
1549 }
1550 _ => panic!("Expected UPDATE"),
1551 }
1552 }
1553
1554 #[test]
1555 fn test_parse_delete() {
1556 let stmt = parse("DELETE FROM \"T\" WHERE pk = 'k1'").unwrap();
1557 match stmt {
1558 Statement::Delete {
1559 table_name,
1560 where_clause,
1561 returning,
1562 } => {
1563 assert_eq!(table_name, "T");
1564 assert!(where_clause.is_some());
1565 assert!(returning.is_none());
1566 }
1567 _ => panic!("Expected DELETE"),
1568 }
1569 }
1570
1571 #[test]
1572 fn test_parse_delete_returning_all_old() {
1573 let stmt = parse("DELETE FROM \"T\" WHERE pk = 'k1' RETURNING ALL OLD *").unwrap();
1574 match stmt {
1575 Statement::Delete {
1576 table_name,
1577 where_clause,
1578 returning,
1579 } => {
1580 assert_eq!(table_name, "T");
1581 assert!(where_clause.is_some());
1582 assert_eq!(returning, Some(ReturningVariant::AllOld));
1583 }
1584 _ => panic!("Expected DELETE"),
1585 }
1586 }
1587
1588 #[test]
1589 fn test_parse_delete_returning_is_case_insensitive() {
1590 let stmt = parse("DELETE FROM \"T\" WHERE pk = 'k1' returning all old *").unwrap();
1591 match stmt {
1592 Statement::Delete { returning, .. } => {
1593 assert_eq!(returning, Some(ReturningVariant::AllOld))
1594 }
1595 _ => panic!("Expected DELETE"),
1596 }
1597 }
1598
1599 #[test]
1600 fn test_parse_delete_returning_recognises_all_variants() {
1601 let cases = [
1605 ("RETURNING ALL OLD *", ReturningVariant::AllOld),
1606 ("RETURNING MODIFIED OLD *", ReturningVariant::ModifiedOld),
1607 ("RETURNING ALL NEW *", ReturningVariant::AllNew),
1608 ("RETURNING MODIFIED NEW *", ReturningVariant::ModifiedNew),
1609 ];
1610 for (clause, expected) in cases {
1611 let stmt = parse(&format!("DELETE FROM \"T\" WHERE pk = 'k1' {clause}")).unwrap();
1612 match stmt {
1613 Statement::Delete { returning, .. } => {
1614 assert_eq!(returning, Some(expected), "clause {clause}")
1615 }
1616 _ => panic!("Expected DELETE for {clause}"),
1617 }
1618 }
1619 }
1620
1621 #[test]
1622 fn test_parse_update_returning_recognises_all_variants() {
1623 let cases = [
1624 ("RETURNING ALL OLD *", ReturningVariant::AllOld),
1625 ("RETURNING MODIFIED OLD *", ReturningVariant::ModifiedOld),
1626 ("RETURNING ALL NEW *", ReturningVariant::AllNew),
1627 ("RETURNING MODIFIED NEW *", ReturningVariant::ModifiedNew),
1628 ];
1629 for (clause, expected) in cases {
1630 let stmt = parse(&format!(
1631 "UPDATE \"T\" SET data = 'x' WHERE pk = 'k1' {clause}"
1632 ))
1633 .unwrap();
1634 match stmt {
1635 Statement::Update { returning, .. } => {
1636 assert_eq!(returning, Some(expected), "clause {clause}")
1637 }
1638 _ => panic!("Expected UPDATE for {clause}"),
1639 }
1640 }
1641 }
1642
1643 #[test]
1644 fn test_parse_delete_returning_rejects_incomplete_clause() {
1645 assert!(parse("DELETE FROM \"T\" WHERE pk = 'k1' RETURNING ALL OLD").is_err());
1646 assert!(parse("DELETE FROM \"T\" WHERE pk = 'k1' RETURNING").is_err());
1647 assert!(parse("DELETE FROM \"T\" WHERE pk = 'k1' RETURNING ALL OLD attr").is_err());
1648 }
1649
1650 #[test]
1651 fn test_parse_returning_rejects_invalid_keyword_pairs() {
1652 for clause in [
1655 "RETURNING OLD ALL *",
1656 "RETURNING NEW MODIFIED *",
1657 "RETURNING ALL ALL *",
1658 "RETURNING FOO BAR *",
1659 ] {
1660 let err = parse(&format!("DELETE FROM \"T\" WHERE pk = 'k1' {clause}")).unwrap_err();
1661 assert!(
1662 err.contains("Unsupported RETURNING clause"),
1663 "clause {clause} gave unexpected error: {err}"
1664 );
1665 }
1666 }
1667
1668 #[test]
1669 fn test_parse_parameter() {
1670 let stmt = parse("SELECT * FROM \"T\" WHERE pk = ?").unwrap();
1671 match stmt {
1672 Statement::Select {
1673 where_clause: Some(wc),
1674 ..
1675 } => match &wc.groups[0][0] {
1676 WhereCondition::Comparison(c) => match &c.value {
1677 PartiqlValue::Parameter(0) => {}
1678 other => panic!("Expected Parameter(0), got {other:?}"),
1679 },
1680 _ => panic!("Expected Comparison"),
1681 },
1682 _ => panic!("Expected SELECT with WHERE"),
1683 }
1684 }
1685
1686 #[test]
1687 fn test_parse_numeric_literal() {
1688 let stmt = parse("SELECT * FROM \"T\" WHERE age > 42").unwrap();
1689 match stmt {
1690 Statement::Select {
1691 where_clause: Some(wc),
1692 ..
1693 } => match &wc.groups[0][0] {
1694 WhereCondition::Comparison(c) => {
1695 assert_eq!(c.op, CompOp::Gt);
1696 match &c.value {
1697 PartiqlValue::Literal(AttributeValue::N(n)) => assert_eq!(n, "42"),
1698 other => panic!("Expected N(42), got {other:?}"),
1699 }
1700 }
1701 _ => panic!("Expected Comparison"),
1702 },
1703 _ => panic!("Expected SELECT"),
1704 }
1705 }
1706
1707 #[test]
1708 fn test_parse_insert_with_number() {
1709 let stmt = parse("INSERT INTO \"T\" VALUE {'pk': 'k1', 'age': 25}").unwrap();
1710 match stmt {
1711 Statement::Insert { item, .. } => {
1712 assert_eq!(
1713 item.get("age"),
1714 Some(&PartiqlValue::Literal(AttributeValue::N("25".to_string())))
1715 );
1716 }
1717 _ => panic!("Expected INSERT"),
1718 }
1719 }
1720
1721 #[test]
1722 fn test_invalid_statement() {
1723 let result = parse("MERGE INTO \"T\"");
1724 assert!(result.is_err());
1725 }
1726
1727 #[test]
1728 fn test_empty_statement() {
1729 let result = parse("");
1730 assert!(result.is_err());
1731 }
1732
1733 #[test]
1734 fn test_parse_between() {
1735 let stmt = parse("SELECT * FROM \"T\" WHERE age BETWEEN 18 AND 65").unwrap();
1736 match stmt {
1737 Statement::Select {
1738 where_clause: Some(wc),
1739 ..
1740 } => {
1741 assert_eq!(wc.groups[0].len(), 1);
1742 match &wc.groups[0][0] {
1743 WhereCondition::Between(path, low, high) => {
1744 assert_eq!(path, "age");
1745 match low {
1746 PartiqlValue::Literal(AttributeValue::N(n)) => assert_eq!(n, "18"),
1747 other => panic!("Expected N(18), got {other:?}"),
1748 }
1749 match high {
1750 PartiqlValue::Literal(AttributeValue::N(n)) => assert_eq!(n, "65"),
1751 other => panic!("Expected N(65), got {other:?}"),
1752 }
1753 }
1754 other => panic!("Expected Between, got {other:?}"),
1755 }
1756 }
1757 _ => panic!("Expected SELECT with WHERE"),
1758 }
1759 }
1760
1761 #[test]
1762 fn test_parse_between_and_other_condition() {
1763 let stmt = parse("SELECT * FROM \"T\" WHERE x BETWEEN 1 AND 10 AND y = 'hello'").unwrap();
1764 match stmt {
1765 Statement::Select {
1766 where_clause: Some(wc),
1767 ..
1768 } => {
1769 assert_eq!(wc.groups[0].len(), 2);
1770 assert!(matches!(&wc.groups[0][0], WhereCondition::Between(..)));
1771 assert!(matches!(&wc.groups[0][1], WhereCondition::Comparison(..)));
1772 }
1773 _ => panic!("Expected SELECT with WHERE"),
1774 }
1775 }
1776
1777 #[test]
1778 fn test_parse_in() {
1779 let stmt = parse("SELECT * FROM \"T\" WHERE status IN ('ACTIVE', 'PENDING')").unwrap();
1780 match stmt {
1781 Statement::Select {
1782 where_clause: Some(wc),
1783 ..
1784 } => {
1785 assert_eq!(wc.groups[0].len(), 1);
1786 match &wc.groups[0][0] {
1787 WhereCondition::In(path, values) => {
1788 assert_eq!(path, "status");
1789 assert_eq!(values.len(), 2);
1790 }
1791 other => panic!("Expected In, got {other:?}"),
1792 }
1793 }
1794 _ => panic!("Expected SELECT with WHERE"),
1795 }
1796 }
1797
1798 #[test]
1799 fn test_parse_contains() {
1800 let stmt = parse("SELECT * FROM \"T\" WHERE CONTAINS(name, 'john')").unwrap();
1801 match stmt {
1802 Statement::Select {
1803 where_clause: Some(wc),
1804 ..
1805 } => {
1806 assert_eq!(wc.groups[0].len(), 1);
1807 match &wc.groups[0][0] {
1808 WhereCondition::Contains(path, val) => {
1809 assert_eq!(path, "name");
1810 match val {
1811 PartiqlValue::Literal(AttributeValue::S(s)) => {
1812 assert_eq!(s, "john")
1813 }
1814 other => panic!("Expected S(john), got {other:?}"),
1815 }
1816 }
1817 other => panic!("Expected Contains, got {other:?}"),
1818 }
1819 }
1820 _ => panic!("Expected SELECT with WHERE"),
1821 }
1822 }
1823
1824 #[test]
1825 fn test_parse_is_missing() {
1826 let stmt = parse("SELECT * FROM \"T\" WHERE email IS MISSING").unwrap();
1827 match stmt {
1828 Statement::Select {
1829 where_clause: Some(wc),
1830 ..
1831 } => {
1832 assert_eq!(wc.groups[0].len(), 1);
1833 match &wc.groups[0][0] {
1834 WhereCondition::IsMissing(path) => assert_eq!(path, "email"),
1835 other => panic!("Expected IsMissing, got {other:?}"),
1836 }
1837 }
1838 _ => panic!("Expected SELECT with WHERE"),
1839 }
1840 }
1841
1842 #[test]
1843 fn test_parse_is_not_missing() {
1844 let stmt = parse("SELECT * FROM \"T\" WHERE email IS NOT MISSING").unwrap();
1845 match stmt {
1846 Statement::Select {
1847 where_clause: Some(wc),
1848 ..
1849 } => {
1850 assert_eq!(wc.groups[0].len(), 1);
1851 match &wc.groups[0][0] {
1852 WhereCondition::IsNotMissing(path) => assert_eq!(path, "email"),
1853 other => panic!("Expected IsNotMissing, got {other:?}"),
1854 }
1855 }
1856 _ => panic!("Expected SELECT with WHERE"),
1857 }
1858 }
1859
1860 #[test]
1861 fn test_parse_nested_projection() {
1862 let stmt = parse("SELECT a.b.c, d FROM \"T\"").unwrap();
1863 match stmt {
1864 Statement::Select { projections, .. } => {
1865 assert_eq!(projections, vec!["a.b.c", "d"]);
1866 }
1867 _ => panic!("Expected SELECT"),
1868 }
1869 }
1870
1871 #[test]
1872 fn test_parse_array_index_projection() {
1873 let stmt = parse("SELECT items[0].name FROM \"T\"").unwrap();
1874 match stmt {
1875 Statement::Select { projections, .. } => {
1876 assert_eq!(projections, vec!["items[0].name"]);
1877 }
1878 _ => panic!("Expected SELECT"),
1879 }
1880 }
1881
1882 #[test]
1883 fn test_parse_update_with_remove() {
1884 let stmt =
1885 parse("UPDATE \"T\" SET name = 'Bob' REMOVE age, email WHERE pk = 'k1'").unwrap();
1886 match stmt {
1887 Statement::Update {
1888 set_clauses,
1889 remove_paths,
1890 where_clause,
1891 ..
1892 } => {
1893 assert_eq!(set_clauses.len(), 1);
1894 assert_eq!(remove_paths, vec!["age", "email"]);
1895 assert!(where_clause.is_some());
1896 }
1897 _ => panic!("Expected UPDATE"),
1898 }
1899 }
1900
1901 #[test]
1902 fn test_parse_update_remove_only() {
1903 let stmt = parse("UPDATE \"T\" REMOVE old_field WHERE pk = 'k1'").unwrap();
1904 match stmt {
1905 Statement::Update {
1906 set_clauses,
1907 remove_paths,
1908 ..
1909 } => {
1910 assert!(set_clauses.is_empty());
1911 assert_eq!(remove_paths, vec!["old_field"]);
1912 }
1913 _ => panic!("Expected UPDATE"),
1914 }
1915 }
1916
1917 #[test]
1918 fn test_parse_set_expression_add() {
1919 let stmt = parse("UPDATE \"T\" SET count = count + 1 WHERE pk = 'k1'").unwrap();
1920 match stmt {
1921 Statement::Update { set_clauses, .. } => {
1922 assert_eq!(set_clauses.len(), 1);
1923 match &set_clauses[0].value {
1924 SetValue::Add(attr, val) => {
1925 assert_eq!(attr, "count");
1926 assert_eq!(
1927 val,
1928 &PartiqlValue::Literal(AttributeValue::N("1".to_string()))
1929 );
1930 }
1931 other => panic!("Expected Add, got {other:?}"),
1932 }
1933 }
1934 _ => panic!("Expected UPDATE"),
1935 }
1936 }
1937
1938 #[test]
1939 fn test_parse_count_star_is_a_parse_error() {
1940 assert!(parse("SELECT COUNT(*) FROM \"T\"").is_err());
1943 }
1944
1945 #[test]
1946 fn test_count_rejection_reports_the_token_position() {
1947 assert_eq!(
1948 count_projection_rejection("SELECT COUNT(*) FROM \"T\""),
1949 Some("Unexpected path component at 1:8:5".to_string())
1950 );
1951 assert_eq!(
1954 count_projection_rejection("SELECT count(pk) FROM \"T\""),
1955 Some("Unexpected path component at 1:9:5".to_string())
1956 );
1957 }
1958
1959 #[test]
1960 fn test_count_rejection_tracks_lines() {
1961 assert_eq!(
1962 count_projection_rejection("SELECT\n COUNT(*) FROM \"T\""),
1963 Some("Unexpected path component at 2:2:5".to_string())
1964 );
1965 }
1966
1967 #[test]
1968 fn test_count_rejection_ignores_quotes_and_word_boundaries() {
1969 assert_eq!(
1972 count_projection_rejection("SELECT \"COUNT\" FROM \"T\""),
1973 None
1974 );
1975 assert_eq!(
1976 count_projection_rejection("SELECT 'count(' FROM \"T\""),
1977 None
1978 );
1979 assert_eq!(
1980 count_projection_rejection("SELECT recount(x) FROM \"T\""),
1981 None
1982 );
1983 }
1984
1985 #[test]
1986 fn test_count_rejection_only_applies_before_from_on_a_select() {
1987 assert_eq!(
1989 count_projection_rejection("UPDATE \"T\" SET count = count + 1 WHERE pk = 'k1'"),
1990 None
1991 );
1992 assert_eq!(
1993 count_projection_rejection("SELECT pk FROM \"T\" WHERE count(x)"),
1994 None
1995 );
1996 }
1997
1998 #[test]
1999 fn test_parse_set_literal() {
2000 let stmt = parse("INSERT INTO \"T\" VALUE {'pk': 'k1', 'tags': <<'a', 'b'>>}").unwrap();
2001 match stmt {
2002 Statement::Insert { item, .. } => match item.get("tags") {
2003 Some(PartiqlValue::Literal(AttributeValue::SS(ss))) => {
2004 assert!(ss.contains(&"a".to_string()));
2005 assert!(ss.contains(&"b".to_string()));
2006 }
2007 other => panic!("Expected SS, got {other:?}"),
2008 },
2009 _ => panic!("Expected INSERT"),
2010 }
2011 }
2012
2013 #[test]
2014 fn test_parse_or_condition() {
2015 let stmt = parse("SELECT * FROM \"T\" WHERE status = 'A' OR status = 'B'").unwrap();
2016 match stmt {
2017 Statement::Select {
2018 where_clause: Some(wc),
2019 ..
2020 } => {
2021 assert_eq!(wc.groups.len(), 2);
2022 assert_eq!(wc.groups[0].len(), 1);
2023 assert_eq!(wc.groups[1].len(), 1);
2024 }
2025 _ => panic!("Expected SELECT with WHERE"),
2026 }
2027 }
2028
2029 #[test]
2030 fn test_parse_and_or_mixed() {
2031 let stmt = parse("SELECT * FROM \"T\" WHERE a = 1 AND b = 2 OR c = 3").unwrap();
2032 match stmt {
2033 Statement::Select {
2034 where_clause: Some(wc),
2035 ..
2036 } => {
2037 assert_eq!(wc.groups.len(), 2);
2038 assert_eq!(wc.groups[0].len(), 2); assert_eq!(wc.groups[1].len(), 1); }
2041 _ => panic!("Expected SELECT with WHERE"),
2042 }
2043 }
2044
2045 #[test]
2046 fn test_parse_insert_if_not_exists() {
2047 let stmt =
2048 parse("INSERT INTO \"T\" VALUE {'pk': 'k1', 'name': 'A'} IF NOT EXISTS").unwrap();
2049 match stmt {
2050 Statement::Insert { if_not_exists, .. } => {
2051 assert!(if_not_exists);
2052 }
2053 _ => panic!("Expected INSERT"),
2054 }
2055 }
2056
2057 #[test]
2058 fn test_parse_nested_path_in_where_function() {
2059 let stmt = parse("SELECT * FROM \"T\" WHERE BEGINS_WITH(address.city, 'Lon')").unwrap();
2060 match stmt {
2061 Statement::Select {
2062 where_clause: Some(wc),
2063 ..
2064 } => match &wc.groups[0][0] {
2065 WhereCondition::BeginsWith(path, _) => {
2066 assert_eq!(path, "address.city");
2067 }
2068 other => panic!("Expected BeginsWith, got {other:?}"),
2069 },
2070 _ => panic!("Expected SELECT with WHERE"),
2071 }
2072 }
2073}