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