Skip to main content

uqa_sql/expr/
json.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! JSON scalar-function helpers for the expression evaluator.
8
9use uqa_core::{DecimalValue, TemporalValue, Value};
10
11use crate::error::{Result, SQLError};
12
13use super::{hex_encode, out_of_range};
14
15mod path;
16mod production;
17#[cfg(test)]
18pub(super) use production::json_delete_with_control;
19pub(super) use production::{
20    cast_json_value_with_control, compare_jsonb_datums_with_control,
21    decode_jsonb_datum_with_control, encode_jsonb_datum, evaluate,
22    format_core_value_as_json_with_control, format_value_as_json_with_control,
23    json_concat_with_control, json_delete_values_with_control, json_extract_operator_with_control,
24    quote_with_control, utf8_lossy_with_control, JsonbInput,
25};
26
27pub(super) use path::{jsonpath_candidate, jsonpath_match};
28
29pub(super) fn parse_json(s: &str) -> Result<serde_json::Value> {
30    serde_json::from_str::<serde_json::Value>(s)
31        .map_err(|_| super::json_strip::invalid_json_input(s))
32}
33
34/// Render a parsed JSON value in `PostgreSQL`'s compact result format. JSONB
35/// objects use `PostgreSQL`'s length-then-bytewise key ordering.
36pub(super) fn format_json(value: &serde_json::Value, jsonb: bool) -> String {
37    if !jsonb {
38        return serde_json::to_string(value).expect("serializing a JSON value cannot fail");
39    }
40    match value {
41        serde_json::Value::Null => "null".to_string(),
42        serde_json::Value::Bool(value) => value.to_string(),
43        serde_json::Value::Number(value) => {
44            let text = value.to_string();
45            DecimalValue::parse(&text).map_or(text, |value| value.to_sql_string())
46        }
47        serde_json::Value::String(value) => serde_json::Value::String(value.clone()).to_string(),
48        serde_json::Value::Array(values) => {
49            let values = values
50                .iter()
51                .map(|value| format_json(value, true))
52                .collect::<Vec<_>>();
53            format!("[{}]", values.join(", "))
54        }
55        serde_json::Value::Object(values) => {
56            let mut values = values.iter().collect::<Vec<_>>();
57            values.sort_by(|(left, _), (right, _)| {
58                left.len()
59                    .cmp(&right.len())
60                    .then_with(|| left.as_bytes().cmp(right.as_bytes()))
61            });
62            let values = values
63                .into_iter()
64                .map(|(key, value)| {
65                    let key = serde_json::Value::String(key.clone()).to_string();
66                    format!("{key}: {}", format_json(value, true))
67                })
68                .collect::<Vec<_>>();
69            format!("{{{}}}", values.join(", "))
70        }
71    }
72}
73
74#[cfg(test)]
75fn format_jsonb_pretty(value: &serde_json::Value) -> String {
76    let Value::Str(text) =
77        ordinary("jsonb_pretty", &[Value::Json(value.to_string())]).expect("valid parsed JSON")
78    else {
79        unreachable!("JSON pretty text")
80    };
81    text
82}
83
84pub(super) fn typed_json_value(value: &serde_json::Value, jsonb: bool) -> Result<Value> {
85    if jsonb {
86        validate_jsonb_numbers(value)?;
87    }
88    let text = format_json(value, jsonb);
89    if jsonb {
90        Ok(Value::JsonB(text))
91    } else {
92        Ok(Value::Json(text))
93    }
94}
95
96fn validate_jsonb_numbers(value: &serde_json::Value) -> Result<()> {
97    match value {
98        serde_json::Value::Number(value) => DecimalValue::parse(&value.to_string())
99            .map(|_| ())
100            .ok_or_else(|| out_of_range("numeric")),
101        serde_json::Value::Array(values) => values.iter().try_for_each(validate_jsonb_numbers),
102        serde_json::Value::Object(values) => values.values().try_for_each(validate_jsonb_numbers),
103        _ => Ok(()),
104    }
105}
106
107/// Render an engine value as `PostgreSQL` JSON input text without losing the
108/// lexical representation of values already typed as `json` or `jsonb`.
109pub fn value_to_json_text(value: &Value) -> Result<String> {
110    Ok(match value {
111        Value::Null => "null".to_string(),
112        Value::Void => "\"\"".to_string(),
113        Value::Bool(value) => value.to_string(),
114        Value::Int(value) => value.to_string(),
115        Value::Float(value) => match serde_json::Number::from_f64(*value) {
116            Some(number) => number.to_string(),
117            None if value.is_nan() => "\"NaN\"".to_string(),
118            None if value.is_sign_positive() => "\"Infinity\"".to_string(),
119            None => "\"-Infinity\"".to_string(),
120        },
121        Value::Decimal(value) => value.to_sql_string(),
122        Value::Str(value) => serde_json::Value::String(value.clone()).to_string(),
123        Value::FixedChar(value) => {
124            serde_json::Value::String(value.trim_end_matches(' ').to_string()).to_string()
125        }
126        Value::Bytes(value) => {
127            serde_json::Value::String(format!("0x{}", hex_encode(value))).to_string()
128        }
129        Value::Temporal(value) => serde_json::Value::String(value.to_sql_string()).to_string(),
130        Value::Json(text) | Value::JsonB(text) => text.clone(),
131        Value::Enum(value) => return Err(super::catalog_output_required(value)),
132        Value::Datum(value) => return value_to_json_text(&super::datums::read(value)?),
133        Value::LegacyVector(vector) => legacy_vector_json(vector).to_string(),
134        Value::Array(array) => array_json_text(array.elements(), array.element_type_oid())?,
135        Value::List(values) => {
136            let values = values
137                .iter()
138                .map(value_to_json_text)
139                .collect::<Result<Vec<_>>>()?;
140            format!("[{}]", values.join(","))
141        }
142        Value::Row(values) => record_json_text(
143            values
144                .iter()
145                .enumerate()
146                .map(|(index, value)| (format!("f{}", index + 1), value)),
147        )?,
148        Value::Record(fields) => {
149            record_json_text(fields.iter().map(|(name, value)| (name.clone(), value)))?
150        }
151        Value::Map(values) => {
152            let values = values
153                .iter()
154                .map(|(key, value)| {
155                    let key = serde_json::Value::String(key.clone()).to_string();
156                    Ok(format!("{key}:{}", value_to_json_text(value)?))
157                })
158                .collect::<Result<Vec<_>>>()?;
159            format!("{{{}}}", values.join(","))
160        }
161    })
162}
163
164fn array_json_text(values: &[Value], oid: Option<u32>) -> Result<String> {
165    let fields = values
166        .iter()
167        .map(|value| match value {
168            Value::List(values) => array_json_text(values, oid),
169            Value::Float(value) if oid == Some(700) && value.is_finite() => {
170                Ok(super::format_real(*value as f32))
171            }
172            value => value_to_json_text(value),
173        })
174        .collect::<Result<Vec<_>>>()?;
175    Ok(format!("[{}]", fields.join(",")))
176}
177
178fn legacy_vector_json(vector: &uqa_core::LegacyVectorValue) -> serde_json::Value {
179    serde_json::Value::Array(
180        vector
181            .elements()
182            .iter()
183            .map(|value| {
184                let Value::Int(value) = value else {
185                    unreachable!("validated legacy vector element");
186                };
187                match vector.kind() {
188                    uqa_core::LegacyVectorKind::SmallInteger => (*value).into(),
189                    uqa_core::LegacyVectorKind::Oid => value.to_string().into(),
190                }
191            })
192            .collect(),
193    )
194}
195
196fn record_json_text<'a>(fields: impl IntoIterator<Item = (String, &'a Value)>) -> Result<String> {
197    let fields = fields
198        .into_iter()
199        .map(|(name, value)| {
200            let name = serde_json::Value::String(name).to_string();
201            Ok(format!("{name}:{}", value_to_json_text(value)?))
202        })
203        .collect::<Result<Vec<_>>>()?;
204    Ok(format!("{{{}}}", fields.join(",")))
205}
206
207pub(super) fn json_build_array_value(args: &[Value], jsonb: bool) -> Result<Value> {
208    let text = format!(
209        "[{}]",
210        args.iter()
211            .map(value_to_json_text)
212            .collect::<Result<Vec<_>>>()?
213            .join(", ")
214    );
215    if jsonb {
216        typed_json_value(&parse_json(&text)?, true)
217    } else {
218        Ok(Value::Json(text))
219    }
220}
221
222/// JSON object keys must be scalar SQL datums, including when an array-shaped catalog vector has a distinct runtime carrier. Each constructor retains its own NULL-key diagnostic.
223pub fn validate_json_object_key_type(value: &Value) -> Result<()> {
224    if matches!(
225        value,
226        Value::Array(_)
227            | Value::LegacyVector(_)
228            | Value::List(_)
229            | Value::Row(_)
230            | Value::Record(_)
231            | Value::Map(_)
232            | Value::Json(_)
233            | Value::JsonB(_)
234    ) {
235        return Err(SQLError::Routine {
236            sqlstate: "22023".into(),
237            message: "key value must be scalar, not array, composite, or json".into(),
238        });
239    }
240    Ok(())
241}
242
243pub(super) fn json_build_object_value(args: &[Value], jsonb: bool) -> Result<Value> {
244    let function = if jsonb {
245        "jsonb_build_object()"
246    } else {
247        "json_build_object()"
248    };
249    if !args.len().is_multiple_of(2) {
250        return Err(SQLError::Diagnostic {
251            sqlstate: "22023".into(),
252            message: "argument list must have even number of elements".into(),
253            detail: None,
254            hint: Some(format!(
255                "The arguments of {function} must consist of alternating keys and values."
256            )),
257        });
258    }
259    let mut fields = Vec::with_capacity(args.len() / 2);
260    for (index, pair) in args.as_chunks::<2>().0.iter().enumerate() {
261        if matches!(pair[0], Value::Null) {
262            return Err(if jsonb {
263                SQLError::Routine {
264                    sqlstate: "22023".into(),
265                    message: format!("argument {}: key must not be null", index * 2 + 1),
266                }
267            } else {
268                SQLError::Routine {
269                    sqlstate: "22004".into(),
270                    message: "null value not allowed for object key".into(),
271                }
272            });
273        }
274        validate_json_object_key_type(&pair[0])?;
275        let key = serde_json::Value::String(super::value_to_string(&pair[0])?).to_string();
276        fields.push(format!("{key} : {}", value_to_json_text(&pair[1])?));
277    }
278    let text = format!("{{{}}}", fields.join(", "));
279    if jsonb {
280        typed_json_value(&parse_json(&text)?, true)
281    } else {
282        Ok(Value::Json(text))
283    }
284}
285
286#[cfg(test)]
287pub(super) fn value_to_json(v: &Value) -> serde_json::Value {
288    match v {
289        Value::Datum(datum) => {
290            value_to_json(&super::datums::read(datum).expect("test datum output"))
291        }
292        Value::Null => serde_json::Value::Null,
293        Value::Void => serde_json::Value::String(String::new()),
294        Value::Bool(b) => serde_json::Value::Bool(*b),
295        Value::Int(i) => serde_json::Value::Number((*i).into()),
296        Value::Float(f) => serde_json::Number::from_f64(*f).map_or_else(
297            || {
298                let label = if f.is_nan() {
299                    "NaN"
300                } else if f.is_sign_positive() {
301                    "Infinity"
302                } else {
303                    "-Infinity"
304                };
305                serde_json::Value::String(label.to_string())
306            },
307            serde_json::Value::Number,
308        ),
309        Value::Decimal(d) => {
310            if d.is_nan() || d.is_infinite() {
311                serde_json::Value::String(d.to_sql_string())
312            } else {
313                d.to_sql_string()
314                    .parse::<serde_json::Number>()
315                    .map(serde_json::Value::Number)
316                    .unwrap_or_else(|_| serde_json::Value::String(d.to_sql_string()))
317            }
318        }
319        Value::Str(s) => serde_json::Value::String(s.clone()),
320        Value::FixedChar(s) => serde_json::Value::String(s.trim_end_matches(' ').to_string()),
321        Value::Bytes(b) => serde_json::Value::String(format!("0x{}", hex_encode(b))),
322        Value::Temporal(t) => serde_json::Value::String(t.to_sql_string()),
323        Value::Json(text) | Value::JsonB(text) => {
324            serde_json::from_str(text).unwrap_or_else(|_| serde_json::Value::String(text.clone()))
325        }
326        Value::LegacyVector(vector) => legacy_vector_json(vector),
327        Value::Array(array) => {
328            serde_json::Value::Array(array.elements().iter().map(value_to_json).collect())
329        }
330        Value::List(items) => serde_json::Value::Array(items.iter().map(value_to_json).collect()),
331        Value::Row(values) => serde_json::Value::Object(
332            values
333                .iter()
334                .enumerate()
335                .map(|(index, value)| (format!("f{}", index + 1), value_to_json(value)))
336                .collect(),
337        ),
338        Value::Record(fields) => serde_json::Value::Object(
339            fields
340                .iter()
341                .map(|(name, value)| (name.clone(), value_to_json(value)))
342                .collect(),
343        ),
344        Value::Map(map) => {
345            let mut obj = serde_json::Map::new();
346            for (k, v) in map {
347                obj.insert(k.clone(), value_to_json(v));
348            }
349            serde_json::Value::Object(obj)
350        }
351        Value::Enum(value) => panic!(
352            "test JSON has no enum label for type OID {}",
353            value.type_oid()
354        ),
355    }
356}
357
358#[allow(dead_code)]
359pub(super) fn json_to_value(json: &serde_json::Value) -> Value {
360    match json {
361        serde_json::Value::Null => Value::Null,
362        serde_json::Value::Bool(b) => Value::Bool(*b),
363        serde_json::Value::Number(n) => {
364            if let Some(i) = n.as_i64() {
365                Value::Int(i)
366            } else if let Some(d) = DecimalValue::parse(&n.to_string()) {
367                Value::Decimal(d)
368            } else if let Some(f) = n.as_f64() {
369                Value::Float(f)
370            } else {
371                Value::Null
372            }
373        }
374        serde_json::Value::String(s) => Value::Str(s.clone()),
375        serde_json::Value::Array(arr) => Value::List(arr.iter().map(json_to_value).collect()),
376        serde_json::Value::Object(obj) => {
377            if let Ok(temporal) =
378                serde_json::from_value::<TemporalValue>(serde_json::Value::Object(obj.clone()))
379            {
380                return Value::Temporal(temporal);
381            }
382            let mut map = std::collections::BTreeMap::new();
383            for (k, v) in obj {
384                map.insert(k.clone(), json_to_value(v));
385            }
386            Value::Map(map)
387        }
388    }
389}
390
391fn ordinary(name: &str, args: &[Value]) -> Result<Value> {
392    Ok(production::evaluate(
393        name,
394        args,
395        &uqa_core::memory::ProductionControl::uncontrolled(),
396    )
397    .expect("known JSON builtin")?
398    .into_uncontrolled()
399    .expect("ordinary JSON has no lease"))
400}
401
402fn json_array_index(len: usize, key: &str) -> Option<usize> {
403    let index = key.parse::<i64>().ok()?;
404    let normalized = if index < 0 { len as i64 + index } else { index };
405    usize::try_from(normalized).ok().filter(|idx| *idx < len)
406}
407
408fn json_insert_index(len: usize, key: &str, insert_after: bool) -> Option<usize> {
409    let raw = key.parse::<i64>().ok()?;
410    let len_i64 = len as i64;
411    let index = if raw >= 0 {
412        if raw >= len_i64 {
413            len_i64
414        } else if insert_after {
415            raw + 1
416        } else {
417            raw
418        }
419    } else {
420        let normalized = len_i64 + raw;
421        if normalized < 0 {
422            0
423        } else if insert_after {
424            normalized + 1
425        } else {
426            normalized
427        }
428    };
429    usize::try_from(index.clamp(0, len_i64)).ok()
430}
431
432#[cfg(test)]
433mod pretty_tests {
434    use super::{format_jsonb_pretty, parse_json, typed_json_value, DecimalValue};
435
436    #[test]
437    fn jsonb_pretty_uses_postgresql_layout_and_key_order() {
438        let value = parse_json(r#"{"zz":1,"b":[],"aa":{"long":3,"x":2}}"#).unwrap();
439        assert_eq!(
440            format_jsonb_pretty(&value),
441            "{\n    \"b\": [\n    ],\n    \"aa\": {\n        \"x\": 2,\n        \"long\": 3\n    },\n    \"zz\": 1\n}"
442        );
443        assert_eq!(format_jsonb_pretty(&parse_json("[]").unwrap()), "[\n]");
444        assert_eq!(format_jsonb_pretty(&parse_json("{}").unwrap()), "{\n}");
445        assert_eq!(
446            format_jsonb_pretty(&parse_json("1e-1000").unwrap()),
447            DecimalValue::parse("1e-1000").unwrap().to_sql_string()
448        );
449        assert_eq!(format_jsonb_pretty(&parse_json("1.00").unwrap()), "1.00");
450        assert_eq!(format_jsonb_pretty(&parse_json("-0").unwrap()), "0");
451    }
452
453    #[test]
454    fn jsonb_rejects_numbers_outside_postgresql_numeric_range() {
455        let maximum = parse_json("1e131071").unwrap();
456        assert!(typed_json_value(&maximum, true).is_ok());
457
458        for text in ["1e131072", "1e-16384", "[1e131072]", r#"{"n":1e131072}"#] {
459            let error = typed_json_value(&parse_json(text).unwrap(), true).unwrap_err();
460            assert_eq!(error.sqlstate(), Some("22003"));
461        }
462
463        assert!(typed_json_value(&parse_json("1e200000").unwrap(), false).is_ok());
464        assert!(typed_json_value(&parse_json("0e200000").unwrap(), true).is_ok());
465    }
466}