LCOV - code coverage report
Current view: top level - proxy/src/http - sql_over_http.rs (source / functions) Coverage Total Hit
Test: 8ac049b474321fdc72ddcb56d7165153a1a900e8.info Lines: 95.3 % 508 484
Test Date: 2023-09-06 10:18:01 Functions: 66.3 % 89 59

            Line data    Source code
       1              : use std::sync::Arc;
       2              : 
       3              : use anyhow::bail;
       4              : use futures::pin_mut;
       5              : use futures::StreamExt;
       6              : use hashbrown::HashMap;
       7              : use hyper::body::HttpBody;
       8              : use hyper::http::HeaderName;
       9              : use hyper::http::HeaderValue;
      10              : use hyper::{Body, HeaderMap, Request};
      11              : use serde_json::json;
      12              : use serde_json::Map;
      13              : use serde_json::Value;
      14              : use tokio_postgres::types::Kind;
      15              : use tokio_postgres::types::Type;
      16              : use tokio_postgres::GenericClient;
      17              : use tokio_postgres::IsolationLevel;
      18              : use tokio_postgres::Row;
      19              : use url::Url;
      20              : 
      21              : use super::conn_pool::ConnInfo;
      22              : use super::conn_pool::GlobalConnPool;
      23              : 
      24          161 : #[derive(serde::Deserialize)]
      25              : struct QueryData {
      26              :     query: String,
      27              :     params: Vec<serde_json::Value>,
      28              : }
      29              : 
      30            6 : #[derive(serde::Deserialize)]
      31              : struct BatchQueryData {
      32              :     queries: Vec<QueryData>,
      33              : }
      34              : 
      35           24 : #[derive(serde::Deserialize)]
      36              : #[serde(untagged)]
      37              : enum Payload {
      38              :     Single(QueryData),
      39              :     Batch(BatchQueryData),
      40              : }
      41              : 
      42              : pub const MAX_RESPONSE_SIZE: usize = 10 * 1024 * 1024; // 10 MB
      43              : const MAX_REQUEST_SIZE: u64 = 1024 * 1024; // 1 MB
      44              : 
      45              : static RAW_TEXT_OUTPUT: HeaderName = HeaderName::from_static("neon-raw-text-output");
      46              : static ARRAY_MODE: HeaderName = HeaderName::from_static("neon-array-mode");
      47              : static ALLOW_POOL: HeaderName = HeaderName::from_static("neon-pool-opt-in");
      48              : static TXN_ISOLATION_LEVEL: HeaderName = HeaderName::from_static("neon-batch-isolation-level");
      49              : static TXN_READ_ONLY: HeaderName = HeaderName::from_static("neon-batch-read-only");
      50              : static TXN_DEFERRABLE: HeaderName = HeaderName::from_static("neon-batch-deferrable");
      51              : 
      52              : static HEADER_VALUE_TRUE: HeaderValue = HeaderValue::from_static("true");
      53              : 
      54              : //
      55              : // Convert json non-string types to strings, so that they can be passed to Postgres
      56              : // as parameters.
      57              : //
      58           35 : fn json_to_pg_text(json: Vec<Value>) -> Result<Vec<Option<String>>, serde_json::Error> {
      59           35 :     json.iter()
      60           35 :         .map(|value| {
      61           15 :             match value {
      62              :                 // special care for nulls
      63            1 :                 Value::Null => Ok(None),
      64              : 
      65              :                 // convert to text with escaping
      66            2 :                 Value::Bool(_) => serde_json::to_string(value).map(Some),
      67            5 :                 Value::Number(_) => serde_json::to_string(value).map(Some),
      68            2 :                 Value::Object(_) => serde_json::to_string(value).map(Some),
      69              : 
      70              :                 // avoid escaping here, as we pass this as a parameter
      71            1 :                 Value::String(s) => Ok(Some(s.to_string())),
      72              : 
      73              :                 // special care for arrays
      74            4 :                 Value::Array(_) => json_array_to_pg_array(value),
      75              :             }
      76           35 :         })
      77           35 :         .collect()
      78           35 : }
      79              : 
      80              : //
      81              : // Serialize a JSON array to a Postgres array. Contrary to the strings in the params
      82              : // in the array we need to escape the strings. Postgres is okay with arrays of form
      83              : // '{1,"2",3}'::int[], so we don't check that array holds values of the same type, leaving
      84              : // it for Postgres to check.
      85              : //
      86              : // Example of the same escaping in node-postgres: packages/pg/lib/utils.js
      87              : //
      88           26 : fn json_array_to_pg_array(value: &Value) -> Result<Option<String>, serde_json::Error> {
      89           26 :     match value {
      90              :         // special care for nulls
      91            2 :         Value::Null => Ok(None),
      92              : 
      93              :         // convert to text with escaping
      94            4 :         Value::Bool(_) => serde_json::to_string(value).map(Some),
      95            8 :         Value::Number(_) => serde_json::to_string(value).map(Some),
      96            0 :         Value::Object(_) => serde_json::to_string(value).map(Some),
      97              : 
      98              :         // here string needs to be escaped, as it is part of the array
      99            5 :         Value::String(_) => serde_json::to_string(value).map(Some),
     100              : 
     101              :         // recurse into array
     102            7 :         Value::Array(arr) => {
     103            7 :             let vals = arr
     104            7 :                 .iter()
     105            7 :                 .map(json_array_to_pg_array)
     106           22 :                 .map(|r| r.map(|v| v.unwrap_or_else(|| "NULL".to_string())))
     107            7 :                 .collect::<Result<Vec<_>, _>>()?
     108            7 :                 .join(",");
     109            7 : 
     110            7 :             Ok(Some(format!("{{{}}}", vals)))
     111              :         }
     112              :     }
     113           26 : }
     114              : 
     115           22 : fn get_conn_info(
     116           22 :     headers: &HeaderMap,
     117           22 :     sni_hostname: Option<String>,
     118           22 : ) -> Result<ConnInfo, anyhow::Error> {
     119           22 :     let connection_string = headers
     120           22 :         .get("Neon-Connection-String")
     121           22 :         .ok_or(anyhow::anyhow!("missing connection string"))?
     122           22 :         .to_str()?;
     123              : 
     124           22 :     let connection_url = Url::parse(connection_string)?;
     125              : 
     126           22 :     let protocol = connection_url.scheme();
     127           22 :     if protocol != "postgres" && protocol != "postgresql" {
     128            0 :         return Err(anyhow::anyhow!(
     129            0 :             "connection string must start with postgres: or postgresql:"
     130            0 :         ));
     131           22 :     }
     132              : 
     133           22 :     let mut url_path = connection_url
     134           22 :         .path_segments()
     135           22 :         .ok_or(anyhow::anyhow!("missing database name"))?;
     136              : 
     137           22 :     let dbname = url_path
     138           22 :         .next()
     139           22 :         .ok_or(anyhow::anyhow!("invalid database name"))?;
     140              : 
     141           22 :     let username = connection_url.username();
     142           22 :     if username.is_empty() {
     143            0 :         return Err(anyhow::anyhow!("missing username"));
     144           22 :     }
     145              : 
     146           22 :     let password = connection_url
     147           22 :         .password()
     148           22 :         .ok_or(anyhow::anyhow!("no password"))?;
     149              : 
     150              :     // TLS certificate selector now based on SNI hostname, so if we are running here
     151              :     // we are sure that SNI hostname is set to one of the configured domain names.
     152           22 :     let sni_hostname = sni_hostname.ok_or(anyhow::anyhow!("no SNI hostname set"))?;
     153              : 
     154           22 :     let hostname = connection_url
     155           22 :         .host_str()
     156           22 :         .ok_or(anyhow::anyhow!("no host"))?;
     157              : 
     158           22 :     let host_header = headers
     159           22 :         .get("host")
     160           22 :         .and_then(|h| h.to_str().ok())
     161           22 :         .and_then(|h| h.split(':').next());
     162           22 : 
     163           22 :     if hostname != sni_hostname {
     164            0 :         return Err(anyhow::anyhow!("mismatched SNI hostname and hostname"));
     165           22 :     } else if let Some(h) = host_header {
     166           22 :         if h != hostname {
     167            0 :             return Err(anyhow::anyhow!("mismatched host header and hostname"));
     168           22 :         }
     169            0 :     }
     170              : 
     171           22 :     Ok(ConnInfo {
     172           22 :         username: username.to_owned(),
     173           22 :         dbname: dbname.to_owned(),
     174           22 :         hostname: hostname.to_owned(),
     175           22 :         password: password.to_owned(),
     176           22 :     })
     177           22 : }
     178              : 
     179              : // TODO: return different http error codes
     180           22 : pub async fn handle(
     181           22 :     request: Request<Body>,
     182           22 :     sni_hostname: Option<String>,
     183           22 :     conn_pool: Arc<GlobalConnPool>,
     184           22 :     session_id: uuid::Uuid,
     185           22 : ) -> anyhow::Result<(Value, HashMap<HeaderName, HeaderValue>)> {
     186           22 :     //
     187           22 :     // Determine the destination and connection params
     188           22 :     //
     189           22 :     let headers = request.headers();
     190           22 :     let conn_info = get_conn_info(headers, sni_hostname)?;
     191              : 
     192              :     // Determine the output options. Default behaviour is 'false'. Anything that is not
     193              :     // strictly 'true' assumed to be false.
     194           22 :     let raw_output = headers.get(&RAW_TEXT_OUTPUT) == Some(&HEADER_VALUE_TRUE);
     195           22 :     let array_mode = headers.get(&ARRAY_MODE) == Some(&HEADER_VALUE_TRUE);
     196           22 : 
     197           22 :     // Allow connection pooling only if explicitly requested
     198           22 :     let allow_pool = headers.get(&ALLOW_POOL) == Some(&HEADER_VALUE_TRUE);
     199           22 : 
     200           22 :     // isolation level, read only and deferrable
     201           22 : 
     202           22 :     let txn_isolation_level_raw = headers.get(&TXN_ISOLATION_LEVEL).cloned();
     203           22 :     let txn_isolation_level = match txn_isolation_level_raw {
     204            2 :         Some(ref x) => Some(match x.as_bytes() {
     205            2 :             b"Serializable" => IsolationLevel::Serializable,
     206            0 :             b"ReadUncommitted" => IsolationLevel::ReadUncommitted,
     207            0 :             b"ReadCommitted" => IsolationLevel::ReadCommitted,
     208            0 :             b"RepeatableRead" => IsolationLevel::RepeatableRead,
     209            0 :             _ => bail!("invalid isolation level"),
     210              :         }),
     211           20 :         None => None,
     212              :     };
     213              : 
     214           22 :     let txn_read_only = headers.get(&TXN_READ_ONLY) == Some(&HEADER_VALUE_TRUE);
     215           22 :     let txn_deferrable = headers.get(&TXN_DEFERRABLE) == Some(&HEADER_VALUE_TRUE);
     216              : 
     217           22 :     let request_content_length = match request.body().size_hint().upper() {
     218           22 :         Some(v) => v,
     219            0 :         None => MAX_REQUEST_SIZE + 1,
     220              :     };
     221              : 
     222           22 :     if request_content_length > MAX_REQUEST_SIZE {
     223            0 :         return Err(anyhow::anyhow!(
     224            0 :             "request is too large (max is {MAX_REQUEST_SIZE} bytes)"
     225            0 :         ));
     226           22 :     }
     227              : 
     228              :     //
     229              :     // Read the query and query params from the request body
     230              :     //
     231           22 :     let body = hyper::body::to_bytes(request.into_body()).await?;
     232           22 :     let payload: Payload = serde_json::from_slice(&body)?;
     233              : 
     234          100 :     let mut client = conn_pool.get(&conn_info, !allow_pool, session_id).await?;
     235              : 
     236              :     //
     237              :     // Now execute the query and return the result
     238              :     //
     239           20 :     let result = match payload {
     240           18 :         Payload::Single(query) => query_to_json(&client.inner, query, raw_output, array_mode)
     241           18 :             .await
     242           18 :             .map(|x| (x, HashMap::default())),
     243            2 :         Payload::Batch(batch_query) => {
     244            2 :             let mut results = Vec::new();
     245            2 :             let mut builder = client.inner.build_transaction();
     246            2 :             if let Some(isolation_level) = txn_isolation_level {
     247            2 :                 builder = builder.isolation_level(isolation_level);
     248            2 :             }
     249            2 :             if txn_read_only {
     250            1 :                 builder = builder.read_only(true);
     251            1 :             }
     252            2 :             if txn_deferrable {
     253            1 :                 builder = builder.deferrable(true);
     254            1 :             }
     255            2 :             let transaction = builder.start().await?;
     256           13 :             for query in batch_query.queries {
     257           11 :                 let result = query_to_json(&transaction, query, raw_output, array_mode).await;
     258           11 :                 match result {
     259           11 :                     Ok(r) => results.push(r),
     260            0 :                     Err(e) => {
     261            0 :                         transaction.rollback().await?;
     262            0 :                         return Err(e);
     263              :                     }
     264              :                 }
     265              :             }
     266            2 :             transaction.commit().await?;
     267            2 :             let mut headers = HashMap::default();
     268            2 :             if txn_read_only {
     269              :                 headers.insert(
     270            1 :                     TXN_READ_ONLY.clone(),
     271            1 :                     HeaderValue::try_from(txn_read_only.to_string())?,
     272              :                 );
     273            1 :             }
     274            2 :             if txn_deferrable {
     275              :                 headers.insert(
     276            1 :                     TXN_DEFERRABLE.clone(),
     277            1 :                     HeaderValue::try_from(txn_deferrable.to_string())?,
     278              :                 );
     279            1 :             }
     280            2 :             if let Some(txn_isolation_level) = txn_isolation_level_raw {
     281            2 :                 headers.insert(TXN_ISOLATION_LEVEL.clone(), txn_isolation_level);
     282            2 :             }
     283            2 :             Ok((json!({ "results": results }), headers))
     284              :         }
     285              :     };
     286              : 
     287           20 :     if allow_pool {
     288            4 :         let current_span = tracing::Span::current();
     289            4 :         // return connection to the pool
     290            4 :         tokio::task::spawn_blocking(move || {
     291            4 :             let _span = current_span.enter();
     292            4 :             let _ = conn_pool.put(&conn_info, client);
     293            4 :         });
     294           16 :     }
     295              : 
     296           20 :     result
     297           22 : }
     298              : 
     299           29 : async fn query_to_json<T: GenericClient>(
     300           29 :     client: &T,
     301           29 :     data: QueryData,
     302           29 :     raw_output: bool,
     303           29 :     array_mode: bool,
     304           29 : ) -> anyhow::Result<Value> {
     305           29 :     let query_params = json_to_pg_text(data.params)?;
     306           29 :     let row_stream = client
     307           29 :         .query_raw_txt::<String, _>(data.query, query_params)
     308           29 :         .await?;
     309              : 
     310              :     // Manually drain the stream into a vector to leave row_stream hanging
     311              :     // around to get a command tag. Also check that the response is not too
     312              :     // big.
     313           29 :     pin_mut!(row_stream);
     314           29 :     let mut rows: Vec<tokio_postgres::Row> = Vec::new();
     315           29 :     let mut current_size = 0;
     316           62 :     while let Some(row) = row_stream.next().await {
     317           33 :         let row = row?;
     318           33 :         current_size += row.body_len();
     319           33 :         rows.push(row);
     320           33 :         if current_size > MAX_RESPONSE_SIZE {
     321            0 :             return Err(anyhow::anyhow!(
     322            0 :                 "response is too large (max is {MAX_RESPONSE_SIZE} bytes)"
     323            0 :             ));
     324           33 :         }
     325              :     }
     326              : 
     327              :     // grab the command tag and number of rows affected
     328           29 :     let command_tag = row_stream.command_tag().unwrap_or_default();
     329           29 :     let mut command_tag_split = command_tag.split(' ');
     330           29 :     let command_tag_name = command_tag_split.next().unwrap_or_default();
     331           29 :     let command_tag_count = if command_tag_name == "INSERT" {
     332              :         // INSERT returns OID first and then number of rows
     333            2 :         command_tag_split.nth(1)
     334              :     } else {
     335              :         // other commands return number of rows (if any)
     336           27 :         command_tag_split.next()
     337              :     }
     338           29 :     .and_then(|s| s.parse::<i64>().ok());
     339              : 
     340           29 :     let fields = if !rows.is_empty() {
     341           25 :         rows[0]
     342           25 :             .columns()
     343           25 :             .iter()
     344           99 :             .map(|c| {
     345           99 :                 json!({
     346           99 :                     "name": Value::String(c.name().to_owned()),
     347           99 :                     "dataTypeID": Value::Number(c.type_().oid().into()),
     348           99 :                     "tableID": c.table_oid(),
     349           99 :                     "columnID": c.column_id(),
     350           99 :                     "dataTypeSize": c.type_size(),
     351           99 :                     "dataTypeModifier": c.type_modifier(),
     352           99 :                     "format": "text",
     353           99 :                 })
     354           99 :             })
     355           25 :             .collect::<Vec<_>>()
     356              :     } else {
     357            4 :         Vec::new()
     358              :     };
     359              : 
     360              :     // convert rows to JSON
     361           29 :     let rows = rows
     362           29 :         .iter()
     363           33 :         .map(|row| pg_text_row_to_json(row, raw_output, array_mode))
     364           29 :         .collect::<Result<Vec<_>, _>>()?;
     365              : 
     366              :     // resulting JSON format is based on the format of node-postgres result
     367           29 :     Ok(json!({
     368           29 :         "command": command_tag_name,
     369           29 :         "rowCount": command_tag_count,
     370           29 :         "rows": rows,
     371           29 :         "fields": fields,
     372           29 :         "rowAsArray": array_mode,
     373           29 :     }))
     374           29 : }
     375              : 
     376              : //
     377              : // Convert postgres row with text-encoded values to JSON object
     378              : //
     379           33 : pub fn pg_text_row_to_json(
     380           33 :     row: &Row,
     381           33 :     raw_output: bool,
     382           33 :     array_mode: bool,
     383           33 : ) -> Result<Value, anyhow::Error> {
     384          111 :     let iter = row.columns().iter().enumerate().map(|(i, column)| {
     385          111 :         let name = column.name();
     386          111 :         let pg_value = row.as_text(i)?;
     387          111 :         let json_value = if raw_output {
     388            6 :             match pg_value {
     389            6 :                 Some(v) => Value::String(v.to_string()),
     390            0 :                 None => Value::Null,
     391              :             }
     392              :         } else {
     393          105 :             pg_text_to_json(pg_value, column.type_())?
     394              :         };
     395          111 :         Ok((name.to_string(), json_value))
     396          111 :     });
     397           33 : 
     398           33 :     if array_mode {
     399              :         // drop keys and aggregate into array
     400            2 :         let arr = iter
     401            6 :             .map(|r| r.map(|(_key, val)| val))
     402            2 :             .collect::<Result<Vec<Value>, anyhow::Error>>()?;
     403            2 :         Ok(Value::Array(arr))
     404              :     } else {
     405           31 :         let obj = iter.collect::<Result<Map<String, Value>, anyhow::Error>>()?;
     406           31 :         Ok(Value::Object(obj))
     407              :     }
     408           33 : }
     409              : 
     410              : //
     411              : // Convert postgres text-encoded value to JSON value
     412              : //
     413              : pub fn pg_text_to_json(pg_value: Option<&str>, pg_type: &Type) -> Result<Value, anyhow::Error> {
     414          197 :     if let Some(val) = pg_value {
     415          185 :         if let Kind::Array(elem_type) = pg_type.kind() {
     416            6 :             return pg_array_parse(val, elem_type);
     417          179 :         }
     418          179 : 
     419          179 :         match *pg_type {
     420           30 :             Type::BOOL => Ok(Value::Bool(val == "t")),
     421              :             Type::INT2 | Type::INT4 => {
     422           63 :                 let val = val.parse::<i32>()?;
     423           63 :                 Ok(Value::Number(serde_json::Number::from(val)))
     424              :             }
     425              :             Type::FLOAT4 | Type::FLOAT8 => {
     426           25 :                 let fval = val.parse::<f64>()?;
     427           25 :                 let num = serde_json::Number::from_f64(fval);
     428           25 :                 if let Some(num) = num {
     429           16 :                     Ok(Value::Number(num))
     430              :                 } else {
     431              :                     // Pass Nan, Inf, -Inf as strings
     432              :                     // JS JSON.stringify() does converts them to null, but we
     433              :                     // want to preserve them, so we pass them as strings
     434            9 :                     Ok(Value::String(val.to_string()))
     435              :                 }
     436              :             }
     437            3 :             Type::JSON | Type::JSONB => Ok(serde_json::from_str(val)?),
     438           58 :             _ => Ok(Value::String(val.to_string())),
     439              :         }
     440              :     } else {
     441           12 :         Ok(Value::Null)
     442              :     }
     443          197 : }
     444              : 
     445              : //
     446              : // Parse postgres array into JSON array.
     447              : //
     448              : // This is a bit involved because we need to handle nested arrays and quoted
     449              : // values. Unlike postgres we don't check that all nested arrays have the same
     450              : // dimensions, we just return them as is.
     451              : //
     452           24 : fn pg_array_parse(pg_array: &str, elem_type: &Type) -> Result<Value, anyhow::Error> {
     453           24 :     _pg_array_parse(pg_array, elem_type, false).map(|(v, _)| v)
     454           24 : }
     455              : 
     456           40 : fn _pg_array_parse(
     457           40 :     pg_array: &str,
     458           40 :     elem_type: &Type,
     459           40 :     nested: bool,
     460           40 : ) -> Result<(Value, usize), anyhow::Error> {
     461           40 :     let mut pg_array_chr = pg_array.char_indices();
     462           40 :     let mut level = 0;
     463           40 :     let mut quote = false;
     464           40 :     let mut entries: Vec<Value> = Vec::new();
     465           40 :     let mut entry = String::new();
     466           40 : 
     467           40 :     // skip bounds decoration
     468           40 :     if let Some('[') = pg_array.chars().next() {
     469           18 :         for (_, c) in pg_array_chr.by_ref() {
     470           18 :             if c == '=' {
     471            1 :                 break;
     472           17 :             }
     473              :         }
     474           39 :     }
     475              : 
     476           97 :     fn push_checked(
     477           97 :         entry: &mut String,
     478           97 :         entries: &mut Vec<Value>,
     479           97 :         elem_type: &Type,
     480           97 :     ) -> Result<(), anyhow::Error> {
     481           97 :         if !entry.is_empty() {
     482              :             // While in usual postgres response we get nulls as None and everything else
     483              :             // as Some(&str), in arrays we get NULL as unquoted 'NULL' string (while
     484              :             // string with value 'NULL' will be represented by '"NULL"'). So catch NULLs
     485              :             // here while we have quotation info and convert them to None.
     486           72 :             if entry == "NULL" {
     487            7 :                 entries.push(pg_text_to_json(None, elem_type)?);
     488              :             } else {
     489           65 :                 entries.push(pg_text_to_json(Some(entry), elem_type)?);
     490              :             }
     491           72 :             entry.clear();
     492           25 :         }
     493              : 
     494           97 :         Ok(())
     495           97 :     }
     496              : 
     497          427 :     while let Some((mut i, mut c)) = pg_array_chr.next() {
     498          403 :         let mut escaped = false;
     499          403 : 
     500          403 :         if c == '\\' {
     501            3 :             escaped = true;
     502            3 :             (i, c) = pg_array_chr.next().unwrap();
     503          400 :         }
     504              : 
     505          133 :         match c {
     506           56 :             '{' if !quote => {
     507           56 :                 level += 1;
     508           56 :                 if level > 1 {
     509           16 :                     let (res, off) = _pg_array_parse(&pg_array[i..], elem_type, true)?;
     510           16 :                     entries.push(res);
     511          165 :                     for _ in 0..off - 1 {
     512          165 :                         pg_array_chr.next();
     513          165 :                     }
     514           40 :                 }
     515              :             }
     516              :             '}' => {
     517           56 :                 level -= 1;
     518           56 :                 if level == 0 {
     519           40 :                     push_checked(&mut entry, &mut entries, elem_type)?;
     520           40 :                     if nested {
     521           16 :                         return Ok((Value::Array(entries), i));
     522           24 :                     }
     523           16 :                 }
     524              :             }
     525           18 :             '"' if !escaped => {
     526           18 :                 if quote {
     527              :                     // end of quoted string, so push it manually without any checks
     528              :                     // for emptiness or nulls
     529            9 :                     entries.push(pg_text_to_json(Some(&entry), elem_type)?);
     530            9 :                     entry.clear();
     531            9 :                 }
     532           18 :                 quote = !quote;
     533              :             }
     534           57 :             ',' if !quote => {
     535           57 :                 push_checked(&mut entry, &mut entries, elem_type)?;
     536              :             }
     537          216 :             _ => {
     538          216 :                 entry.push(c);
     539          216 :             }
     540              :         }
     541              :     }
     542              : 
     543           24 :     if level != 0 {
     544            0 :         return Err(anyhow::anyhow!("unbalanced array"));
     545           24 :     }
     546           24 : 
     547           24 :     Ok((Value::Array(entries), 0))
     548           40 : }
     549              : 
     550              : #[cfg(test)]
     551              : mod tests {
     552              :     use super::*;
     553              :     use serde_json::json;
     554              : 
     555            1 :     #[test]
     556            1 :     fn test_atomic_types_to_pg_params() {
     557            1 :         let json = vec![Value::Bool(true), Value::Bool(false)];
     558            1 :         let pg_params = json_to_pg_text(json).unwrap();
     559            1 :         assert_eq!(
     560            1 :             pg_params,
     561            1 :             vec![Some("true".to_owned()), Some("false".to_owned())]
     562            1 :         );
     563              : 
     564            1 :         let json = vec![Value::Number(serde_json::Number::from(42))];
     565            1 :         let pg_params = json_to_pg_text(json).unwrap();
     566            1 :         assert_eq!(pg_params, vec![Some("42".to_owned())]);
     567              : 
     568            1 :         let json = vec![Value::String("foo\"".to_string())];
     569            1 :         let pg_params = json_to_pg_text(json).unwrap();
     570            1 :         assert_eq!(pg_params, vec![Some("foo\"".to_owned())]);
     571              : 
     572            1 :         let json = vec![Value::Null];
     573            1 :         let pg_params = json_to_pg_text(json).unwrap();
     574            1 :         assert_eq!(pg_params, vec![None]);
     575            1 :     }
     576              : 
     577            1 :     #[test]
     578            1 :     fn test_json_array_to_pg_array() {
     579            1 :         // atoms and escaping
     580            1 :         let json = "[true, false, null, \"NULL\", 42, \"foo\", \"bar\\\"-\\\\\"]";
     581            1 :         let json: Value = serde_json::from_str(json).unwrap();
     582            1 :         let pg_params = json_to_pg_text(vec![json]).unwrap();
     583            1 :         assert_eq!(
     584            1 :             pg_params,
     585            1 :             vec![Some(
     586            1 :                 "{true,false,NULL,\"NULL\",42,\"foo\",\"bar\\\"-\\\\\"}".to_owned()
     587            1 :             )]
     588            1 :         );
     589              : 
     590              :         // nested arrays
     591            1 :         let json = "[[true, false], [null, 42], [\"foo\", \"bar\\\"-\\\\\"]]";
     592            1 :         let json: Value = serde_json::from_str(json).unwrap();
     593            1 :         let pg_params = json_to_pg_text(vec![json]).unwrap();
     594            1 :         assert_eq!(
     595            1 :             pg_params,
     596            1 :             vec![Some(
     597            1 :                 "{{true,false},{NULL,42},{\"foo\",\"bar\\\"-\\\\\"}}".to_owned()
     598            1 :             )]
     599            1 :         );
     600            1 :     }
     601              : 
     602            1 :     #[test]
     603            1 :     fn test_atomic_types_parse() {
     604            1 :         assert_eq!(
     605            1 :             pg_text_to_json(Some("foo"), &Type::TEXT).unwrap(),
     606            1 :             json!("foo")
     607            1 :         );
     608            1 :         assert_eq!(pg_text_to_json(None, &Type::TEXT).unwrap(), json!(null));
     609            1 :         assert_eq!(pg_text_to_json(Some("42"), &Type::INT4).unwrap(), json!(42));
     610            1 :         assert_eq!(pg_text_to_json(Some("42"), &Type::INT2).unwrap(), json!(42));
     611            1 :         assert_eq!(
     612            1 :             pg_text_to_json(Some("42"), &Type::INT8).unwrap(),
     613            1 :             json!("42")
     614            1 :         );
     615            1 :         assert_eq!(
     616            1 :             pg_text_to_json(Some("42.42"), &Type::FLOAT8).unwrap(),
     617            1 :             json!(42.42)
     618            1 :         );
     619            1 :         assert_eq!(
     620            1 :             pg_text_to_json(Some("42.42"), &Type::FLOAT4).unwrap(),
     621            1 :             json!(42.42)
     622            1 :         );
     623            1 :         assert_eq!(
     624            1 :             pg_text_to_json(Some("NaN"), &Type::FLOAT4).unwrap(),
     625            1 :             json!("NaN")
     626            1 :         );
     627            1 :         assert_eq!(
     628            1 :             pg_text_to_json(Some("Infinity"), &Type::FLOAT4).unwrap(),
     629            1 :             json!("Infinity")
     630            1 :         );
     631            1 :         assert_eq!(
     632            1 :             pg_text_to_json(Some("-Infinity"), &Type::FLOAT4).unwrap(),
     633            1 :             json!("-Infinity")
     634            1 :         );
     635              : 
     636            1 :         let json: Value =
     637            1 :             serde_json::from_str("{\"s\":\"str\",\"n\":42,\"f\":4.2,\"a\":[null,3,\"a\"]}")
     638            1 :                 .unwrap();
     639            1 :         assert_eq!(
     640            1 :             pg_text_to_json(
     641            1 :                 Some(r#"{"s":"str","n":42,"f":4.2,"a":[null,3,"a"]}"#),
     642            1 :                 &Type::JSONB
     643            1 :             )
     644            1 :             .unwrap(),
     645            1 :             json
     646            1 :         );
     647            1 :     }
     648              : 
     649            1 :     #[test]
     650            1 :     fn test_pg_array_parse_text() {
     651            4 :         fn pt(pg_arr: &str) -> Value {
     652            4 :             pg_array_parse(pg_arr, &Type::TEXT).unwrap()
     653            4 :         }
     654            1 :         assert_eq!(
     655            1 :             pt(r#"{"aa\"\\\,a",cha,"bbbb"}"#),
     656            1 :             json!(["aa\"\\,a", "cha", "bbbb"])
     657            1 :         );
     658            1 :         assert_eq!(
     659            1 :             pt(r#"{{"foo","bar"},{"bee","bop"}}"#),
     660            1 :             json!([["foo", "bar"], ["bee", "bop"]])
     661            1 :         );
     662            1 :         assert_eq!(
     663            1 :             pt(r#"{{{{"foo",NULL,"bop",bup}}}}"#),
     664            1 :             json!([[[["foo", null, "bop", "bup"]]]])
     665            1 :         );
     666            1 :         assert_eq!(
     667            1 :             pt(r#"{{"1",2,3},{4,NULL,6},{NULL,NULL,NULL}}"#),
     668            1 :             json!([["1", "2", "3"], ["4", null, "6"], [null, null, null]])
     669            1 :         );
     670            1 :     }
     671              : 
     672            1 :     #[test]
     673            1 :     fn test_pg_array_parse_bool() {
     674            4 :         fn pb(pg_arr: &str) -> Value {
     675            4 :             pg_array_parse(pg_arr, &Type::BOOL).unwrap()
     676            4 :         }
     677            1 :         assert_eq!(pb(r#"{t,f,t}"#), json!([true, false, true]));
     678            1 :         assert_eq!(pb(r#"{{t,f,t}}"#), json!([[true, false, true]]));
     679            1 :         assert_eq!(
     680            1 :             pb(r#"{{t,f},{f,t}}"#),
     681            1 :             json!([[true, false], [false, true]])
     682            1 :         );
     683            1 :         assert_eq!(
     684            1 :             pb(r#"{{t,NULL},{NULL,f}}"#),
     685            1 :             json!([[true, null], [null, false]])
     686            1 :         );
     687            1 :     }
     688              : 
     689            1 :     #[test]
     690            1 :     fn test_pg_array_parse_numbers() {
     691            9 :         fn pn(pg_arr: &str, ty: &Type) -> Value {
     692            9 :             pg_array_parse(pg_arr, ty).unwrap()
     693            9 :         }
     694            1 :         assert_eq!(pn(r#"{1,2,3}"#, &Type::INT4), json!([1, 2, 3]));
     695            1 :         assert_eq!(pn(r#"{1,2,3}"#, &Type::INT2), json!([1, 2, 3]));
     696            1 :         assert_eq!(pn(r#"{1,2,3}"#, &Type::INT8), json!(["1", "2", "3"]));
     697            1 :         assert_eq!(pn(r#"{1,2,3}"#, &Type::FLOAT4), json!([1.0, 2.0, 3.0]));
     698            1 :         assert_eq!(pn(r#"{1,2,3}"#, &Type::FLOAT8), json!([1.0, 2.0, 3.0]));
     699            1 :         assert_eq!(
     700            1 :             pn(r#"{1.1,2.2,3.3}"#, &Type::FLOAT4),
     701            1 :             json!([1.1, 2.2, 3.3])
     702            1 :         );
     703            1 :         assert_eq!(
     704            1 :             pn(r#"{1.1,2.2,3.3}"#, &Type::FLOAT8),
     705            1 :             json!([1.1, 2.2, 3.3])
     706            1 :         );
     707            1 :         assert_eq!(
     708            1 :             pn(r#"{NaN,Infinity,-Infinity}"#, &Type::FLOAT4),
     709            1 :             json!(["NaN", "Infinity", "-Infinity"])
     710            1 :         );
     711            1 :         assert_eq!(
     712            1 :             pn(r#"{NaN,Infinity,-Infinity}"#, &Type::FLOAT8),
     713            1 :             json!(["NaN", "Infinity", "-Infinity"])
     714            1 :         );
     715            1 :     }
     716              : 
     717            1 :     #[test]
     718            1 :     fn test_pg_array_with_decoration() {
     719            1 :         fn p(pg_arr: &str) -> Value {
     720            1 :             pg_array_parse(pg_arr, &Type::INT2).unwrap()
     721            1 :         }
     722            1 :         assert_eq!(
     723            1 :             p(r#"[1:1][-2:-1][3:5]={{{1,2,3},{4,5,6}}}"#),
     724            1 :             json!([[[1, 2, 3], [4, 5, 6]]])
     725            1 :         );
     726            1 :     }
     727              : }
        

Generated by: LCOV version 2.1-beta