Line data Source code
1 : use serde_json::Map;
2 : use serde_json::Value;
3 : use tokio_postgres::types::Kind;
4 : use tokio_postgres::types::Type;
5 : use tokio_postgres::Row;
6 :
7 : //
8 : // Convert json non-string types to strings, so that they can be passed to Postgres
9 : // as parameters.
10 : //
11 66 : pub fn json_to_pg_text(json: Vec<Value>) -> Vec<Option<String>> {
12 66 : json.iter()
13 66 : .map(|value| {
14 26 : match value {
15 : // special care for nulls
16 2 : Value::Null => None,
17 :
18 : // convert to text with escaping
19 12 : v @ (Value::Bool(_) | Value::Number(_) | Value::Object(_)) => Some(v.to_string()),
20 :
21 : // avoid escaping here, as we pass this as a parameter
22 2 : Value::String(s) => Some(s.to_string()),
23 :
24 : // special care for arrays
25 10 : Value::Array(_) => json_array_to_pg_array(value),
26 : }
27 66 : })
28 66 : .collect()
29 66 : }
30 :
31 : //
32 : // Serialize a JSON array to a Postgres array. Contrary to the strings in the params
33 : // in the array we need to escape the strings. Postgres is okay with arrays of form
34 : // '{1,"2",3}'::int[], so we don't check that array holds values of the same type, leaving
35 : // it for Postgres to check.
36 : //
37 : // Example of the same escaping in node-postgres: packages/pg/lib/utils.js
38 : //
39 62 : fn json_array_to_pg_array(value: &Value) -> Option<String> {
40 62 : match value {
41 : // special care for nulls
42 4 : Value::Null => None,
43 :
44 : // convert to text with escaping
45 : // here string needs to be escaped, as it is part of the array
46 35 : v @ (Value::Bool(_) | Value::Number(_) | Value::String(_)) => Some(v.to_string()),
47 7 : v @ Value::Object(_) => json_array_to_pg_array(&Value::String(v.to_string())),
48 :
49 : // recurse into array
50 16 : Value::Array(arr) => {
51 16 : let vals = arr
52 16 : .iter()
53 16 : .map(json_array_to_pg_array)
54 45 : .map(|v| v.unwrap_or_else(|| "NULL".to_string()))
55 16 : .collect::<Vec<_>>()
56 16 : .join(",");
57 16 :
58 16 : Some(format!("{{{}}}", vals))
59 : }
60 : }
61 62 : }
62 :
63 : //
64 : // Convert postgres row with text-encoded values to JSON object
65 : //
66 52 : pub fn pg_text_row_to_json(
67 52 : row: &Row,
68 52 : columns: &[Type],
69 52 : raw_output: bool,
70 52 : array_mode: bool,
71 52 : ) -> Result<Value, anyhow::Error> {
72 52 : let iter = row
73 52 : .columns()
74 52 : .iter()
75 52 : .zip(columns)
76 52 : .enumerate()
77 130 : .map(|(i, (column, typ))| {
78 130 : let name = column.name();
79 130 : let pg_value = row.as_text(i)?;
80 130 : let json_value = if raw_output {
81 6 : match pg_value {
82 6 : Some(v) => Value::String(v.to_string()),
83 0 : None => Value::Null,
84 : }
85 : } else {
86 124 : pg_text_to_json(pg_value, typ)?
87 : };
88 130 : Ok((name.to_string(), json_value))
89 130 : });
90 52 :
91 52 : if array_mode {
92 : // drop keys and aggregate into array
93 2 : let arr = iter
94 6 : .map(|r| r.map(|(_key, val)| val))
95 2 : .collect::<Result<Vec<Value>, anyhow::Error>>()?;
96 2 : Ok(Value::Array(arr))
97 : } else {
98 50 : let obj = iter.collect::<Result<Map<String, Value>, anyhow::Error>>()?;
99 50 : Ok(Value::Object(obj))
100 : }
101 52 : }
102 :
103 : //
104 : // Convert postgres text-encoded value to JSON value
105 : //
106 310 : fn pg_text_to_json(pg_value: Option<&str>, pg_type: &Type) -> Result<Value, anyhow::Error> {
107 310 : if let Some(val) = pg_value {
108 288 : if let Kind::Array(elem_type) = pg_type.kind() {
109 7 : return pg_array_parse(val, elem_type);
110 281 : }
111 281 :
112 281 : match *pg_type {
113 42 : Type::BOOL => Ok(Value::Bool(val == "t")),
114 : Type::INT2 | Type::INT4 => {
115 82 : let val = val.parse::<i32>()?;
116 82 : Ok(Value::Number(serde_json::Number::from(val)))
117 : }
118 : Type::FLOAT4 | Type::FLOAT8 => {
119 48 : let fval = val.parse::<f64>()?;
120 48 : let num = serde_json::Number::from_f64(fval);
121 48 : if let Some(num) = num {
122 30 : Ok(Value::Number(num))
123 : } else {
124 : // Pass Nan, Inf, -Inf as strings
125 : // JS JSON.stringify() does converts them to null, but we
126 : // want to preserve them, so we pass them as strings
127 18 : Ok(Value::String(val.to_string()))
128 : }
129 : }
130 19 : Type::JSON | Type::JSONB => Ok(serde_json::from_str(val)?),
131 90 : _ => Ok(Value::String(val.to_string())),
132 : }
133 : } else {
134 22 : Ok(Value::Null)
135 : }
136 310 : }
137 :
138 : //
139 : // Parse postgres array into JSON array.
140 : //
141 : // This is a bit involved because we need to handle nested arrays and quoted
142 : // values. Unlike postgres we don't check that all nested arrays have the same
143 : // dimensions, we just return them as is.
144 : //
145 51 : fn pg_array_parse(pg_array: &str, elem_type: &Type) -> Result<Value, anyhow::Error> {
146 51 : _pg_array_parse(pg_array, elem_type, false).map(|(v, _)| v)
147 51 : }
148 :
149 85 : fn _pg_array_parse(
150 85 : pg_array: &str,
151 85 : elem_type: &Type,
152 85 : nested: bool,
153 85 : ) -> Result<(Value, usize), anyhow::Error> {
154 85 : let mut pg_array_chr = pg_array.char_indices();
155 85 : let mut level = 0;
156 85 : let mut quote = false;
157 85 : let mut entries: Vec<Value> = Vec::new();
158 85 : let mut entry = String::new();
159 85 :
160 85 : // skip bounds decoration
161 85 : if let Some('[') = pg_array.chars().next() {
162 36 : for (_, c) in pg_array_chr.by_ref() {
163 36 : if c == '=' {
164 2 : break;
165 34 : }
166 : }
167 83 : }
168 :
169 198 : fn push_checked(
170 198 : entry: &mut String,
171 198 : entries: &mut Vec<Value>,
172 198 : elem_type: &Type,
173 198 : ) -> Result<(), anyhow::Error> {
174 198 : if !entry.is_empty() {
175 : // While in usual postgres response we get nulls as None and everything else
176 : // as Some(&str), in arrays we get NULL as unquoted 'NULL' string (while
177 : // string with value 'NULL' will be represented by '"NULL"'). So catch NULLs
178 : // here while we have quotation info and convert them to None.
179 131 : if entry == "NULL" {
180 14 : entries.push(pg_text_to_json(None, elem_type)?);
181 : } else {
182 117 : entries.push(pg_text_to_json(Some(entry), elem_type)?);
183 : }
184 131 : entry.clear();
185 67 : }
186 :
187 198 : Ok(())
188 198 : }
189 :
190 955 : while let Some((mut i, mut c)) = pg_array_chr.next() {
191 904 : let mut escaped = false;
192 904 :
193 904 : if c == '\\' {
194 34 : escaped = true;
195 34 : (i, c) = pg_array_chr.next().unwrap();
196 870 : }
197 :
198 481 : match c {
199 119 : '{' if !quote => {
200 119 : level += 1;
201 119 : if level > 1 {
202 34 : let (res, off) = _pg_array_parse(&pg_array[i..], elem_type, true)?;
203 34 : entries.push(res);
204 390 : for _ in 0..off - 1 {
205 390 : pg_array_chr.next();
206 390 : }
207 85 : }
208 : }
209 119 : '}' if !quote => {
210 119 : level -= 1;
211 119 : if level == 0 {
212 85 : push_checked(&mut entry, &mut entries, elem_type)?;
213 85 : if nested {
214 34 : return Ok((Value::Array(entries), i));
215 51 : }
216 34 : }
217 : }
218 66 : '"' if !escaped => {
219 66 : if quote {
220 : // end of quoted string, so push it manually without any checks
221 : // for emptiness or nulls
222 33 : entries.push(pg_text_to_json(Some(&entry), elem_type)?);
223 33 : entry.clear();
224 33 : }
225 66 : quote = !quote;
226 : }
227 113 : ',' if !quote => {
228 113 : push_checked(&mut entry, &mut entries, elem_type)?;
229 : }
230 487 : _ => {
231 487 : entry.push(c);
232 487 : }
233 : }
234 : }
235 :
236 51 : if level != 0 {
237 0 : return Err(anyhow::anyhow!("unbalanced array"));
238 51 : }
239 51 :
240 51 : Ok((Value::Array(entries), 0))
241 85 : }
242 :
243 : #[cfg(test)]
244 : mod tests {
245 : use super::*;
246 : use serde_json::json;
247 :
248 2 : #[test]
249 2 : fn test_atomic_types_to_pg_params() {
250 2 : let json = vec![Value::Bool(true), Value::Bool(false)];
251 2 : let pg_params = json_to_pg_text(json);
252 2 : assert_eq!(
253 2 : pg_params,
254 2 : vec![Some("true".to_owned()), Some("false".to_owned())]
255 2 : );
256 :
257 2 : let json = vec![Value::Number(serde_json::Number::from(42))];
258 2 : let pg_params = json_to_pg_text(json);
259 2 : assert_eq!(pg_params, vec![Some("42".to_owned())]);
260 :
261 2 : let json = vec![Value::String("foo\"".to_string())];
262 2 : let pg_params = json_to_pg_text(json);
263 2 : assert_eq!(pg_params, vec![Some("foo\"".to_owned())]);
264 :
265 2 : let json = vec![Value::Null];
266 2 : let pg_params = json_to_pg_text(json);
267 2 : assert_eq!(pg_params, vec![None]);
268 2 : }
269 :
270 2 : #[test]
271 2 : fn test_json_array_to_pg_array() {
272 2 : // atoms and escaping
273 2 : let json = "[true, false, null, \"NULL\", 42, \"foo\", \"bar\\\"-\\\\\"]";
274 2 : let json: Value = serde_json::from_str(json).unwrap();
275 2 : let pg_params = json_to_pg_text(vec![json]);
276 2 : assert_eq!(
277 2 : pg_params,
278 2 : vec![Some(
279 2 : "{true,false,NULL,\"NULL\",42,\"foo\",\"bar\\\"-\\\\\"}".to_owned()
280 2 : )]
281 2 : );
282 :
283 : // nested arrays
284 2 : let json = "[[true, false], [null, 42], [\"foo\", \"bar\\\"-\\\\\"]]";
285 2 : let json: Value = serde_json::from_str(json).unwrap();
286 2 : let pg_params = json_to_pg_text(vec![json]);
287 2 : assert_eq!(
288 2 : pg_params,
289 2 : vec![Some(
290 2 : "{{true,false},{NULL,42},{\"foo\",\"bar\\\"-\\\\\"}}".to_owned()
291 2 : )]
292 2 : );
293 : // array of objects
294 2 : let json = r#"[{"foo": 1},{"bar": 2}]"#;
295 2 : let json: Value = serde_json::from_str(json).unwrap();
296 2 : let pg_params = json_to_pg_text(vec![json]);
297 2 : assert_eq!(
298 2 : pg_params,
299 2 : vec![Some(r#"{"{\"foo\":1}","{\"bar\":2}"}"#.to_owned())]
300 2 : );
301 2 : }
302 :
303 2 : #[test]
304 2 : fn test_atomic_types_parse() {
305 2 : assert_eq!(
306 2 : pg_text_to_json(Some("foo"), &Type::TEXT).unwrap(),
307 2 : json!("foo")
308 2 : );
309 2 : assert_eq!(pg_text_to_json(None, &Type::TEXT).unwrap(), json!(null));
310 2 : assert_eq!(pg_text_to_json(Some("42"), &Type::INT4).unwrap(), json!(42));
311 2 : assert_eq!(pg_text_to_json(Some("42"), &Type::INT2).unwrap(), json!(42));
312 2 : assert_eq!(
313 2 : pg_text_to_json(Some("42"), &Type::INT8).unwrap(),
314 2 : json!("42")
315 2 : );
316 2 : assert_eq!(
317 2 : pg_text_to_json(Some("42.42"), &Type::FLOAT8).unwrap(),
318 2 : json!(42.42)
319 2 : );
320 2 : assert_eq!(
321 2 : pg_text_to_json(Some("42.42"), &Type::FLOAT4).unwrap(),
322 2 : json!(42.42)
323 2 : );
324 2 : assert_eq!(
325 2 : pg_text_to_json(Some("NaN"), &Type::FLOAT4).unwrap(),
326 2 : json!("NaN")
327 2 : );
328 2 : assert_eq!(
329 2 : pg_text_to_json(Some("Infinity"), &Type::FLOAT4).unwrap(),
330 2 : json!("Infinity")
331 2 : );
332 2 : assert_eq!(
333 2 : pg_text_to_json(Some("-Infinity"), &Type::FLOAT4).unwrap(),
334 2 : json!("-Infinity")
335 2 : );
336 :
337 2 : let json: Value =
338 2 : serde_json::from_str("{\"s\":\"str\",\"n\":42,\"f\":4.2,\"a\":[null,3,\"a\"]}")
339 2 : .unwrap();
340 2 : assert_eq!(
341 2 : pg_text_to_json(
342 2 : Some(r#"{"s":"str","n":42,"f":4.2,"a":[null,3,"a"]}"#),
343 2 : &Type::JSONB
344 2 : )
345 2 : .unwrap(),
346 2 : json
347 2 : );
348 2 : }
349 :
350 2 : #[test]
351 2 : fn test_pg_array_parse_text() {
352 8 : fn pt(pg_arr: &str) -> Value {
353 8 : pg_array_parse(pg_arr, &Type::TEXT).unwrap()
354 8 : }
355 2 : assert_eq!(
356 2 : pt(r#"{"aa\"\\\,a",cha,"bbbb"}"#),
357 2 : json!(["aa\"\\,a", "cha", "bbbb"])
358 2 : );
359 2 : assert_eq!(
360 2 : pt(r#"{{"foo","bar"},{"bee","bop"}}"#),
361 2 : json!([["foo", "bar"], ["bee", "bop"]])
362 2 : );
363 2 : assert_eq!(
364 2 : pt(r#"{{{{"foo",NULL,"bop",bup}}}}"#),
365 2 : json!([[[["foo", null, "bop", "bup"]]]])
366 2 : );
367 2 : assert_eq!(
368 2 : pt(r#"{{"1",2,3},{4,NULL,6},{NULL,NULL,NULL}}"#),
369 2 : json!([["1", "2", "3"], ["4", null, "6"], [null, null, null]])
370 2 : );
371 2 : }
372 :
373 2 : #[test]
374 2 : fn test_pg_array_parse_bool() {
375 8 : fn pb(pg_arr: &str) -> Value {
376 8 : pg_array_parse(pg_arr, &Type::BOOL).unwrap()
377 8 : }
378 2 : assert_eq!(pb(r#"{t,f,t}"#), json!([true, false, true]));
379 2 : assert_eq!(pb(r#"{{t,f,t}}"#), json!([[true, false, true]]));
380 2 : assert_eq!(
381 2 : pb(r#"{{t,f},{f,t}}"#),
382 2 : json!([[true, false], [false, true]])
383 2 : );
384 2 : assert_eq!(
385 2 : pb(r#"{{t,NULL},{NULL,f}}"#),
386 2 : json!([[true, null], [null, false]])
387 2 : );
388 2 : }
389 :
390 2 : #[test]
391 2 : fn test_pg_array_parse_numbers() {
392 18 : fn pn(pg_arr: &str, ty: &Type) -> Value {
393 18 : pg_array_parse(pg_arr, ty).unwrap()
394 18 : }
395 2 : assert_eq!(pn(r#"{1,2,3}"#, &Type::INT4), json!([1, 2, 3]));
396 2 : assert_eq!(pn(r#"{1,2,3}"#, &Type::INT2), json!([1, 2, 3]));
397 2 : assert_eq!(pn(r#"{1,2,3}"#, &Type::INT8), json!(["1", "2", "3"]));
398 2 : assert_eq!(pn(r#"{1,2,3}"#, &Type::FLOAT4), json!([1.0, 2.0, 3.0]));
399 2 : assert_eq!(pn(r#"{1,2,3}"#, &Type::FLOAT8), json!([1.0, 2.0, 3.0]));
400 2 : assert_eq!(
401 2 : pn(r#"{1.1,2.2,3.3}"#, &Type::FLOAT4),
402 2 : json!([1.1, 2.2, 3.3])
403 2 : );
404 2 : assert_eq!(
405 2 : pn(r#"{1.1,2.2,3.3}"#, &Type::FLOAT8),
406 2 : json!([1.1, 2.2, 3.3])
407 2 : );
408 2 : assert_eq!(
409 2 : pn(r#"{NaN,Infinity,-Infinity}"#, &Type::FLOAT4),
410 2 : json!(["NaN", "Infinity", "-Infinity"])
411 2 : );
412 2 : assert_eq!(
413 2 : pn(r#"{NaN,Infinity,-Infinity}"#, &Type::FLOAT8),
414 2 : json!(["NaN", "Infinity", "-Infinity"])
415 2 : );
416 2 : }
417 :
418 2 : #[test]
419 2 : fn test_pg_array_with_decoration() {
420 2 : fn p(pg_arr: &str) -> Value {
421 2 : pg_array_parse(pg_arr, &Type::INT2).unwrap()
422 2 : }
423 2 : assert_eq!(
424 2 : p(r#"[1:1][-2:-1][3:5]={{{1,2,3},{4,5,6}}}"#),
425 2 : json!([[[1, 2, 3], [4, 5, 6]]])
426 2 : );
427 2 : }
428 :
429 2 : #[test]
430 2 : fn test_pg_array_parse_json() {
431 8 : fn pt(pg_arr: &str) -> Value {
432 8 : pg_array_parse(pg_arr, &Type::JSONB).unwrap()
433 8 : }
434 2 : assert_eq!(pt(r#"{"{}"}"#), json!([{}]));
435 2 : assert_eq!(
436 2 : pt(r#"{"{\"foo\": 1, \"bar\": 2}"}"#),
437 2 : json!([{"foo": 1, "bar": 2}])
438 2 : );
439 2 : assert_eq!(
440 2 : pt(r#"{"{\"foo\": 1}", "{\"bar\": 2}"}"#),
441 2 : json!([{"foo": 1}, {"bar": 2}])
442 2 : );
443 2 : assert_eq!(
444 2 : pt(r#"{{"{\"foo\": 1}", "{\"bar\": 2}"}}"#),
445 2 : json!([[{"foo": 1}, {"bar": 2}]])
446 2 : );
447 2 : }
448 : }
|