LCOV - code coverage report
Current view: top level - proxy/src/serverless - json.rs (source / functions) Coverage Total Hit
Test: 32f4a56327bc9da697706839ed4836b2a00a408f.info Lines: 99.4 % 337 335
Test Date: 2024-02-07 07:37:29 Functions: 100.0 % 35 35

            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              : }
        

Generated by: LCOV version 2.1-beta