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) -> Result<String> {
108    Ok(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::Enum(value) => return Err(super::catalog_output_required(value)),
130        Value::LegacyVector(vector) => legacy_vector_json(vector).to_string(),
131        Value::Array(array) => {
132            let values = array
133                .elements()
134                .iter()
135                .map(value_to_json_text)
136                .collect::<Result<Vec<_>>>()?;
137            format!("[{}]", values.join(","))
138        }
139        Value::List(values) => {
140            let values = values
141                .iter()
142                .map(value_to_json_text)
143                .collect::<Result<Vec<_>>>()?;
144            format!("[{}]", values.join(","))
145        }
146        Value::Row(values) => record_json_text(
147            values
148                .iter()
149                .enumerate()
150                .map(|(index, value)| (format!("f{}", index + 1), value)),
151        )?,
152        Value::Record(fields) => {
153            record_json_text(fields.iter().map(|(name, value)| (name.clone(), value)))?
154        }
155        Value::Map(values) => {
156            let values = values
157                .iter()
158                .map(|(key, value)| {
159                    let key = serde_json::Value::String(key.clone()).to_string();
160                    Ok(format!("{key}:{}", value_to_json_text(value)?))
161                })
162                .collect::<Result<Vec<_>>>()?;
163            format!("{{{}}}", values.join(","))
164        }
165    })
166}
167
168fn legacy_vector_json(vector: &uqa_core::LegacyVectorValue) -> serde_json::Value {
169    serde_json::Value::Array(
170        vector
171            .elements()
172            .iter()
173            .map(|value| {
174                let Value::Int(value) = value else {
175                    unreachable!("validated legacy vector element");
176                };
177                match vector.kind() {
178                    uqa_core::LegacyVectorKind::SmallInteger => (*value).into(),
179                    uqa_core::LegacyVectorKind::Oid => value.to_string().into(),
180                }
181            })
182            .collect(),
183    )
184}
185
186fn record_json_text<'a>(fields: impl IntoIterator<Item = (String, &'a Value)>) -> Result<String> {
187    let fields = fields
188        .into_iter()
189        .map(|(name, value)| {
190            let name = serde_json::Value::String(name).to_string();
191            Ok(format!("{name}:{}", value_to_json_text(value)?))
192        })
193        .collect::<Result<Vec<_>>>()?;
194    Ok(format!("{{{}}}", fields.join(",")))
195}
196
197pub(super) fn json_build_array_value(args: &[Value], jsonb: bool) -> Result<Value> {
198    let text = format!(
199        "[{}]",
200        args.iter()
201            .map(value_to_json_text)
202            .collect::<Result<Vec<_>>>()?
203            .join(", ")
204    );
205    if jsonb {
206        typed_json_value(&parse_json(&text)?, true)
207    } else {
208        Ok(Value::Json(text))
209    }
210}
211
212/// 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.
213pub fn validate_json_object_key_type(value: &Value) -> Result<()> {
214    if matches!(
215        value,
216        Value::Array(_)
217            | Value::LegacyVector(_)
218            | Value::List(_)
219            | Value::Row(_)
220            | Value::Record(_)
221            | Value::Map(_)
222            | Value::Json(_)
223            | Value::JsonB(_)
224    ) {
225        return Err(SQLError::Routine {
226            sqlstate: "22023".into(),
227            message: "key value must be scalar, not array, composite, or json".into(),
228        });
229    }
230    Ok(())
231}
232
233pub(super) fn json_build_object_value(args: &[Value], jsonb: bool) -> Result<Value> {
234    let function = if jsonb {
235        "jsonb_build_object()"
236    } else {
237        "json_build_object()"
238    };
239    if !args.len().is_multiple_of(2) {
240        return Err(SQLError::Diagnostic {
241            sqlstate: "22023".into(),
242            message: "argument list must have even number of elements".into(),
243            detail: None,
244            hint: Some(format!(
245                "The arguments of {function} must consist of alternating keys and values."
246            )),
247        });
248    }
249    let mut fields = Vec::with_capacity(args.len() / 2);
250    for (index, pair) in args.as_chunks::<2>().0.iter().enumerate() {
251        if matches!(pair[0], Value::Null) {
252            return Err(if jsonb {
253                SQLError::Routine {
254                    sqlstate: "22023".into(),
255                    message: format!("argument {}: key must not be null", index * 2 + 1),
256                }
257            } else {
258                SQLError::Routine {
259                    sqlstate: "22004".into(),
260                    message: "null value not allowed for object key".into(),
261                }
262            });
263        }
264        validate_json_object_key_type(&pair[0])?;
265        let key = serde_json::Value::String(super::value_to_string(&pair[0])?).to_string();
266        fields.push(format!("{key} : {}", value_to_json_text(&pair[1])?));
267    }
268    let text = format!("{{{}}}", fields.join(", "));
269    if jsonb {
270        typed_json_value(&parse_json(&text)?, true)
271    } else {
272        Ok(Value::Json(text))
273    }
274}
275
276#[cfg(test)]
277pub(super) fn value_to_json(v: &Value) -> serde_json::Value {
278    match v {
279        Value::Null => serde_json::Value::Null,
280        Value::Void => serde_json::Value::String(String::new()),
281        Value::Bool(b) => serde_json::Value::Bool(*b),
282        Value::Int(i) => serde_json::Value::Number((*i).into()),
283        Value::Float(f) => serde_json::Number::from_f64(*f).map_or_else(
284            || {
285                let label = if f.is_nan() {
286                    "NaN"
287                } else if f.is_sign_positive() {
288                    "Infinity"
289                } else {
290                    "-Infinity"
291                };
292                serde_json::Value::String(label.to_string())
293            },
294            serde_json::Value::Number,
295        ),
296        Value::Decimal(d) => {
297            if d.is_nan() || d.is_infinite() {
298                serde_json::Value::String(d.to_sql_string())
299            } else {
300                d.to_sql_string()
301                    .parse::<serde_json::Number>()
302                    .map(serde_json::Value::Number)
303                    .unwrap_or_else(|_| serde_json::Value::String(d.to_sql_string()))
304            }
305        }
306        Value::Str(s) => serde_json::Value::String(s.clone()),
307        Value::FixedChar(s) => serde_json::Value::String(s.trim_end_matches(' ').to_string()),
308        Value::Bytes(b) => serde_json::Value::String(format!("0x{}", hex_encode(b))),
309        Value::Temporal(t) => serde_json::Value::String(t.to_sql_string()),
310        Value::Json(text) | Value::JsonB(text) => {
311            serde_json::from_str(text).unwrap_or_else(|_| serde_json::Value::String(text.clone()))
312        }
313        Value::LegacyVector(vector) => legacy_vector_json(vector),
314        Value::Array(array) => {
315            serde_json::Value::Array(array.elements().iter().map(value_to_json).collect())
316        }
317        Value::List(items) => serde_json::Value::Array(items.iter().map(value_to_json).collect()),
318        Value::Row(values) => serde_json::Value::Object(
319            values
320                .iter()
321                .enumerate()
322                .map(|(index, value)| (format!("f{}", index + 1), value_to_json(value)))
323                .collect(),
324        ),
325        Value::Record(fields) => serde_json::Value::Object(
326            fields
327                .iter()
328                .map(|(name, value)| (name.clone(), value_to_json(value)))
329                .collect(),
330        ),
331        Value::Map(map) => {
332            let mut obj = serde_json::Map::new();
333            for (k, v) in map {
334                obj.insert(k.clone(), value_to_json(v));
335            }
336            serde_json::Value::Object(obj)
337        }
338        Value::Enum(value) => panic!(
339            "test JSON has no enum label for type OID {}",
340            value.type_oid()
341        ),
342    }
343}
344
345#[allow(dead_code)]
346pub(super) fn json_to_value(json: &serde_json::Value) -> Value {
347    match json {
348        serde_json::Value::Null => Value::Null,
349        serde_json::Value::Bool(b) => Value::Bool(*b),
350        serde_json::Value::Number(n) => {
351            if let Some(i) = n.as_i64() {
352                Value::Int(i)
353            } else if let Some(d) = DecimalValue::parse(&n.to_string()) {
354                Value::Decimal(d)
355            } else if let Some(f) = n.as_f64() {
356                Value::Float(f)
357            } else {
358                Value::Null
359            }
360        }
361        serde_json::Value::String(s) => Value::Str(s.clone()),
362        serde_json::Value::Array(arr) => Value::List(arr.iter().map(json_to_value).collect()),
363        serde_json::Value::Object(obj) => {
364            if let Ok(temporal) =
365                serde_json::from_value::<TemporalValue>(serde_json::Value::Object(obj.clone()))
366            {
367                return Value::Temporal(temporal);
368            }
369            let mut map = std::collections::BTreeMap::new();
370            for (k, v) in obj {
371                map.insert(k.clone(), json_to_value(v));
372            }
373            Value::Map(map)
374        }
375    }
376}
377
378fn ordinary(name: &str, args: &[Value]) -> Result<Value> {
379    Ok(production::evaluate(
380        name,
381        args,
382        &uqa_core::memory::ProductionControl::uncontrolled(),
383    )
384    .expect("known JSON builtin")?
385    .into_uncontrolled()
386    .expect("ordinary JSON has no lease"))
387}
388
389fn json_array_index(len: usize, key: &str) -> Option<usize> {
390    let index = key.parse::<i64>().ok()?;
391    let normalized = if index < 0 { len as i64 + index } else { index };
392    usize::try_from(normalized).ok().filter(|idx| *idx < len)
393}
394
395fn json_insert_index(len: usize, key: &str, insert_after: bool) -> Option<usize> {
396    let raw = key.parse::<i64>().ok()?;
397    let len_i64 = len as i64;
398    let index = if raw >= 0 {
399        if raw >= len_i64 {
400            len_i64
401        } else if insert_after {
402            raw + 1
403        } else {
404            raw
405        }
406    } else {
407        let normalized = len_i64 + raw;
408        if normalized < 0 {
409            0
410        } else if insert_after {
411            normalized + 1
412        } else {
413            normalized
414        }
415    };
416    usize::try_from(index.clamp(0, len_i64)).ok()
417}
418
419#[cfg(test)]
420mod pretty_tests {
421    use super::{format_jsonb_pretty, parse_json, typed_json_value, DecimalValue};
422
423    #[test]
424    fn jsonb_pretty_uses_postgresql_layout_and_key_order() {
425        let value = parse_json(r#"{"zz":1,"b":[],"aa":{"long":3,"x":2}}"#).unwrap();
426        assert_eq!(
427            format_jsonb_pretty(&value),
428            "{\n    \"b\": [\n    ],\n    \"aa\": {\n        \"x\": 2,\n        \"long\": 3\n    },\n    \"zz\": 1\n}"
429        );
430        assert_eq!(format_jsonb_pretty(&parse_json("[]").unwrap()), "[\n]");
431        assert_eq!(format_jsonb_pretty(&parse_json("{}").unwrap()), "{\n}");
432        assert_eq!(
433            format_jsonb_pretty(&parse_json("1e-1000").unwrap()),
434            DecimalValue::parse("1e-1000").unwrap().to_sql_string()
435        );
436        assert_eq!(format_jsonb_pretty(&parse_json("1.00").unwrap()), "1.00");
437        assert_eq!(format_jsonb_pretty(&parse_json("-0").unwrap()), "0");
438    }
439
440    #[test]
441    fn jsonb_rejects_numbers_outside_postgresql_numeric_range() {
442        let maximum = parse_json("1e131071").unwrap();
443        assert!(typed_json_value(&maximum, true).is_ok());
444
445        for text in ["1e131072", "1e-16384", "[1e131072]", r#"{"n":1e131072}"#] {
446            let error = typed_json_value(&parse_json(text).unwrap(), true).unwrap_err();
447            assert_eq!(error.sqlstate(), Some("22003"));
448        }
449
450        assert!(typed_json_value(&parse_json("1e200000").unwrap(), false).is_ok());
451        assert!(typed_json_value(&parse_json("0e200000").unwrap(), true).is_ok());
452    }
453}