1use compact_str::CompactString;
8use smallvec::SmallVec;
9use std::collections::{HashMap, HashSet};
10
11use crate::error::Error;
12
13use super::range::Range;
14use super::types::*;
15
16#[derive(Debug, Clone)]
24pub struct QueryParams {
25 pub canonical: String,
27 pub params: Vec<(CompactString, CompactString)>,
29 pub ranges: HashMap<CompactString, Range>,
31 pub order: Vec<(EmbedPath, Vec<OrderTerm>)>,
33 pub logic: Vec<(EmbedPath, LogicTree)>,
35 pub columns: Option<HashSet<FieldName>>,
37 pub select: Vec<SelectItem>,
39 pub filters: Vec<(EmbedPath, Filter)>,
41 pub filters_root: Vec<Filter>,
43 pub filters_not_root: Vec<(EmbedPath, Filter)>,
45 pub filter_fields: HashSet<FieldName>,
47 pub on_conflict: Option<Vec<FieldName>>,
49}
50
51impl Default for QueryParams {
52 fn default() -> Self {
53 Self {
54 canonical: String::new(),
55 params: Vec::new(),
56 ranges: HashMap::new(),
57 order: Vec::new(),
58 logic: Vec::new(),
59 columns: None,
60 select: vec![SelectItem::Field {
61 field: ("*".into(), SmallVec::new()),
62 alias: None,
63 cast: None,
64 aggregate: None,
65 aggregate_cast: None,
66 }],
67 filters: Vec::new(),
68 filters_root: Vec::new(),
69 filters_not_root: Vec::new(),
70 filter_fields: HashSet::new(),
71 on_conflict: None,
72 }
73 }
74}
75
76pub fn parse(is_rpc_read: bool, query_string: &str) -> Result<QueryParams, Error> {
80 let pairs: Vec<(String, String)> = form_urlencoded::parse(query_string.as_bytes())
81 .into_owned()
82 .collect();
83
84 let mut sorted_pairs = pairs.clone();
86 sorted_pairs.sort_by(|a, b| a.0.cmp(&b.0));
87 let canonical = sorted_pairs
88 .iter()
89 .map(|(k, v)| {
90 if v.is_empty() {
91 form_urlencoded::byte_serialize(k.as_bytes()).collect::<String>()
92 } else {
93 format!(
94 "{}={}",
95 form_urlencoded::byte_serialize(k.as_bytes()).collect::<String>(),
96 form_urlencoded::byte_serialize(v.as_bytes()).collect::<String>()
97 )
98 }
99 })
100 .collect::<Vec<_>>()
101 .join("&");
102
103 let reserved = ["select", "columns", "on_conflict"];
105 let reserved_embeddable = ["order", "limit", "offset", "and", "or"];
106
107 let select_str = pairs
108 .iter()
109 .find(|(k, _)| k == "select")
110 .map(|(_, v)| v.as_str())
111 .unwrap_or("*");
112
113 let columns_str = pairs
114 .iter()
115 .find(|(k, _)| k == "columns")
116 .map(|(_, v)| v.as_str());
117
118 let on_conflict_str = pairs
119 .iter()
120 .find(|(k, _)| k == "on_conflict")
121 .map(|(_, v)| v.as_str());
122
123 let select = parse_select(select_str)?;
125
126 let columns = match columns_str {
128 Some(s) => Some(parse_columns(s)?),
129 None => None,
130 };
131
132 let on_conflict = match on_conflict_str {
134 Some(s) => Some(parse_columns_list(s)?),
135 None => None,
136 };
137
138 let mut order_params = Vec::new();
140 let mut logic_params = Vec::new();
141 let mut limit_params = Vec::new();
142 let mut offset_params = Vec::new();
143 let mut filter_params = Vec::new();
144
145 for (k, v) in &pairs {
146 if v.is_empty() && reserved.contains(&k.as_str()) {
147 continue;
148 }
149 if reserved.contains(&k.as_str()) {
150 continue;
151 }
152
153 let last_word = k.rsplit('.').next().unwrap_or(k);
154 if last_word == "order" {
155 order_params.push((k.as_str(), v.as_str()));
156 } else if last_word == "limit" {
157 limit_params.push((k.as_str(), v.as_str()));
158 } else if last_word == "offset" {
159 offset_params.push((k.as_str(), v.as_str()));
160 } else if last_word == "and" || last_word == "or" {
161 logic_params.push((k.as_str(), v.as_str()));
162 } else if !reserved_embeddable.contains(&last_word) {
163 filter_params.push((k.as_str(), v.as_str()));
164 }
165 }
166
167 let mut order = Vec::new();
169 for (k, v) in &order_params {
170 let (path, _) = parse_tree_path(k)?;
171 let terms = parse_order(v)?;
172 order.push((path, terms));
173 }
174
175 let mut logic = Vec::new();
177 for (k, v) in &logic_params {
178 let (mut path, op) = parse_logic_path(k)?;
179 let negated = path.contains(&CompactString::from("not"));
181 path.retain(|s| s.as_str() != "not");
182
183 let op_str = if negated {
184 format!("not.{}{}", op, v)
185 } else {
186 format!("{}{}", op, v)
187 };
188 let tree = parse_logic_tree(&op_str)?;
189 logic.push((path, tree));
190 }
191
192 let mut ranges: HashMap<CompactString, Range> = HashMap::new();
194
195 for (k, v) in &limit_params {
196 let embed_key = replace_last_segment(k, "limit");
197 if let Ok(limit) = v.parse::<i64>() {
198 let range = Range::all().restrict(Some(limit));
199 ranges.insert(CompactString::from(embed_key), range);
200 }
201 }
202
203 for (k, v) in &offset_params {
204 let embed_key = replace_last_segment(k, "limit");
206 if let Ok(offset) = v.parse::<i64>() {
207 let entry = ranges
208 .entry(CompactString::from(embed_key))
209 .or_insert_with(Range::all);
210 if let Some(limit) = entry.limit() {
212 *entry = Range::new(offset, offset + limit - 1);
213 } else {
214 *entry = Range::from_offset(offset);
215 }
216 }
217 }
218
219 let mut all_filters = Vec::new();
221 let mut rpc_params = Vec::new();
222 let mut filter_fields = HashSet::new();
223
224 for (k, v) in &filter_params {
225 filter_fields.insert(CompactString::from(*k));
226
227 if k.contains("::") {
229 return Err(Error::InvalidQueryParam {
230 param: "filter".to_string(),
231 message: "casting not allowed in filters".to_string(),
232 });
233 }
234
235 let (path, field) = parse_tree_path(k)?;
236 let op_expr = parse_filter_value(v, is_rpc_read)?;
237
238 match &op_expr {
239 OpExpr::NoOp(val) => {
240 rpc_params.push((field.0.clone(), val.clone()));
241 }
242 _ => {
243 all_filters.push((path, Filter { field, op_expr }));
244 }
245 }
246 }
247
248 let mut filters_root = Vec::new();
250 let mut filters_not_root = Vec::new();
251 for (path, filter) in &all_filters {
252 if path.is_empty() {
253 filters_root.push(filter.clone());
254 } else {
255 filters_not_root.push((path.clone(), filter.clone()));
256 }
257 }
258
259 Ok(QueryParams {
260 canonical,
261 params: rpc_params,
262 ranges,
263 order,
264 logic,
265 columns,
266 select,
267 filters: all_filters,
268 filters_root,
269 filters_not_root,
270 filter_fields,
271 on_conflict,
272 })
273}
274
275pub fn parse_select(input: &str) -> Result<Vec<SelectItem>, Error> {
284 if input.is_empty() {
285 return Ok(Vec::new());
286 }
287
288 let mut items = Vec::new();
289 let mut chars = input;
290
291 while !chars.is_empty() {
292 let (item, rest) = parse_select_item(chars)?;
293 items.push(item);
294
295 chars = rest.trim_start();
296 if chars.starts_with(',') {
297 chars = &chars[1..];
298 chars = chars.trim_start();
299 } else if !chars.is_empty() && !chars.starts_with(')') {
300 return Err(Error::ParseError {
301 location: "select".to_string(),
302 message: format!("unexpected character '{}' in select", &chars[..1]),
303 });
304 }
305 }
306
307 Ok(items)
308}
309
310fn parse_select_item(input: &str) -> Result<(SelectItem, &str), Error> {
311 let input = input.trim_start();
312
313 if let Some(rest) = input.strip_prefix("...") {
315 return parse_spread_relation(rest);
316 }
317
318 let (alias, after_alias) = try_parse_alias(input);
321
322 let after = after_alias;
325
326 if let Some((item, rest)) = try_parse_relation(after, alias.clone())? {
328 return Ok((item, rest));
329 }
330
331 parse_field_select(after, alias)
333}
334
335fn parse_spread_relation(input: &str) -> Result<(SelectItem, &str), Error> {
336 let (name, rest) = parse_field_name(input)?;
338
339 let (hint, join_type, rest) = parse_embed_params(rest);
341
342 if !rest.starts_with('(') {
344 return Err(Error::ParseError {
345 location: "select".to_string(),
346 message: format!("expected '(' after spread relation '{}'", name),
347 });
348 }
349
350 let (children, rest) = parse_children(&rest[1..])?;
351
352 Ok((
353 SelectItem::Spread {
354 relation: name,
355 hint,
356 join_type,
357 children,
358 },
359 rest,
360 ))
361}
362
363fn try_parse_relation(
364 input: &str,
365 alias: Option<Alias>,
366) -> Result<Option<(SelectItem, &str)>, Error> {
367 let (name, rest) = match parse_field_name(input) {
369 Ok(r) => r,
370 Err(_) => return Ok(None),
371 };
372
373 if name.as_str() == "count" {
375 return Ok(None);
376 }
377
378 let (hint, join_type, rest) = parse_embed_params(rest);
379
380 if !rest.starts_with('(') {
382 return Ok(None);
383 }
384
385 let (children, rest) = parse_children(&rest[1..])?;
386
387 Ok(Some((
388 SelectItem::Relation {
389 relation: name,
390 alias,
391 hint,
392 join_type,
393 children,
394 },
395 rest,
396 )))
397}
398
399fn parse_field_select(input: &str, alias: Option<Alias>) -> Result<(SelectItem, &str), Error> {
400 if let Some(rest) = input.strip_prefix('*') {
402 return Ok((
404 SelectItem::Field {
405 field: ("*".into(), SmallVec::new()),
406 alias: None,
407 cast: None,
408 aggregate: None,
409 aggregate_cast: None,
410 },
411 rest,
412 ));
413 }
414
415 if let Some(after_count) = input.strip_prefix("count()") {
417 let (agg_cast, rest) = parse_optional_cast(after_count);
418 return Ok((
419 SelectItem::Field {
420 field: ("*".into(), SmallVec::new()),
421 alias,
422 cast: None,
423 aggregate: Some(AggregateFunction::Count),
424 aggregate_cast: agg_cast,
425 },
426 rest,
427 ));
428 }
429
430 let (name, rest) = parse_field_name(input)?;
432 let (json_path, rest) = parse_json_path(rest);
433
434 let (cast, rest) = parse_optional_cast(rest);
436
437 let (aggregate, rest) = parse_optional_aggregate(rest);
439 let (aggregate_cast, rest) = if aggregate.is_some() {
440 parse_optional_cast(rest)
441 } else {
442 (None, rest)
443 };
444
445 Ok((
446 SelectItem::Field {
447 field: (name, json_path),
448 alias,
449 cast,
450 aggregate,
451 aggregate_cast,
452 },
453 rest,
454 ))
455}
456
457fn parse_children(input: &str) -> Result<(Vec<SelectItem>, &str), Error> {
458 let items = parse_select_until_close(input)?;
459 Ok(items)
460}
461
462fn parse_select_until_close(input: &str) -> Result<(Vec<SelectItem>, &str), Error> {
463 let mut items = Vec::new();
464 let mut chars = input;
465
466 loop {
467 chars = chars.trim_start();
468 if let Some(after_close) = chars.strip_prefix(')') {
469 return Ok((items, after_close));
470 }
471 if chars.is_empty() {
472 return Err(Error::ParseError {
473 location: "select".to_string(),
474 message: "unclosed parenthesis in select".to_string(),
475 });
476 }
477
478 let (item, rest) = parse_select_item(chars)?;
479 items.push(item);
480 chars = rest.trim_start();
481
482 if let Some(after_comma) = chars.strip_prefix(',') {
483 chars = after_comma;
484 }
485 }
486}
487
488fn parse_field_name(input: &str) -> Result<(FieldName, &str), Error> {
493 if input.starts_with('"') {
495 return parse_quoted_value(input).map(|(v, r)| (CompactString::from(v), r));
496 }
497
498 if let Some(rest) = input.strip_prefix('*') {
500 return Ok(("*".into(), rest));
501 }
502
503 let mut end = 0;
505 let bytes = input.as_bytes();
506 let len = bytes.len();
507
508 while end < len {
509 let ch = bytes[end] as char;
510 if ch.is_alphanumeric() || ch == '_' || ch == '$' || ch == ' ' {
511 end += 1;
512 } else if ch == '-' && end + 1 < len && bytes[end + 1] != b'>' {
513 end += 1;
515 } else {
516 break;
517 }
518 }
519
520 if end == 0 {
521 return Err(Error::ParseError {
522 location: "field name".to_string(),
523 message: format!(
524 "expected field name, got '{}'",
525 &input[..input.len().min(10)]
526 ),
527 });
528 }
529
530 let name = input[..end].trim();
531 Ok((CompactString::from(name), &input[end..]))
532}
533
534fn parse_quoted_value(input: &str) -> Result<(&str, &str), Error> {
535 if !input.starts_with('"') {
536 return Err(Error::ParseError {
537 location: "quoted value".to_string(),
538 message: "expected opening quote".to_string(),
539 });
540 }
541
542 let bytes = input.as_bytes();
543 let mut i = 1;
544 while i < bytes.len() {
545 if bytes[i] == b'\\' && i + 1 < bytes.len() {
546 i += 2; } else if bytes[i] == b'"' {
548 return Ok((&input[1..i], &input[i + 1..]));
549 } else {
550 i += 1;
551 }
552 }
553
554 Err(Error::ParseError {
555 location: "quoted value".to_string(),
556 message: "unclosed quote".to_string(),
557 })
558}
559
560fn parse_json_path(input: &str) -> (JsonPath, &str) {
565 let mut path = SmallVec::new();
566 let mut rest = input;
567
568 loop {
569 if let Some(after) = rest.strip_prefix("->>") {
570 match parse_json_operand(after) {
571 Ok((operand, r)) => {
572 path.push(JsonOperation::Arrow2(operand));
573 rest = r;
574 }
575 Err(_) => break,
576 }
577 } else if let Some(after) = rest.strip_prefix("->") {
578 match parse_json_operand(after) {
579 Ok((operand, r)) => {
580 path.push(JsonOperation::Arrow(operand));
581 rest = r;
582 }
583 Err(_) => break,
584 }
585 } else {
586 break;
587 }
588 }
589
590 (path, rest)
591}
592
593fn parse_json_operand(input: &str) -> Result<(JsonOperand, &str), Error> {
594 let bytes = input.as_bytes();
596 if !bytes.is_empty() {
597 let (has_sign, start) = if bytes[0] == b'-' {
598 (true, 1)
599 } else {
600 (false, 0)
601 };
602
603 if start < bytes.len() && bytes[start].is_ascii_digit() {
604 let mut end = start + 1;
605 while end < bytes.len() && bytes[end].is_ascii_digit() {
606 end += 1;
607 }
608 if end >= bytes.len()
610 || bytes[end] == b'-'
611 || bytes[end] == b':'
612 || bytes[end] == b'.'
613 || bytes[end] == b','
614 || bytes[end] == b')'
615 {
616 let sign = if has_sign { "-" } else { "+" };
617 let idx = format!("{}{}", sign, &input[start..end]);
618 return Ok((JsonOperand::Idx(CompactString::from(idx)), &input[end..]));
619 }
620 }
621 }
622
623 let mut end = 0;
625 while end < bytes.len() {
626 let ch = bytes[end] as char;
627 if ch == '(' || ch == '-' || ch == ':' || ch == '.' || ch == ',' || ch == '>' || ch == ')' {
629 break;
630 }
631 end += 1;
632 }
633
634 if end == 0 {
635 return Err(Error::ParseError {
636 location: "json operand".to_string(),
637 message: "empty json key".to_string(),
638 });
639 }
640
641 Ok((
642 JsonOperand::Key(CompactString::from(&input[..end])),
643 &input[end..],
644 ))
645}
646
647fn try_parse_alias(input: &str) -> (Option<Alias>, &str) {
652 let bytes = input.as_bytes();
655 let mut colon_pos = None;
656
657 for (i, &b) in bytes.iter().enumerate() {
658 if b == b':' {
659 if i + 1 < bytes.len() && bytes[i + 1] == b':' {
660 break;
662 }
663 colon_pos = Some(i);
664 break;
665 }
666 if b == b'(' || b == b')' || b == b',' || b == b'!' || b == b'.' {
668 break;
669 }
670 if b == b'-' && i + 1 < bytes.len() && bytes[i + 1] == b'>' {
671 break;
672 }
673 }
674
675 match colon_pos {
676 Some(pos) if pos > 0 => {
677 let alias = input[..pos].trim();
678 (Some(CompactString::from(alias)), &input[pos + 1..])
679 }
680 _ => (None, input),
681 }
682}
683
684fn parse_embed_params(input: &str) -> (Option<Hint>, Option<JoinType>, &str) {
689 let mut hint = None;
690 let mut join_type = None;
691 let mut rest = input;
692
693 for _ in 0..2 {
695 if let Some(after_bang) = rest.strip_prefix('!') {
696 if let Some(r) = after_bang.strip_prefix("left")
697 && !r.starts_with(|c: char| c.is_alphanumeric() || c == '_')
698 {
699 join_type = join_type.or(Some(JoinType::Left));
700 rest = r;
701 continue;
702 }
703 if let Some(r) = after_bang.strip_prefix("inner")
704 && !r.starts_with(|c: char| c.is_alphanumeric() || c == '_')
705 {
706 join_type = join_type.or(Some(JoinType::Inner));
707 rest = r;
708 continue;
709 }
710 if let Ok((name, r)) = parse_field_name(after_bang) {
712 hint = hint.or(Some(name));
713 rest = r;
714 continue;
715 }
716 }
717 break;
718 }
719
720 (hint, join_type, rest)
721}
722
723fn parse_optional_cast(input: &str) -> (Option<Cast>, &str) {
728 if let Some(rest) = input.strip_prefix("::") {
729 let mut end = 0;
730 let bytes = rest.as_bytes();
731 while end < bytes.len() {
732 let ch = bytes[end] as char;
733 if ch.is_alphanumeric() || ch == '_' || ch == ' ' {
734 end += 1;
735 } else {
736 break;
737 }
738 }
739 if end > 0 {
740 let cast = rest[..end].trim();
741 (Some(CompactString::from(cast)), &rest[end..])
742 } else {
743 (None, input) }
745 } else {
746 (None, input)
747 }
748}
749
750fn parse_optional_aggregate(input: &str) -> (Option<AggregateFunction>, &str) {
755 if !input.starts_with('.') {
756 return (None, input);
757 }
758
759 let rest = &input[1..];
760 let aggregates = [
761 ("count()", AggregateFunction::Count),
762 ("sum()", AggregateFunction::Sum),
763 ("avg()", AggregateFunction::Avg),
764 ("max()", AggregateFunction::Max),
765 ("min()", AggregateFunction::Min),
766 ];
767
768 for (prefix, agg) in &aggregates {
769 if let Some(after) = rest.strip_prefix(prefix) {
770 return (Some(*agg), after);
771 }
772 }
773
774 (None, input) }
776
777fn parse_filter_value(value: &str, is_rpc_read: bool) -> Result<OpExpr, Error> {
783 match try_parse_op_expr(value) {
784 Ok(expr) => Ok(expr),
785 Err(_) if is_rpc_read => Ok(OpExpr::NoOp(CompactString::from(value))),
786 Err(e) => Err(e),
787 }
788}
789
790fn try_parse_op_expr(value: &str) -> Result<OpExpr, Error> {
791 let (negated, rest) = if let Some(after) = value.strip_prefix("not.") {
793 (true, after)
794 } else {
795 (false, value)
796 };
797
798 let operation = parse_operation(rest)?;
799 Ok(OpExpr::Expr { negated, operation })
800}
801
802fn parse_operation(input: &str) -> Result<Operation, Error> {
803 if let Some(rest) = input.strip_prefix("in.") {
807 let list = parse_list_val(rest)?;
808 return Ok(Operation::In(list));
809 }
810
811 if let Some(rest) = input.strip_prefix("is.") {
813 let val = parse_is_val(rest)?;
814 return Ok(Operation::Is(val));
815 }
816
817 if let Some(rest) = input.strip_prefix("isdistinct.") {
819 return Ok(Operation::IsDistinctFrom(CompactString::from(rest)));
820 }
821
822 if let Some(rest) = input.strip_prefix("fts") {
824 let (lang, val) = parse_fts_args(rest)?;
825 return Ok(Operation::Fts(FtsOperator::Fts, lang, val));
826 }
827 if let Some(rest) = input.strip_prefix("plfts") {
828 let (lang, val) = parse_fts_args(rest)?;
829 return Ok(Operation::Fts(FtsOperator::FtsPlain, lang, val));
830 }
831 if let Some(rest) = input.strip_prefix("phfts") {
832 let (lang, val) = parse_fts_args(rest)?;
833 return Ok(Operation::Fts(FtsOperator::FtsPhrase, lang, val));
834 }
835 if let Some(rest) = input.strip_prefix("wfts") {
836 let (lang, val) = parse_fts_args(rest)?;
837 return Ok(Operation::Fts(FtsOperator::FtsWebsearch, lang, val));
838 }
839
840 let simple_ops = [
842 ("neq.", SimpleOperator::NotEqual),
843 ("cs.", SimpleOperator::Contains),
844 ("cd.", SimpleOperator::Contained),
845 ("ov.", SimpleOperator::Overlap),
846 ("sl.", SimpleOperator::StrictlyLeft),
847 ("sr.", SimpleOperator::StrictlyRight),
848 ("nxr.", SimpleOperator::NotExtendsRight),
849 ("nxl.", SimpleOperator::NotExtendsLeft),
850 ("adj.", SimpleOperator::Adjacent),
851 ];
852
853 for (prefix, op) in &simple_ops {
854 if let Some(rest) = input.strip_prefix(prefix) {
855 return Ok(Operation::Simple(*op, CompactString::from(rest)));
856 }
857 }
858
859 let quant_ops = [
861 ("eq", QuantOperator::Equal),
862 ("gte", QuantOperator::GreaterThanEqual),
863 ("gt", QuantOperator::GreaterThan),
864 ("lte", QuantOperator::LessThanEqual),
865 ("lt", QuantOperator::LessThan),
866 ("like", QuantOperator::Like),
867 ("ilike", QuantOperator::ILike),
868 ("match", QuantOperator::Match),
869 ("imatch", QuantOperator::IMatch),
870 ];
871
872 for (prefix, op) in &quant_ops {
873 if let Some(rest) = input.strip_prefix(prefix) {
874 let (quant, rest) = if let Some(after_any) = rest.strip_prefix("(any)") {
876 (Some(OpQuantifier::Any), after_any)
877 } else if let Some(after_all) = rest.strip_prefix("(all)") {
878 (Some(OpQuantifier::All), after_all)
879 } else {
880 (None, rest)
881 };
882
883 if let Some(val) = rest.strip_prefix('.') {
884 return Ok(Operation::Quant(*op, quant, CompactString::from(val)));
885 }
886 }
887 }
888
889 Err(Error::ParseError {
890 location: "filter".to_string(),
891 message: format!("unknown operator in '{}'", input),
892 })
893}
894
895fn parse_is_val(input: &str) -> Result<IsValue, Error> {
896 let lower = input.to_lowercase();
897 match lower.as_str() {
898 "null" => Ok(IsValue::Null),
899 "not_null" => Ok(IsValue::NotNull),
900 "true" => Ok(IsValue::True),
901 "false" => Ok(IsValue::False),
902 "unknown" => Ok(IsValue::Unknown),
903 _ => Err(Error::ParseError {
904 location: "is value".to_string(),
905 message: format!(
906 "expected null, not_null, true, false, or unknown, got '{}'",
907 input
908 ),
909 }),
910 }
911}
912
913fn parse_fts_args(input: &str) -> Result<(Option<Language>, SingleVal), Error> {
914 if input.starts_with('(') {
916 if let Some(close) = input.find(')') {
917 let lang = &input[1..close];
918 let rest = &input[close + 1..];
919 if let Some(val) = rest.strip_prefix('.') {
920 return Ok((Some(CompactString::from(lang)), CompactString::from(val)));
921 }
922 }
923 return Err(Error::ParseError {
924 location: "fts".to_string(),
925 message: "malformed FTS expression".to_string(),
926 });
927 }
928
929 if let Some(val) = input.strip_prefix('.') {
930 Ok((None, CompactString::from(val)))
931 } else {
932 Err(Error::ParseError {
933 location: "fts".to_string(),
934 message: format!("expected '.' after FTS operator, got '{}'", input),
935 })
936 }
937}
938
939fn parse_list_val(input: &str) -> Result<ListVal, Error> {
940 let input = input.trim();
942 if !input.starts_with('(') {
943 return Err(Error::ParseError {
944 location: "list value".to_string(),
945 message: "expected '(' for in list".to_string(),
946 });
947 }
948
949 let inner = &input[1..];
950 let close = find_matching_paren(inner).ok_or_else(|| Error::ParseError {
951 location: "list value".to_string(),
952 message: "unclosed '(' in list".to_string(),
953 })?;
954
955 let content = &inner[..close];
956 let values = split_list_elements(content);
957
958 Ok(values
959 .into_iter()
960 .map(|s| CompactString::from(s.as_str()))
961 .collect())
962}
963
964fn find_matching_paren(input: &str) -> Option<usize> {
965 let mut depth = 0;
966 let mut in_quote = false;
967 for (i, ch) in input.chars().enumerate() {
968 if ch == '"' {
969 in_quote = !in_quote;
970 } else if !in_quote {
971 if ch == '(' {
972 depth += 1;
973 } else if ch == ')' {
974 if depth == 0 {
975 return Some(i);
976 }
977 depth -= 1;
978 }
979 }
980 }
981 None
982}
983
984fn split_list_elements(input: &str) -> Vec<String> {
985 let mut elements = Vec::new();
986 let mut current = String::new();
987 let chars = input.chars();
988 let mut escape_next = false;
989 let mut in_quote = false;
990
991 for ch in chars {
992 if escape_next {
993 current.push(ch);
994 escape_next = false;
995 continue;
996 }
997
998 if in_quote {
999 if ch == '\\' {
1000 escape_next = true;
1001 } else if ch == '"' {
1002 in_quote = false;
1003 } else {
1004 current.push(ch);
1005 }
1006 } else if ch == '"' {
1007 in_quote = true;
1008 } else if ch == ',' {
1009 elements.push(current.clone());
1010 current.clear();
1011 } else {
1012 current.push(ch);
1013 }
1014 }
1015 elements.push(current);
1016 elements
1017}
1018
1019pub fn parse_order(input: &str) -> Result<Vec<OrderTerm>, Error> {
1025 let mut terms = Vec::new();
1026
1027 for part in input.split(',') {
1028 let part = part.trim();
1029 if part.is_empty() {
1030 continue;
1031 }
1032 terms.push(parse_order_term(part)?);
1033 }
1034
1035 Ok(terms)
1036}
1037
1038fn parse_order_term(input: &str) -> Result<OrderTerm, Error> {
1039 if let Some(paren_pos) = input.find('(')
1041 && let Some(close_pos) = input.find(')')
1042 {
1043 let relation = CompactString::from(input[..paren_pos].trim());
1044 let field_str = &input[paren_pos + 1..close_pos];
1045 let (name, rest_in_paren) = parse_field_name(field_str)?;
1046 let (json_path, _) = parse_json_path(rest_in_paren);
1047
1048 let after_paren = &input[close_pos + 1..];
1049 let (direction, nulls) = parse_order_modifiers(after_paren);
1050
1051 return Ok(OrderTerm::RelationTerm {
1052 relation,
1053 field: (name, json_path),
1054 direction,
1055 nulls,
1056 });
1057 }
1058
1059 let (name, rest) = parse_field_name(input)?;
1061 let (json_path, rest) = parse_json_path(rest);
1062 let (direction, nulls) = parse_order_modifiers(rest);
1063
1064 Ok(OrderTerm::Term {
1065 field: (name, json_path),
1066 direction,
1067 nulls,
1068 })
1069}
1070
1071fn parse_order_modifiers(input: &str) -> (Option<OrderDirection>, Option<OrderNulls>) {
1072 let mut direction = None;
1073 let mut nulls = None;
1074 let mut rest = input;
1075
1076 if let Some(r) = rest.strip_prefix(".asc") {
1078 if !r.starts_with(|c: char| c.is_alphanumeric()) {
1079 direction = Some(OrderDirection::Asc);
1080 rest = r;
1081 }
1082 } else if let Some(r) = rest.strip_prefix(".desc")
1083 && !r.starts_with(|c: char| c.is_alphanumeric())
1084 {
1085 direction = Some(OrderDirection::Desc);
1086 rest = r;
1087 }
1088
1089 if let Some(r) = rest.strip_prefix(".nullsfirst") {
1091 if !r.starts_with(|c: char| c.is_alphanumeric()) {
1092 nulls = Some(OrderNulls::First);
1093 }
1094 } else if let Some(r) = rest.strip_prefix(".nullslast")
1095 && !r.starts_with(|c: char| c.is_alphanumeric())
1096 {
1097 nulls = Some(OrderNulls::Last);
1098 }
1099
1100 (direction, nulls)
1101}
1102
1103pub fn parse_logic_tree(input: &str) -> Result<LogicTree, Error> {
1110 let input = input.trim();
1111
1112 let (negated, rest) = if let Some(after) = input.strip_prefix("not.") {
1114 (true, after)
1115 } else {
1116 (false, input)
1117 };
1118
1119 let (operator, rest) = if let Some(rest) = rest.strip_prefix("and") {
1121 (Some(LogicOperator::And), rest)
1122 } else if let Some(rest) = rest.strip_prefix("or") {
1123 (Some(LogicOperator::Or), rest)
1124 } else {
1125 (None, rest)
1126 };
1127
1128 match operator {
1129 Some(op) => {
1130 let rest = rest.trim();
1132 if !rest.starts_with('(') {
1133 return Err(Error::ParseError {
1134 location: "logic tree".to_string(),
1135 message: format!("expected '(' after logic operator, got '{}'", rest),
1136 });
1137 }
1138
1139 let inner = &rest[1..];
1140 let close = find_matching_paren(inner).ok_or_else(|| Error::ParseError {
1141 location: "logic tree".to_string(),
1142 message: "unclosed '(' in logic tree".to_string(),
1143 })?;
1144
1145 let content = &inner[..close];
1146 let children = parse_logic_children(content)?;
1147
1148 Ok(LogicTree::Expr {
1149 negated,
1150 operator: op,
1151 children,
1152 })
1153 }
1154 None => {
1155 let filter = parse_logic_filter(rest)?;
1157 Ok(LogicTree::Stmnt(filter))
1158 }
1159 }
1160}
1161
1162fn parse_logic_children(input: &str) -> Result<Vec<LogicTree>, Error> {
1163 let parts = split_logic_args(input);
1164 let mut children = Vec::new();
1165
1166 for part in parts {
1167 let part = part.trim();
1168 if part.is_empty() {
1169 continue;
1170 }
1171 children.push(parse_logic_tree(part)?);
1172 }
1173
1174 if children.is_empty() {
1175 return Err(Error::ParseError {
1176 location: "logic tree".to_string(),
1177 message: "empty logic expression".to_string(),
1178 });
1179 }
1180
1181 Ok(children)
1182}
1183
1184fn split_logic_args(input: &str) -> Vec<&str> {
1185 let mut parts = Vec::new();
1186 let mut depth = 0;
1187 let mut start = 0;
1188
1189 for (i, ch) in input.char_indices() {
1190 match ch {
1191 '(' => depth += 1,
1192 ')' => depth -= 1,
1193 ',' if depth == 0 => {
1194 parts.push(&input[start..i]);
1195 start = i + ch.len_utf8();
1196 }
1197 _ => {}
1198 }
1199 }
1200 parts.push(&input[start..]);
1201 parts
1202}
1203
1204fn parse_logic_filter(input: &str) -> Result<Filter, Error> {
1205 let (name, rest) = parse_field_name(input)?;
1207 let (json_path, rest) = parse_json_path(rest);
1208
1209 let rest = rest.strip_prefix('.').ok_or_else(|| Error::ParseError {
1210 location: "logic filter".to_string(),
1211 message: format!("expected '.' after field name '{}'", name),
1212 })?;
1213
1214 let op_expr = try_parse_op_expr(rest)?;
1215
1216 Ok(Filter {
1217 field: (name, json_path),
1218 op_expr,
1219 })
1220}
1221
1222fn parse_tree_path(key: &str) -> Result<(EmbedPath, Field), Error> {
1230 let parts: Vec<&str> = key.split('.').collect();
1231 if parts.is_empty() {
1232 return Err(Error::ParseError {
1233 location: "tree path".to_string(),
1234 message: "empty path".to_string(),
1235 });
1236 }
1237
1238 let path: Vec<FieldName> = parts[..parts.len() - 1]
1239 .iter()
1240 .map(|s| CompactString::from(*s))
1241 .collect();
1242
1243 let last = *parts.last().unwrap();
1244 let (name, rest) = parse_field_name(last)?;
1245 let (json_path, _) = parse_json_path(rest);
1246
1247 Ok((path, (name, json_path)))
1248}
1249
1250fn parse_logic_path(key: &str) -> Result<(EmbedPath, String), Error> {
1252 let parts: Vec<&str> = key.split('.').collect();
1253 if parts.is_empty() {
1254 return Err(Error::ParseError {
1255 location: "logic path".to_string(),
1256 message: "empty path".to_string(),
1257 });
1258 }
1259
1260 let op = parts.last().unwrap().to_string();
1261 let path: Vec<FieldName> = parts[..parts.len() - 1]
1262 .iter()
1263 .map(|s| CompactString::from(*s))
1264 .collect();
1265
1266 Ok((path, op))
1267}
1268
1269fn parse_columns(input: &str) -> Result<HashSet<FieldName>, Error> {
1274 Ok(parse_columns_list(input)?.into_iter().collect())
1275}
1276
1277fn parse_columns_list(input: &str) -> Result<Vec<FieldName>, Error> {
1278 Ok(input
1279 .split(',')
1280 .map(|s| CompactString::from(s.trim()))
1281 .filter(|s| !s.is_empty())
1282 .collect())
1283}
1284
1285fn replace_last_segment(key: &str, _replacement: &str) -> String {
1290 let parts: Vec<&str> = key.split('.').collect();
1291 if parts.len() <= 1 {
1292 return "limit".to_string();
1293 }
1294 let mut result: Vec<&str> = parts[..parts.len() - 1].to_vec();
1295 result.push("limit");
1296 result.join(".")
1297}
1298
1299#[cfg(test)]
1304mod tests {
1305 use super::*;
1306
1307 #[test]
1310 fn test_parse_select_star() {
1311 let items = parse_select("*").unwrap();
1312 assert_eq!(items.len(), 1);
1313 assert!(matches!(&items[0], SelectItem::Field { field, .. } if field.0 == "*"));
1314 }
1315
1316 #[test]
1317 fn test_parse_select_simple_fields() {
1318 let items = parse_select("id,name").unwrap();
1319 assert_eq!(items.len(), 2);
1320 assert!(matches!(&items[0], SelectItem::Field { field, .. } if field.0 == "id"));
1321 assert!(matches!(&items[1], SelectItem::Field { field, .. } if field.0 == "name"));
1322 }
1323
1324 #[test]
1325 fn test_parse_select_with_alias() {
1326 let items = parse_select("my_id:id,my_name:name").unwrap();
1327 assert_eq!(items.len(), 2);
1328 if let SelectItem::Field { alias, field, .. } = &items[0] {
1329 assert_eq!(alias.as_deref(), Some("my_id"));
1330 assert_eq!(field.0.as_str(), "id");
1331 }
1332 }
1333
1334 #[test]
1335 fn test_parse_select_with_cast() {
1336 let items = parse_select("id::text").unwrap();
1337 assert_eq!(items.len(), 1);
1338 if let SelectItem::Field { cast, .. } = &items[0] {
1339 assert_eq!(cast.as_deref(), Some("text"));
1340 }
1341 }
1342
1343 #[test]
1344 fn test_parse_select_with_json_path() {
1345 let items = parse_select("data->key->>value").unwrap();
1346 assert_eq!(items.len(), 1);
1347 if let SelectItem::Field { field, .. } = &items[0] {
1348 assert_eq!(field.0.as_str(), "data");
1349 assert_eq!(field.1.len(), 2);
1350 assert!(matches!(&field.1[0], JsonOperation::Arrow(JsonOperand::Key(k)) if k == "key"));
1351 assert!(
1352 matches!(&field.1[1], JsonOperation::Arrow2(JsonOperand::Key(k)) if k == "value")
1353 );
1354 }
1355 }
1356
1357 #[test]
1358 fn test_parse_select_with_aggregate() {
1359 let items = parse_select("amount.sum()").unwrap();
1360 assert_eq!(items.len(), 1);
1361 if let SelectItem::Field {
1362 field, aggregate, ..
1363 } = &items[0]
1364 {
1365 assert_eq!(field.0.as_str(), "amount");
1366 assert_eq!(*aggregate, Some(AggregateFunction::Sum));
1367 }
1368 }
1369
1370 #[test]
1371 fn test_parse_select_count() {
1372 let items = parse_select("count()").unwrap();
1373 assert_eq!(items.len(), 1);
1374 if let SelectItem::Field {
1375 field, aggregate, ..
1376 } = &items[0]
1377 {
1378 assert_eq!(field.0.as_str(), "*");
1379 assert_eq!(*aggregate, Some(AggregateFunction::Count));
1380 }
1381 }
1382
1383 #[test]
1384 fn test_parse_select_relation() {
1385 let items = parse_select("posts(id,title)").unwrap();
1386 assert_eq!(items.len(), 1);
1387 if let SelectItem::Relation {
1388 relation, children, ..
1389 } = &items[0]
1390 {
1391 assert_eq!(relation.as_str(), "posts");
1392 assert_eq!(children.len(), 2);
1393 }
1394 }
1395
1396 #[test]
1397 fn test_parse_select_relation_with_alias_and_hint() {
1398 let items = parse_select("my_posts:posts!fk_author(*)").unwrap();
1399 assert_eq!(items.len(), 1);
1400 if let SelectItem::Relation {
1401 relation,
1402 alias,
1403 hint,
1404 children,
1405 ..
1406 } = &items[0]
1407 {
1408 assert_eq!(relation.as_str(), "posts");
1409 assert_eq!(alias.as_deref(), Some("my_posts"));
1410 assert_eq!(hint.as_deref(), Some("fk_author"));
1411 assert_eq!(children.len(), 1);
1412 }
1413 }
1414
1415 #[test]
1416 fn test_parse_select_relation_with_join_type() {
1417 let items = parse_select("posts!inner(*)").unwrap();
1418 assert_eq!(items.len(), 1);
1419 if let SelectItem::Relation {
1420 relation,
1421 join_type,
1422 ..
1423 } = &items[0]
1424 {
1425 assert_eq!(relation.as_str(), "posts");
1426 assert_eq!(*join_type, Some(JoinType::Inner));
1427 }
1428 }
1429
1430 #[test]
1431 fn test_parse_select_spread() {
1432 let items = parse_select("...details(*)").unwrap();
1433 assert_eq!(items.len(), 1);
1434 if let SelectItem::Spread {
1435 relation, children, ..
1436 } = &items[0]
1437 {
1438 assert_eq!(relation.as_str(), "details");
1439 assert_eq!(children.len(), 1);
1440 }
1441 }
1442
1443 #[test]
1444 fn test_parse_select_nested() {
1445 let items = parse_select("*,clients(*,projects(*))").unwrap();
1446 assert_eq!(items.len(), 2);
1447 if let SelectItem::Relation { children, .. } = &items[1] {
1448 assert_eq!(children.len(), 2);
1449 assert!(
1450 matches!(&children[1], SelectItem::Relation { relation, .. } if relation == "projects")
1451 );
1452 }
1453 }
1454
1455 #[test]
1456 fn test_parse_select_empty() {
1457 let items = parse_select("").unwrap();
1458 assert!(items.is_empty());
1459 }
1460
1461 #[test]
1462 fn test_parse_select_complex() {
1463 let items =
1464 parse_select("alias:name->key::cast,posts!hint!inner(id,author(*)),...spread(*)")
1465 .unwrap();
1466 assert_eq!(items.len(), 3);
1467 }
1468
1469 #[test]
1472 fn test_parse_filter_eq() {
1473 let expr = try_parse_op_expr("eq.5").unwrap();
1474 assert_eq!(
1475 expr,
1476 OpExpr::Expr {
1477 negated: false,
1478 operation: Operation::Quant(QuantOperator::Equal, None, "5".into())
1479 }
1480 );
1481 }
1482
1483 #[test]
1484 fn test_parse_filter_not_eq() {
1485 let expr = try_parse_op_expr("not.eq.5").unwrap();
1486 assert_eq!(
1487 expr,
1488 OpExpr::Expr {
1489 negated: true,
1490 operation: Operation::Quant(QuantOperator::Equal, None, "5".into())
1491 }
1492 );
1493 }
1494
1495 #[test]
1496 fn test_parse_filter_neq() {
1497 let expr = try_parse_op_expr("neq.5").unwrap();
1498 assert_eq!(
1499 expr,
1500 OpExpr::Expr {
1501 negated: false,
1502 operation: Operation::Simple(SimpleOperator::NotEqual, "5".into())
1503 }
1504 );
1505 }
1506
1507 #[test]
1508 fn test_parse_filter_gt_lt() {
1509 let gt = try_parse_op_expr("gt.10").unwrap();
1510 assert!(matches!(
1511 gt,
1512 OpExpr::Expr {
1513 operation: Operation::Quant(QuantOperator::GreaterThan, None, _),
1514 ..
1515 }
1516 ));
1517
1518 let lt = try_parse_op_expr("lt.5").unwrap();
1519 assert!(matches!(
1520 lt,
1521 OpExpr::Expr {
1522 operation: Operation::Quant(QuantOperator::LessThan, None, _),
1523 ..
1524 }
1525 ));
1526 }
1527
1528 #[test]
1529 fn test_parse_filter_in() {
1530 let expr = try_parse_op_expr("in.(1,2,3)").unwrap();
1531 if let OpExpr::Expr {
1532 operation: Operation::In(vals),
1533 ..
1534 } = &expr
1535 {
1536 assert_eq!(vals.len(), 3);
1537 assert_eq!(vals[0].as_str(), "1");
1538 assert_eq!(vals[1].as_str(), "2");
1539 assert_eq!(vals[2].as_str(), "3");
1540 } else {
1541 panic!("expected In operation");
1542 }
1543 }
1544
1545 #[test]
1546 fn test_parse_filter_is_null() {
1547 let expr = try_parse_op_expr("is.null").unwrap();
1548 assert_eq!(
1549 expr,
1550 OpExpr::Expr {
1551 negated: false,
1552 operation: Operation::Is(IsValue::Null)
1553 }
1554 );
1555 }
1556
1557 #[test]
1558 fn test_parse_filter_is_true() {
1559 let expr = try_parse_op_expr("is.true").unwrap();
1560 assert_eq!(
1561 expr,
1562 OpExpr::Expr {
1563 negated: false,
1564 operation: Operation::Is(IsValue::True)
1565 }
1566 );
1567 }
1568
1569 #[test]
1570 fn test_parse_filter_fts() {
1571 let expr = try_parse_op_expr("fts.search_term").unwrap();
1572 if let OpExpr::Expr {
1573 operation: Operation::Fts(op, lang, val),
1574 ..
1575 } = &expr
1576 {
1577 assert_eq!(*op, FtsOperator::Fts);
1578 assert!(lang.is_none());
1579 assert_eq!(val.as_str(), "search_term");
1580 }
1581 }
1582
1583 #[test]
1584 fn test_parse_filter_fts_with_lang() {
1585 let expr = try_parse_op_expr("fts(english).search_term").unwrap();
1586 if let OpExpr::Expr {
1587 operation: Operation::Fts(_, lang, _),
1588 ..
1589 } = &expr
1590 {
1591 assert_eq!(lang.as_deref(), Some("english"));
1592 }
1593 }
1594
1595 #[test]
1596 fn test_parse_filter_like() {
1597 let expr = try_parse_op_expr("like.*john*").unwrap();
1598 assert!(matches!(
1599 expr,
1600 OpExpr::Expr {
1601 operation: Operation::Quant(QuantOperator::Like, None, _),
1602 ..
1603 }
1604 ));
1605 }
1606
1607 #[test]
1608 fn test_parse_filter_quant_any() {
1609 let expr = try_parse_op_expr("eq(any).5").unwrap();
1610 assert_eq!(
1611 expr,
1612 OpExpr::Expr {
1613 negated: false,
1614 operation: Operation::Quant(
1615 QuantOperator::Equal,
1616 Some(OpQuantifier::Any),
1617 "5".into()
1618 )
1619 }
1620 );
1621 }
1622
1623 #[test]
1624 fn test_parse_filter_quant_all() {
1625 let expr = try_parse_op_expr("eq(all).5").unwrap();
1626 assert_eq!(
1627 expr,
1628 OpExpr::Expr {
1629 negated: false,
1630 operation: Operation::Quant(
1631 QuantOperator::Equal,
1632 Some(OpQuantifier::All),
1633 "5".into()
1634 )
1635 }
1636 );
1637 }
1638
1639 #[test]
1640 fn test_parse_filter_isdistinct() {
1641 let expr = try_parse_op_expr("isdistinct.value").unwrap();
1642 assert_eq!(
1643 expr,
1644 OpExpr::Expr {
1645 negated: false,
1646 operation: Operation::IsDistinctFrom("value".into())
1647 }
1648 );
1649 }
1650
1651 #[test]
1652 fn test_parse_filter_cs_cd() {
1653 let cs = try_parse_op_expr("cs.{1,2,3}").unwrap();
1654 assert!(matches!(
1655 cs,
1656 OpExpr::Expr {
1657 operation: Operation::Simple(SimpleOperator::Contains, _),
1658 ..
1659 }
1660 ));
1661
1662 let cd = try_parse_op_expr("cd.{1,2}").unwrap();
1663 assert!(matches!(
1664 cd,
1665 OpExpr::Expr {
1666 operation: Operation::Simple(SimpleOperator::Contained, _),
1667 ..
1668 }
1669 ));
1670 }
1671
1672 #[test]
1673 fn test_parse_filter_rpc_no_op() {
1674 let expr = parse_filter_value("plain_value", true).unwrap();
1675 assert_eq!(expr, OpExpr::NoOp("plain_value".into()));
1676 }
1677
1678 #[test]
1679 fn test_parse_filter_rpc_with_op() {
1680 let expr = parse_filter_value("eq.5", true).unwrap();
1681 assert!(matches!(expr, OpExpr::Expr { .. }));
1682 }
1683
1684 #[test]
1685 fn test_parse_filter_non_rpc_requires_op() {
1686 let result = parse_filter_value("plain_value", false);
1687 assert!(result.is_err());
1688 }
1689
1690 #[test]
1693 fn test_parse_order_simple() {
1694 let terms = parse_order("name").unwrap();
1695 assert_eq!(terms.len(), 1);
1696 if let OrderTerm::Term {
1697 field,
1698 direction,
1699 nulls,
1700 } = &terms[0]
1701 {
1702 assert_eq!(field.0.as_str(), "name");
1703 assert!(direction.is_none());
1704 assert!(nulls.is_none());
1705 }
1706 }
1707
1708 #[test]
1709 fn test_parse_order_desc_nullsfirst() {
1710 let terms = parse_order("name.desc.nullsfirst").unwrap();
1711 assert_eq!(terms.len(), 1);
1712 if let OrderTerm::Term {
1713 direction, nulls, ..
1714 } = &terms[0]
1715 {
1716 assert_eq!(*direction, Some(OrderDirection::Desc));
1717 assert_eq!(*nulls, Some(OrderNulls::First));
1718 }
1719 }
1720
1721 #[test]
1722 fn test_parse_order_multiple() {
1723 let terms = parse_order("name.asc,id.desc").unwrap();
1724 assert_eq!(terms.len(), 2);
1725 }
1726
1727 #[test]
1728 fn test_parse_order_json() {
1729 let terms = parse_order("json_col->key.asc.nullslast").unwrap();
1730 assert_eq!(terms.len(), 1);
1731 if let OrderTerm::Term { field, .. } = &terms[0] {
1732 assert_eq!(field.0.as_str(), "json_col");
1733 assert_eq!(field.1.len(), 1);
1734 }
1735 }
1736
1737 #[test]
1738 fn test_parse_order_relation() {
1739 let terms = parse_order("clients(name).desc.nullsfirst").unwrap();
1740 assert_eq!(terms.len(), 1);
1741 assert!(
1742 matches!(&terms[0], OrderTerm::RelationTerm { relation, .. } if relation == "clients")
1743 );
1744 }
1745
1746 #[test]
1749 fn test_parse_logic_tree_simple() {
1750 let tree = parse_logic_tree("and(name.eq.John,age.gt.18)").unwrap();
1751 if let LogicTree::Expr {
1752 negated,
1753 operator,
1754 children,
1755 } = &tree
1756 {
1757 assert!(!negated);
1758 assert_eq!(*operator, LogicOperator::And);
1759 assert_eq!(children.len(), 2);
1760 }
1761 }
1762
1763 #[test]
1764 fn test_parse_logic_tree_or() {
1765 let tree = parse_logic_tree("or(id.eq.1,id.eq.2)").unwrap();
1766 if let LogicTree::Expr { operator, .. } = &tree {
1767 assert_eq!(*operator, LogicOperator::Or);
1768 }
1769 }
1770
1771 #[test]
1772 fn test_parse_logic_tree_nested() {
1773 let tree = parse_logic_tree("and(name.eq.John,or(id.eq.1,id.eq.2))").unwrap();
1774 if let LogicTree::Expr { children, .. } = &tree {
1775 assert_eq!(children.len(), 2);
1776 assert!(matches!(
1777 &children[1],
1778 LogicTree::Expr {
1779 operator: LogicOperator::Or,
1780 ..
1781 }
1782 ));
1783 }
1784 }
1785
1786 #[test]
1787 fn test_parse_logic_tree_negated() {
1788 let tree = parse_logic_tree("not.and(a.eq.1,b.eq.2)").unwrap();
1789 if let LogicTree::Expr { negated, .. } = &tree {
1790 assert!(negated);
1791 }
1792 }
1793
1794 #[test]
1797 fn test_parse_full_query() {
1798 let qp = parse(false, "select=id,name&id=eq.1&order=name.asc").unwrap();
1799 assert_eq!(qp.select.len(), 2);
1800 assert_eq!(qp.filters_root.len(), 1);
1801 assert_eq!(qp.order.len(), 1);
1802 }
1803
1804 #[test]
1805 fn test_parse_with_limit_offset() {
1806 let qp = parse(false, "select=*&limit=25&offset=50").unwrap();
1807 let range = qp.ranges.get("limit").unwrap();
1808 assert_eq!(range.offset, 50);
1809 assert_eq!(range.limit(), Some(25));
1810 }
1811
1812 #[test]
1813 fn test_parse_canonical() {
1814 let qp = parse(false, "b=eq.2&a=eq.1").unwrap();
1815 assert!(qp.canonical.starts_with("a="));
1817 }
1818
1819 #[test]
1820 fn test_parse_columns() {
1821 let qp = parse(false, "select=*&columns=id,name").unwrap();
1822 let cols = qp.columns.as_ref().unwrap();
1823 assert!(cols.contains("id"));
1824 assert!(cols.contains("name"));
1825 }
1826
1827 #[test]
1828 fn test_parse_on_conflict() {
1829 let qp = parse(false, "select=*&on_conflict=id,email").unwrap();
1830 let oc = qp.on_conflict.as_ref().unwrap();
1831 assert_eq!(oc.len(), 2);
1832 }
1833
1834 #[test]
1835 fn test_parse_rpc_params() {
1836 let qp = parse(true, "id=5&name=john").unwrap();
1837 assert_eq!(qp.params.len(), 2);
1838 }
1839
1840 #[test]
1841 fn test_parse_embedded_filter() {
1842 let qp = parse(false, "select=*,posts(*)&posts.status=eq.published").unwrap();
1843 assert_eq!(qp.filters_not_root.len(), 1);
1844 assert_eq!(qp.filters_not_root[0].0, vec![CompactString::from("posts")]);
1845 }
1846
1847 #[test]
1848 fn test_parse_embedded_order() {
1849 let qp = parse(false, "select=*,posts(*)&posts.order=created_at.desc").unwrap();
1850 assert_eq!(qp.order.len(), 1);
1851 assert_eq!(qp.order[0].0, vec![CompactString::from("posts")]);
1852 }
1853
1854 #[test]
1855 fn test_parse_logic() {
1856 let qp = parse(false, "select=*&or=(id.eq.1,id.eq.2)").unwrap();
1857 assert_eq!(qp.logic.len(), 1);
1858 }
1859
1860 #[test]
1861 fn test_parse_default_select() {
1862 let qp = parse(false, "id=eq.1").unwrap();
1863 assert_eq!(qp.select.len(), 1);
1864 assert!(matches!(&qp.select[0], SelectItem::Field { field, .. } if field.0 == "*"));
1865 }
1866
1867 #[test]
1868 fn test_parse_all_simple_operators() {
1869 for op in ["neq", "cs", "cd", "ov", "sl", "sr", "nxr", "nxl", "adj"] {
1870 let expr = try_parse_op_expr(&format!("{}.value", op)).unwrap();
1871 assert!(
1872 matches!(
1873 expr,
1874 OpExpr::Expr {
1875 operation: Operation::Simple(..),
1876 ..
1877 }
1878 ),
1879 "operator {} should parse as Simple",
1880 op
1881 );
1882 }
1883 }
1884
1885 #[test]
1886 fn test_parse_all_quant_operators() {
1887 for op in [
1888 "eq", "gte", "gt", "lte", "lt", "like", "ilike", "match", "imatch",
1889 ] {
1890 let expr = try_parse_op_expr(&format!("{}.value", op)).unwrap();
1891 assert!(
1892 matches!(
1893 expr,
1894 OpExpr::Expr {
1895 operation: Operation::Quant(..),
1896 ..
1897 }
1898 ),
1899 "operator {} should parse as Quant",
1900 op
1901 );
1902 }
1903 }
1904
1905 #[test]
1906 fn test_parse_all_fts_operators() {
1907 for op in ["fts", "plfts", "phfts", "wfts"] {
1908 let expr = try_parse_op_expr(&format!("{}.term", op)).unwrap();
1909 assert!(
1910 matches!(
1911 expr,
1912 OpExpr::Expr {
1913 operation: Operation::Fts(..),
1914 ..
1915 }
1916 ),
1917 "operator {} should parse as Fts",
1918 op
1919 );
1920 }
1921 }
1922
1923 #[test]
1924 fn test_parse_is_values() {
1925 for (val, expected) in [
1926 ("is.null", IsValue::Null),
1927 ("is.not_null", IsValue::NotNull),
1928 ("is.true", IsValue::True),
1929 ("is.false", IsValue::False),
1930 ("is.unknown", IsValue::Unknown),
1931 ] {
1932 let expr = try_parse_op_expr(val).unwrap();
1933 assert_eq!(
1934 expr,
1935 OpExpr::Expr {
1936 negated: false,
1937 operation: Operation::Is(expected)
1938 },
1939 "is value {} should parse",
1940 val
1941 );
1942 }
1943 }
1944}