1use serde::Serialize;
121
122pub mod ast;
123pub mod error;
124pub mod parser;
125pub mod sql;
126
127#[cfg(any(feature = "postgres", feature = "wasm"))]
128pub mod schema_cache;
129
130#[cfg(feature = "wasm")]
131pub mod wasm;
132
133pub use ast::{
134 Cardinality, Column, ConflictAction, Count, DeleteParams, Direction, Field, Filter,
135 FilterOperator, FilterValue, InsertParams, InsertValues, ItemHint, ItemType, JsonOp, Junction,
136 LogicCondition, LogicOperator, LogicTree, Missing, Nulls, OnConflict, Operation, OrderTerm,
137 ParsedParams, Plurality, PreferOptions, Quantifier, Relationship, Resolution, ResolvedTable,
138 ReturnRepresentation, RpcParams, SelectItem, Table, UpdateParams,
139};
140pub use error::{Error, ParseError, SqlError};
141pub use parser::{
142 field, get_profile_header, identifier, json_path, json_path_segment, logic_key,
143 parse_delete_params, parse_filter, parse_insert_params, parse_json_body, parse_logic,
144 parse_order, parse_order_term, parse_prefer_header, parse_qualified_table, parse_rpc_params,
145 parse_select, parse_update_params, reserved_key, resolve_schema, type_cast,
146 validate_insert_body, validate_update_body,
147};
148pub use sql::{QueryBuilder, QueryResult};
149
150#[cfg(feature = "postgres")]
151pub use schema_cache::{ForeignKey, RelationType, SchemaCache};
152
153pub fn parse_query_string(query_string: &str) -> Result<ParsedParams, Error> {
177 let pairs: Vec<(String, String)> = query_string
178 .split('&')
179 .filter_map(|pair| {
180 let parts: Vec<&str> = pair.splitn(2, '=').collect();
181 if parts.len() == 2 {
182 Some((parts[0].to_string(), parts[1].to_string()))
183 } else {
184 None
185 }
186 })
187 .collect();
188
189 parse_params_from_pairs(pairs)
190}
191
192pub fn parse_params(
215 params: &std::collections::HashMap<String, String>,
216) -> Result<ParsedParams, Error> {
217 let select_str = params.get("select").map(|s| s.to_string());
218 let order_str = params.get("order").map(|s| s.to_string());
219 let filters = parse_filters_from_map(params)?;
220 let limit = params.get("limit").and_then(|s| s.parse::<u64>().ok());
221 let offset = params.get("offset").and_then(|s| s.parse::<u64>().ok());
222
223 let mut parsed = ParsedParams::new().with_filters(filters);
224
225 if let Some(select_str) = select_str {
226 parsed = parsed.with_select(parse_select(&select_str)?);
227 }
228
229 if let Some(order_str) = order_str {
230 parsed = parsed.with_order(parse_order(&order_str)?);
231 }
232
233 if let Some(lim) = limit {
234 parsed = parsed.with_limit(lim);
235 }
236
237 if let Some(off) = offset {
238 parsed = parsed.with_offset(off);
239 }
240
241 Ok(parsed)
242}
243
244pub fn parse_params_from_pairs(pairs: Vec<(String, String)>) -> Result<ParsedParams, Error> {
245 let mut single_value_map = std::collections::HashMap::new();
248 let mut filter_pairs = Vec::new();
249
250 for (key, value) in pairs {
251 if parser::filter::reserved_key(&key) {
252 single_value_map.insert(key, value);
254 } else {
255 filter_pairs.push((key, value));
257 }
258 }
259
260 let select_str = single_value_map.get("select").map(|s| s.to_string());
262 let order_str = single_value_map.get("order").map(|s| s.to_string());
263 let limit = single_value_map
264 .get("limit")
265 .and_then(|s| s.parse::<u64>().ok());
266 let offset = single_value_map
267 .get("offset")
268 .and_then(|s| s.parse::<u64>().ok());
269
270 let filters = parse_filters_from_pairs(&filter_pairs)?;
272
273 let mut parsed = ParsedParams::new().with_filters(filters);
274
275 if let Some(select_str) = select_str {
276 parsed = parsed.with_select(parse_select(&select_str)?);
277 }
278
279 if let Some(order_str) = order_str {
280 parsed = parsed.with_order(parse_order(&order_str)?);
281 }
282
283 if let Some(lim) = limit {
284 parsed = parsed.with_limit(lim);
285 }
286
287 if let Some(off) = offset {
288 parsed = parsed.with_offset(off);
289 }
290
291 Ok(parsed)
292}
293
294pub fn to_sql(table: &str, params: &ParsedParams) -> Result<QueryResult, Error> {
319 if table.is_empty() {
320 return Err(Error::Sql(SqlError::EmptyTableName));
321 }
322
323 let mut builder = QueryBuilder::new();
324 builder.build_select(table, params).map_err(Error::Sql)
325}
326
327pub fn query_string_to_sql(table: &str, query_string: &str) -> Result<QueryResult, Error> {
355 let params = parse_query_string(query_string)?;
356 to_sql(table, ¶ms)
357}
358
359pub fn build_filter_clause(filters: &[LogicCondition]) -> Result<FilterClauseResult, Error> {
387 let mut builder = QueryBuilder::new();
388 builder.build_where_clause(filters).map_err(Error::Sql)?;
389
390 Ok(FilterClauseResult {
391 clause: builder.sql.clone(),
392 params: builder.params.clone(),
393 })
394}
395
396pub fn parse(
426 method: &str,
427 table: &str,
428 query_string: &str,
429 body: Option<&str>,
430 headers: Option<&std::collections::HashMap<String, String>>,
431) -> Result<Operation, Error> {
432 if let Some(function_name) = table.strip_prefix("rpc/") {
434 if function_name.is_empty() {
436 return Err(Error::Parse(ParseError::InvalidTableName(
437 "RPC function name cannot be empty".to_string(),
438 )));
439 }
440
441 let _resolved_table = resolve_schema(function_name, method, headers)?;
443
444 let prefer = headers
446 .and_then(|h| {
447 h.get("Prefer")
448 .or_else(|| h.get("prefer"))
449 .or_else(|| h.get("PREFER"))
450 })
451 .map(|p| parse_prefer_header(p))
452 .transpose()?;
453
454 let params = parse_rpc_params(function_name, query_string, body)?;
456 return Ok(Operation::Rpc(params, prefer));
457 }
458
459 let _resolved_table = resolve_schema(table, method, headers)?;
461
462 let prefer = headers
464 .and_then(|h| {
465 h.get("Prefer")
466 .or_else(|| h.get("prefer"))
467 .or_else(|| h.get("PREFER"))
468 })
469 .map(|p| parse_prefer_header(p))
470 .transpose()?;
471
472 match method.to_uppercase().as_str() {
473 "GET" => {
474 let params = parse_query_string(query_string)?;
475 Ok(Operation::Select(params, prefer))
476 }
477 "POST" => {
478 let body = body.ok_or_else(|| {
479 Error::Parse(ParseError::InvalidInsertBody(
480 "Body is required for INSERT".to_string(),
481 ))
482 })?;
483 let params = parse_insert_params(query_string, body)?;
484 Ok(Operation::Insert(params, prefer))
485 }
486 "PUT" => {
487 let body = body.ok_or_else(|| {
489 Error::Parse(ParseError::InvalidInsertBody(
490 "Body is required for PUT/upsert".to_string(),
491 ))
492 })?;
493 let mut params = parse_insert_params(query_string, body)?;
494
495 if params.on_conflict.is_none() {
497 let conflict_columns = extract_conflict_columns_from_query(query_string);
499 if !conflict_columns.is_empty() {
500 params = params.with_on_conflict(OnConflict::do_update(conflict_columns));
501 }
502 }
503
504 Ok(Operation::Insert(params, prefer))
505 }
506 "PATCH" => {
507 let body = body.ok_or_else(|| {
508 Error::Parse(ParseError::InvalidUpdateBody(
509 "Body is required for UPDATE".to_string(),
510 ))
511 })?;
512 let params = parse_update_params(query_string, body)?;
513 Ok(Operation::Update(params, prefer))
514 }
515 "DELETE" => {
516 let params = parse_delete_params(query_string)?;
517 Ok(Operation::Delete(params, prefer))
518 }
519 _ => Err(Error::Parse(ParseError::UnsupportedMethod(format!(
520 "Unsupported HTTP method: {}",
521 method
522 )))),
523 }
524}
525
526pub fn operation_to_sql(table: &str, operation: &Operation) -> Result<QueryResult, Error> {
543 match operation {
547 Operation::Select(params, _prefer) => to_sql(table, params),
548 Operation::Insert(params, _prefer) => {
549 let resolved_table = resolve_schema(table, "POST", None)?;
551 let mut builder = QueryBuilder::new();
552 builder
553 .build_insert(&resolved_table, params)
554 .map_err(Error::Sql)
555 }
556 Operation::Update(params, _prefer) => {
557 let resolved_table = resolve_schema(table, "PATCH", None)?;
558 let mut builder = QueryBuilder::new();
559 builder
560 .build_update(&resolved_table, params)
561 .map_err(Error::Sql)
562 }
563 Operation::Delete(params, _prefer) => {
564 let resolved_table = resolve_schema(table, "DELETE", None)?;
565 let mut builder = QueryBuilder::new();
566 builder
567 .build_delete(&resolved_table, params)
568 .map_err(Error::Sql)
569 }
570 Operation::Rpc(params, _prefer) => {
571 let function_name = table.strip_prefix("rpc/").unwrap_or(table);
573 let resolved_table = resolve_schema(function_name, "POST", None)?;
574 let mut builder = QueryBuilder::new();
575 builder
576 .build_rpc(&resolved_table, params)
577 .map_err(Error::Sql)
578 }
579 }
580}
581
582fn extract_conflict_columns_from_query(query_string: &str) -> Vec<String> {
586 if query_string.is_empty() {
587 return Vec::new();
588 }
589
590 let mut columns = Vec::new();
591 for pair in query_string.split('&') {
592 let parts: Vec<&str> = pair.splitn(2, '=').collect();
593 if parts.len() == 2 {
594 let key = parts[0];
595 if !parser::filter::reserved_key(key) && !parser::logic::logic_key(key) {
597 let column_name = if let Some(arrow_pos) = key.find("->") {
599 &key[..arrow_pos]
600 } else {
601 key
602 };
603 if !columns.contains(&column_name.to_string()) {
604 columns.push(column_name.to_string());
605 }
606 }
607 }
608 }
609 columns
610}
611
612fn parse_filters_from_map(
613 params: &std::collections::HashMap<String, String>,
614) -> Result<Vec<LogicCondition>, Error> {
615 let mut filters = Vec::new();
616
617 for (key, value) in params {
618 if parser::filter::reserved_key(key) {
619 continue;
620 }
621
622 if parser::logic::logic_key(key) {
623 let tree = parse_logic(key, value)?;
624 filters.push(LogicCondition::Logic(tree));
625 } else {
626 let filter = parse_filter(key, value)?;
627 filters.push(LogicCondition::Filter(filter));
628 }
629 }
630
631 Ok(filters)
632}
633
634fn parse_filters_from_pairs(pairs: &[(String, String)]) -> Result<Vec<LogicCondition>, Error> {
639 let mut filters = Vec::new();
640
641 for (key, value) in pairs {
642 if parser::filter::reserved_key(key) {
643 continue;
644 }
645
646 if parser::logic::logic_key(key) {
647 let tree = parse_logic(key, value)?;
648 filters.push(LogicCondition::Logic(tree));
649 } else {
650 let filter = parse_filter(key, value)?;
651 filters.push(LogicCondition::Filter(filter));
652 }
653 }
654
655 Ok(filters)
656}
657
658#[derive(Debug, Clone, Serialize)]
662#[serde(rename_all = "camelCase")]
663pub struct FilterClauseResult {
664 pub clause: String,
666 pub params: Vec<serde_json::Value>,
668}
669
670#[cfg(test)]
671mod tests {
672 use super::*;
673
674 #[test]
675 fn test_parse_query_string_empty() {
676 let result = parse_query_string("");
677 assert!(result.is_ok());
678 let params = result.unwrap();
679 assert!(params.is_empty());
680 }
681
682 #[test]
683 fn test_parse_query_string_simple() {
684 let result = parse_query_string("select=id,name&id=eq.1");
685 assert!(result.is_ok());
686 let params = result.unwrap();
687 assert!(params.has_select());
688 assert!(params.has_filters());
689 }
690
691 #[test]
692 fn test_parse_query_string_with_order() {
693 let result = parse_query_string("select=id&order=id.desc");
694 assert!(result.is_ok());
695 let params = result.unwrap();
696 assert!(params.has_select());
697 assert!(!params.order.is_empty());
698 }
699
700 #[test]
701 fn test_parse_query_string_with_limit() {
702 let result = parse_query_string("select=id&limit=10");
703 assert!(result.is_ok());
704 let params = result.unwrap();
705 assert_eq!(params.limit, Some(10));
706 }
707
708 #[test]
709 fn test_to_sql_simple() {
710 let params = ParsedParams::new()
711 .with_select(vec![SelectItem::field("id"), SelectItem::field("name")]);
712
713 let result = to_sql("users", ¶ms);
714 assert!(result.is_ok());
715 let query = result.unwrap();
716 assert!(query.query.contains("SELECT"));
717 assert!(query.query.contains("users"));
718 }
719
720 #[test]
721 fn test_query_string_to_sql() {
722 let result = query_string_to_sql("users", "select=id,name");
723 assert!(result.is_ok());
724 let query = result.unwrap();
725 assert!(query.query.contains("SELECT"));
726 assert!(query.query.contains("users"));
727 assert_eq!(query.tables, vec!["users"]);
728 }
729
730 #[test]
731 fn test_build_filter_clause() {
732 let filter = LogicCondition::Filter(Filter::new(
733 Field::new("id"),
734 FilterOperator::Eq,
735 FilterValue::Single("1".to_string()),
736 ));
737
738 let result = build_filter_clause(&[filter]);
739 assert!(result.is_ok());
740 let clause = result.unwrap();
741 assert!(clause.clause.contains("\"id\""));
742 assert!(clause.clause.contains("="));
743 }
744
745 #[test]
746 fn test_complex_query_with_multiple_filters() {
747 let query_str = "select=id,name,email&age=gte.18&status=in.(active,pending)&order=created_at.desc&limit=10";
748 let result = parse_query_string(query_str);
749 assert!(result.is_ok());
750 let params = result.unwrap();
751
752 assert!(params.has_select());
753 assert!(params.has_filters());
754 assert_eq!(params.filters.len(), 2);
755 assert_eq!(params.order.len(), 1);
756 assert_eq!(params.limit, Some(10));
757 }
758
759 #[test]
760 fn test_query_with_logic_operators() {
761 let query_str = "and=(age.gte.18,status.eq.active)";
762 let result = parse_query_string(query_str);
763 assert!(result.is_ok());
764 let params = result.unwrap();
765 assert!(params.has_filters());
766 }
767
768 #[test]
769 fn test_query_with_json_path() {
770 let query_str = "data->name=eq.John&data->age=gt.25";
771 let result = parse_query_string(query_str);
772 assert!(result.is_ok());
773 let params = result.unwrap();
774 assert_eq!(params.filters.len(), 2);
775 }
776
777 #[test]
778 fn test_query_with_type_cast() {
779 let query_str = "price::numeric=gt.100";
780 let result = parse_query_string(query_str);
781 assert!(result.is_ok());
782 let params = result.unwrap();
783 assert_eq!(params.filters.len(), 1);
784 }
785
786 #[test]
787 fn test_query_to_sql_with_comparison_operators() {
788 let query_str = "age=gte.18&price=lte.100";
789 let result = query_string_to_sql("users", query_str);
790 assert!(result.is_ok());
791 let query = result.unwrap();
792 assert!(query.query.contains(">="));
793 assert!(query.query.contains("<="));
794 assert_eq!(query.params.len(), 2);
795 }
796
797 #[test]
798 fn test_multiple_filters_same_column() {
799 let query_str = "price=gte.50&price=lte.150";
801 let params = parse_query_string(query_str).unwrap();
802
803 assert_eq!(params.filters.len(), 2, "Should have both filters");
805
806 let result = query_string_to_sql("products", query_str).unwrap();
808 assert!(result.query.contains(">="), "Should have >= operator");
809 assert!(result.query.contains("<="), "Should have <= operator");
810 assert_eq!(result.params.len(), 2, "Should have 2 parameter values");
811
812 assert!(result.query.contains("WHERE"));
814 assert!(result.query.contains("AND") || result.query.matches("price").count() == 2);
815 }
816
817 #[test]
818 fn test_query_to_sql_with_fts() {
819 let query_str = "content=fts(english).search term";
820 let result = query_string_to_sql("articles", query_str);
821 assert!(result.is_ok());
822 let query = result.unwrap();
823 assert!(query.query.contains("to_tsvector"));
824 assert!(query.query.contains("plainto_tsquery"));
825 assert!(query.query.contains("english"));
826 }
827
828 #[test]
829 fn test_query_to_sql_with_array_operators() {
830 let query_str = "tags=cs.{rust}";
831 let result = query_string_to_sql("posts", query_str);
832 assert!(result.is_ok());
833 let query = result.unwrap();
834 assert!(query.query.contains("@>"));
835 }
836
837 #[test]
838 fn test_query_to_sql_with_negation() {
839 let query_str = "status=not.eq.deleted";
840 let result = query_string_to_sql("users", query_str);
841 assert!(result.is_ok());
842 let query = result.unwrap();
843 assert!(query.query.contains("<>"));
844 }
845
846 #[test]
847 fn test_complex_nested_query() {
848 let query_str = "select=id,name,orders(id,total)&status=eq.active&age=gte.18&order=created_at.desc&limit=10&offset=20";
849 let result = parse_query_string(query_str);
850 assert!(result.is_ok());
851 let params = result.unwrap();
852
853 assert!(params.has_select());
854 assert_eq!(params.filters.len(), 2);
855 assert_eq!(params.order.len(), 1);
856 assert_eq!(params.limit, Some(10));
857 assert_eq!(params.offset, Some(20));
858 }
859
860 #[test]
861 fn test_query_with_quantifiers() {
862 let query_str = "tags=eq(any).{rust,elixir,go}";
863 let result = query_string_to_sql("posts", query_str);
864 assert!(result.is_ok());
865 let query = result.unwrap();
866 assert!(query.query.contains("= ANY"));
867 }
868
869 #[test]
872 fn test_insert_with_return_representation() {
873 use std::collections::HashMap;
875 let mut headers = HashMap::new();
876 headers.insert("Prefer".to_string(), "return=representation".to_string());
877
878 let body = r#"{"email": "alice@example.com", "name": "Alice"}"#;
879 let op = parse("POST", "users", "", Some(body), Some(&headers)).unwrap();
880
881 match op {
882 Operation::Insert(_, Some(prefer)) => {
883 assert_eq!(
884 prefer.return_representation,
885 Some(ReturnRepresentation::Full)
886 );
887 }
888 _ => panic!("Expected Insert operation with Prefer"),
889 }
890 }
891
892 #[test]
893 fn test_insert_with_minimal_return() {
894 use std::collections::HashMap;
896 let mut headers = HashMap::new();
897 headers.insert("Prefer".to_string(), "return=minimal".to_string());
898
899 let body = r#"[{"name": "Alice"}, {"name": "Bob"}]"#;
900 let op = parse("POST", "users", "", Some(body), Some(&headers)).unwrap();
901
902 match op {
903 Operation::Insert(_, Some(prefer)) => {
904 assert_eq!(
905 prefer.return_representation,
906 Some(ReturnRepresentation::Minimal)
907 );
908 }
909 _ => panic!("Expected Insert with minimal return"),
910 }
911 }
912
913 #[test]
914 fn test_upsert_with_merge_duplicates() {
915 use std::collections::HashMap;
917 let mut headers = HashMap::new();
918 headers.insert(
919 "Prefer".to_string(),
920 "resolution=merge-duplicates".to_string(),
921 );
922
923 let body = r#"{"user_id": 123, "theme": "dark"}"#;
924 let op = parse(
925 "POST",
926 "preferences",
927 "on_conflict=user_id",
928 Some(body),
929 Some(&headers),
930 )
931 .unwrap();
932
933 match op {
934 Operation::Insert(params, Some(prefer)) => {
935 assert_eq!(prefer.resolution, Some(Resolution::MergeDuplicates));
936 assert!(params.on_conflict.is_some());
937 }
938 _ => panic!("Expected Insert with resolution preference"),
939 }
940 }
941
942 #[test]
943 fn test_select_with_count_exact() {
944 use std::collections::HashMap;
946 let mut headers = HashMap::new();
947 headers.insert("Prefer".to_string(), "count=exact".to_string());
948
949 let op = parse("GET", "users", "limit=10&offset=0", None, Some(&headers)).unwrap();
950
951 match op {
952 Operation::Select(_, Some(prefer)) => {
953 assert_eq!(prefer.count, Some(Count::Exact));
954 }
955 _ => panic!("Expected Select with count"),
956 }
957 }
958
959 #[test]
960 fn test_multiple_prefer_options() {
961 use std::collections::HashMap;
963 let mut headers = HashMap::new();
964 headers.insert(
965 "Prefer".to_string(),
966 "return=representation, missing=default, plurality=singular".to_string(),
967 );
968
969 let body = r#"{"name": "Bob"}"#;
970 let op = parse("POST", "users", "", Some(body), Some(&headers)).unwrap();
971
972 match op {
973 Operation::Insert(_, Some(prefer)) => {
974 assert_eq!(
975 prefer.return_representation,
976 Some(ReturnRepresentation::Full)
977 );
978 assert_eq!(prefer.missing, Some(Missing::Default));
979 assert_eq!(prefer.plurality, Some(Plurality::Singular));
980 }
981 _ => panic!("Expected Insert with multiple preferences"),
982 }
983 }
984
985 #[test]
986 fn test_update_with_prefer_headers() {
987 use std::collections::HashMap;
989 let mut headers = HashMap::new();
990 headers.insert("Prefer".to_string(), "return=representation".to_string());
991
992 let body = r#"{"status": "active"}"#;
993 let op = parse("PATCH", "users", "id=eq.123", Some(body), Some(&headers)).unwrap();
994
995 match op {
996 Operation::Update(_, Some(prefer)) => {
997 assert_eq!(
998 prefer.return_representation,
999 Some(ReturnRepresentation::Full)
1000 );
1001 }
1002 _ => panic!("Expected Update with Prefer"),
1003 }
1004 }
1005
1006 #[test]
1007 fn test_delete_with_prefer_headers() {
1008 use std::collections::HashMap;
1010 let mut headers = HashMap::new();
1011 headers.insert("Prefer".to_string(), "return=headers-only".to_string());
1012
1013 let op = parse("DELETE", "users", "status=eq.deleted", None, Some(&headers)).unwrap();
1014
1015 match op {
1016 Operation::Delete(_, Some(prefer)) => {
1017 assert_eq!(
1018 prefer.return_representation,
1019 Some(ReturnRepresentation::HeadersOnly)
1020 );
1021 }
1022 _ => panic!("Expected Delete with Prefer"),
1023 }
1024 }
1025
1026 #[test]
1027 fn test_prefer_header_case_insensitive() {
1028 use std::collections::HashMap;
1030 let mut headers = HashMap::new();
1031 headers.insert("prefer".to_string(), "count=exact".to_string());
1032
1033 let op = parse("GET", "users", "", None, Some(&headers)).unwrap();
1034
1035 match op {
1036 Operation::Select(_, Some(prefer)) => {
1037 assert_eq!(prefer.count, Some(Count::Exact));
1038 }
1039 _ => panic!("Expected Select with Prefer"),
1040 }
1041 }
1042
1043 #[test]
1044 fn test_no_prefer_headers() {
1045 let op = parse("GET", "users", "id=eq.123", None, None).unwrap();
1047
1048 match op {
1049 Operation::Select(_, prefer) => {
1050 assert!(prefer.is_none());
1051 }
1052 _ => panic!("Expected Select without Prefer"),
1053 }
1054 }
1055
1056 #[test]
1057 fn test_prefer_with_schema_headers() {
1058 use std::collections::HashMap;
1060 let mut headers = HashMap::new();
1061 headers.insert("Prefer".to_string(), "return=representation".to_string());
1062 headers.insert("Content-Profile".to_string(), "auth".to_string());
1063
1064 let body = r#"{"email": "alice@example.com"}"#;
1065 let op = parse("POST", "users", "", Some(body), Some(&headers)).unwrap();
1066
1067 match op {
1068 Operation::Insert(_, Some(prefer)) => {
1069 assert_eq!(
1070 prefer.return_representation,
1071 Some(ReturnRepresentation::Full)
1072 );
1073 }
1074 _ => panic!("Expected Insert with both Prefer and schema headers"),
1075 }
1076 }
1077
1078 #[test]
1081 fn test_rpc_post_with_args() {
1082 let body = r#"{"user_id": 123, "status": "active"}"#;
1084 let op = parse("POST", "rpc/get_user_posts", "", Some(body), None).unwrap();
1085
1086 match op {
1087 Operation::Rpc(params, prefer) => {
1088 assert_eq!(params.function_name, "get_user_posts");
1089 assert_eq!(params.args.len(), 2);
1090 assert!(prefer.is_none());
1091 }
1092 _ => panic!("Expected RPC operation"),
1093 }
1094 }
1095
1096 #[test]
1097 fn test_rpc_get_no_args() {
1098 let op = parse("GET", "rpc/health_check", "", None, None).unwrap();
1100
1101 match op {
1102 Operation::Rpc(params, _) => {
1103 assert_eq!(params.function_name, "health_check");
1104 assert!(params.args.is_empty());
1105 }
1106 _ => panic!("Expected RPC operation"),
1107 }
1108 }
1109
1110 #[test]
1111 fn test_rpc_with_filters() {
1112 let body = r#"{"department_id": 5}"#;
1114 let query = "age=gte.25&salary=lt.100000";
1115 let op = parse("POST", "rpc/find_employees", query, Some(body), None).unwrap();
1116
1117 match op {
1118 Operation::Rpc(params, _) => {
1119 assert_eq!(params.function_name, "find_employees");
1120 assert_eq!(params.filters.len(), 2);
1121 }
1122 _ => panic!("Expected RPC operation"),
1123 }
1124 }
1125
1126 #[test]
1127 fn test_rpc_with_order_limit() {
1128 let query = "order=created_at.desc&limit=10&offset=20";
1130 let op = parse("GET", "rpc/list_recent_posts", query, None, None).unwrap();
1131
1132 match op {
1133 Operation::Rpc(params, _) => {
1134 assert_eq!(params.function_name, "list_recent_posts");
1135 assert_eq!(params.order.len(), 1);
1136 assert_eq!(params.limit, Some(10));
1137 assert_eq!(params.offset, Some(20));
1138 }
1139 _ => panic!("Expected RPC operation"),
1140 }
1141 }
1142
1143 #[test]
1144 fn test_rpc_with_select() {
1145 let body = r#"{"search_term": "laptop"}"#;
1147 let query = "select=id,name,price";
1148 let op = parse("POST", "rpc/search_products", query, Some(body), None).unwrap();
1149
1150 match op {
1151 Operation::Rpc(params, _) => {
1152 assert_eq!(params.function_name, "search_products");
1153 assert!(params.returning.is_some());
1154 assert_eq!(params.returning.unwrap().len(), 3);
1155 }
1156 _ => panic!("Expected RPC operation"),
1157 }
1158 }
1159
1160 #[test]
1161 fn test_rpc_with_prefer_headers() {
1162 use std::collections::HashMap;
1164 let mut headers = HashMap::new();
1165 headers.insert("Prefer".to_string(), "return=representation".to_string());
1166
1167 let body = r#"{"amount": 100.50}"#;
1168 let op = parse(
1169 "POST",
1170 "rpc/process_payment",
1171 "",
1172 Some(body),
1173 Some(&headers),
1174 )
1175 .unwrap();
1176
1177 match op {
1178 Operation::Rpc(params, Some(prefer)) => {
1179 assert_eq!(params.function_name, "process_payment");
1180 assert_eq!(
1181 prefer.return_representation,
1182 Some(ReturnRepresentation::Full)
1183 );
1184 }
1185 _ => panic!("Expected RPC operation with Prefer header"),
1186 }
1187 }
1188
1189 #[test]
1190 fn test_rpc_to_sql_simple() {
1191 let body = r#"{"user_id": 42}"#;
1193 let op = parse("POST", "rpc/get_profile", "", Some(body), None).unwrap();
1194 let result = operation_to_sql("rpc/get_profile", &op).unwrap();
1195
1196 assert!(result.query.contains(r#"FROM "public"."get_profile"("#));
1197 assert!(result.query.contains(r#""user_id" := $1"#));
1198 assert_eq!(result.params.len(), 1);
1199 }
1200
1201 #[test]
1202 fn test_rpc_to_sql_with_schema() {
1203 let body = r#"{"query": "test"}"#;
1205 let op = parse("POST", "rpc/api.search", "", Some(body), None).unwrap();
1206 let result = operation_to_sql("rpc/api.search", &op).unwrap();
1207
1208 assert!(result.query.contains(r#"FROM "api"."search"("#));
1209 }
1210
1211 #[test]
1212 fn test_rpc_to_sql_complex() {
1213 let body = r#"{"min_price": 100, "max_price": 1000}"#;
1215 let query = "category=eq.electronics&in_stock=eq.true&order=price.asc&limit=20&select=id,name,price";
1216 let op = parse("POST", "rpc/find_products", query, Some(body), None).unwrap();
1217 let result = operation_to_sql("rpc/find_products", &op).unwrap();
1218
1219 assert!(result.query.contains(r#"FROM "public"."find_products"("#));
1220 assert!(result.query.contains(r#""max_price" := $1"#));
1221 assert!(result.query.contains(r#""min_price" := $2"#));
1222 assert!(result.query.contains("WHERE"));
1223 assert!(result.query.contains("ORDER BY"));
1224 assert!(result.query.contains("LIMIT"));
1225 assert!(result.params.len() > 2);
1226 }
1227
1228 #[test]
1229 fn test_rpc_invalid_empty_function_name() {
1230 let result = parse("POST", "rpc/", "", None, None);
1232 assert!(result.is_err());
1233 }
1234
1235 #[test]
1236 fn test_rpc_get_with_query_params() {
1237 let query = "limit=5";
1240 let op = parse("GET", "rpc/get_stats", query, None, None).unwrap();
1241
1242 match op {
1243 Operation::Rpc(params, _) => {
1244 assert_eq!(params.function_name, "get_stats");
1245 assert_eq!(params.limit, Some(5));
1246 }
1247 _ => panic!("Expected RPC operation"),
1248 }
1249 }
1250
1251 #[test]
1254 fn test_insert_with_select_parameter() {
1255 let body = r#"{"email": "bob@example.com", "name": "Bob"}"#;
1257 let query = "select=id,email,created_at";
1258 let op = parse("POST", "users", query, Some(body), None).unwrap();
1259
1260 match op {
1261 Operation::Insert(params, _) => {
1262 assert!(params.returning.is_some());
1263 let returning = params.returning.unwrap();
1264 assert_eq!(returning.len(), 3);
1265 assert_eq!(returning[0].name, "id");
1266 assert_eq!(returning[1].name, "email");
1267 assert_eq!(returning[2].name, "created_at");
1268 }
1269 _ => panic!("Expected Insert with select"),
1270 }
1271 }
1272
1273 #[test]
1274 fn test_update_with_select_parameter() {
1275 let body = r#"{"status": "verified"}"#;
1277 let query = "id=eq.123&select=id,status,updated_at";
1278 let op = parse("PATCH", "users", query, Some(body), None).unwrap();
1279
1280 match op {
1281 Operation::Update(params, _) => {
1282 assert!(params.returning.is_some());
1283 let returning = params.returning.unwrap();
1284 assert_eq!(returning.len(), 3);
1285 }
1286 _ => panic!("Expected Update with select"),
1287 }
1288 }
1289
1290 #[test]
1291 fn test_delete_with_select_parameter() {
1292 let query = "status=eq.inactive&select=id,email";
1294 let op = parse("DELETE", "users", query, None, None).unwrap();
1295
1296 match op {
1297 Operation::Delete(params, _) => {
1298 assert!(params.returning.is_some());
1299 let returning = params.returning.unwrap();
1300 assert_eq!(returning.len(), 2);
1301 }
1302 _ => panic!("Expected Delete with select"),
1303 }
1304 }
1305
1306 #[test]
1307 fn test_insert_with_returning_backwards_compat() {
1308 let body = r#"{"email": "alice@example.com"}"#;
1310 let query = "returning=id,created_at";
1311 let op = parse("POST", "users", query, Some(body), None).unwrap();
1312
1313 match op {
1314 Operation::Insert(params, _) => {
1315 assert!(params.returning.is_some());
1316 assert_eq!(params.returning.unwrap().len(), 2);
1317 }
1318 _ => panic!("Expected Insert with returning"),
1319 }
1320 }
1321
1322 #[test]
1323 fn test_select_takes_precedence_over_returning() {
1324 let body = r#"{"email": "test@example.com"}"#;
1326 let query = "select=id&returning=id,email,name";
1327 let op = parse("POST", "users", query, Some(body), None).unwrap();
1328
1329 match op {
1330 Operation::Insert(params, _) => {
1331 assert!(params.returning.is_some());
1332 let returning = params.returning.unwrap();
1333 assert_eq!(returning.len(), 1);
1335 assert_eq!(returning[0].name, "id");
1336 }
1337 _ => panic!("Expected Insert"),
1338 }
1339 }
1340
1341 #[test]
1342 fn test_mutation_select_to_sql() {
1343 let body = r#"{"name": "New Product", "price": 99.99}"#;
1345 let query = "select=id,name,created_at";
1346 let op = parse("POST", "products", query, Some(body), None).unwrap();
1347 let result = operation_to_sql("products", &op).unwrap();
1348
1349 assert!(result.query.contains("RETURNING"));
1350 assert!(result.query.contains(r#""id""#));
1351 assert!(result.query.contains(r#""name""#));
1352 assert!(result.query.contains(r#""created_at""#));
1353 }
1354
1355 #[test]
1358 fn test_put_upsert_basic() {
1359 let body = r#"{"email": "alice@example.com", "name": "Alice Updated"}"#;
1361 let query = "email=eq.alice@example.com";
1362 let op = parse("PUT", "users", query, Some(body), None).unwrap();
1363
1364 match op {
1365 Operation::Insert(params, _) => {
1366 assert!(params.on_conflict.is_some());
1367 let conflict = params.on_conflict.unwrap();
1368 assert_eq!(conflict.columns, vec!["email"]);
1369 assert_eq!(conflict.action, ConflictAction::DoUpdate);
1370 }
1371 _ => panic!("Expected Insert (upsert) operation"),
1372 }
1373 }
1374
1375 #[test]
1376 fn test_put_upsert_multiple_columns() {
1377 let body = r#"{"email": "bob@example.com", "team": "engineering", "role": "senior"}"#;
1379 let query = "email=eq.bob@example.com&team=eq.engineering";
1380 let op = parse("PUT", "users", query, Some(body), None).unwrap();
1381
1382 match op {
1383 Operation::Insert(params, _) => {
1384 assert!(params.on_conflict.is_some());
1385 let conflict = params.on_conflict.unwrap();
1386 assert_eq!(conflict.columns.len(), 2);
1387 assert!(conflict.columns.contains(&"email".to_string()));
1388 assert!(conflict.columns.contains(&"team".to_string()));
1389 }
1390 _ => panic!("Expected Insert with multi-column conflict"),
1391 }
1392 }
1393
1394 #[test]
1395 fn test_put_with_explicit_on_conflict() {
1396 let body = r#"{"id": 123, "name": "Test"}"#;
1398 let query = "id=eq.123&on_conflict=id";
1399 let op = parse("PUT", "items", query, Some(body), None).unwrap();
1400
1401 match op {
1402 Operation::Insert(params, _) => {
1403 assert!(params.on_conflict.is_some());
1404 let conflict = params.on_conflict.unwrap();
1406 assert_eq!(conflict.columns, vec!["id"]);
1407 }
1408 _ => panic!("Expected Insert"),
1409 }
1410 }
1411
1412 #[test]
1413 fn test_put_without_filters() {
1414 let body = r#"{"name": "New Item"}"#;
1416 let op = parse("PUT", "items", "", Some(body), None).unwrap();
1417
1418 match op {
1419 Operation::Insert(params, _) => {
1420 assert!(params.on_conflict.is_none());
1422 }
1423 _ => panic!("Expected Insert"),
1424 }
1425 }
1426
1427 #[test]
1428 fn test_put_to_sql() {
1429 let body = r#"{"email": "test@example.com", "name": "Test User"}"#;
1431 let query = "email=eq.test@example.com&select=id,email,name";
1432 let op = parse("PUT", "users", query, Some(body), None).unwrap();
1433 let result = operation_to_sql("users", &op).unwrap();
1434
1435 assert!(result.query.contains("INSERT INTO"));
1436 assert!(result.query.contains("ON CONFLICT"));
1437 assert!(result.query.contains("DO UPDATE SET"));
1438 assert!(result.query.contains("RETURNING"));
1439 }
1440
1441 #[test]
1442 fn test_put_requires_body() {
1443 let result = parse("PUT", "users", "id=eq.123", None, None);
1445 assert!(result.is_err());
1446 }
1447
1448 #[test]
1451 fn test_on_conflict_with_where_clause() {
1452 use crate::parser::parse_filter;
1454
1455 let body = r#"{"email": "alice@example.com", "name": "Alice"}"#;
1456 let mut params = parse_insert_params("", body).unwrap();
1457
1458 let filter = parse_filter("deleted_at", "is.null").unwrap();
1460 let conflict = OnConflict::do_update(vec!["email".to_string()])
1461 .with_where_clause(vec![LogicCondition::Filter(filter)]);
1462
1463 params = params.with_on_conflict(conflict);
1464 let op = Operation::Insert(params, None);
1465 let result = operation_to_sql("users", &op).unwrap();
1466
1467 assert!(result.query.contains("ON CONFLICT"));
1468 assert!(result.query.contains(r#"("email")"#));
1469 assert!(result.query.contains("WHERE"));
1470 assert!(result.query.contains("deleted_at"));
1471 }
1472
1473 #[test]
1474 fn test_on_conflict_with_specific_update_columns() {
1475 let body = r#"{"email": "bob@example.com", "name": "Bob", "role": "admin"}"#;
1477 let mut params = parse_insert_params("", body).unwrap();
1478
1479 let conflict = OnConflict::do_update(vec!["email".to_string()])
1481 .with_update_columns(vec!["name".to_string()]);
1482
1483 params = params.with_on_conflict(conflict);
1484 let op = Operation::Insert(params, None);
1485 let result = operation_to_sql("users", &op).unwrap();
1486
1487 assert!(result.query.contains("ON CONFLICT"));
1488 assert!(result.query.contains(r#""name" = EXCLUDED."name""#));
1489 assert!(!result.query.contains(r#""role" = EXCLUDED."role""#));
1491 }
1492
1493 #[test]
1494 fn test_on_conflict_complex() {
1495 use crate::parser::parse_filter;
1497
1498 let body = r#"{"user_id": 123, "post_id": 456, "reaction": "like"}"#;
1499 let mut params = parse_insert_params("", body).unwrap();
1500
1501 let filter = parse_filter("deleted_at", "is.null").unwrap();
1502 let conflict = OnConflict::do_update(vec!["user_id".to_string(), "post_id".to_string()])
1503 .with_where_clause(vec![LogicCondition::Filter(filter)])
1504 .with_update_columns(vec!["reaction".to_string()]);
1505
1506 params = params.with_on_conflict(conflict);
1507 let op = Operation::Insert(params, None);
1508 let result = operation_to_sql("reactions", &op).unwrap();
1509
1510 println!("SQL: {}", result.query);
1512 assert!(
1513 result
1514 .query
1515 .contains(r#"ON CONFLICT ("post_id", "user_id")"#)
1516 || result
1517 .query
1518 .contains(r#"ON CONFLICT ("user_id", "post_id")"#)
1519 );
1520 assert!(result.query.contains("WHERE"));
1521 assert!(result.query.contains(r#""reaction" = EXCLUDED."reaction""#));
1522 }
1523
1524 #[test]
1527 fn test_ecommerce_workflow() {
1528 use std::collections::HashMap;
1529
1530 let body = r#"[
1532 {"product_id": 1, "quantity": 2, "price": 29.99},
1533 {"product_id": 3, "quantity": 1, "price": 49.99}
1534 ]"#;
1535 let mut headers = HashMap::new();
1536 headers.insert("Prefer".to_string(), "return=representation".to_string());
1537 headers.insert("Content-Profile".to_string(), "sales".to_string());
1538
1539 let op = parse(
1540 "POST",
1541 "order_items",
1542 "select=*",
1543 Some(body),
1544 Some(&headers),
1545 )
1546 .unwrap();
1547 match op {
1548 Operation::Insert(params, Some(prefer)) => {
1549 assert_eq!(
1550 prefer.return_representation,
1551 Some(ReturnRepresentation::Full)
1552 );
1553 assert!(params.returning.is_some());
1554 }
1555 _ => panic!("Expected Insert with Prefer"),
1556 }
1557
1558 let body = r#"{"status": "shipped", "shipped_at": "2024-01-15"}"#;
1560 let op = parse(
1561 "PATCH",
1562 "orders",
1563 "id=eq.123&select=id,status,shipped_at",
1564 Some(body),
1565 None,
1566 )
1567 .unwrap();
1568 match op {
1569 Operation::Update(params, _) => {
1570 assert!(params.has_filters());
1571 assert!(params.returning.is_some());
1572 }
1573 _ => panic!("Expected Update"),
1574 }
1575
1576 let body = r#"{"order_id": 123}"#;
1578 let op = parse("POST", "rpc/calculate_order_total", "", Some(body), None).unwrap();
1579 match op {
1580 Operation::Rpc(params, _) => {
1581 assert_eq!(params.function_name, "calculate_order_total");
1582 }
1583 _ => panic!("Expected RPC"),
1584 }
1585 }
1586
1587 #[test]
1588 fn test_social_media_workflow() {
1589 use std::collections::HashMap;
1590
1591 let body = r#"{"content": "Hello World!", "user_id": 456}"#;
1593 let mut headers = HashMap::new();
1594 headers.insert("Prefer".to_string(), "return=representation".to_string());
1595
1596 let op = parse(
1597 "POST",
1598 "posts",
1599 "select=id,content,user_id",
1600 Some(body),
1601 Some(&headers),
1602 )
1603 .unwrap();
1604 match op {
1605 Operation::Insert(_, Some(prefer)) => {
1606 assert_eq!(
1607 prefer.return_representation,
1608 Some(ReturnRepresentation::Full)
1609 );
1610 }
1611 _ => panic!("Expected Insert"),
1612 }
1613
1614 let body = r#"{"user_id": 789, "post_id": 123}"#;
1616 let op = parse(
1617 "PUT",
1618 "likes",
1619 "user_id=eq.789&post_id=eq.123",
1620 Some(body),
1621 None,
1622 )
1623 .unwrap();
1624 match op {
1625 Operation::Insert(params, _) => {
1626 assert!(params.on_conflict.is_some());
1627 }
1628 _ => panic!("Expected upsert"),
1629 }
1630
1631 let op = parse(
1633 "DELETE",
1634 "posts",
1635 "created_at=lt.2020-01-01&order=created_at.asc&limit=100",
1636 None,
1637 None,
1638 )
1639 .unwrap();
1640 match op {
1641 Operation::Delete(params, _) => {
1642 assert!(params.has_filters());
1643 assert_eq!(params.limit, Some(100));
1644 }
1645 _ => panic!("Expected Delete"),
1646 }
1647 }
1648
1649 #[test]
1650 fn test_analytics_workflow() {
1651 use std::collections::HashMap;
1652
1653 let body = r#"[
1655 {"metric": "pageviews", "value": 1234, "date": "2024-01-15"},
1656 {"metric": "signups", "value": 56, "date": "2024-01-15"}
1657 ]"#;
1658 let mut headers = HashMap::new();
1659 headers.insert(
1660 "Prefer".to_string(),
1661 "resolution=merge-duplicates".to_string(),
1662 );
1663
1664 let op = parse(
1665 "POST",
1666 "metrics",
1667 "on_conflict=metric,date",
1668 Some(body),
1669 Some(&headers),
1670 )
1671 .unwrap();
1672 match op {
1673 Operation::Insert(params, Some(prefer)) => {
1674 assert!(params.on_conflict.is_some());
1675 assert_eq!(prefer.resolution, Some(Resolution::MergeDuplicates));
1676 }
1677 _ => panic!("Expected Insert with resolution"),
1678 }
1679
1680 let body = r#"{"start_date": "2024-01-01", "end_date": "2024-01-31"}"#;
1682 let op = parse(
1683 "POST",
1684 "rpc/get_monthly_stats",
1685 "metric=eq.pageviews",
1686 Some(body),
1687 None,
1688 )
1689 .unwrap();
1690 match op {
1691 Operation::Rpc(params, _) => {
1692 assert_eq!(params.function_name, "get_monthly_stats");
1693 assert!(!params.filters.is_empty());
1694 }
1695 _ => panic!("Expected RPC with filters"),
1696 }
1697
1698 let mut headers = HashMap::new();
1700 headers.insert("Prefer".to_string(), "count=exact".to_string());
1701
1702 let op = parse(
1703 "GET",
1704 "events",
1705 "created_at=gte.2024-01-01",
1706 None,
1707 Some(&headers),
1708 )
1709 .unwrap();
1710 match op {
1711 Operation::Select(_, Some(prefer)) => {
1712 assert_eq!(prefer.count, Some(Count::Exact));
1713 }
1714 _ => panic!("Expected Select with count"),
1715 }
1716 }
1717
1718 #[test]
1721 fn test_embedding_many_to_one_via_fk() {
1722 let result = query_string_to_sql("posts", "select=*,profiles(username,avatar_url)");
1723 assert!(result.is_ok());
1724 let query = result.unwrap();
1725 assert!(query.query.contains("SELECT"));
1726 assert!(query.query.contains("profiles"));
1727 assert!(
1729 !query.query.contains("row_to_json(profiles.\"username\""),
1730 "row_to_json must not receive individual columns: {}",
1731 query.query
1732 );
1733 assert!(
1734 query.query.contains("row_to_json("),
1735 "should use row_to_json with a subquery record: {}",
1736 query.query
1737 );
1738 }
1739
1740 #[test]
1741 fn test_embedding_one_to_many() {
1742 let result = query_string_to_sql("posts", "select=title,comments(id,body)");
1743 assert!(result.is_ok());
1744 let query = result.unwrap();
1745 assert!(query.query.contains("\"title\""));
1746 assert!(query.query.contains("comments"));
1747 assert!(
1749 !query.query.contains("row_to_json(comments.\"id\""),
1750 "row_to_json must not receive individual columns: {}",
1751 query.query
1752 );
1753 }
1754
1755 #[test]
1756 fn test_embedding_select_star_produces_valid_row_to_json() {
1757 let result = query_string_to_sql("posts", "select=*,comments(*)");
1758 assert!(result.is_ok());
1759 let query = result.unwrap();
1760 assert!(
1761 query.query.contains("row_to_json("),
1762 "should use row_to_json: {}",
1763 query.query
1764 );
1765 }
1766
1767 #[test]
1768 fn test_embedding_nested_produces_valid_sql() {
1769 let result = query_string_to_sql(
1770 "posts",
1771 "select=id,comments(id,body,author:profiles(name,avatar_url))",
1772 );
1773 assert!(result.is_ok());
1774 let query = result.unwrap();
1775 assert!(
1776 !query.query.contains("row_to_json(profiles.\"name\""),
1777 "nested row_to_json must not receive individual columns: {}",
1778 query.query
1779 );
1780 }
1781
1782 #[test]
1783 fn test_embedding_aliased_relation() {
1784 let params = parse_query_string("select=*,author:profiles(name)").unwrap();
1786 let select = params.select.as_ref().unwrap();
1787 let relation = &select[1];
1788 assert_eq!(relation.name, "profiles");
1789 assert_eq!(relation.alias, Some("author".to_string()));
1790 assert_eq!(relation.item_type, ItemType::Relation);
1791 }
1792
1793 #[test]
1794 fn test_embedding_nested_with_alias() {
1795 let params = parse_query_string("select=*,comments(id,author:profiles(name))").unwrap();
1797 let select = params.select.as_ref().unwrap();
1798 let comments = &select[1];
1799 assert_eq!(comments.name, "comments");
1800 let children = comments.children.as_ref().unwrap();
1801 assert_eq!(children[1].name, "profiles");
1802 assert_eq!(children[1].alias, Some("author".to_string()));
1803 assert_eq!(children[1].item_type, ItemType::Relation);
1804 let nested = children[1].children.as_ref().unwrap();
1805 assert_eq!(nested[0].name, "name");
1806 }
1807
1808 #[test]
1809 fn test_embedding_fk_hint_disambiguation() {
1810 let params = parse_query_string("select=*,author:profiles!author_id_fkey(name)").unwrap();
1812 let select = params.select.as_ref().unwrap();
1813 let relation = &select[1];
1814 assert_eq!(relation.name, "profiles");
1815 assert_eq!(relation.alias, Some("author".to_string()));
1816 assert!(relation.hint.is_some());
1817 assert_eq!(
1818 relation.hint,
1819 Some(ItemHint::Inner("author_id_fkey".to_string()))
1820 );
1821 }
1822
1823 #[test]
1824 fn test_embedding_with_filters_and_ordering() {
1825 let query_str = "select=id,title,author:profiles(name,avatar_url),comments(id,body)&status=eq.published&order=created_at.desc&limit=10";
1827 let params = parse_query_string(query_str).unwrap();
1828
1829 assert!(params.has_select());
1830 let select = params.select.as_ref().unwrap();
1831 assert_eq!(select.len(), 4); assert_eq!(select[2].alias, Some("author".to_string()));
1833 assert_eq!(select[3].name, "comments");
1834
1835 assert!(params.has_filters());
1836 assert_eq!(params.order.len(), 1);
1837 assert_eq!(params.limit, Some(10));
1838 }
1839
1840 #[test]
1841 fn test_embedding_supabase_blog_example() {
1842 let query_str = "select=id,title,content,author:profiles!author_id_fkey(name,avatar_url),comments(id,body,created_at,commenter:profiles!commenter_id_fkey(name))&published=eq.true&order=created_at.desc&limit=20";
1844 let params = parse_query_string(query_str).unwrap();
1845
1846 let select = params.select.as_ref().unwrap();
1847 assert_eq!(select.len(), 5); let author = &select[3];
1851 assert_eq!(author.name, "profiles");
1852 assert_eq!(author.alias, Some("author".to_string()));
1853 assert_eq!(
1854 author.hint,
1855 Some(ItemHint::Inner("author_id_fkey".to_string()))
1856 );
1857
1858 let comments = &select[4];
1860 assert_eq!(comments.name, "comments");
1861 let comment_children = comments.children.as_ref().unwrap();
1862 assert_eq!(comment_children.len(), 4); let commenter = &comment_children[3];
1865 assert_eq!(commenter.name, "profiles");
1866 assert_eq!(commenter.alias, Some("commenter".to_string()));
1867 assert_eq!(
1868 commenter.hint,
1869 Some(ItemHint::Inner("commenter_id_fkey".to_string()))
1870 );
1871 }
1872
1873 #[test]
1874 fn test_100_percent_parity_demonstration() {
1875 use std::collections::HashMap;
1877
1878 let body = r#"{"email": "test@example.com"}"#;
1880 assert!(parse("POST", "users", "", Some(body), None).is_ok());
1881 assert!(parse("PUT", "users", "id=eq.1", Some(body), None).is_ok());
1882 assert!(parse("PATCH", "users", "id=eq.1", Some(body), None).is_ok());
1883 assert!(parse("DELETE", "users", "id=eq.1", None, None).is_ok());
1884
1885 assert!(parse("POST", "rpc/my_function", "", Some(body), None).is_ok());
1887 assert!(parse("GET", "rpc/my_function", "", None, None).is_ok());
1888
1889 let mut headers = HashMap::new();
1891 headers.insert(
1892 "Prefer".to_string(),
1893 "return=representation, count=exact, resolution=merge-duplicates, plurality=singular, missing=default".to_string(),
1894 );
1895 let op = parse("GET", "users", "", None, Some(&headers)).unwrap();
1896 match op {
1897 Operation::Select(_, Some(prefer)) => {
1898 assert_eq!(
1899 prefer.return_representation,
1900 Some(ReturnRepresentation::Full)
1901 );
1902 assert_eq!(prefer.count, Some(Count::Exact));
1903 assert_eq!(prefer.resolution, Some(Resolution::MergeDuplicates));
1904 assert_eq!(prefer.plurality, Some(Plurality::Singular));
1905 assert_eq!(prefer.missing, Some(Missing::Default));
1906 }
1907 _ => panic!("Expected all prefer options"),
1908 }
1909
1910 let mut headers = HashMap::new();
1912 headers.insert("Accept-Profile".to_string(), "api".to_string());
1913 assert!(parse("GET", "users", "", None, Some(&headers)).is_ok());
1914
1915 assert!(parse(
1917 "GET",
1918 "users",
1919 "age=gte.18&status=in.(active,verified)&order=created_at.desc&limit=10&offset=20&select=id,name",
1920 None,
1921 None
1922 )
1923 .is_ok());
1924
1925 assert!(parse("POST", "users", "on_conflict=email", Some(body), None).is_ok());
1927
1928 println!("✅ 100% PostgREST Parity Achieved!");
1929 }
1930}