postgrest_parser/sql/
rpc.rs1use crate::ast::{ResolvedTable, RpcParams};
2use crate::error::SqlError;
3use crate::sql::{QueryBuilder, QueryResult};
4
5impl QueryBuilder {
6 pub fn build_rpc(
11 &mut self,
12 resolved_table: &ResolvedTable,
13 params: &RpcParams,
14 ) -> Result<QueryResult, SqlError> {
15 self.tables.push(params.function_name.clone());
16
17 if let Some(ref returning) = params.returning {
19 self.build_select_clause(returning)?;
20 } else {
21 self.sql.push_str("SELECT *");
22 }
23
24 self.sql.push_str(" FROM ");
26 self.sql.push_str(&format!(
27 "\"{}\".\"{}\"(",
28 resolved_table.schema, resolved_table.name
29 ));
30
31 if !params.args.is_empty() {
33 let mut sorted_args: Vec<(&String, &serde_json::Value)> = params.args.iter().collect();
34 sorted_args.sort_by_key(|(k, _)| *k);
35
36 for (i, (name, value)) in sorted_args.iter().enumerate() {
37 if i > 0 {
38 self.sql.push_str(", ");
39 }
40 let param_placeholder = self.add_param((*value).clone());
41 self.sql
42 .push_str(&format!("\"{}\" := {}", name, param_placeholder));
43 }
44 }
45
46 self.sql.push(')');
47
48 if !params.filters.is_empty() {
50 self.build_where_clause(¶ms.filters)?;
51 }
52
53 if !params.order.is_empty() {
55 self.build_order_clause(¶ms.order)?;
56 }
57
58 self.build_limit_offset(params.limit, params.offset)?;
60
61 Ok(QueryResult {
62 query: self.sql.clone(),
63 params: self.params.clone(),
64 tables: self.tables.clone(),
65 })
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use crate::ast::{LogicCondition, ResolvedTable, RpcParams};
73 use crate::parser::{parse_filter, parse_order};
74 use serde_json::Value;
75 use std::collections::HashMap;
76
77 #[test]
78 fn test_build_rpc_simple() {
79 let mut builder = QueryBuilder::new();
80 let resolved = ResolvedTable::new("public", "get_user_profile");
81
82 let mut args = HashMap::new();
83 args.insert("user_id".to_string(), Value::Number(123.into()));
84
85 let params = RpcParams::new("get_user_profile", args);
86
87 let result = builder.build_rpc(&resolved, ¶ms).unwrap();
88
89 assert_eq!(
90 result.query,
91 r#"SELECT * FROM "public"."get_user_profile"("user_id" := $1)"#
92 );
93 assert_eq!(result.params.len(), 1);
94 }
95
96 #[test]
97 fn test_build_rpc_no_args() {
98 let mut builder = QueryBuilder::new();
99 let resolved = ResolvedTable::new("api", "health_check");
100
101 let params = RpcParams::new("health_check", HashMap::new());
102
103 let result = builder.build_rpc(&resolved, ¶ms).unwrap();
104
105 assert_eq!(result.query, r#"SELECT * FROM "api"."health_check"()"#);
106 assert!(result.params.is_empty());
107 }
108
109 #[test]
110 fn test_build_rpc_multiple_args() {
111 let mut builder = QueryBuilder::new();
112 let resolved = ResolvedTable::new("public", "find_employees");
113
114 let mut args = HashMap::new();
115 args.insert("department".to_string(), Value::String("IT".to_string()));
116 args.insert("min_salary".to_string(), Value::Number(50000.into()));
117
118 let params = RpcParams::new("find_employees", args);
119
120 let result = builder.build_rpc(&resolved, ¶ms).unwrap();
121
122 assert!(result.query.contains(r#""department" := $1"#));
123 assert!(result.query.contains(r#""min_salary" := $2"#));
124 assert_eq!(result.params.len(), 2);
125 }
126
127 #[test]
128 fn test_build_rpc_with_filters() {
129 let mut builder = QueryBuilder::new();
130 let resolved = ResolvedTable::new("public", "get_recent_posts");
131
132 let filter = parse_filter("status", "eq.published").unwrap();
133 let params = RpcParams::new("get_recent_posts", HashMap::new())
134 .with_filters(vec![LogicCondition::Filter(filter)]);
135
136 let result = builder.build_rpc(&resolved, ¶ms).unwrap();
137
138 assert!(result.query.contains("WHERE"));
139 assert!(result.query.contains(r#""status" = $1"#));
140 }
141
142 #[test]
143 fn test_build_rpc_with_order() {
144 let mut builder = QueryBuilder::new();
145 let resolved = ResolvedTable::new("public", "get_posts");
146
147 let params = RpcParams::new("get_posts", HashMap::new())
148 .with_order(parse_order("created_at.desc").unwrap());
149
150 let result = builder.build_rpc(&resolved, ¶ms).unwrap();
151
152 assert!(result.query.contains("ORDER BY"));
153 assert!(result.query.contains(r#""created_at" DESC"#));
154 }
155
156 #[test]
157 fn test_build_rpc_with_limit_offset() {
158 let mut builder = QueryBuilder::new();
159 let resolved = ResolvedTable::new("public", "list_users");
160
161 let params = RpcParams::new("list_users", HashMap::new())
162 .with_limit(10)
163 .with_offset(20);
164
165 let result = builder.build_rpc(&resolved, ¶ms).unwrap();
166
167 assert!(result.query.contains("LIMIT $1"));
168 assert!(result.query.contains("OFFSET $2"));
169 assert_eq!(result.params.len(), 2);
170 }
171
172 #[test]
173 fn test_build_rpc_complex() {
174 let mut builder = QueryBuilder::new();
175 let resolved = ResolvedTable::new("api", "search_products");
176
177 let mut args = HashMap::new();
178 args.insert("query".to_string(), Value::String("laptop".to_string()));
179 args.insert("min_price".to_string(), Value::Number(500.into()));
180
181 let filter = parse_filter("in_stock", "eq.true").unwrap();
182 let params = RpcParams::new("search_products", args)
183 .with_filters(vec![LogicCondition::Filter(filter)])
184 .with_order(parse_order("price.asc").unwrap())
185 .with_limit(20);
186
187 let result = builder.build_rpc(&resolved, ¶ms).unwrap();
188
189 assert!(result.query.contains(r#"FROM "api"."search_products"("#));
190 assert!(result.query.contains("WHERE"));
191 assert!(result.query.contains("ORDER BY"));
192 assert!(result.query.contains("LIMIT"));
193 assert!(result.params.len() >= 3);
194 }
195}