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