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::{jsonb_equality_key, DecimalValue, TemporalValue, Value};
10
11use crate::error::{Result, SQLError};
12
13use super::{hex_encode, out_of_range, value_to_string};
14
15pub(super) fn parse_json(s: &str) -> Result<serde_json::Value> {
16    serde_json::from_str::<serde_json::Value>(s)
17        .map_err(|e| SQLError::TypeMismatch(format!("invalid JSON: {e}")))
18}
19
20/// Render a parsed JSON value in `PostgreSQL`'s compact result format. JSONB
21/// objects use `PostgreSQL`'s length-then-bytewise key ordering.
22pub(super) fn format_json(value: &serde_json::Value, jsonb: bool) -> String {
23    if !jsonb {
24        return serde_json::to_string(value).expect("serializing a JSON value cannot fail");
25    }
26    match value {
27        serde_json::Value::Null => "null".to_string(),
28        serde_json::Value::Bool(value) => value.to_string(),
29        serde_json::Value::Number(value) => {
30            let text = value.to_string();
31            DecimalValue::parse(&text).map_or(text, |value| value.to_sql_string())
32        }
33        serde_json::Value::String(value) => serde_json::Value::String(value.clone()).to_string(),
34        serde_json::Value::Array(values) => {
35            let values = values
36                .iter()
37                .map(|value| format_json(value, true))
38                .collect::<Vec<_>>();
39            format!("[{}]", values.join(", "))
40        }
41        serde_json::Value::Object(values) => {
42            let mut values = values.iter().collect::<Vec<_>>();
43            values.sort_by(|(left, _), (right, _)| {
44                left.len()
45                    .cmp(&right.len())
46                    .then_with(|| left.as_bytes().cmp(right.as_bytes()))
47            });
48            let values = values
49                .into_iter()
50                .map(|(key, value)| {
51                    let key = serde_json::Value::String(key.clone()).to_string();
52                    format!("{key}: {}", format_json(value, true))
53                })
54                .collect::<Vec<_>>();
55            format!("{{{}}}", values.join(", "))
56        }
57    }
58}
59
60pub(super) fn format_jsonb_pretty(value: &serde_json::Value) -> String {
61    format_jsonb_pretty_at_depth(value, 0)
62}
63
64fn format_jsonb_pretty_at_depth(value: &serde_json::Value, depth: usize) -> String {
65    let indent = " ".repeat(depth * 4);
66    let child_indent = " ".repeat((depth + 1) * 4);
67    match value {
68        serde_json::Value::Array(values) => {
69            if values.is_empty() {
70                return format!("[\n{indent}]");
71            }
72            let values = values
73                .iter()
74                .map(|value| {
75                    format!(
76                        "{child_indent}{}",
77                        format_jsonb_pretty_at_depth(value, depth + 1)
78                    )
79                })
80                .collect::<Vec<_>>();
81            format!("[\n{}\n{indent}]", values.join(",\n"))
82        }
83        serde_json::Value::Object(values) => {
84            if values.is_empty() {
85                return format!("{{\n{indent}}}");
86            }
87            let mut values = values.iter().collect::<Vec<_>>();
88            values.sort_by(|(left, _), (right, _)| {
89                left.len()
90                    .cmp(&right.len())
91                    .then_with(|| left.as_bytes().cmp(right.as_bytes()))
92            });
93            let values = values
94                .into_iter()
95                .map(|(key, value)| {
96                    let key = serde_json::Value::String(key.clone()).to_string();
97                    format!(
98                        "{child_indent}{key}: {}",
99                        format_jsonb_pretty_at_depth(value, depth + 1)
100                    )
101                })
102                .collect::<Vec<_>>();
103            format!("{{\n{}\n{indent}}}", values.join(",\n"))
104        }
105        _ => format_json(value, true),
106    }
107}
108
109pub(super) fn typed_json_value(value: &serde_json::Value, jsonb: bool) -> Result<Value> {
110    if jsonb {
111        validate_jsonb_numbers(value)?;
112    }
113    let text = format_json(value, jsonb);
114    if jsonb {
115        Ok(Value::JsonB(text))
116    } else {
117        Ok(Value::Json(text))
118    }
119}
120
121fn validate_jsonb_numbers(value: &serde_json::Value) -> Result<()> {
122    match value {
123        serde_json::Value::Number(value) => DecimalValue::parse(&value.to_string())
124            .map(|_| ())
125            .ok_or_else(|| out_of_range("numeric")),
126        serde_json::Value::Array(values) => values.iter().try_for_each(validate_jsonb_numbers),
127        serde_json::Value::Object(values) => values.values().try_for_each(validate_jsonb_numbers),
128        _ => Ok(()),
129    }
130}
131
132/// Render an engine value as `PostgreSQL` JSON input text without losing the
133/// lexical representation of values already typed as `json` or `jsonb`.
134pub fn value_to_json_text(value: &Value) -> String {
135    match value {
136        Value::Null => "null".to_string(),
137        Value::Bool(value) => value.to_string(),
138        Value::Int(value) => value.to_string(),
139        Value::Float(value) => match serde_json::Number::from_f64(*value) {
140            Some(number) => number.to_string(),
141            None if value.is_nan() => "\"NaN\"".to_string(),
142            None if value.is_sign_positive() => "\"Infinity\"".to_string(),
143            None => "\"-Infinity\"".to_string(),
144        },
145        Value::Decimal(value) => value.to_sql_string(),
146        Value::Str(value) => serde_json::Value::String(value.clone()).to_string(),
147        Value::FixedChar(value) => {
148            serde_json::Value::String(value.trim_end_matches(' ').to_string()).to_string()
149        }
150        Value::Bytes(value) => {
151            serde_json::Value::String(format!("0x{}", hex_encode(value))).to_string()
152        }
153        Value::Temporal(value) => serde_json::Value::String(value.to_sql_string()).to_string(),
154        Value::Json(text) | Value::JsonB(text) => text.clone(),
155        Value::Array(array) => {
156            let values = array
157                .elements()
158                .iter()
159                .map(value_to_json_text)
160                .collect::<Vec<_>>();
161            format!("[{}]", values.join(","))
162        }
163        Value::List(values) => {
164            let values = values.iter().map(value_to_json_text).collect::<Vec<_>>();
165            format!("[{}]", values.join(","))
166        }
167        Value::Row(values) => record_json_text(
168            values
169                .iter()
170                .enumerate()
171                .map(|(index, value)| (format!("f{}", index + 1), value)),
172        ),
173        Value::Record(fields) => {
174            record_json_text(fields.iter().map(|(name, value)| (name.clone(), value)))
175        }
176        Value::Map(values) => {
177            let values = values
178                .iter()
179                .map(|(key, value)| {
180                    let key = serde_json::Value::String(key.clone()).to_string();
181                    format!("{key}:{}", value_to_json_text(value))
182                })
183                .collect::<Vec<_>>();
184            format!("{{{}}}", values.join(","))
185        }
186    }
187}
188
189fn record_json_text<'a>(fields: impl IntoIterator<Item = (String, &'a Value)>) -> String {
190    let fields = fields
191        .into_iter()
192        .map(|(name, value)| {
193            let name = serde_json::Value::String(name).to_string();
194            format!("{name}:{}", value_to_json_text(value))
195        })
196        .collect::<Vec<_>>();
197    format!("{{{}}}", fields.join(","))
198}
199
200pub(super) fn json_build_array_value(args: &[Value], jsonb: bool) -> Result<Value> {
201    let text = format!(
202        "[{}]",
203        args.iter()
204            .map(value_to_json_text)
205            .collect::<Vec<_>>()
206            .join(", ")
207    );
208    if jsonb {
209        typed_json_value(&parse_json(&text)?, true)
210    } else {
211        Ok(Value::Json(text))
212    }
213}
214
215pub(super) fn json_build_object_value(args: &[Value], jsonb: bool) -> Result<Value> {
216    if !args.len().is_multiple_of(2) {
217        return Err(SQLError::TypeMismatch(
218            "json_build_object requires an even number of args".into(),
219        ));
220    }
221    let mut fields = Vec::with_capacity(args.len() / 2);
222    for pair in args.chunks_exact(2) {
223        if matches!(pair[0], Value::Null) {
224            return Err(SQLError::TypeMismatch(
225                "json_build_object key must not be NULL".into(),
226            ));
227        }
228        let key = serde_json::Value::String(value_to_string(&pair[0])).to_string();
229        fields.push(format!("{key} : {}", value_to_json_text(&pair[1])));
230    }
231    let text = format!("{{{}}}", fields.join(", "));
232    if jsonb {
233        typed_json_value(&parse_json(&text)?, true)
234    } else {
235        Ok(Value::Json(text))
236    }
237}
238
239pub(super) fn value_to_json(v: &Value) -> serde_json::Value {
240    match v {
241        Value::Null => serde_json::Value::Null,
242        Value::Bool(b) => serde_json::Value::Bool(*b),
243        Value::Int(i) => serde_json::Value::Number((*i).into()),
244        Value::Float(f) => serde_json::Number::from_f64(*f).map_or_else(
245            || {
246                let label = if f.is_nan() {
247                    "NaN"
248                } else if f.is_sign_positive() {
249                    "Infinity"
250                } else {
251                    "-Infinity"
252                };
253                serde_json::Value::String(label.to_string())
254            },
255            serde_json::Value::Number,
256        ),
257        Value::Decimal(d) => {
258            if d.is_nan() || d.is_infinite() {
259                serde_json::Value::String(d.to_sql_string())
260            } else {
261                d.to_sql_string()
262                    .parse::<serde_json::Number>()
263                    .map(serde_json::Value::Number)
264                    .unwrap_or_else(|_| serde_json::Value::String(d.to_sql_string()))
265            }
266        }
267        Value::Str(s) => serde_json::Value::String(s.clone()),
268        Value::FixedChar(s) => serde_json::Value::String(s.trim_end_matches(' ').to_string()),
269        Value::Bytes(b) => serde_json::Value::String(format!("0x{}", hex_encode(b))),
270        Value::Temporal(t) => serde_json::Value::String(t.to_sql_string()),
271        Value::Json(text) | Value::JsonB(text) => {
272            serde_json::from_str(text).unwrap_or_else(|_| serde_json::Value::String(text.clone()))
273        }
274        Value::Array(array) => {
275            serde_json::Value::Array(array.elements().iter().map(value_to_json).collect())
276        }
277        Value::List(items) => serde_json::Value::Array(items.iter().map(value_to_json).collect()),
278        Value::Row(values) => serde_json::Value::Object(
279            values
280                .iter()
281                .enumerate()
282                .map(|(index, value)| (format!("f{}", index + 1), value_to_json(value)))
283                .collect(),
284        ),
285        Value::Record(fields) => serde_json::Value::Object(
286            fields
287                .iter()
288                .map(|(name, value)| (name.clone(), value_to_json(value)))
289                .collect(),
290        ),
291        Value::Map(map) => {
292            let mut obj = serde_json::Map::new();
293            for (k, v) in map {
294                obj.insert(k.clone(), value_to_json(v));
295            }
296            serde_json::Value::Object(obj)
297        }
298    }
299}
300
301#[allow(dead_code)]
302pub(super) fn json_to_value(json: &serde_json::Value) -> Value {
303    match json {
304        serde_json::Value::Null => Value::Null,
305        serde_json::Value::Bool(b) => Value::Bool(*b),
306        serde_json::Value::Number(n) => {
307            if let Some(i) = n.as_i64() {
308                Value::Int(i)
309            } else if let Some(d) = DecimalValue::parse(&n.to_string()) {
310                Value::Decimal(d)
311            } else if let Some(f) = n.as_f64() {
312                Value::Float(f)
313            } else {
314                Value::Null
315            }
316        }
317        serde_json::Value::String(s) => Value::Str(s.clone()),
318        serde_json::Value::Array(arr) => Value::List(arr.iter().map(json_to_value).collect()),
319        serde_json::Value::Object(obj) => {
320            if let Ok(temporal) =
321                serde_json::from_value::<TemporalValue>(serde_json::Value::Object(obj.clone()))
322            {
323                return Value::Temporal(temporal);
324            }
325            let mut map = std::collections::BTreeMap::new();
326            for (k, v) in obj {
327                map.insert(k.clone(), json_to_value(v));
328            }
329            Value::Map(map)
330        }
331    }
332}
333
334pub(super) fn json_typeof(v: &serde_json::Value) -> &'static str {
335    match v {
336        serde_json::Value::Null => "null",
337        serde_json::Value::Bool(_) => "boolean",
338        serde_json::Value::Number(_) => "number",
339        serde_json::Value::String(_) => "string",
340        serde_json::Value::Array(_) => "array",
341        serde_json::Value::Object(_) => "object",
342    }
343}
344
345pub(super) fn json_extract_path(args: &[Value], as_text: bool, jsonb: bool) -> Result<Value> {
346    if args.len() < 2 {
347        return Err(SQLError::TypeMismatch(
348            "json_extract_path takes 2+ args".into(),
349        ));
350    }
351    let jsonb = jsonb || matches!(args[0], Value::JsonB(_));
352    let mut current = parse_json(&value_to_string(&args[0]))?;
353    for key in &args[1..] {
354        let key_str = value_to_string(key);
355        current = match current {
356            serde_json::Value::Object(mut obj) => {
357                obj.remove(&key_str).unwrap_or(serde_json::Value::Null)
358            }
359            serde_json::Value::Array(arr) => json_array_index(arr.len(), &key_str)
360                .and_then(|idx| arr.into_iter().nth(idx))
361                .unwrap_or(serde_json::Value::Null),
362            _ => serde_json::Value::Null,
363        };
364    }
365    if as_text {
366        Ok(Value::Str(match current {
367            serde_json::Value::String(s) => s,
368            serde_json::Value::Null => return Ok(Value::Null),
369            other => format_json(&other, jsonb),
370        }))
371    } else if matches!(current, serde_json::Value::Null) {
372        Ok(Value::Null)
373    } else {
374        typed_json_value(&current, jsonb)
375    }
376}
377
378fn json_array_index(len: usize, key: &str) -> Option<usize> {
379    let index = key.parse::<i64>().ok()?;
380    let normalized = if index < 0 { len as i64 + index } else { index };
381    usize::try_from(normalized).ok().filter(|idx| *idx < len)
382}
383
384pub(super) fn json_contains(args: &[Value]) -> Result<Value> {
385    if args.len() != 2 {
386        return Err(SQLError::TypeMismatch("json_contains takes 2 args".into()));
387    }
388    let lhs = parse_json(&value_to_string(&args[0]))?;
389    let rhs = parse_json(&value_to_string(&args[1]))?;
390    Ok(Value::Bool(json_contains_value(&lhs, &rhs)))
391}
392
393pub(super) fn json_contained_by(args: &[Value]) -> Result<Value> {
394    if args.len() != 2 {
395        return Err(SQLError::TypeMismatch(
396            "json_contained_by takes 2 args".into(),
397        ));
398    }
399    let lhs = parse_json(&value_to_string(&args[0]))?;
400    let rhs = parse_json(&value_to_string(&args[1]))?;
401    Ok(Value::Bool(json_contains_value(&rhs, &lhs)))
402}
403
404fn json_contains_value(lhs: &serde_json::Value, rhs: &serde_json::Value) -> bool {
405    json_contains_value_at_depth(lhs, rhs, true)
406}
407
408fn json_contains_value_at_depth(
409    lhs: &serde_json::Value,
410    rhs: &serde_json::Value,
411    top_level: bool,
412) -> bool {
413    match (lhs, rhs) {
414        (serde_json::Value::Object(l), serde_json::Value::Object(r)) => r.iter().all(|(k, rv)| {
415            l.get(k)
416                .is_some_and(|lv| json_contains_value_at_depth(lv, rv, false))
417        }),
418        (serde_json::Value::Array(l), serde_json::Value::Array(r)) => r.iter().all(|rv| {
419            l.iter()
420                .any(|lv| json_contains_value_at_depth(lv, rv, false))
421        }),
422        (serde_json::Value::Array(l), r) if top_level && jsonb_is_primitive(r) => l
423            .iter()
424            .any(|lv| json_contains_value_at_depth(lv, r, false)),
425        _ => jsonb_values_equal(lhs, rhs),
426    }
427}
428
429fn jsonb_is_primitive(value: &serde_json::Value) -> bool {
430    !matches!(
431        value,
432        serde_json::Value::Array(_) | serde_json::Value::Object(_)
433    )
434}
435
436fn jsonb_values_equal(lhs: &serde_json::Value, rhs: &serde_json::Value) -> bool {
437    let lhs = serde_json::to_string(lhs).expect("serializing parsed JSON cannot fail");
438    let rhs = serde_json::to_string(rhs).expect("serializing parsed JSON cannot fail");
439    match (jsonb_equality_key(&lhs), jsonb_equality_key(&rhs)) {
440        (Some(lhs), Some(rhs)) => lhs == rhs,
441        _ => false,
442    }
443}
444
445pub(super) fn json_has_key(args: &[Value]) -> Result<Value> {
446    if args.len() != 2 {
447        return Err(SQLError::TypeMismatch("json_has_key takes 2 args".into()));
448    }
449    let obj = parse_json(&value_to_string(&args[0]))?;
450    let key = value_to_string(&args[1]);
451    Ok(Value::Bool(match obj {
452        serde_json::Value::Object(map) => map.contains_key(&key),
453        serde_json::Value::Array(items) => items
454            .iter()
455            .any(|item| matches!(item, serde_json::Value::String(value) if value == &key)),
456        _ => false,
457    }))
458}
459
460pub(super) fn json_has_keys(args: &[Value], require_all: bool) -> Result<Value> {
461    if args.len() != 2 {
462        return Err(SQLError::TypeMismatch("json_has_keys takes 2 args".into()));
463    }
464    let obj = parse_json(&value_to_string(&args[0]))?;
465    let keys = match &args[1] {
466        Value::Array(array) => array_strings(array.elements()),
467        Value::List(items) => array_strings(items),
468        other => {
469            return Err(SQLError::TypeMismatch(format!(
470                "json key list must be array, got {other:?}"
471            )));
472        }
473    };
474    let found = |key: &String| match &obj {
475        serde_json::Value::Object(map) => map.contains_key(key),
476        serde_json::Value::Array(items) => items
477            .iter()
478            .any(|item| matches!(item, serde_json::Value::String(value) if value == key)),
479        _ => false,
480    };
481    Ok(Value::Bool(if require_all {
482        keys.iter().all(found)
483    } else {
484        keys.iter().any(found)
485    }))
486}
487
488pub(super) fn jsonpath_exists(args: &[Value]) -> Result<Value> {
489    if args.len() < 2 {
490        return Err(SQLError::TypeMismatch(
491            "jsonpath_exists takes at least 2 args".into(),
492        ));
493    }
494    let root = parse_json(&value_to_string(&args[0]))?;
495    let path = value_to_string(&args[1]);
496    Ok(Value::Bool(jsonpath_exists_value(&root, &path)?))
497}
498
499pub(super) fn jsonpath_match(args: &[Value]) -> Result<Value> {
500    if args.len() < 2 {
501        return Err(SQLError::TypeMismatch(
502            "jsonpath_match takes at least 2 args".into(),
503        ));
504    }
505    let root = parse_json(&value_to_string(&args[0]))?;
506    let path = value_to_string(&args[1]);
507    Ok(Value::Bool(jsonpath_match_value(&root, &path)?))
508}
509
510pub(super) fn jsonpath_candidate(args: &[Value]) -> bool {
511    matches!(args.get(1), Some(Value::Str(path)) if path.trim_start().starts_with('$'))
512        && matches!(
513            args.first(),
514            Some(Value::Json(_) | Value::JsonB(_) | Value::Map(_) | Value::List(_))
515        )
516}
517
518fn jsonpath_exists_value(root: &serde_json::Value, path: &str) -> Result<bool> {
519    let path = normalize_jsonpath(path);
520    let (selector, filter) = split_jsonpath_filter(&path);
521    let values = jsonpath_select(root, selector)?;
522    let Some(filter) = filter else {
523        return Ok(!values.is_empty());
524    };
525    for value in values {
526        if eval_jsonpath_predicate(root, &value, filter)? {
527            return Ok(true);
528        }
529    }
530    Ok(false)
531}
532
533fn jsonpath_match_value(root: &serde_json::Value, path: &str) -> Result<bool> {
534    let path = normalize_jsonpath(path);
535    if let Some((left, op, right)) = split_jsonpath_comparison(&path) {
536        let values = if left == "@" {
537            vec![root.clone()]
538        } else {
539            jsonpath_select(root, left)?
540        };
541        let rhs = parse_jsonpath_literal(right)?;
542        return Ok(values
543            .iter()
544            .any(|value| compare_jsonpath_values(value, &rhs, op)));
545    }
546    let values = jsonpath_select(root, &path)?;
547    Ok(values
548        .first()
549        .is_some_and(|value| matches!(value, serde_json::Value::Bool(true))))
550}
551
552fn normalize_jsonpath(path: &str) -> String {
553    let path = path.trim();
554    path.strip_prefix("strict ")
555        .or_else(|| path.strip_prefix("lax "))
556        .unwrap_or(path)
557        .trim()
558        .to_string()
559}
560
561fn split_jsonpath_filter(path: &str) -> (&str, Option<&str>) {
562    let Some(pos) = path.find('?') else {
563        return (path.trim(), None);
564    };
565    let selector = path[..pos].trim();
566    let filter = path[pos + 1..].trim();
567    (selector, Some(strip_wrapping_parens(filter)))
568}
569
570fn strip_wrapping_parens(input: &str) -> &str {
571    let input = input.trim();
572    if input.starts_with('(') && input.ends_with(')') {
573        input[1..input.len() - 1].trim()
574    } else {
575        input
576    }
577}
578
579fn jsonpath_select(root: &serde_json::Value, selector: &str) -> Result<Vec<serde_json::Value>> {
580    let mut rest = selector.trim();
581    if rest == "$" {
582        return Ok(vec![root.clone()]);
583    }
584    let Some(after_root) = rest.strip_prefix('$') else {
585        return Err(SQLError::TypeMismatch(format!(
586            "jsonpath selector must start with $, got {selector:?}"
587        )));
588    };
589    rest = after_root;
590    let mut current = vec![root.clone()];
591    while !rest.is_empty() {
592        if let Some(next) = rest.strip_prefix('.') {
593            let (key, after_key) = take_jsonpath_key(next)?;
594            current = current
595                .into_iter()
596                .filter_map(|value| match value {
597                    serde_json::Value::Object(map) => map.get(key).cloned(),
598                    _ => None,
599                })
600                .collect();
601            rest = after_key;
602        } else if let Some(next) = rest.strip_prefix("[*]") {
603            current = current
604                .into_iter()
605                .flat_map(|value| match value {
606                    serde_json::Value::Array(items) => items,
607                    _ => Vec::new(),
608                })
609                .collect();
610            rest = next;
611        } else if let Some(next) = rest.strip_prefix('[') {
612            let Some(end) = next.find(']') else {
613                return Err(SQLError::TypeMismatch(format!(
614                    "unterminated jsonpath array index in {selector:?}"
615                )));
616            };
617            let index = &next[..end];
618            current = current
619                .into_iter()
620                .filter_map(|value| match value {
621                    serde_json::Value::Array(items) => {
622                        json_array_index(items.len(), index).and_then(|idx| items.get(idx).cloned())
623                    }
624                    _ => None,
625                })
626                .collect();
627            rest = &next[end + 1..];
628        } else {
629            return Err(SQLError::TypeMismatch(format!(
630                "unsupported jsonpath selector tail {rest:?}"
631            )));
632        }
633    }
634    Ok(current)
635}
636
637fn take_jsonpath_key(input: &str) -> Result<(&str, &str)> {
638    if let Some(quoted) = input.strip_prefix('"') {
639        let Some(end) = quoted.find('"') else {
640            return Err(SQLError::TypeMismatch(
641                "unterminated quoted jsonpath key".into(),
642            ));
643        };
644        return Ok((&quoted[..end], &quoted[end + 1..]));
645    }
646    let end = input
647        .find(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
648        .unwrap_or(input.len());
649    if end == 0 {
650        return Err(SQLError::TypeMismatch(format!(
651            "expected jsonpath key in {input:?}"
652        )));
653    }
654    Ok((&input[..end], &input[end..]))
655}
656
657fn eval_jsonpath_predicate(
658    root: &serde_json::Value,
659    current: &serde_json::Value,
660    predicate: &str,
661) -> Result<bool> {
662    if let Some((left, op, right)) = split_jsonpath_comparison(predicate) {
663        let values = if left == "@" {
664            vec![current.clone()]
665        } else if let Some(selector) = left.strip_prefix('@') {
666            jsonpath_select(current, &format!("${selector}"))?
667        } else {
668            jsonpath_select(root, left)?
669        };
670        let rhs = parse_jsonpath_literal(right)?;
671        return Ok(values
672            .iter()
673            .any(|value| compare_jsonpath_values(value, &rhs, op)));
674    }
675    let values = if predicate == "@" {
676        vec![current.clone()]
677    } else {
678        jsonpath_select(root, predicate)?
679    };
680    Ok(!values.is_empty())
681}
682
683fn split_jsonpath_comparison(input: &str) -> Option<(&str, &'static str, &str)> {
684    for op in ["==", "!=", ">=", "<=", ">", "<"] {
685        if let Some(pos) = input.find(op) {
686            return Some((input[..pos].trim(), op, input[pos + op.len()..].trim()));
687        }
688    }
689    None
690}
691
692fn parse_jsonpath_literal(input: &str) -> Result<serde_json::Value> {
693    let input = input.trim();
694    if let Ok(value) = serde_json::from_str::<serde_json::Value>(input) {
695        return Ok(value);
696    }
697    if input.starts_with('"') && input.ends_with('"') {
698        return Ok(serde_json::Value::String(
699            input[1..input.len() - 1].to_string(),
700        ));
701    }
702    Err(SQLError::TypeMismatch(format!(
703        "unsupported jsonpath literal {input:?}"
704    )))
705}
706
707fn compare_jsonpath_values(lhs: &serde_json::Value, rhs: &serde_json::Value, op: &str) -> bool {
708    match (lhs, rhs) {
709        (serde_json::Value::Number(left), serde_json::Value::Number(right)) => {
710            let Some(left) = left.as_f64() else {
711                return false;
712            };
713            let Some(right) = right.as_f64() else {
714                return false;
715            };
716            compare_f64(left, right, op)
717        }
718        (serde_json::Value::String(left), serde_json::Value::String(right)) => {
719            compare_ordering(left.cmp(right), op)
720        }
721        (serde_json::Value::Bool(left), serde_json::Value::Bool(right)) => {
722            compare_ordering(left.cmp(right), op)
723        }
724        (serde_json::Value::Null, serde_json::Value::Null) => matches!(op, "==" | ">=" | "<="),
725        _ => matches!(op, "!="),
726    }
727}
728
729fn compare_f64(left: f64, right: f64, op: &str) -> bool {
730    match op {
731        "==" => left == right,
732        "!=" => left != right,
733        ">" => left > right,
734        ">=" => left >= right,
735        "<" => left < right,
736        "<=" => left <= right,
737        _ => false,
738    }
739}
740
741fn compare_ordering(ordering: std::cmp::Ordering, op: &str) -> bool {
742    match op {
743        "==" => ordering.is_eq(),
744        "!=" => !ordering.is_eq(),
745        ">" => ordering.is_gt(),
746        ">=" => ordering.is_ge(),
747        "<" => ordering.is_lt(),
748        "<=" => ordering.is_le(),
749        _ => false,
750    }
751}
752
753pub(super) fn json_concat(args: &[Value]) -> Result<Option<Value>> {
754    if args.len() != 2 {
755        return Err(SQLError::TypeMismatch("json_concat takes 2 args".into()));
756    }
757    if !args.iter().any(|arg| matches!(arg, Value::JsonB(_))) {
758        return Ok(None);
759    }
760    let lhs = value_to_json(&args[0]);
761    let rhs = value_to_json(&args[1]);
762    let out = match (lhs, rhs) {
763        (serde_json::Value::Object(mut left), serde_json::Value::Object(right)) => {
764            for (key, value) in right {
765                left.insert(key, value);
766            }
767            serde_json::Value::Object(left)
768        }
769        (serde_json::Value::Array(mut left), serde_json::Value::Array(right)) => {
770            left.extend(right);
771            serde_json::Value::Array(left)
772        }
773        (serde_json::Value::Array(mut left), right) => {
774            left.push(right);
775            serde_json::Value::Array(left)
776        }
777        (left, serde_json::Value::Array(mut right)) => {
778            let mut out = vec![left];
779            out.append(&mut right);
780            serde_json::Value::Array(out)
781        }
782        (left, right) => serde_json::Value::Array(vec![left, right]),
783    };
784    typed_json_value(&out, true).map(Some)
785}
786
787pub(super) fn json_delete(args: &[Value]) -> Result<Option<Value>> {
788    if args.len() != 2 {
789        return Err(SQLError::TypeMismatch("json_delete takes 2 args".into()));
790    }
791    if !matches!(args[0], Value::JsonB(_) | Value::Map(_) | Value::List(_)) {
792        return Ok(None);
793    }
794    let mut target = value_to_json(&args[0]);
795    match &args[1] {
796        Value::Int(index) => delete_array_index(&mut target, *index),
797        Value::Array(array) => {
798            for key in array_strings(array.elements()) {
799                delete_key_or_string(&mut target, &key);
800            }
801        }
802        Value::List(keys) => {
803            for key in array_strings(keys) {
804                delete_key_or_string(&mut target, &key);
805            }
806        }
807        key => delete_key_or_string(&mut target, &value_to_string(key)),
808    }
809    typed_json_value(&target, true).map(Some)
810}
811
812pub(super) fn json_delete_path(args: &[Value]) -> Result<Value> {
813    if args.len() != 2 {
814        return Err(SQLError::TypeMismatch(
815            "json_delete_path takes 2 args".into(),
816        ));
817    }
818    let mut target = value_to_json(&args[0]);
819    let path = path_arg(&args[1])?;
820    delete_path(&mut target, &path);
821    typed_json_value(&target, true)
822}
823
824fn path_arg(value: &Value) -> Result<Vec<String>> {
825    match value {
826        Value::Array(array) => Ok(array_strings(array.elements())),
827        Value::List(items) => Ok(array_strings(items)),
828        Value::Str(s) => Ok(s
829            .trim_matches(|c| c == '{' || c == '}')
830            .split(',')
831            .filter(|part| !part.is_empty())
832            .map(|part| part.trim().to_string())
833            .collect()),
834        other => Err(SQLError::TypeMismatch(format!(
835            "JSON path must be an array, got {other:?}"
836        ))),
837    }
838}
839
840fn array_strings(values: &[Value]) -> Vec<String> {
841    fn append(values: &[Value], output: &mut Vec<String>) {
842        for value in values {
843            if let Value::List(nested) = value {
844                append(nested, output);
845            } else {
846                output.push(value_to_string(value));
847            }
848        }
849    }
850
851    let mut output = Vec::new();
852    append(values, &mut output);
853    output
854}
855
856fn delete_key_or_string(target: &mut serde_json::Value, key: &str) {
857    match target {
858        serde_json::Value::Object(map) => {
859            map.remove(key);
860        }
861        serde_json::Value::Array(items) => {
862            items.retain(|item| !matches!(item, serde_json::Value::String(value) if value == key));
863        }
864        _ => {}
865    }
866}
867
868fn delete_array_index(target: &mut serde_json::Value, index: i64) {
869    let serde_json::Value::Array(items) = target else {
870        return;
871    };
872    let normalized = if index < 0 {
873        items.len() as i64 + index
874    } else {
875        index
876    };
877    if let Ok(index) = usize::try_from(normalized) {
878        if index < items.len() {
879            items.remove(index);
880        }
881    }
882}
883
884fn delete_path(target: &mut serde_json::Value, path: &[String]) {
885    let Some((head, rest)) = path.split_first() else {
886        return;
887    };
888    if rest.is_empty() {
889        match target {
890            serde_json::Value::Object(map) => {
891                map.remove(head);
892            }
893            serde_json::Value::Array(items) => {
894                if let Some(index) = json_array_index(items.len(), head) {
895                    items.remove(index);
896                }
897            }
898            _ => {}
899        }
900        return;
901    }
902    match target {
903        serde_json::Value::Object(map) => {
904            if let Some(next) = map.get_mut(head) {
905                delete_path(next, rest);
906            }
907        }
908        serde_json::Value::Array(items) => {
909            if let Some(index) = json_array_index(items.len(), head) {
910                delete_path(&mut items[index], rest);
911            }
912        }
913        _ => {}
914    }
915}
916
917pub(super) fn jsonb_set(args: &[Value]) -> Result<Value> {
918    if !(3..=4).contains(&args.len()) {
919        return Err(SQLError::TypeMismatch("jsonb_set takes 3-4 args".into()));
920    }
921    let mut current = parse_json(&value_to_string(&args[0]))?;
922    let path = path_arg(&args[1])?;
923    let new_val = parse_json(&value_to_string(&args[2]))
924        .unwrap_or_else(|_| serde_json::Value::String(value_to_string(&args[2])));
925    let create_missing = args.get(3).is_none_or(|value| match value {
926        Value::Bool(value) => *value,
927        Value::Null => false,
928        other => value_to_string(other).eq_ignore_ascii_case("true"),
929    });
930    json_set_path(&mut current, &path, new_val, create_missing);
931    typed_json_value(&current, true)
932}
933
934pub(super) fn jsonb_insert(args: &[Value]) -> Result<Value> {
935    if !(3..=4).contains(&args.len()) {
936        return Err(SQLError::TypeMismatch("jsonb_insert takes 3-4 args".into()));
937    }
938    let mut current = parse_json(&value_to_string(&args[0]))?;
939    let path = path_arg(&args[1])?;
940    let new_val = parse_json(&value_to_string(&args[2]))
941        .unwrap_or_else(|_| serde_json::Value::String(value_to_string(&args[2])));
942    let insert_after = args.get(3).is_some_and(|value| match value {
943        Value::Bool(value) => *value,
944        other => value_to_string(other).eq_ignore_ascii_case("true"),
945    });
946    json_insert_path(&mut current, &path, new_val, insert_after);
947    typed_json_value(&current, true)
948}
949
950fn json_insert_path(
951    current: &mut serde_json::Value,
952    path: &[String],
953    new_val: serde_json::Value,
954    insert_after: bool,
955) -> bool {
956    let Some((head, rest)) = path.split_first() else {
957        return false;
958    };
959    if rest.is_empty() {
960        return match current {
961            serde_json::Value::Object(map) => {
962                if map.contains_key(head) {
963                    false
964                } else {
965                    map.insert(head.clone(), new_val);
966                    true
967                }
968            }
969            serde_json::Value::Array(items) => {
970                let Some(index) = json_insert_index(items.len(), head, insert_after) else {
971                    return false;
972                };
973                items.insert(index, new_val);
974                true
975            }
976            _ => false,
977        };
978    }
979    match current {
980        serde_json::Value::Object(map) => map
981            .get_mut(head)
982            .is_some_and(|next| json_insert_path(next, rest, new_val, insert_after)),
983        serde_json::Value::Array(items) => json_array_index(items.len(), head)
984            .is_some_and(|index| json_insert_path(&mut items[index], rest, new_val, insert_after)),
985        _ => false,
986    }
987}
988
989fn json_insert_index(len: usize, key: &str, insert_after: bool) -> Option<usize> {
990    let raw = key.parse::<i64>().ok()?;
991    let len_i64 = len as i64;
992    let index = if raw >= 0 {
993        if raw >= len_i64 {
994            len_i64
995        } else if insert_after {
996            raw + 1
997        } else {
998            raw
999        }
1000    } else {
1001        let normalized = len_i64 + raw;
1002        if normalized < 0 {
1003            0
1004        } else if insert_after {
1005            normalized + 1
1006        } else {
1007            normalized
1008        }
1009    };
1010    usize::try_from(index.clamp(0, len_i64)).ok()
1011}
1012
1013fn json_set_path(
1014    current: &mut serde_json::Value,
1015    path: &[String],
1016    new_val: serde_json::Value,
1017    create_missing: bool,
1018) -> bool {
1019    if path.is_empty() {
1020        *current = new_val;
1021        return true;
1022    }
1023    let head = &path[0];
1024    let rest = &path[1..];
1025    match current {
1026        serde_json::Value::Object(obj) => {
1027            if !obj.contains_key(head) && !create_missing {
1028                return false;
1029            }
1030            let entry = obj.entry(head.clone()).or_insert(serde_json::Value::Null);
1031            json_set_path(entry, rest, new_val, create_missing)
1032        }
1033        serde_json::Value::Array(arr) => {
1034            if let Some(idx) = json_array_index(arr.len(), head) {
1035                json_set_path(&mut arr[idx], rest, new_val, create_missing)
1036            } else if create_missing && rest.is_empty() {
1037                if let Ok(idx) = head.parse::<usize>() {
1038                    while arr.len() <= idx {
1039                        arr.push(serde_json::Value::Null);
1040                    }
1041                    arr[idx] = new_val;
1042                    true
1043                } else {
1044                    false
1045                }
1046            } else {
1047                false
1048            }
1049        }
1050        _ if create_missing => {
1051            let mut new_obj = serde_json::Map::new();
1052            new_obj.insert(head.clone(), serde_json::Value::Null);
1053            let mut wrapper = serde_json::Value::Object(new_obj);
1054            let changed = json_set_path(&mut wrapper, path, new_val, create_missing);
1055            if changed {
1056                *current = wrapper;
1057            }
1058            changed
1059        }
1060        _ => false,
1061    }
1062}
1063pub(super) fn strip_nulls(value: &mut serde_json::Value, strip_in_arrays: bool) {
1064    match value {
1065        serde_json::Value::Object(obj) => {
1066            obj.retain(|_, v| !v.is_null());
1067            for v in obj.values_mut() {
1068                strip_nulls(v, strip_in_arrays);
1069            }
1070        }
1071        serde_json::Value::Array(arr) => {
1072            if strip_in_arrays {
1073                arr.retain(|value| !value.is_null());
1074            }
1075            for v in arr.iter_mut() {
1076                strip_nulls(v, strip_in_arrays);
1077            }
1078        }
1079        _ => {}
1080    }
1081}
1082
1083#[cfg(test)]
1084mod pretty_tests {
1085    use super::{format_jsonb_pretty, parse_json, typed_json_value, DecimalValue};
1086
1087    #[test]
1088    fn jsonb_pretty_uses_postgresql_layout_and_key_order() {
1089        let value = parse_json(r#"{"zz":1,"b":[],"aa":{"long":3,"x":2}}"#).unwrap();
1090        assert_eq!(
1091            format_jsonb_pretty(&value),
1092            "{\n    \"b\": [\n    ],\n    \"aa\": {\n        \"x\": 2,\n        \"long\": 3\n    },\n    \"zz\": 1\n}"
1093        );
1094        assert_eq!(format_jsonb_pretty(&parse_json("[]").unwrap()), "[\n]");
1095        assert_eq!(format_jsonb_pretty(&parse_json("{}").unwrap()), "{\n}");
1096        assert_eq!(
1097            format_jsonb_pretty(&parse_json("1e-1000").unwrap()),
1098            DecimalValue::parse("1e-1000").unwrap().to_sql_string()
1099        );
1100        assert_eq!(format_jsonb_pretty(&parse_json("1.00").unwrap()), "1.00");
1101        assert_eq!(format_jsonb_pretty(&parse_json("-0").unwrap()), "0");
1102    }
1103
1104    #[test]
1105    fn jsonb_rejects_numbers_outside_postgresql_numeric_range() {
1106        let maximum = parse_json("1e131071").unwrap();
1107        assert!(typed_json_value(&maximum, true).is_ok());
1108
1109        for text in ["1e131072", "1e-16384", "[1e131072]", r#"{"n":1e131072}"#] {
1110            let error = typed_json_value(&parse_json(text).unwrap(), true).unwrap_err();
1111            assert_eq!(error.sqlstate(), Some("22003"));
1112        }
1113
1114        assert!(typed_json_value(&parse_json("1e200000").unwrap(), false).is_ok());
1115        assert!(typed_json_value(&parse_json("0e200000").unwrap(), true).is_ok());
1116    }
1117}