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
866pub fn key_exists(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
881 let lhs_text = match lhs {
882 Value::Json(s) | Value::Text(s) => s.as_ref(),
883 Value::Null => return Ok(Value::Null),
884 other => {
885 return Err(EvalError::TypeMismatch {
886 detail: alloc::format!(
887 "JSON ?: left side must be JSON or TEXT, got {}",
888 crate::conversions::pg_type_name_for_error_opt(other.data_type())
889 ),
890 });
891 }
892 };
893 let key = match rhs {
894 Value::Text(s) => s.as_ref(),
895 Value::Null => return Ok(Value::Null),
896 other => {
897 return Err(EvalError::TypeMismatch {
898 detail: alloc::format!(
899 "JSON ?: right side must be TEXT, got {}",
900 crate::conversions::pg_type_name_for_error_opt(other.data_type())
901 ),
902 });
903 }
904 };
905 let doc = parse(lhs_text).map_err(|e| EvalError::TypeMismatch {
906 detail: alloc::format!("invalid JSON on left of ?: {e}"),
907 })?;
908 Ok(Value::Bool(node_has_key(&doc, key)))
909}
910
911fn node_has_key(v: &JsonValue, key: &str) -> bool {
912 match v {
913 JsonValue::Object(members) => members.iter().any(|(k, _)| k == key),
914 JsonValue::Array(items) => items
915 .iter()
916 .any(|item| matches!(item, JsonValue::String(s) if s == key)),
917 JsonValue::String(s) => s == key,
918 _ => false,
919 }
920}
921
922fn collect_keys(v: &Value) -> Result<Option<Vec<String>>, EvalError> {
925 match v {
926 Value::Null => Ok(None),
927 Value::TextArray(items) => Ok(Some(items.iter().filter_map(|x| x.clone()).collect())),
928 Value::Text(s) => Ok(Some(alloc::vec![s.to_string()])),
929 other => Err(EvalError::TypeMismatch {
930 detail: alloc::format!(
931 "JSON ?|/?&: right side must be TEXT[] or TEXT, got {}",
932 crate::conversions::pg_type_name_for_error_opt(other.data_type())
933 ),
934 }),
935 }
936}
937
938pub fn keys_any(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
941 let lhs_text = match lhs {
942 Value::Json(s) | Value::Text(s) => s.as_ref(),
943 Value::Null => return Ok(Value::Null),
944 other => {
945 return Err(EvalError::TypeMismatch {
946 detail: alloc::format!(
947 "JSON ?|: left side must be JSON or TEXT, got {}",
948 crate::conversions::pg_type_name_for_error_opt(other.data_type())
949 ),
950 });
951 }
952 };
953 let Some(keys) = collect_keys(rhs)? else {
954 return Ok(Value::Null);
955 };
956 let doc = parse(lhs_text).map_err(|e| EvalError::TypeMismatch {
957 detail: alloc::format!("invalid JSON on left of ?|: {e}"),
958 })?;
959 Ok(Value::Bool(keys.iter().any(|k| node_has_key(&doc, k))))
960}
961
962pub fn keys_all(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
965 let lhs_text = match lhs {
966 Value::Json(s) | Value::Text(s) => s.as_ref(),
967 Value::Null => return Ok(Value::Null),
968 other => {
969 return Err(EvalError::TypeMismatch {
970 detail: alloc::format!(
971 "JSON ?&: left side must be JSON or TEXT, got {}",
972 crate::conversions::pg_type_name_for_error_opt(other.data_type())
973 ),
974 });
975 }
976 };
977 let Some(keys) = collect_keys(rhs)? else {
978 return Ok(Value::Null);
979 };
980 let doc = parse(lhs_text).map_err(|e| EvalError::TypeMismatch {
981 detail: alloc::format!("invalid JSON on left of ?&: {e}"),
982 })?;
983 Ok(Value::Bool(keys.iter().all(|k| node_has_key(&doc, k))))
984}
985
986pub fn contains(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
987 let lhs_text = match lhs {
988 Value::Json(s) | Value::Text(s) => s.as_ref(),
989 Value::Null => return Ok(Value::Null),
990 other => {
991 return Err(EvalError::TypeMismatch {
992 detail: alloc::format!(
993 "JSON @>: left side must be JSON or TEXT, got {}",
994 crate::conversions::pg_type_name_for_error_opt(other.data_type())
995 ),
996 });
997 }
998 };
999 let rhs_text = match rhs {
1000 Value::Json(s) | Value::Text(s) => s.as_ref(),
1001 Value::Null => return Ok(Value::Null),
1002 other => {
1003 return Err(EvalError::TypeMismatch {
1004 detail: alloc::format!(
1005 "JSON @>: right side must be JSON or TEXT, got {}",
1006 crate::conversions::pg_type_name_for_error_opt(other.data_type())
1007 ),
1008 });
1009 }
1010 };
1011 let rhs_doc = parse(rhs_text).map_err(|e| EvalError::TypeMismatch {
1012 detail: alloc::format!("invalid JSON on right of @>: {e}"),
1013 })?;
1014 if let Some(verdict) = contains_flat_object(lhs_text, &rhs_doc) {
1023 return Ok(Value::Bool(verdict));
1024 }
1025 let lhs_doc = parse(lhs_text).map_err(|e| EvalError::TypeMismatch {
1026 detail: alloc::format!("invalid JSON on left of @>: {e}"),
1027 })?;
1028 let result = match (&lhs_doc, &rhs_doc) {
1035 (JsonValue::Array(items), scalar)
1036 if !matches!(scalar, JsonValue::Array(_) | JsonValue::Object(_)) =>
1037 {
1038 items.iter().any(|it| json_eq(it, scalar))
1039 }
1040 _ => json_contains(&lhs_doc, &rhs_doc),
1041 };
1042 Ok(Value::Bool(result))
1043}
1044
1045fn contains_flat_object(lhs_text: &str, rhs_doc: &JsonValue) -> Option<bool> {
1061 let JsonValue::Object(members) = rhs_doc else {
1062 return None;
1063 };
1064 if members
1065 .iter()
1066 .any(|(_, v)| matches!(v, JsonValue::Object(_) | JsonValue::Array(_)))
1067 {
1068 return None;
1069 }
1070 let b = lhs_text.as_bytes();
1071 if b.get(skip_ws_at(b, 0)) != Some(&b'{') {
1072 return None;
1073 }
1074 for (key, want) in members {
1075 let Some(slice) = locate_member(lhs_text, key) else {
1076 return Some(false);
1077 };
1078 let got = parse(slice).ok()?;
1079 if !json_eq(&got, want) {
1080 return Some(false);
1081 }
1082 }
1083 Some(true)
1084}
1085
1086pub fn equals(lhs: &Value, rhs: &Value) -> Result<bool, EvalError> {
1094 let lhs_text = match lhs {
1095 Value::Json(s) | Value::Text(s) => s.as_ref(),
1096 other => {
1097 return Err(EvalError::TypeMismatch {
1098 detail: alloc::format!(
1099 "jsonb =: left side must be JSON or TEXT, got {}",
1100 crate::conversions::pg_type_name_for_error_opt(other.data_type())
1101 ),
1102 });
1103 }
1104 };
1105 let rhs_text = match rhs {
1106 Value::Json(s) | Value::Text(s) => s.as_ref(),
1107 other => {
1108 return Err(EvalError::TypeMismatch {
1109 detail: alloc::format!(
1110 "jsonb =: right side must be JSON or TEXT, got {}",
1111 crate::conversions::pg_type_name_for_error_opt(other.data_type())
1112 ),
1113 });
1114 }
1115 };
1116 let lhs_doc = parse(lhs_text).map_err(|e| EvalError::TypeMismatch {
1117 detail: alloc::format!("invalid JSON on left of =: {e}"),
1118 })?;
1119 let rhs_doc = parse(rhs_text).map_err(|e| EvalError::TypeMismatch {
1120 detail: alloc::format!("invalid JSON on right of =: {e}"),
1121 })?;
1122 Ok(json_eq(&lhs_doc, &rhs_doc))
1123}
1124
1125fn json_contains(lhs: &JsonValue, rhs: &JsonValue) -> bool {
1126 match (lhs, rhs) {
1127 (JsonValue::Object(l), JsonValue::Object(r)) => r
1128 .iter()
1129 .all(|(rk, rv)| l.iter().any(|(lk, lv)| lk == rk && json_contains(lv, rv))),
1130 (JsonValue::Array(l), JsonValue::Array(r)) => {
1131 r.iter().all(|rv| l.iter().any(|lv| json_contains(lv, rv)))
1132 }
1133 _ => json_eq(lhs, rhs),
1134 }
1135}
1136
1137fn json_eq(a: &JsonValue, b: &JsonValue) -> bool {
1138 match (a, b) {
1139 (JsonValue::Null, JsonValue::Null) => true,
1140 (JsonValue::Bool(x), JsonValue::Bool(y)) => x == y,
1141 (JsonValue::String(x), JsonValue::String(y)) => x == y,
1142 (
1147 JsonValue::Number(_) | JsonValue::NumberText(_),
1148 JsonValue::Number(_) | JsonValue::NumberText(_),
1149 ) => json_number_key(a) == json_number_key(b),
1150 (JsonValue::Array(x), JsonValue::Array(y)) => {
1151 x.len() == y.len() && x.iter().zip(y).all(|(a, b)| json_eq(a, b))
1152 }
1153 (JsonValue::Object(x), JsonValue::Object(y)) => {
1154 x.len() == y.len()
1155 && x.iter()
1156 .all(|(k, v)| y.iter().any(|(k2, v2)| k == k2 && json_eq(v, v2)))
1157 }
1158 _ => false,
1159 }
1160}
1161
1162fn numeric_eq_key(lexeme: &str) -> String {
1167 let c = canon_json_number(lexeme);
1168 if c.contains('.') {
1169 c.trim_end_matches('0').trim_end_matches('.').to_string()
1170 } else {
1171 c.into_owned()
1172 }
1173}
1174
1175fn json_number_key(v: &JsonValue) -> Option<String> {
1176 match v {
1177 JsonValue::NumberText(s) => Some(numeric_eq_key(s)),
1178 JsonValue::Number(x) => Some(numeric_eq_key(&alloc::format!("{x}"))),
1179 _ => None,
1180 }
1181}
1182
1183fn parse_text_array(s: &str) -> Result<Vec<String>, EvalError> {
1188 let trimmed = s.trim();
1189 let inner = if let Some(stripped) = trimmed.strip_prefix('{').and_then(|s| s.strip_suffix('}'))
1190 {
1191 stripped
1192 } else {
1193 return Err(EvalError::TypeMismatch {
1194 detail: alloc::format!("path walk: expected PG array literal `{{…}}`, got {s:?}"),
1195 });
1196 };
1197 if inner.trim().is_empty() {
1198 return Ok(Vec::new());
1199 }
1200 let mut out = Vec::new();
1201 let mut cur = String::new();
1202 let mut in_quotes = false;
1203 let mut chars = inner.chars().peekable();
1204 while let Some(c) = chars.next() {
1205 match c {
1206 '"' => in_quotes = !in_quotes,
1207 ',' if !in_quotes => {
1208 out.push(cur.trim().to_string());
1209 cur = String::new();
1210 }
1211 '\\' => {
1212 if let Some(&next) = chars.peek() {
1213 cur.push(next);
1214 chars.next();
1215 }
1216 }
1217 _ => cur.push(c),
1218 }
1219 }
1220 out.push(cur.trim().to_string());
1221 Ok(out)
1222}
1223
1224fn validate_unless_known_json(lhs: &Value, src: &str, what: &str) -> Result<(), EvalError> {
1244 if matches!(lhs, Value::Json(_)) {
1245 return Ok(());
1246 }
1247 parse(src).map(|_| ()).map_err(|e| EvalError::TypeMismatch {
1248 detail: alloc::format!("invalid JSON for {what}: {e}"),
1249 })
1250}
1251
1252pub fn path_get(lhs: &Value, rhs: &Value, as_text: bool) -> Result<Value<'static>, EvalError> {
1253 let src = match lhs {
1254 Value::Json(s) | Value::Text(s) => s.as_ref(),
1255 Value::Null => return Ok(Value::Null),
1256 other => {
1257 return Err(EvalError::TypeMismatch {
1258 detail: alloc::format!(
1259 "JSON path operator: left side must be JSON or TEXT, got {}",
1260 crate::conversions::pg_type_name_for_error_opt(other.data_type())
1261 ),
1262 });
1263 }
1264 };
1265 validate_unless_known_json(lhs, src, "path access")?;
1268 let located = match rhs {
1269 Value::Text(k) => locate_member(src, k),
1270 Value::Int(idx) => locate_index(src, i64::from(*idx)),
1271 Value::BigInt(idx) => locate_index(src, *idx),
1272 Value::Null => return Ok(Value::Null),
1273 _ => None,
1274 };
1275 Ok(located.map_or(Value::Null, |slice| {
1276 verbatim_accessor_result(slice, as_text)
1277 }))
1278}
1279
1280#[derive(Debug)]
1283pub enum ParseError {
1284 Unexpected(char, usize),
1285 Truncated,
1286 InvalidEscape(usize),
1287 InvalidNumber(usize),
1288}
1289
1290impl core::fmt::Display for ParseError {
1291 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1292 match self {
1293 Self::Unexpected(c, p) => write!(f, "unexpected {c:?} at offset {p}"),
1294 Self::Truncated => f.write_str("unexpected end of JSON input"),
1295 Self::InvalidEscape(p) => write!(f, "invalid string escape at offset {p}"),
1296 Self::InvalidNumber(p) => write!(f, "invalid number at offset {p}"),
1297 }
1298 }
1299}
1300
1301pub fn parse(src: &str) -> Result<JsonValue, ParseError> {
1302 let bytes = src.as_bytes();
1303 let mut p = 0;
1304 skip_ws(bytes, &mut p);
1305 let value = parse_value(bytes, &mut p)?;
1306 skip_ws(bytes, &mut p);
1307 if p != bytes.len() {
1308 return Err(ParseError::Unexpected(bytes[p] as char, p));
1309 }
1310 Ok(value)
1311}
1312
1313#[must_use]
1320pub fn jsonb_compare(a: &JsonValue, b: &JsonValue) -> core::cmp::Ordering {
1321 use core::cmp::Ordering;
1322 fn rank(v: &JsonValue) -> u8 {
1323 match v {
1324 JsonValue::Null => 0,
1325 JsonValue::String(_) => 1,
1326 JsonValue::Number(_) | JsonValue::NumberText(_) => 2,
1327 JsonValue::Bool(_) => 3,
1328 JsonValue::Array(_) => 4,
1329 JsonValue::Object(_) => 5,
1330 }
1331 }
1332 fn num(v: &JsonValue) -> f64 {
1333 match v {
1334 JsonValue::Number(x) => *x,
1335 JsonValue::NumberText(s) => s.parse::<f64>().unwrap_or(0.0),
1336 _ => 0.0,
1337 }
1338 }
1339 let (ra, rb) = (rank(a), rank(b));
1340 if ra != rb {
1341 return ra.cmp(&rb);
1342 }
1343 match (a, b) {
1344 (JsonValue::String(x), JsonValue::String(y)) => x.cmp(y),
1345 (JsonValue::Bool(x), JsonValue::Bool(y)) => x.cmp(y),
1346 (
1347 JsonValue::Number(_) | JsonValue::NumberText(_),
1348 JsonValue::Number(_) | JsonValue::NumberText(_),
1349 ) => num(a).partial_cmp(&num(b)).unwrap_or(Ordering::Equal),
1350 (JsonValue::Array(x), JsonValue::Array(y)) => x.len().cmp(&y.len()).then_with(|| {
1351 x.iter()
1352 .zip(y.iter())
1353 .map(|(ea, eb)| jsonb_compare(ea, eb))
1354 .find(|o| *o != Ordering::Equal)
1355 .unwrap_or(Ordering::Equal)
1356 }),
1357 (JsonValue::Object(x), JsonValue::Object(y)) => x.len().cmp(&y.len()).then_with(|| {
1358 x.iter()
1359 .zip(y.iter())
1360 .map(|((ka, va), (kb, vb))| ka.cmp(kb).then_with(|| jsonb_compare(va, vb)))
1361 .find(|o| *o != Ordering::Equal)
1362 .unwrap_or(Ordering::Equal)
1363 }),
1364 _ => Ordering::Equal,
1366 }
1367}
1368
1369fn skip_ws(bytes: &[u8], p: &mut usize) {
1370 while *p < bytes.len() && matches!(bytes[*p], b' ' | b'\t' | b'\n' | b'\r') {
1371 *p += 1;
1372 }
1373}
1374
1375fn parse_value(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
1376 skip_ws(bytes, p);
1377 if *p >= bytes.len() {
1378 return Err(ParseError::Truncated);
1379 }
1380 match bytes[*p] {
1381 b'{' => parse_object(bytes, p),
1382 b'[' => parse_array(bytes, p),
1383 b'"' => parse_string(bytes, p).map(JsonValue::String),
1384 b't' | b'f' => parse_bool(bytes, p),
1385 b'n' => parse_null(bytes, p),
1386 b'-' | b'0'..=b'9' => parse_number(bytes, p),
1387 c => Err(ParseError::Unexpected(c as char, *p)),
1388 }
1389}
1390
1391fn parse_object(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
1392 debug_assert_eq!(bytes[*p], b'{');
1393 *p += 1;
1394 let mut entries = Vec::new();
1395 skip_ws(bytes, p);
1396 if *p < bytes.len() && bytes[*p] == b'}' {
1397 *p += 1;
1398 return Ok(JsonValue::Object(entries));
1399 }
1400 loop {
1401 skip_ws(bytes, p);
1402 if *p >= bytes.len() || bytes[*p] != b'"' {
1403 return Err(ParseError::Unexpected(
1404 bytes.get(*p).copied().unwrap_or(0) as char,
1405 *p,
1406 ));
1407 }
1408 let key = parse_string(bytes, p)?;
1409 skip_ws(bytes, p);
1410 if *p >= bytes.len() || bytes[*p] != b':' {
1411 return Err(ParseError::Unexpected(
1412 bytes.get(*p).copied().unwrap_or(0) as char,
1413 *p,
1414 ));
1415 }
1416 *p += 1;
1417 let value = parse_value(bytes, p)?;
1418 entries.push((key, value));
1419 skip_ws(bytes, p);
1420 if *p >= bytes.len() {
1421 return Err(ParseError::Truncated);
1422 }
1423 match bytes[*p] {
1424 b',' => {
1425 *p += 1;
1426 continue;
1427 }
1428 b'}' => {
1429 *p += 1;
1430 return Ok(JsonValue::Object(entries));
1431 }
1432 c => return Err(ParseError::Unexpected(c as char, *p)),
1433 }
1434 }
1435}
1436
1437fn parse_array(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
1438 debug_assert_eq!(bytes[*p], b'[');
1439 *p += 1;
1440 let mut items = Vec::new();
1441 skip_ws(bytes, p);
1442 if *p < bytes.len() && bytes[*p] == b']' {
1443 *p += 1;
1444 return Ok(JsonValue::Array(items));
1445 }
1446 loop {
1447 items.push(parse_value(bytes, p)?);
1448 skip_ws(bytes, p);
1449 if *p >= bytes.len() {
1450 return Err(ParseError::Truncated);
1451 }
1452 match bytes[*p] {
1453 b',' => {
1454 *p += 1;
1455 continue;
1456 }
1457 b']' => {
1458 *p += 1;
1459 return Ok(JsonValue::Array(items));
1460 }
1461 c => return Err(ParseError::Unexpected(c as char, *p)),
1462 }
1463 }
1464}
1465
1466fn parse_string(bytes: &[u8], p: &mut usize) -> Result<String, ParseError> {
1467 debug_assert_eq!(bytes[*p], b'"');
1468 *p += 1;
1469 let mut out = String::new();
1470 while *p < bytes.len() {
1471 match bytes[*p] {
1472 b'"' => {
1473 *p += 1;
1474 return Ok(out);
1475 }
1476 b'\\' => {
1477 let start = *p;
1478 *p += 1;
1479 if *p >= bytes.len() {
1480 return Err(ParseError::Truncated);
1481 }
1482 match bytes[*p] {
1483 b'"' => {
1484 out.push('"');
1485 *p += 1;
1486 }
1487 b'\\' => {
1488 out.push('\\');
1489 *p += 1;
1490 }
1491 b'/' => {
1492 out.push('/');
1493 *p += 1;
1494 }
1495 b'b' => {
1496 out.push('\u{08}');
1497 *p += 1;
1498 }
1499 b'f' => {
1500 out.push('\u{0c}');
1501 *p += 1;
1502 }
1503 b'n' => {
1504 out.push('\n');
1505 *p += 1;
1506 }
1507 b'r' => {
1508 out.push('\r');
1509 *p += 1;
1510 }
1511 b't' => {
1512 out.push('\t');
1513 *p += 1;
1514 }
1515 b'u' => {
1516 if *p + 5 > bytes.len() {
1517 return Err(ParseError::Truncated);
1518 }
1519 let hex = &bytes[*p + 1..*p + 5];
1520 let n = u32::from_str_radix(
1521 core::str::from_utf8(hex)
1522 .map_err(|_| ParseError::InvalidEscape(start))?,
1523 16,
1524 )
1525 .map_err(|_| ParseError::InvalidEscape(start))?;
1526 out.push(char::from_u32(n).ok_or(ParseError::InvalidEscape(start))?);
1527 *p += 5;
1528 }
1529 _ => return Err(ParseError::InvalidEscape(start)),
1530 }
1531 }
1532 c if c < 0x20 => return Err(ParseError::Unexpected(c as char, *p)),
1533 _ => {
1534 let s = core::str::from_utf8(&bytes[*p..])
1536 .map_err(|_| ParseError::Unexpected(bytes[*p] as char, *p))?;
1537 let c = s.chars().next().unwrap();
1538 out.push(c);
1539 *p += c.len_utf8();
1540 }
1541 }
1542 }
1543 Err(ParseError::Truncated)
1544}
1545
1546fn parse_bool(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
1547 if bytes[*p..].starts_with(b"true") {
1548 *p += 4;
1549 Ok(JsonValue::Bool(true))
1550 } else if bytes[*p..].starts_with(b"false") {
1551 *p += 5;
1552 Ok(JsonValue::Bool(false))
1553 } else {
1554 Err(ParseError::Unexpected(bytes[*p] as char, *p))
1555 }
1556}
1557
1558fn parse_null(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
1559 if bytes[*p..].starts_with(b"null") {
1560 *p += 4;
1561 Ok(JsonValue::Null)
1562 } else {
1563 Err(ParseError::Unexpected(bytes[*p] as char, *p))
1564 }
1565}
1566
1567fn parse_number(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
1568 let start = *p;
1569 if bytes[*p] == b'-' {
1570 *p += 1;
1571 }
1572 while *p < bytes.len() && bytes[*p].is_ascii_digit() {
1573 *p += 1;
1574 }
1575 if *p < bytes.len() && bytes[*p] == b'.' {
1576 *p += 1;
1577 while *p < bytes.len() && bytes[*p].is_ascii_digit() {
1578 *p += 1;
1579 }
1580 }
1581 if *p < bytes.len() && matches!(bytes[*p], b'e' | b'E') {
1582 *p += 1;
1583 if *p < bytes.len() && matches!(bytes[*p], b'+' | b'-') {
1584 *p += 1;
1585 }
1586 while *p < bytes.len() && bytes[*p].is_ascii_digit() {
1587 *p += 1;
1588 }
1589 }
1590 let text = core::str::from_utf8(&bytes[start..*p])
1591 .map_err(|_| ParseError::InvalidNumber(start))?
1592 .to_string();
1593 if text.parse::<f64>().is_err() {
1595 return Err(ParseError::InvalidNumber(start));
1596 }
1597 Ok(JsonValue::NumberText(text))
1598}
1599
1600#[derive(Debug, Clone, Copy)]
1621enum IdxBound {
1622 At(usize),
1623 FromLast(usize),
1624}
1625
1626impl IdxBound {
1627 fn resolve(self, len: usize) -> Option<usize> {
1629 match self {
1630 Self::At(n) => (n < len).then_some(n),
1631 Self::FromLast(off) => len.checked_sub(1 + off),
1632 }
1633 }
1634}
1635
1636#[derive(Debug, Clone, Copy)]
1638enum NumMethod {
1639 Abs,
1640 Floor,
1641 Ceiling,
1642 Double,
1643}
1644
1645#[derive(Debug, Clone)]
1646enum PathStep {
1647 Field(String),
1648 Index(IdxBound),
1649 Wildcard,
1650 Range(IdxBound, IdxBound),
1653 Filter(FilterExpr),
1656 Size,
1658 TypeOf,
1660 Num(NumMethod),
1662 RecursiveAll,
1664}
1665
1666#[derive(Debug, Clone)]
1667struct FilterPred {
1668 path: Vec<String>,
1670 op: FilterOp,
1671 val: FilterVal,
1672 regex_flags: Option<String>,
1676}
1677
1678#[derive(Debug, Clone)]
1680enum FilterExpr {
1681 Cmp(FilterPred),
1682 And(alloc::boxed::Box<FilterExpr>, alloc::boxed::Box<FilterExpr>),
1683 Or(alloc::boxed::Box<FilterExpr>, alloc::boxed::Box<FilterExpr>),
1684}
1685
1686#[derive(Debug, Clone, Copy)]
1687enum FilterOp {
1688 Gt,
1689 Lt,
1690 Ge,
1691 Le,
1692 Eq,
1693 Ne,
1694 StartsWith,
1696 LikeRegex,
1698}
1699
1700#[derive(Debug, Clone)]
1701enum FilterVal {
1702 Num(f64),
1703 Str(String),
1704 Bool(bool),
1705 Null,
1707 Var(String),
1710}
1711
1712fn parse_jsonpath_mode(p: &str) -> Result<(bool, Vec<PathStep>), EvalError> {
1717 let trimmed = p.trim_start();
1718 let (strict, p) = if let Some(rest) = trimmed.strip_prefix("strict") {
1719 (true, rest.trim_start())
1720 } else if let Some(rest) = trimmed.strip_prefix("lax") {
1721 (false, rest.trim_start())
1722 } else {
1723 (false, trimmed)
1724 };
1725 let chars: Vec<char> = p.chars().collect();
1726 let mut i = 0;
1727 if i >= chars.len() || chars[i] != '$' {
1728 return Err(EvalError::TypeMismatch {
1729 detail: alloc::format!("jsonpath must start with '$', got {p:?}"),
1730 });
1731 }
1732 i += 1;
1733 let mut steps: Vec<PathStep> = Vec::new();
1734 while i < chars.len() {
1735 match chars[i] {
1736 '.' => {
1737 i += 1;
1738 if i + 1 < chars.len() && chars[i] == '*' && chars[i + 1] == '*' {
1741 i += 2;
1742 steps.push(PathStep::RecursiveAll);
1743 continue;
1744 }
1745 if i < chars.len() && chars[i] == '"' {
1746 i += 1;
1747 let start = i;
1748 while i < chars.len() && chars[i] != '"' {
1749 i += 1;
1750 }
1751 if i >= chars.len() {
1752 return Err(EvalError::TypeMismatch {
1753 detail: "jsonpath: unterminated quoted field".into(),
1754 });
1755 }
1756 steps.push(PathStep::Field(chars[start..i].iter().collect()));
1757 i += 1;
1758 } else {
1759 let start = i;
1760 while i < chars.len()
1761 && chars[i] != '.'
1762 && chars[i] != '['
1763 && chars[i] != '('
1764 && !chars[i].is_whitespace()
1765 {
1766 i += 1;
1767 }
1768 if start == i {
1769 return Err(EvalError::TypeMismatch {
1770 detail: "jsonpath: missing field name after '.'".into(),
1771 });
1772 }
1773 let name: String = chars[start..i].iter().collect();
1774 if i < chars.len() && chars[i] == '(' {
1776 i += 1;
1777 while i < chars.len() && chars[i] != ')' {
1778 i += 1;
1779 }
1780 if i >= chars.len() {
1781 return Err(EvalError::TypeMismatch {
1782 detail: "jsonpath: unterminated method call".into(),
1783 });
1784 }
1785 i += 1; match name.as_str() {
1787 "size" => steps.push(PathStep::Size),
1788 "type" => steps.push(PathStep::TypeOf),
1789 "abs" => steps.push(PathStep::Num(NumMethod::Abs)),
1791 "floor" => steps.push(PathStep::Num(NumMethod::Floor)),
1792 "ceiling" => steps.push(PathStep::Num(NumMethod::Ceiling)),
1793 "double" => steps.push(PathStep::Num(NumMethod::Double)),
1794 other => {
1795 return Err(EvalError::TypeMismatch {
1796 detail: alloc::format!(
1797 "jsonpath: unsupported method .{other}()"
1798 ),
1799 });
1800 }
1801 }
1802 } else {
1803 steps.push(PathStep::Field(name));
1804 }
1805 }
1806 }
1807 '?' => {
1808 i += 1;
1810 let (pred, ni) = parse_filter_pred(&chars, i)?;
1811 i = ni;
1812 steps.push(PathStep::Filter(pred));
1813 }
1814 '[' => {
1815 i += 1;
1816 if i < chars.len() && chars[i] == '*' {
1817 i += 1;
1818 if i >= chars.len() || chars[i] != ']' {
1819 return Err(EvalError::TypeMismatch {
1820 detail: "jsonpath: expected ']' after '[*'".into(),
1821 });
1822 }
1823 i += 1;
1824 steps.push(PathStep::Wildcard);
1825 } else {
1826 let mut parse_bound = |i: &mut usize| -> Result<IdxBound, EvalError> {
1828 jp_skip_ws(&chars, i);
1829 if chars[*i..].starts_with(&['l', 'a', 's', 't']) {
1830 *i += 4;
1831 jp_skip_ws(&chars, i);
1832 if *i < chars.len() && chars[*i] == '-' {
1833 *i += 1;
1834 jp_skip_ws(&chars, i);
1835 let s = *i;
1836 while *i < chars.len() && chars[*i].is_ascii_digit() {
1837 *i += 1;
1838 }
1839 let off: usize =
1840 chars[s..*i].iter().collect::<String>().parse().map_err(
1841 |_| EvalError::TypeMismatch {
1842 detail: "jsonpath: invalid `last - N` offset".into(),
1843 },
1844 )?;
1845 return Ok(IdxBound::FromLast(off));
1846 }
1847 return Ok(IdxBound::FromLast(0));
1848 }
1849 let s = *i;
1850 while *i < chars.len() && chars[*i].is_ascii_digit() {
1851 *i += 1;
1852 }
1853 if s == *i {
1854 return Err(EvalError::TypeMismatch {
1855 detail: "jsonpath: expected `N`, `last[ - K]` or `*` subscript"
1856 .into(),
1857 });
1858 }
1859 Ok(IdxBound::At(
1860 chars[s..*i]
1861 .iter()
1862 .collect::<String>()
1863 .parse()
1864 .map_err(|_| EvalError::TypeMismatch {
1865 detail: "jsonpath: invalid array index".into(),
1866 })?,
1867 ))
1868 };
1869 let idx = parse_bound(&mut i)?;
1870 while i < chars.len() && chars[i].is_whitespace() {
1872 i += 1;
1873 }
1874 if i + 1 < chars.len() && chars[i] == 't' && chars[i + 1] == 'o' {
1875 i += 2;
1876 let hi = parse_bound(&mut i)?;
1877 while i < chars.len() && chars[i].is_whitespace() {
1878 i += 1;
1879 }
1880 if i >= chars.len() || chars[i] != ']' {
1881 return Err(EvalError::TypeMismatch {
1882 detail: "jsonpath: expected ']' after range".into(),
1883 });
1884 }
1885 i += 1;
1886 steps.push(PathStep::Range(idx, hi));
1887 } else {
1888 if i >= chars.len() || chars[i] != ']' {
1889 return Err(EvalError::TypeMismatch {
1890 detail: "jsonpath: expected ']' after array index".into(),
1891 });
1892 }
1893 i += 1;
1894 steps.push(PathStep::Index(idx));
1895 }
1896 }
1897 }
1898 c if c.is_whitespace() => {
1899 i += 1;
1900 }
1901 c => {
1902 return Err(EvalError::TypeMismatch {
1903 detail: alloc::format!(
1904 "jsonpath: unexpected char '{c}' (supports `$.field`, `[N]`, `[N to M]`, `[*]`, `? (...)`, `.size()`, `.type()`)"
1905 ),
1906 });
1907 }
1908 }
1909 }
1910 Ok((strict, steps))
1911}
1912
1913fn parse_jsonpath(p: &str) -> Result<Vec<PathStep>, EvalError> {
1915 parse_jsonpath_mode(p).map(|(_, steps)| steps)
1916}
1917
1918fn jp_skip_ws(chars: &[char], i: &mut usize) {
1919 while *i < chars.len() && chars[*i].is_whitespace() {
1920 *i += 1;
1921 }
1922}
1923
1924fn parse_filter_pred(chars: &[char], mut i: usize) -> Result<(FilterExpr, usize), EvalError> {
1928 let err = |m: &str| EvalError::TypeMismatch {
1929 detail: alloc::format!("jsonpath filter: {m}"),
1930 };
1931 jp_skip_ws(chars, &mut i);
1932 if i >= chars.len() || chars[i] != '(' {
1933 return Err(err("expected '(' after '?'"));
1934 }
1935 i += 1;
1936 let (expr, ni) = parse_filter_or(chars, i)?;
1937 i = ni;
1938 jp_skip_ws(chars, &mut i);
1939 if i >= chars.len() || chars[i] != ')' {
1940 return Err(err("expected ')' to close the filter"));
1941 }
1942 i += 1;
1943 Ok((expr, i))
1944}
1945
1946fn parse_filter_or(chars: &[char], i: usize) -> Result<(FilterExpr, usize), EvalError> {
1948 let (mut left, mut i) = parse_filter_and(chars, i)?;
1949 loop {
1950 jp_skip_ws(chars, &mut i);
1951 if i + 1 < chars.len() && chars[i] == '|' && chars[i + 1] == '|' {
1952 i += 2;
1953 let (right, ni) = parse_filter_and(chars, i)?;
1954 i = ni;
1955 left = FilterExpr::Or(alloc::boxed::Box::new(left), alloc::boxed::Box::new(right));
1956 } else {
1957 return Ok((left, i));
1958 }
1959 }
1960}
1961
1962fn parse_filter_and(chars: &[char], i: usize) -> Result<(FilterExpr, usize), EvalError> {
1964 let (mut left, mut i) = parse_filter_atom(chars, i)?;
1965 loop {
1966 jp_skip_ws(chars, &mut i);
1967 if i + 1 < chars.len() && chars[i] == '&' && chars[i + 1] == '&' {
1968 i += 2;
1969 let (right, ni) = parse_filter_atom(chars, i)?;
1970 i = ni;
1971 left = FilterExpr::And(alloc::boxed::Box::new(left), alloc::boxed::Box::new(right));
1972 } else {
1973 return Ok((left, i));
1974 }
1975 }
1976}
1977
1978fn parse_filter_atom(chars: &[char], mut i: usize) -> Result<(FilterExpr, usize), EvalError> {
1980 let err = |m: &str| EvalError::TypeMismatch {
1981 detail: alloc::format!("jsonpath filter: {m}"),
1982 };
1983 jp_skip_ws(chars, &mut i);
1984 if i < chars.len() && chars[i] == '(' {
1985 i += 1;
1986 let (expr, ni) = parse_filter_or(chars, i)?;
1987 i = ni;
1988 jp_skip_ws(chars, &mut i);
1989 if i >= chars.len() || chars[i] != ')' {
1990 return Err(err("expected ')' in grouped predicate"));
1991 }
1992 i += 1;
1993 return Ok((expr, i));
1994 }
1995 if i >= chars.len() || chars[i] != '@' {
1996 return Err(err("only `@`-based predicates are supported"));
1997 }
1998 i += 1;
1999 let mut path: Vec<String> = Vec::new();
2000 while i < chars.len() && chars[i] == '.' {
2001 i += 1;
2002 let start = i;
2003 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
2004 i += 1;
2005 }
2006 path.push(chars[start..i].iter().collect());
2007 }
2008 let (op, val, regex_flags, ni) = parse_cmp_and_literal(chars, i)?;
2009 i = ni;
2010 Ok((
2011 FilterExpr::Cmp(FilterPred {
2012 path,
2013 op,
2014 val,
2015 regex_flags,
2016 }),
2017 i,
2018 ))
2019}
2020
2021fn parse_cmp_and_literal(
2025 chars: &[char],
2026 mut i: usize,
2027) -> Result<(FilterOp, FilterVal, Option<String>, usize), EvalError> {
2028 let err = |m: &str| EvalError::TypeMismatch {
2029 detail: alloc::format!("jsonpath predicate: {m}"),
2030 };
2031 while i < chars.len() && chars[i].is_whitespace() {
2032 i += 1;
2033 }
2034 let kw = |i: usize, w: &str| -> bool {
2035 let wc: Vec<char> = w.chars().collect();
2036 chars[i..].starts_with(&wc)
2037 };
2038 let op = if i + 1 < chars.len() && chars[i] == '>' && chars[i + 1] == '=' {
2039 i += 2;
2040 FilterOp::Ge
2041 } else if i + 1 < chars.len() && chars[i] == '<' && chars[i + 1] == '=' {
2042 i += 2;
2043 FilterOp::Le
2044 } else if i + 1 < chars.len() && chars[i] == '=' && chars[i + 1] == '=' {
2045 i += 2;
2046 FilterOp::Eq
2047 } else if i + 1 < chars.len() && chars[i] == '!' && chars[i + 1] == '=' {
2048 i += 2;
2049 FilterOp::Ne
2050 } else if i < chars.len() && chars[i] == '>' {
2051 i += 1;
2052 FilterOp::Gt
2053 } else if i < chars.len() && chars[i] == '<' {
2054 i += 1;
2055 FilterOp::Lt
2056 } else if kw(i, "starts") {
2058 i += 6;
2059 while i < chars.len() && chars[i].is_whitespace() {
2060 i += 1;
2061 }
2062 if !kw(i, "with") {
2063 return Err(err("expected `with` after `starts`"));
2064 }
2065 i += 4;
2066 FilterOp::StartsWith
2067 } else if kw(i, "like_regex") {
2068 i += 10;
2069 FilterOp::LikeRegex
2070 } else {
2071 return Err(err(
2072 "expected a comparison operator (> < >= <= == != starts with like_regex)",
2073 ));
2074 };
2075 while i < chars.len() && chars[i].is_whitespace() {
2076 i += 1;
2077 }
2078 let val = if i < chars.len() && chars[i] == '"' {
2079 i += 1;
2080 let start = i;
2081 while i < chars.len() && chars[i] != '"' {
2082 i += 1;
2083 }
2084 if i >= chars.len() {
2085 return Err(err("unterminated string literal"));
2086 }
2087 let s: String = chars[start..i].iter().collect();
2088 i += 1;
2089 FilterVal::Str(s)
2090 } else if chars[i..].starts_with(&['t', 'r', 'u', 'e']) {
2091 i += 4;
2092 FilterVal::Bool(true)
2093 } else if chars[i..].starts_with(&['f', 'a', 'l', 's', 'e']) {
2094 i += 5;
2095 FilterVal::Bool(false)
2096 } else if chars[i..].starts_with(&['n', 'u', 'l', 'l']) {
2098 i += 4;
2099 FilterVal::Null
2100 } else if i < chars.len() && chars[i] == '$' {
2101 i += 1;
2102 let start = i;
2103 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
2104 i += 1;
2105 }
2106 if start == i {
2107 return Err(err("expected a variable name after '$'"));
2108 }
2109 FilterVal::Var(chars[start..i].iter().collect())
2110 } else {
2111 let start = i;
2112 if i < chars.len() && (chars[i] == '-' || chars[i] == '+') {
2113 i += 1;
2114 }
2115 while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '.') {
2116 i += 1;
2117 }
2118 let num: f64 = chars[start..i]
2119 .iter()
2120 .collect::<String>()
2121 .parse()
2122 .map_err(|_| err("invalid numeric literal"))?;
2123 FilterVal::Num(num)
2124 };
2125 let mut flags: Option<String> = None;
2128 if matches!(op, FilterOp::LikeRegex) {
2129 let mut j = i;
2130 while j < chars.len() && chars[j].is_whitespace() {
2131 j += 1;
2132 }
2133 if chars[j..].starts_with(&['f', 'l', 'a', 'g']) {
2134 j += 4;
2135 while j < chars.len() && chars[j].is_whitespace() {
2136 j += 1;
2137 }
2138 if j < chars.len() && chars[j] == '"' {
2139 j += 1;
2140 let start = j;
2141 while j < chars.len() && chars[j] != '"' {
2142 j += 1;
2143 }
2144 if j < chars.len() {
2145 flags = Some(chars[start..j].iter().collect());
2146 j += 1;
2147 i = j;
2148 }
2149 }
2150 }
2151 }
2152 Ok((op, val, flags, i))
2153}
2154
2155fn json_type_name(v: &JsonValue) -> &'static str {
2157 match v {
2158 JsonValue::Null => "null",
2159 JsonValue::Bool(_) => "boolean",
2160 JsonValue::Number(_) | JsonValue::NumberText(_) => "number",
2161 JsonValue::String(_) => "string",
2162 JsonValue::Array(_) => "array",
2163 JsonValue::Object(_) => "object",
2164 }
2165}
2166
2167fn json_num(v: &JsonValue) -> Option<f64> {
2169 match v {
2170 JsonValue::Number(n) => Some(*n),
2171 JsonValue::NumberText(s) => s.parse().ok(),
2172 _ => None,
2173 }
2174}
2175
2176fn resolve_accessor<'a>(node: &'a JsonValue, path: &[String]) -> Option<&'a JsonValue> {
2178 let mut cur = node;
2179 for key in path {
2180 match cur {
2181 JsonValue::Object(entries) => {
2182 cur = &entries.iter().find(|(k, _)| k == key)?.1;
2183 }
2184 _ => return None,
2185 }
2186 }
2187 Some(cur)
2188}
2189
2190fn filter_matches(node: &JsonValue, pred: &FilterPred, vars: Option<&JsonValue>) -> bool {
2194 let Some(target) = resolve_accessor(node, &pred.path) else {
2195 return false;
2196 };
2197 let resolved;
2199 let val = match &pred.val {
2200 FilterVal::Var(name) => {
2201 let Some(JsonValue::Object(entries)) = vars else {
2202 return false;
2203 };
2204 let Some((_, v)) = entries.iter().find(|(k, _)| k == name) else {
2205 return false;
2206 };
2207 resolved = match v {
2208 JsonValue::Number(n) => FilterVal::Num(*n),
2209 JsonValue::NumberText(s) => match s.parse::<f64>() {
2210 Ok(n) => FilterVal::Num(n),
2211 Err(_) => return false,
2212 },
2213 JsonValue::String(s) => FilterVal::Str(s.clone()),
2214 JsonValue::Bool(b) => FilterVal::Bool(*b),
2215 JsonValue::Null => FilterVal::Null,
2216 _ => return false,
2217 };
2218 &resolved
2219 }
2220 other => other,
2221 };
2222 match val {
2223 FilterVal::Num(rhs) => match json_num(target) {
2224 Some(lhs) => match pred.op {
2225 FilterOp::Gt => lhs > *rhs,
2226 FilterOp::Lt => lhs < *rhs,
2227 FilterOp::Ge => lhs >= *rhs,
2228 FilterOp::Le => lhs <= *rhs,
2229 FilterOp::Eq => lhs == *rhs,
2230 FilterOp::Ne => lhs != *rhs,
2231 FilterOp::StartsWith | FilterOp::LikeRegex => false,
2232 },
2233 None => false,
2234 },
2235 FilterVal::Str(rhs) => match target {
2236 JsonValue::String(lhs) => match pred.op {
2237 FilterOp::Eq => lhs == rhs,
2238 FilterOp::Ne => lhs != rhs,
2239 FilterOp::Gt => lhs.as_str() > rhs.as_str(),
2240 FilterOp::Lt => lhs.as_str() < rhs.as_str(),
2241 FilterOp::Ge => lhs.as_str() >= rhs.as_str(),
2242 FilterOp::Le => lhs.as_str() <= rhs.as_str(),
2243 FilterOp::StartsWith => lhs.starts_with(rhs.as_str()),
2245 FilterOp::LikeRegex => {
2246 if pred.regex_flags.as_deref().is_some_and(|f| f.contains('i')) {
2249 crate::eval::regex_is_match(&rhs.to_lowercase(), &lhs.to_lowercase())
2250 .unwrap_or(false)
2251 } else {
2252 crate::eval::regex_is_match(rhs, lhs).unwrap_or(false)
2253 }
2254 }
2255 },
2256 _ => false,
2257 },
2258 FilterVal::Bool(rhs) => match target {
2259 JsonValue::Bool(lhs) => match pred.op {
2260 FilterOp::Eq => lhs == rhs,
2261 FilterOp::Ne => lhs != rhs,
2262 _ => false,
2263 },
2264 _ => false,
2265 },
2266 FilterVal::Null => match pred.op {
2268 FilterOp::Eq => matches!(target, JsonValue::Null),
2269 FilterOp::Ne => !matches!(target, JsonValue::Null),
2270 _ => false,
2271 },
2272 FilterVal::Var(_) => false, }
2274}
2275
2276fn filter_expr_matches(node: &JsonValue, expr: &FilterExpr, vars: Option<&JsonValue>) -> bool {
2278 match expr {
2279 FilterExpr::Cmp(pred) => filter_matches(node, pred, vars),
2280 FilterExpr::And(a, b) => {
2281 filter_expr_matches(node, a, vars) && filter_expr_matches(node, b, vars)
2282 }
2283 FilterExpr::Or(a, b) => {
2284 filter_expr_matches(node, a, vars) || filter_expr_matches(node, b, vars)
2285 }
2286 }
2287}
2288
2289fn apply_jsonpath(
2291 root: &JsonValue,
2292 steps: &[PathStep],
2293 vars: Option<&JsonValue>,
2294) -> Vec<JsonValue> {
2295 apply_jsonpath_mode(root, steps, vars, false).unwrap_or_default()
2296}
2297
2298fn apply_jsonpath_mode(
2312 root: &JsonValue,
2313 steps: &[PathStep],
2314 vars: Option<&JsonValue>,
2315 strict: bool,
2316) -> Result<Vec<JsonValue>, EvalError> {
2317 let err = |m: alloc::string::String| Err(EvalError::TypeMismatch { detail: m });
2318 let mut cur: Vec<JsonValue> = alloc::vec![root.clone()];
2319 for step in steps {
2320 if !strict {
2322 match step {
2323 PathStep::Field(_) => {
2325 let mut flat: Vec<JsonValue> = Vec::new();
2326 for node in cur {
2327 match node {
2328 JsonValue::Array(items) => flat.extend(items),
2329 other => flat.push(other),
2330 }
2331 }
2332 cur = flat;
2333 }
2334 PathStep::Wildcard | PathStep::Index(_) | PathStep::Range(..) => {
2336 cur = cur
2337 .into_iter()
2338 .map(|n| match n {
2339 arr @ JsonValue::Array(_) => arr,
2340 other => JsonValue::Array(alloc::vec![other]),
2341 })
2342 .collect();
2343 }
2344 _ => {}
2345 }
2346 } else {
2347 for node in &cur {
2349 match step {
2350 PathStep::Field(k) => match node {
2351 JsonValue::Object(entries) => {
2352 if !entries.iter().any(|(name, _)| name == k) {
2353 return err(alloc::format!(
2354 "JSON object does not contain key \"{k}\""
2355 ));
2356 }
2357 }
2358 _ => {
2359 return err(
2360 "jsonpath member accessor can only be applied to an object".into(),
2361 );
2362 }
2363 },
2364 PathStep::Wildcard => {
2365 if !matches!(node, JsonValue::Array(_)) {
2366 return err(
2367 "jsonpath wildcard array accessor can only be applied to an array"
2368 .into(),
2369 );
2370 }
2371 }
2372 PathStep::Index(idx) => match node {
2373 JsonValue::Array(items) => {
2374 if idx.resolve(items.len()).is_none_or(|p| p >= items.len()) {
2375 return err("jsonpath array subscript is out of bounds".into());
2376 }
2377 }
2378 _ => {
2379 return err(
2380 "jsonpath array accessor can only be applied to an array".into()
2381 );
2382 }
2383 },
2384 PathStep::Range(lo, hi) => match node {
2385 JsonValue::Array(items) => {
2386 let n = items.len();
2387 if lo.resolve(n).is_none_or(|p| p >= n)
2388 || hi.resolve(n).is_none_or(|p| p >= n)
2389 {
2390 return err("jsonpath array subscript is out of bounds".into());
2391 }
2392 }
2393 _ => {
2394 return err(
2395 "jsonpath array accessor can only be applied to an array".into()
2396 );
2397 }
2398 },
2399 _ => {}
2400 }
2401 }
2402 }
2403 let mut next: Vec<JsonValue> = Vec::new();
2404 for node in &cur {
2405 match (step, node) {
2406 (PathStep::Field(k), JsonValue::Object(entries)) => {
2407 if let Some((_, v)) = entries.iter().find(|(name, _)| name == k) {
2408 next.push(v.clone());
2409 }
2410 }
2411 (PathStep::Index(idx), JsonValue::Array(items)) => {
2412 if let Some(pos) = idx.resolve(items.len())
2413 && let Some(v) = items.get(pos)
2414 {
2415 next.push(v.clone());
2416 }
2417 }
2418 (PathStep::Wildcard, JsonValue::Array(items)) => {
2419 next.extend(items.iter().cloned());
2420 }
2421 (PathStep::Range(lo, hi), JsonValue::Array(items)) => {
2423 if let (Some(a), Some(b)) = (lo.resolve(items.len()), hi.resolve(items.len())) {
2424 for idx in a..=b {
2425 if let Some(v) = items.get(idx) {
2426 next.push(v.clone());
2427 }
2428 }
2429 }
2430 }
2431 (PathStep::Filter(expr), node) => {
2432 if filter_expr_matches(node, expr, vars) {
2433 next.push(node.clone());
2434 }
2435 }
2436 (PathStep::Size, JsonValue::Array(items)) => {
2437 next.push(JsonValue::Number(items.len() as f64));
2438 }
2439 (PathStep::Size, _) => next.push(JsonValue::Number(1.0)),
2441 (PathStep::TypeOf, node) => {
2442 next.push(JsonValue::String(json_type_name(node).into()));
2443 }
2444 (PathStep::RecursiveAll, node) => {
2447 fn descend(v: &JsonValue, out: &mut Vec<JsonValue>) {
2448 out.push(v.clone());
2449 match v {
2450 JsonValue::Object(entries) => {
2451 for (_, child) in entries {
2452 descend(child, out);
2453 }
2454 }
2455 JsonValue::Array(items) => {
2456 for child in items {
2457 descend(child, out);
2458 }
2459 }
2460 _ => {}
2461 }
2462 }
2463 descend(node, &mut next);
2464 }
2465 (PathStep::Num(m), node) => {
2467 let n = match m {
2468 NumMethod::Double => match node {
2470 JsonValue::String(s) => s.parse::<f64>().ok(),
2471 other => json_num(other),
2472 },
2473 _ => json_num(node),
2474 };
2475 if let Some(x) = n {
2476 let out = match m {
2477 NumMethod::Abs => x.abs(),
2478 NumMethod::Floor => x.floor(),
2479 NumMethod::Ceiling => x.ceil(),
2480 NumMethod::Double => x,
2481 };
2482 next.push(JsonValue::Number(out));
2483 }
2484 }
2485 _ => {} }
2487 }
2488 cur = next;
2489 if cur.is_empty() {
2490 return Ok(Vec::new());
2491 }
2492 }
2493 Ok(cur)
2494}
2495
2496pub fn path_predicate(doc: &Value, path: &Value) -> Result<Option<bool>, EvalError> {
2501 path_predicate_vars(doc, path, None)
2502}
2503
2504pub fn path_predicate_vars(
2506 doc: &Value,
2507 path: &Value,
2508 vars: Option<&JsonValue>,
2509) -> Result<Option<bool>, EvalError> {
2510 let (src, ptext) = match (doc, path) {
2511 (Value::Null, _) | (_, Value::Null) => return Ok(None),
2512 (Value::Json(s) | Value::Text(s), Value::Text(p) | Value::Json(p)) => (s, p),
2513 _ => return Ok(None),
2514 };
2515 let trimmed = ptext.trim();
2517 if let Some(inner) = trimmed
2518 .strip_prefix("exists")
2519 .map(str::trim_start)
2520 .and_then(|r| r.strip_prefix('('))
2521 .and_then(|r| r.strip_suffix(')'))
2522 {
2523 let (strict, steps) = parse_jsonpath_mode(inner.trim())?;
2524 let root = parse(src).map_err(|e| EvalError::TypeMismatch {
2525 detail: alloc::format!("{e}"),
2526 })?;
2527 return Ok(Some(
2528 !apply_jsonpath_mode(&root, &steps, vars, strict)?.is_empty(),
2529 ));
2530 }
2531 let chars: Vec<char> = ptext.chars().collect();
2532 let mut depth = 0i32;
2535 let mut i = 0;
2536 let mut op_at = None;
2537 while i < chars.len() {
2538 match chars[i] {
2539 '(' | '[' => depth += 1,
2540 ')' | ']' => depth -= 1,
2541 '"' => {
2542 i += 1;
2543 while i < chars.len() && chars[i] != '"' {
2544 i += 1;
2545 }
2546 }
2547 '>' | '<' | '=' | '!' if depth == 0 => {
2548 op_at = Some(i);
2549 break;
2550 }
2551 _ => {}
2552 }
2553 i += 1;
2554 }
2555 let Some(pos) = op_at else { return Ok(None) };
2556 let left: String = chars[..pos].iter().collect();
2557 let (strict, steps) = parse_jsonpath_mode(left.trim())?;
2558 let (op, val, regex_flags, _) = parse_cmp_and_literal(&chars, pos)?;
2559 let root = parse(src).map_err(|e| EvalError::TypeMismatch {
2560 detail: alloc::format!("{e}"),
2561 })?;
2562 let results = apply_jsonpath_mode(&root, &steps, vars, strict)?;
2565 let pred = FilterPred {
2566 path: Vec::new(),
2567 op,
2568 val,
2569 regex_flags,
2570 };
2571 Ok(Some(results.iter().any(|v| filter_matches(v, &pred, vars))))
2572}
2573
2574pub fn path_query(doc: &Value, path: &Value) -> Result<Value<'static>, EvalError> {
2578 path_query_vars(doc, path, None)
2579}
2580
2581pub fn parse_path_vars(v: &Value) -> Result<Option<JsonValue>, EvalError> {
2584 match v {
2585 Value::Null => Ok(None),
2586 Value::Json(s) | Value::Text(s) => {
2587 let parsed = parse(s).map_err(|e| EvalError::TypeMismatch {
2588 detail: alloc::format!("invalid jsonpath vars document: {e}"),
2589 })?;
2590 if !matches!(parsed, JsonValue::Object(_)) {
2591 return Err(EvalError::TypeMismatch {
2592 detail: "jsonpath vars must be a JSON object".into(),
2593 });
2594 }
2595 Ok(Some(parsed))
2596 }
2597 other => Err(EvalError::TypeMismatch {
2598 detail: alloc::format!(
2599 "jsonpath vars must be jsonb, got {}",
2600 crate::conversions::pg_type_name_for_error_opt(other.data_type())
2601 ),
2602 }),
2603 }
2604}
2605
2606pub fn path_query_vars(
2608 doc: &Value,
2609 path: &Value,
2610 vars: Option<&JsonValue>,
2611) -> Result<Value<'static>, EvalError> {
2612 let (src, path_text) = match (doc, path) {
2613 (Value::Null, _) | (_, Value::Null) => return Ok(Value::Null),
2614 (Value::Json(s) | Value::Text(s), Value::Text(p) | Value::Json(p)) => (s, p),
2615 _ => {
2616 return Err(EvalError::TypeMismatch {
2617 detail: "jsonb_path_query() expects (JSON, TEXT)".into(),
2618 });
2619 }
2620 };
2621 let root = parse(src).map_err(|e| EvalError::TypeMismatch {
2622 detail: alloc::format!("invalid JSON for jsonb_path_query: {e}"),
2623 })?;
2624 let trimmed = path_text.trim();
2626 if let Some(inner) = trimmed
2627 .strip_prefix("exists")
2628 .map(str::trim_start)
2629 .and_then(|r| r.strip_prefix('('))
2630 .and_then(|r| r.strip_suffix(')'))
2631 {
2632 let steps = parse_jsonpath(inner.trim())?;
2633 let hit = !apply_jsonpath(&root, &steps, vars).is_empty();
2634 return Ok(Value::TextArray(alloc::vec![Some(
2635 if hit { "true" } else { "false" }.into()
2636 )]));
2637 }
2638 let (strict, steps) = parse_jsonpath_mode(path_text)?;
2641 let matches = apply_jsonpath_mode(&root, &steps, vars, strict)?;
2642 let arr: Vec<Option<String>> = matches
2643 .into_iter()
2644 .map(|v| Some(json_canonical_string(&v)))
2645 .collect();
2646 Ok(Value::TextArray(arr))
2647}
2648
2649pub fn path_query_first(doc: &Value, path: &Value) -> Result<Value<'static>, EvalError> {
2652 path_query_first_vars(doc, path, None)
2653}
2654
2655pub fn path_query_first_vars(
2657 doc: &Value,
2658 path: &Value,
2659 vars: Option<&JsonValue>,
2660) -> Result<Value<'static>, EvalError> {
2661 let q = path_query_vars(doc, path, vars)?;
2662 match q {
2663 Value::TextArray(items) => {
2664 if let Some(Some(first)) = items.into_iter().next() {
2665 Ok(Value::json(first))
2666 } else {
2667 Ok(Value::Null)
2668 }
2669 }
2670 other => Ok(other),
2671 }
2672}
2673
2674pub fn path_query_array(doc: &Value, path: &Value) -> Result<Value<'static>, EvalError> {
2677 path_query_array_vars(doc, path, None)
2678}
2679
2680pub fn path_query_array_vars(
2682 doc: &Value,
2683 path: &Value,
2684 vars: Option<&JsonValue>,
2685) -> Result<Value<'static>, EvalError> {
2686 let q = path_query_vars(doc, path, vars)?;
2687 let arr = match q {
2688 Value::TextArray(items) => {
2689 let mut buf = String::from("[");
2690 let mut first = true;
2691 for s in items.into_iter().flatten() {
2692 if !first {
2693 buf.push_str(", ");
2694 }
2695 buf.push_str(&s);
2696 first = false;
2697 }
2698 buf.push(']');
2699 Value::json(buf)
2700 }
2701 other => other,
2702 };
2703 Ok(canonicalize_value(arr))
2707}
2708
2709pub fn jsonpath_canonical(input: &str) -> Result<String, EvalError> {
2736 let trimmed = input.trim();
2737 let (strict, body) = if let Some(rest) = trimmed.strip_prefix("strict ") {
2738 (true, rest.trim_start())
2739 } else if let Some(rest) = trimmed.strip_prefix("lax ") {
2740 (false, rest.trim_start())
2741 } else {
2742 (false, trimmed)
2743 };
2744 let steps = parse_jsonpath(body).map_err(|_| {
2745 let tok: String = body.chars().take(1).collect();
2748 EvalError::TypeMismatch {
2749 detail: alloc::format!("syntax error at or near {tok:?} of jsonpath input"),
2750 }
2751 })?;
2752 let mut out = String::new();
2753 if strict {
2754 out.push_str("strict ");
2755 }
2756 out.push('$');
2757 fn idx(b: &IdxBound, out: &mut String) {
2758 match b {
2759 IdxBound::At(n) => {
2760 let _ = core::fmt::Write::write_fmt(out, format_args!("{n}"));
2761 }
2762 IdxBound::FromLast(0) => out.push_str("last"),
2763 IdxBound::FromLast(k) => {
2764 let _ = core::fmt::Write::write_fmt(out, format_args!("last - {k}"));
2765 }
2766 }
2767 }
2768 fn fval(v: &FilterVal, out: &mut String) {
2769 match v {
2770 FilterVal::Num(x) => {
2771 if x.fract() == 0.0 && x.abs() < 1e15 {
2772 let _ = core::fmt::Write::write_fmt(out, format_args!("{}", *x as i64));
2773 } else {
2774 let _ = core::fmt::Write::write_fmt(out, format_args!("{x}"));
2775 }
2776 }
2777 FilterVal::Str(s) => {
2778 let _ = core::fmt::Write::write_fmt(out, format_args!("{s:?}"));
2779 }
2780 FilterVal::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
2781 FilterVal::Null => out.push_str("null"),
2782 FilterVal::Var(n) => {
2783 let _ = core::fmt::Write::write_fmt(out, format_args!("$\"{n}\""));
2784 }
2785 }
2786 }
2787 fn fexpr(e: &FilterExpr, out: &mut String) {
2788 match e {
2789 FilterExpr::Cmp(p) => {
2790 out.push('@');
2791 for seg in &p.path {
2792 let _ = core::fmt::Write::write_fmt(out, format_args!(".\"{seg}\""));
2793 }
2794 let op = match p.op {
2795 FilterOp::Gt => " > ",
2796 FilterOp::Lt => " < ",
2797 FilterOp::Ge => " >= ",
2798 FilterOp::Le => " <= ",
2799 FilterOp::Eq => " == ",
2800 FilterOp::Ne => " != ",
2801 FilterOp::StartsWith => " starts with ",
2802 FilterOp::LikeRegex => " like_regex ",
2803 };
2804 out.push_str(op);
2805 fval(&p.val, out);
2806 if let Some(f) = &p.regex_flags {
2807 let _ = core::fmt::Write::write_fmt(out, format_args!(" flag \"{f}\""));
2808 }
2809 }
2810 FilterExpr::And(l, r) => {
2811 fexpr(l, out);
2812 out.push_str(" && ");
2813 fexpr(r, out);
2814 }
2815 FilterExpr::Or(l, r) => {
2816 fexpr(l, out);
2817 out.push_str(" || ");
2818 fexpr(r, out);
2819 }
2820 }
2821 }
2822 for st in &steps {
2823 match st {
2824 PathStep::Field(f) => {
2825 let _ = core::fmt::Write::write_fmt(&mut out, format_args!(".\"{f}\""));
2826 }
2827 PathStep::Index(b) => {
2828 out.push('[');
2829 idx(b, &mut out);
2830 out.push(']');
2831 }
2832 PathStep::Wildcard => out.push_str("[*]"),
2833 PathStep::Range(a, b) => {
2834 out.push('[');
2835 idx(a, &mut out);
2836 out.push_str(" to ");
2837 idx(b, &mut out);
2838 out.push(']');
2839 }
2840 PathStep::Filter(e) => {
2841 out.push_str("?(");
2842 fexpr(e, &mut out);
2843 out.push(')');
2844 }
2845 PathStep::Size => out.push_str(".size()"),
2846 PathStep::TypeOf => out.push_str(".type()"),
2847 PathStep::Num(m) => out.push_str(match m {
2848 NumMethod::Abs => ".abs()",
2849 NumMethod::Floor => ".floor()",
2850 NumMethod::Ceiling => ".ceiling()",
2851 NumMethod::Double => ".double()",
2852 }),
2853 PathStep::RecursiveAll => out.push_str(".**"),
2854 }
2855 }
2856 Ok(out)
2857}
2858
2859pub fn value_to_json_text(v: &Value) -> String {
2860 let mut out = String::new();
2861 encode_value_into(v, &mut out);
2862 out
2863}
2864
2865fn encode_value_into(v: &Value, out: &mut String) {
2866 match v {
2867 Value::Null => out.push_str("null"),
2868 Value::Bool(true) => out.push_str("true"),
2869 Value::Bool(false) => out.push_str("false"),
2870 Value::SmallInt(n) => out.push_str(&alloc::format!("{n}")),
2871 Value::Int(n) => out.push_str(&alloc::format!("{n}")),
2872 Value::BigInt(n) => out.push_str(&alloc::format!("{n}")),
2873 Value::Float(x) if !x.is_finite() => {
2876 let txt = if x.is_nan() {
2877 "NaN"
2878 } else if *x > 0.0 {
2879 "Infinity"
2880 } else {
2881 "-Infinity"
2882 };
2883 write_json(&JsonValue::String(txt.into()), out);
2884 }
2885 Value::Float(x) => out.push_str(&alloc::format!("{x}")),
2886 Value::Real(x) if !x.is_finite() => {
2887 let txt = if x.is_nan() {
2888 "NaN"
2889 } else if *x > 0.0 {
2890 "Infinity"
2891 } else {
2892 "-Infinity"
2893 };
2894 write_json(&JsonValue::String(txt.into()), out);
2895 }
2896 Value::Numeric {
2897 scaled,
2898 scale,
2899 kind,
2900 } => {
2901 use spg_storage::NumericKind as NK;
2902 match kind {
2903 NK::NaN => write_json(&JsonValue::String("NaN".into()), out),
2904 NK::PosInf => write_json(&JsonValue::String("Infinity".into()), out),
2905 NK::NegInf => write_json(&JsonValue::String("-Infinity".into()), out),
2906 NK::Finite => out.push_str(&render_numeric(*scaled, *scale)),
2908 }
2909 }
2910 Value::Text(s) => write_json(&JsonValue::String(s.to_string()), out),
2911 Value::Json(s) => {
2912 out.push_str(s);
2916 }
2917 Value::Composite(fields) => {
2920 out.push('{');
2921 for (i, (name, fv)) in fields.iter().enumerate() {
2922 if i > 0 {
2923 out.push(',');
2924 }
2925 write_json(&JsonValue::String(name.clone()), out);
2926 out.push(':');
2927 encode_value_into(fv, out);
2928 }
2929 out.push('}');
2930 }
2931 Value::TextArray(items) => {
2932 out.push('[');
2933 for (i, it) in items.iter().enumerate() {
2934 if i > 0 {
2935 out.push(',');
2936 }
2937 match it {
2938 Some(s) => write_json(&JsonValue::String(s.clone()), out),
2939 None => out.push_str("null"),
2940 }
2941 }
2942 out.push(']');
2943 }
2944 Value::IntArray(items) => {
2945 out.push('[');
2946 for (i, it) in items.iter().enumerate() {
2947 if i > 0 {
2948 out.push(',');
2949 }
2950 match it {
2951 Some(n) => out.push_str(&alloc::format!("{n}")),
2952 None => out.push_str("null"),
2953 }
2954 }
2955 out.push(']');
2956 }
2957 Value::BigIntArray(items) => {
2958 out.push('[');
2959 for (i, it) in items.iter().enumerate() {
2960 if i > 0 {
2961 out.push(',');
2962 }
2963 match it {
2964 Some(n) => out.push_str(&alloc::format!("{n}")),
2965 None => out.push_str("null"),
2966 }
2967 }
2968 out.push(']');
2969 }
2970 Value::Timestamp(_) => {
2974 let txt = crate::eval::values::value_to_text(v).replacen(' ', "T", 1);
2975 write_json(&JsonValue::String(txt), out);
2976 }
2977 other => {
2989 if let Some(elems) = crate::eval::values::array_elements(other) {
2990 out.push('[');
2991 for (i, e) in elems.iter().enumerate() {
2992 if i > 0 {
2993 out.push(',');
2994 }
2995 encode_value_into(e, out);
2996 }
2997 out.push(']');
2998 return;
2999 }
3000 let txt = crate::eval::values::value_to_text(other);
3001 write_json(&JsonValue::String(txt), out);
3002 }
3003 }
3004}
3005
3006fn render_numeric(scaled: i128, scale: u16) -> String {
3007 let neg = scaled < 0;
3008 let mag_str = alloc::format!("{}", scaled.unsigned_abs());
3009 let s = scale as usize;
3010 let body = if s == 0 {
3011 mag_str
3012 } else if mag_str.len() > s {
3013 let p = mag_str.len() - s;
3014 alloc::format!("{}.{}", &mag_str[..p], &mag_str[p..])
3015 } else {
3016 let pad = s - mag_str.len();
3017 alloc::format!("0.{}{}", "0".repeat(pad), mag_str)
3018 };
3019 if neg { alloc::format!("-{body}") } else { body }
3020}
3021
3022pub fn concat(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
3030 concat_inner(lhs, rhs).map(canonicalize_value)
3031}
3032
3033fn concat_inner(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
3034 let (a_src, b_src) = match (lhs, rhs) {
3035 (Value::Null, _) | (_, Value::Null) => return Ok(Value::Null),
3036 (Value::Json(a) | Value::Text(a), Value::Json(b) | Value::Text(b)) => {
3037 (a.as_ref(), b.as_ref())
3038 }
3039 _ => {
3040 return Err(EvalError::TypeMismatch {
3041 detail: "jsonb_concat() expects (JSON, JSON)".into(),
3042 });
3043 }
3044 };
3045 let a = parse(a_src).map_err(|e| EvalError::TypeMismatch {
3046 detail: alloc::format!("invalid JSON lhs for concat: {e}"),
3047 })?;
3048 let b = parse(b_src).map_err(|e| EvalError::TypeMismatch {
3049 detail: alloc::format!("invalid JSON rhs for concat: {e}"),
3050 })?;
3051 let merged = match (a, b) {
3052 (JsonValue::Object(mut ea), JsonValue::Object(eb)) => {
3053 for (k, v) in eb {
3055 if let Some(slot) = ea.iter_mut().find(|(ek, _)| *ek == k) {
3056 slot.1 = v;
3057 } else {
3058 ea.push((k, v));
3059 }
3060 }
3061 JsonValue::Object(ea)
3062 }
3063 (JsonValue::Array(mut ia), JsonValue::Array(ib)) => {
3064 ia.extend(ib);
3065 JsonValue::Array(ia)
3066 }
3067 (JsonValue::Array(mut ia), scalar) => {
3068 ia.push(scalar);
3069 JsonValue::Array(ia)
3070 }
3071 (scalar, JsonValue::Array(ib)) => {
3072 let mut out = alloc::vec![scalar];
3073 out.extend(ib);
3074 JsonValue::Array(out)
3075 }
3076 (sa, sb) => JsonValue::Array(alloc::vec![sa, sb]),
3077 };
3078 Ok(Value::json(merged.to_json_text()))
3079}
3080
3081pub fn delete_key(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
3085 delete_key_inner(lhs, rhs).map(canonicalize_value)
3086}
3087
3088fn delete_key_inner(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
3089 let src = match lhs {
3090 Value::Null => return Ok(Value::Null),
3091 Value::Json(s) | Value::Text(s) => s.as_ref(),
3092 _ => {
3093 return Err(EvalError::TypeMismatch {
3094 detail: "jsonb_delete() expects JSON lhs".into(),
3095 });
3096 }
3097 };
3098 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
3099 detail: alloc::format!("invalid JSON for delete: {e}"),
3100 })?;
3101 let out = match (doc, rhs) {
3102 (_, Value::Null) => return Ok(Value::Null),
3103 (JsonValue::Object(entries), Value::Text(key)) => {
3104 let filtered: Vec<(String, JsonValue)> = entries
3105 .into_iter()
3106 .filter(|(k, _)| k != key.as_ref())
3107 .collect();
3108 JsonValue::Object(filtered)
3109 }
3110 (JsonValue::Object(entries), Value::TextArray(keys)) => {
3112 let filtered: Vec<(String, JsonValue)> = entries
3113 .into_iter()
3114 .filter(|(k, _)| !keys.iter().any(|kk| kk.as_deref() == Some(k.as_str())))
3115 .collect();
3116 JsonValue::Object(filtered)
3117 }
3118 (JsonValue::Array(items), Value::Int(idx)) => {
3119 let n = *idx;
3120 let len = items.len() as i64;
3121 let real = if n >= 0 {
3122 i64::from(n)
3123 } else {
3124 len + i64::from(n)
3125 };
3126 let filtered: Vec<JsonValue> = items
3127 .into_iter()
3128 .enumerate()
3129 .filter(|(i, _)| *i as i64 != real)
3130 .map(|(_, v)| v)
3131 .collect();
3132 JsonValue::Array(filtered)
3133 }
3134 (JsonValue::Object(_), Value::Int(_) | Value::SmallInt(_) | Value::BigInt(_)) => {
3140 return Err(EvalError::TypeMismatch {
3141 detail: "cannot delete from object using integer index".into(),
3142 });
3143 }
3144 (other, _) if !matches!(other, JsonValue::Object(_) | JsonValue::Array(_)) => {
3145 return Err(EvalError::TypeMismatch {
3146 detail: "cannot delete from scalar".into(),
3147 });
3148 }
3149 (other, _) => other,
3152 };
3153 Ok(Value::json(out.to_json_text()))
3154}
3155
3156pub fn build_object(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3157 if !args.len().is_multiple_of(2) {
3158 return Err(EvalError::TypeMismatch {
3159 detail: alloc::format!(
3160 "json_build_object() needs an even number of args, got {}",
3161 args.len()
3162 ),
3163 });
3164 }
3165 let mut out = String::from("{");
3166 let mut first = true;
3167 let (pairs, _) = args.as_chunks::<2>();
3168 for pair in pairs {
3169 if !first {
3170 out.push_str(", ");
3174 }
3175 first = false;
3176 let key = match &pair[0] {
3177 Value::Null => {
3178 return Err(EvalError::TypeMismatch {
3179 detail: "json_build_object() key cannot be NULL".into(),
3180 });
3181 }
3182 Value::Text(s) | Value::Json(s) => s.to_string(),
3183 other => format_value_as_text(other),
3184 };
3185 write_json(&JsonValue::String(key), &mut out);
3186 out.push_str(" : ");
3187 encode_value_into(&pair[1], &mut out);
3188 }
3189 out.push('}');
3190 Ok(Value::json(out))
3191}
3192
3193pub fn build_array(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3196 let mut out = String::from("[");
3197 for (i, v) in args.iter().enumerate() {
3198 if i > 0 {
3199 out.push_str(", ");
3203 }
3204 encode_value_into(v, &mut out);
3205 }
3206 out.push(']');
3207 Ok(Value::json(out))
3208}
3209
3210fn format_value_as_text(v: &Value) -> String {
3211 match v {
3212 Value::SmallInt(n) => alloc::format!("{n}"),
3213 Value::Int(n) => alloc::format!("{n}"),
3214 Value::BigInt(n) => alloc::format!("{n}"),
3215 Value::Float(x) => alloc::format!("{x}"),
3216 Value::Bool(b) => alloc::format!("{b}"),
3217 other => alloc::format!("{other:?}"),
3218 }
3219}
3220
3221pub fn set(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3230 if !(3..=4).contains(&args.len()) {
3231 return Err(EvalError::TypeMismatch {
3232 detail: alloc::format!("jsonb_set() takes 3 or 4 args, got {}", args.len()),
3233 });
3234 }
3235 if args.iter().take(3).any(|v| matches!(v, Value::Null)) {
3236 return Ok(Value::Null);
3237 }
3238 let create_missing = match args.get(3) {
3239 None | Some(Value::Null) => true,
3240 Some(Value::Bool(b)) => *b,
3241 Some(other) => {
3242 return Err(EvalError::TypeMismatch {
3243 detail: alloc::format!(
3244 "jsonb_set() create_missing must be BOOL, got {}",
3245 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3246 ),
3247 });
3248 }
3249 };
3250 let doc_text = json_text_arg(&args[0], "jsonb_set", "target")?;
3251 let path = path_text_arg(&args[1], "jsonb_set")?;
3252 let new_text = json_text_arg(&args[2], "jsonb_set", "new_value")?;
3253 let mut root = parse(doc_text).map_err(|e| EvalError::TypeMismatch {
3254 detail: alloc::format!("jsonb_set(): invalid JSON target — {e}"),
3255 })?;
3256 let new_val = parse(new_text).map_err(|e| EvalError::TypeMismatch {
3257 detail: alloc::format!("jsonb_set(): invalid JSON new_value — {e}"),
3258 })?;
3259 if path.is_empty() {
3264 return Ok(Value::json(root.to_json_text()));
3265 }
3266 if is_json_scalar(&root) {
3267 return Err(EvalError::TypeMismatch {
3268 detail: "cannot set path in scalar".into(),
3269 });
3270 }
3271 set_at_path(&mut root, &path, new_val, create_missing);
3272 Ok(Value::json(root.to_json_text()))
3273}
3274
3275pub fn delete_path(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3279 delete_path_inner(args).map(canonicalize_value)
3280}
3281
3282fn delete_path_inner(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3283 if args.len() != 2 {
3284 return Err(EvalError::TypeMismatch {
3285 detail: alloc::format!("jsonb_delete_path() takes 2 args, got {}", args.len()),
3286 });
3287 }
3288 if args.iter().any(|v| matches!(v, Value::Null)) {
3289 return Ok(Value::Null);
3290 }
3291 let doc_text = json_text_arg(&args[0], "jsonb_delete_path", "target")?;
3292 let path = path_text_arg(&args[1], "jsonb_delete_path")?;
3293 let mut root = parse(doc_text).map_err(|e| EvalError::TypeMismatch {
3294 detail: alloc::format!("jsonb_delete_path(): invalid JSON target — {e}"),
3295 })?;
3296 if is_json_scalar(&root) && !path.is_empty() {
3299 return Err(EvalError::TypeMismatch {
3300 detail: "cannot delete path in scalar".into(),
3301 });
3302 }
3303 delete_at_path(&mut root, &path);
3304 Ok(Value::json(root.to_json_text()))
3305}
3306
3307fn delete_at_path(node: &mut JsonValue, path: &[String]) {
3308 if path.is_empty() {
3309 return;
3310 }
3311 let step = &path[0];
3312 if path.len() == 1 {
3313 match node {
3315 JsonValue::Object(entries) => {
3316 entries.retain(|(k, _)| k != step);
3317 }
3318 JsonValue::Array(items) => {
3319 if let Ok(idx) = step.parse::<i64>() {
3320 let len = items.len() as i64;
3321 let real = if idx >= 0 { idx } else { len + idx };
3322 if real >= 0 && real < len {
3323 items.remove(real as usize);
3324 }
3325 }
3326 }
3327 _ => {}
3328 }
3329 return;
3330 }
3331 match node {
3333 JsonValue::Object(entries) => {
3334 if let Some((_, child)) = entries.iter_mut().find(|(k, _)| k == step) {
3335 delete_at_path(child, &path[1..]);
3336 }
3337 }
3338 JsonValue::Array(items) => {
3339 if let Ok(idx) = step.parse::<i64>() {
3340 let len = items.len() as i64;
3341 let real = if idx >= 0 { idx } else { len + idx };
3342 if real >= 0 && real < len {
3343 delete_at_path(&mut items[real as usize], &path[1..]);
3344 }
3345 }
3346 }
3347 _ => {}
3348 }
3349}
3350
3351fn set_at_path(node: &mut JsonValue, path: &[String], new_val: JsonValue, create_missing: bool) {
3352 if path.is_empty() {
3353 *node = new_val;
3354 return;
3355 }
3356 let step = &path[0];
3357 let rest = &path[1..];
3358 match node {
3359 JsonValue::Object(entries) => {
3360 if let Some(pos) = entries.iter().position(|(k, _)| k == step) {
3361 if rest.is_empty() {
3362 entries[pos].1 = new_val;
3363 } else {
3364 set_at_path(&mut entries[pos].1, rest, new_val, create_missing);
3365 }
3366 } else if create_missing && rest.is_empty() {
3367 entries.push((step.clone(), new_val));
3368 }
3369 }
3372 JsonValue::Array(items) => {
3373 let Some(idx) = resolve_array_index(step, items.len()) else {
3374 if create_missing && rest.is_empty() {
3375 if let Ok(n) = step.parse::<i64>() {
3377 if n < 0 {
3378 items.insert(0, new_val);
3379 } else {
3380 items.push(new_val);
3381 }
3382 }
3383 }
3384 return;
3385 };
3386 if rest.is_empty() {
3387 items[idx] = new_val;
3388 } else {
3389 set_at_path(&mut items[idx], rest, new_val, create_missing);
3390 }
3391 }
3392 _ => {
3393 }
3395 }
3396}
3397
3398fn resolve_array_index(step: &str, len: usize) -> Option<usize> {
3399 let n = step.parse::<i64>().ok()?;
3400 if n >= 0 {
3401 let i = n as usize;
3402 if i < len { Some(i) } else { None }
3403 } else {
3404 let from_end = len as i64 + n;
3405 if from_end >= 0 {
3406 Some(from_end as usize)
3407 } else {
3408 None
3409 }
3410 }
3411}
3412
3413pub fn insert(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3421 if !(3..=4).contains(&args.len()) {
3422 return Err(EvalError::TypeMismatch {
3423 detail: alloc::format!("jsonb_insert() takes 3 or 4 args, got {}", args.len()),
3424 });
3425 }
3426 if args.iter().take(3).any(|v| matches!(v, Value::Null)) {
3427 return Ok(Value::Null);
3428 }
3429 let insert_after = match args.get(3) {
3430 None | Some(Value::Null) => false,
3431 Some(Value::Bool(b)) => *b,
3432 Some(other) => {
3433 return Err(EvalError::TypeMismatch {
3434 detail: alloc::format!(
3435 "jsonb_insert() insert_after must be BOOL, got {}",
3436 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3437 ),
3438 });
3439 }
3440 };
3441 let doc_text = json_text_arg(&args[0], "jsonb_insert", "target")?;
3442 let path = path_text_arg(&args[1], "jsonb_insert")?;
3443 let new_text = json_text_arg(&args[2], "jsonb_insert", "new_value")?;
3444 let mut root = parse(doc_text).map_err(|e| EvalError::TypeMismatch {
3445 detail: alloc::format!("jsonb_insert(): invalid JSON target — {e}"),
3446 })?;
3447 if path.is_empty() {
3451 return Ok(Value::json(root.to_json_text()));
3452 }
3453 if is_json_scalar(&root) {
3454 return Err(EvalError::TypeMismatch {
3455 detail: "cannot set path in scalar".into(),
3456 });
3457 }
3458 let new_val = parse(new_text).map_err(|e| EvalError::TypeMismatch {
3459 detail: alloc::format!("jsonb_insert(): invalid JSON new_value — {e}"),
3460 })?;
3461 insert_at_path(&mut root, &path, new_val, insert_after)?;
3462 Ok(Value::json(root.to_json_text()))
3463}
3464
3465fn insert_at_path(
3466 node: &mut JsonValue,
3467 path: &[String],
3468 new_val: JsonValue,
3469 insert_after: bool,
3470) -> Result<(), EvalError> {
3471 debug_assert!(!path.is_empty());
3472 if path.len() == 1 {
3473 let step = &path[0];
3474 match node {
3475 JsonValue::Object(entries) => {
3476 if entries.iter().any(|(k, _)| k == step) {
3477 return Err(EvalError::TypeMismatch {
3478 detail: alloc::format!(
3479 "jsonb_insert(): cannot replace existing key {step:?}"
3480 ),
3481 });
3482 }
3483 entries.push((step.clone(), new_val));
3484 Ok(())
3485 }
3486 JsonValue::Array(items) => {
3487 let Ok(n) = step.parse::<i64>() else {
3488 return Err(EvalError::TypeMismatch {
3489 detail: alloc::format!(
3490 "jsonb_insert(): array step must be integer, got {step:?}"
3491 ),
3492 });
3493 };
3494 let mut idx = if n >= 0 {
3495 let i = n as usize;
3496 if i > items.len() { items.len() } else { i }
3497 } else {
3498 let from_end = items.len() as i64 + n;
3499 if from_end < 0 { 0 } else { from_end as usize }
3500 };
3501 if insert_after && idx < items.len() {
3502 idx += 1;
3503 }
3504 items.insert(idx, new_val);
3505 Ok(())
3506 }
3507 _ => Err(EvalError::TypeMismatch {
3508 detail: "jsonb_insert(): parent at path is a scalar".into(),
3509 }),
3510 }
3511 } else {
3512 let step = &path[0];
3513 let rest = &path[1..];
3514 match node {
3515 JsonValue::Object(entries) => {
3516 if let Some(pos) = entries.iter().position(|(k, _)| k == step) {
3517 insert_at_path(&mut entries[pos].1, rest, new_val, insert_after)
3518 } else {
3519 Err(EvalError::TypeMismatch {
3520 detail: alloc::format!("jsonb_insert(): path {step:?} does not exist"),
3521 })
3522 }
3523 }
3524 JsonValue::Array(items) => {
3525 let Some(idx) = resolve_array_index(step, items.len()) else {
3526 return Err(EvalError::TypeMismatch {
3527 detail: alloc::format!("jsonb_insert(): array index {step:?} out of range"),
3528 });
3529 };
3530 insert_at_path(&mut items[idx], rest, new_val, insert_after)
3531 }
3532 _ => Err(EvalError::TypeMismatch {
3533 detail: "jsonb_insert(): parent at path is a scalar".into(),
3534 }),
3535 }
3536 }
3537}
3538
3539fn json_text_arg<'a>(v: &'a Value, fname: &str, role: &str) -> Result<&'a str, EvalError> {
3540 match v {
3541 Value::Json(s) | Value::Text(s) => Ok(s.as_ref()),
3542 other => Err(EvalError::TypeMismatch {
3543 detail: alloc::format!(
3544 "{fname}() {role} must be JSON or TEXT, got {}",
3545 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3546 ),
3547 }),
3548 }
3549}
3550
3551fn path_text_arg(v: &Value, fname: &str) -> Result<Vec<String>, EvalError> {
3552 match v {
3553 Value::Text(s) | Value::Json(s) => parse_text_array(s.as_ref()),
3554 Value::TextArray(items) => Ok(items
3555 .iter()
3556 .map(|o| o.clone().unwrap_or_default())
3557 .collect()),
3558 other => Err(EvalError::TypeMismatch {
3559 detail: alloc::format!(
3560 "{fname}() path must be TEXT[] or TEXT, got {}",
3561 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3562 ),
3563 }),
3564 }
3565}
3566
3567#[cfg(test)]
3568mod tests {
3569 use super::*;
3570
3571 fn canon(s: &str) -> String {
3572 canonicalize_jsonb(s).unwrap()
3573 }
3574
3575 #[test]
3576 fn canon_number_rules() {
3577 assert_eq!(canon_json_number("1.0"), "1.0");
3579 assert_eq!(canon_json_number("1e2"), "100");
3580 assert_eq!(canon_json_number("1.10"), "1.10");
3581 assert_eq!(canon_json_number("100.00"), "100.00");
3582 assert_eq!(canon_json_number("0.5"), "0.5");
3583 assert_eq!(canon_json_number("-0"), "0");
3584 assert_eq!(canon_json_number("1E-3"), "0.001");
3585 assert_eq!(canon_json_number("42"), "42");
3586 assert_eq!(canon_json_number("-2.5"), "-2.5");
3587 assert_eq!(canon_json_number("2.5e3"), "2500");
3588 }
3589
3590 #[test]
3591 fn canon_key_order_dedup_and_whitespace() {
3592 assert_eq!(
3594 canon(r#"{"b":1,"a":2,"aa":3,"":9,"z":4}"#),
3595 r#"{"": 9, "a": 2, "b": 1, "z": 4, "aa": 3}"#
3596 );
3597 assert_eq!(canon(r#"{"a":1,"a":2,"a":3}"#), r#"{"a": 3}"#);
3599 assert_eq!(canon("[3,2,1]"), "[3, 2, 1]");
3601 }
3602
3603 #[test]
3604 fn json_number_equality_by_value() {
3605 let eq = |a: &str, b: &str| json_eq(&parse(a).unwrap(), &parse(b).unwrap());
3606 assert!(eq("1", "1.0"));
3607 assert!(eq("1.50", "1.5"));
3608 assert!(eq("1e3", "1000.00"));
3609 assert!(eq("0", "-0"));
3610 assert!(eq("2.5e3", "2500"));
3611 assert!(!eq("1.5", "1.6"));
3612 assert!(eq("[1, 2.0]", "[1.0, 2]"));
3614 assert!(eq(r#"{"a":1}"#, r#"{"a":1.0}"#));
3615 }
3616
3617 #[test]
3618 fn canon_nested_and_scalars() {
3619 assert_eq!(
3620 canon(r#"{"x":{"b":1,"a":2},"y":[3,{"d":1,"c":2}]}"#),
3621 r#"{"x": {"a": 2, "b": 1}, "y": [3, {"c": 2, "d": 1}]}"#
3622 );
3623 assert_eq!(canon(" true "), "true");
3624 assert_eq!(canon(" 42 "), "42");
3625 assert_eq!(canon("{}"), "{}");
3626 assert_eq!(canon("[]"), "[]");
3627 assert_eq!(
3629 canon(r#"{"e":"café","t":"a\nb"}"#),
3630 r#"{"e": "café", "t": "a\nb"}"#
3631 );
3632 }
3633
3634 #[test]
3635 fn parse_atoms() {
3636 assert_eq!(parse("null").unwrap(), JsonValue::Null);
3637 assert_eq!(parse("true").unwrap(), JsonValue::Bool(true));
3638 assert_eq!(parse("false").unwrap(), JsonValue::Bool(false));
3639 assert_eq!(
3640 parse("\"hello\"").unwrap(),
3641 JsonValue::String("hello".into())
3642 );
3643 assert!(matches!(
3644 parse("42").unwrap(),
3645 JsonValue::NumberText(ref s) if s == "42"
3646 ));
3647 }
3648
3649 #[test]
3650 fn parse_nested() {
3651 let doc = parse(r#"{"a":1,"b":[true,null,"x"]}"#).unwrap();
3652 let JsonValue::Object(entries) = doc else {
3653 panic!("expected object");
3654 };
3655 assert_eq!(entries.len(), 2);
3656 assert_eq!(entries[0].0, "a");
3657 assert_eq!(entries[1].0, "b");
3658 }
3659
3660 #[test]
3661 fn parse_string_escapes() {
3662 let s = parse(r#""he said \"hi\" and\\then\n""#).unwrap();
3663 assert_eq!(s, JsonValue::String("he said \"hi\" and\\then\n".into()));
3664 }
3665
3666 #[test]
3667 fn parse_unicode_escape() {
3668 assert_eq!(parse(r#""é""#).unwrap(), JsonValue::String("é".into()));
3669 }
3670
3671 #[test]
3672 fn path_object_key_returns_value() {
3673 let doc = Value::json::<String>(r#"{"name":"alice","age":30}"#.into());
3674 let key = Value::text("name");
3675 let v = path_get(&doc, &key, true).unwrap();
3676 assert_eq!(v, Value::text("alice"));
3677 let v = path_get(&doc, &key, false).unwrap();
3678 assert_eq!(v, Value::json("\"alice\""));
3679 }
3680
3681 #[test]
3682 fn path_array_index_supports_negative() {
3683 let doc = Value::json("[10,20,30]");
3684 let v = path_get(&doc, &Value::Int(1), true).unwrap();
3685 assert_eq!(v, Value::text("20"));
3686 let v = path_get(&doc, &Value::Int(-1), true).unwrap();
3687 assert_eq!(v, Value::text("30"));
3688 }
3689
3690 #[test]
3691 fn path_missing_key_returns_null() {
3692 let doc = Value::json::<String>(r#"{"a":1}"#.into());
3693 let v = path_get(&doc, &Value::text("missing"), true).unwrap();
3694 assert_eq!(v, Value::Null);
3695 }
3696
3697 #[test]
3698 fn path_get_nested_subtree_is_verbatim() {
3699 let doc = Value::json::<String>(r#"{"k":{"x":[1,2]}}"#.into());
3705 let v = path_get(&doc, &Value::text("k"), false).unwrap();
3706 assert_eq!(v, Value::json::<String>(r#"{"x":[1,2]}"#.into()));
3707
3708 let canon = Value::json::<String>(r#"{"k": {"x": [1, 2]}}"#.into());
3710 let v = path_get(&canon, &Value::text("k"), false).unwrap();
3711 assert_eq!(v, Value::json::<String>(r#"{"x": [1, 2]}"#.into()));
3712
3713 let raw = Value::json::<String>(r#"{"a":{ "y" : 2e2 },"k":1,"k":2}"#.into());
3716 assert_eq!(
3717 path_get(&raw, &Value::text("a"), false).unwrap(),
3718 Value::json::<String>(r#"{ "y" : 2e2 }"#.into())
3719 );
3720 assert_eq!(
3721 path_get(&raw, &Value::text("k"), false).unwrap(),
3722 Value::json::<String>("2".into())
3723 );
3724
3725 let n = Value::json::<String>(r#"{"a":null}"#.into());
3727 assert_eq!(
3728 path_get(&n, &Value::text("a"), false).unwrap(),
3729 Value::json::<String>("null".into())
3730 );
3731 assert_eq!(path_get(&n, &Value::text("a"), true).unwrap(), Value::Null);
3732 }
3733}
3734
3735#[derive(Debug)]
3738pub enum MysqlPathStep {
3739 Key(String),
3740 Index(usize),
3741}
3742
3743pub fn mysql_path_steps(path: &str) -> Result<Vec<MysqlPathStep>, EvalError> {
3748 let chars: Vec<char> = path.trim().chars().collect();
3749 if chars.first() != Some(&'$') {
3750 return Err(EvalError::TypeMismatch {
3751 detail: alloc::format!("invalid JSON path expression (must start with $): {path:?}"),
3752 });
3753 }
3754 let mut steps = Vec::new();
3755 let mut i = 1;
3756 while i < chars.len() {
3757 match chars[i] {
3758 '.' => {
3759 i += 1;
3760 if i < chars.len() && chars[i] == '"' {
3761 i += 1;
3762 let mut key = String::new();
3763 while i < chars.len() && chars[i] != '"' {
3764 if chars[i] == '\\' && i + 1 < chars.len() {
3765 i += 1;
3766 }
3767 key.push(chars[i]);
3768 i += 1;
3769 }
3770 if i >= chars.len() {
3771 return Err(EvalError::TypeMismatch {
3772 detail: alloc::format!(
3773 "invalid JSON path expression (unterminated quote): {path:?}"
3774 ),
3775 });
3776 }
3777 i += 1; steps.push(MysqlPathStep::Key(key));
3779 } else {
3780 let mut key = String::new();
3781 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
3782 key.push(chars[i]);
3783 i += 1;
3784 }
3785 if key.is_empty() {
3786 return Err(EvalError::TypeMismatch {
3787 detail: alloc::format!(
3788 "unsupported JSON path step at position {i} in {path:?} \
3789 (wildcards are not supported)"
3790 ),
3791 });
3792 }
3793 steps.push(MysqlPathStep::Key(key));
3794 }
3795 }
3796 '[' => {
3797 i += 1;
3798 let mut num = String::new();
3799 while i < chars.len() && chars[i] != ']' {
3800 num.push(chars[i]);
3801 i += 1;
3802 }
3803 if i >= chars.len() {
3804 return Err(EvalError::TypeMismatch {
3805 detail: alloc::format!(
3806 "invalid JSON path expression (unterminated bracket): {path:?}"
3807 ),
3808 });
3809 }
3810 i += 1; let idx: usize = num.trim().parse().map_err(|_| EvalError::TypeMismatch {
3812 detail: alloc::format!(
3813 "unsupported JSON path index {num:?} in {path:?} \
3814 (wildcards are not supported)"
3815 ),
3816 })?;
3817 steps.push(MysqlPathStep::Index(idx));
3818 }
3819 other => {
3820 return Err(EvalError::TypeMismatch {
3821 detail: alloc::format!(
3822 "invalid JSON path expression (unexpected {other:?}): {path:?}"
3823 ),
3824 });
3825 }
3826 }
3827 }
3828 Ok(steps)
3829}
3830
3831pub fn mysql_path_get<'a>(doc: &'a JsonValue, steps: &[MysqlPathStep]) -> Option<&'a JsonValue> {
3834 let mut cur = doc;
3835 for step in steps {
3836 match (step, cur) {
3837 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
3838 cur = members.iter().find(|(mk, _)| mk == k).map(|(_, v)| v)?;
3839 }
3840 (MysqlPathStep::Index(idx), JsonValue::Array(items)) => {
3841 cur = items.get(*idx)?;
3842 }
3843 (MysqlPathStep::Index(0), scalar) => {
3846 cur = scalar;
3847 }
3848 _ => return None,
3849 }
3850 }
3851 Some(cur)
3852}
3853
3854pub fn mysql_json_extract(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3859 if args.len() < 2 {
3860 return Err(EvalError::TypeMismatch {
3861 detail: alloc::format!(
3862 "json_extract() takes a document and at least one path, got {} args",
3863 args.len()
3864 ),
3865 });
3866 }
3867 if args.iter().any(|a| matches!(a, Value::Null)) {
3868 return Ok(Value::Null);
3869 }
3870 let src = match &args[0] {
3871 Value::Json(s) | Value::Text(s) => s.as_ref(),
3872 other => {
3873 return Err(EvalError::TypeMismatch {
3874 detail: alloc::format!(
3875 "json_extract() document must be json, got {}",
3876 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3877 ),
3878 });
3879 }
3880 };
3881 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
3882 detail: alloc::format!("json_extract(): invalid JSON: {e}"),
3883 })?;
3884 let mut hits: Vec<String> = Vec::new();
3885 for path_v in &args[1..] {
3886 let Value::Text(p) = path_v else {
3887 return Err(EvalError::TypeMismatch {
3888 detail: alloc::format!(
3889 "json_extract() paths must be text, got {}",
3890 crate::conversions::pg_type_name_for_error_opt(path_v.data_type())
3891 ),
3892 });
3893 };
3894 let steps = mysql_path_steps(p)?;
3895 if let Some(v) = mysql_path_get(&doc, &steps) {
3896 hits.push(v.to_json_text());
3897 }
3898 }
3899 match (args.len() - 1, hits.len()) {
3900 (_, 0) => Ok(Value::Null),
3901 (1, _) => Ok(Value::Json(alloc::borrow::Cow::Owned(
3902 hits.into_iter().next().unwrap(),
3903 ))),
3904 _ => {
3905 let mut out = String::from("[");
3906 for (i, h) in hits.iter().enumerate() {
3907 if i > 0 {
3908 out.push_str(", ");
3909 }
3910 out.push_str(h);
3911 }
3912 out.push(']');
3913 Ok(Value::Json(alloc::borrow::Cow::Owned(out)))
3914 }
3915 }
3916}
3917
3918pub fn mysql_json_contains_path(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3921 if args.len() < 3 {
3922 return Err(EvalError::TypeMismatch {
3923 detail: alloc::format!(
3924 "json_contains_path() takes a document, one/all, and at least one path, got {} args",
3925 args.len()
3926 ),
3927 });
3928 }
3929 if args.iter().any(|a| matches!(a, Value::Null)) {
3930 return Ok(Value::Null);
3931 }
3932 let src = match &args[0] {
3933 Value::Json(s) | Value::Text(s) => s.as_ref(),
3934 other => {
3935 return Err(EvalError::TypeMismatch {
3936 detail: alloc::format!(
3937 "json_contains_path() document must be json, got {}",
3938 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3939 ),
3940 });
3941 }
3942 };
3943 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
3944 detail: alloc::format!("json_contains_path(): invalid JSON: {e}"),
3945 })?;
3946 let mode = match &args[1] {
3947 Value::Text(m) if m.eq_ignore_ascii_case("one") => false,
3948 Value::Text(m) if m.eq_ignore_ascii_case("all") => true,
3949 other => {
3950 return Err(EvalError::TypeMismatch {
3951 detail: alloc::format!(
3952 "json_contains_path() second arg must be 'one' or 'all', got {other:?}"
3953 ),
3954 });
3955 }
3956 };
3957 let mut found_any = false;
3958 let mut found_all = true;
3959 for path_v in &args[2..] {
3960 let Value::Text(p) = path_v else {
3961 return Err(EvalError::TypeMismatch {
3962 detail: alloc::format!(
3963 "json_contains_path() paths must be text, got {}",
3964 crate::conversions::pg_type_name_for_error_opt(path_v.data_type())
3965 ),
3966 });
3967 };
3968 let steps = mysql_path_steps(p)?;
3969 if mysql_path_get(&doc, &steps).is_some() {
3970 found_any = true;
3971 } else {
3972 found_all = false;
3973 }
3974 }
3975 Ok(Value::Bool(if mode { found_all } else { found_any }))
3976}
3977
3978fn value_to_jsonvalue(v: &Value) -> Result<JsonValue, EvalError> {
3982 Ok(match v {
3983 Value::Null => JsonValue::Null,
3984 Value::Bool(b) => JsonValue::Bool(*b),
3985 Value::Json(s) => parse(s).map_err(|e| EvalError::TypeMismatch {
3986 detail: alloc::format!("invalid JSON value: {e}"),
3987 })?,
3988 Value::Text(s) => JsonValue::String(s.to_string()),
3989 Value::Composite(fields) => {
3992 let mut entries = alloc::vec::Vec::with_capacity(fields.len());
3993 for (name, fv) in fields.iter() {
3994 entries.push((name.clone(), value_to_jsonvalue(fv)?));
3995 }
3996 JsonValue::Object(entries)
3997 }
3998 other => {
3999 let text = value_to_json_text(other);
4002 parse(&text).map_err(|e| EvalError::TypeMismatch {
4003 detail: alloc::format!("invalid JSON value: {e}"),
4004 })?
4005 }
4006 })
4007}
4008
4009#[derive(Clone, Copy, PartialEq, Debug)]
4010enum MutateMode {
4011 Set,
4013 Insert,
4015 Replace,
4017}
4018
4019fn mutate_at(cur: &mut JsonValue, steps: &[MysqlPathStep], mode: MutateMode, newval: &JsonValue) {
4022 match steps {
4023 [] => {
4024 if matches!(mode, MutateMode::Set | MutateMode::Replace) {
4025 *cur = newval.clone();
4026 }
4027 }
4028 [last] => match (last, &mut *cur) {
4029 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4030 if let Some(slot) = members.iter_mut().find(|(mk, _)| mk == k) {
4031 if matches!(mode, MutateMode::Set | MutateMode::Replace) {
4032 slot.1 = newval.clone();
4033 }
4034 } else if matches!(mode, MutateMode::Set | MutateMode::Insert) {
4035 members.push((k.clone(), newval.clone()));
4036 }
4037 }
4038 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
4039 if *i < items.len() {
4040 if matches!(mode, MutateMode::Set | MutateMode::Replace) {
4041 items[*i] = newval.clone();
4042 }
4043 } else if matches!(mode, MutateMode::Set | MutateMode::Insert) {
4044 items.push(newval.clone());
4046 }
4047 }
4048 (MysqlPathStep::Index(0), scalar) => {
4050 if matches!(mode, MutateMode::Set | MutateMode::Replace) {
4051 *scalar = newval.clone();
4052 }
4053 }
4054 _ => {}
4055 },
4056 [head, rest @ ..] => match (head, cur) {
4057 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4058 if let Some(slot) = members.iter_mut().find(|(mk, _)| mk == k) {
4059 mutate_at(&mut slot.1, rest, mode, newval);
4060 }
4061 }
4062 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
4063 if let Some(slot) = items.get_mut(*i) {
4064 mutate_at(slot, rest, mode, newval);
4065 }
4066 }
4067 _ => {}
4068 },
4069 }
4070}
4071
4072fn mysql_json_mutate(
4073 args: &[Value<'_>],
4074 mode: MutateMode,
4075 fn_name: &str,
4076) -> Result<Value<'static>, EvalError> {
4077 if args.len() < 3 || args.len() % 2 == 0 {
4078 return Err(EvalError::TypeMismatch {
4079 detail: alloc::format!(
4080 "{fn_name}() takes a document plus (path, value) pairs, got {} args",
4081 args.len()
4082 ),
4083 });
4084 }
4085 if matches!(args[0], Value::Null) {
4086 return Ok(Value::Null);
4087 }
4088 let src = match &args[0] {
4089 Value::Json(s) | Value::Text(s) => s.as_ref(),
4090 other => {
4091 return Err(EvalError::TypeMismatch {
4092 detail: alloc::format!(
4093 "{fn_name}() document must be json, got {}",
4094 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4095 ),
4096 });
4097 }
4098 };
4099 let mut doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4100 detail: alloc::format!("{fn_name}(): invalid JSON: {e}"),
4101 })?;
4102 for pair in args[1..].chunks(2) {
4103 let Value::Text(p) = &pair[0] else {
4104 if matches!(pair[0], Value::Null) {
4105 return Ok(Value::Null);
4106 }
4107 return Err(EvalError::TypeMismatch {
4108 detail: alloc::format!(
4109 "{fn_name}() paths must be text, got {}",
4110 crate::conversions::pg_type_name_for_error_opt(pair[0].data_type())
4111 ),
4112 });
4113 };
4114 let steps = mysql_path_steps(p)?;
4115 let newval = value_to_jsonvalue(&pair[1])?;
4116 mutate_at(&mut doc, &steps, mode, &newval);
4117 }
4118 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4122 doc.to_json_text(),
4123 ))))
4124}
4125
4126pub fn mysql_json_set(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4130 mysql_json_mutate(args, MutateMode::Set, "json_set")
4131}
4132
4133pub fn mysql_json_insert(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4134 mysql_json_mutate(args, MutateMode::Insert, "json_insert")
4135}
4136
4137pub fn mysql_json_replace(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4138 mysql_json_mutate(args, MutateMode::Replace, "json_replace")
4139}
4140
4141pub fn mysql_json_remove(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4144 if args.len() < 2 {
4145 return Err(EvalError::TypeMismatch {
4146 detail: alloc::format!(
4147 "json_remove() takes a document and at least one path, got {} args",
4148 args.len()
4149 ),
4150 });
4151 }
4152 if args.iter().any(|a| matches!(a, Value::Null)) {
4153 return Ok(Value::Null);
4154 }
4155 let src = match &args[0] {
4156 Value::Json(s) | Value::Text(s) => s.as_ref(),
4157 other => {
4158 return Err(EvalError::TypeMismatch {
4159 detail: alloc::format!(
4160 "json_remove() document must be json, got {}",
4161 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4162 ),
4163 });
4164 }
4165 };
4166 let mut doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4167 detail: alloc::format!("json_remove(): invalid JSON: {e}"),
4168 })?;
4169 fn remove_at(cur: &mut JsonValue, steps: &[MysqlPathStep]) {
4170 match steps {
4171 [] => {}
4172 [last] => match (last, cur) {
4173 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4174 members.retain(|(mk, _)| mk != k);
4175 }
4176 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
4177 if *i < items.len() {
4178 items.remove(*i);
4179 }
4180 }
4181 _ => {}
4182 },
4183 [head, rest @ ..] => match (head, cur) {
4184 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4185 if let Some(slot) = members.iter_mut().find(|(mk, _)| mk == k) {
4186 remove_at(&mut slot.1, rest);
4187 }
4188 }
4189 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
4190 if let Some(slot) = items.get_mut(*i) {
4191 remove_at(slot, rest);
4192 }
4193 }
4194 _ => {}
4195 },
4196 }
4197 }
4198 for path_v in &args[1..] {
4199 let Value::Text(p) = path_v else {
4200 return Err(EvalError::TypeMismatch {
4201 detail: alloc::format!(
4202 "json_remove() paths must be text, got {}",
4203 crate::conversions::pg_type_name_for_error_opt(path_v.data_type())
4204 ),
4205 });
4206 };
4207 let steps = mysql_path_steps(p)?;
4208 if steps.is_empty() {
4209 return Err(EvalError::TypeMismatch {
4210 detail: "The path expression '$' is not allowed in this context".into(),
4211 });
4212 }
4213 remove_at(&mut doc, &steps);
4214 }
4215 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4217 doc.to_json_text(),
4218 ))))
4219}
4220
4221fn modify_at(cur: &mut JsonValue, steps: &[MysqlPathStep], f: &mut dyn FnMut(&mut JsonValue)) {
4224 match steps {
4225 [] => f(cur),
4226 [head, rest @ ..] => match (head, cur) {
4227 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4228 if let Some(slot) = members.iter_mut().find(|(mk, _)| mk == k) {
4229 modify_at(&mut slot.1, rest, f);
4230 }
4231 }
4232 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
4233 if let Some(slot) = items.get_mut(*i) {
4234 modify_at(slot, rest, f);
4235 }
4236 }
4237 _ => {}
4238 },
4239 }
4240}
4241
4242fn mysql_doc_and_pairs<'a>(
4244 args: &'a [Value<'_>],
4245 fn_name: &str,
4246) -> Result<Option<(JsonValue, &'a [Value<'a>])>, EvalError> {
4247 if args.len() < 3 || args.len() % 2 == 0 {
4248 return Err(EvalError::TypeMismatch {
4249 detail: alloc::format!(
4250 "{fn_name}() takes a document plus (path, value) pairs, got {} args",
4251 args.len()
4252 ),
4253 });
4254 }
4255 if args.iter().any(|a| matches!(a, Value::Null)) {
4256 return Ok(None);
4257 }
4258 let src = match &args[0] {
4259 Value::Json(s) | Value::Text(s) => s.as_ref(),
4260 other => {
4261 return Err(EvalError::TypeMismatch {
4262 detail: alloc::format!(
4263 "{fn_name}() document must be json, got {}",
4264 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4265 ),
4266 });
4267 }
4268 };
4269 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4270 detail: alloc::format!("{fn_name}(): invalid JSON: {e}"),
4271 })?;
4272 Ok(Some((doc, &args[1..])))
4273}
4274
4275pub fn mysql_json_array_append(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4279 let Some((mut doc, pairs)) = mysql_doc_and_pairs(args, "json_array_append")? else {
4280 return Ok(Value::Null);
4281 };
4282 for pair in pairs.chunks(2) {
4283 let Value::Text(p) = &pair[0] else {
4284 return Err(EvalError::TypeMismatch {
4285 detail: alloc::format!(
4286 "json_array_append() paths must be text, got {}",
4287 crate::conversions::pg_type_name_for_error_opt(pair[0].data_type())
4288 ),
4289 });
4290 };
4291 let steps = mysql_path_steps(p)?;
4292 let newval = value_to_jsonvalue(&pair[1])?;
4293 modify_at(&mut doc, &steps, &mut |v| match v {
4294 JsonValue::Array(items) => items.push(newval.clone()),
4295 other => {
4296 let old = core::mem::replace(other, JsonValue::Null);
4297 *other = JsonValue::Array(alloc::vec![old, newval.clone()]);
4298 }
4299 });
4300 }
4301 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4303 doc.to_json_text(),
4304 ))))
4305}
4306
4307pub fn mysql_json_array_insert(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4312 let Some((mut doc, pairs)) = mysql_doc_and_pairs(args, "json_array_insert")? else {
4313 return Ok(Value::Null);
4314 };
4315 for pair in pairs.chunks(2) {
4316 let Value::Text(p) = &pair[0] else {
4317 return Err(EvalError::TypeMismatch {
4318 detail: alloc::format!(
4319 "json_array_insert() paths must be text, got {}",
4320 crate::conversions::pg_type_name_for_error_opt(pair[0].data_type())
4321 ),
4322 });
4323 };
4324 let steps = mysql_path_steps(p)?;
4325 let Some(MysqlPathStep::Index(idx)) = steps.last() else {
4326 return Err(EvalError::TypeMismatch {
4327 detail: alloc::format!(
4328 "json_array_insert() path must end with an array index: {p:?}"
4329 ),
4330 });
4331 };
4332 let idx = *idx;
4333 let newval = value_to_jsonvalue(&pair[1])?;
4334 modify_at(&mut doc, &steps[..steps.len() - 1], &mut |v| {
4335 if let JsonValue::Array(items) = v {
4336 let at = idx.min(items.len());
4337 items.insert(at, newval.clone());
4338 }
4339 });
4340 }
4341 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4343 doc.to_json_text(),
4344 ))))
4345}
4346
4347fn mysql_contains(target: &JsonValue, cand: &JsonValue) -> bool {
4352 match (target, cand) {
4353 (JsonValue::Object(t), JsonValue::Object(c)) => c
4354 .iter()
4355 .all(|(ck, cv)| t.iter().any(|(tk, tv)| tk == ck && mysql_contains(tv, cv))),
4356 (JsonValue::Array(t), JsonValue::Array(c)) => {
4357 c.iter().all(|cv| t.iter().any(|tv| mysql_contains(tv, cv)))
4358 }
4359 (JsonValue::Array(t), scalar) => t.iter().any(|tv| mysql_contains(tv, scalar)),
4360 (JsonValue::Number(a), JsonValue::NumberText(b))
4362 | (JsonValue::NumberText(b), JsonValue::Number(a)) => {
4363 b.parse::<f64>().map(|x| x == *a).unwrap_or(false)
4364 }
4365 (JsonValue::NumberText(a), JsonValue::NumberText(b)) => {
4366 a == b
4367 || (a.parse::<f64>().ok().zip(b.parse::<f64>().ok()))
4368 .map(|(x, y)| x == y)
4369 .unwrap_or(false)
4370 }
4371 (a, b) => a == b,
4372 }
4373}
4374
4375pub fn mysql_json_contains(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4378 if !matches!(args.len(), 2 | 3) {
4379 return Err(EvalError::TypeMismatch {
4380 detail: alloc::format!("json_contains() takes 2 or 3 args, got {}", args.len()),
4381 });
4382 }
4383 if args.iter().any(|a| matches!(a, Value::Null)) {
4384 return Ok(Value::Null);
4385 }
4386 let parse_arg = |v: &Value<'_>, which: &str| -> Result<JsonValue, EvalError> {
4387 match v {
4388 Value::Json(s) | Value::Text(s) => parse(s).map_err(|e| EvalError::TypeMismatch {
4389 detail: alloc::format!("json_contains(): invalid {which} JSON: {e}"),
4390 }),
4391 other => Err(EvalError::TypeMismatch {
4392 detail: alloc::format!(
4393 "json_contains() {which} must be json, got {}",
4394 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4395 ),
4396 }),
4397 }
4398 };
4399 let target = parse_arg(&args[0], "target")?;
4400 let cand = parse_arg(&args[1], "candidate")?;
4401 let effective = match args.get(2) {
4402 None => Some(&target),
4403 Some(Value::Text(p)) => {
4404 let steps = mysql_path_steps(p)?;
4405 mysql_path_get(&target, &steps)
4406 }
4407 Some(other) => {
4408 return Err(EvalError::TypeMismatch {
4409 detail: alloc::format!(
4410 "json_contains() path must be text, got {}",
4411 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4412 ),
4413 });
4414 }
4415 };
4416 match effective {
4417 None => Ok(Value::Null),
4418 Some(t) => Ok(Value::Bool(mysql_contains(t, &cand))),
4419 }
4420}
4421
4422fn merge_patch(target: JsonValue, patch: JsonValue) -> JsonValue {
4426 let JsonValue::Object(patch_members) = patch else {
4427 return patch;
4428 };
4429 let mut out = match target {
4430 JsonValue::Object(members) => members,
4431 _ => Vec::new(),
4432 };
4433 for (k, v) in patch_members {
4434 if matches!(v, JsonValue::Null) {
4435 out.retain(|(mk, _)| *mk != k);
4436 } else if let Some(slot) = out.iter_mut().find(|(mk, _)| *mk == k) {
4437 let old = core::mem::replace(&mut slot.1, JsonValue::Null);
4438 slot.1 = merge_patch(old, v);
4439 } else {
4440 out.push((k, merge_patch(JsonValue::Null, v)));
4442 }
4443 }
4444 JsonValue::Object(out)
4445}
4446
4447fn merge_preserve(a: JsonValue, b: JsonValue) -> JsonValue {
4452 match (a, b) {
4453 (JsonValue::Object(mut ma), JsonValue::Object(mb)) => {
4454 for (k, v) in mb {
4455 if let Some(pos) = ma.iter().position(|(mk, _)| *mk == k) {
4456 let (_, old) = ma.remove(pos);
4457 ma.insert(pos, (k, merge_preserve(old, v)));
4458 } else {
4459 ma.push((k, v));
4460 }
4461 }
4462 JsonValue::Object(ma)
4463 }
4464 (JsonValue::Array(mut xs), JsonValue::Array(ys)) => {
4465 xs.extend(ys);
4466 JsonValue::Array(xs)
4467 }
4468 (JsonValue::Array(mut xs), scalar) => {
4469 xs.push(scalar);
4470 JsonValue::Array(xs)
4471 }
4472 (scalar, JsonValue::Array(ys)) => {
4473 let mut xs = alloc::vec![scalar];
4474 xs.extend(ys);
4475 JsonValue::Array(xs)
4476 }
4477 (sa, sb) => JsonValue::Array(alloc::vec![sa, sb]),
4478 }
4479}
4480
4481fn mysql_json_merge(
4482 args: &[Value<'_>],
4483 fn_name: &str,
4484 combine: fn(JsonValue, JsonValue) -> JsonValue,
4485) -> Result<Value<'static>, EvalError> {
4486 if args.len() < 2 {
4487 return Err(EvalError::TypeMismatch {
4488 detail: alloc::format!("{fn_name}() takes at least 2 documents, got {}", args.len()),
4489 });
4490 }
4491 if args.iter().any(|a| matches!(a, Value::Null)) {
4492 return Ok(Value::Null);
4493 }
4494 let mut acc: Option<JsonValue> = None;
4495 for arg in args {
4496 let src = match arg {
4497 Value::Json(s) | Value::Text(s) => s.as_ref(),
4498 other => {
4499 return Err(EvalError::TypeMismatch {
4500 detail: alloc::format!(
4501 "{fn_name}() arguments must be json, got {}",
4502 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4503 ),
4504 });
4505 }
4506 };
4507 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4508 detail: alloc::format!("{fn_name}(): invalid JSON: {e}"),
4509 })?;
4510 acc = Some(match acc {
4511 None => doc,
4512 Some(prev) => combine(prev, doc),
4513 });
4514 }
4515 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4517 acc.unwrap().to_json_text(),
4518 ))))
4519}
4520
4521pub fn mysql_json_merge_patch(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4523 mysql_json_merge(args, "json_merge_patch", merge_patch)
4524}
4525
4526pub fn mysql_json_merge_preserve(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4529 mysql_json_merge(args, "json_merge_preserve", merge_preserve)
4530}
4531
4532pub fn mysql_json_overlaps(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4536 if args.len() != 2 {
4537 return Err(EvalError::TypeMismatch {
4538 detail: alloc::format!("json_overlaps() takes 2 args, got {}", args.len()),
4539 });
4540 }
4541 if args.iter().any(|a| matches!(a, Value::Null)) {
4542 return Ok(Value::Null);
4543 }
4544 let parse_arg = |v: &Value<'_>| -> Result<JsonValue, EvalError> {
4545 match v {
4546 Value::Json(s) | Value::Text(s) => parse(s).map_err(|e| EvalError::TypeMismatch {
4547 detail: alloc::format!("json_overlaps(): invalid JSON: {e}"),
4548 }),
4549 other => Err(EvalError::TypeMismatch {
4550 detail: alloc::format!(
4551 "json_overlaps() arguments must be json, got {}",
4552 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4553 ),
4554 }),
4555 }
4556 };
4557 let a = parse_arg(&args[0])?;
4558 let b = parse_arg(&args[1])?;
4559 let overlaps = match (&a, &b) {
4560 (JsonValue::Array(xs), JsonValue::Array(ys)) => xs.iter().any(|x| {
4561 ys.iter()
4562 .any(|y| mysql_contains(x, y) && mysql_contains(y, x))
4563 }),
4564 (JsonValue::Object(ma), JsonValue::Object(mb)) => ma.iter().any(|(k, v)| {
4565 mb.iter()
4566 .any(|(k2, v2)| k == k2 && mysql_contains(v, v2) && mysql_contains(v2, v))
4567 }),
4568 (JsonValue::Array(xs), scalar) | (scalar, JsonValue::Array(xs)) => xs
4569 .iter()
4570 .any(|x| mysql_contains(x, scalar) && mysql_contains(scalar, x)),
4571 (sa, sb) => mysql_contains(sa, sb) && mysql_contains(sb, sa),
4572 };
4573 Ok(Value::Bool(overlaps))
4574}
4575
4576fn like_match(text: &[char], pat: &[char], escape: char) -> bool {
4579 match pat {
4580 [] => text.is_empty(),
4581 ['%', rest @ ..] => (0..=text.len()).any(|skip| like_match(&text[skip..], rest, escape)),
4582 ['_', rest @ ..] => !text.is_empty() && like_match(&text[1..], rest, escape),
4583 [e, lit, rest @ ..] if *e == escape => {
4584 text.first() == Some(lit) && like_match(&text[1..], rest, escape)
4585 }
4586 [c, rest @ ..] => text.first() == Some(c) && like_match(&text[1..], rest, escape),
4587 }
4588}
4589
4590fn push_path_step(out: &mut String, step_key: Option<&str>, step_idx: Option<usize>) {
4593 if let Some(k) = step_key {
4594 let ident_shaped = !k.is_empty()
4595 && k.chars().all(|c| c.is_alphanumeric() || c == '_')
4596 && !k.chars().next().unwrap().is_numeric();
4597 if ident_shaped {
4598 out.push('.');
4599 out.push_str(k);
4600 } else {
4601 out.push_str(".\"");
4602 for c in k.chars() {
4603 if c == '"' || c == '\\' {
4604 out.push('\\');
4605 }
4606 out.push(c);
4607 }
4608 out.push('"');
4609 }
4610 }
4611 if let Some(i) = step_idx {
4612 out.push('[');
4613 out.push_str(&alloc::format!("{i}"));
4614 out.push(']');
4615 }
4616}
4617
4618pub fn mysql_json_search(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4624 if args.len() < 3 {
4625 return Err(EvalError::TypeMismatch {
4626 detail: alloc::format!(
4627 "json_search() takes doc, one/all, pattern [, escape [, path...]], got {} args",
4628 args.len()
4629 ),
4630 });
4631 }
4632 if args[..3].iter().any(|a| matches!(a, Value::Null)) {
4633 return Ok(Value::Null);
4634 }
4635 let src = match &args[0] {
4636 Value::Json(s) | Value::Text(s) => s.as_ref(),
4637 other => {
4638 return Err(EvalError::TypeMismatch {
4639 detail: alloc::format!(
4640 "json_search() document must be json, got {}",
4641 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4642 ),
4643 });
4644 }
4645 };
4646 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4647 detail: alloc::format!("json_search(): invalid JSON: {e}"),
4648 })?;
4649 let one = match &args[1] {
4650 Value::Text(m) if m.eq_ignore_ascii_case("one") => true,
4651 Value::Text(m) if m.eq_ignore_ascii_case("all") => false,
4652 other => {
4653 return Err(EvalError::TypeMismatch {
4654 detail: alloc::format!(
4655 "json_search() second arg must be 'one' or 'all', got {other:?}"
4656 ),
4657 });
4658 }
4659 };
4660 let Value::Text(pattern) = &args[2] else {
4661 return Err(EvalError::TypeMismatch {
4662 detail: alloc::format!(
4663 "json_search() pattern must be text, got {}",
4664 crate::conversions::pg_type_name_for_error_opt(args[2].data_type())
4665 ),
4666 });
4667 };
4668 let escape = match args.get(3) {
4669 None | Some(Value::Null) => '\\',
4670 Some(Value::Text(e)) if e.chars().count() == 1 => e.chars().next().unwrap(),
4671 Some(other) => {
4672 return Err(EvalError::TypeMismatch {
4673 detail: alloc::format!(
4674 "json_search() escape must be a single character, got {other:?}"
4675 ),
4676 });
4677 }
4678 };
4679 let pat: Vec<char> = pattern.chars().collect();
4680 fn walk(
4681 v: &JsonValue,
4682 path: &str,
4683 pat: &[char],
4684 escape: char,
4685 hits: &mut Vec<String>,
4686 stop_at_one: bool,
4687 ) {
4688 if stop_at_one && !hits.is_empty() {
4689 return;
4690 }
4691 match v {
4692 JsonValue::String(s) => {
4693 let chars: Vec<char> = s.chars().collect();
4694 if like_match(&chars, pat, escape) {
4695 hits.push(path.to_string());
4696 }
4697 }
4698 JsonValue::Object(members) => {
4699 for (k, mv) in members {
4700 let mut p = path.to_string();
4701 push_path_step(&mut p, Some(k), None);
4702 walk(mv, &p, pat, escape, hits, stop_at_one);
4703 }
4704 }
4705 JsonValue::Array(items) => {
4706 for (i, iv) in items.iter().enumerate() {
4707 let mut p = path.to_string();
4708 push_path_step(&mut p, None, Some(i));
4709 walk(iv, &p, pat, escape, hits, stop_at_one);
4710 }
4711 }
4712 _ => {}
4713 }
4714 }
4715 let mut hits: Vec<String> = Vec::new();
4716 let start_paths: Vec<String> = args
4717 .get(4..)
4718 .unwrap_or(&[])
4719 .iter()
4720 .map(|v| match v {
4721 Value::Text(p) => Ok(p.to_string()),
4722 other => Err(EvalError::TypeMismatch {
4723 detail: alloc::format!(
4724 "json_search() paths must be text, got {}",
4725 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4726 ),
4727 }),
4728 })
4729 .collect::<Result<_, _>>()?;
4730 if start_paths.is_empty() {
4731 walk(&doc, "$", &pat, escape, &mut hits, one);
4732 } else {
4733 for p in &start_paths {
4734 let steps = mysql_path_steps(p)?;
4735 if let Some(sub) = mysql_path_get(&doc, &steps) {
4736 walk(sub, p.trim(), &pat, escape, &mut hits, one);
4737 }
4738 }
4739 }
4740 match hits.len() {
4741 0 => Ok(Value::Null),
4742 1 => Ok(Value::Json(alloc::borrow::Cow::Owned(
4743 JsonValue::String(hits.into_iter().next().unwrap()).to_json_text(),
4744 ))),
4745 _ => {
4746 let arr = JsonValue::Array(hits.into_iter().map(JsonValue::String).collect());
4747 Ok(Value::Json(alloc::borrow::Cow::Owned(arr.to_json_text())))
4748 }
4749 }
4750}
4751
4752pub fn mysql_json_value(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4757 if args.len() != 2 {
4758 return Err(EvalError::TypeMismatch {
4759 detail: alloc::format!("json_value() takes 2 args, got {}", args.len()),
4760 });
4761 }
4762 if args.iter().any(|a| matches!(a, Value::Null)) {
4763 return Ok(Value::Null);
4764 }
4765 let src = match &args[0] {
4766 Value::Json(s) | Value::Text(s) => s.as_ref(),
4767 other => {
4768 return Err(EvalError::TypeMismatch {
4769 detail: alloc::format!(
4770 "json_value() document must be json, got {}",
4771 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4772 ),
4773 });
4774 }
4775 };
4776 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4777 detail: alloc::format!("json_value(): invalid JSON: {e}"),
4778 })?;
4779 let Value::Text(p) = &args[1] else {
4780 return Err(EvalError::TypeMismatch {
4781 detail: alloc::format!(
4782 "json_value() path must be text, got {}",
4783 crate::conversions::pg_type_name_for_error_opt(args[1].data_type())
4784 ),
4785 });
4786 };
4787 let steps = mysql_path_steps(p)?;
4788 match mysql_path_get(&doc, &steps) {
4789 None => Ok(Value::Null),
4790 Some(JsonValue::Null) => Ok(Value::Null),
4791 Some(v) => Ok(Value::text(v.as_text())),
4792 }
4793}
4794
4795fn is_json_scalar(v: &JsonValue) -> bool {
4799 !matches!(v, JsonValue::Object(_) | JsonValue::Array(_))
4800}
4801
4802#[cfg(test)]
4803mod round619_number_fast_path {
4804 use super::*;
4805
4806 #[test]
4810 fn fast_path_agrees_with_the_full_canonicaliser() {
4811 let mut cases: Vec<String> = Vec::new();
4812 for sign in ["", "-"] {
4813 for body in [
4814 "0",
4815 "1",
4816 "7",
4817 "10",
4818 "123",
4819 "0123",
4820 "00",
4821 "000",
4822 "9223372036854775807",
4823 "170141183460469231731687303715884105727",
4824 "1.0",
4825 "1.5",
4826 "0.5",
4827 ".5",
4828 "1.",
4829 "1e3",
4830 "1E3",
4831 "1e-3",
4832 "1.5e2",
4833 "1.50",
4834 "100",
4835 "0.0",
4836 "0.00",
4837 "10.010",
4838 "1e0",
4839 "1e+3",
4840 "0e0",
4841 "12345678901234567890.12345678901234567890",
4842 ] {
4843 cases.push(alloc::format!("{sign}{body}"));
4844 }
4845 }
4846 for c in &cases {
4847 assert_eq!(
4848 canon_json_number(c).as_ref(),
4849 canon_json_number_slow(c).as_str(),
4850 "lexeme {c:?} canonicalises differently through the shortcut"
4851 );
4852 }
4853 }
4854
4855 #[test]
4857 fn one_entry_object_writes_what_the_general_writer_writes() {
4858 for src in [
4859 r#"{"a":1}"#,
4860 r#"{"":1}"#,
4861 r#"{"a":{"b":2}}"#,
4862 r#"{"a":[1,2,3]}"#,
4863 r#"{"a\"b":"c\\d"}"#,
4864 r#"{"日本":"語"}"#,
4865 r#"{"a":null}"#,
4866 r#"{}"#,
4867 ] {
4868 let JsonValue::Object(entries) = parse(src).expect("valid json") else {
4869 panic!("{src} is not an object");
4870 };
4871 let mut fast = String::new();
4872 write_json_canonical(&JsonValue::Object(entries.clone()), &mut fast);
4873 let mut general = String::new();
4874 write_object_general(&entries, &mut general);
4875 assert_eq!(fast, general, "{src}");
4876 }
4877 }
4878}