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
99#[derive(Debug, Clone)]
101pub struct SetClause {
102 pub path: String,
103 pub value: SetValue,
104}
105
106#[derive(Debug, Clone, PartialEq)]
109pub enum SetValue {
110 Simple(PartiqlValue),
112 Add(String, PartiqlValue),
114 Sub(String, PartiqlValue),
116 ListAppend(PartiqlValue, PartiqlValue),
118}
119
120#[derive(Debug, Clone)]
125pub struct WhereClause {
126 pub groups: Vec<Vec<WhereCondition>>,
128}
129
130impl WhereClause {
131 pub fn from_conditions(conditions: Vec<WhereCondition>) -> Self {
133 Self {
134 groups: vec![conditions],
135 }
136 }
137
138 pub fn from_groups(groups: Vec<Vec<WhereCondition>>) -> Self {
140 Self { groups }
141 }
142}
143
144#[derive(Debug, Clone)]
146pub enum WhereCondition {
147 Comparison(Condition),
148 Exists(String),
149 NotExists(String),
150 BeginsWith(String, PartiqlValue),
151 NotBeginsWith(String, PartiqlValue),
152 Between(String, PartiqlValue, PartiqlValue),
153 In(String, Vec<PartiqlValue>),
154 Contains(String, PartiqlValue),
155 IsMissing(String),
156 IsNotMissing(String),
157}
158
159#[derive(Debug, Clone)]
161pub struct Condition {
162 pub path: String,
163 pub op: CompOp,
164 pub value: PartiqlValue,
165}
166
167#[derive(Debug, Clone, PartialEq)]
169pub enum CompOp {
170 Eq,
171 Ne,
172 Lt,
173 Le,
174 Gt,
175 Ge,
176}
177
178#[derive(Debug, Clone, PartialEq)]
180pub enum PartiqlValue {
181 Literal(AttributeValue),
182 Parameter(usize), }
184
185pub fn parse(input: &str) -> Result<Statement, String> {
187 let mut tokenizer = Tokenizer::new(input)?;
188 let first = tokenizer
189 .next_token()?
190 .ok_or("Empty statement")?
191 .to_uppercase();
192
193 match first.as_str() {
194 "SELECT" => parse_select(&mut tokenizer),
195 "INSERT" => parse_insert(&mut tokenizer),
196 "UPDATE" => parse_update(&mut tokenizer),
197 "DELETE" => parse_delete(&mut tokenizer),
198 _ => Err("Expected data manipulation".to_string()),
201 }
202}
203
204fn parse_select(t: &mut Tokenizer) -> Result<Statement, String> {
205 let projections = parse_projections(t)?;
207
208 expect_keyword(t, "FROM")?;
210
211 let table_name = parse_table_name(t)?;
213
214 let where_clause = parse_optional_where(t)?;
216
217 Ok(Statement::Select {
218 table_name,
219 projections,
220 where_clause,
221 })
222}
223
224fn parse_projections(t: &mut Tokenizer) -> Result<Vec<String>, String> {
225 let tok = t.peek_token()?.ok_or("Expected projection")?;
226
227 if tok == "*" {
228 t.next_token()?; return Ok(Vec::new());
230 }
231
232 if tok.eq_ignore_ascii_case("COUNT") {
234 t.next_token()?; expect_char(t, "(")?;
236 expect_char(t, "*")?;
237 expect_char(t, ")")?;
238 return Ok(vec!["COUNT(*)".to_string()]);
239 }
240
241 let mut projections = Vec::new();
242 loop {
243 let name = t
244 .next_token()?
245 .ok_or("Expected projection attribute name")?;
246 let mut path = unquote(&name);
247
248 loop {
250 match t.peek_token()? {
251 Some(ref s) if s == "." => {
252 t.next_token()?; let segment = t.next_token()?.ok_or("Expected attribute name after '.'")?;
254 path.push('.');
255 path.push_str(&unquote(&segment));
256 }
257 Some(ref s) if s == "[" => {
258 t.next_token()?; let idx = t.next_token()?.ok_or("Expected index in '[]'")?;
260 let close = t.next_token()?.ok_or("Expected ']'")?;
261 if close != "]" {
262 return Err(format!("Expected ']' but got '{close}'"));
263 }
264 path.push('[');
265 path.push_str(&idx);
266 path.push(']');
267 }
268 _ => break,
269 }
270 }
271
272 projections.push(path);
273
274 match t.peek_token()? {
275 Some(ref s) if s == "," => {
276 t.next_token()?; }
278 _ => break,
279 }
280 }
281
282 Ok(projections)
283}
284
285fn parse_insert(t: &mut Tokenizer) -> Result<Statement, String> {
286 expect_keyword(t, "INTO")?;
287 let table_name = parse_table_name(t)?;
288 expect_keyword(t, "VALUE")?;
289
290 let item = parse_item_literal_partiql(t)?;
292
293 let if_not_exists = if let Some(ref tok) = t.peek_token()? {
295 if tok.eq_ignore_ascii_case("IF") {
296 t.next_token()?; expect_keyword(t, "NOT")?;
298 expect_keyword(t, "EXISTS")?;
299 true
300 } else {
301 false
302 }
303 } else {
304 false
305 };
306
307 Ok(Statement::Insert {
308 table_name,
309 item,
310 if_not_exists,
311 })
312}
313
314fn parse_update(t: &mut Tokenizer) -> Result<Statement, String> {
315 let table_name = parse_table_name(t)?;
316
317 let mut set_clauses = Vec::new();
320 let mut remove_paths = Vec::new();
321
322 if let Some(ref tok) = t.peek_token()? {
323 if tok.eq_ignore_ascii_case("SET") {
324 t.next_token()?; loop {
326 let path_tok = t.next_token()?.ok_or("Expected attribute path in SET")?;
327 let path = parse_dotted_path_from_token(&path_tok, t)?;
328
329 let eq = t.next_token()?.ok_or("Expected '='")?;
330 if eq != "=" {
331 return Err(format!("Expected '=' but got '{eq}'"));
332 }
333
334 let value = parse_set_value(t)?;
335 set_clauses.push(SetClause { path, value });
336
337 match t.peek_token()? {
338 Some(ref s) if s == "," => {
339 t.next_token()?; }
341 _ => break,
342 }
343 }
344 }
345 }
346
347 if let Some(ref tok) = t.peek_token()? {
349 if tok.eq_ignore_ascii_case("REMOVE") {
350 t.next_token()?; loop {
352 let path_tok = t.next_token()?.ok_or("Expected attribute path in REMOVE")?;
353 let path = parse_dotted_path_from_token(&path_tok, t)?;
354 remove_paths.push(path);
355 match t.peek_token()? {
356 Some(ref s) if s == "," => {
357 t.next_token()?;
358 }
359 _ => break,
360 }
361 }
362 }
363 }
364
365 if set_clauses.is_empty() && remove_paths.is_empty() {
366 return Err("UPDATE requires at least one SET or REMOVE clause".to_string());
367 }
368
369 let where_clause = parse_optional_where(t)?;
370 let returning = parse_optional_returning(t)?;
371
372 Ok(Statement::Update {
373 table_name,
374 set_clauses,
375 remove_paths,
376 where_clause,
377 returning,
378 })
379}
380
381fn parse_set_value(t: &mut Tokenizer) -> Result<SetValue, String> {
384 if let Some(ref tok) = t.peek_token()? {
386 if tok.eq_ignore_ascii_case("list_append") {
387 t.next_token()?; expect_char(t, "(")?;
389 let first = parse_value(t)?;
390 let comma = t.next_token()?.ok_or("Expected ',' in list_append")?;
391 if comma != "," {
392 return Err(format!("Expected ',' but got '{comma}'"));
393 }
394 let second = parse_value(t)?;
395 expect_char(t, ")")?;
396 return Ok(SetValue::ListAppend(first, second));
397 }
398 }
399
400 let first = parse_value(t)?;
401
402 match t.peek_token()? {
404 Some(ref s) if s == "+" => {
405 t.next_token()?; let second = parse_value(t)?;
407 let attr_path = match &first {
410 PartiqlValue::Literal(AttributeValue::S(s)) => s.clone(),
411 _ => {
416 return Err(
417 "Expected attribute path on left side of '+' expression".to_string()
418 );
419 }
420 };
421 Ok(SetValue::Add(attr_path, second))
422 }
423 Some(ref s) if s == "-" => {
424 t.next_token()?; let second = parse_value(t)?;
426 let attr_path = match &first {
427 PartiqlValue::Literal(AttributeValue::S(s)) => s.clone(),
428 _ => {
429 return Err(
430 "Expected attribute path on left side of '-' expression".to_string()
431 );
432 }
433 };
434 Ok(SetValue::Sub(attr_path, second))
435 }
436 _ => Ok(SetValue::Simple(first)),
437 }
438}
439
440fn parse_delete(t: &mut Tokenizer) -> Result<Statement, String> {
441 expect_keyword(t, "FROM")?;
442 let table_name = parse_table_name(t)?;
443 let where_clause = parse_optional_where(t)?;
444 let returning = parse_optional_returning(t)?;
445
446 Ok(Statement::Delete {
447 table_name,
448 where_clause,
449 returning,
450 })
451}
452
453fn parse_optional_returning(t: &mut Tokenizer) -> Result<Option<ReturningVariant>, String> {
460 match t.peek_token()? {
461 Some(ref s) if s.eq_ignore_ascii_case("RETURNING") => {
462 t.next_token()?; let first = t
464 .next_token()?
465 .ok_or("Expected returning variant after RETURNING")?;
466 let second = t
467 .next_token()?
468 .ok_or("Expected returning variant after RETURNING")?;
469 let star = t.next_token()?.ok_or("Expected '*' in RETURNING clause")?;
470 if star != "*" || t.peek_token()?.is_some() {
471 return Err(
472 "Malformed RETURNING clause; expected RETURNING <ALL|MODIFIED> <OLD|NEW> *"
473 .to_string(),
474 );
475 }
476 let variant = match (
477 first.to_ascii_uppercase().as_str(),
478 second.to_ascii_uppercase().as_str(),
479 ) {
480 ("ALL", "OLD") => ReturningVariant::AllOld,
481 ("MODIFIED", "OLD") => ReturningVariant::ModifiedOld,
482 ("ALL", "NEW") => ReturningVariant::AllNew,
483 ("MODIFIED", "NEW") => ReturningVariant::ModifiedNew,
484 _ => {
485 return Err(format!(
486 "Unsupported RETURNING clause: RETURNING {} {} *",
487 first.to_ascii_uppercase(),
488 second.to_ascii_uppercase()
489 ));
490 }
491 };
492 Ok(Some(variant))
493 }
494 _ => Ok(None),
495 }
496}
497
498fn parse_table_name(t: &mut Tokenizer) -> Result<String, String> {
499 let name = t.next_token()?.ok_or("Expected table name")?;
500 Ok(unquote(&name))
501}
502
503fn parse_optional_where(t: &mut Tokenizer) -> Result<Option<WhereClause>, String> {
504 match t.peek_token()? {
505 Some(ref s) if s.eq_ignore_ascii_case("WHERE") => {
506 t.next_token()?; let groups = parse_conditions_with_or(t)?;
508 Ok(Some(WhereClause::from_groups(groups)))
509 }
510 _ => Ok(None),
511 }
512}
513
514fn parse_conditions_with_or(t: &mut Tokenizer) -> Result<Vec<Vec<WhereCondition>>, String> {
517 let mut groups: Vec<Vec<WhereCondition>> = Vec::new();
518 let mut current_group: Vec<WhereCondition> = Vec::new();
519
520 loop {
521 let condition = parse_single_condition(t)?;
522 current_group.push(condition);
523
524 match t.peek_token()? {
525 Some(ref s) if s.eq_ignore_ascii_case("AND") => {
526 t.next_token()?; }
528 Some(ref s) if s.eq_ignore_ascii_case("OR") => {
529 t.next_token()?; groups.push(current_group);
531 current_group = Vec::new();
532 }
533 _ => break,
534 }
535 }
536
537 groups.push(current_group);
538 Ok(groups)
539}
540
541fn parse_single_condition(t: &mut Tokenizer) -> Result<WhereCondition, String> {
543 let tok = t.next_token()?.ok_or("Expected condition in WHERE")?;
544 let tok_upper = tok.to_uppercase();
545
546 match tok_upper.as_str() {
547 "EXISTS" => {
548 expect_char(t, "(")?;
549 let path = parse_function_path(t)?;
550 expect_char(t, ")")?;
551 Ok(WhereCondition::Exists(path))
552 }
553 "BEGINS_WITH" => {
554 expect_char(t, "(")?;
555 let path = parse_function_path(t)?;
556 let comma = t.next_token()?.ok_or("Expected ',' in BEGINS_WITH")?;
557 if comma != "," {
558 return Err(format!("Expected ',' but got '{comma}'"));
559 }
560 let value = parse_value(t)?;
561 expect_char(t, ")")?;
562 Ok(WhereCondition::BeginsWith(path, value))
563 }
564 "CONTAINS" => {
565 expect_char(t, "(")?;
566 let path = parse_function_path(t)?;
567 let comma = t.next_token()?.ok_or("Expected ',' in CONTAINS")?;
568 if comma != "," {
569 return Err(format!("Expected ',' but got '{comma}'"));
570 }
571 let value = parse_value(t)?;
572 expect_char(t, ")")?;
573 Ok(WhereCondition::Contains(path, value))
574 }
575 "NOT" => {
576 let func = t.next_token()?.ok_or("Expected function name after NOT")?;
577 if func.eq_ignore_ascii_case("EXISTS") {
578 expect_char(t, "(")?;
579 let path = parse_function_path(t)?;
580 expect_char(t, ")")?;
581 Ok(WhereCondition::NotExists(path))
582 } else if func.eq_ignore_ascii_case("BEGINS_WITH") {
583 expect_char(t, "(")?;
584 let path = parse_function_path(t)?;
585 let comma = t.next_token()?.ok_or("Expected ',' in NOT BEGINS_WITH")?;
586 if comma != "," {
587 return Err(format!("Expected ',' but got '{comma}'"));
588 }
589 let value = parse_value(t)?;
590 expect_char(t, ")")?;
591 Ok(WhereCondition::NotBeginsWith(path, value))
592 } else {
593 Err(format!("Unsupported NOT function: {func}"))
594 }
595 }
596 _ => {
597 let path = parse_dotted_path_from_token(&tok, t)?;
600
601 let next = t
603 .peek_token()?
604 .ok_or("Expected operator after attribute path")?;
605 let next_upper = next.to_uppercase();
606
607 match next_upper.as_str() {
608 "BETWEEN" => {
609 t.next_token()?; let low = parse_value(t)?;
611 expect_keyword(t, "AND")?;
612 let high = parse_value(t)?;
613 Ok(WhereCondition::Between(path, low, high))
614 }
615 "IN" => {
616 t.next_token()?; let open = t.next_token()?.ok_or("Expected '(' or '[' after IN")?;
621 let close_char = match open.as_str() {
622 "(" => ")",
623 "[" => "]",
624 other => {
625 return Err(format!("Expected '(' or '[' after IN, got '{other}'"));
626 }
627 };
628 let mut values = Vec::new();
629 loop {
630 let peek = t.peek_token()?.ok_or("Unexpected end of IN list")?;
631 if peek == close_char {
632 t.next_token()?; break;
634 }
635 if peek == "," {
636 t.next_token()?; continue;
638 }
639 values.push(parse_value(t)?);
640 }
641 Ok(WhereCondition::In(path, values))
642 }
643 "IS" => {
644 t.next_token()?; let kw = t.next_token()?.ok_or("Expected MISSING or NOT after IS")?;
646 let kw_upper = kw.to_uppercase();
647 match kw_upper.as_str() {
648 "MISSING" => Ok(WhereCondition::IsMissing(path)),
649 "NOT" => {
650 expect_keyword(t, "MISSING")?;
651 Ok(WhereCondition::IsNotMissing(path))
652 }
653 other => Err(format!(
654 "Expected MISSING or NOT MISSING after IS, got '{other}'"
655 )),
656 }
657 }
658 _ => {
659 let op_tok = t.next_token()?.ok_or("Expected comparison operator")?;
661 let op = match op_tok.as_str() {
662 "=" => CompOp::Eq,
663 "<>" | "!=" => CompOp::Ne,
664 "<" => CompOp::Lt,
665 "<=" => CompOp::Le,
666 ">" => CompOp::Gt,
667 ">=" => CompOp::Ge,
668 other => return Err(format!("Unknown operator: {other}")),
669 };
670 let value = parse_value(t)?;
671 Ok(WhereCondition::Comparison(Condition { path, op, value }))
672 }
673 }
674 }
675 }
676}
677
678fn parse_dotted_path_from_token(first_tok: &str, t: &mut Tokenizer) -> Result<String, String> {
681 let mut path = unquote(first_tok);
682 while let Some(ref next) = t.peek_token()? {
683 if next == "." {
684 t.next_token()?; let seg = t.next_token()?.ok_or("Expected attribute name after '.'")?;
686 path.push('.');
687 path.push_str(&unquote(&seg));
688 } else if next == "[" {
689 t.next_token()?; let idx = t.next_token()?.ok_or("Expected index in '[]'")?;
691 let close = t.next_token()?.ok_or("Expected ']'")?;
692 if close != "]" {
693 return Err(format!("Expected ']' but got '{close}'"));
694 }
695 path.push('[');
696 path.push_str(&idx);
697 path.push(']');
698 } else {
699 break;
700 }
701 }
702 Ok(path)
703}
704
705fn parse_function_path(t: &mut Tokenizer) -> Result<String, String> {
708 let tok = t.next_token()?.ok_or("Expected path in function")?;
709 parse_dotted_path_from_token(&tok, t)
710}
711
712fn expect_char(t: &mut Tokenizer, expected: &str) -> Result<(), String> {
713 let tok = t.next_token()?.ok_or(format!("Expected '{expected}'"))?;
714 if tok != expected {
715 return Err(format!("Expected '{expected}' but got '{tok}'"));
716 }
717 Ok(())
718}
719
720fn parse_value(t: &mut Tokenizer) -> Result<PartiqlValue, String> {
721 let tok = t.next_token()?.ok_or("Expected value")?;
722
723 if tok == "?" {
724 let idx = t.next_param_index();
725 return Ok(PartiqlValue::Parameter(idx));
726 }
727
728 if tok.starts_with('\'') && tok.ends_with('\'') && tok.len() >= 2 {
730 let s = tok[1..tok.len() - 1].to_string();
731 return Ok(PartiqlValue::Literal(AttributeValue::S(s)));
732 }
733
734 if tok == "<" {
736 if let Some(ref next) = t.peek_token()? {
737 if next == "<" {
738 t.next_token()?; let mut elements = Vec::new();
740 loop {
741 let peek = t.peek_token()?.ok_or("Unexpected end of set literal")?;
742 if peek == ">" {
743 t.next_token()?; let next_close = t.peek_token()?;
746 if next_close.as_deref() == Some(">") {
747 t.next_token()?;
748 }
749 break;
750 }
751 if peek == "," {
752 t.next_token()?;
753 continue;
754 }
755 elements.push(parse_value(t)?);
756 }
757 return set_literal_to_value(elements);
758 }
759 }
760 }
761
762 if tok == "[" {
764 let mut items = Vec::new();
765 loop {
766 let peek = t.peek_token()?.ok_or("Unexpected end of list")?;
767 if peek == "]" {
768 t.next_token()?;
769 break;
770 }
771 if peek == "," {
772 t.next_token()?;
773 continue;
774 }
775 items.push(parse_value(t)?);
776 }
777 let mut avs = Vec::new();
779 for item in items {
780 match item {
781 PartiqlValue::Literal(av) => avs.push(av),
782 PartiqlValue::Parameter(_) => {
783 return Err(
787 "Parameter placeholders inside list literals are not yet supported"
788 .to_string(),
789 );
790 }
791 }
792 }
793 return Ok(PartiqlValue::Literal(AttributeValue::L(avs)));
794 }
795
796 if tok == "{" {
798 let mut map = HashMap::new();
799 loop {
800 let peek = t.peek_token()?.ok_or("Unexpected end of map literal")?;
801 if peek == "}" {
802 t.next_token()?;
803 break;
804 }
805 if peek == "," {
806 t.next_token()?;
807 continue;
808 }
809 let key_tok = t.next_token()?.ok_or("Expected key in map literal")?;
810 let key = unquote(&key_tok);
811 let colon = t.next_token()?.ok_or("Expected ':'")?;
812 if colon != ":" {
813 return Err(format!("Expected ':' but got '{colon}'"));
814 }
815 let val = parse_value(t)?;
816 match val {
817 PartiqlValue::Literal(av) => {
818 map.insert(key, av);
819 }
820 PartiqlValue::Parameter(_) => {
821 return Err(
822 "Parameter placeholders inside map literals are not yet supported"
823 .to_string(),
824 );
825 }
826 }
827 }
828 return Ok(PartiqlValue::Literal(AttributeValue::M(map)));
829 }
830
831 if tok == "-" || tok == "+" {
833 if let Some(ref next) = t.peek_token()? {
834 if next.starts_with(|c: char| c.is_ascii_digit()) {
835 let num = t.next_token()?.unwrap();
836 return Ok(PartiqlValue::Literal(AttributeValue::N(format!(
837 "{tok}{num}"
838 ))));
839 }
840 }
841 }
842
843 if tok.starts_with(|c: char| c.is_ascii_digit()) {
845 return Ok(PartiqlValue::Literal(AttributeValue::N(tok)));
846 }
847
848 match tok.to_uppercase().as_str() {
850 "TRUE" => return Ok(PartiqlValue::Literal(AttributeValue::BOOL(true))),
851 "FALSE" => return Ok(PartiqlValue::Literal(AttributeValue::BOOL(false))),
852 "NULL" => return Ok(PartiqlValue::Literal(AttributeValue::NULL(true))),
853 _ => {}
854 }
855
856 if tok
859 .chars()
860 .next()
861 .is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
862 {
863 let mut path = tok.clone();
865 while let Some(ref next) = t.peek_token()? {
866 if next == "." {
867 t.next_token()?;
868 let seg = t.next_token()?.ok_or("Expected attribute name after '.'")?;
869 path.push('.');
870 path.push_str(&unquote(&seg));
871 } else {
872 break;
873 }
874 }
875 return Ok(PartiqlValue::Literal(AttributeValue::S(path)));
876 }
877
878 Err(format!("Unexpected value token: {tok}"))
879}
880
881fn set_literal_to_value(elements: Vec<PartiqlValue>) -> Result<PartiqlValue, String> {
883 if elements.is_empty() {
884 return Err("Set literals cannot be empty".to_string());
885 }
886
887 let first = match &elements[0] {
889 PartiqlValue::Literal(av) => av,
890 PartiqlValue::Parameter(_) => {
891 return Err("Parameter placeholders in set literals are not supported".to_string());
892 }
893 };
894
895 match first {
896 AttributeValue::S(_) => {
897 let mut ss = Vec::new();
898 for elem in &elements {
899 match elem {
900 PartiqlValue::Literal(AttributeValue::S(s)) => ss.push(s.clone()),
901 _ => return Err("Mixed types in string set literal".to_string()),
902 }
903 }
904 Ok(PartiqlValue::Literal(AttributeValue::SS(ss)))
905 }
906 AttributeValue::N(_) => {
907 let mut ns = Vec::new();
908 for elem in &elements {
909 match elem {
910 PartiqlValue::Literal(AttributeValue::N(n)) => ns.push(n.clone()),
911 _ => return Err("Mixed types in number set literal".to_string()),
912 }
913 }
914 Ok(PartiqlValue::Literal(AttributeValue::NS(ns)))
915 }
916 _ => Err(format!(
917 "Unsupported element type in set literal: {first:?}"
918 )),
919 }
920}
921
922fn parse_item_literal(t: &mut Tokenizer) -> Result<HashMap<String, AttributeValue>, String> {
924 let open = t.next_token()?.ok_or("Expected '{'")?;
925 if open != "{" {
926 return Err(format!("Expected '{{' but got '{open}'"));
927 }
928
929 let mut item = HashMap::new();
930
931 loop {
932 let tok = t.peek_token()?.ok_or("Unexpected end of item literal")?;
933 if tok == "}" {
934 t.next_token()?; break;
936 }
937
938 if tok == "," {
940 t.next_token()?;
941 continue;
942 }
943
944 let key_tok = t.next_token()?.ok_or("Expected key in item literal")?;
946 let key = unquote(&key_tok);
947
948 let colon = t.next_token()?.ok_or("Expected ':'")?;
949 if colon != ":" {
950 return Err(format!("Expected ':' but got '{colon}'"));
951 }
952
953 let val = parse_item_value(t)?;
955 item.insert(key, val);
956 }
957
958 Ok(item)
959}
960
961fn parse_item_value(t: &mut Tokenizer) -> Result<AttributeValue, String> {
963 let tok = t.peek_token()?.ok_or("Expected value")?;
964
965 if tok == "{" {
966 let inner = parse_item_literal(t)?;
968 return Ok(AttributeValue::M(inner));
969 }
970
971 if tok == "[" {
972 t.next_token()?; let mut items = Vec::new();
975 loop {
976 let peek = t.peek_token()?.ok_or("Unexpected end of list")?;
977 if peek == "]" {
978 t.next_token()?;
979 break;
980 }
981 if peek == "," {
982 t.next_token()?;
983 continue;
984 }
985 items.push(parse_item_value(t)?);
986 }
987 return Ok(AttributeValue::L(items));
988 }
989
990 if tok == "<" {
992 if let Some(ref next_tok) = t.peek_token_at(1)? {
993 if next_tok == "<" {
994 t.next_token()?; t.next_token()?; let mut elements = Vec::new();
997 loop {
998 let peek = t.peek_token()?.ok_or("Unexpected end of set literal")?;
999 if peek == ">" {
1000 t.next_token()?; if t.peek_token()?.as_deref() == Some(">") {
1002 t.next_token()?; }
1004 break;
1005 }
1006 if peek == "," {
1007 t.next_token()?;
1008 continue;
1009 }
1010 elements.push(parse_item_value(t)?);
1011 }
1012 return item_value_set_literal(elements);
1013 }
1014 }
1015 }
1016
1017 let tok = t.next_token()?.ok_or("Expected value")?;
1019
1020 if tok.starts_with('\'') && tok.ends_with('\'') && tok.len() >= 2 {
1022 return Ok(AttributeValue::S(tok[1..tok.len() - 1].to_string()));
1023 }
1024
1025 if tok == "-" || tok == "+" {
1027 if let Some(ref next) = t.peek_token()? {
1028 if next.starts_with(|c: char| c.is_ascii_digit()) {
1029 let num = t.next_token()?.unwrap();
1030 return Ok(AttributeValue::N(format!("{tok}{num}")));
1031 }
1032 }
1033 }
1034
1035 if tok.starts_with(|c: char| c.is_ascii_digit()) {
1037 return Ok(AttributeValue::N(tok));
1038 }
1039
1040 match tok.to_uppercase().as_str() {
1041 "TRUE" => Ok(AttributeValue::BOOL(true)),
1042 "FALSE" => Ok(AttributeValue::BOOL(false)),
1043 "NULL" => Ok(AttributeValue::NULL(true)),
1044 _ => Err(format!("Unexpected value in item literal: {tok}")),
1045 }
1046}
1047
1048fn item_value_set_literal(elements: Vec<AttributeValue>) -> Result<AttributeValue, String> {
1050 if elements.is_empty() {
1051 return Err("Set literals cannot be empty".to_string());
1052 }
1053 match &elements[0] {
1054 AttributeValue::S(_) => {
1055 let mut ss = Vec::new();
1056 for e in elements {
1057 match e {
1058 AttributeValue::S(s) => ss.push(s),
1059 _ => return Err("Mixed types in string set literal".to_string()),
1060 }
1061 }
1062 Ok(AttributeValue::SS(ss))
1063 }
1064 AttributeValue::N(_) => {
1065 let mut ns = Vec::new();
1066 for e in elements {
1067 match e {
1068 AttributeValue::N(n) => ns.push(n),
1069 _ => return Err("Mixed types in number set literal".to_string()),
1070 }
1071 }
1072 Ok(AttributeValue::NS(ns))
1073 }
1074 _ => Err(format!(
1075 "Unsupported element type in set literal: {:?}",
1076 elements[0]
1077 )),
1078 }
1079}
1080
1081fn parse_item_literal_partiql(t: &mut Tokenizer) -> Result<HashMap<String, PartiqlValue>, String> {
1084 let open = t.next_token()?.ok_or("Expected '{'")?;
1085 if open != "{" {
1086 return Err(format!("Expected '{{' but got '{open}'"));
1087 }
1088
1089 let mut item = HashMap::new();
1090
1091 loop {
1092 let tok = t.peek_token()?.ok_or("Unexpected end of item literal")?;
1093 if tok == "}" {
1094 t.next_token()?; break;
1096 }
1097
1098 if tok == "," {
1100 t.next_token()?;
1101 continue;
1102 }
1103
1104 let key_tok = t.next_token()?.ok_or("Expected key in item literal")?;
1106 let key = unquote(&key_tok);
1107
1108 let colon = t.next_token()?.ok_or("Expected ':'")?;
1109 if colon != ":" {
1110 return Err(format!("Expected ':' but got '{colon}'"));
1111 }
1112
1113 let val = parse_item_value_partiql(t)?;
1115 item.insert(key, val);
1116 }
1117
1118 Ok(item)
1119}
1120
1121fn parse_item_value_partiql(t: &mut Tokenizer) -> Result<PartiqlValue, String> {
1124 let tok = t.peek_token()?.ok_or("Expected value")?;
1125
1126 if tok == "?" {
1127 t.next_token()?; let idx = t.next_param_index();
1129 return Ok(PartiqlValue::Parameter(idx));
1130 }
1131
1132 if tok == "[" {
1134 return parse_value(t);
1135 }
1136
1137 if tok == "{" {
1139 let inner = parse_item_literal_partiql(t)?;
1141 let mut map = HashMap::new();
1143 for (k, v) in inner {
1144 match v {
1145 PartiqlValue::Literal(av) => {
1146 map.insert(k, av);
1147 }
1148 PartiqlValue::Parameter(_) => {
1149 return Err(
1152 "Parameter placeholders inside nested map literals are not yet fully supported"
1153 .to_string(),
1154 );
1155 }
1156 }
1157 }
1158 return Ok(PartiqlValue::Literal(AttributeValue::M(map)));
1159 }
1160
1161 let av = parse_item_value(t)?;
1164 Ok(PartiqlValue::Literal(av))
1165}
1166
1167fn unquote(s: &str) -> String {
1169 if (s.starts_with('"') && s.ends_with('"')) || (s.starts_with('\'') && s.ends_with('\'')) {
1170 s[1..s.len() - 1].to_string()
1171 } else {
1172 s.to_string()
1173 }
1174}
1175
1176fn expect_keyword(t: &mut Tokenizer, kw: &str) -> Result<(), String> {
1177 let tok = t.next_token()?.ok_or(format!("Expected '{kw}'"))?;
1178 if !tok.eq_ignore_ascii_case(kw) {
1179 return Err(format!("Expected '{kw}' but got '{tok}'"));
1180 }
1181 Ok(())
1182}
1183
1184struct Tokenizer {
1189 tokens: Vec<String>,
1190 pos: usize,
1191 param_counter: usize,
1192}
1193
1194impl Tokenizer {
1195 fn new(input: &str) -> Result<Self, String> {
1196 let tokens = tokenize(input)?;
1197 Ok(Self {
1198 tokens,
1199 pos: 0,
1200 param_counter: 0,
1201 })
1202 }
1203
1204 fn next_token(&mut self) -> Result<Option<String>, String> {
1205 if self.pos >= self.tokens.len() {
1206 return Ok(None);
1207 }
1208 let tok = self.tokens[self.pos].clone();
1209 self.pos += 1;
1210 Ok(Some(tok))
1211 }
1212
1213 fn peek_token(&self) -> Result<Option<String>, String> {
1214 if self.pos >= self.tokens.len() {
1215 return Ok(None);
1216 }
1217 Ok(Some(self.tokens[self.pos].clone()))
1218 }
1219
1220 fn peek_token_at(&self, offset: usize) -> Result<Option<String>, String> {
1222 let idx = self.pos + offset;
1223 if idx >= self.tokens.len() {
1224 return Ok(None);
1225 }
1226 Ok(Some(self.tokens[idx].clone()))
1227 }
1228
1229 fn next_param_index(&mut self) -> usize {
1230 let idx = self.param_counter;
1231 self.param_counter += 1;
1232 idx
1233 }
1234}
1235
1236fn tokenize(input: &str) -> Result<Vec<String>, String> {
1238 let mut tokens = Vec::new();
1239 let chars: Vec<char> = input.chars().collect();
1240 let len = chars.len();
1241 let mut i = 0;
1242
1243 while i < len {
1244 if chars[i].is_ascii_whitespace() {
1246 i += 1;
1247 continue;
1248 }
1249
1250 match chars[i] {
1252 '{' | '}' | '[' | ']' | '(' | ')' | ',' | ':' | '*' | '?' | '+' | '-' | '.' => {
1253 tokens.push(chars[i].to_string());
1255 i += 1;
1256 continue;
1257 }
1258 _ => {}
1259 }
1260
1261 if i + 1 < len {
1263 let two = format!("{}{}", chars[i], chars[i + 1]);
1264 match two.as_str() {
1265 "<>" | "<=" | ">=" | "!=" => {
1266 tokens.push(two);
1267 i += 2;
1268 continue;
1269 }
1270 _ => {}
1271 }
1272 }
1273
1274 if matches!(chars[i], '=' | '<' | '>') {
1276 tokens.push(chars[i].to_string());
1277 i += 1;
1278 continue;
1279 }
1280
1281 if chars[i] == '\'' {
1283 let mut s = String::from('\'');
1284 i += 1;
1285 while i < len {
1286 if chars[i] == '\'' {
1287 if i + 1 < len && chars[i + 1] == '\'' {
1289 s.push('\'');
1290 i += 2;
1291 } else {
1292 break; }
1294 } else {
1295 s.push(chars[i]);
1296 i += 1;
1297 }
1298 }
1299 if i < len {
1300 s.push('\'');
1301 i += 1;
1302 }
1303 tokens.push(s);
1304 continue;
1305 }
1306
1307 if chars[i] == '"' {
1309 let mut s = String::from('"');
1310 i += 1;
1311 while i < len {
1312 if chars[i] == '"' {
1313 if i + 1 < len && chars[i + 1] == '"' {
1315 s.push('"');
1316 i += 2;
1317 } else {
1318 break; }
1320 } else {
1321 s.push(chars[i]);
1322 i += 1;
1323 }
1324 }
1325 if i < len {
1326 s.push('"');
1327 i += 1;
1328 }
1329 tokens.push(s);
1330 continue;
1331 }
1332
1333 if chars[i].is_ascii_digit() {
1335 let mut s = String::new();
1336 while i < len && (chars[i].is_ascii_digit() || chars[i] == '.') {
1337 s.push(chars[i]);
1338 i += 1;
1339 }
1340 tokens.push(s);
1341 continue;
1342 }
1343
1344 if chars[i].is_ascii_alphabetic() || chars[i] == '_' {
1346 let mut s = String::new();
1347 while i < len && (chars[i].is_ascii_alphanumeric() || chars[i] == '_') {
1348 s.push(chars[i]);
1349 i += 1;
1350 }
1351 tokens.push(s);
1352 continue;
1353 }
1354
1355 return Err(format!("Unexpected character: '{}'", chars[i]));
1357 }
1358
1359 Ok(tokens)
1360}
1361
1362#[cfg(test)]
1363mod tests {
1364 use super::*;
1365
1366 #[test]
1367 fn test_parse_select_star() {
1368 let stmt = parse("SELECT * FROM \"TestTable\"").unwrap();
1369 match stmt {
1370 Statement::Select {
1371 table_name,
1372 projections,
1373 where_clause,
1374 } => {
1375 assert_eq!(table_name, "TestTable");
1376 assert!(projections.is_empty());
1377 assert!(where_clause.is_none());
1378 }
1379 _ => panic!("Expected SELECT"),
1380 }
1381 }
1382
1383 #[test]
1384 fn test_parse_select_with_where() {
1385 let stmt = parse("SELECT * FROM \"T\" WHERE pk = 'hello'").unwrap();
1386 match stmt {
1387 Statement::Select {
1388 where_clause: Some(wc),
1389 ..
1390 } => {
1391 assert_eq!(wc.groups[0].len(), 1);
1392 match &wc.groups[0][0] {
1393 WhereCondition::Comparison(c) => {
1394 assert_eq!(c.path, "pk");
1395 assert_eq!(c.op, CompOp::Eq);
1396 }
1397 _ => panic!("Expected Comparison"),
1398 }
1399 }
1400 _ => panic!("Expected SELECT with WHERE"),
1401 }
1402 }
1403
1404 #[test]
1405 fn test_parse_select_with_projection() {
1406 let stmt = parse("SELECT name, age FROM \"Users\"").unwrap();
1407 match stmt {
1408 Statement::Select { projections, .. } => {
1409 assert_eq!(projections, vec!["name", "age"]);
1410 }
1411 _ => panic!("Expected SELECT"),
1412 }
1413 }
1414
1415 #[test]
1416 fn test_parse_insert() {
1417 let stmt =
1418 parse("INSERT INTO \"TestTable\" VALUE {'pk': 'key1', 'data': 'hello'}").unwrap();
1419 match stmt {
1420 Statement::Insert {
1421 table_name, item, ..
1422 } => {
1423 assert_eq!(table_name, "TestTable");
1424 assert_eq!(
1425 item.get("pk"),
1426 Some(&PartiqlValue::Literal(AttributeValue::S(
1427 "key1".to_string()
1428 )))
1429 );
1430 assert_eq!(
1431 item.get("data"),
1432 Some(&PartiqlValue::Literal(AttributeValue::S(
1433 "hello".to_string()
1434 )))
1435 );
1436 }
1437 _ => panic!("Expected INSERT"),
1438 }
1439 }
1440
1441 #[test]
1442 fn test_parse_update() {
1443 let stmt = parse("UPDATE \"T\" SET name = 'Bob' WHERE pk = 'k1'").unwrap();
1444 match stmt {
1445 Statement::Update {
1446 table_name,
1447 set_clauses,
1448 where_clause,
1449 ..
1450 } => {
1451 assert_eq!(table_name, "T");
1452 assert_eq!(set_clauses.len(), 1);
1453 assert_eq!(set_clauses[0].path, "name");
1454 assert!(where_clause.is_some());
1455 }
1456 _ => panic!("Expected UPDATE"),
1457 }
1458 }
1459
1460 #[test]
1461 fn test_parse_delete() {
1462 let stmt = parse("DELETE FROM \"T\" WHERE pk = 'k1'").unwrap();
1463 match stmt {
1464 Statement::Delete {
1465 table_name,
1466 where_clause,
1467 returning,
1468 } => {
1469 assert_eq!(table_name, "T");
1470 assert!(where_clause.is_some());
1471 assert!(returning.is_none());
1472 }
1473 _ => panic!("Expected DELETE"),
1474 }
1475 }
1476
1477 #[test]
1478 fn test_parse_delete_returning_all_old() {
1479 let stmt = parse("DELETE FROM \"T\" WHERE pk = 'k1' RETURNING ALL OLD *").unwrap();
1480 match stmt {
1481 Statement::Delete {
1482 table_name,
1483 where_clause,
1484 returning,
1485 } => {
1486 assert_eq!(table_name, "T");
1487 assert!(where_clause.is_some());
1488 assert_eq!(returning, Some(ReturningVariant::AllOld));
1489 }
1490 _ => panic!("Expected DELETE"),
1491 }
1492 }
1493
1494 #[test]
1495 fn test_parse_delete_returning_is_case_insensitive() {
1496 let stmt = parse("DELETE FROM \"T\" WHERE pk = 'k1' returning all old *").unwrap();
1497 match stmt {
1498 Statement::Delete { returning, .. } => {
1499 assert_eq!(returning, Some(ReturningVariant::AllOld))
1500 }
1501 _ => panic!("Expected DELETE"),
1502 }
1503 }
1504
1505 #[test]
1506 fn test_parse_delete_returning_recognises_all_variants() {
1507 let cases = [
1511 ("RETURNING ALL OLD *", ReturningVariant::AllOld),
1512 ("RETURNING MODIFIED OLD *", ReturningVariant::ModifiedOld),
1513 ("RETURNING ALL NEW *", ReturningVariant::AllNew),
1514 ("RETURNING MODIFIED NEW *", ReturningVariant::ModifiedNew),
1515 ];
1516 for (clause, expected) in cases {
1517 let stmt = parse(&format!("DELETE FROM \"T\" WHERE pk = 'k1' {clause}")).unwrap();
1518 match stmt {
1519 Statement::Delete { returning, .. } => {
1520 assert_eq!(returning, Some(expected), "clause {clause}")
1521 }
1522 _ => panic!("Expected DELETE for {clause}"),
1523 }
1524 }
1525 }
1526
1527 #[test]
1528 fn test_parse_update_returning_recognises_all_variants() {
1529 let cases = [
1530 ("RETURNING ALL OLD *", ReturningVariant::AllOld),
1531 ("RETURNING MODIFIED OLD *", ReturningVariant::ModifiedOld),
1532 ("RETURNING ALL NEW *", ReturningVariant::AllNew),
1533 ("RETURNING MODIFIED NEW *", ReturningVariant::ModifiedNew),
1534 ];
1535 for (clause, expected) in cases {
1536 let stmt = parse(&format!(
1537 "UPDATE \"T\" SET data = 'x' WHERE pk = 'k1' {clause}"
1538 ))
1539 .unwrap();
1540 match stmt {
1541 Statement::Update { returning, .. } => {
1542 assert_eq!(returning, Some(expected), "clause {clause}")
1543 }
1544 _ => panic!("Expected UPDATE for {clause}"),
1545 }
1546 }
1547 }
1548
1549 #[test]
1550 fn test_parse_delete_returning_rejects_incomplete_clause() {
1551 assert!(parse("DELETE FROM \"T\" WHERE pk = 'k1' RETURNING ALL OLD").is_err());
1552 assert!(parse("DELETE FROM \"T\" WHERE pk = 'k1' RETURNING").is_err());
1553 assert!(parse("DELETE FROM \"T\" WHERE pk = 'k1' RETURNING ALL OLD attr").is_err());
1554 }
1555
1556 #[test]
1557 fn test_parse_returning_rejects_invalid_keyword_pairs() {
1558 for clause in [
1561 "RETURNING OLD ALL *",
1562 "RETURNING NEW MODIFIED *",
1563 "RETURNING ALL ALL *",
1564 "RETURNING FOO BAR *",
1565 ] {
1566 let err = parse(&format!("DELETE FROM \"T\" WHERE pk = 'k1' {clause}")).unwrap_err();
1567 assert!(
1568 err.contains("Unsupported RETURNING clause"),
1569 "clause {clause} gave unexpected error: {err}"
1570 );
1571 }
1572 }
1573
1574 #[test]
1575 fn test_parse_parameter() {
1576 let stmt = parse("SELECT * FROM \"T\" WHERE pk = ?").unwrap();
1577 match stmt {
1578 Statement::Select {
1579 where_clause: Some(wc),
1580 ..
1581 } => match &wc.groups[0][0] {
1582 WhereCondition::Comparison(c) => match &c.value {
1583 PartiqlValue::Parameter(0) => {}
1584 other => panic!("Expected Parameter(0), got {other:?}"),
1585 },
1586 _ => panic!("Expected Comparison"),
1587 },
1588 _ => panic!("Expected SELECT with WHERE"),
1589 }
1590 }
1591
1592 #[test]
1593 fn test_parse_numeric_literal() {
1594 let stmt = parse("SELECT * FROM \"T\" WHERE age > 42").unwrap();
1595 match stmt {
1596 Statement::Select {
1597 where_clause: Some(wc),
1598 ..
1599 } => match &wc.groups[0][0] {
1600 WhereCondition::Comparison(c) => {
1601 assert_eq!(c.op, CompOp::Gt);
1602 match &c.value {
1603 PartiqlValue::Literal(AttributeValue::N(n)) => assert_eq!(n, "42"),
1604 other => panic!("Expected N(42), got {other:?}"),
1605 }
1606 }
1607 _ => panic!("Expected Comparison"),
1608 },
1609 _ => panic!("Expected SELECT"),
1610 }
1611 }
1612
1613 #[test]
1614 fn test_parse_insert_with_number() {
1615 let stmt = parse("INSERT INTO \"T\" VALUE {'pk': 'k1', 'age': 25}").unwrap();
1616 match stmt {
1617 Statement::Insert { item, .. } => {
1618 assert_eq!(
1619 item.get("age"),
1620 Some(&PartiqlValue::Literal(AttributeValue::N("25".to_string())))
1621 );
1622 }
1623 _ => panic!("Expected INSERT"),
1624 }
1625 }
1626
1627 #[test]
1628 fn test_invalid_statement() {
1629 let result = parse("MERGE INTO \"T\"");
1630 assert!(result.is_err());
1631 }
1632
1633 #[test]
1634 fn test_empty_statement() {
1635 let result = parse("");
1636 assert!(result.is_err());
1637 }
1638
1639 #[test]
1640 fn test_parse_between() {
1641 let stmt = parse("SELECT * FROM \"T\" WHERE age BETWEEN 18 AND 65").unwrap();
1642 match stmt {
1643 Statement::Select {
1644 where_clause: Some(wc),
1645 ..
1646 } => {
1647 assert_eq!(wc.groups[0].len(), 1);
1648 match &wc.groups[0][0] {
1649 WhereCondition::Between(path, low, high) => {
1650 assert_eq!(path, "age");
1651 match low {
1652 PartiqlValue::Literal(AttributeValue::N(n)) => assert_eq!(n, "18"),
1653 other => panic!("Expected N(18), got {other:?}"),
1654 }
1655 match high {
1656 PartiqlValue::Literal(AttributeValue::N(n)) => assert_eq!(n, "65"),
1657 other => panic!("Expected N(65), got {other:?}"),
1658 }
1659 }
1660 other => panic!("Expected Between, got {other:?}"),
1661 }
1662 }
1663 _ => panic!("Expected SELECT with WHERE"),
1664 }
1665 }
1666
1667 #[test]
1668 fn test_parse_between_and_other_condition() {
1669 let stmt = parse("SELECT * FROM \"T\" WHERE x BETWEEN 1 AND 10 AND y = 'hello'").unwrap();
1670 match stmt {
1671 Statement::Select {
1672 where_clause: Some(wc),
1673 ..
1674 } => {
1675 assert_eq!(wc.groups[0].len(), 2);
1676 assert!(matches!(&wc.groups[0][0], WhereCondition::Between(..)));
1677 assert!(matches!(&wc.groups[0][1], WhereCondition::Comparison(..)));
1678 }
1679 _ => panic!("Expected SELECT with WHERE"),
1680 }
1681 }
1682
1683 #[test]
1684 fn test_parse_in() {
1685 let stmt = parse("SELECT * FROM \"T\" WHERE status IN ('ACTIVE', 'PENDING')").unwrap();
1686 match stmt {
1687 Statement::Select {
1688 where_clause: Some(wc),
1689 ..
1690 } => {
1691 assert_eq!(wc.groups[0].len(), 1);
1692 match &wc.groups[0][0] {
1693 WhereCondition::In(path, values) => {
1694 assert_eq!(path, "status");
1695 assert_eq!(values.len(), 2);
1696 }
1697 other => panic!("Expected In, got {other:?}"),
1698 }
1699 }
1700 _ => panic!("Expected SELECT with WHERE"),
1701 }
1702 }
1703
1704 #[test]
1705 fn test_parse_contains() {
1706 let stmt = parse("SELECT * FROM \"T\" WHERE CONTAINS(name, 'john')").unwrap();
1707 match stmt {
1708 Statement::Select {
1709 where_clause: Some(wc),
1710 ..
1711 } => {
1712 assert_eq!(wc.groups[0].len(), 1);
1713 match &wc.groups[0][0] {
1714 WhereCondition::Contains(path, val) => {
1715 assert_eq!(path, "name");
1716 match val {
1717 PartiqlValue::Literal(AttributeValue::S(s)) => {
1718 assert_eq!(s, "john")
1719 }
1720 other => panic!("Expected S(john), got {other:?}"),
1721 }
1722 }
1723 other => panic!("Expected Contains, got {other:?}"),
1724 }
1725 }
1726 _ => panic!("Expected SELECT with WHERE"),
1727 }
1728 }
1729
1730 #[test]
1731 fn test_parse_is_missing() {
1732 let stmt = parse("SELECT * FROM \"T\" WHERE email IS MISSING").unwrap();
1733 match stmt {
1734 Statement::Select {
1735 where_clause: Some(wc),
1736 ..
1737 } => {
1738 assert_eq!(wc.groups[0].len(), 1);
1739 match &wc.groups[0][0] {
1740 WhereCondition::IsMissing(path) => assert_eq!(path, "email"),
1741 other => panic!("Expected IsMissing, got {other:?}"),
1742 }
1743 }
1744 _ => panic!("Expected SELECT with WHERE"),
1745 }
1746 }
1747
1748 #[test]
1749 fn test_parse_is_not_missing() {
1750 let stmt = parse("SELECT * FROM \"T\" WHERE email IS NOT MISSING").unwrap();
1751 match stmt {
1752 Statement::Select {
1753 where_clause: Some(wc),
1754 ..
1755 } => {
1756 assert_eq!(wc.groups[0].len(), 1);
1757 match &wc.groups[0][0] {
1758 WhereCondition::IsNotMissing(path) => assert_eq!(path, "email"),
1759 other => panic!("Expected IsNotMissing, got {other:?}"),
1760 }
1761 }
1762 _ => panic!("Expected SELECT with WHERE"),
1763 }
1764 }
1765
1766 #[test]
1767 fn test_parse_nested_projection() {
1768 let stmt = parse("SELECT a.b.c, d FROM \"T\"").unwrap();
1769 match stmt {
1770 Statement::Select { projections, .. } => {
1771 assert_eq!(projections, vec!["a.b.c", "d"]);
1772 }
1773 _ => panic!("Expected SELECT"),
1774 }
1775 }
1776
1777 #[test]
1778 fn test_parse_array_index_projection() {
1779 let stmt = parse("SELECT items[0].name FROM \"T\"").unwrap();
1780 match stmt {
1781 Statement::Select { projections, .. } => {
1782 assert_eq!(projections, vec!["items[0].name"]);
1783 }
1784 _ => panic!("Expected SELECT"),
1785 }
1786 }
1787
1788 #[test]
1789 fn test_parse_update_with_remove() {
1790 let stmt =
1791 parse("UPDATE \"T\" SET name = 'Bob' REMOVE age, email WHERE pk = 'k1'").unwrap();
1792 match stmt {
1793 Statement::Update {
1794 set_clauses,
1795 remove_paths,
1796 where_clause,
1797 ..
1798 } => {
1799 assert_eq!(set_clauses.len(), 1);
1800 assert_eq!(remove_paths, vec!["age", "email"]);
1801 assert!(where_clause.is_some());
1802 }
1803 _ => panic!("Expected UPDATE"),
1804 }
1805 }
1806
1807 #[test]
1808 fn test_parse_update_remove_only() {
1809 let stmt = parse("UPDATE \"T\" REMOVE old_field WHERE pk = 'k1'").unwrap();
1810 match stmt {
1811 Statement::Update {
1812 set_clauses,
1813 remove_paths,
1814 ..
1815 } => {
1816 assert!(set_clauses.is_empty());
1817 assert_eq!(remove_paths, vec!["old_field"]);
1818 }
1819 _ => panic!("Expected UPDATE"),
1820 }
1821 }
1822
1823 #[test]
1824 fn test_parse_set_expression_add() {
1825 let stmt = parse("UPDATE \"T\" SET count = count + 1 WHERE pk = 'k1'").unwrap();
1826 match stmt {
1827 Statement::Update { set_clauses, .. } => {
1828 assert_eq!(set_clauses.len(), 1);
1829 match &set_clauses[0].value {
1830 SetValue::Add(attr, val) => {
1831 assert_eq!(attr, "count");
1832 assert_eq!(
1833 val,
1834 &PartiqlValue::Literal(AttributeValue::N("1".to_string()))
1835 );
1836 }
1837 other => panic!("Expected Add, got {other:?}"),
1838 }
1839 }
1840 _ => panic!("Expected UPDATE"),
1841 }
1842 }
1843
1844 #[test]
1845 fn test_parse_count_star() {
1846 let stmt = parse("SELECT COUNT(*) FROM \"T\"").unwrap();
1847 match stmt {
1848 Statement::Select { projections, .. } => {
1849 assert_eq!(projections, vec!["COUNT(*)"]);
1850 }
1851 _ => panic!("Expected SELECT"),
1852 }
1853 }
1854
1855 #[test]
1856 fn test_parse_set_literal() {
1857 let stmt = parse("INSERT INTO \"T\" VALUE {'pk': 'k1', 'tags': <<'a', 'b'>>}").unwrap();
1858 match stmt {
1859 Statement::Insert { item, .. } => match item.get("tags") {
1860 Some(PartiqlValue::Literal(AttributeValue::SS(ss))) => {
1861 assert!(ss.contains(&"a".to_string()));
1862 assert!(ss.contains(&"b".to_string()));
1863 }
1864 other => panic!("Expected SS, got {other:?}"),
1865 },
1866 _ => panic!("Expected INSERT"),
1867 }
1868 }
1869
1870 #[test]
1871 fn test_parse_or_condition() {
1872 let stmt = parse("SELECT * FROM \"T\" WHERE status = 'A' OR status = 'B'").unwrap();
1873 match stmt {
1874 Statement::Select {
1875 where_clause: Some(wc),
1876 ..
1877 } => {
1878 assert_eq!(wc.groups.len(), 2);
1879 assert_eq!(wc.groups[0].len(), 1);
1880 assert_eq!(wc.groups[1].len(), 1);
1881 }
1882 _ => panic!("Expected SELECT with WHERE"),
1883 }
1884 }
1885
1886 #[test]
1887 fn test_parse_and_or_mixed() {
1888 let stmt = parse("SELECT * FROM \"T\" WHERE a = 1 AND b = 2 OR c = 3").unwrap();
1889 match stmt {
1890 Statement::Select {
1891 where_clause: Some(wc),
1892 ..
1893 } => {
1894 assert_eq!(wc.groups.len(), 2);
1895 assert_eq!(wc.groups[0].len(), 2); assert_eq!(wc.groups[1].len(), 1); }
1898 _ => panic!("Expected SELECT with WHERE"),
1899 }
1900 }
1901
1902 #[test]
1903 fn test_parse_insert_if_not_exists() {
1904 let stmt =
1905 parse("INSERT INTO \"T\" VALUE {'pk': 'k1', 'name': 'A'} IF NOT EXISTS").unwrap();
1906 match stmt {
1907 Statement::Insert { if_not_exists, .. } => {
1908 assert!(if_not_exists);
1909 }
1910 _ => panic!("Expected INSERT"),
1911 }
1912 }
1913
1914 #[test]
1915 fn test_parse_nested_path_in_where_function() {
1916 let stmt = parse("SELECT * FROM \"T\" WHERE BEGINS_WITH(address.city, 'Lon')").unwrap();
1917 match stmt {
1918 Statement::Select {
1919 where_clause: Some(wc),
1920 ..
1921 } => match &wc.groups[0][0] {
1922 WhereCondition::BeginsWith(path, _) => {
1923 assert_eq!(path, "address.city");
1924 }
1925 other => panic!("Expected BeginsWith, got {other:?}"),
1926 },
1927 _ => panic!("Expected SELECT with WHERE"),
1928 }
1929 }
1930}