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::TypeMismatch {
3528 detail: alloc::format!("jsonb_set() takes 3 or 4 args, got {}", args.len()),
3529 });
3530 }
3531 if args.iter().take(3).any(|v| matches!(v, Value::Null)) {
3532 return Ok(Value::Null);
3533 }
3534 let create_missing = match args.get(3) {
3535 None | Some(Value::Null) => true,
3536 Some(Value::Bool(b)) => *b,
3537 Some(other) => {
3538 return Err(EvalError::TypeMismatch {
3539 detail: alloc::format!(
3540 "jsonb_set() create_missing must be BOOL, got {}",
3541 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3542 ),
3543 });
3544 }
3545 };
3546 let doc_text = json_text_arg(&args[0], "jsonb_set", "target")?;
3547 let path = path_text_arg(&args[1], "jsonb_set")?;
3548 let new_text = json_text_arg(&args[2], "jsonb_set", "new_value")?;
3549 let mut root = parse(doc_text).map_err(|e| EvalError::TypeMismatch {
3550 detail: alloc::format!("jsonb_set(): invalid JSON target — {e}"),
3551 })?;
3552 let new_val = parse(new_text).map_err(|e| EvalError::TypeMismatch {
3553 detail: alloc::format!("jsonb_set(): invalid JSON new_value — {e}"),
3554 })?;
3555 if path.is_empty() {
3560 return Ok(Value::json(root.to_json_text()));
3561 }
3562 if is_json_scalar(&root) {
3563 return Err(EvalError::TypeMismatch {
3564 detail: "cannot set path in scalar".into(),
3565 });
3566 }
3567 set_at_path(&mut root, &path, new_val, create_missing);
3568 Ok(Value::json(root.to_json_text()))
3569}
3570
3571pub fn delete_path(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3575 delete_path_inner(args).map(canonicalize_value)
3576}
3577
3578fn delete_path_inner(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3579 if args.len() != 2 {
3580 return Err(EvalError::TypeMismatch {
3581 detail: alloc::format!("jsonb_delete_path() takes 2 args, got {}", args.len()),
3582 });
3583 }
3584 if args.iter().any(|v| matches!(v, Value::Null)) {
3585 return Ok(Value::Null);
3586 }
3587 let doc_text = json_text_arg(&args[0], "jsonb_delete_path", "target")?;
3588 let path = path_text_arg(&args[1], "jsonb_delete_path")?;
3589 let mut root = parse(doc_text).map_err(|e| EvalError::TypeMismatch {
3590 detail: alloc::format!("jsonb_delete_path(): invalid JSON target — {e}"),
3591 })?;
3592 if is_json_scalar(&root) && !path.is_empty() {
3595 return Err(EvalError::TypeMismatch {
3596 detail: "cannot delete path in scalar".into(),
3597 });
3598 }
3599 delete_at_path(&mut root, &path);
3600 Ok(Value::json(root.to_json_text()))
3601}
3602
3603fn delete_at_path(node: &mut JsonValue, path: &[String]) {
3604 if path.is_empty() {
3605 return;
3606 }
3607 let step = &path[0];
3608 if path.len() == 1 {
3609 match node {
3611 JsonValue::Object(entries) => {
3612 entries.retain(|(k, _)| k != step);
3613 }
3614 JsonValue::Array(items) => {
3615 if let Ok(idx) = step.parse::<i64>() {
3616 let len = items.len() as i64;
3617 let real = if idx >= 0 { idx } else { len + idx };
3618 if real >= 0 && real < len {
3619 items.remove(real as usize);
3620 }
3621 }
3622 }
3623 _ => {}
3624 }
3625 return;
3626 }
3627 match node {
3629 JsonValue::Object(entries) => {
3630 if let Some((_, child)) = entries.iter_mut().find(|(k, _)| k == step) {
3631 delete_at_path(child, &path[1..]);
3632 }
3633 }
3634 JsonValue::Array(items) => {
3635 if let Ok(idx) = step.parse::<i64>() {
3636 let len = items.len() as i64;
3637 let real = if idx >= 0 { idx } else { len + idx };
3638 if real >= 0 && real < len {
3639 delete_at_path(&mut items[real as usize], &path[1..]);
3640 }
3641 }
3642 }
3643 _ => {}
3644 }
3645}
3646
3647fn set_at_path(node: &mut JsonValue, path: &[String], new_val: JsonValue, create_missing: bool) {
3648 if path.is_empty() {
3649 *node = new_val;
3650 return;
3651 }
3652 let step = &path[0];
3653 let rest = &path[1..];
3654 match node {
3655 JsonValue::Object(entries) => {
3656 if let Some(pos) = entries.iter().position(|(k, _)| k == step) {
3657 if rest.is_empty() {
3658 entries[pos].1 = new_val;
3659 } else {
3660 set_at_path(&mut entries[pos].1, rest, new_val, create_missing);
3661 }
3662 } else if create_missing && rest.is_empty() {
3663 entries.push((step.clone(), new_val));
3664 }
3665 }
3668 JsonValue::Array(items) => {
3669 let Some(idx) = resolve_array_index(step, items.len()) else {
3670 if create_missing && rest.is_empty() {
3671 if let Ok(n) = step.parse::<i64>() {
3673 if n < 0 {
3674 items.insert(0, new_val);
3675 } else {
3676 items.push(new_val);
3677 }
3678 }
3679 }
3680 return;
3681 };
3682 if rest.is_empty() {
3683 items[idx] = new_val;
3684 } else {
3685 set_at_path(&mut items[idx], rest, new_val, create_missing);
3686 }
3687 }
3688 _ => {
3689 }
3691 }
3692}
3693
3694fn resolve_array_index(step: &str, len: usize) -> Option<usize> {
3695 let n = step.parse::<i64>().ok()?;
3696 if n >= 0 {
3697 let i = n as usize;
3698 if i < len { Some(i) } else { None }
3699 } else {
3700 let from_end = len as i64 + n;
3701 if from_end >= 0 {
3702 Some(from_end as usize)
3703 } else {
3704 None
3705 }
3706 }
3707}
3708
3709pub fn insert(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3717 if !(3..=4).contains(&args.len()) {
3718 return Err(EvalError::TypeMismatch {
3719 detail: alloc::format!("jsonb_insert() takes 3 or 4 args, got {}", args.len()),
3720 });
3721 }
3722 if args.iter().take(3).any(|v| matches!(v, Value::Null)) {
3723 return Ok(Value::Null);
3724 }
3725 let insert_after = match args.get(3) {
3726 None | Some(Value::Null) => false,
3727 Some(Value::Bool(b)) => *b,
3728 Some(other) => {
3729 return Err(EvalError::TypeMismatch {
3730 detail: alloc::format!(
3731 "jsonb_insert() insert_after must be BOOL, got {}",
3732 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3733 ),
3734 });
3735 }
3736 };
3737 let doc_text = json_text_arg(&args[0], "jsonb_insert", "target")?;
3738 let path = path_text_arg(&args[1], "jsonb_insert")?;
3739 let new_text = json_text_arg(&args[2], "jsonb_insert", "new_value")?;
3740 let mut root = parse(doc_text).map_err(|e| EvalError::TypeMismatch {
3741 detail: alloc::format!("jsonb_insert(): invalid JSON target — {e}"),
3742 })?;
3743 if path.is_empty() {
3747 return Ok(Value::json(root.to_json_text()));
3748 }
3749 if is_json_scalar(&root) {
3750 return Err(EvalError::TypeMismatch {
3751 detail: "cannot set path in scalar".into(),
3752 });
3753 }
3754 let new_val = parse(new_text).map_err(|e| EvalError::TypeMismatch {
3755 detail: alloc::format!("jsonb_insert(): invalid JSON new_value — {e}"),
3756 })?;
3757 insert_at_path(&mut root, &path, new_val, insert_after)?;
3758 Ok(Value::json(root.to_json_text()))
3759}
3760
3761fn insert_at_path(
3762 node: &mut JsonValue,
3763 path: &[String],
3764 new_val: JsonValue,
3765 insert_after: bool,
3766) -> Result<(), EvalError> {
3767 debug_assert!(!path.is_empty());
3768 if path.len() == 1 {
3769 let step = &path[0];
3770 match node {
3771 JsonValue::Object(entries) => {
3772 if entries.iter().any(|(k, _)| k == step) {
3773 return Err(EvalError::TypeMismatch {
3774 detail: alloc::format!(
3775 "jsonb_insert(): cannot replace existing key {step:?}"
3776 ),
3777 });
3778 }
3779 entries.push((step.clone(), new_val));
3780 Ok(())
3781 }
3782 JsonValue::Array(items) => {
3783 let Ok(n) = step.parse::<i64>() else {
3784 return Err(EvalError::TypeMismatch {
3785 detail: alloc::format!(
3786 "jsonb_insert(): array step must be integer, got {step:?}"
3787 ),
3788 });
3789 };
3790 let mut idx = if n >= 0 {
3791 let i = n as usize;
3792 if i > items.len() { items.len() } else { i }
3793 } else {
3794 let from_end = items.len() as i64 + n;
3795 if from_end < 0 { 0 } else { from_end as usize }
3796 };
3797 if insert_after && idx < items.len() {
3798 idx += 1;
3799 }
3800 items.insert(idx, new_val);
3801 Ok(())
3802 }
3803 _ => Err(EvalError::TypeMismatch {
3804 detail: "jsonb_insert(): parent at path is a scalar".into(),
3805 }),
3806 }
3807 } else {
3808 let step = &path[0];
3809 let rest = &path[1..];
3810 match node {
3811 JsonValue::Object(entries) => {
3812 if let Some(pos) = entries.iter().position(|(k, _)| k == step) {
3813 insert_at_path(&mut entries[pos].1, rest, new_val, insert_after)
3814 } else {
3815 Err(EvalError::TypeMismatch {
3816 detail: alloc::format!("jsonb_insert(): path {step:?} does not exist"),
3817 })
3818 }
3819 }
3820 JsonValue::Array(items) => {
3821 let Some(idx) = resolve_array_index(step, items.len()) else {
3822 return Err(EvalError::TypeMismatch {
3823 detail: alloc::format!("jsonb_insert(): array index {step:?} out of range"),
3824 });
3825 };
3826 insert_at_path(&mut items[idx], rest, new_val, insert_after)
3827 }
3828 _ => Err(EvalError::TypeMismatch {
3829 detail: "jsonb_insert(): parent at path is a scalar".into(),
3830 }),
3831 }
3832 }
3833}
3834
3835fn json_text_arg<'a>(v: &'a Value, fname: &str, role: &str) -> Result<&'a str, EvalError> {
3836 match v {
3837 Value::Json(s) | Value::Text(s) => Ok(s.as_ref()),
3838 other => Err(EvalError::TypeMismatch {
3839 detail: alloc::format!(
3840 "{fname}() {role} must be JSON or TEXT, got {}",
3841 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3842 ),
3843 }),
3844 }
3845}
3846
3847fn path_text_arg(v: &Value, fname: &str) -> Result<Vec<String>, EvalError> {
3848 match v {
3849 Value::Text(s) | Value::Json(s) => parse_text_array(s.as_ref()),
3850 Value::TextArray(items) => Ok(items
3851 .iter()
3852 .map(|o| o.clone().unwrap_or_default())
3853 .collect()),
3854 other => Err(EvalError::TypeMismatch {
3855 detail: alloc::format!(
3856 "{fname}() path must be TEXT[] or TEXT, got {}",
3857 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3858 ),
3859 }),
3860 }
3861}
3862
3863#[cfg(test)]
3864mod tests {
3865 use super::*;
3866
3867 fn canon(s: &str) -> String {
3868 canonicalize_jsonb(s).unwrap()
3869 }
3870
3871 #[test]
3872 fn canon_number_rules() {
3873 assert_eq!(canon_json_number("1.0"), "1.0");
3875 assert_eq!(canon_json_number("1e2"), "100");
3876 assert_eq!(canon_json_number("1.10"), "1.10");
3877 assert_eq!(canon_json_number("100.00"), "100.00");
3878 assert_eq!(canon_json_number("0.5"), "0.5");
3879 assert_eq!(canon_json_number("-0"), "0");
3880 assert_eq!(canon_json_number("1E-3"), "0.001");
3881 assert_eq!(canon_json_number("42"), "42");
3882 assert_eq!(canon_json_number("-2.5"), "-2.5");
3883 assert_eq!(canon_json_number("2.5e3"), "2500");
3884 }
3885
3886 #[test]
3887 fn canon_key_order_dedup_and_whitespace() {
3888 assert_eq!(
3890 canon(r#"{"b":1,"a":2,"aa":3,"":9,"z":4}"#),
3891 r#"{"": 9, "a": 2, "b": 1, "z": 4, "aa": 3}"#
3892 );
3893 assert_eq!(canon(r#"{"a":1,"a":2,"a":3}"#), r#"{"a": 3}"#);
3895 assert_eq!(canon("[3,2,1]"), "[3, 2, 1]");
3897 }
3898
3899 #[test]
3900 fn json_number_equality_by_value() {
3901 let eq = |a: &str, b: &str| json_eq(&parse(a).unwrap(), &parse(b).unwrap());
3902 assert!(eq("1", "1.0"));
3903 assert!(eq("1.50", "1.5"));
3904 assert!(eq("1e3", "1000.00"));
3905 assert!(eq("0", "-0"));
3906 assert!(eq("2.5e3", "2500"));
3907 assert!(!eq("1.5", "1.6"));
3908 assert!(eq("[1, 2.0]", "[1.0, 2]"));
3910 assert!(eq(r#"{"a":1}"#, r#"{"a":1.0}"#));
3911 }
3912
3913 #[test]
3914 fn canon_nested_and_scalars() {
3915 assert_eq!(
3916 canon(r#"{"x":{"b":1,"a":2},"y":[3,{"d":1,"c":2}]}"#),
3917 r#"{"x": {"a": 2, "b": 1}, "y": [3, {"c": 2, "d": 1}]}"#
3918 );
3919 assert_eq!(canon(" true "), "true");
3920 assert_eq!(canon(" 42 "), "42");
3921 assert_eq!(canon("{}"), "{}");
3922 assert_eq!(canon("[]"), "[]");
3923 assert_eq!(
3925 canon(r#"{"e":"café","t":"a\nb"}"#),
3926 r#"{"e": "café", "t": "a\nb"}"#
3927 );
3928 }
3929
3930 #[test]
3931 fn parse_atoms() {
3932 assert_eq!(parse("null").unwrap(), JsonValue::Null);
3933 assert_eq!(parse("true").unwrap(), JsonValue::Bool(true));
3934 assert_eq!(parse("false").unwrap(), JsonValue::Bool(false));
3935 assert_eq!(
3936 parse("\"hello\"").unwrap(),
3937 JsonValue::String("hello".into())
3938 );
3939 assert!(matches!(
3940 parse("42").unwrap(),
3941 JsonValue::NumberText(ref s) if s == "42"
3942 ));
3943 }
3944
3945 #[test]
3946 fn parse_nested() {
3947 let doc = parse(r#"{"a":1,"b":[true,null,"x"]}"#).unwrap();
3948 let JsonValue::Object(entries) = doc else {
3949 panic!("expected object");
3950 };
3951 assert_eq!(entries.len(), 2);
3952 assert_eq!(entries[0].0, "a");
3953 assert_eq!(entries[1].0, "b");
3954 }
3955
3956 #[test]
3957 fn parse_string_escapes() {
3958 let s = parse(r#""he said \"hi\" and\\then\n""#).unwrap();
3959 assert_eq!(s, JsonValue::String("he said \"hi\" and\\then\n".into()));
3960 }
3961
3962 #[test]
3963 fn parse_unicode_escape() {
3964 assert_eq!(parse(r#""é""#).unwrap(), JsonValue::String("é".into()));
3965 }
3966
3967 #[test]
3968 fn path_object_key_returns_value() {
3969 let doc = Value::json::<String>(r#"{"name":"alice","age":30}"#.into());
3970 let key = Value::text("name");
3971 let v = path_get(&doc, &key, true).unwrap();
3972 assert_eq!(v, Value::text("alice"));
3973 let v = path_get(&doc, &key, false).unwrap();
3974 assert_eq!(v, Value::json("\"alice\""));
3975 }
3976
3977 #[test]
3978 fn path_array_index_supports_negative() {
3979 let doc = Value::json("[10,20,30]");
3980 let v = path_get(&doc, &Value::Int(1), true).unwrap();
3981 assert_eq!(v, Value::text("20"));
3982 let v = path_get(&doc, &Value::Int(-1), true).unwrap();
3983 assert_eq!(v, Value::text("30"));
3984 }
3985
3986 #[test]
3987 fn path_missing_key_returns_null() {
3988 let doc = Value::json::<String>(r#"{"a":1}"#.into());
3989 let v = path_get(&doc, &Value::text("missing"), true).unwrap();
3990 assert_eq!(v, Value::Null);
3991 }
3992
3993 #[test]
3994 fn path_get_nested_subtree_is_verbatim() {
3995 let doc = Value::json::<String>(r#"{"k":{"x":[1,2]}}"#.into());
4001 let v = path_get(&doc, &Value::text("k"), false).unwrap();
4002 assert_eq!(v, Value::json::<String>(r#"{"x":[1,2]}"#.into()));
4003
4004 let canon = Value::json::<String>(r#"{"k": {"x": [1, 2]}}"#.into());
4006 let v = path_get(&canon, &Value::text("k"), false).unwrap();
4007 assert_eq!(v, Value::json::<String>(r#"{"x": [1, 2]}"#.into()));
4008
4009 let raw = Value::json::<String>(r#"{"a":{ "y" : 2e2 },"k":1,"k":2}"#.into());
4012 assert_eq!(
4013 path_get(&raw, &Value::text("a"), false).unwrap(),
4014 Value::json::<String>(r#"{ "y" : 2e2 }"#.into())
4015 );
4016 assert_eq!(
4017 path_get(&raw, &Value::text("k"), false).unwrap(),
4018 Value::json::<String>("2".into())
4019 );
4020
4021 let n = Value::json::<String>(r#"{"a":null}"#.into());
4023 assert_eq!(
4024 path_get(&n, &Value::text("a"), false).unwrap(),
4025 Value::json::<String>("null".into())
4026 );
4027 assert_eq!(path_get(&n, &Value::text("a"), true).unwrap(), Value::Null);
4028 }
4029}
4030
4031#[derive(Debug)]
4034pub enum MysqlPathStep {
4035 Key(String),
4036 Index(usize),
4037}
4038
4039pub fn mysql_path_steps(path: &str) -> Result<Vec<MysqlPathStep>, EvalError> {
4044 let chars: Vec<char> = path.trim().chars().collect();
4045 if chars.first() != Some(&'$') {
4046 return Err(EvalError::TypeMismatch {
4047 detail: alloc::format!("invalid JSON path expression (must start with $): {path:?}"),
4048 });
4049 }
4050 let mut steps = Vec::new();
4051 let mut i = 1;
4052 while i < chars.len() {
4053 match chars[i] {
4054 '.' => {
4055 i += 1;
4056 if i < chars.len() && chars[i] == '"' {
4057 i += 1;
4058 let mut key = String::new();
4059 while i < chars.len() && chars[i] != '"' {
4060 if chars[i] == '\\' && i + 1 < chars.len() {
4061 i += 1;
4062 }
4063 key.push(chars[i]);
4064 i += 1;
4065 }
4066 if i >= chars.len() {
4067 return Err(EvalError::TypeMismatch {
4068 detail: alloc::format!(
4069 "invalid JSON path expression (unterminated quote): {path:?}"
4070 ),
4071 });
4072 }
4073 i += 1; steps.push(MysqlPathStep::Key(key));
4075 } else {
4076 let mut key = String::new();
4077 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
4078 key.push(chars[i]);
4079 i += 1;
4080 }
4081 if key.is_empty() {
4082 return Err(EvalError::TypeMismatch {
4083 detail: alloc::format!(
4084 "unsupported JSON path step at position {i} in {path:?} \
4085 (wildcards are not supported)"
4086 ),
4087 });
4088 }
4089 steps.push(MysqlPathStep::Key(key));
4090 }
4091 }
4092 '[' => {
4093 i += 1;
4094 let mut num = String::new();
4095 while i < chars.len() && chars[i] != ']' {
4096 num.push(chars[i]);
4097 i += 1;
4098 }
4099 if i >= chars.len() {
4100 return Err(EvalError::TypeMismatch {
4101 detail: alloc::format!(
4102 "invalid JSON path expression (unterminated bracket): {path:?}"
4103 ),
4104 });
4105 }
4106 i += 1; let idx: usize = num.trim().parse().map_err(|_| EvalError::TypeMismatch {
4108 detail: alloc::format!(
4109 "unsupported JSON path index {num:?} in {path:?} \
4110 (wildcards are not supported)"
4111 ),
4112 })?;
4113 steps.push(MysqlPathStep::Index(idx));
4114 }
4115 other => {
4116 return Err(EvalError::TypeMismatch {
4117 detail: alloc::format!(
4118 "invalid JSON path expression (unexpected {other:?}): {path:?}"
4119 ),
4120 });
4121 }
4122 }
4123 }
4124 Ok(steps)
4125}
4126
4127pub fn mysql_path_get<'a>(doc: &'a JsonValue, steps: &[MysqlPathStep]) -> Option<&'a JsonValue> {
4130 let mut cur = doc;
4131 for step in steps {
4132 match (step, cur) {
4133 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4134 cur = members.iter().find(|(mk, _)| mk == k).map(|(_, v)| v)?;
4135 }
4136 (MysqlPathStep::Index(idx), JsonValue::Array(items)) => {
4137 cur = items.get(*idx)?;
4138 }
4139 (MysqlPathStep::Index(0), scalar) => {
4142 cur = scalar;
4143 }
4144 _ => return None,
4145 }
4146 }
4147 Some(cur)
4148}
4149
4150pub fn mysql_json_extract(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4155 if args.len() < 2 {
4156 return Err(EvalError::TypeMismatch {
4157 detail: alloc::format!(
4158 "json_extract() takes a document and at least one path, got {} args",
4159 args.len()
4160 ),
4161 });
4162 }
4163 if args.iter().any(|a| matches!(a, Value::Null)) {
4164 return Ok(Value::Null);
4165 }
4166 let src = match &args[0] {
4167 Value::Json(s) | Value::Text(s) => s.as_ref(),
4168 other => {
4169 return Err(EvalError::TypeMismatch {
4170 detail: alloc::format!(
4171 "json_extract() document must be json, got {}",
4172 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4173 ),
4174 });
4175 }
4176 };
4177 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4178 detail: alloc::format!("json_extract(): invalid JSON: {e}"),
4179 })?;
4180 let mut hits: Vec<String> = Vec::new();
4181 for path_v in &args[1..] {
4182 let Value::Text(p) = path_v else {
4183 return Err(EvalError::TypeMismatch {
4184 detail: alloc::format!(
4185 "json_extract() paths must be text, got {}",
4186 crate::conversions::pg_type_name_for_error_opt(path_v.data_type())
4187 ),
4188 });
4189 };
4190 let steps = mysql_path_steps(p)?;
4191 if let Some(v) = mysql_path_get(&doc, &steps) {
4192 hits.push(v.to_json_text());
4193 }
4194 }
4195 match (args.len() - 1, hits.len()) {
4196 (_, 0) => Ok(Value::Null),
4197 (1, _) => Ok(Value::Json(alloc::borrow::Cow::Owned(
4198 hits.into_iter().next().unwrap(),
4199 ))),
4200 _ => {
4201 let mut out = String::from("[");
4202 for (i, h) in hits.iter().enumerate() {
4203 if i > 0 {
4204 out.push_str(", ");
4205 }
4206 out.push_str(h);
4207 }
4208 out.push(']');
4209 Ok(Value::Json(alloc::borrow::Cow::Owned(out)))
4210 }
4211 }
4212}
4213
4214pub fn mysql_json_contains_path(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4217 if args.len() < 3 {
4218 return Err(EvalError::TypeMismatch {
4219 detail: alloc::format!(
4220 "json_contains_path() takes a document, one/all, and at least one path, got {} args",
4221 args.len()
4222 ),
4223 });
4224 }
4225 if args.iter().any(|a| matches!(a, Value::Null)) {
4226 return Ok(Value::Null);
4227 }
4228 let src = match &args[0] {
4229 Value::Json(s) | Value::Text(s) => s.as_ref(),
4230 other => {
4231 return Err(EvalError::TypeMismatch {
4232 detail: alloc::format!(
4233 "json_contains_path() document must be json, got {}",
4234 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4235 ),
4236 });
4237 }
4238 };
4239 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4240 detail: alloc::format!("json_contains_path(): invalid JSON: {e}"),
4241 })?;
4242 let mode = match &args[1] {
4243 Value::Text(m) if m.eq_ignore_ascii_case("one") => false,
4244 Value::Text(m) if m.eq_ignore_ascii_case("all") => true,
4245 other => {
4246 return Err(EvalError::TypeMismatch {
4247 detail: alloc::format!(
4248 "json_contains_path() second arg must be 'one' or 'all', got {other:?}"
4249 ),
4250 });
4251 }
4252 };
4253 let mut found_any = false;
4254 let mut found_all = true;
4255 for path_v in &args[2..] {
4256 let Value::Text(p) = path_v else {
4257 return Err(EvalError::TypeMismatch {
4258 detail: alloc::format!(
4259 "json_contains_path() paths must be text, got {}",
4260 crate::conversions::pg_type_name_for_error_opt(path_v.data_type())
4261 ),
4262 });
4263 };
4264 let steps = mysql_path_steps(p)?;
4265 if mysql_path_get(&doc, &steps).is_some() {
4266 found_any = true;
4267 } else {
4268 found_all = false;
4269 }
4270 }
4271 Ok(Value::Bool(if mode { found_all } else { found_any }))
4272}
4273
4274fn value_to_jsonvalue(v: &Value) -> Result<JsonValue, EvalError> {
4278 Ok(match v {
4279 Value::Null => JsonValue::Null,
4280 Value::Bool(b) => JsonValue::Bool(*b),
4281 Value::Json(s) => parse(s).map_err(|e| EvalError::TypeMismatch {
4282 detail: alloc::format!("invalid JSON value: {e}"),
4283 })?,
4284 Value::Text(s) => JsonValue::String(s.to_string()),
4285 Value::Composite(fields) => {
4288 let mut entries = alloc::vec::Vec::with_capacity(fields.len());
4289 for (name, fv) in fields.iter() {
4290 entries.push((name.clone(), value_to_jsonvalue(fv)?));
4291 }
4292 JsonValue::Object(entries)
4293 }
4294 other => {
4295 let text = value_to_json_text(other);
4298 parse(&text).map_err(|e| EvalError::TypeMismatch {
4299 detail: alloc::format!("invalid JSON value: {e}"),
4300 })?
4301 }
4302 })
4303}
4304
4305#[derive(Clone, Copy, PartialEq, Debug)]
4306enum MutateMode {
4307 Set,
4309 Insert,
4311 Replace,
4313}
4314
4315fn mutate_at(cur: &mut JsonValue, steps: &[MysqlPathStep], mode: MutateMode, newval: &JsonValue) {
4318 match steps {
4319 [] => {
4320 if matches!(mode, MutateMode::Set | MutateMode::Replace) {
4321 *cur = newval.clone();
4322 }
4323 }
4324 [last] => match (last, &mut *cur) {
4325 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4326 if let Some(slot) = members.iter_mut().find(|(mk, _)| mk == k) {
4327 if matches!(mode, MutateMode::Set | MutateMode::Replace) {
4328 slot.1 = newval.clone();
4329 }
4330 } else if matches!(mode, MutateMode::Set | MutateMode::Insert) {
4331 members.push((k.clone(), newval.clone()));
4332 }
4333 }
4334 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
4335 if *i < items.len() {
4336 if matches!(mode, MutateMode::Set | MutateMode::Replace) {
4337 items[*i] = newval.clone();
4338 }
4339 } else if matches!(mode, MutateMode::Set | MutateMode::Insert) {
4340 items.push(newval.clone());
4342 }
4343 }
4344 (MysqlPathStep::Index(0), scalar) => {
4346 if matches!(mode, MutateMode::Set | MutateMode::Replace) {
4347 *scalar = newval.clone();
4348 }
4349 }
4350 _ => {}
4351 },
4352 [head, rest @ ..] => match (head, cur) {
4353 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4354 if let Some(slot) = members.iter_mut().find(|(mk, _)| mk == k) {
4355 mutate_at(&mut slot.1, rest, mode, newval);
4356 }
4357 }
4358 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
4359 if let Some(slot) = items.get_mut(*i) {
4360 mutate_at(slot, rest, mode, newval);
4361 }
4362 }
4363 _ => {}
4364 },
4365 }
4366}
4367
4368fn mysql_json_mutate(
4369 args: &[Value<'_>],
4370 mode: MutateMode,
4371 fn_name: &str,
4372) -> Result<Value<'static>, EvalError> {
4373 if args.len() < 3 || args.len() % 2 == 0 {
4374 return Err(EvalError::TypeMismatch {
4375 detail: alloc::format!(
4376 "{fn_name}() takes a document plus (path, value) pairs, got {} args",
4377 args.len()
4378 ),
4379 });
4380 }
4381 if matches!(args[0], Value::Null) {
4382 return Ok(Value::Null);
4383 }
4384 let src = match &args[0] {
4385 Value::Json(s) | Value::Text(s) => s.as_ref(),
4386 other => {
4387 return Err(EvalError::TypeMismatch {
4388 detail: alloc::format!(
4389 "{fn_name}() document must be json, got {}",
4390 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4391 ),
4392 });
4393 }
4394 };
4395 let mut doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4396 detail: alloc::format!("{fn_name}(): invalid JSON: {e}"),
4397 })?;
4398 for pair in args[1..].chunks(2) {
4399 let Value::Text(p) = &pair[0] else {
4400 if matches!(pair[0], Value::Null) {
4401 return Ok(Value::Null);
4402 }
4403 return Err(EvalError::TypeMismatch {
4404 detail: alloc::format!(
4405 "{fn_name}() paths must be text, got {}",
4406 crate::conversions::pg_type_name_for_error_opt(pair[0].data_type())
4407 ),
4408 });
4409 };
4410 let steps = mysql_path_steps(p)?;
4411 let newval = value_to_jsonvalue(&pair[1])?;
4412 mutate_at(&mut doc, &steps, mode, &newval);
4413 }
4414 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4418 doc.to_json_text(),
4419 ))))
4420}
4421
4422pub fn mysql_json_set(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4426 mysql_json_mutate(args, MutateMode::Set, "json_set")
4427}
4428
4429pub fn mysql_json_insert(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4430 mysql_json_mutate(args, MutateMode::Insert, "json_insert")
4431}
4432
4433pub fn mysql_json_replace(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4434 mysql_json_mutate(args, MutateMode::Replace, "json_replace")
4435}
4436
4437pub fn mysql_json_remove(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4440 if args.len() < 2 {
4441 return Err(EvalError::TypeMismatch {
4442 detail: alloc::format!(
4443 "json_remove() takes a document and at least one path, got {} args",
4444 args.len()
4445 ),
4446 });
4447 }
4448 if args.iter().any(|a| matches!(a, Value::Null)) {
4449 return Ok(Value::Null);
4450 }
4451 let src = match &args[0] {
4452 Value::Json(s) | Value::Text(s) => s.as_ref(),
4453 other => {
4454 return Err(EvalError::TypeMismatch {
4455 detail: alloc::format!(
4456 "json_remove() document must be json, got {}",
4457 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4458 ),
4459 });
4460 }
4461 };
4462 let mut doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4463 detail: alloc::format!("json_remove(): invalid JSON: {e}"),
4464 })?;
4465 fn remove_at(cur: &mut JsonValue, steps: &[MysqlPathStep]) {
4466 match steps {
4467 [] => {}
4468 [last] => match (last, cur) {
4469 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4470 members.retain(|(mk, _)| mk != k);
4471 }
4472 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
4473 if *i < items.len() {
4474 items.remove(*i);
4475 }
4476 }
4477 _ => {}
4478 },
4479 [head, rest @ ..] => match (head, cur) {
4480 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4481 if let Some(slot) = members.iter_mut().find(|(mk, _)| mk == k) {
4482 remove_at(&mut slot.1, rest);
4483 }
4484 }
4485 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
4486 if let Some(slot) = items.get_mut(*i) {
4487 remove_at(slot, rest);
4488 }
4489 }
4490 _ => {}
4491 },
4492 }
4493 }
4494 for path_v in &args[1..] {
4495 let Value::Text(p) = path_v else {
4496 return Err(EvalError::TypeMismatch {
4497 detail: alloc::format!(
4498 "json_remove() paths must be text, got {}",
4499 crate::conversions::pg_type_name_for_error_opt(path_v.data_type())
4500 ),
4501 });
4502 };
4503 let steps = mysql_path_steps(p)?;
4504 if steps.is_empty() {
4505 return Err(EvalError::TypeMismatch {
4506 detail: "The path expression '$' is not allowed in this context".into(),
4507 });
4508 }
4509 remove_at(&mut doc, &steps);
4510 }
4511 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4513 doc.to_json_text(),
4514 ))))
4515}
4516
4517fn modify_at(cur: &mut JsonValue, steps: &[MysqlPathStep], f: &mut dyn FnMut(&mut JsonValue)) {
4520 match steps {
4521 [] => f(cur),
4522 [head, rest @ ..] => match (head, cur) {
4523 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4524 if let Some(slot) = members.iter_mut().find(|(mk, _)| mk == k) {
4525 modify_at(&mut slot.1, rest, f);
4526 }
4527 }
4528 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
4529 if let Some(slot) = items.get_mut(*i) {
4530 modify_at(slot, rest, f);
4531 }
4532 }
4533 _ => {}
4534 },
4535 }
4536}
4537
4538fn mysql_doc_and_pairs<'a>(
4540 args: &'a [Value<'_>],
4541 fn_name: &str,
4542) -> Result<Option<(JsonValue, &'a [Value<'a>])>, EvalError> {
4543 if args.len() < 3 || args.len() % 2 == 0 {
4544 return Err(EvalError::TypeMismatch {
4545 detail: alloc::format!(
4546 "{fn_name}() takes a document plus (path, value) pairs, got {} args",
4547 args.len()
4548 ),
4549 });
4550 }
4551 if args.iter().any(|a| matches!(a, Value::Null)) {
4552 return Ok(None);
4553 }
4554 let src = match &args[0] {
4555 Value::Json(s) | Value::Text(s) => s.as_ref(),
4556 other => {
4557 return Err(EvalError::TypeMismatch {
4558 detail: alloc::format!(
4559 "{fn_name}() document must be json, got {}",
4560 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4561 ),
4562 });
4563 }
4564 };
4565 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4566 detail: alloc::format!("{fn_name}(): invalid JSON: {e}"),
4567 })?;
4568 Ok(Some((doc, &args[1..])))
4569}
4570
4571pub fn mysql_json_array_append(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4575 let Some((mut doc, pairs)) = mysql_doc_and_pairs(args, "json_array_append")? else {
4576 return Ok(Value::Null);
4577 };
4578 for pair in pairs.chunks(2) {
4579 let Value::Text(p) = &pair[0] else {
4580 return Err(EvalError::TypeMismatch {
4581 detail: alloc::format!(
4582 "json_array_append() paths must be text, got {}",
4583 crate::conversions::pg_type_name_for_error_opt(pair[0].data_type())
4584 ),
4585 });
4586 };
4587 let steps = mysql_path_steps(p)?;
4588 let newval = value_to_jsonvalue(&pair[1])?;
4589 modify_at(&mut doc, &steps, &mut |v| match v {
4590 JsonValue::Array(items) => items.push(newval.clone()),
4591 other => {
4592 let old = core::mem::replace(other, JsonValue::Null);
4593 *other = JsonValue::Array(alloc::vec![old, newval.clone()]);
4594 }
4595 });
4596 }
4597 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4599 doc.to_json_text(),
4600 ))))
4601}
4602
4603pub fn mysql_json_array_insert(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4608 let Some((mut doc, pairs)) = mysql_doc_and_pairs(args, "json_array_insert")? else {
4609 return Ok(Value::Null);
4610 };
4611 for pair in pairs.chunks(2) {
4612 let Value::Text(p) = &pair[0] else {
4613 return Err(EvalError::TypeMismatch {
4614 detail: alloc::format!(
4615 "json_array_insert() paths must be text, got {}",
4616 crate::conversions::pg_type_name_for_error_opt(pair[0].data_type())
4617 ),
4618 });
4619 };
4620 let steps = mysql_path_steps(p)?;
4621 let Some(MysqlPathStep::Index(idx)) = steps.last() else {
4622 return Err(EvalError::TypeMismatch {
4623 detail: alloc::format!(
4624 "json_array_insert() path must end with an array index: {p:?}"
4625 ),
4626 });
4627 };
4628 let idx = *idx;
4629 let newval = value_to_jsonvalue(&pair[1])?;
4630 modify_at(&mut doc, &steps[..steps.len() - 1], &mut |v| {
4631 if let JsonValue::Array(items) = v {
4632 let at = idx.min(items.len());
4633 items.insert(at, newval.clone());
4634 }
4635 });
4636 }
4637 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4639 doc.to_json_text(),
4640 ))))
4641}
4642
4643fn mysql_contains(target: &JsonValue, cand: &JsonValue) -> bool {
4648 match (target, cand) {
4649 (JsonValue::Object(t), JsonValue::Object(c)) => c
4650 .iter()
4651 .all(|(ck, cv)| t.iter().any(|(tk, tv)| tk == ck && mysql_contains(tv, cv))),
4652 (JsonValue::Array(t), JsonValue::Array(c)) => {
4653 c.iter().all(|cv| t.iter().any(|tv| mysql_contains(tv, cv)))
4654 }
4655 (JsonValue::Array(t), scalar) => t.iter().any(|tv| mysql_contains(tv, scalar)),
4656 (JsonValue::Number(a), JsonValue::NumberText(b))
4658 | (JsonValue::NumberText(b), JsonValue::Number(a)) => {
4659 b.parse::<f64>().map(|x| x == *a).unwrap_or(false)
4660 }
4661 (JsonValue::NumberText(a), JsonValue::NumberText(b)) => {
4662 a == b
4663 || (a.parse::<f64>().ok().zip(b.parse::<f64>().ok()))
4664 .map(|(x, y)| x == y)
4665 .unwrap_or(false)
4666 }
4667 (a, b) => a == b,
4668 }
4669}
4670
4671pub fn mysql_json_contains(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4674 if !matches!(args.len(), 2 | 3) {
4675 return Err(EvalError::TypeMismatch {
4676 detail: alloc::format!("json_contains() takes 2 or 3 args, got {}", args.len()),
4677 });
4678 }
4679 if args.iter().any(|a| matches!(a, Value::Null)) {
4680 return Ok(Value::Null);
4681 }
4682 let parse_arg = |v: &Value<'_>, which: &str| -> Result<JsonValue, EvalError> {
4683 match v {
4684 Value::Json(s) | Value::Text(s) => parse(s).map_err(|e| EvalError::TypeMismatch {
4685 detail: alloc::format!("json_contains(): invalid {which} JSON: {e}"),
4686 }),
4687 other => Err(EvalError::TypeMismatch {
4688 detail: alloc::format!(
4689 "json_contains() {which} must be json, got {}",
4690 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4691 ),
4692 }),
4693 }
4694 };
4695 let target = parse_arg(&args[0], "target")?;
4696 let cand = parse_arg(&args[1], "candidate")?;
4697 let effective = match args.get(2) {
4698 None => Some(&target),
4699 Some(Value::Text(p)) => {
4700 let steps = mysql_path_steps(p)?;
4701 mysql_path_get(&target, &steps)
4702 }
4703 Some(other) => {
4704 return Err(EvalError::TypeMismatch {
4705 detail: alloc::format!(
4706 "json_contains() path must be text, got {}",
4707 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4708 ),
4709 });
4710 }
4711 };
4712 match effective {
4713 None => Ok(Value::Null),
4714 Some(t) => Ok(Value::Bool(mysql_contains(t, &cand))),
4715 }
4716}
4717
4718fn merge_patch(target: JsonValue, patch: JsonValue) -> JsonValue {
4722 let JsonValue::Object(patch_members) = patch else {
4723 return patch;
4724 };
4725 let mut out = match target {
4726 JsonValue::Object(members) => members,
4727 _ => Vec::new(),
4728 };
4729 for (k, v) in patch_members {
4730 if matches!(v, JsonValue::Null) {
4731 out.retain(|(mk, _)| *mk != k);
4732 } else if let Some(slot) = out.iter_mut().find(|(mk, _)| *mk == k) {
4733 let old = core::mem::replace(&mut slot.1, JsonValue::Null);
4734 slot.1 = merge_patch(old, v);
4735 } else {
4736 out.push((k, merge_patch(JsonValue::Null, v)));
4738 }
4739 }
4740 JsonValue::Object(out)
4741}
4742
4743fn merge_preserve(a: JsonValue, b: JsonValue) -> JsonValue {
4748 match (a, b) {
4749 (JsonValue::Object(mut ma), JsonValue::Object(mb)) => {
4750 for (k, v) in mb {
4751 if let Some(pos) = ma.iter().position(|(mk, _)| *mk == k) {
4752 let (_, old) = ma.remove(pos);
4753 ma.insert(pos, (k, merge_preserve(old, v)));
4754 } else {
4755 ma.push((k, v));
4756 }
4757 }
4758 JsonValue::Object(ma)
4759 }
4760 (JsonValue::Array(mut xs), JsonValue::Array(ys)) => {
4761 xs.extend(ys);
4762 JsonValue::Array(xs)
4763 }
4764 (JsonValue::Array(mut xs), scalar) => {
4765 xs.push(scalar);
4766 JsonValue::Array(xs)
4767 }
4768 (scalar, JsonValue::Array(ys)) => {
4769 let mut xs = alloc::vec![scalar];
4770 xs.extend(ys);
4771 JsonValue::Array(xs)
4772 }
4773 (sa, sb) => JsonValue::Array(alloc::vec![sa, sb]),
4774 }
4775}
4776
4777fn mysql_json_merge(
4778 args: &[Value<'_>],
4779 fn_name: &str,
4780 combine: fn(JsonValue, JsonValue) -> JsonValue,
4781) -> Result<Value<'static>, EvalError> {
4782 if args.len() < 2 {
4783 return Err(EvalError::TypeMismatch {
4784 detail: alloc::format!("{fn_name}() takes at least 2 documents, got {}", args.len()),
4785 });
4786 }
4787 if args.iter().any(|a| matches!(a, Value::Null)) {
4788 return Ok(Value::Null);
4789 }
4790 let mut acc: Option<JsonValue> = None;
4791 for arg in args {
4792 let src = match arg {
4793 Value::Json(s) | Value::Text(s) => s.as_ref(),
4794 other => {
4795 return Err(EvalError::TypeMismatch {
4796 detail: alloc::format!(
4797 "{fn_name}() arguments must be json, got {}",
4798 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4799 ),
4800 });
4801 }
4802 };
4803 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4804 detail: alloc::format!("{fn_name}(): invalid JSON: {e}"),
4805 })?;
4806 acc = Some(match acc {
4807 None => doc,
4808 Some(prev) => combine(prev, doc),
4809 });
4810 }
4811 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4813 acc.unwrap().to_json_text(),
4814 ))))
4815}
4816
4817pub fn mysql_json_merge_patch(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4819 mysql_json_merge(args, "json_merge_patch", merge_patch)
4820}
4821
4822pub fn mysql_json_merge_preserve(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4825 mysql_json_merge(args, "json_merge_preserve", merge_preserve)
4826}
4827
4828pub fn mysql_json_overlaps(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4832 if args.len() != 2 {
4833 return Err(EvalError::TypeMismatch {
4834 detail: alloc::format!("json_overlaps() takes 2 args, got {}", args.len()),
4835 });
4836 }
4837 if args.iter().any(|a| matches!(a, Value::Null)) {
4838 return Ok(Value::Null);
4839 }
4840 let parse_arg = |v: &Value<'_>| -> Result<JsonValue, EvalError> {
4841 match v {
4842 Value::Json(s) | Value::Text(s) => parse(s).map_err(|e| EvalError::TypeMismatch {
4843 detail: alloc::format!("json_overlaps(): invalid JSON: {e}"),
4844 }),
4845 other => Err(EvalError::TypeMismatch {
4846 detail: alloc::format!(
4847 "json_overlaps() arguments must be json, got {}",
4848 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4849 ),
4850 }),
4851 }
4852 };
4853 let a = parse_arg(&args[0])?;
4854 let b = parse_arg(&args[1])?;
4855 let overlaps = match (&a, &b) {
4856 (JsonValue::Array(xs), JsonValue::Array(ys)) => xs.iter().any(|x| {
4857 ys.iter()
4858 .any(|y| mysql_contains(x, y) && mysql_contains(y, x))
4859 }),
4860 (JsonValue::Object(ma), JsonValue::Object(mb)) => ma.iter().any(|(k, v)| {
4861 mb.iter()
4862 .any(|(k2, v2)| k == k2 && mysql_contains(v, v2) && mysql_contains(v2, v))
4863 }),
4864 (JsonValue::Array(xs), scalar) | (scalar, JsonValue::Array(xs)) => xs
4865 .iter()
4866 .any(|x| mysql_contains(x, scalar) && mysql_contains(scalar, x)),
4867 (sa, sb) => mysql_contains(sa, sb) && mysql_contains(sb, sa),
4868 };
4869 Ok(Value::Bool(overlaps))
4870}
4871
4872fn like_match(text: &[char], pat: &[char], escape: char) -> bool {
4875 match pat {
4876 [] => text.is_empty(),
4877 ['%', rest @ ..] => (0..=text.len()).any(|skip| like_match(&text[skip..], rest, escape)),
4878 ['_', rest @ ..] => !text.is_empty() && like_match(&text[1..], rest, escape),
4879 [e, lit, rest @ ..] if *e == escape => {
4880 text.first() == Some(lit) && like_match(&text[1..], rest, escape)
4881 }
4882 [c, rest @ ..] => text.first() == Some(c) && like_match(&text[1..], rest, escape),
4883 }
4884}
4885
4886fn push_path_step(out: &mut String, step_key: Option<&str>, step_idx: Option<usize>) {
4889 if let Some(k) = step_key {
4890 let ident_shaped = !k.is_empty()
4891 && k.chars().all(|c| c.is_alphanumeric() || c == '_')
4892 && !k.chars().next().unwrap().is_numeric();
4893 if ident_shaped {
4894 out.push('.');
4895 out.push_str(k);
4896 } else {
4897 out.push_str(".\"");
4898 for c in k.chars() {
4899 if c == '"' || c == '\\' {
4900 out.push('\\');
4901 }
4902 out.push(c);
4903 }
4904 out.push('"');
4905 }
4906 }
4907 if let Some(i) = step_idx {
4908 out.push('[');
4909 out.push_str(&alloc::format!("{i}"));
4910 out.push(']');
4911 }
4912}
4913
4914pub fn mysql_json_search(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4920 if args.len() < 3 {
4921 return Err(EvalError::TypeMismatch {
4922 detail: alloc::format!(
4923 "json_search() takes doc, one/all, pattern [, escape [, path...]], got {} args",
4924 args.len()
4925 ),
4926 });
4927 }
4928 if args[..3].iter().any(|a| matches!(a, Value::Null)) {
4929 return Ok(Value::Null);
4930 }
4931 let src = match &args[0] {
4932 Value::Json(s) | Value::Text(s) => s.as_ref(),
4933 other => {
4934 return Err(EvalError::TypeMismatch {
4935 detail: alloc::format!(
4936 "json_search() document must be json, got {}",
4937 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4938 ),
4939 });
4940 }
4941 };
4942 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4943 detail: alloc::format!("json_search(): invalid JSON: {e}"),
4944 })?;
4945 let one = match &args[1] {
4946 Value::Text(m) if m.eq_ignore_ascii_case("one") => true,
4947 Value::Text(m) if m.eq_ignore_ascii_case("all") => false,
4948 other => {
4949 return Err(EvalError::TypeMismatch {
4950 detail: alloc::format!(
4951 "json_search() second arg must be 'one' or 'all', got {other:?}"
4952 ),
4953 });
4954 }
4955 };
4956 let Value::Text(pattern) = &args[2] else {
4957 return Err(EvalError::TypeMismatch {
4958 detail: alloc::format!(
4959 "json_search() pattern must be text, got {}",
4960 crate::conversions::pg_type_name_for_error_opt(args[2].data_type())
4961 ),
4962 });
4963 };
4964 let escape = match args.get(3) {
4965 None | Some(Value::Null) => '\\',
4966 Some(Value::Text(e)) if e.chars().count() == 1 => e.chars().next().unwrap(),
4967 Some(other) => {
4968 return Err(EvalError::TypeMismatch {
4969 detail: alloc::format!(
4970 "json_search() escape must be a single character, got {other:?}"
4971 ),
4972 });
4973 }
4974 };
4975 let pat: Vec<char> = pattern.chars().collect();
4976 fn walk(
4977 v: &JsonValue,
4978 path: &str,
4979 pat: &[char],
4980 escape: char,
4981 hits: &mut Vec<String>,
4982 stop_at_one: bool,
4983 ) {
4984 if stop_at_one && !hits.is_empty() {
4985 return;
4986 }
4987 match v {
4988 JsonValue::String(s) => {
4989 let chars: Vec<char> = s.chars().collect();
4990 if like_match(&chars, pat, escape) {
4991 hits.push(path.to_string());
4992 }
4993 }
4994 JsonValue::Object(members) => {
4995 for (k, mv) in members {
4996 let mut p = path.to_string();
4997 push_path_step(&mut p, Some(k), None);
4998 walk(mv, &p, pat, escape, hits, stop_at_one);
4999 }
5000 }
5001 JsonValue::Array(items) => {
5002 for (i, iv) in items.iter().enumerate() {
5003 let mut p = path.to_string();
5004 push_path_step(&mut p, None, Some(i));
5005 walk(iv, &p, pat, escape, hits, stop_at_one);
5006 }
5007 }
5008 _ => {}
5009 }
5010 }
5011 let mut hits: Vec<String> = Vec::new();
5012 let start_paths: Vec<String> = args
5013 .get(4..)
5014 .unwrap_or(&[])
5015 .iter()
5016 .map(|v| match v {
5017 Value::Text(p) => Ok(p.to_string()),
5018 other => Err(EvalError::TypeMismatch {
5019 detail: alloc::format!(
5020 "json_search() paths must be text, got {}",
5021 crate::conversions::pg_type_name_for_error_opt(other.data_type())
5022 ),
5023 }),
5024 })
5025 .collect::<Result<_, _>>()?;
5026 if start_paths.is_empty() {
5027 walk(&doc, "$", &pat, escape, &mut hits, one);
5028 } else {
5029 for p in &start_paths {
5030 let steps = mysql_path_steps(p)?;
5031 if let Some(sub) = mysql_path_get(&doc, &steps) {
5032 walk(sub, p.trim(), &pat, escape, &mut hits, one);
5033 }
5034 }
5035 }
5036 match hits.len() {
5037 0 => Ok(Value::Null),
5038 1 => Ok(Value::Json(alloc::borrow::Cow::Owned(
5039 JsonValue::String(hits.into_iter().next().unwrap()).to_json_text(),
5040 ))),
5041 _ => {
5042 let arr = JsonValue::Array(hits.into_iter().map(JsonValue::String).collect());
5043 Ok(Value::Json(alloc::borrow::Cow::Owned(arr.to_json_text())))
5044 }
5045 }
5046}
5047
5048pub fn mysql_json_value(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
5053 if args.len() != 2 {
5054 return Err(EvalError::TypeMismatch {
5055 detail: alloc::format!("json_value() takes 2 args, got {}", args.len()),
5056 });
5057 }
5058 if args.iter().any(|a| matches!(a, Value::Null)) {
5059 return Ok(Value::Null);
5060 }
5061 let src = match &args[0] {
5062 Value::Json(s) | Value::Text(s) => s.as_ref(),
5063 other => {
5064 return Err(EvalError::TypeMismatch {
5065 detail: alloc::format!(
5066 "json_value() document must be json, got {}",
5067 crate::conversions::pg_type_name_for_error_opt(other.data_type())
5068 ),
5069 });
5070 }
5071 };
5072 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
5073 detail: alloc::format!("json_value(): invalid JSON: {e}"),
5074 })?;
5075 let Value::Text(p) = &args[1] else {
5076 return Err(EvalError::TypeMismatch {
5077 detail: alloc::format!(
5078 "json_value() path must be text, got {}",
5079 crate::conversions::pg_type_name_for_error_opt(args[1].data_type())
5080 ),
5081 });
5082 };
5083 let steps = mysql_path_steps(p)?;
5084 match mysql_path_get(&doc, &steps) {
5085 None => Ok(Value::Null),
5086 Some(JsonValue::Null) => Ok(Value::Null),
5087 Some(v) => Ok(Value::text(v.as_text())),
5088 }
5089}
5090
5091fn is_json_scalar(v: &JsonValue) -> bool {
5095 !matches!(v, JsonValue::Object(_) | JsonValue::Array(_))
5096}
5097
5098#[cfg(test)]
5099mod round619_number_fast_path {
5100 use super::*;
5101
5102 #[test]
5106 fn fast_path_agrees_with_the_full_canonicaliser() {
5107 let mut cases: Vec<String> = Vec::new();
5108 for sign in ["", "-"] {
5109 for body in [
5110 "0",
5111 "1",
5112 "7",
5113 "10",
5114 "123",
5115 "0123",
5116 "00",
5117 "000",
5118 "9223372036854775807",
5119 "170141183460469231731687303715884105727",
5120 "1.0",
5121 "1.5",
5122 "0.5",
5123 ".5",
5124 "1.",
5125 "1e3",
5126 "1E3",
5127 "1e-3",
5128 "1.5e2",
5129 "1.50",
5130 "100",
5131 "0.0",
5132 "0.00",
5133 "10.010",
5134 "1e0",
5135 "1e+3",
5136 "0e0",
5137 "12345678901234567890.12345678901234567890",
5138 ] {
5139 cases.push(alloc::format!("{sign}{body}"));
5140 }
5141 }
5142 for c in &cases {
5143 assert_eq!(
5144 canon_json_number(c).as_ref(),
5145 canon_json_number_slow(c).as_str(),
5146 "lexeme {c:?} canonicalises differently through the shortcut"
5147 );
5148 }
5149 }
5150
5151 #[test]
5153 fn one_entry_object_writes_what_the_general_writer_writes() {
5154 for src in [
5155 r#"{"a":1}"#,
5156 r#"{"":1}"#,
5157 r#"{"a":{"b":2}}"#,
5158 r#"{"a":[1,2,3]}"#,
5159 r#"{"a\"b":"c\\d"}"#,
5160 r#"{"日本":"語"}"#,
5161 r#"{"a":null}"#,
5162 r#"{}"#,
5163 ] {
5164 let JsonValue::Object(entries) = parse(src).expect("valid json") else {
5165 panic!("{src} is not an object");
5166 };
5167 let mut fast = String::new();
5168 write_json_canonical(&JsonValue::Object(entries.clone()), &mut fast);
5169 let mut general = String::new();
5170 write_object_general(&entries, &mut general);
5171 assert_eq!(fast, general, "{src}");
5172 }
5173 }
5174}