franken_snowflake_sqlapi/
request.rs1use std::collections::BTreeMap;
10
11use franken_snowflake_core::ids::{DatabaseName, RoleName, SchemaName, WarehouseName};
12use serde::{Deserialize, Serialize};
13
14#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
21#[serde(rename_all = "camelCase")]
22pub struct SubmitStatementRequest {
23 pub statement: String,
26
27 #[serde(skip_serializing_if = "Option::is_none", default)]
30 pub timeout: Option<u32>,
31
32 #[serde(skip_serializing_if = "Option::is_none", default)]
34 pub database: Option<DatabaseName>,
35
36 #[serde(skip_serializing_if = "Option::is_none", default)]
38 pub schema: Option<SchemaName>,
39
40 #[serde(skip_serializing_if = "Option::is_none", default)]
42 pub warehouse: Option<WarehouseName>,
43
44 #[serde(skip_serializing_if = "Option::is_none", default)]
46 pub role: Option<RoleName>,
47
48 #[serde(skip_serializing_if = "Option::is_none", default)]
50 pub bindings: Option<BTreeMap<String, Binding>>,
51
52 #[serde(skip_serializing_if = "Option::is_none", default)]
56 pub parameters: Option<BTreeMap<String, String>>,
57}
58
59impl SubmitStatementRequest {
60 #[must_use]
62 pub fn new(statement: impl Into<String>) -> Self {
63 Self {
64 statement: statement.into(),
65 timeout: None,
66 database: None,
67 schema: None,
68 warehouse: None,
69 role: None,
70 bindings: None,
71 parameters: None,
72 }
73 }
74}
75
76#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
84pub struct Binding {
85 #[serde(rename = "type")]
88 pub value_type: String,
89 pub value: String,
91}
92
93impl Binding {
94 #[must_use]
96 pub fn new(value_type: impl Into<String>, value: impl Into<String>) -> Self {
97 Self {
98 value_type: value_type.into(),
99 value: value.into(),
100 }
101 }
102}
103
104pub mod bind_type {
106 pub const TEXT: &str = "TEXT";
108 pub const FIXED: &str = "FIXED";
110 pub const REAL: &str = "REAL";
112 pub const BOOLEAN: &str = "BOOLEAN";
114 pub const DATE: &str = "DATE";
116 pub const TIME: &str = "TIME";
118 pub const TIMESTAMP_NTZ: &str = "TIMESTAMP_NTZ";
120 pub const TIMESTAMP_LTZ: &str = "TIMESTAMP_LTZ";
122 pub const TIMESTAMP_TZ: &str = "TIMESTAMP_TZ";
124 pub const BINARY: &str = "BINARY";
126}
127
128#[derive(Clone, Debug, PartialEq, Eq, Default)]
134pub struct SubmitQueryParams {
135 pub request_id: Option<String>,
137 pub retry: bool,
139 pub asynchronous: bool,
141 pub nullable: Option<bool>,
144}
145
146impl SubmitQueryParams {
147 #[must_use]
150 pub fn to_query_pairs(&self) -> Vec<(&'static str, String)> {
151 let mut pairs = Vec::new();
152 if let Some(id) = &self.request_id {
153 pairs.push(("requestId", id.clone()));
154 }
155 if self.retry {
156 pairs.push(("retry", "true".to_owned()));
157 }
158 if self.asynchronous {
159 pairs.push(("async", "true".to_owned()));
160 }
161 if let Some(nullable) = self.nullable {
162 pairs.push(("nullable", nullable.to_string()));
163 }
164 pairs
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn submit_request_minimal_serializes_cleanly() -> Result<(), serde_json::Error> {
174 let req = SubmitStatementRequest::new("SELECT 1");
175 let json = serde_json::to_string(&req)?;
176 assert_eq!(json, r#"{"statement":"SELECT 1"}"#);
177
178 let roundtrip: SubmitStatementRequest = serde_json::from_str(&json)?;
179 assert_eq!(roundtrip, req);
180 Ok(())
181 }
182
183 #[test]
184 fn submit_request_all_fields_roundtrip() -> Result<(), serde_json::Error> {
185 let mut bindings = BTreeMap::new();
186 bindings.insert("1".to_string(), Binding::new(bind_type::FIXED, "42"));
187 bindings.insert("2".to_string(), Binding::new(bind_type::TEXT, "hello"));
188
189 let mut parameters = BTreeMap::new();
190 parameters.insert("MULTI_STATEMENT_COUNT".to_string(), "1".to_string());
191 parameters.insert("TIMEZONE".to_string(), "UTC".to_string());
192
193 let req = SubmitStatementRequest {
194 statement: "SELECT ? as id, ? as greeting".to_string(),
195 timeout: Some(30),
196 database: Some(DatabaseName::new("MY_DB")),
197 schema: Some(SchemaName::new("PUBLIC")),
198 warehouse: Some(WarehouseName::new("COMPUTE_WH")),
199 role: Some(RoleName::new("ANALYST")),
200 bindings: Some(bindings),
201 parameters: Some(parameters),
202 };
203
204 let json = serde_json::to_string(&req)?;
205 assert!(json.contains(r#""statement":"SELECT ? as id, ? as greeting""#));
206 assert!(json.contains(r#""timeout":30"#));
207 assert!(json.contains(r#""database":"MY_DB""#));
208 assert!(json.contains(r#""schema":"PUBLIC""#));
209 assert!(json.contains(r#""warehouse":"COMPUTE_WH""#));
210 assert!(json.contains(r#""role":"ANALYST""#));
211 assert!(json.contains(
212 r#""bindings":{"1":{"type":"FIXED","value":"42"},"2":{"type":"TEXT","value":"hello"}}"#
213 ));
214 assert!(json.contains(r#""parameters":{"MULTI_STATEMENT_COUNT":"1","TIMEZONE":"UTC"}"#));
215
216 let roundtrip: SubmitStatementRequest = serde_json::from_str(&json)?;
217 assert_eq!(roundtrip, req);
218 Ok(())
219 }
220
221 #[test]
222 fn binding_constructors_and_types() -> Result<(), serde_json::Error> {
223 let b = Binding::new(bind_type::BOOLEAN, "true");
224 assert_eq!(b.value_type, "BOOLEAN");
225 assert_eq!(b.value, "true");
226
227 let json = serde_json::to_string(&b)?;
228 assert_eq!(json, r#"{"type":"BOOLEAN","value":"true"}"#);
229 let roundtrip: Binding = serde_json::from_str(&json)?;
230 assert_eq!(roundtrip, b);
231
232 assert_eq!(bind_type::TEXT, "TEXT");
233 assert_eq!(bind_type::FIXED, "FIXED");
234 assert_eq!(bind_type::REAL, "REAL");
235 assert_eq!(bind_type::DATE, "DATE");
236 assert_eq!(bind_type::TIME, "TIME");
237 assert_eq!(bind_type::TIMESTAMP_NTZ, "TIMESTAMP_NTZ");
238 assert_eq!(bind_type::TIMESTAMP_LTZ, "TIMESTAMP_LTZ");
239 assert_eq!(bind_type::TIMESTAMP_TZ, "TIMESTAMP_TZ");
240 assert_eq!(bind_type::BINARY, "BINARY");
241 Ok(())
242 }
243
244 #[test]
245 fn submit_query_params_pairs() {
246 let empty = SubmitQueryParams::default();
247 assert!(empty.to_query_pairs().is_empty());
248
249 let params = SubmitQueryParams {
250 request_id: Some("uuid-123".to_string()),
251 retry: true,
252 asynchronous: true,
253 nullable: Some(false),
254 };
255 let pairs = params.to_query_pairs();
256 assert_eq!(
257 pairs,
258 vec![
259 ("requestId", "uuid-123".to_string()),
260 ("retry", "true".to_string()),
261 ("async", "true".to_string()),
262 ("nullable", "false".to_string()),
263 ]
264 );
265
266 let nullable_true = SubmitQueryParams {
267 nullable: Some(true),
268 ..Default::default()
269 };
270 assert_eq!(
271 nullable_true.to_query_pairs(),
272 vec![("nullable", "true".to_string())]
273 );
274 }
275}