1#![allow(
4 clippy::cast_lossless,
5 clippy::cast_possible_truncation,
6 clippy::cast_possible_wrap,
7 clippy::cast_sign_loss,
8 clippy::doc_markdown,
9 clippy::format_push_string,
10 clippy::needless_continue,
11 clippy::needless_range_loop,
12 clippy::single_match,
13 clippy::uninlined_format_args
14)]
15
16use alloc::string::{String, ToString};
34use alloc::vec::Vec;
35
36use spg_storage::Value;
37
38use crate::eval::EvalError;
39
40#[derive(Debug, Clone, PartialEq)]
41pub enum JsonValue {
42 Null,
43 Bool(bool),
44 Number(f64),
45 NumberText(String),
49 String(String),
50 Array(Vec<JsonValue>),
51 Object(Vec<(String, JsonValue)>),
52}
53
54pub(crate) fn parse_doc(src: &str) -> Result<JsonValue, EvalError> {
58 parse(src).map_err(|e| EvalError::TypeMismatch {
59 detail: alloc::format!("invalid JSON for JSON_TABLE: {e}"),
60 })
61}
62
63pub(crate) fn json_table_path(
69 root: &JsonValue,
70 path: &str,
71 vars: Option<&JsonValue>,
72) -> Result<Vec<JsonValue>, EvalError> {
73 let (strict, steps) = parse_jsonpath_mode(path)?;
74 apply_jsonpath_mode(root, &steps, vars, strict)
75}
76
77impl JsonValue {
78 pub(crate) fn scalar_text(&self) -> String {
83 self.as_text()
84 }
85
86 pub(crate) fn canonical_json_text(&self) -> String {
91 let mut out = String::new();
92 write_json_canonical(self, &mut out);
93 out
94 }
95
96 pub(crate) fn is_json_null(&self) -> bool {
99 matches!(self, Self::Null)
100 }
101
102 fn as_text(&self) -> String {
103 match self {
104 Self::Null => "null".into(),
105 Self::Bool(b) => if *b { "true" } else { "false" }.into(),
106 Self::Number(x) => alloc::format!("{x}"),
107 Self::NumberText(s) | Self::String(s) => s.clone(),
108 Self::Array(_) | Self::Object(_) => self.to_json_text(),
109 }
110 }
111
112 pub(crate) fn to_json_text(&self) -> String {
113 let mut out = String::new();
114 write_json(self, &mut out);
115 out
116 }
117}
118
119fn write_json(v: &JsonValue, out: &mut String) {
120 match v {
121 JsonValue::Null => out.push_str("null"),
122 JsonValue::Bool(true) => out.push_str("true"),
123 JsonValue::Bool(false) => out.push_str("false"),
124 JsonValue::Number(x) => out.push_str(&alloc::format!("{x}")),
125 JsonValue::NumberText(s) => out.push_str(s),
126 JsonValue::String(s) => {
127 out.push('"');
128 for c in s.chars() {
129 match c {
130 '"' => out.push_str("\\\""),
131 '\\' => out.push_str("\\\\"),
132 '\n' => out.push_str("\\n"),
133 '\r' => out.push_str("\\r"),
134 '\t' => out.push_str("\\t"),
135 c if (c as u32) < 0x20 => {
136 out.push_str(&alloc::format!("\\u{:04x}", c as u32));
137 }
138 c => out.push(c),
139 }
140 }
141 out.push('"');
142 }
143 JsonValue::Array(items) => {
144 out.push('[');
145 for (i, it) in items.iter().enumerate() {
146 if i > 0 {
147 out.push(',');
148 }
149 write_json(it, out);
150 }
151 out.push(']');
152 }
153 JsonValue::Object(entries) => {
154 out.push('{');
155 for (i, (k, val)) in entries.iter().enumerate() {
156 if i > 0 {
157 out.push(',');
158 }
159 write_json_string(k, out);
160 out.push(':');
161 write_json(val, out);
162 }
163 out.push('}');
164 }
165 }
166}
167
168fn write_json_string(s: &str, out: &mut String) {
172 out.push('"');
173 for c in s.chars() {
174 match c {
175 '"' => out.push_str("\\\""),
176 '\\' => out.push_str("\\\\"),
177 '\n' => out.push_str("\\n"),
178 '\r' => out.push_str("\\r"),
179 '\t' => out.push_str("\\t"),
180 c if (c as u32) < 0x20 => out.push_str(&alloc::format!("\\u{:04x}", c as u32)),
181 c => out.push(c),
182 }
183 }
184 out.push('"');
185}
186
187pub fn canonicalize_jsonb(src: &str) -> Result<String, ParseError> {
195 let v = parse(src)?;
196 let mut out = String::with_capacity(src.len() + src.len() / 4 + 8);
199 write_json_canonical(&v, &mut out);
200 Ok(out)
201}
202
203#[must_use]
209fn simple_scalar_json(v: &Value<'_>) -> Option<JsonValue> {
220 Some(match v {
221 Value::Null => JsonValue::Null,
222 Value::Bool(b) => JsonValue::Bool(*b),
223 Value::SmallInt(n) => JsonValue::NumberText(alloc::format!("{n}")),
224 Value::Int(n) => JsonValue::NumberText(alloc::format!("{n}")),
225 Value::BigInt(n) => JsonValue::NumberText(alloc::format!("{n}")),
226 Value::Text(s) | Value::BpChar(s) => JsonValue::String(s.to_string()),
227 _ => return None,
228 })
229}
230
231pub(crate) fn to_jsonb_scalar(v: &Value<'_>) -> Option<Value<'static>> {
234 match v {
238 Value::Null => return Some(Value::json(String::from("null"))),
239 Value::Bool(b) => {
240 return Some(Value::json(String::from(if *b { "true" } else { "false" })));
241 }
242 Value::SmallInt(n) => return Some(Value::json(alloc::format!("{n}"))),
243 Value::Int(n) => return Some(Value::json(alloc::format!("{n}"))),
244 Value::BigInt(n) => return Some(Value::json(alloc::format!("{n}"))),
245 _ => {}
246 }
247 simple_scalar_json(v).map(|jv| Value::json(json_canonical_string(&jv)))
248}
249
250pub(crate) fn build_object_canonical(args: &[Value<'_>]) -> Option<Value<'static>> {
258 if !args.len().is_multiple_of(2) {
259 return None;
260 }
261 let mut entries: alloc::vec::Vec<(String, JsonValue)> =
262 alloc::vec::Vec::with_capacity(args.len() / 2);
263 let (pairs, _) = args.as_chunks::<2>();
264 for pair in pairs {
265 let key = match &pair[0] {
267 Value::Text(s) | Value::BpChar(s) => s.to_string(),
268 Value::SmallInt(n) => alloc::format!("{n}"),
269 Value::Int(n) => alloc::format!("{n}"),
270 Value::BigInt(n) => alloc::format!("{n}"),
271 _ => return None,
272 };
273 entries.push((key, simple_scalar_json(&pair[1])?));
274 }
275 Some(Value::json(json_canonical_string(&JsonValue::Object(
276 entries,
277 ))))
278}
279
280pub fn canonicalize_value(v: Value<'static>) -> Value<'static> {
281 match v {
282 Value::Json(s) => {
283 Value::json(canonicalize_jsonb(s.as_ref()).unwrap_or_else(|_| s.into_owned()))
284 }
285 other => other,
286 }
287}
288
289fn accessor_result(v: &JsonValue, as_text: bool) -> Value<'static> {
295 if as_text && !matches!(v, JsonValue::Array(_) | JsonValue::Object(_)) {
296 return Value::text(v.as_text());
297 }
298 let s = json_canonical_string(v);
299 if as_text {
300 Value::text(s)
301 } else {
302 Value::json(s)
303 }
304}
305
306fn skip_ws_at(b: &[u8], mut i: usize) -> usize {
318 while i < b.len() && matches!(b[i], b' ' | b'\t' | b'\n' | b'\r') {
319 i += 1;
320 }
321 i
322}
323
324fn scan_string(b: &[u8], i: usize) -> Option<usize> {
327 debug_assert_eq!(b.get(i), Some(&b'"'));
328 let mut j = i + 1;
329 while j < b.len() {
330 match b[j] {
331 b'\\' => j += 2,
332 b'"' => return Some(j + 1),
333 _ => j += 1,
334 }
335 }
336 None
337}
338
339fn scan_value(b: &[u8], i: usize) -> Option<usize> {
345 match *b.get(i)? {
346 b'"' => scan_string(b, i),
347 open @ (b'{' | b'[') => {
348 let close = if open == b'{' { b'}' } else { b']' };
349 let mut depth = 0usize;
350 let mut j = i;
351 while j < b.len() {
352 match b[j] {
353 b'"' => j = scan_string(b, j)?,
354 c if c == open => {
355 depth += 1;
356 j += 1;
357 }
358 c if c == close => {
359 depth -= 1;
360 j += 1;
361 if depth == 0 {
362 return Some(j);
363 }
364 }
365 _ => j += 1,
366 }
367 }
368 None
369 }
370 _ => {
371 let mut j = i;
372 while j < b.len() && !matches!(b[j], b',' | b'}' | b']' | b' ' | b'\t' | b'\n' | b'\r')
373 {
374 j += 1;
375 }
376 (j > i).then_some(j)
377 }
378 }
379}
380
381fn decode_string_token(tok: &str) -> Option<String> {
383 match parse(tok).ok()? {
384 JsonValue::String(s) => Some(s),
385 _ => None,
386 }
387}
388
389fn key_token_eq(tok: &str, key: &str) -> bool {
402 let inner = match tok.strip_prefix('"').and_then(|t| t.strip_suffix('"')) {
403 Some(i) => i,
404 None => return false,
405 };
406 if inner.as_bytes().contains(&b'\\') {
407 return decode_string_token(tok).is_some_and(|d| d == key);
408 }
409 inner == key
410}
411
412fn locate_member<'a>(src: &'a str, key: &str) -> Option<&'a str> {
416 let b = src.as_bytes();
417 let mut i = skip_ws_at(b, 0);
418 if b.get(i) != Some(&b'{') {
419 return None;
420 }
421 i += 1;
422 let mut found: Option<&'a str> = None;
423 loop {
424 i = skip_ws_at(b, i);
425 match b.get(i)? {
426 b'}' => return found,
427 b'"' => {}
428 _ => return None,
429 }
430 let key_end = scan_string(b, i)?;
431 let key_tok = src.get(i..key_end)?;
432 i = skip_ws_at(b, key_end);
433 if b.get(i) != Some(&b':') {
434 return None;
435 }
436 i = skip_ws_at(b, i + 1);
437 let val_end = scan_value(b, i)?;
438 if key_token_eq(key_tok, key) {
439 found = Some(src.get(i..val_end)?);
440 }
441 i = skip_ws_at(b, val_end);
442 match b.get(i)? {
443 b',' => i += 1,
444 b'}' => return found,
445 _ => return None,
446 }
447 }
448}
449
450fn locate_index(src: &str, idx: i64) -> Option<&str> {
453 let b = src.as_bytes();
454 let mut i = skip_ws_at(b, 0);
455 if b.get(i) != Some(&b'[') {
456 return None;
457 }
458 i += 1;
459 let mut spans: Vec<(usize, usize)> = Vec::new();
460 loop {
461 i = skip_ws_at(b, i);
462 if b.get(i)? == &b']' {
463 break;
464 }
465 let end = scan_value(b, i)?;
466 spans.push((i, end));
467 i = skip_ws_at(b, end);
468 match b.get(i)? {
469 b',' => i += 1,
470 b']' => break,
471 _ => return None,
472 }
473 }
474 let n = if idx >= 0 {
475 usize::try_from(idx).ok()?
476 } else {
477 usize::try_from(i64::try_from(spans.len()).ok()? + idx).ok()?
478 };
479 let (s, e) = *spans.get(n)?;
480 src.get(s..e)
481}
482
483fn string_token_verbatim(tok: &str) -> Option<&str> {
490 let inner = tok.strip_prefix('"')?.strip_suffix('"')?;
491 if inner.as_bytes().contains(&b'\\') {
492 return None;
493 }
494 Some(inner)
495}
496
497fn verbatim_accessor_result(slice: &str, as_text: bool) -> Value<'static> {
498 match slice.as_bytes().first() {
499 Some(b'n') if slice == "null" => {
500 if as_text {
501 Value::Null
502 } else {
503 Value::json("null")
504 }
505 }
506 Some(b'"') if as_text => match string_token_verbatim(slice) {
516 Some(inner) => Value::text(inner.to_string()),
517 None => decode_string_token(slice).map_or(Value::Null, Value::text),
518 },
519 _ if as_text => Value::text(slice.to_string()),
520 _ => Value::json(slice.to_string()),
521 }
522}
523
524fn json_canonical_string(v: &JsonValue) -> String {
526 let mut s = String::new();
527 write_json_canonical(v, &mut s);
528 s
529}
530
531fn write_json_canonical(v: &JsonValue, out: &mut String) {
532 match v {
533 JsonValue::Null => out.push_str("null"),
534 JsonValue::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
535 JsonValue::Number(x) => out.push_str(&canon_json_number(&alloc::format!("{x}"))),
536 JsonValue::NumberText(s) => out.push_str(&canon_json_number(s)),
537 JsonValue::String(s) => write_json_string(s, out),
538 JsonValue::Array(items) => {
539 out.push('[');
540 for (i, it) in items.iter().enumerate() {
541 if i > 0 {
542 out.push_str(", ");
543 }
544 write_json_canonical(it, out);
545 }
546 out.push(']');
547 }
548 JsonValue::Object(entries) => {
549 if entries.len() <= 1 {
552 out.push('{');
553 if let Some((k, val)) = entries.first() {
554 write_json_string(k, out);
555 out.push_str(": ");
556 write_json_canonical(val, out);
557 }
558 out.push('}');
559 return;
560 }
561 write_object_general(entries, out);
562 }
563 }
564}
565
566fn write_object_general(entries: &[(String, JsonValue)], out: &mut String) {
569 {
570 {
571 let mut deduped: Vec<(&String, &JsonValue)> = Vec::new();
574 for (k, val) in entries {
575 if let Some(slot) = deduped.iter_mut().find(|(mk, _)| *mk == k) {
576 slot.1 = val;
577 } else {
578 deduped.push((k, val));
579 }
580 }
581 deduped.sort_by(|a, b| {
582 a.0.len()
583 .cmp(&b.0.len())
584 .then_with(|| a.0.as_bytes().cmp(b.0.as_bytes()))
585 });
586 out.push('{');
587 for (i, (k, val)) in deduped.iter().enumerate() {
588 if i > 0 {
589 out.push_str(", ");
590 }
591 write_json_string(k, out);
592 out.push_str(": ");
593 write_json_canonical(val, out);
594 }
595 out.push('}');
596 }
597 }
598}
599
600fn canon_json_number(lexeme: &str) -> alloc::borrow::Cow<'_, str> {
612 let body = lexeme.strip_prefix('-').unwrap_or(lexeme);
613 if !body.is_empty()
614 && body.bytes().all(|b| b.is_ascii_digit())
615 && (body == "0" || !body.starts_with('0'))
617 && !(lexeme.starts_with('-') && body == "0")
619 {
620 return alloc::borrow::Cow::Borrowed(lexeme);
621 }
622 alloc::borrow::Cow::Owned(canon_json_number_slow(lexeme))
623}
624
625fn canon_json_number_slow(lexeme: &str) -> String {
626 let neg = lexeme.starts_with('-');
627 let body = lexeme.trim_start_matches(['-', '+']);
628 let (mantissa, exp) = match body.split_once(['e', 'E']) {
630 Some((m, e)) => (m, e.parse::<i64>().unwrap_or(0)),
631 None => (body, 0),
632 };
633 let (int_part, frac_part) = match mantissa.split_once('.') {
634 Some((i, f)) => (i, f),
635 None => (mantissa, ""),
636 };
637 let digits: String = alloc::format!("{int_part}{frac_part}");
638 let shift = frac_part.len() as i64 - exp;
642 let all_zero = digits.bytes().all(|b| b == b'0');
643 let sign = if neg && !all_zero { "-" } else { "" };
644 let strip = |s: &str| -> String {
645 let t = s.trim_start_matches('0');
646 if t.is_empty() { "0".into() } else { t.into() }
647 };
648 if shift <= 0 {
649 let zeros = "0".repeat((-shift) as usize);
651 alloc::format!("{sign}{}{zeros}", strip(&digits))
652 } else {
653 let shift = shift as usize;
654 let (int_str, frac_str) = if digits.len() > shift {
655 (
656 digits[..digits.len() - shift].to_string(),
657 digits[digits.len() - shift..].to_string(),
658 )
659 } else {
660 (
661 "0".to_string(),
662 alloc::format!("{}{}", "0".repeat(shift - digits.len()), digits),
663 )
664 };
665 alloc::format!("{sign}{}.{frac_str}", strip(&int_str))
666 }
667}
668
669pub fn jsonb_each_text_rows(arg: &Value) -> Result<Vec<(String, Option<String>)>, EvalError> {
683 each_rows(arg, true, "jsonb_each_text")
684}
685
686pub fn each_rows(
692 arg: &Value,
693 as_text: bool,
694 fn_name: &str,
695) -> Result<Vec<(String, Option<String>)>, EvalError> {
696 let src = match arg {
697 Value::Null => return Ok(Vec::new()),
698 Value::Json(s) | Value::Text(s) => s.as_ref(),
699 other => {
700 return Err(EvalError::TypeMismatch {
701 detail: alloc::format!(
702 "{fn_name}: argument must be JSON / JSONB, got {}",
703 crate::conversions::pg_type_name_for_error_opt(other.data_type())
704 ),
705 });
706 }
707 };
708 let parsed = parse(src).map_err(|e| EvalError::TypeMismatch {
709 detail: alloc::format!("{fn_name}: invalid JSON: {e}"),
710 })?;
711 match parsed {
712 JsonValue::Object(entries) => {
713 let mut out: Vec<(String, Option<String>)> = Vec::with_capacity(entries.len());
714 for (k, v) in entries {
715 let text = if as_text {
716 match &v {
717 JsonValue::Null => None,
718 JsonValue::Bool(b) => Some(if *b {
719 "true".to_string()
720 } else {
721 "false".to_string()
722 }),
723 JsonValue::Number(_) | JsonValue::NumberText(_) | JsonValue::String(_) => {
724 Some(v.as_text())
725 }
726 JsonValue::Array(_) | JsonValue::Object(_) => {
727 Some(json_canonical_string(&v))
728 }
729 }
730 } else {
731 Some(json_canonical_string(&v))
732 };
733 out.push((k, text));
734 }
735 Ok(out)
736 }
737 other => Err(EvalError::TypeMismatch {
738 detail: alloc::format!("cannot call {fn_name} on a non-object ({other:?})"),
739 }),
740 }
741}
742
743pub fn array_element_rows(
753 arg: &Value,
754 as_text: bool,
755 fn_name: &str,
756) -> Result<Vec<Option<String>>, EvalError> {
757 let src = match arg {
758 Value::Null => return Ok(Vec::new()),
759 Value::Json(s) | Value::Text(s) => s.as_ref(),
760 other => {
761 return Err(EvalError::TypeMismatch {
762 detail: alloc::format!(
763 "{fn_name}: argument must be JSON / JSONB, got {}",
764 crate::conversions::pg_type_name_for_error_opt(other.data_type())
765 ),
766 });
767 }
768 };
769 let parsed = parse(src).map_err(|e| EvalError::TypeMismatch {
770 detail: alloc::format!("{fn_name}: invalid JSON: {e}"),
771 })?;
772 match parsed {
773 JsonValue::Array(items) => {
774 let mut out: Vec<Option<String>> = Vec::with_capacity(items.len());
775 for v in items {
776 let text = if as_text {
777 match &v {
778 JsonValue::Null => None,
779 JsonValue::Bool(b) => Some(if *b {
780 "true".to_string()
781 } else {
782 "false".to_string()
783 }),
784 JsonValue::Number(_) | JsonValue::NumberText(_) | JsonValue::String(_) => {
785 Some(v.as_text())
786 }
787 JsonValue::Array(_) | JsonValue::Object(_) => {
788 Some(json_canonical_string(&v))
789 }
790 }
791 } else {
792 Some(json_canonical_string(&v))
794 };
795 out.push(text);
796 }
797 Ok(out)
798 }
799 other => Err(EvalError::TypeMismatch {
800 detail: alloc::format!(
801 "cannot extract elements from a non-array ({fn_name}: got {other:?})"
802 ),
803 }),
804 }
805}
806
807pub fn path_walk(lhs: &Value, rhs: &Value, as_text: bool) -> Result<Value<'static>, EvalError> {
809 let src = match lhs {
810 Value::Json(s) | Value::Text(s) => s.as_ref(),
811 Value::Null => return Ok(Value::Null),
812 other => {
813 return Err(EvalError::TypeMismatch {
814 detail: alloc::format!(
815 "JSON path walk: left side must be JSON or TEXT, got {}",
816 crate::conversions::pg_type_name_for_error_opt(other.data_type())
817 ),
818 });
819 }
820 };
821 let owned_steps: Vec<String>;
825 let path: Vec<String> = match rhs {
826 Value::TextArray(items) => {
827 if items.iter().any(Option::is_none) {
828 return Ok(Value::Null);
829 }
830 owned_steps = items.iter().flatten().cloned().collect();
831 owned_steps
832 }
833 Value::Text(s) | Value::Json(s) => parse_text_array(s.as_ref())?,
834 Value::Null => return Ok(Value::Null),
835 other => {
836 return Err(EvalError::TypeMismatch {
837 detail: alloc::format!(
838 "JSON path walk: right side must be TEXT, got {}",
839 crate::conversions::pg_type_name_for_error_opt(other.data_type())
840 ),
841 });
842 }
843 };
844 validate_unless_known_json(lhs, src, "path walk")?;
847 let mut cur: &str = src;
848 for step in &path {
849 let at = skip_ws_at(cur.as_bytes(), 0);
850 let next = match cur.as_bytes().get(at) {
851 Some(b'{') => locate_member(cur, step),
852 Some(b'[') => match step.parse::<i64>() {
853 Ok(idx) => locate_index(cur, idx),
854 Err(_) => return Ok(Value::Null),
855 },
856 _ => return Ok(Value::Null),
857 };
858 cur = match next {
859 None => return Ok(Value::Null),
860 Some(slice) => slice,
861 };
862 }
863 Ok(verbatim_accessor_result(cur, as_text))
864}
865
866fn scan_has_key(src: &str, key: &str) -> Option<bool> {
896 let mut found = [false];
897 scan_mark_keys(src, &KeyList::One(key), &mut found)?;
898 Some(found[0])
899}
900
901fn scan_mark_keys(src: &str, keys: &KeyList<'_>, found: &mut [bool]) -> Option<()> {
912 debug_assert_eq!(keys.len(), found.len());
913 let mut outstanding = found.iter().filter(|f| !**f).count();
916 let mut mark = |tok: &str, found: &mut [bool], outstanding: &mut usize| {
917 for n in 0..keys.len() {
918 if !found[n]
919 && let Some(k) = keys.get(n)
920 && key_token_eq(tok, k)
921 {
922 found[n] = true;
923 *outstanding -= 1;
924 }
925 }
926 };
927 if outstanding == 0 {
928 return Some(());
929 }
930 let b = src.as_bytes();
931 let mut i = skip_ws_at(b, 0);
932 match b.get(i)? {
933 b'{' => {
934 i += 1;
935 loop {
936 i = skip_ws_at(b, i);
937 match b.get(i)? {
938 b'}' => return Some(()),
939 b'"' => {}
940 _ => return None,
941 }
942 let key_end = scan_string(b, i)?;
943 mark(src.get(i..key_end)?, found, &mut outstanding);
944 if outstanding == 0 {
945 return Some(());
946 }
947 i = skip_ws_at(b, key_end);
948 if b.get(i) != Some(&b':') {
949 return None;
950 }
951 i = skip_ws_at(b, i + 1);
952 i = skip_ws_at(b, scan_value(b, i)?);
953 match b.get(i)? {
954 b',' => i += 1,
955 b'}' => return Some(()),
956 _ => return None,
957 }
958 }
959 }
960 b'[' => {
961 i += 1;
962 loop {
963 i = skip_ws_at(b, i);
964 if b.get(i) == Some(&b']') {
965 return Some(());
966 }
967 let start = i;
968 let end = scan_value(b, i)?;
969 if b.get(start) == Some(&b'"') {
973 mark(src.get(start..end)?, found, &mut outstanding);
974 if outstanding == 0 {
975 return Some(());
976 }
977 }
978 i = skip_ws_at(b, end);
979 match b.get(i)? {
980 b',' => i += 1,
981 b']' => return Some(()),
982 _ => return None,
983 }
984 }
985 }
986 b'"' => {
987 let end = scan_string(b, i)?;
988 mark(src.get(i..end)?, found, &mut outstanding);
989 Some(())
990 }
991 _ => Some(()),
992 }
993}
994
995fn existence_lhs<'a>(lhs: &'a Value, op: &str) -> Result<Option<&'a str>, EvalError> {
1000 let src = match lhs {
1001 Value::Json(s) | Value::Text(s) => s.as_ref(),
1002 Value::Null => return Ok(None),
1003 other => {
1004 return Err(EvalError::TypeMismatch {
1005 detail: alloc::format!(
1006 "JSON {op}: left side must be JSON or TEXT, got {}",
1007 crate::conversions::pg_type_name_for_error_opt(other.data_type())
1008 ),
1009 });
1010 }
1011 };
1012 if !matches!(lhs, Value::Json(_)) {
1013 parse(src).map_err(|e| EvalError::TypeMismatch {
1014 detail: alloc::format!("invalid JSON on left of {op}: {e}"),
1015 })?;
1016 }
1017 Ok(Some(src))
1018}
1019
1020pub fn key_exists(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
1028 let Some(lhs_text) = existence_lhs(lhs, "?")? else {
1029 return Ok(Value::Null);
1030 };
1031 let key = match rhs {
1032 Value::Text(s) => s.as_ref(),
1033 Value::Null => return Ok(Value::Null),
1034 other => {
1035 return Err(EvalError::TypeMismatch {
1036 detail: alloc::format!(
1037 "JSON ?: right side must be TEXT, got {}",
1038 crate::conversions::pg_type_name_for_error_opt(other.data_type())
1039 ),
1040 });
1041 }
1042 };
1043 Ok(Value::Bool(scan_has_key(lhs_text, key).unwrap_or(false)))
1044}
1045
1046fn node_has_key(v: &JsonValue, key: &str) -> bool {
1047 match v {
1048 JsonValue::Object(members) => members.iter().any(|(k, _)| k == key),
1049 JsonValue::Array(items) => items
1050 .iter()
1051 .any(|item| matches!(item, JsonValue::String(s) if s == key)),
1052 JsonValue::String(s) => s == key,
1053 _ => false,
1054 }
1055}
1056
1057enum KeyList<'a> {
1069 One(&'a str),
1070 Many(&'a [Option<alloc::string::String>]),
1071}
1072
1073impl KeyList<'_> {
1074 fn len(&self) -> usize {
1075 match self {
1076 KeyList::One(_) => 1,
1077 KeyList::Many(items) => items.len(),
1078 }
1079 }
1080
1081 fn get(&self, n: usize) -> Option<&str> {
1083 match self {
1084 KeyList::One(k) => (n == 0).then_some(*k),
1085 KeyList::Many(items) => items.get(n)?.as_deref(),
1086 }
1087 }
1088}
1089
1090fn borrow_keys<'a>(v: &'a Value, op: &str) -> Result<Option<KeyList<'a>>, EvalError> {
1092 match v {
1093 Value::Null => Ok(None),
1094 Value::TextArray(items) => Ok(Some(KeyList::Many(items))),
1095 Value::Text(s) => Ok(Some(KeyList::One(s.as_ref()))),
1096 other => Err(EvalError::TypeMismatch {
1097 detail: alloc::format!(
1098 "JSON {op}: right side must be TEXT[] or TEXT, got {}",
1099 crate::conversions::pg_type_name_for_error_opt(other.data_type())
1100 ),
1101 }),
1102 }
1103}
1104
1105const INLINE_KEY_MARKS: usize = 32;
1108
1109fn any_or_all_keys(lhs_text: &str, keys: &KeyList<'_>, want_all: bool) -> bool {
1110 let n = keys.len();
1111 if n == 0 {
1112 return want_all;
1115 }
1116 let mut inline = [false; INLINE_KEY_MARKS];
1117 let mut spilled;
1118 let found: &mut [bool] = if n <= INLINE_KEY_MARKS {
1119 &mut inline[..n]
1120 } else {
1121 spilled = alloc::vec![false; n];
1122 &mut spilled
1123 };
1124 for (n, mark) in found.iter_mut().enumerate() {
1129 if keys.get(n).is_none() {
1130 *mark = true;
1131 }
1132 }
1133 let _ = scan_mark_keys(lhs_text, keys, found);
1134 if want_all {
1135 found.iter().all(|f| *f)
1136 } else {
1137 (0..n).any(|i| keys.get(i).is_some() && found[i])
1138 }
1139}
1140
1141pub fn keys_any(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
1144 let Some(lhs_text) = existence_lhs(lhs, "?|")? else {
1145 return Ok(Value::Null);
1146 };
1147 let Some(keys) = borrow_keys(rhs, "?|")? else {
1148 return Ok(Value::Null);
1149 };
1150 Ok(Value::Bool(any_or_all_keys(lhs_text, &keys, false)))
1151}
1152
1153pub fn keys_all(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
1156 let Some(lhs_text) = existence_lhs(lhs, "?&")? else {
1157 return Ok(Value::Null);
1158 };
1159 let Some(keys) = borrow_keys(rhs, "?&")? else {
1160 return Ok(Value::Null);
1161 };
1162 Ok(Value::Bool(any_or_all_keys(lhs_text, &keys, true)))
1163}
1164
1165pub fn contains(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
1166 let lhs_text = match lhs {
1167 Value::Json(s) | Value::Text(s) => s.as_ref(),
1168 Value::Null => return Ok(Value::Null),
1169 other => {
1170 return Err(EvalError::TypeMismatch {
1171 detail: alloc::format!(
1172 "JSON @>: left side must be JSON or TEXT, got {}",
1173 crate::conversions::pg_type_name_for_error_opt(other.data_type())
1174 ),
1175 });
1176 }
1177 };
1178 let rhs_text = match rhs {
1179 Value::Json(s) | Value::Text(s) => s.as_ref(),
1180 Value::Null => return Ok(Value::Null),
1181 other => {
1182 return Err(EvalError::TypeMismatch {
1183 detail: alloc::format!(
1184 "JSON @>: right side must be JSON or TEXT, got {}",
1185 crate::conversions::pg_type_name_for_error_opt(other.data_type())
1186 ),
1187 });
1188 }
1189 };
1190 if let Some(verdict) = contains_flat_text(lhs_text, rhs_text) {
1194 return Ok(Value::Bool(verdict));
1195 }
1196 let rhs_doc = parse(rhs_text).map_err(|e| EvalError::TypeMismatch {
1197 detail: alloc::format!("invalid JSON on right of @>: {e}"),
1198 })?;
1199 if let Some(verdict) = contains_flat_object(lhs_text, &rhs_doc) {
1208 return Ok(Value::Bool(verdict));
1209 }
1210 let lhs_doc = parse(lhs_text).map_err(|e| EvalError::TypeMismatch {
1211 detail: alloc::format!("invalid JSON on left of @>: {e}"),
1212 })?;
1213 let result = match (&lhs_doc, &rhs_doc) {
1220 (JsonValue::Array(items), scalar)
1221 if !matches!(scalar, JsonValue::Array(_) | JsonValue::Object(_)) =>
1222 {
1223 items.iter().any(|it| json_eq(it, scalar))
1224 }
1225 _ => json_contains(&lhs_doc, &rhs_doc),
1226 };
1227 Ok(Value::Bool(result))
1228}
1229
1230fn token_denotes_itself(tok: &str) -> bool {
1277 match tok.as_bytes().first() {
1278 Some(b'"') => !tok.as_bytes().contains(&b'\\'),
1279 _ => matches!(tok, "true" | "false" | "null"),
1280 }
1281}
1282
1283fn only_whitespace_after(b: &[u8], i: usize) -> bool {
1288 skip_ws_at(b, i) >= b.len()
1289}
1290
1291fn contains_flat_text(lhs_text: &str, rhs_text: &str) -> Option<bool> {
1292 let rb = rhs_text.as_bytes();
1293 let lb = lhs_text.as_bytes();
1294 if lb.get(skip_ws_at(lb, 0)) != Some(&b'{') {
1295 return None;
1296 }
1297 let mut i = skip_ws_at(rb, 0);
1298 if rb.get(i) != Some(&b'{') {
1299 return None;
1300 }
1301 i += 1;
1302 loop {
1303 i = skip_ws_at(rb, i);
1304 match rb.get(i)? {
1305 b'}' => return only_whitespace_after(rb, i + 1).then_some(true),
1306 b'"' => {}
1307 _ => return None,
1308 }
1309 let key_end = scan_string(rb, i)?;
1310 let key_tok = rhs_text.get(i..key_end)?;
1311 let key_owned;
1314 let key: &str = match key_tok
1315 .strip_prefix('"')
1316 .and_then(|t| t.strip_suffix('"'))
1317 .filter(|inner| !inner.as_bytes().contains(&b'\\'))
1318 {
1319 Some(plain) => plain,
1320 None => {
1321 key_owned = decode_string_token(key_tok)?;
1322 &key_owned
1323 }
1324 };
1325 i = skip_ws_at(rb, key_end);
1326 if rb.get(i) != Some(&b':') {
1327 return None;
1328 }
1329 i = skip_ws_at(rb, i + 1);
1330 let val_start = i;
1331 let val_end = scan_value(rb, i)?;
1332 let want = rhs_text.get(val_start..val_end)?;
1333 if !token_denotes_itself(want) {
1334 return None;
1335 }
1336 match locate_member(lhs_text, key) {
1337 None => return Some(false),
1338 Some(got) => {
1339 if !token_denotes_itself(got) {
1340 return None;
1341 }
1342 if got != want {
1343 return Some(false);
1344 }
1345 }
1346 }
1347 i = skip_ws_at(rb, val_end);
1348 match rb.get(i)? {
1349 b',' => i += 1,
1350 b'}' => return only_whitespace_after(rb, i + 1).then_some(true),
1351 _ => return None,
1352 }
1353 }
1354}
1355
1356fn contains_flat_object(lhs_text: &str, rhs_doc: &JsonValue) -> Option<bool> {
1357 let JsonValue::Object(members) = rhs_doc else {
1358 return None;
1359 };
1360 if members
1361 .iter()
1362 .any(|(_, v)| matches!(v, JsonValue::Object(_) | JsonValue::Array(_)))
1363 {
1364 return None;
1365 }
1366 let b = lhs_text.as_bytes();
1367 if b.get(skip_ws_at(b, 0)) != Some(&b'{') {
1368 return None;
1369 }
1370 for (key, want) in members {
1371 let Some(slice) = locate_member(lhs_text, key) else {
1372 return Some(false);
1373 };
1374 let got = parse(slice).ok()?;
1375 if !json_eq(&got, want) {
1376 return Some(false);
1377 }
1378 }
1379 Some(true)
1380}
1381
1382pub fn equals(lhs: &Value, rhs: &Value) -> Result<bool, EvalError> {
1390 let lhs_text = match lhs {
1391 Value::Json(s) | Value::Text(s) => s.as_ref(),
1392 other => {
1393 return Err(EvalError::TypeMismatch {
1394 detail: alloc::format!(
1395 "jsonb =: left side must be JSON or TEXT, got {}",
1396 crate::conversions::pg_type_name_for_error_opt(other.data_type())
1397 ),
1398 });
1399 }
1400 };
1401 let rhs_text = match rhs {
1402 Value::Json(s) | Value::Text(s) => s.as_ref(),
1403 other => {
1404 return Err(EvalError::TypeMismatch {
1405 detail: alloc::format!(
1406 "jsonb =: right side must be JSON or TEXT, got {}",
1407 crate::conversions::pg_type_name_for_error_opt(other.data_type())
1408 ),
1409 });
1410 }
1411 };
1412 let lhs_doc = parse(lhs_text).map_err(|e| EvalError::TypeMismatch {
1413 detail: alloc::format!("invalid JSON on left of =: {e}"),
1414 })?;
1415 let rhs_doc = parse(rhs_text).map_err(|e| EvalError::TypeMismatch {
1416 detail: alloc::format!("invalid JSON on right of =: {e}"),
1417 })?;
1418 Ok(json_eq(&lhs_doc, &rhs_doc))
1419}
1420
1421fn json_contains(lhs: &JsonValue, rhs: &JsonValue) -> bool {
1422 match (lhs, rhs) {
1423 (JsonValue::Object(l), JsonValue::Object(r)) => r
1424 .iter()
1425 .all(|(rk, rv)| l.iter().any(|(lk, lv)| lk == rk && json_contains(lv, rv))),
1426 (JsonValue::Array(l), JsonValue::Array(r)) => {
1427 r.iter().all(|rv| l.iter().any(|lv| json_contains(lv, rv)))
1428 }
1429 _ => json_eq(lhs, rhs),
1430 }
1431}
1432
1433fn json_eq(a: &JsonValue, b: &JsonValue) -> bool {
1434 match (a, b) {
1435 (JsonValue::Null, JsonValue::Null) => true,
1436 (JsonValue::Bool(x), JsonValue::Bool(y)) => x == y,
1437 (JsonValue::String(x), JsonValue::String(y)) => x == y,
1438 (
1443 JsonValue::Number(_) | JsonValue::NumberText(_),
1444 JsonValue::Number(_) | JsonValue::NumberText(_),
1445 ) => json_number_key(a) == json_number_key(b),
1446 (JsonValue::Array(x), JsonValue::Array(y)) => {
1447 x.len() == y.len() && x.iter().zip(y).all(|(a, b)| json_eq(a, b))
1448 }
1449 (JsonValue::Object(x), JsonValue::Object(y)) => {
1450 x.len() == y.len()
1451 && x.iter()
1452 .all(|(k, v)| y.iter().any(|(k2, v2)| k == k2 && json_eq(v, v2)))
1453 }
1454 _ => false,
1455 }
1456}
1457
1458fn numeric_eq_key(lexeme: &str) -> String {
1463 let c = canon_json_number(lexeme);
1464 if c.contains('.') {
1465 c.trim_end_matches('0').trim_end_matches('.').to_string()
1466 } else {
1467 c.into_owned()
1468 }
1469}
1470
1471fn json_number_key(v: &JsonValue) -> Option<String> {
1472 match v {
1473 JsonValue::NumberText(s) => Some(numeric_eq_key(s)),
1474 JsonValue::Number(x) => Some(numeric_eq_key(&alloc::format!("{x}"))),
1475 _ => None,
1476 }
1477}
1478
1479fn parse_text_array(s: &str) -> Result<Vec<String>, EvalError> {
1484 let trimmed = s.trim();
1485 let inner = if let Some(stripped) = trimmed.strip_prefix('{').and_then(|s| s.strip_suffix('}'))
1486 {
1487 stripped
1488 } else {
1489 return Err(EvalError::TypeMismatch {
1490 detail: alloc::format!("path walk: expected PG array literal `{{…}}`, got {s:?}"),
1491 });
1492 };
1493 if inner.trim().is_empty() {
1494 return Ok(Vec::new());
1495 }
1496 let mut out = Vec::new();
1497 let mut cur = String::new();
1498 let mut in_quotes = false;
1499 let mut chars = inner.chars().peekable();
1500 while let Some(c) = chars.next() {
1501 match c {
1502 '"' => in_quotes = !in_quotes,
1503 ',' if !in_quotes => {
1504 out.push(cur.trim().to_string());
1505 cur = String::new();
1506 }
1507 '\\' => {
1508 if let Some(&next) = chars.peek() {
1509 cur.push(next);
1510 chars.next();
1511 }
1512 }
1513 _ => cur.push(c),
1514 }
1515 }
1516 out.push(cur.trim().to_string());
1517 Ok(out)
1518}
1519
1520fn validate_unless_known_json(lhs: &Value, src: &str, what: &str) -> Result<(), EvalError> {
1540 if matches!(lhs, Value::Json(_)) {
1541 return Ok(());
1542 }
1543 parse(src).map(|_| ()).map_err(|e| EvalError::TypeMismatch {
1544 detail: alloc::format!("invalid JSON for {what}: {e}"),
1545 })
1546}
1547
1548pub fn path_get(lhs: &Value, rhs: &Value, as_text: bool) -> Result<Value<'static>, EvalError> {
1549 let src = match lhs {
1550 Value::Json(s) | Value::Text(s) => s.as_ref(),
1551 Value::Null => return Ok(Value::Null),
1552 other => {
1553 return Err(EvalError::TypeMismatch {
1554 detail: alloc::format!(
1555 "JSON path operator: left side must be JSON or TEXT, got {}",
1556 crate::conversions::pg_type_name_for_error_opt(other.data_type())
1557 ),
1558 });
1559 }
1560 };
1561 validate_unless_known_json(lhs, src, "path access")?;
1564 let located = match rhs {
1565 Value::Text(k) => locate_member(src, k),
1566 Value::Int(idx) => locate_index(src, i64::from(*idx)),
1567 Value::BigInt(idx) => locate_index(src, *idx),
1568 Value::Null => return Ok(Value::Null),
1569 _ => None,
1570 };
1571 Ok(located.map_or(Value::Null, |slice| {
1572 verbatim_accessor_result(slice, as_text)
1573 }))
1574}
1575
1576#[derive(Debug)]
1579pub enum ParseError {
1580 Unexpected(char, usize),
1581 Truncated,
1582 InvalidEscape(usize),
1583 InvalidNumber(usize),
1584}
1585
1586impl core::fmt::Display for ParseError {
1587 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1588 match self {
1589 Self::Unexpected(c, p) => write!(f, "unexpected {c:?} at offset {p}"),
1590 Self::Truncated => f.write_str("unexpected end of JSON input"),
1591 Self::InvalidEscape(p) => write!(f, "invalid string escape at offset {p}"),
1592 Self::InvalidNumber(p) => write!(f, "invalid number at offset {p}"),
1593 }
1594 }
1595}
1596
1597pub fn parse(src: &str) -> Result<JsonValue, ParseError> {
1598 let bytes = src.as_bytes();
1599 let mut p = 0;
1600 skip_ws(bytes, &mut p);
1601 let value = parse_value(bytes, &mut p)?;
1602 skip_ws(bytes, &mut p);
1603 if p != bytes.len() {
1604 return Err(ParseError::Unexpected(bytes[p] as char, p));
1605 }
1606 Ok(value)
1607}
1608
1609#[must_use]
1616pub fn jsonb_compare(a: &JsonValue, b: &JsonValue) -> core::cmp::Ordering {
1617 use core::cmp::Ordering;
1618 fn rank(v: &JsonValue) -> u8 {
1619 match v {
1620 JsonValue::Null => 0,
1621 JsonValue::String(_) => 1,
1622 JsonValue::Number(_) | JsonValue::NumberText(_) => 2,
1623 JsonValue::Bool(_) => 3,
1624 JsonValue::Array(_) => 4,
1625 JsonValue::Object(_) => 5,
1626 }
1627 }
1628 fn num(v: &JsonValue) -> f64 {
1629 match v {
1630 JsonValue::Number(x) => *x,
1631 JsonValue::NumberText(s) => s.parse::<f64>().unwrap_or(0.0),
1632 _ => 0.0,
1633 }
1634 }
1635 let (ra, rb) = (rank(a), rank(b));
1636 if ra != rb {
1637 return ra.cmp(&rb);
1638 }
1639 match (a, b) {
1640 (JsonValue::String(x), JsonValue::String(y)) => x.cmp(y),
1641 (JsonValue::Bool(x), JsonValue::Bool(y)) => x.cmp(y),
1642 (
1643 JsonValue::Number(_) | JsonValue::NumberText(_),
1644 JsonValue::Number(_) | JsonValue::NumberText(_),
1645 ) => num(a).partial_cmp(&num(b)).unwrap_or(Ordering::Equal),
1646 (JsonValue::Array(x), JsonValue::Array(y)) => x.len().cmp(&y.len()).then_with(|| {
1647 x.iter()
1648 .zip(y.iter())
1649 .map(|(ea, eb)| jsonb_compare(ea, eb))
1650 .find(|o| *o != Ordering::Equal)
1651 .unwrap_or(Ordering::Equal)
1652 }),
1653 (JsonValue::Object(x), JsonValue::Object(y)) => x.len().cmp(&y.len()).then_with(|| {
1654 x.iter()
1655 .zip(y.iter())
1656 .map(|((ka, va), (kb, vb))| ka.cmp(kb).then_with(|| jsonb_compare(va, vb)))
1657 .find(|o| *o != Ordering::Equal)
1658 .unwrap_or(Ordering::Equal)
1659 }),
1660 _ => Ordering::Equal,
1662 }
1663}
1664
1665fn skip_ws(bytes: &[u8], p: &mut usize) {
1666 while *p < bytes.len() && matches!(bytes[*p], b' ' | b'\t' | b'\n' | b'\r') {
1667 *p += 1;
1668 }
1669}
1670
1671fn parse_value(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
1672 skip_ws(bytes, p);
1673 if *p >= bytes.len() {
1674 return Err(ParseError::Truncated);
1675 }
1676 match bytes[*p] {
1677 b'{' => parse_object(bytes, p),
1678 b'[' => parse_array(bytes, p),
1679 b'"' => parse_string(bytes, p).map(JsonValue::String),
1680 b't' | b'f' => parse_bool(bytes, p),
1681 b'n' => parse_null(bytes, p),
1682 b'-' | b'0'..=b'9' => parse_number(bytes, p),
1683 c => Err(ParseError::Unexpected(c as char, *p)),
1684 }
1685}
1686
1687fn parse_object(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
1688 debug_assert_eq!(bytes[*p], b'{');
1689 *p += 1;
1690 let mut entries = Vec::new();
1691 skip_ws(bytes, p);
1692 if *p < bytes.len() && bytes[*p] == b'}' {
1693 *p += 1;
1694 return Ok(JsonValue::Object(entries));
1695 }
1696 loop {
1697 skip_ws(bytes, p);
1698 if *p >= bytes.len() || bytes[*p] != b'"' {
1699 return Err(ParseError::Unexpected(
1700 bytes.get(*p).copied().unwrap_or(0) as char,
1701 *p,
1702 ));
1703 }
1704 let key = parse_string(bytes, p)?;
1705 skip_ws(bytes, p);
1706 if *p >= bytes.len() || bytes[*p] != b':' {
1707 return Err(ParseError::Unexpected(
1708 bytes.get(*p).copied().unwrap_or(0) as char,
1709 *p,
1710 ));
1711 }
1712 *p += 1;
1713 let value = parse_value(bytes, p)?;
1714 entries.push((key, value));
1715 skip_ws(bytes, p);
1716 if *p >= bytes.len() {
1717 return Err(ParseError::Truncated);
1718 }
1719 match bytes[*p] {
1720 b',' => {
1721 *p += 1;
1722 continue;
1723 }
1724 b'}' => {
1725 *p += 1;
1726 return Ok(JsonValue::Object(entries));
1727 }
1728 c => return Err(ParseError::Unexpected(c as char, *p)),
1729 }
1730 }
1731}
1732
1733fn parse_array(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
1734 debug_assert_eq!(bytes[*p], b'[');
1735 *p += 1;
1736 let mut items = Vec::new();
1737 skip_ws(bytes, p);
1738 if *p < bytes.len() && bytes[*p] == b']' {
1739 *p += 1;
1740 return Ok(JsonValue::Array(items));
1741 }
1742 loop {
1743 items.push(parse_value(bytes, p)?);
1744 skip_ws(bytes, p);
1745 if *p >= bytes.len() {
1746 return Err(ParseError::Truncated);
1747 }
1748 match bytes[*p] {
1749 b',' => {
1750 *p += 1;
1751 continue;
1752 }
1753 b']' => {
1754 *p += 1;
1755 return Ok(JsonValue::Array(items));
1756 }
1757 c => return Err(ParseError::Unexpected(c as char, *p)),
1758 }
1759 }
1760}
1761
1762fn parse_string(bytes: &[u8], p: &mut usize) -> Result<String, ParseError> {
1763 debug_assert_eq!(bytes[*p], b'"');
1764 *p += 1;
1765 let mut out = String::new();
1766 while *p < bytes.len() {
1767 match bytes[*p] {
1768 b'"' => {
1769 *p += 1;
1770 return Ok(out);
1771 }
1772 b'\\' => {
1773 let start = *p;
1774 *p += 1;
1775 if *p >= bytes.len() {
1776 return Err(ParseError::Truncated);
1777 }
1778 match bytes[*p] {
1779 b'"' => {
1780 out.push('"');
1781 *p += 1;
1782 }
1783 b'\\' => {
1784 out.push('\\');
1785 *p += 1;
1786 }
1787 b'/' => {
1788 out.push('/');
1789 *p += 1;
1790 }
1791 b'b' => {
1792 out.push('\u{08}');
1793 *p += 1;
1794 }
1795 b'f' => {
1796 out.push('\u{0c}');
1797 *p += 1;
1798 }
1799 b'n' => {
1800 out.push('\n');
1801 *p += 1;
1802 }
1803 b'r' => {
1804 out.push('\r');
1805 *p += 1;
1806 }
1807 b't' => {
1808 out.push('\t');
1809 *p += 1;
1810 }
1811 b'u' => {
1812 if *p + 5 > bytes.len() {
1813 return Err(ParseError::Truncated);
1814 }
1815 let hex = &bytes[*p + 1..*p + 5];
1816 let n = u32::from_str_radix(
1817 core::str::from_utf8(hex)
1818 .map_err(|_| ParseError::InvalidEscape(start))?,
1819 16,
1820 )
1821 .map_err(|_| ParseError::InvalidEscape(start))?;
1822 out.push(char::from_u32(n).ok_or(ParseError::InvalidEscape(start))?);
1823 *p += 5;
1824 }
1825 _ => return Err(ParseError::InvalidEscape(start)),
1826 }
1827 }
1828 c if c < 0x20 => return Err(ParseError::Unexpected(c as char, *p)),
1829 _ => {
1830 let s = core::str::from_utf8(&bytes[*p..])
1832 .map_err(|_| ParseError::Unexpected(bytes[*p] as char, *p))?;
1833 let c = s.chars().next().unwrap();
1834 out.push(c);
1835 *p += c.len_utf8();
1836 }
1837 }
1838 }
1839 Err(ParseError::Truncated)
1840}
1841
1842fn parse_bool(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
1843 if bytes[*p..].starts_with(b"true") {
1844 *p += 4;
1845 Ok(JsonValue::Bool(true))
1846 } else if bytes[*p..].starts_with(b"false") {
1847 *p += 5;
1848 Ok(JsonValue::Bool(false))
1849 } else {
1850 Err(ParseError::Unexpected(bytes[*p] as char, *p))
1851 }
1852}
1853
1854fn parse_null(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
1855 if bytes[*p..].starts_with(b"null") {
1856 *p += 4;
1857 Ok(JsonValue::Null)
1858 } else {
1859 Err(ParseError::Unexpected(bytes[*p] as char, *p))
1860 }
1861}
1862
1863fn parse_number(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
1864 let start = *p;
1865 if bytes[*p] == b'-' {
1866 *p += 1;
1867 }
1868 while *p < bytes.len() && bytes[*p].is_ascii_digit() {
1869 *p += 1;
1870 }
1871 if *p < bytes.len() && bytes[*p] == b'.' {
1872 *p += 1;
1873 while *p < bytes.len() && bytes[*p].is_ascii_digit() {
1874 *p += 1;
1875 }
1876 }
1877 if *p < bytes.len() && matches!(bytes[*p], b'e' | b'E') {
1878 *p += 1;
1879 if *p < bytes.len() && matches!(bytes[*p], b'+' | b'-') {
1880 *p += 1;
1881 }
1882 while *p < bytes.len() && bytes[*p].is_ascii_digit() {
1883 *p += 1;
1884 }
1885 }
1886 let text = core::str::from_utf8(&bytes[start..*p])
1887 .map_err(|_| ParseError::InvalidNumber(start))?
1888 .to_string();
1889 if text.parse::<f64>().is_err() {
1891 return Err(ParseError::InvalidNumber(start));
1892 }
1893 Ok(JsonValue::NumberText(text))
1894}
1895
1896#[derive(Debug, Clone, Copy)]
1917enum IdxBound {
1918 At(usize),
1919 FromLast(usize),
1920}
1921
1922impl IdxBound {
1923 fn resolve(self, len: usize) -> Option<usize> {
1925 match self {
1926 Self::At(n) => (n < len).then_some(n),
1927 Self::FromLast(off) => len.checked_sub(1 + off),
1928 }
1929 }
1930}
1931
1932#[derive(Debug, Clone, Copy)]
1934enum NumMethod {
1935 Abs,
1936 Floor,
1937 Ceiling,
1938 Double,
1939}
1940
1941#[derive(Debug, Clone)]
1942enum PathStep {
1943 Field(String),
1944 Index(IdxBound),
1945 Wildcard,
1946 Range(IdxBound, IdxBound),
1949 Filter(FilterExpr),
1952 Size,
1954 TypeOf,
1956 Num(NumMethod),
1958 RecursiveAll,
1960}
1961
1962#[derive(Debug, Clone)]
1963struct FilterPred {
1964 path: Vec<String>,
1966 op: FilterOp,
1967 val: FilterVal,
1968 regex_flags: Option<String>,
1972}
1973
1974#[derive(Debug, Clone)]
1976enum FilterExpr {
1977 Cmp(FilterPred),
1978 And(alloc::boxed::Box<FilterExpr>, alloc::boxed::Box<FilterExpr>),
1979 Or(alloc::boxed::Box<FilterExpr>, alloc::boxed::Box<FilterExpr>),
1980}
1981
1982#[derive(Debug, Clone, Copy)]
1983enum FilterOp {
1984 Gt,
1985 Lt,
1986 Ge,
1987 Le,
1988 Eq,
1989 Ne,
1990 StartsWith,
1992 LikeRegex,
1994}
1995
1996#[derive(Debug, Clone)]
1997enum FilterVal {
1998 Num(f64),
1999 Str(String),
2000 Bool(bool),
2001 Null,
2003 Var(String),
2006}
2007
2008fn parse_jsonpath_mode(p: &str) -> Result<(bool, Vec<PathStep>), EvalError> {
2013 let trimmed = p.trim_start();
2014 let (strict, p) = if let Some(rest) = trimmed.strip_prefix("strict") {
2015 (true, rest.trim_start())
2016 } else if let Some(rest) = trimmed.strip_prefix("lax") {
2017 (false, rest.trim_start())
2018 } else {
2019 (false, trimmed)
2020 };
2021 let chars: Vec<char> = p.chars().collect();
2022 let mut i = 0;
2023 if i >= chars.len() || chars[i] != '$' {
2024 return Err(EvalError::TypeMismatch {
2025 detail: alloc::format!("jsonpath must start with '$', got {p:?}"),
2026 });
2027 }
2028 i += 1;
2029 let mut steps: Vec<PathStep> = Vec::new();
2030 while i < chars.len() {
2031 match chars[i] {
2032 '.' => {
2033 i += 1;
2034 if i + 1 < chars.len() && chars[i] == '*' && chars[i + 1] == '*' {
2037 i += 2;
2038 steps.push(PathStep::RecursiveAll);
2039 continue;
2040 }
2041 if i < chars.len() && chars[i] == '"' {
2042 i += 1;
2043 let start = i;
2044 while i < chars.len() && chars[i] != '"' {
2045 i += 1;
2046 }
2047 if i >= chars.len() {
2048 return Err(EvalError::TypeMismatch {
2049 detail: "jsonpath: unterminated quoted field".into(),
2050 });
2051 }
2052 steps.push(PathStep::Field(chars[start..i].iter().collect()));
2053 i += 1;
2054 } else {
2055 let start = i;
2056 while i < chars.len()
2057 && chars[i] != '.'
2058 && chars[i] != '['
2059 && chars[i] != '('
2060 && !chars[i].is_whitespace()
2061 {
2062 i += 1;
2063 }
2064 if start == i {
2065 return Err(EvalError::TypeMismatch {
2066 detail: "jsonpath: missing field name after '.'".into(),
2067 });
2068 }
2069 let name: String = chars[start..i].iter().collect();
2070 if i < chars.len() && chars[i] == '(' {
2072 i += 1;
2073 while i < chars.len() && chars[i] != ')' {
2074 i += 1;
2075 }
2076 if i >= chars.len() {
2077 return Err(EvalError::TypeMismatch {
2078 detail: "jsonpath: unterminated method call".into(),
2079 });
2080 }
2081 i += 1; match name.as_str() {
2083 "size" => steps.push(PathStep::Size),
2084 "type" => steps.push(PathStep::TypeOf),
2085 "abs" => steps.push(PathStep::Num(NumMethod::Abs)),
2087 "floor" => steps.push(PathStep::Num(NumMethod::Floor)),
2088 "ceiling" => steps.push(PathStep::Num(NumMethod::Ceiling)),
2089 "double" => steps.push(PathStep::Num(NumMethod::Double)),
2090 other => {
2091 return Err(EvalError::TypeMismatch {
2092 detail: alloc::format!(
2093 "jsonpath: unsupported method .{other}()"
2094 ),
2095 });
2096 }
2097 }
2098 } else {
2099 steps.push(PathStep::Field(name));
2100 }
2101 }
2102 }
2103 '?' => {
2104 i += 1;
2106 let (pred, ni) = parse_filter_pred(&chars, i)?;
2107 i = ni;
2108 steps.push(PathStep::Filter(pred));
2109 }
2110 '[' => {
2111 i += 1;
2112 if i < chars.len() && chars[i] == '*' {
2113 i += 1;
2114 if i >= chars.len() || chars[i] != ']' {
2115 return Err(EvalError::TypeMismatch {
2116 detail: "jsonpath: expected ']' after '[*'".into(),
2117 });
2118 }
2119 i += 1;
2120 steps.push(PathStep::Wildcard);
2121 } else {
2122 let mut parse_bound = |i: &mut usize| -> Result<IdxBound, EvalError> {
2124 jp_skip_ws(&chars, i);
2125 if chars[*i..].starts_with(&['l', 'a', 's', 't']) {
2126 *i += 4;
2127 jp_skip_ws(&chars, i);
2128 if *i < chars.len() && chars[*i] == '-' {
2129 *i += 1;
2130 jp_skip_ws(&chars, i);
2131 let s = *i;
2132 while *i < chars.len() && chars[*i].is_ascii_digit() {
2133 *i += 1;
2134 }
2135 let off: usize =
2136 chars[s..*i].iter().collect::<String>().parse().map_err(
2137 |_| EvalError::TypeMismatch {
2138 detail: "jsonpath: invalid `last - N` offset".into(),
2139 },
2140 )?;
2141 return Ok(IdxBound::FromLast(off));
2142 }
2143 return Ok(IdxBound::FromLast(0));
2144 }
2145 let s = *i;
2146 while *i < chars.len() && chars[*i].is_ascii_digit() {
2147 *i += 1;
2148 }
2149 if s == *i {
2150 return Err(EvalError::TypeMismatch {
2151 detail: "jsonpath: expected `N`, `last[ - K]` or `*` subscript"
2152 .into(),
2153 });
2154 }
2155 Ok(IdxBound::At(
2156 chars[s..*i]
2157 .iter()
2158 .collect::<String>()
2159 .parse()
2160 .map_err(|_| EvalError::TypeMismatch {
2161 detail: "jsonpath: invalid array index".into(),
2162 })?,
2163 ))
2164 };
2165 let idx = parse_bound(&mut i)?;
2166 while i < chars.len() && chars[i].is_whitespace() {
2168 i += 1;
2169 }
2170 if i + 1 < chars.len() && chars[i] == 't' && chars[i + 1] == 'o' {
2171 i += 2;
2172 let hi = parse_bound(&mut i)?;
2173 while i < chars.len() && chars[i].is_whitespace() {
2174 i += 1;
2175 }
2176 if i >= chars.len() || chars[i] != ']' {
2177 return Err(EvalError::TypeMismatch {
2178 detail: "jsonpath: expected ']' after range".into(),
2179 });
2180 }
2181 i += 1;
2182 steps.push(PathStep::Range(idx, hi));
2183 } else {
2184 if i >= chars.len() || chars[i] != ']' {
2185 return Err(EvalError::TypeMismatch {
2186 detail: "jsonpath: expected ']' after array index".into(),
2187 });
2188 }
2189 i += 1;
2190 steps.push(PathStep::Index(idx));
2191 }
2192 }
2193 }
2194 c if c.is_whitespace() => {
2195 i += 1;
2196 }
2197 c => {
2198 return Err(EvalError::TypeMismatch {
2199 detail: alloc::format!(
2200 "jsonpath: unexpected char '{c}' (supports `$.field`, `[N]`, `[N to M]`, `[*]`, `? (...)`, `.size()`, `.type()`)"
2201 ),
2202 });
2203 }
2204 }
2205 }
2206 Ok((strict, steps))
2207}
2208
2209fn parse_jsonpath(p: &str) -> Result<Vec<PathStep>, EvalError> {
2211 parse_jsonpath_mode(p).map(|(_, steps)| steps)
2212}
2213
2214fn jp_skip_ws(chars: &[char], i: &mut usize) {
2215 while *i < chars.len() && chars[*i].is_whitespace() {
2216 *i += 1;
2217 }
2218}
2219
2220fn parse_filter_pred(chars: &[char], mut i: usize) -> Result<(FilterExpr, usize), EvalError> {
2224 let err = |m: &str| EvalError::TypeMismatch {
2225 detail: alloc::format!("jsonpath filter: {m}"),
2226 };
2227 jp_skip_ws(chars, &mut i);
2228 if i >= chars.len() || chars[i] != '(' {
2229 return Err(err("expected '(' after '?'"));
2230 }
2231 i += 1;
2232 let (expr, ni) = parse_filter_or(chars, i)?;
2233 i = ni;
2234 jp_skip_ws(chars, &mut i);
2235 if i >= chars.len() || chars[i] != ')' {
2236 return Err(err("expected ')' to close the filter"));
2237 }
2238 i += 1;
2239 Ok((expr, i))
2240}
2241
2242fn parse_filter_or(chars: &[char], i: usize) -> Result<(FilterExpr, usize), EvalError> {
2244 let (mut left, mut i) = parse_filter_and(chars, i)?;
2245 loop {
2246 jp_skip_ws(chars, &mut i);
2247 if i + 1 < chars.len() && chars[i] == '|' && chars[i + 1] == '|' {
2248 i += 2;
2249 let (right, ni) = parse_filter_and(chars, i)?;
2250 i = ni;
2251 left = FilterExpr::Or(alloc::boxed::Box::new(left), alloc::boxed::Box::new(right));
2252 } else {
2253 return Ok((left, i));
2254 }
2255 }
2256}
2257
2258fn parse_filter_and(chars: &[char], i: usize) -> Result<(FilterExpr, usize), EvalError> {
2260 let (mut left, mut i) = parse_filter_atom(chars, i)?;
2261 loop {
2262 jp_skip_ws(chars, &mut i);
2263 if i + 1 < chars.len() && chars[i] == '&' && chars[i + 1] == '&' {
2264 i += 2;
2265 let (right, ni) = parse_filter_atom(chars, i)?;
2266 i = ni;
2267 left = FilterExpr::And(alloc::boxed::Box::new(left), alloc::boxed::Box::new(right));
2268 } else {
2269 return Ok((left, i));
2270 }
2271 }
2272}
2273
2274fn parse_filter_atom(chars: &[char], mut i: usize) -> Result<(FilterExpr, usize), EvalError> {
2276 let err = |m: &str| EvalError::TypeMismatch {
2277 detail: alloc::format!("jsonpath filter: {m}"),
2278 };
2279 jp_skip_ws(chars, &mut i);
2280 if i < chars.len() && chars[i] == '(' {
2281 i += 1;
2282 let (expr, ni) = parse_filter_or(chars, i)?;
2283 i = ni;
2284 jp_skip_ws(chars, &mut i);
2285 if i >= chars.len() || chars[i] != ')' {
2286 return Err(err("expected ')' in grouped predicate"));
2287 }
2288 i += 1;
2289 return Ok((expr, i));
2290 }
2291 if i >= chars.len() || chars[i] != '@' {
2292 return Err(err("only `@`-based predicates are supported"));
2293 }
2294 i += 1;
2295 let mut path: Vec<String> = Vec::new();
2296 while i < chars.len() && chars[i] == '.' {
2297 i += 1;
2298 let start = i;
2299 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
2300 i += 1;
2301 }
2302 path.push(chars[start..i].iter().collect());
2303 }
2304 let (op, val, regex_flags, ni) = parse_cmp_and_literal(chars, i)?;
2305 i = ni;
2306 Ok((
2307 FilterExpr::Cmp(FilterPred {
2308 path,
2309 op,
2310 val,
2311 regex_flags,
2312 }),
2313 i,
2314 ))
2315}
2316
2317fn parse_cmp_and_literal(
2321 chars: &[char],
2322 mut i: usize,
2323) -> Result<(FilterOp, FilterVal, Option<String>, usize), EvalError> {
2324 let err = |m: &str| EvalError::TypeMismatch {
2325 detail: alloc::format!("jsonpath predicate: {m}"),
2326 };
2327 while i < chars.len() && chars[i].is_whitespace() {
2328 i += 1;
2329 }
2330 let kw = |i: usize, w: &str| -> bool {
2331 let wc: Vec<char> = w.chars().collect();
2332 chars[i..].starts_with(&wc)
2333 };
2334 let op = if i + 1 < chars.len() && chars[i] == '>' && chars[i + 1] == '=' {
2335 i += 2;
2336 FilterOp::Ge
2337 } else if i + 1 < chars.len() && chars[i] == '<' && chars[i + 1] == '=' {
2338 i += 2;
2339 FilterOp::Le
2340 } else if i + 1 < chars.len() && chars[i] == '=' && chars[i + 1] == '=' {
2341 i += 2;
2342 FilterOp::Eq
2343 } else if i + 1 < chars.len() && chars[i] == '!' && chars[i + 1] == '=' {
2344 i += 2;
2345 FilterOp::Ne
2346 } else if i < chars.len() && chars[i] == '>' {
2347 i += 1;
2348 FilterOp::Gt
2349 } else if i < chars.len() && chars[i] == '<' {
2350 i += 1;
2351 FilterOp::Lt
2352 } else if kw(i, "starts") {
2354 i += 6;
2355 while i < chars.len() && chars[i].is_whitespace() {
2356 i += 1;
2357 }
2358 if !kw(i, "with") {
2359 return Err(err("expected `with` after `starts`"));
2360 }
2361 i += 4;
2362 FilterOp::StartsWith
2363 } else if kw(i, "like_regex") {
2364 i += 10;
2365 FilterOp::LikeRegex
2366 } else {
2367 return Err(err(
2368 "expected a comparison operator (> < >= <= == != starts with like_regex)",
2369 ));
2370 };
2371 while i < chars.len() && chars[i].is_whitespace() {
2372 i += 1;
2373 }
2374 let val = if i < chars.len() && chars[i] == '"' {
2375 i += 1;
2376 let start = i;
2377 while i < chars.len() && chars[i] != '"' {
2378 i += 1;
2379 }
2380 if i >= chars.len() {
2381 return Err(err("unterminated string literal"));
2382 }
2383 let s: String = chars[start..i].iter().collect();
2384 i += 1;
2385 FilterVal::Str(s)
2386 } else if chars[i..].starts_with(&['t', 'r', 'u', 'e']) {
2387 i += 4;
2388 FilterVal::Bool(true)
2389 } else if chars[i..].starts_with(&['f', 'a', 'l', 's', 'e']) {
2390 i += 5;
2391 FilterVal::Bool(false)
2392 } else if chars[i..].starts_with(&['n', 'u', 'l', 'l']) {
2394 i += 4;
2395 FilterVal::Null
2396 } else if i < chars.len() && chars[i] == '$' {
2397 i += 1;
2398 let start = i;
2399 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
2400 i += 1;
2401 }
2402 if start == i {
2403 return Err(err("expected a variable name after '$'"));
2404 }
2405 FilterVal::Var(chars[start..i].iter().collect())
2406 } else {
2407 let start = i;
2408 if i < chars.len() && (chars[i] == '-' || chars[i] == '+') {
2409 i += 1;
2410 }
2411 while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '.') {
2412 i += 1;
2413 }
2414 let num: f64 = chars[start..i]
2415 .iter()
2416 .collect::<String>()
2417 .parse()
2418 .map_err(|_| err("invalid numeric literal"))?;
2419 FilterVal::Num(num)
2420 };
2421 let mut flags: Option<String> = None;
2424 if matches!(op, FilterOp::LikeRegex) {
2425 let mut j = i;
2426 while j < chars.len() && chars[j].is_whitespace() {
2427 j += 1;
2428 }
2429 if chars[j..].starts_with(&['f', 'l', 'a', 'g']) {
2430 j += 4;
2431 while j < chars.len() && chars[j].is_whitespace() {
2432 j += 1;
2433 }
2434 if j < chars.len() && chars[j] == '"' {
2435 j += 1;
2436 let start = j;
2437 while j < chars.len() && chars[j] != '"' {
2438 j += 1;
2439 }
2440 if j < chars.len() {
2441 flags = Some(chars[start..j].iter().collect());
2442 j += 1;
2443 i = j;
2444 }
2445 }
2446 }
2447 }
2448 Ok((op, val, flags, i))
2449}
2450
2451fn json_type_name(v: &JsonValue) -> &'static str {
2453 match v {
2454 JsonValue::Null => "null",
2455 JsonValue::Bool(_) => "boolean",
2456 JsonValue::Number(_) | JsonValue::NumberText(_) => "number",
2457 JsonValue::String(_) => "string",
2458 JsonValue::Array(_) => "array",
2459 JsonValue::Object(_) => "object",
2460 }
2461}
2462
2463fn json_num(v: &JsonValue) -> Option<f64> {
2465 match v {
2466 JsonValue::Number(n) => Some(*n),
2467 JsonValue::NumberText(s) => s.parse().ok(),
2468 _ => None,
2469 }
2470}
2471
2472fn resolve_accessor<'a>(node: &'a JsonValue, path: &[String]) -> Option<&'a JsonValue> {
2474 let mut cur = node;
2475 for key in path {
2476 match cur {
2477 JsonValue::Object(entries) => {
2478 cur = &entries.iter().find(|(k, _)| k == key)?.1;
2479 }
2480 _ => return None,
2481 }
2482 }
2483 Some(cur)
2484}
2485
2486fn filter_matches(node: &JsonValue, pred: &FilterPred, vars: Option<&JsonValue>) -> bool {
2490 let Some(target) = resolve_accessor(node, &pred.path) else {
2491 return false;
2492 };
2493 let resolved;
2495 let val = match &pred.val {
2496 FilterVal::Var(name) => {
2497 let Some(JsonValue::Object(entries)) = vars else {
2498 return false;
2499 };
2500 let Some((_, v)) = entries.iter().find(|(k, _)| k == name) else {
2501 return false;
2502 };
2503 resolved = match v {
2504 JsonValue::Number(n) => FilterVal::Num(*n),
2505 JsonValue::NumberText(s) => match s.parse::<f64>() {
2506 Ok(n) => FilterVal::Num(n),
2507 Err(_) => return false,
2508 },
2509 JsonValue::String(s) => FilterVal::Str(s.clone()),
2510 JsonValue::Bool(b) => FilterVal::Bool(*b),
2511 JsonValue::Null => FilterVal::Null,
2512 _ => return false,
2513 };
2514 &resolved
2515 }
2516 other => other,
2517 };
2518 match val {
2519 FilterVal::Num(rhs) => match json_num(target) {
2520 Some(lhs) => match pred.op {
2521 FilterOp::Gt => lhs > *rhs,
2522 FilterOp::Lt => lhs < *rhs,
2523 FilterOp::Ge => lhs >= *rhs,
2524 FilterOp::Le => lhs <= *rhs,
2525 FilterOp::Eq => lhs == *rhs,
2526 FilterOp::Ne => lhs != *rhs,
2527 FilterOp::StartsWith | FilterOp::LikeRegex => false,
2528 },
2529 None => false,
2530 },
2531 FilterVal::Str(rhs) => match target {
2532 JsonValue::String(lhs) => match pred.op {
2533 FilterOp::Eq => lhs == rhs,
2534 FilterOp::Ne => lhs != rhs,
2535 FilterOp::Gt => lhs.as_str() > rhs.as_str(),
2536 FilterOp::Lt => lhs.as_str() < rhs.as_str(),
2537 FilterOp::Ge => lhs.as_str() >= rhs.as_str(),
2538 FilterOp::Le => lhs.as_str() <= rhs.as_str(),
2539 FilterOp::StartsWith => lhs.starts_with(rhs.as_str()),
2541 FilterOp::LikeRegex => {
2542 if pred.regex_flags.as_deref().is_some_and(|f| f.contains('i')) {
2545 crate::eval::regex_is_match(&rhs.to_lowercase(), &lhs.to_lowercase())
2546 .unwrap_or(false)
2547 } else {
2548 crate::eval::regex_is_match(rhs, lhs).unwrap_or(false)
2549 }
2550 }
2551 },
2552 _ => false,
2553 },
2554 FilterVal::Bool(rhs) => match target {
2555 JsonValue::Bool(lhs) => match pred.op {
2556 FilterOp::Eq => lhs == rhs,
2557 FilterOp::Ne => lhs != rhs,
2558 _ => false,
2559 },
2560 _ => false,
2561 },
2562 FilterVal::Null => match pred.op {
2564 FilterOp::Eq => matches!(target, JsonValue::Null),
2565 FilterOp::Ne => !matches!(target, JsonValue::Null),
2566 _ => false,
2567 },
2568 FilterVal::Var(_) => false, }
2570}
2571
2572fn filter_expr_matches(node: &JsonValue, expr: &FilterExpr, vars: Option<&JsonValue>) -> bool {
2574 match expr {
2575 FilterExpr::Cmp(pred) => filter_matches(node, pred, vars),
2576 FilterExpr::And(a, b) => {
2577 filter_expr_matches(node, a, vars) && filter_expr_matches(node, b, vars)
2578 }
2579 FilterExpr::Or(a, b) => {
2580 filter_expr_matches(node, a, vars) || filter_expr_matches(node, b, vars)
2581 }
2582 }
2583}
2584
2585fn apply_jsonpath(
2587 root: &JsonValue,
2588 steps: &[PathStep],
2589 vars: Option<&JsonValue>,
2590) -> Vec<JsonValue> {
2591 apply_jsonpath_mode(root, steps, vars, false).unwrap_or_default()
2592}
2593
2594fn apply_jsonpath_mode(
2608 root: &JsonValue,
2609 steps: &[PathStep],
2610 vars: Option<&JsonValue>,
2611 strict: bool,
2612) -> Result<Vec<JsonValue>, EvalError> {
2613 let err = |m: alloc::string::String| Err(EvalError::TypeMismatch { detail: m });
2614 let mut cur: Vec<JsonValue> = alloc::vec![root.clone()];
2615 for step in steps {
2616 if !strict {
2618 match step {
2619 PathStep::Field(_) => {
2621 let mut flat: Vec<JsonValue> = Vec::new();
2622 for node in cur {
2623 match node {
2624 JsonValue::Array(items) => flat.extend(items),
2625 other => flat.push(other),
2626 }
2627 }
2628 cur = flat;
2629 }
2630 PathStep::Wildcard | PathStep::Index(_) | PathStep::Range(..) => {
2632 cur = cur
2633 .into_iter()
2634 .map(|n| match n {
2635 arr @ JsonValue::Array(_) => arr,
2636 other => JsonValue::Array(alloc::vec![other]),
2637 })
2638 .collect();
2639 }
2640 _ => {}
2641 }
2642 } else {
2643 for node in &cur {
2645 match step {
2646 PathStep::Field(k) => match node {
2647 JsonValue::Object(entries) => {
2648 if !entries.iter().any(|(name, _)| name == k) {
2649 return err(alloc::format!(
2650 "JSON object does not contain key \"{k}\""
2651 ));
2652 }
2653 }
2654 _ => {
2655 return err(
2656 "jsonpath member accessor can only be applied to an object".into(),
2657 );
2658 }
2659 },
2660 PathStep::Wildcard => {
2661 if !matches!(node, JsonValue::Array(_)) {
2662 return err(
2663 "jsonpath wildcard array accessor can only be applied to an array"
2664 .into(),
2665 );
2666 }
2667 }
2668 PathStep::Index(idx) => match node {
2669 JsonValue::Array(items) => {
2670 if idx.resolve(items.len()).is_none_or(|p| p >= items.len()) {
2671 return err("jsonpath array subscript is out of bounds".into());
2672 }
2673 }
2674 _ => {
2675 return err(
2676 "jsonpath array accessor can only be applied to an array".into()
2677 );
2678 }
2679 },
2680 PathStep::Range(lo, hi) => match node {
2681 JsonValue::Array(items) => {
2682 let n = items.len();
2683 if lo.resolve(n).is_none_or(|p| p >= n)
2684 || hi.resolve(n).is_none_or(|p| p >= n)
2685 {
2686 return err("jsonpath array subscript is out of bounds".into());
2687 }
2688 }
2689 _ => {
2690 return err(
2691 "jsonpath array accessor can only be applied to an array".into()
2692 );
2693 }
2694 },
2695 _ => {}
2696 }
2697 }
2698 }
2699 let mut next: Vec<JsonValue> = Vec::new();
2700 for node in &cur {
2701 match (step, node) {
2702 (PathStep::Field(k), JsonValue::Object(entries)) => {
2703 if let Some((_, v)) = entries.iter().find(|(name, _)| name == k) {
2704 next.push(v.clone());
2705 }
2706 }
2707 (PathStep::Index(idx), JsonValue::Array(items)) => {
2708 if let Some(pos) = idx.resolve(items.len())
2709 && let Some(v) = items.get(pos)
2710 {
2711 next.push(v.clone());
2712 }
2713 }
2714 (PathStep::Wildcard, JsonValue::Array(items)) => {
2715 next.extend(items.iter().cloned());
2716 }
2717 (PathStep::Range(lo, hi), JsonValue::Array(items)) => {
2719 if let (Some(a), Some(b)) = (lo.resolve(items.len()), hi.resolve(items.len())) {
2720 for idx in a..=b {
2721 if let Some(v) = items.get(idx) {
2722 next.push(v.clone());
2723 }
2724 }
2725 }
2726 }
2727 (PathStep::Filter(expr), node) => {
2728 if filter_expr_matches(node, expr, vars) {
2729 next.push(node.clone());
2730 }
2731 }
2732 (PathStep::Size, JsonValue::Array(items)) => {
2733 next.push(JsonValue::Number(items.len() as f64));
2734 }
2735 (PathStep::Size, _) => next.push(JsonValue::Number(1.0)),
2737 (PathStep::TypeOf, node) => {
2738 next.push(JsonValue::String(json_type_name(node).into()));
2739 }
2740 (PathStep::RecursiveAll, node) => {
2743 fn descend(v: &JsonValue, out: &mut Vec<JsonValue>) {
2744 out.push(v.clone());
2745 match v {
2746 JsonValue::Object(entries) => {
2747 for (_, child) in entries {
2748 descend(child, out);
2749 }
2750 }
2751 JsonValue::Array(items) => {
2752 for child in items {
2753 descend(child, out);
2754 }
2755 }
2756 _ => {}
2757 }
2758 }
2759 descend(node, &mut next);
2760 }
2761 (PathStep::Num(m), node) => {
2763 let n = match m {
2764 NumMethod::Double => match node {
2766 JsonValue::String(s) => s.parse::<f64>().ok(),
2767 other => json_num(other),
2768 },
2769 _ => json_num(node),
2770 };
2771 if let Some(x) = n {
2772 let out = match m {
2773 NumMethod::Abs => x.abs(),
2774 NumMethod::Floor => x.floor(),
2775 NumMethod::Ceiling => x.ceil(),
2776 NumMethod::Double => x,
2777 };
2778 next.push(JsonValue::Number(out));
2779 }
2780 }
2781 _ => {} }
2783 }
2784 cur = next;
2785 if cur.is_empty() {
2786 return Ok(Vec::new());
2787 }
2788 }
2789 Ok(cur)
2790}
2791
2792pub fn path_predicate(doc: &Value, path: &Value) -> Result<Option<bool>, EvalError> {
2797 path_predicate_vars(doc, path, None)
2798}
2799
2800pub fn path_predicate_vars(
2802 doc: &Value,
2803 path: &Value,
2804 vars: Option<&JsonValue>,
2805) -> Result<Option<bool>, EvalError> {
2806 let (src, ptext) = match (doc, path) {
2807 (Value::Null, _) | (_, Value::Null) => return Ok(None),
2808 (Value::Json(s) | Value::Text(s), Value::Text(p) | Value::Json(p)) => (s, p),
2809 _ => return Ok(None),
2810 };
2811 let trimmed = ptext.trim();
2813 if let Some(inner) = trimmed
2814 .strip_prefix("exists")
2815 .map(str::trim_start)
2816 .and_then(|r| r.strip_prefix('('))
2817 .and_then(|r| r.strip_suffix(')'))
2818 {
2819 let (strict, steps) = parse_jsonpath_mode(inner.trim())?;
2820 let root = parse(src).map_err(|e| EvalError::TypeMismatch {
2821 detail: alloc::format!("{e}"),
2822 })?;
2823 return Ok(Some(
2824 !apply_jsonpath_mode(&root, &steps, vars, strict)?.is_empty(),
2825 ));
2826 }
2827 let chars: Vec<char> = ptext.chars().collect();
2828 let mut depth = 0i32;
2831 let mut i = 0;
2832 let mut op_at = None;
2833 while i < chars.len() {
2834 match chars[i] {
2835 '(' | '[' => depth += 1,
2836 ')' | ']' => depth -= 1,
2837 '"' => {
2838 i += 1;
2839 while i < chars.len() && chars[i] != '"' {
2840 i += 1;
2841 }
2842 }
2843 '>' | '<' | '=' | '!' if depth == 0 => {
2844 op_at = Some(i);
2845 break;
2846 }
2847 _ => {}
2848 }
2849 i += 1;
2850 }
2851 let Some(pos) = op_at else { return Ok(None) };
2852 let left: String = chars[..pos].iter().collect();
2853 let (strict, steps) = parse_jsonpath_mode(left.trim())?;
2854 let (op, val, regex_flags, _) = parse_cmp_and_literal(&chars, pos)?;
2855 let root = parse(src).map_err(|e| EvalError::TypeMismatch {
2856 detail: alloc::format!("{e}"),
2857 })?;
2858 let results = apply_jsonpath_mode(&root, &steps, vars, strict)?;
2861 let pred = FilterPred {
2862 path: Vec::new(),
2863 op,
2864 val,
2865 regex_flags,
2866 };
2867 Ok(Some(results.iter().any(|v| filter_matches(v, &pred, vars))))
2868}
2869
2870pub fn path_query(doc: &Value, path: &Value) -> Result<Value<'static>, EvalError> {
2874 path_query_vars(doc, path, None)
2875}
2876
2877pub fn parse_path_vars(v: &Value) -> Result<Option<JsonValue>, EvalError> {
2880 match v {
2881 Value::Null => Ok(None),
2882 Value::Json(s) | Value::Text(s) => {
2883 let parsed = parse(s).map_err(|e| EvalError::TypeMismatch {
2884 detail: alloc::format!("invalid jsonpath vars document: {e}"),
2885 })?;
2886 if !matches!(parsed, JsonValue::Object(_)) {
2887 return Err(EvalError::TypeMismatch {
2888 detail: "jsonpath vars must be a JSON object".into(),
2889 });
2890 }
2891 Ok(Some(parsed))
2892 }
2893 other => Err(EvalError::TypeMismatch {
2894 detail: alloc::format!(
2895 "jsonpath vars must be jsonb, got {}",
2896 crate::conversions::pg_type_name_for_error_opt(other.data_type())
2897 ),
2898 }),
2899 }
2900}
2901
2902pub fn path_query_vars(
2904 doc: &Value,
2905 path: &Value,
2906 vars: Option<&JsonValue>,
2907) -> Result<Value<'static>, EvalError> {
2908 let (src, path_text) = match (doc, path) {
2909 (Value::Null, _) | (_, Value::Null) => return Ok(Value::Null),
2910 (Value::Json(s) | Value::Text(s), Value::Text(p) | Value::Json(p)) => (s, p),
2911 _ => {
2912 return Err(EvalError::TypeMismatch {
2913 detail: "jsonb_path_query() expects (JSON, TEXT)".into(),
2914 });
2915 }
2916 };
2917 let root = parse(src).map_err(|e| EvalError::TypeMismatch {
2918 detail: alloc::format!("invalid JSON for jsonb_path_query: {e}"),
2919 })?;
2920 let trimmed = path_text.trim();
2922 if let Some(inner) = trimmed
2923 .strip_prefix("exists")
2924 .map(str::trim_start)
2925 .and_then(|r| r.strip_prefix('('))
2926 .and_then(|r| r.strip_suffix(')'))
2927 {
2928 let steps = parse_jsonpath(inner.trim())?;
2929 let hit = !apply_jsonpath(&root, &steps, vars).is_empty();
2930 return Ok(Value::TextArray(alloc::vec![Some(
2931 if hit { "true" } else { "false" }.into()
2932 )]));
2933 }
2934 let (strict, steps) = parse_jsonpath_mode(path_text)?;
2937 let matches = apply_jsonpath_mode(&root, &steps, vars, strict)?;
2938 let arr: Vec<Option<String>> = matches
2939 .into_iter()
2940 .map(|v| Some(json_canonical_string(&v)))
2941 .collect();
2942 Ok(Value::TextArray(arr))
2943}
2944
2945pub fn path_query_first(doc: &Value, path: &Value) -> Result<Value<'static>, EvalError> {
2948 path_query_first_vars(doc, path, None)
2949}
2950
2951pub fn path_query_first_vars(
2953 doc: &Value,
2954 path: &Value,
2955 vars: Option<&JsonValue>,
2956) -> Result<Value<'static>, EvalError> {
2957 let q = path_query_vars(doc, path, vars)?;
2958 match q {
2959 Value::TextArray(items) => {
2960 if let Some(Some(first)) = items.into_iter().next() {
2961 Ok(Value::json(first))
2962 } else {
2963 Ok(Value::Null)
2964 }
2965 }
2966 other => Ok(other),
2967 }
2968}
2969
2970pub fn path_query_array(doc: &Value, path: &Value) -> Result<Value<'static>, EvalError> {
2973 path_query_array_vars(doc, path, None)
2974}
2975
2976pub fn path_query_array_vars(
2978 doc: &Value,
2979 path: &Value,
2980 vars: Option<&JsonValue>,
2981) -> Result<Value<'static>, EvalError> {
2982 let q = path_query_vars(doc, path, vars)?;
2983 let arr = match q {
2984 Value::TextArray(items) => {
2985 let mut buf = String::from("[");
2986 let mut first = true;
2987 for s in items.into_iter().flatten() {
2988 if !first {
2989 buf.push_str(", ");
2990 }
2991 buf.push_str(&s);
2992 first = false;
2993 }
2994 buf.push(']');
2995 Value::json(buf)
2996 }
2997 other => other,
2998 };
2999 Ok(canonicalize_value(arr))
3003}
3004
3005pub fn jsonpath_canonical(input: &str) -> Result<String, EvalError> {
3032 let trimmed = input.trim();
3033 let (strict, body) = if let Some(rest) = trimmed.strip_prefix("strict ") {
3034 (true, rest.trim_start())
3035 } else if let Some(rest) = trimmed.strip_prefix("lax ") {
3036 (false, rest.trim_start())
3037 } else {
3038 (false, trimmed)
3039 };
3040 let steps = parse_jsonpath(body).map_err(|_| {
3041 let tok: String = body.chars().take(1).collect();
3044 EvalError::TypeMismatch {
3045 detail: alloc::format!("syntax error at or near {tok:?} of jsonpath input"),
3046 }
3047 })?;
3048 let mut out = String::new();
3049 if strict {
3050 out.push_str("strict ");
3051 }
3052 out.push('$');
3053 fn idx(b: &IdxBound, out: &mut String) {
3054 match b {
3055 IdxBound::At(n) => {
3056 let _ = core::fmt::Write::write_fmt(out, format_args!("{n}"));
3057 }
3058 IdxBound::FromLast(0) => out.push_str("last"),
3059 IdxBound::FromLast(k) => {
3060 let _ = core::fmt::Write::write_fmt(out, format_args!("last - {k}"));
3061 }
3062 }
3063 }
3064 fn fval(v: &FilterVal, out: &mut String) {
3065 match v {
3066 FilterVal::Num(x) => {
3067 if x.fract() == 0.0 && x.abs() < 1e15 {
3068 let _ = core::fmt::Write::write_fmt(out, format_args!("{}", *x as i64));
3069 } else {
3070 let _ = core::fmt::Write::write_fmt(out, format_args!("{x}"));
3071 }
3072 }
3073 FilterVal::Str(s) => {
3074 let _ = core::fmt::Write::write_fmt(out, format_args!("{s:?}"));
3075 }
3076 FilterVal::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
3077 FilterVal::Null => out.push_str("null"),
3078 FilterVal::Var(n) => {
3079 let _ = core::fmt::Write::write_fmt(out, format_args!("$\"{n}\""));
3080 }
3081 }
3082 }
3083 fn fexpr(e: &FilterExpr, out: &mut String) {
3084 match e {
3085 FilterExpr::Cmp(p) => {
3086 out.push('@');
3087 for seg in &p.path {
3088 let _ = core::fmt::Write::write_fmt(out, format_args!(".\"{seg}\""));
3089 }
3090 let op = match p.op {
3091 FilterOp::Gt => " > ",
3092 FilterOp::Lt => " < ",
3093 FilterOp::Ge => " >= ",
3094 FilterOp::Le => " <= ",
3095 FilterOp::Eq => " == ",
3096 FilterOp::Ne => " != ",
3097 FilterOp::StartsWith => " starts with ",
3098 FilterOp::LikeRegex => " like_regex ",
3099 };
3100 out.push_str(op);
3101 fval(&p.val, out);
3102 if let Some(f) = &p.regex_flags {
3103 let _ = core::fmt::Write::write_fmt(out, format_args!(" flag \"{f}\""));
3104 }
3105 }
3106 FilterExpr::And(l, r) => {
3107 fexpr(l, out);
3108 out.push_str(" && ");
3109 fexpr(r, out);
3110 }
3111 FilterExpr::Or(l, r) => {
3112 fexpr(l, out);
3113 out.push_str(" || ");
3114 fexpr(r, out);
3115 }
3116 }
3117 }
3118 for st in &steps {
3119 match st {
3120 PathStep::Field(f) => {
3121 let _ = core::fmt::Write::write_fmt(&mut out, format_args!(".\"{f}\""));
3122 }
3123 PathStep::Index(b) => {
3124 out.push('[');
3125 idx(b, &mut out);
3126 out.push(']');
3127 }
3128 PathStep::Wildcard => out.push_str("[*]"),
3129 PathStep::Range(a, b) => {
3130 out.push('[');
3131 idx(a, &mut out);
3132 out.push_str(" to ");
3133 idx(b, &mut out);
3134 out.push(']');
3135 }
3136 PathStep::Filter(e) => {
3137 out.push_str("?(");
3138 fexpr(e, &mut out);
3139 out.push(')');
3140 }
3141 PathStep::Size => out.push_str(".size()"),
3142 PathStep::TypeOf => out.push_str(".type()"),
3143 PathStep::Num(m) => out.push_str(match m {
3144 NumMethod::Abs => ".abs()",
3145 NumMethod::Floor => ".floor()",
3146 NumMethod::Ceiling => ".ceiling()",
3147 NumMethod::Double => ".double()",
3148 }),
3149 PathStep::RecursiveAll => out.push_str(".**"),
3150 }
3151 }
3152 Ok(out)
3153}
3154
3155pub fn value_to_json_text(v: &Value) -> String {
3156 let mut out = String::new();
3157 encode_value_into(v, &mut out);
3158 out
3159}
3160
3161fn encode_value_into(v: &Value, out: &mut String) {
3162 match v {
3163 Value::Null => out.push_str("null"),
3164 Value::Bool(true) => out.push_str("true"),
3165 Value::Bool(false) => out.push_str("false"),
3166 Value::SmallInt(n) => out.push_str(&alloc::format!("{n}")),
3167 Value::Int(n) => out.push_str(&alloc::format!("{n}")),
3168 Value::BigInt(n) => out.push_str(&alloc::format!("{n}")),
3169 Value::Float(x) if !x.is_finite() => {
3172 let txt = if x.is_nan() {
3173 "NaN"
3174 } else if *x > 0.0 {
3175 "Infinity"
3176 } else {
3177 "-Infinity"
3178 };
3179 write_json(&JsonValue::String(txt.into()), out);
3180 }
3181 Value::Float(x) => out.push_str(&alloc::format!("{x}")),
3182 Value::Real(x) if !x.is_finite() => {
3183 let txt = if x.is_nan() {
3184 "NaN"
3185 } else if *x > 0.0 {
3186 "Infinity"
3187 } else {
3188 "-Infinity"
3189 };
3190 write_json(&JsonValue::String(txt.into()), out);
3191 }
3192 Value::Numeric {
3193 scaled,
3194 scale,
3195 kind,
3196 } => {
3197 use spg_storage::NumericKind as NK;
3198 match kind {
3199 NK::NaN => write_json(&JsonValue::String("NaN".into()), out),
3200 NK::PosInf => write_json(&JsonValue::String("Infinity".into()), out),
3201 NK::NegInf => write_json(&JsonValue::String("-Infinity".into()), out),
3202 NK::Finite => out.push_str(&render_numeric(*scaled, *scale)),
3204 }
3205 }
3206 Value::Text(s) => write_json(&JsonValue::String(s.to_string()), out),
3207 Value::Json(s) => {
3208 out.push_str(s);
3212 }
3213 Value::Composite(fields) => {
3216 out.push('{');
3217 for (i, (name, fv)) in fields.iter().enumerate() {
3218 if i > 0 {
3219 out.push(',');
3220 }
3221 write_json(&JsonValue::String(name.clone()), out);
3222 out.push(':');
3223 encode_value_into(fv, out);
3224 }
3225 out.push('}');
3226 }
3227 Value::TextArray(items) => {
3228 out.push('[');
3229 for (i, it) in items.iter().enumerate() {
3230 if i > 0 {
3231 out.push(',');
3232 }
3233 match it {
3234 Some(s) => write_json(&JsonValue::String(s.clone()), out),
3235 None => out.push_str("null"),
3236 }
3237 }
3238 out.push(']');
3239 }
3240 Value::IntArray(items) => {
3241 out.push('[');
3242 for (i, it) in items.iter().enumerate() {
3243 if i > 0 {
3244 out.push(',');
3245 }
3246 match it {
3247 Some(n) => out.push_str(&alloc::format!("{n}")),
3248 None => out.push_str("null"),
3249 }
3250 }
3251 out.push(']');
3252 }
3253 Value::BigIntArray(items) => {
3254 out.push('[');
3255 for (i, it) in items.iter().enumerate() {
3256 if i > 0 {
3257 out.push(',');
3258 }
3259 match it {
3260 Some(n) => out.push_str(&alloc::format!("{n}")),
3261 None => out.push_str("null"),
3262 }
3263 }
3264 out.push(']');
3265 }
3266 Value::Timestamp(_) => {
3270 let txt = crate::eval::values::value_to_text(v).replacen(' ', "T", 1);
3271 write_json(&JsonValue::String(txt), out);
3272 }
3273 other => {
3285 if let Some(elems) = crate::eval::values::array_elements(other) {
3286 out.push('[');
3287 for (i, e) in elems.iter().enumerate() {
3288 if i > 0 {
3289 out.push(',');
3290 }
3291 encode_value_into(e, out);
3292 }
3293 out.push(']');
3294 return;
3295 }
3296 let txt = crate::eval::values::value_to_text(other);
3297 write_json(&JsonValue::String(txt), out);
3298 }
3299 }
3300}
3301
3302fn render_numeric(scaled: i128, scale: u16) -> String {
3303 let neg = scaled < 0;
3304 let mag_str = alloc::format!("{}", scaled.unsigned_abs());
3305 let s = scale as usize;
3306 let body = if s == 0 {
3307 mag_str
3308 } else if mag_str.len() > s {
3309 let p = mag_str.len() - s;
3310 alloc::format!("{}.{}", &mag_str[..p], &mag_str[p..])
3311 } else {
3312 let pad = s - mag_str.len();
3313 alloc::format!("0.{}{}", "0".repeat(pad), mag_str)
3314 };
3315 if neg { alloc::format!("-{body}") } else { body }
3316}
3317
3318pub fn concat(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
3326 concat_inner(lhs, rhs).map(canonicalize_value)
3327}
3328
3329fn concat_inner(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
3330 let (a_src, b_src) = match (lhs, rhs) {
3331 (Value::Null, _) | (_, Value::Null) => return Ok(Value::Null),
3332 (Value::Json(a) | Value::Text(a), Value::Json(b) | Value::Text(b)) => {
3333 (a.as_ref(), b.as_ref())
3334 }
3335 _ => {
3336 return Err(EvalError::TypeMismatch {
3337 detail: "jsonb_concat() expects (JSON, JSON)".into(),
3338 });
3339 }
3340 };
3341 let a = parse(a_src).map_err(|e| EvalError::TypeMismatch {
3342 detail: alloc::format!("invalid JSON lhs for concat: {e}"),
3343 })?;
3344 let b = parse(b_src).map_err(|e| EvalError::TypeMismatch {
3345 detail: alloc::format!("invalid JSON rhs for concat: {e}"),
3346 })?;
3347 let merged = match (a, b) {
3348 (JsonValue::Object(mut ea), JsonValue::Object(eb)) => {
3349 for (k, v) in eb {
3351 if let Some(slot) = ea.iter_mut().find(|(ek, _)| *ek == k) {
3352 slot.1 = v;
3353 } else {
3354 ea.push((k, v));
3355 }
3356 }
3357 JsonValue::Object(ea)
3358 }
3359 (JsonValue::Array(mut ia), JsonValue::Array(ib)) => {
3360 ia.extend(ib);
3361 JsonValue::Array(ia)
3362 }
3363 (JsonValue::Array(mut ia), scalar) => {
3364 ia.push(scalar);
3365 JsonValue::Array(ia)
3366 }
3367 (scalar, JsonValue::Array(ib)) => {
3368 let mut out = alloc::vec![scalar];
3369 out.extend(ib);
3370 JsonValue::Array(out)
3371 }
3372 (sa, sb) => JsonValue::Array(alloc::vec![sa, sb]),
3373 };
3374 Ok(Value::json(merged.to_json_text()))
3375}
3376
3377pub fn delete_key(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
3381 delete_key_inner(lhs, rhs).map(canonicalize_value)
3382}
3383
3384fn delete_key_inner(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
3385 let src = match lhs {
3386 Value::Null => return Ok(Value::Null),
3387 Value::Json(s) | Value::Text(s) => s.as_ref(),
3388 _ => {
3389 return Err(EvalError::TypeMismatch {
3390 detail: "jsonb_delete() expects JSON lhs".into(),
3391 });
3392 }
3393 };
3394 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
3395 detail: alloc::format!("invalid JSON for delete: {e}"),
3396 })?;
3397 let out = match (doc, rhs) {
3398 (_, Value::Null) => return Ok(Value::Null),
3399 (JsonValue::Object(entries), Value::Text(key)) => {
3400 let filtered: Vec<(String, JsonValue)> = entries
3401 .into_iter()
3402 .filter(|(k, _)| k != key.as_ref())
3403 .collect();
3404 JsonValue::Object(filtered)
3405 }
3406 (JsonValue::Object(entries), Value::TextArray(keys)) => {
3408 let filtered: Vec<(String, JsonValue)> = entries
3409 .into_iter()
3410 .filter(|(k, _)| !keys.iter().any(|kk| kk.as_deref() == Some(k.as_str())))
3411 .collect();
3412 JsonValue::Object(filtered)
3413 }
3414 (JsonValue::Array(items), Value::Int(idx)) => {
3415 let n = *idx;
3416 let len = items.len() as i64;
3417 let real = if n >= 0 {
3418 i64::from(n)
3419 } else {
3420 len + i64::from(n)
3421 };
3422 let filtered: Vec<JsonValue> = items
3423 .into_iter()
3424 .enumerate()
3425 .filter(|(i, _)| *i as i64 != real)
3426 .map(|(_, v)| v)
3427 .collect();
3428 JsonValue::Array(filtered)
3429 }
3430 (JsonValue::Object(_), Value::Int(_) | Value::SmallInt(_) | Value::BigInt(_)) => {
3436 return Err(EvalError::TypeMismatch {
3437 detail: "cannot delete from object using integer index".into(),
3438 });
3439 }
3440 (other, _) if !matches!(other, JsonValue::Object(_) | JsonValue::Array(_)) => {
3441 return Err(EvalError::TypeMismatch {
3442 detail: "cannot delete from scalar".into(),
3443 });
3444 }
3445 (other, _) => other,
3448 };
3449 Ok(Value::json(out.to_json_text()))
3450}
3451
3452pub fn build_object(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3453 if !args.len().is_multiple_of(2) {
3454 return Err(EvalError::TypeMismatch {
3455 detail: alloc::format!(
3456 "json_build_object() needs an even number of args, got {}",
3457 args.len()
3458 ),
3459 });
3460 }
3461 let mut out = String::from("{");
3462 let mut first = true;
3463 let (pairs, _) = args.as_chunks::<2>();
3464 for pair in pairs {
3465 if !first {
3466 out.push_str(", ");
3470 }
3471 first = false;
3472 let key = match &pair[0] {
3473 Value::Null => {
3474 return Err(EvalError::TypeMismatch {
3475 detail: "json_build_object() key cannot be NULL".into(),
3476 });
3477 }
3478 Value::Text(s) | Value::Json(s) => s.to_string(),
3479 other => format_value_as_text(other),
3480 };
3481 write_json(&JsonValue::String(key), &mut out);
3482 out.push_str(" : ");
3483 encode_value_into(&pair[1], &mut out);
3484 }
3485 out.push('}');
3486 Ok(Value::json(out))
3487}
3488
3489pub fn build_array(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3492 let mut out = String::from("[");
3493 for (i, v) in args.iter().enumerate() {
3494 if i > 0 {
3495 out.push_str(", ");
3499 }
3500 encode_value_into(v, &mut out);
3501 }
3502 out.push(']');
3503 Ok(Value::json(out))
3504}
3505
3506fn format_value_as_text(v: &Value) -> String {
3507 match v {
3508 Value::SmallInt(n) => alloc::format!("{n}"),
3509 Value::Int(n) => alloc::format!("{n}"),
3510 Value::BigInt(n) => alloc::format!("{n}"),
3511 Value::Float(x) => alloc::format!("{x}"),
3512 Value::Bool(b) => alloc::format!("{b}"),
3513 other => alloc::format!("{other:?}"),
3514 }
3515}
3516
3517pub fn set(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3526 if !(3..=4).contains(&args.len()) {
3527 return Err(EvalError::WrongArity {
3528 name: alloc::string::String::from("jsonb_set"),
3529 types: crate::eval::functions::arg_type_list(args),
3530 });
3531 }
3532 if args.iter().take(3).any(|v| matches!(v, Value::Null)) {
3533 return Ok(Value::Null);
3534 }
3535 let create_missing = match args.get(3) {
3536 None | Some(Value::Null) => true,
3537 Some(Value::Bool(b)) => *b,
3538 Some(other) => {
3539 return Err(EvalError::TypeMismatch {
3540 detail: alloc::format!(
3541 "jsonb_set() create_missing must be BOOL, got {}",
3542 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3543 ),
3544 });
3545 }
3546 };
3547 let doc_text = json_text_arg(&args[0], "jsonb_set", "target")?;
3548 let path = path_text_arg(&args[1], "jsonb_set")?;
3549 let new_text = json_text_arg(&args[2], "jsonb_set", "new_value")?;
3550 let mut root = parse(doc_text).map_err(|e| EvalError::TypeMismatch {
3551 detail: alloc::format!("jsonb_set(): invalid JSON target — {e}"),
3552 })?;
3553 let new_val = parse(new_text).map_err(|e| EvalError::TypeMismatch {
3554 detail: alloc::format!("jsonb_set(): invalid JSON new_value — {e}"),
3555 })?;
3556 if path.is_empty() {
3561 return Ok(Value::json(root.to_json_text()));
3562 }
3563 if is_json_scalar(&root) {
3564 return Err(EvalError::TypeMismatch {
3565 detail: "cannot set path in scalar".into(),
3566 });
3567 }
3568 set_at_path(&mut root, &path, new_val, create_missing);
3569 Ok(Value::json(root.to_json_text()))
3570}
3571
3572pub fn delete_path(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3576 delete_path_inner(args).map(canonicalize_value)
3577}
3578
3579fn delete_path_inner(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3580 if args.len() != 2 {
3581 return Err(EvalError::WrongArity {
3582 name: alloc::string::String::from("jsonb_delete_path"),
3583 types: crate::eval::functions::arg_type_list(args),
3584 });
3585 }
3586 if args.iter().any(|v| matches!(v, Value::Null)) {
3587 return Ok(Value::Null);
3588 }
3589 let doc_text = json_text_arg(&args[0], "jsonb_delete_path", "target")?;
3590 let path = path_text_arg(&args[1], "jsonb_delete_path")?;
3591 let mut root = parse(doc_text).map_err(|e| EvalError::TypeMismatch {
3592 detail: alloc::format!("jsonb_delete_path(): invalid JSON target — {e}"),
3593 })?;
3594 if is_json_scalar(&root) && !path.is_empty() {
3597 return Err(EvalError::TypeMismatch {
3598 detail: "cannot delete path in scalar".into(),
3599 });
3600 }
3601 delete_at_path(&mut root, &path);
3602 Ok(Value::json(root.to_json_text()))
3603}
3604
3605fn delete_at_path(node: &mut JsonValue, path: &[String]) {
3606 if path.is_empty() {
3607 return;
3608 }
3609 let step = &path[0];
3610 if path.len() == 1 {
3611 match node {
3613 JsonValue::Object(entries) => {
3614 entries.retain(|(k, _)| k != step);
3615 }
3616 JsonValue::Array(items) => {
3617 if let Ok(idx) = step.parse::<i64>() {
3618 let len = items.len() as i64;
3619 let real = if idx >= 0 { idx } else { len + idx };
3620 if real >= 0 && real < len {
3621 items.remove(real as usize);
3622 }
3623 }
3624 }
3625 _ => {}
3626 }
3627 return;
3628 }
3629 match node {
3631 JsonValue::Object(entries) => {
3632 if let Some((_, child)) = entries.iter_mut().find(|(k, _)| k == step) {
3633 delete_at_path(child, &path[1..]);
3634 }
3635 }
3636 JsonValue::Array(items) => {
3637 if let Ok(idx) = step.parse::<i64>() {
3638 let len = items.len() as i64;
3639 let real = if idx >= 0 { idx } else { len + idx };
3640 if real >= 0 && real < len {
3641 delete_at_path(&mut items[real as usize], &path[1..]);
3642 }
3643 }
3644 }
3645 _ => {}
3646 }
3647}
3648
3649fn set_at_path(node: &mut JsonValue, path: &[String], new_val: JsonValue, create_missing: bool) {
3650 if path.is_empty() {
3651 *node = new_val;
3652 return;
3653 }
3654 let step = &path[0];
3655 let rest = &path[1..];
3656 match node {
3657 JsonValue::Object(entries) => {
3658 if let Some(pos) = entries.iter().position(|(k, _)| k == step) {
3659 if rest.is_empty() {
3660 entries[pos].1 = new_val;
3661 } else {
3662 set_at_path(&mut entries[pos].1, rest, new_val, create_missing);
3663 }
3664 } else if create_missing && rest.is_empty() {
3665 entries.push((step.clone(), new_val));
3666 }
3667 }
3670 JsonValue::Array(items) => {
3671 let Some(idx) = resolve_array_index(step, items.len()) else {
3672 if create_missing && rest.is_empty() {
3673 if let Ok(n) = step.parse::<i64>() {
3675 if n < 0 {
3676 items.insert(0, new_val);
3677 } else {
3678 items.push(new_val);
3679 }
3680 }
3681 }
3682 return;
3683 };
3684 if rest.is_empty() {
3685 items[idx] = new_val;
3686 } else {
3687 set_at_path(&mut items[idx], rest, new_val, create_missing);
3688 }
3689 }
3690 _ => {
3691 }
3693 }
3694}
3695
3696fn resolve_array_index(step: &str, len: usize) -> Option<usize> {
3697 let n = step.parse::<i64>().ok()?;
3698 if n >= 0 {
3699 let i = n as usize;
3700 if i < len { Some(i) } else { None }
3701 } else {
3702 let from_end = len as i64 + n;
3703 if from_end >= 0 {
3704 Some(from_end as usize)
3705 } else {
3706 None
3707 }
3708 }
3709}
3710
3711pub fn insert(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3719 if !(3..=4).contains(&args.len()) {
3720 return Err(EvalError::WrongArity {
3721 name: alloc::string::String::from("jsonb_insert"),
3722 types: crate::eval::functions::arg_type_list(args),
3723 });
3724 }
3725 if args.iter().take(3).any(|v| matches!(v, Value::Null)) {
3726 return Ok(Value::Null);
3727 }
3728 let insert_after = match args.get(3) {
3729 None | Some(Value::Null) => false,
3730 Some(Value::Bool(b)) => *b,
3731 Some(other) => {
3732 return Err(EvalError::TypeMismatch {
3733 detail: alloc::format!(
3734 "jsonb_insert() insert_after must be BOOL, got {}",
3735 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3736 ),
3737 });
3738 }
3739 };
3740 let doc_text = json_text_arg(&args[0], "jsonb_insert", "target")?;
3741 let path = path_text_arg(&args[1], "jsonb_insert")?;
3742 let new_text = json_text_arg(&args[2], "jsonb_insert", "new_value")?;
3743 let mut root = parse(doc_text).map_err(|e| EvalError::TypeMismatch {
3744 detail: alloc::format!("jsonb_insert(): invalid JSON target — {e}"),
3745 })?;
3746 if path.is_empty() {
3750 return Ok(Value::json(root.to_json_text()));
3751 }
3752 if is_json_scalar(&root) {
3753 return Err(EvalError::TypeMismatch {
3754 detail: "cannot set path in scalar".into(),
3755 });
3756 }
3757 let new_val = parse(new_text).map_err(|e| EvalError::TypeMismatch {
3758 detail: alloc::format!("jsonb_insert(): invalid JSON new_value — {e}"),
3759 })?;
3760 insert_at_path(&mut root, &path, new_val, insert_after)?;
3761 Ok(Value::json(root.to_json_text()))
3762}
3763
3764fn insert_at_path(
3765 node: &mut JsonValue,
3766 path: &[String],
3767 new_val: JsonValue,
3768 insert_after: bool,
3769) -> Result<(), EvalError> {
3770 debug_assert!(!path.is_empty());
3771 if path.len() == 1 {
3772 let step = &path[0];
3773 match node {
3774 JsonValue::Object(entries) => {
3775 if entries.iter().any(|(k, _)| k == step) {
3776 return Err(EvalError::TypeMismatch {
3777 detail: alloc::format!(
3778 "jsonb_insert(): cannot replace existing key {step:?}"
3779 ),
3780 });
3781 }
3782 entries.push((step.clone(), new_val));
3783 Ok(())
3784 }
3785 JsonValue::Array(items) => {
3786 let Ok(n) = step.parse::<i64>() else {
3787 return Err(EvalError::TypeMismatch {
3788 detail: alloc::format!(
3789 "jsonb_insert(): array step must be integer, got {step:?}"
3790 ),
3791 });
3792 };
3793 let mut idx = if n >= 0 {
3794 let i = n as usize;
3795 if i > items.len() { items.len() } else { i }
3796 } else {
3797 let from_end = items.len() as i64 + n;
3798 if from_end < 0 { 0 } else { from_end as usize }
3799 };
3800 if insert_after && idx < items.len() {
3801 idx += 1;
3802 }
3803 items.insert(idx, new_val);
3804 Ok(())
3805 }
3806 _ => Err(EvalError::TypeMismatch {
3807 detail: "jsonb_insert(): parent at path is a scalar".into(),
3808 }),
3809 }
3810 } else {
3811 let step = &path[0];
3812 let rest = &path[1..];
3813 match node {
3814 JsonValue::Object(entries) => {
3815 if let Some(pos) = entries.iter().position(|(k, _)| k == step) {
3816 insert_at_path(&mut entries[pos].1, rest, new_val, insert_after)
3817 } else {
3818 Err(EvalError::TypeMismatch {
3819 detail: alloc::format!("jsonb_insert(): path {step:?} does not exist"),
3820 })
3821 }
3822 }
3823 JsonValue::Array(items) => {
3824 let Some(idx) = resolve_array_index(step, items.len()) else {
3825 return Err(EvalError::TypeMismatch {
3826 detail: alloc::format!("jsonb_insert(): array index {step:?} out of range"),
3827 });
3828 };
3829 insert_at_path(&mut items[idx], rest, new_val, insert_after)
3830 }
3831 _ => Err(EvalError::TypeMismatch {
3832 detail: "jsonb_insert(): parent at path is a scalar".into(),
3833 }),
3834 }
3835 }
3836}
3837
3838fn json_text_arg<'a>(v: &'a Value, fname: &str, role: &str) -> Result<&'a str, EvalError> {
3839 match v {
3840 Value::Json(s) | Value::Text(s) => Ok(s.as_ref()),
3841 other => Err(EvalError::TypeMismatch {
3842 detail: alloc::format!(
3843 "{fname}() {role} must be JSON or TEXT, got {}",
3844 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3845 ),
3846 }),
3847 }
3848}
3849
3850fn path_text_arg(v: &Value, fname: &str) -> Result<Vec<String>, EvalError> {
3851 match v {
3852 Value::Text(s) | Value::Json(s) => parse_text_array(s.as_ref()),
3853 Value::TextArray(items) => Ok(items
3854 .iter()
3855 .map(|o| o.clone().unwrap_or_default())
3856 .collect()),
3857 other => Err(EvalError::TypeMismatch {
3858 detail: alloc::format!(
3859 "{fname}() path must be TEXT[] or TEXT, got {}",
3860 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3861 ),
3862 }),
3863 }
3864}
3865
3866#[cfg(test)]
3867mod tests {
3868 use super::*;
3869
3870 fn canon(s: &str) -> String {
3871 canonicalize_jsonb(s).unwrap()
3872 }
3873
3874 #[test]
3875 fn canon_number_rules() {
3876 assert_eq!(canon_json_number("1.0"), "1.0");
3878 assert_eq!(canon_json_number("1e2"), "100");
3879 assert_eq!(canon_json_number("1.10"), "1.10");
3880 assert_eq!(canon_json_number("100.00"), "100.00");
3881 assert_eq!(canon_json_number("0.5"), "0.5");
3882 assert_eq!(canon_json_number("-0"), "0");
3883 assert_eq!(canon_json_number("1E-3"), "0.001");
3884 assert_eq!(canon_json_number("42"), "42");
3885 assert_eq!(canon_json_number("-2.5"), "-2.5");
3886 assert_eq!(canon_json_number("2.5e3"), "2500");
3887 }
3888
3889 #[test]
3890 fn canon_key_order_dedup_and_whitespace() {
3891 assert_eq!(
3893 canon(r#"{"b":1,"a":2,"aa":3,"":9,"z":4}"#),
3894 r#"{"": 9, "a": 2, "b": 1, "z": 4, "aa": 3}"#
3895 );
3896 assert_eq!(canon(r#"{"a":1,"a":2,"a":3}"#), r#"{"a": 3}"#);
3898 assert_eq!(canon("[3,2,1]"), "[3, 2, 1]");
3900 }
3901
3902 #[test]
3903 fn json_number_equality_by_value() {
3904 let eq = |a: &str, b: &str| json_eq(&parse(a).unwrap(), &parse(b).unwrap());
3905 assert!(eq("1", "1.0"));
3906 assert!(eq("1.50", "1.5"));
3907 assert!(eq("1e3", "1000.00"));
3908 assert!(eq("0", "-0"));
3909 assert!(eq("2.5e3", "2500"));
3910 assert!(!eq("1.5", "1.6"));
3911 assert!(eq("[1, 2.0]", "[1.0, 2]"));
3913 assert!(eq(r#"{"a":1}"#, r#"{"a":1.0}"#));
3914 }
3915
3916 #[test]
3917 fn canon_nested_and_scalars() {
3918 assert_eq!(
3919 canon(r#"{"x":{"b":1,"a":2},"y":[3,{"d":1,"c":2}]}"#),
3920 r#"{"x": {"a": 2, "b": 1}, "y": [3, {"c": 2, "d": 1}]}"#
3921 );
3922 assert_eq!(canon(" true "), "true");
3923 assert_eq!(canon(" 42 "), "42");
3924 assert_eq!(canon("{}"), "{}");
3925 assert_eq!(canon("[]"), "[]");
3926 assert_eq!(
3928 canon(r#"{"e":"café","t":"a\nb"}"#),
3929 r#"{"e": "café", "t": "a\nb"}"#
3930 );
3931 }
3932
3933 #[test]
3934 fn parse_atoms() {
3935 assert_eq!(parse("null").unwrap(), JsonValue::Null);
3936 assert_eq!(parse("true").unwrap(), JsonValue::Bool(true));
3937 assert_eq!(parse("false").unwrap(), JsonValue::Bool(false));
3938 assert_eq!(
3939 parse("\"hello\"").unwrap(),
3940 JsonValue::String("hello".into())
3941 );
3942 assert!(matches!(
3943 parse("42").unwrap(),
3944 JsonValue::NumberText(ref s) if s == "42"
3945 ));
3946 }
3947
3948 #[test]
3949 fn parse_nested() {
3950 let doc = parse(r#"{"a":1,"b":[true,null,"x"]}"#).unwrap();
3951 let JsonValue::Object(entries) = doc else {
3952 panic!("expected object");
3953 };
3954 assert_eq!(entries.len(), 2);
3955 assert_eq!(entries[0].0, "a");
3956 assert_eq!(entries[1].0, "b");
3957 }
3958
3959 #[test]
3960 fn parse_string_escapes() {
3961 let s = parse(r#""he said \"hi\" and\\then\n""#).unwrap();
3962 assert_eq!(s, JsonValue::String("he said \"hi\" and\\then\n".into()));
3963 }
3964
3965 #[test]
3966 fn parse_unicode_escape() {
3967 assert_eq!(parse(r#""é""#).unwrap(), JsonValue::String("é".into()));
3968 }
3969
3970 #[test]
3971 fn path_object_key_returns_value() {
3972 let doc = Value::json::<String>(r#"{"name":"alice","age":30}"#.into());
3973 let key = Value::text("name");
3974 let v = path_get(&doc, &key, true).unwrap();
3975 assert_eq!(v, Value::text("alice"));
3976 let v = path_get(&doc, &key, false).unwrap();
3977 assert_eq!(v, Value::json("\"alice\""));
3978 }
3979
3980 #[test]
3981 fn path_array_index_supports_negative() {
3982 let doc = Value::json("[10,20,30]");
3983 let v = path_get(&doc, &Value::Int(1), true).unwrap();
3984 assert_eq!(v, Value::text("20"));
3985 let v = path_get(&doc, &Value::Int(-1), true).unwrap();
3986 assert_eq!(v, Value::text("30"));
3987 }
3988
3989 #[test]
3990 fn path_missing_key_returns_null() {
3991 let doc = Value::json::<String>(r#"{"a":1}"#.into());
3992 let v = path_get(&doc, &Value::text("missing"), true).unwrap();
3993 assert_eq!(v, Value::Null);
3994 }
3995
3996 #[test]
3997 fn path_get_nested_subtree_is_verbatim() {
3998 let doc = Value::json::<String>(r#"{"k":{"x":[1,2]}}"#.into());
4004 let v = path_get(&doc, &Value::text("k"), false).unwrap();
4005 assert_eq!(v, Value::json::<String>(r#"{"x":[1,2]}"#.into()));
4006
4007 let canon = Value::json::<String>(r#"{"k": {"x": [1, 2]}}"#.into());
4009 let v = path_get(&canon, &Value::text("k"), false).unwrap();
4010 assert_eq!(v, Value::json::<String>(r#"{"x": [1, 2]}"#.into()));
4011
4012 let raw = Value::json::<String>(r#"{"a":{ "y" : 2e2 },"k":1,"k":2}"#.into());
4015 assert_eq!(
4016 path_get(&raw, &Value::text("a"), false).unwrap(),
4017 Value::json::<String>(r#"{ "y" : 2e2 }"#.into())
4018 );
4019 assert_eq!(
4020 path_get(&raw, &Value::text("k"), false).unwrap(),
4021 Value::json::<String>("2".into())
4022 );
4023
4024 let n = Value::json::<String>(r#"{"a":null}"#.into());
4026 assert_eq!(
4027 path_get(&n, &Value::text("a"), false).unwrap(),
4028 Value::json::<String>("null".into())
4029 );
4030 assert_eq!(path_get(&n, &Value::text("a"), true).unwrap(), Value::Null);
4031 }
4032}
4033
4034#[derive(Debug)]
4037pub enum MysqlPathStep {
4038 Key(String),
4039 Index(usize),
4040}
4041
4042pub fn mysql_path_steps(path: &str) -> Result<Vec<MysqlPathStep>, EvalError> {
4047 let chars: Vec<char> = path.trim().chars().collect();
4048 if chars.first() != Some(&'$') {
4049 return Err(EvalError::TypeMismatch {
4050 detail: alloc::format!("invalid JSON path expression (must start with $): {path:?}"),
4051 });
4052 }
4053 let mut steps = Vec::new();
4054 let mut i = 1;
4055 while i < chars.len() {
4056 match chars[i] {
4057 '.' => {
4058 i += 1;
4059 if i < chars.len() && chars[i] == '"' {
4060 i += 1;
4061 let mut key = String::new();
4062 while i < chars.len() && chars[i] != '"' {
4063 if chars[i] == '\\' && i + 1 < chars.len() {
4064 i += 1;
4065 }
4066 key.push(chars[i]);
4067 i += 1;
4068 }
4069 if i >= chars.len() {
4070 return Err(EvalError::TypeMismatch {
4071 detail: alloc::format!(
4072 "invalid JSON path expression (unterminated quote): {path:?}"
4073 ),
4074 });
4075 }
4076 i += 1; steps.push(MysqlPathStep::Key(key));
4078 } else {
4079 let mut key = String::new();
4080 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
4081 key.push(chars[i]);
4082 i += 1;
4083 }
4084 if key.is_empty() {
4085 return Err(EvalError::TypeMismatch {
4086 detail: alloc::format!(
4087 "unsupported JSON path step at position {i} in {path:?} \
4088 (wildcards are not supported)"
4089 ),
4090 });
4091 }
4092 steps.push(MysqlPathStep::Key(key));
4093 }
4094 }
4095 '[' => {
4096 i += 1;
4097 let mut num = String::new();
4098 while i < chars.len() && chars[i] != ']' {
4099 num.push(chars[i]);
4100 i += 1;
4101 }
4102 if i >= chars.len() {
4103 return Err(EvalError::TypeMismatch {
4104 detail: alloc::format!(
4105 "invalid JSON path expression (unterminated bracket): {path:?}"
4106 ),
4107 });
4108 }
4109 i += 1; let idx: usize = num.trim().parse().map_err(|_| EvalError::TypeMismatch {
4111 detail: alloc::format!(
4112 "unsupported JSON path index {num:?} in {path:?} \
4113 (wildcards are not supported)"
4114 ),
4115 })?;
4116 steps.push(MysqlPathStep::Index(idx));
4117 }
4118 other => {
4119 return Err(EvalError::TypeMismatch {
4120 detail: alloc::format!(
4121 "invalid JSON path expression (unexpected {other:?}): {path:?}"
4122 ),
4123 });
4124 }
4125 }
4126 }
4127 Ok(steps)
4128}
4129
4130pub fn mysql_path_get<'a>(doc: &'a JsonValue, steps: &[MysqlPathStep]) -> Option<&'a JsonValue> {
4133 let mut cur = doc;
4134 for step in steps {
4135 match (step, cur) {
4136 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4137 cur = members.iter().find(|(mk, _)| mk == k).map(|(_, v)| v)?;
4138 }
4139 (MysqlPathStep::Index(idx), JsonValue::Array(items)) => {
4140 cur = items.get(*idx)?;
4141 }
4142 (MysqlPathStep::Index(0), scalar) => {
4145 cur = scalar;
4146 }
4147 _ => return None,
4148 }
4149 }
4150 Some(cur)
4151}
4152
4153pub fn mysql_json_extract(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4158 if args.len() < 2 {
4159 return Err(EvalError::WrongArity {
4160 name: alloc::string::String::from("json_extract"),
4161 types: crate::eval::functions::arg_type_list(args),
4162 });
4163 }
4164 if args.iter().any(|a| matches!(a, Value::Null)) {
4165 return Ok(Value::Null);
4166 }
4167 let src = match &args[0] {
4168 Value::Json(s) | Value::Text(s) => s.as_ref(),
4169 other => {
4170 return Err(EvalError::TypeMismatch {
4171 detail: alloc::format!(
4172 "json_extract() document must be json, got {}",
4173 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4174 ),
4175 });
4176 }
4177 };
4178 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4179 detail: alloc::format!("json_extract(): invalid JSON: {e}"),
4180 })?;
4181 let mut hits: Vec<String> = Vec::new();
4182 for path_v in &args[1..] {
4183 let Value::Text(p) = path_v else {
4184 return Err(EvalError::TypeMismatch {
4185 detail: alloc::format!(
4186 "json_extract() paths must be text, got {}",
4187 crate::conversions::pg_type_name_for_error_opt(path_v.data_type())
4188 ),
4189 });
4190 };
4191 let steps = mysql_path_steps(p)?;
4192 if let Some(v) = mysql_path_get(&doc, &steps) {
4193 hits.push(v.to_json_text());
4194 }
4195 }
4196 match (args.len() - 1, hits.len()) {
4197 (_, 0) => Ok(Value::Null),
4198 (1, _) => Ok(Value::Json(alloc::borrow::Cow::Owned(
4199 hits.into_iter().next().unwrap(),
4200 ))),
4201 _ => {
4202 let mut out = String::from("[");
4203 for (i, h) in hits.iter().enumerate() {
4204 if i > 0 {
4205 out.push_str(", ");
4206 }
4207 out.push_str(h);
4208 }
4209 out.push(']');
4210 Ok(Value::Json(alloc::borrow::Cow::Owned(out)))
4211 }
4212 }
4213}
4214
4215pub fn mysql_json_contains_path(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4218 if args.len() < 3 {
4219 return Err(EvalError::WrongArity {
4220 name: alloc::string::String::from("json_contains_path"),
4221 types: crate::eval::functions::arg_type_list(args),
4222 });
4223 }
4224 if args.iter().any(|a| matches!(a, Value::Null)) {
4225 return Ok(Value::Null);
4226 }
4227 let src = match &args[0] {
4228 Value::Json(s) | Value::Text(s) => s.as_ref(),
4229 other => {
4230 return Err(EvalError::TypeMismatch {
4231 detail: alloc::format!(
4232 "json_contains_path() document must be json, got {}",
4233 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4234 ),
4235 });
4236 }
4237 };
4238 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4239 detail: alloc::format!("json_contains_path(): invalid JSON: {e}"),
4240 })?;
4241 let mode = match &args[1] {
4242 Value::Text(m) if m.eq_ignore_ascii_case("one") => false,
4243 Value::Text(m) if m.eq_ignore_ascii_case("all") => true,
4244 other => {
4245 return Err(EvalError::TypeMismatch {
4246 detail: alloc::format!(
4247 "json_contains_path() second arg must be 'one' or 'all', got {other:?}"
4248 ),
4249 });
4250 }
4251 };
4252 let mut found_any = false;
4253 let mut found_all = true;
4254 for path_v in &args[2..] {
4255 let Value::Text(p) = path_v else {
4256 return Err(EvalError::TypeMismatch {
4257 detail: alloc::format!(
4258 "json_contains_path() paths must be text, got {}",
4259 crate::conversions::pg_type_name_for_error_opt(path_v.data_type())
4260 ),
4261 });
4262 };
4263 let steps = mysql_path_steps(p)?;
4264 if mysql_path_get(&doc, &steps).is_some() {
4265 found_any = true;
4266 } else {
4267 found_all = false;
4268 }
4269 }
4270 Ok(Value::Bool(if mode { found_all } else { found_any }))
4271}
4272
4273fn value_to_jsonvalue(v: &Value) -> Result<JsonValue, EvalError> {
4277 Ok(match v {
4278 Value::Null => JsonValue::Null,
4279 Value::Bool(b) => JsonValue::Bool(*b),
4280 Value::Json(s) => parse(s).map_err(|e| EvalError::TypeMismatch {
4281 detail: alloc::format!("invalid JSON value: {e}"),
4282 })?,
4283 Value::Text(s) => JsonValue::String(s.to_string()),
4284 Value::Composite(fields) => {
4287 let mut entries = alloc::vec::Vec::with_capacity(fields.len());
4288 for (name, fv) in fields.iter() {
4289 entries.push((name.clone(), value_to_jsonvalue(fv)?));
4290 }
4291 JsonValue::Object(entries)
4292 }
4293 other => {
4294 let text = value_to_json_text(other);
4297 parse(&text).map_err(|e| EvalError::TypeMismatch {
4298 detail: alloc::format!("invalid JSON value: {e}"),
4299 })?
4300 }
4301 })
4302}
4303
4304#[derive(Clone, Copy, PartialEq, Debug)]
4305enum MutateMode {
4306 Set,
4308 Insert,
4310 Replace,
4312}
4313
4314fn mutate_at(cur: &mut JsonValue, steps: &[MysqlPathStep], mode: MutateMode, newval: &JsonValue) {
4317 match steps {
4318 [] => {
4319 if matches!(mode, MutateMode::Set | MutateMode::Replace) {
4320 *cur = newval.clone();
4321 }
4322 }
4323 [last] => match (last, &mut *cur) {
4324 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4325 if let Some(slot) = members.iter_mut().find(|(mk, _)| mk == k) {
4326 if matches!(mode, MutateMode::Set | MutateMode::Replace) {
4327 slot.1 = newval.clone();
4328 }
4329 } else if matches!(mode, MutateMode::Set | MutateMode::Insert) {
4330 members.push((k.clone(), newval.clone()));
4331 }
4332 }
4333 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
4334 if *i < items.len() {
4335 if matches!(mode, MutateMode::Set | MutateMode::Replace) {
4336 items[*i] = newval.clone();
4337 }
4338 } else if matches!(mode, MutateMode::Set | MutateMode::Insert) {
4339 items.push(newval.clone());
4341 }
4342 }
4343 (MysqlPathStep::Index(0), scalar) => {
4345 if matches!(mode, MutateMode::Set | MutateMode::Replace) {
4346 *scalar = newval.clone();
4347 }
4348 }
4349 _ => {}
4350 },
4351 [head, rest @ ..] => match (head, cur) {
4352 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4353 if let Some(slot) = members.iter_mut().find(|(mk, _)| mk == k) {
4354 mutate_at(&mut slot.1, rest, mode, newval);
4355 }
4356 }
4357 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
4358 if let Some(slot) = items.get_mut(*i) {
4359 mutate_at(slot, rest, mode, newval);
4360 }
4361 }
4362 _ => {}
4363 },
4364 }
4365}
4366
4367fn mysql_json_mutate(
4368 args: &[Value<'_>],
4369 mode: MutateMode,
4370 fn_name: &str,
4371) -> Result<Value<'static>, EvalError> {
4372 if args.len() < 3 || args.len() % 2 == 0 {
4373 return Err(EvalError::WrongArity {
4374 name: alloc::string::String::from(fn_name),
4375 types: crate::eval::functions::arg_type_list(args),
4376 });
4377 }
4378 if matches!(args[0], Value::Null) {
4379 return Ok(Value::Null);
4380 }
4381 let src = match &args[0] {
4382 Value::Json(s) | Value::Text(s) => s.as_ref(),
4383 other => {
4384 return Err(EvalError::TypeMismatch {
4385 detail: alloc::format!(
4386 "{fn_name}() document must be json, got {}",
4387 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4388 ),
4389 });
4390 }
4391 };
4392 let mut doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4393 detail: alloc::format!("{fn_name}(): invalid JSON: {e}"),
4394 })?;
4395 for pair in args[1..].chunks(2) {
4396 let Value::Text(p) = &pair[0] else {
4397 if matches!(pair[0], Value::Null) {
4398 return Ok(Value::Null);
4399 }
4400 return Err(EvalError::TypeMismatch {
4401 detail: alloc::format!(
4402 "{fn_name}() paths must be text, got {}",
4403 crate::conversions::pg_type_name_for_error_opt(pair[0].data_type())
4404 ),
4405 });
4406 };
4407 let steps = mysql_path_steps(p)?;
4408 let newval = value_to_jsonvalue(&pair[1])?;
4409 mutate_at(&mut doc, &steps, mode, &newval);
4410 }
4411 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4415 doc.to_json_text(),
4416 ))))
4417}
4418
4419pub fn mysql_json_set(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4423 mysql_json_mutate(args, MutateMode::Set, "json_set")
4424}
4425
4426pub fn mysql_json_insert(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4427 mysql_json_mutate(args, MutateMode::Insert, "json_insert")
4428}
4429
4430pub fn mysql_json_replace(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4431 mysql_json_mutate(args, MutateMode::Replace, "json_replace")
4432}
4433
4434pub fn mysql_json_remove(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4437 if args.len() < 2 {
4438 return Err(EvalError::WrongArity {
4439 name: alloc::string::String::from("json_remove"),
4440 types: crate::eval::functions::arg_type_list(args),
4441 });
4442 }
4443 if args.iter().any(|a| matches!(a, Value::Null)) {
4444 return Ok(Value::Null);
4445 }
4446 let src = match &args[0] {
4447 Value::Json(s) | Value::Text(s) => s.as_ref(),
4448 other => {
4449 return Err(EvalError::TypeMismatch {
4450 detail: alloc::format!(
4451 "json_remove() document must be json, got {}",
4452 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4453 ),
4454 });
4455 }
4456 };
4457 let mut doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4458 detail: alloc::format!("json_remove(): invalid JSON: {e}"),
4459 })?;
4460 fn remove_at(cur: &mut JsonValue, steps: &[MysqlPathStep]) {
4461 match steps {
4462 [] => {}
4463 [last] => match (last, cur) {
4464 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4465 members.retain(|(mk, _)| mk != k);
4466 }
4467 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
4468 if *i < items.len() {
4469 items.remove(*i);
4470 }
4471 }
4472 _ => {}
4473 },
4474 [head, rest @ ..] => match (head, cur) {
4475 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4476 if let Some(slot) = members.iter_mut().find(|(mk, _)| mk == k) {
4477 remove_at(&mut slot.1, rest);
4478 }
4479 }
4480 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
4481 if let Some(slot) = items.get_mut(*i) {
4482 remove_at(slot, rest);
4483 }
4484 }
4485 _ => {}
4486 },
4487 }
4488 }
4489 for path_v in &args[1..] {
4490 let Value::Text(p) = path_v else {
4491 return Err(EvalError::TypeMismatch {
4492 detail: alloc::format!(
4493 "json_remove() paths must be text, got {}",
4494 crate::conversions::pg_type_name_for_error_opt(path_v.data_type())
4495 ),
4496 });
4497 };
4498 let steps = mysql_path_steps(p)?;
4499 if steps.is_empty() {
4500 return Err(EvalError::TypeMismatch {
4501 detail: "The path expression '$' is not allowed in this context".into(),
4502 });
4503 }
4504 remove_at(&mut doc, &steps);
4505 }
4506 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4508 doc.to_json_text(),
4509 ))))
4510}
4511
4512fn modify_at(cur: &mut JsonValue, steps: &[MysqlPathStep], f: &mut dyn FnMut(&mut JsonValue)) {
4515 match steps {
4516 [] => f(cur),
4517 [head, rest @ ..] => match (head, cur) {
4518 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4519 if let Some(slot) = members.iter_mut().find(|(mk, _)| mk == k) {
4520 modify_at(&mut slot.1, rest, f);
4521 }
4522 }
4523 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
4524 if let Some(slot) = items.get_mut(*i) {
4525 modify_at(slot, rest, f);
4526 }
4527 }
4528 _ => {}
4529 },
4530 }
4531}
4532
4533fn mysql_doc_and_pairs<'a>(
4535 args: &'a [Value<'_>],
4536 fn_name: &str,
4537) -> Result<Option<(JsonValue, &'a [Value<'a>])>, EvalError> {
4538 if args.len() < 3 || args.len() % 2 == 0 {
4539 return Err(EvalError::WrongArity {
4540 name: alloc::string::String::from(fn_name),
4541 types: crate::eval::functions::arg_type_list(args),
4542 });
4543 }
4544 if args.iter().any(|a| matches!(a, Value::Null)) {
4545 return Ok(None);
4546 }
4547 let src = match &args[0] {
4548 Value::Json(s) | Value::Text(s) => s.as_ref(),
4549 other => {
4550 return Err(EvalError::TypeMismatch {
4551 detail: alloc::format!(
4552 "{fn_name}() document must be json, got {}",
4553 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4554 ),
4555 });
4556 }
4557 };
4558 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4559 detail: alloc::format!("{fn_name}(): invalid JSON: {e}"),
4560 })?;
4561 Ok(Some((doc, &args[1..])))
4562}
4563
4564pub fn mysql_json_array_append(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4568 let Some((mut doc, pairs)) = mysql_doc_and_pairs(args, "json_array_append")? else {
4569 return Ok(Value::Null);
4570 };
4571 for pair in pairs.chunks(2) {
4572 let Value::Text(p) = &pair[0] else {
4573 return Err(EvalError::TypeMismatch {
4574 detail: alloc::format!(
4575 "json_array_append() paths must be text, got {}",
4576 crate::conversions::pg_type_name_for_error_opt(pair[0].data_type())
4577 ),
4578 });
4579 };
4580 let steps = mysql_path_steps(p)?;
4581 let newval = value_to_jsonvalue(&pair[1])?;
4582 modify_at(&mut doc, &steps, &mut |v| match v {
4583 JsonValue::Array(items) => items.push(newval.clone()),
4584 other => {
4585 let old = core::mem::replace(other, JsonValue::Null);
4586 *other = JsonValue::Array(alloc::vec![old, newval.clone()]);
4587 }
4588 });
4589 }
4590 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4592 doc.to_json_text(),
4593 ))))
4594}
4595
4596pub fn mysql_json_array_insert(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4601 let Some((mut doc, pairs)) = mysql_doc_and_pairs(args, "json_array_insert")? else {
4602 return Ok(Value::Null);
4603 };
4604 for pair in pairs.chunks(2) {
4605 let Value::Text(p) = &pair[0] else {
4606 return Err(EvalError::TypeMismatch {
4607 detail: alloc::format!(
4608 "json_array_insert() paths must be text, got {}",
4609 crate::conversions::pg_type_name_for_error_opt(pair[0].data_type())
4610 ),
4611 });
4612 };
4613 let steps = mysql_path_steps(p)?;
4614 let Some(MysqlPathStep::Index(idx)) = steps.last() else {
4615 return Err(EvalError::TypeMismatch {
4616 detail: alloc::format!(
4617 "json_array_insert() path must end with an array index: {p:?}"
4618 ),
4619 });
4620 };
4621 let idx = *idx;
4622 let newval = value_to_jsonvalue(&pair[1])?;
4623 modify_at(&mut doc, &steps[..steps.len() - 1], &mut |v| {
4624 if let JsonValue::Array(items) = v {
4625 let at = idx.min(items.len());
4626 items.insert(at, newval.clone());
4627 }
4628 });
4629 }
4630 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4632 doc.to_json_text(),
4633 ))))
4634}
4635
4636fn mysql_contains(target: &JsonValue, cand: &JsonValue) -> bool {
4641 match (target, cand) {
4642 (JsonValue::Object(t), JsonValue::Object(c)) => c
4643 .iter()
4644 .all(|(ck, cv)| t.iter().any(|(tk, tv)| tk == ck && mysql_contains(tv, cv))),
4645 (JsonValue::Array(t), JsonValue::Array(c)) => {
4646 c.iter().all(|cv| t.iter().any(|tv| mysql_contains(tv, cv)))
4647 }
4648 (JsonValue::Array(t), scalar) => t.iter().any(|tv| mysql_contains(tv, scalar)),
4649 (JsonValue::Number(a), JsonValue::NumberText(b))
4651 | (JsonValue::NumberText(b), JsonValue::Number(a)) => {
4652 b.parse::<f64>().map(|x| x == *a).unwrap_or(false)
4653 }
4654 (JsonValue::NumberText(a), JsonValue::NumberText(b)) => {
4655 a == b
4656 || (a.parse::<f64>().ok().zip(b.parse::<f64>().ok()))
4657 .map(|(x, y)| x == y)
4658 .unwrap_or(false)
4659 }
4660 (a, b) => a == b,
4661 }
4662}
4663
4664pub fn mysql_json_contains(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4667 if !matches!(args.len(), 2 | 3) {
4668 return Err(EvalError::WrongArity {
4669 name: alloc::string::String::from("json_contains"),
4670 types: crate::eval::functions::arg_type_list(args),
4671 });
4672 }
4673 if args.iter().any(|a| matches!(a, Value::Null)) {
4674 return Ok(Value::Null);
4675 }
4676 let parse_arg = |v: &Value<'_>, which: &str| -> Result<JsonValue, EvalError> {
4677 match v {
4678 Value::Json(s) | Value::Text(s) => parse(s).map_err(|e| EvalError::TypeMismatch {
4679 detail: alloc::format!("json_contains(): invalid {which} JSON: {e}"),
4680 }),
4681 other => Err(EvalError::TypeMismatch {
4682 detail: alloc::format!(
4683 "json_contains() {which} must be json, got {}",
4684 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4685 ),
4686 }),
4687 }
4688 };
4689 let target = parse_arg(&args[0], "target")?;
4690 let cand = parse_arg(&args[1], "candidate")?;
4691 let effective = match args.get(2) {
4692 None => Some(&target),
4693 Some(Value::Text(p)) => {
4694 let steps = mysql_path_steps(p)?;
4695 mysql_path_get(&target, &steps)
4696 }
4697 Some(other) => {
4698 return Err(EvalError::TypeMismatch {
4699 detail: alloc::format!(
4700 "json_contains() path must be text, got {}",
4701 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4702 ),
4703 });
4704 }
4705 };
4706 match effective {
4707 None => Ok(Value::Null),
4708 Some(t) => Ok(Value::Bool(mysql_contains(t, &cand))),
4709 }
4710}
4711
4712fn merge_patch(target: JsonValue, patch: JsonValue) -> JsonValue {
4716 let JsonValue::Object(patch_members) = patch else {
4717 return patch;
4718 };
4719 let mut out = match target {
4720 JsonValue::Object(members) => members,
4721 _ => Vec::new(),
4722 };
4723 for (k, v) in patch_members {
4724 if matches!(v, JsonValue::Null) {
4725 out.retain(|(mk, _)| *mk != k);
4726 } else if let Some(slot) = out.iter_mut().find(|(mk, _)| *mk == k) {
4727 let old = core::mem::replace(&mut slot.1, JsonValue::Null);
4728 slot.1 = merge_patch(old, v);
4729 } else {
4730 out.push((k, merge_patch(JsonValue::Null, v)));
4732 }
4733 }
4734 JsonValue::Object(out)
4735}
4736
4737fn merge_preserve(a: JsonValue, b: JsonValue) -> JsonValue {
4742 match (a, b) {
4743 (JsonValue::Object(mut ma), JsonValue::Object(mb)) => {
4744 for (k, v) in mb {
4745 if let Some(pos) = ma.iter().position(|(mk, _)| *mk == k) {
4746 let (_, old) = ma.remove(pos);
4747 ma.insert(pos, (k, merge_preserve(old, v)));
4748 } else {
4749 ma.push((k, v));
4750 }
4751 }
4752 JsonValue::Object(ma)
4753 }
4754 (JsonValue::Array(mut xs), JsonValue::Array(ys)) => {
4755 xs.extend(ys);
4756 JsonValue::Array(xs)
4757 }
4758 (JsonValue::Array(mut xs), scalar) => {
4759 xs.push(scalar);
4760 JsonValue::Array(xs)
4761 }
4762 (scalar, JsonValue::Array(ys)) => {
4763 let mut xs = alloc::vec![scalar];
4764 xs.extend(ys);
4765 JsonValue::Array(xs)
4766 }
4767 (sa, sb) => JsonValue::Array(alloc::vec![sa, sb]),
4768 }
4769}
4770
4771fn mysql_json_merge(
4772 args: &[Value<'_>],
4773 fn_name: &str,
4774 combine: fn(JsonValue, JsonValue) -> JsonValue,
4775) -> Result<Value<'static>, EvalError> {
4776 if args.len() < 2 {
4777 return Err(EvalError::WrongArity {
4778 name: alloc::string::String::from(fn_name),
4779 types: crate::eval::functions::arg_type_list(args),
4780 });
4781 }
4782 if args.iter().any(|a| matches!(a, Value::Null)) {
4783 return Ok(Value::Null);
4784 }
4785 let mut acc: Option<JsonValue> = None;
4786 for arg in args {
4787 let src = match arg {
4788 Value::Json(s) | Value::Text(s) => s.as_ref(),
4789 other => {
4790 return Err(EvalError::TypeMismatch {
4791 detail: alloc::format!(
4792 "{fn_name}() arguments must be json, got {}",
4793 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4794 ),
4795 });
4796 }
4797 };
4798 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4799 detail: alloc::format!("{fn_name}(): invalid JSON: {e}"),
4800 })?;
4801 acc = Some(match acc {
4802 None => doc,
4803 Some(prev) => combine(prev, doc),
4804 });
4805 }
4806 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4808 acc.unwrap().to_json_text(),
4809 ))))
4810}
4811
4812pub fn mysql_json_merge_patch(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4814 mysql_json_merge(args, "json_merge_patch", merge_patch)
4815}
4816
4817pub fn mysql_json_merge_preserve(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4820 mysql_json_merge(args, "json_merge_preserve", merge_preserve)
4821}
4822
4823pub fn mysql_json_overlaps(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4827 if args.len() != 2 {
4828 return Err(EvalError::WrongArity {
4829 name: alloc::string::String::from("json_overlaps"),
4830 types: crate::eval::functions::arg_type_list(args),
4831 });
4832 }
4833 if args.iter().any(|a| matches!(a, Value::Null)) {
4834 return Ok(Value::Null);
4835 }
4836 let parse_arg = |v: &Value<'_>| -> Result<JsonValue, EvalError> {
4837 match v {
4838 Value::Json(s) | Value::Text(s) => parse(s).map_err(|e| EvalError::TypeMismatch {
4839 detail: alloc::format!("json_overlaps(): invalid JSON: {e}"),
4840 }),
4841 other => Err(EvalError::TypeMismatch {
4842 detail: alloc::format!(
4843 "json_overlaps() arguments must be json, got {}",
4844 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4845 ),
4846 }),
4847 }
4848 };
4849 let a = parse_arg(&args[0])?;
4850 let b = parse_arg(&args[1])?;
4851 let overlaps = match (&a, &b) {
4852 (JsonValue::Array(xs), JsonValue::Array(ys)) => xs.iter().any(|x| {
4853 ys.iter()
4854 .any(|y| mysql_contains(x, y) && mysql_contains(y, x))
4855 }),
4856 (JsonValue::Object(ma), JsonValue::Object(mb)) => ma.iter().any(|(k, v)| {
4857 mb.iter()
4858 .any(|(k2, v2)| k == k2 && mysql_contains(v, v2) && mysql_contains(v2, v))
4859 }),
4860 (JsonValue::Array(xs), scalar) | (scalar, JsonValue::Array(xs)) => xs
4861 .iter()
4862 .any(|x| mysql_contains(x, scalar) && mysql_contains(scalar, x)),
4863 (sa, sb) => mysql_contains(sa, sb) && mysql_contains(sb, sa),
4864 };
4865 Ok(Value::Bool(overlaps))
4866}
4867
4868fn like_match(text: &[char], pat: &[char], escape: char) -> bool {
4871 match pat {
4872 [] => text.is_empty(),
4873 ['%', rest @ ..] => (0..=text.len()).any(|skip| like_match(&text[skip..], rest, escape)),
4874 ['_', rest @ ..] => !text.is_empty() && like_match(&text[1..], rest, escape),
4875 [e, lit, rest @ ..] if *e == escape => {
4876 text.first() == Some(lit) && like_match(&text[1..], rest, escape)
4877 }
4878 [c, rest @ ..] => text.first() == Some(c) && like_match(&text[1..], rest, escape),
4879 }
4880}
4881
4882fn push_path_step(out: &mut String, step_key: Option<&str>, step_idx: Option<usize>) {
4885 if let Some(k) = step_key {
4886 let ident_shaped = !k.is_empty()
4887 && k.chars().all(|c| c.is_alphanumeric() || c == '_')
4888 && !k.chars().next().unwrap().is_numeric();
4889 if ident_shaped {
4890 out.push('.');
4891 out.push_str(k);
4892 } else {
4893 out.push_str(".\"");
4894 for c in k.chars() {
4895 if c == '"' || c == '\\' {
4896 out.push('\\');
4897 }
4898 out.push(c);
4899 }
4900 out.push('"');
4901 }
4902 }
4903 if let Some(i) = step_idx {
4904 out.push('[');
4905 out.push_str(&alloc::format!("{i}"));
4906 out.push(']');
4907 }
4908}
4909
4910pub fn mysql_json_search(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4916 if args.len() < 3 {
4917 return Err(EvalError::WrongArity {
4918 name: alloc::string::String::from("json_search"),
4919 types: crate::eval::functions::arg_type_list(args),
4920 });
4921 }
4922 if args[..3].iter().any(|a| matches!(a, Value::Null)) {
4923 return Ok(Value::Null);
4924 }
4925 let src = match &args[0] {
4926 Value::Json(s) | Value::Text(s) => s.as_ref(),
4927 other => {
4928 return Err(EvalError::TypeMismatch {
4929 detail: alloc::format!(
4930 "json_search() document must be json, got {}",
4931 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4932 ),
4933 });
4934 }
4935 };
4936 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4937 detail: alloc::format!("json_search(): invalid JSON: {e}"),
4938 })?;
4939 let one = match &args[1] {
4940 Value::Text(m) if m.eq_ignore_ascii_case("one") => true,
4941 Value::Text(m) if m.eq_ignore_ascii_case("all") => false,
4942 other => {
4943 return Err(EvalError::TypeMismatch {
4944 detail: alloc::format!(
4945 "json_search() second arg must be 'one' or 'all', got {other:?}"
4946 ),
4947 });
4948 }
4949 };
4950 let Value::Text(pattern) = &args[2] else {
4951 return Err(EvalError::TypeMismatch {
4952 detail: alloc::format!(
4953 "json_search() pattern must be text, got {}",
4954 crate::conversions::pg_type_name_for_error_opt(args[2].data_type())
4955 ),
4956 });
4957 };
4958 let escape = match args.get(3) {
4959 None | Some(Value::Null) => '\\',
4960 Some(Value::Text(e)) if e.chars().count() == 1 => e.chars().next().unwrap(),
4961 Some(other) => {
4962 return Err(EvalError::TypeMismatch {
4963 detail: alloc::format!(
4964 "json_search() escape must be a single character, got {other:?}"
4965 ),
4966 });
4967 }
4968 };
4969 let pat: Vec<char> = pattern.chars().collect();
4970 fn walk(
4971 v: &JsonValue,
4972 path: &str,
4973 pat: &[char],
4974 escape: char,
4975 hits: &mut Vec<String>,
4976 stop_at_one: bool,
4977 ) {
4978 if stop_at_one && !hits.is_empty() {
4979 return;
4980 }
4981 match v {
4982 JsonValue::String(s) => {
4983 let chars: Vec<char> = s.chars().collect();
4984 if like_match(&chars, pat, escape) {
4985 hits.push(path.to_string());
4986 }
4987 }
4988 JsonValue::Object(members) => {
4989 for (k, mv) in members {
4990 let mut p = path.to_string();
4991 push_path_step(&mut p, Some(k), None);
4992 walk(mv, &p, pat, escape, hits, stop_at_one);
4993 }
4994 }
4995 JsonValue::Array(items) => {
4996 for (i, iv) in items.iter().enumerate() {
4997 let mut p = path.to_string();
4998 push_path_step(&mut p, None, Some(i));
4999 walk(iv, &p, pat, escape, hits, stop_at_one);
5000 }
5001 }
5002 _ => {}
5003 }
5004 }
5005 let mut hits: Vec<String> = Vec::new();
5006 let start_paths: Vec<String> = args
5007 .get(4..)
5008 .unwrap_or(&[])
5009 .iter()
5010 .map(|v| match v {
5011 Value::Text(p) => Ok(p.to_string()),
5012 other => Err(EvalError::TypeMismatch {
5013 detail: alloc::format!(
5014 "json_search() paths must be text, got {}",
5015 crate::conversions::pg_type_name_for_error_opt(other.data_type())
5016 ),
5017 }),
5018 })
5019 .collect::<Result<_, _>>()?;
5020 if start_paths.is_empty() {
5021 walk(&doc, "$", &pat, escape, &mut hits, one);
5022 } else {
5023 for p in &start_paths {
5024 let steps = mysql_path_steps(p)?;
5025 if let Some(sub) = mysql_path_get(&doc, &steps) {
5026 walk(sub, p.trim(), &pat, escape, &mut hits, one);
5027 }
5028 }
5029 }
5030 match hits.len() {
5031 0 => Ok(Value::Null),
5032 1 => Ok(Value::Json(alloc::borrow::Cow::Owned(
5033 JsonValue::String(hits.into_iter().next().unwrap()).to_json_text(),
5034 ))),
5035 _ => {
5036 let arr = JsonValue::Array(hits.into_iter().map(JsonValue::String).collect());
5037 Ok(Value::Json(alloc::borrow::Cow::Owned(arr.to_json_text())))
5038 }
5039 }
5040}
5041
5042pub fn mysql_json_value(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
5047 if args.len() != 2 {
5048 return Err(EvalError::WrongArity {
5049 name: alloc::string::String::from("json_value"),
5050 types: crate::eval::functions::arg_type_list(args),
5051 });
5052 }
5053 if args.iter().any(|a| matches!(a, Value::Null)) {
5054 return Ok(Value::Null);
5055 }
5056 let src = match &args[0] {
5057 Value::Json(s) | Value::Text(s) => s.as_ref(),
5058 other => {
5059 return Err(EvalError::TypeMismatch {
5060 detail: alloc::format!(
5061 "json_value() document must be json, got {}",
5062 crate::conversions::pg_type_name_for_error_opt(other.data_type())
5063 ),
5064 });
5065 }
5066 };
5067 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
5068 detail: alloc::format!("json_value(): invalid JSON: {e}"),
5069 })?;
5070 let Value::Text(p) = &args[1] else {
5071 return Err(EvalError::TypeMismatch {
5072 detail: alloc::format!(
5073 "json_value() path must be text, got {}",
5074 crate::conversions::pg_type_name_for_error_opt(args[1].data_type())
5075 ),
5076 });
5077 };
5078 let steps = mysql_path_steps(p)?;
5079 match mysql_path_get(&doc, &steps) {
5080 None => Ok(Value::Null),
5081 Some(JsonValue::Null) => Ok(Value::Null),
5082 Some(v) => Ok(Value::text(v.as_text())),
5083 }
5084}
5085
5086fn is_json_scalar(v: &JsonValue) -> bool {
5090 !matches!(v, JsonValue::Object(_) | JsonValue::Array(_))
5091}
5092
5093#[cfg(test)]
5094mod round619_number_fast_path {
5095 use super::*;
5096
5097 #[test]
5101 fn fast_path_agrees_with_the_full_canonicaliser() {
5102 let mut cases: Vec<String> = Vec::new();
5103 for sign in ["", "-"] {
5104 for body in [
5105 "0",
5106 "1",
5107 "7",
5108 "10",
5109 "123",
5110 "0123",
5111 "00",
5112 "000",
5113 "9223372036854775807",
5114 "170141183460469231731687303715884105727",
5115 "1.0",
5116 "1.5",
5117 "0.5",
5118 ".5",
5119 "1.",
5120 "1e3",
5121 "1E3",
5122 "1e-3",
5123 "1.5e2",
5124 "1.50",
5125 "100",
5126 "0.0",
5127 "0.00",
5128 "10.010",
5129 "1e0",
5130 "1e+3",
5131 "0e0",
5132 "12345678901234567890.12345678901234567890",
5133 ] {
5134 cases.push(alloc::format!("{sign}{body}"));
5135 }
5136 }
5137 for c in &cases {
5138 assert_eq!(
5139 canon_json_number(c).as_ref(),
5140 canon_json_number_slow(c).as_str(),
5141 "lexeme {c:?} canonicalises differently through the shortcut"
5142 );
5143 }
5144 }
5145
5146 #[test]
5148 fn one_entry_object_writes_what_the_general_writer_writes() {
5149 for src in [
5150 r#"{"a":1}"#,
5151 r#"{"":1}"#,
5152 r#"{"a":{"b":2}}"#,
5153 r#"{"a":[1,2,3]}"#,
5154 r#"{"a\"b":"c\\d"}"#,
5155 r#"{"日本":"語"}"#,
5156 r#"{"a":null}"#,
5157 r#"{}"#,
5158 ] {
5159 let JsonValue::Object(entries) = parse(src).expect("valid json") else {
5160 panic!("{src} is not an object");
5161 };
5162 let mut fast = String::new();
5163 write_json_canonical(&JsonValue::Object(entries.clone()), &mut fast);
5164 let mut general = String::new();
5165 write_object_general(&entries, &mut general);
5166 assert_eq!(fast, general, "{src}");
5167 }
5168 }
5169}