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 for pair in args.chunks_exact(2) {
264 let key = match &pair[0] {
266 Value::Text(s) | Value::BpChar(s) => s.to_string(),
267 Value::SmallInt(n) => alloc::format!("{n}"),
268 Value::Int(n) => alloc::format!("{n}"),
269 Value::BigInt(n) => alloc::format!("{n}"),
270 _ => return None,
271 };
272 entries.push((key, simple_scalar_json(&pair[1])?));
273 }
274 Some(Value::json(json_canonical_string(&JsonValue::Object(
275 entries,
276 ))))
277}
278
279pub fn canonicalize_value(v: Value<'static>) -> Value<'static> {
280 match v {
281 Value::Json(s) => {
282 Value::json(canonicalize_jsonb(s.as_ref()).unwrap_or_else(|_| s.into_owned()))
283 }
284 other => other,
285 }
286}
287
288fn accessor_result(v: &JsonValue, as_text: bool) -> Value<'static> {
294 if as_text && !matches!(v, JsonValue::Array(_) | JsonValue::Object(_)) {
295 return Value::text(v.as_text());
296 }
297 let s = json_canonical_string(v);
298 if as_text {
299 Value::text(s)
300 } else {
301 Value::json(s)
302 }
303}
304
305fn skip_ws_at(b: &[u8], mut i: usize) -> usize {
317 while i < b.len() && matches!(b[i], b' ' | b'\t' | b'\n' | b'\r') {
318 i += 1;
319 }
320 i
321}
322
323fn scan_string(b: &[u8], i: usize) -> Option<usize> {
326 debug_assert_eq!(b.get(i), Some(&b'"'));
327 let mut j = i + 1;
328 while j < b.len() {
329 match b[j] {
330 b'\\' => j += 2,
331 b'"' => return Some(j + 1),
332 _ => j += 1,
333 }
334 }
335 None
336}
337
338fn scan_value(b: &[u8], i: usize) -> Option<usize> {
344 match *b.get(i)? {
345 b'"' => scan_string(b, i),
346 open @ (b'{' | b'[') => {
347 let close = if open == b'{' { b'}' } else { b']' };
348 let mut depth = 0usize;
349 let mut j = i;
350 while j < b.len() {
351 match b[j] {
352 b'"' => j = scan_string(b, j)?,
353 c if c == open => {
354 depth += 1;
355 j += 1;
356 }
357 c if c == close => {
358 depth -= 1;
359 j += 1;
360 if depth == 0 {
361 return Some(j);
362 }
363 }
364 _ => j += 1,
365 }
366 }
367 None
368 }
369 _ => {
370 let mut j = i;
371 while j < b.len() && !matches!(b[j], b',' | b'}' | b']' | b' ' | b'\t' | b'\n' | b'\r')
372 {
373 j += 1;
374 }
375 (j > i).then_some(j)
376 }
377 }
378}
379
380fn decode_string_token(tok: &str) -> Option<String> {
382 match parse(tok).ok()? {
383 JsonValue::String(s) => Some(s),
384 _ => None,
385 }
386}
387
388fn key_token_eq(tok: &str, key: &str) -> bool {
401 let inner = match tok.strip_prefix('"').and_then(|t| t.strip_suffix('"')) {
402 Some(i) => i,
403 None => return false,
404 };
405 if inner.as_bytes().contains(&b'\\') {
406 return decode_string_token(tok).is_some_and(|d| d == key);
407 }
408 inner == key
409}
410
411fn locate_member<'a>(src: &'a str, key: &str) -> Option<&'a str> {
415 let b = src.as_bytes();
416 let mut i = skip_ws_at(b, 0);
417 if b.get(i) != Some(&b'{') {
418 return None;
419 }
420 i += 1;
421 let mut found: Option<&'a str> = None;
422 loop {
423 i = skip_ws_at(b, i);
424 match b.get(i)? {
425 b'}' => return found,
426 b'"' => {}
427 _ => return None,
428 }
429 let key_end = scan_string(b, i)?;
430 let key_tok = src.get(i..key_end)?;
431 i = skip_ws_at(b, key_end);
432 if b.get(i) != Some(&b':') {
433 return None;
434 }
435 i = skip_ws_at(b, i + 1);
436 let val_end = scan_value(b, i)?;
437 if key_token_eq(key_tok, key) {
438 found = Some(src.get(i..val_end)?);
439 }
440 i = skip_ws_at(b, val_end);
441 match b.get(i)? {
442 b',' => i += 1,
443 b'}' => return found,
444 _ => return None,
445 }
446 }
447}
448
449fn locate_index(src: &str, idx: i64) -> Option<&str> {
452 let b = src.as_bytes();
453 let mut i = skip_ws_at(b, 0);
454 if b.get(i) != Some(&b'[') {
455 return None;
456 }
457 i += 1;
458 let mut spans: Vec<(usize, usize)> = Vec::new();
459 loop {
460 i = skip_ws_at(b, i);
461 if b.get(i)? == &b']' {
462 break;
463 }
464 let end = scan_value(b, i)?;
465 spans.push((i, end));
466 i = skip_ws_at(b, end);
467 match b.get(i)? {
468 b',' => i += 1,
469 b']' => break,
470 _ => return None,
471 }
472 }
473 let n = if idx >= 0 {
474 usize::try_from(idx).ok()?
475 } else {
476 usize::try_from(i64::try_from(spans.len()).ok()? + idx).ok()?
477 };
478 let (s, e) = *spans.get(n)?;
479 src.get(s..e)
480}
481
482fn verbatim_accessor_result(slice: &str, as_text: bool) -> Value<'static> {
486 match slice.as_bytes().first() {
487 Some(b'n') if slice == "null" => {
488 if as_text {
489 Value::Null
490 } else {
491 Value::json("null")
492 }
493 }
494 Some(b'"') if as_text => decode_string_token(slice).map_or(Value::Null, Value::text),
495 _ if as_text => Value::text(slice.to_string()),
496 _ => Value::json(slice.to_string()),
497 }
498}
499
500fn json_canonical_string(v: &JsonValue) -> String {
502 let mut s = String::new();
503 write_json_canonical(v, &mut s);
504 s
505}
506
507fn write_json_canonical(v: &JsonValue, out: &mut String) {
508 match v {
509 JsonValue::Null => out.push_str("null"),
510 JsonValue::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
511 JsonValue::Number(x) => out.push_str(&canon_json_number(&alloc::format!("{x}"))),
512 JsonValue::NumberText(s) => out.push_str(&canon_json_number(s)),
513 JsonValue::String(s) => write_json_string(s, out),
514 JsonValue::Array(items) => {
515 out.push('[');
516 for (i, it) in items.iter().enumerate() {
517 if i > 0 {
518 out.push_str(", ");
519 }
520 write_json_canonical(it, out);
521 }
522 out.push(']');
523 }
524 JsonValue::Object(entries) => {
525 if entries.len() <= 1 {
528 out.push('{');
529 if let Some((k, val)) = entries.first() {
530 write_json_string(k, out);
531 out.push_str(": ");
532 write_json_canonical(val, out);
533 }
534 out.push('}');
535 return;
536 }
537 write_object_general(entries, out);
538 }
539 }
540}
541
542fn write_object_general(entries: &[(String, JsonValue)], out: &mut String) {
545 {
546 {
547 let mut deduped: Vec<(&String, &JsonValue)> = Vec::new();
550 for (k, val) in entries {
551 if let Some(slot) = deduped.iter_mut().find(|(mk, _)| *mk == k) {
552 slot.1 = val;
553 } else {
554 deduped.push((k, val));
555 }
556 }
557 deduped.sort_by(|a, b| {
558 a.0.len()
559 .cmp(&b.0.len())
560 .then_with(|| a.0.as_bytes().cmp(b.0.as_bytes()))
561 });
562 out.push('{');
563 for (i, (k, val)) in deduped.iter().enumerate() {
564 if i > 0 {
565 out.push_str(", ");
566 }
567 write_json_string(k, out);
568 out.push_str(": ");
569 write_json_canonical(val, out);
570 }
571 out.push('}');
572 }
573 }
574}
575
576fn canon_json_number(lexeme: &str) -> alloc::borrow::Cow<'_, str> {
588 let body = lexeme.strip_prefix('-').unwrap_or(lexeme);
589 if !body.is_empty()
590 && body.bytes().all(|b| b.is_ascii_digit())
591 && (body == "0" || !body.starts_with('0'))
593 && !(lexeme.starts_with('-') && body == "0")
595 {
596 return alloc::borrow::Cow::Borrowed(lexeme);
597 }
598 alloc::borrow::Cow::Owned(canon_json_number_slow(lexeme))
599}
600
601fn canon_json_number_slow(lexeme: &str) -> String {
602 let neg = lexeme.starts_with('-');
603 let body = lexeme.trim_start_matches(['-', '+']);
604 let (mantissa, exp) = match body.split_once(['e', 'E']) {
606 Some((m, e)) => (m, e.parse::<i64>().unwrap_or(0)),
607 None => (body, 0),
608 };
609 let (int_part, frac_part) = match mantissa.split_once('.') {
610 Some((i, f)) => (i, f),
611 None => (mantissa, ""),
612 };
613 let digits: String = alloc::format!("{int_part}{frac_part}");
614 let shift = frac_part.len() as i64 - exp;
618 let all_zero = digits.bytes().all(|b| b == b'0');
619 let sign = if neg && !all_zero { "-" } else { "" };
620 let strip = |s: &str| -> String {
621 let t = s.trim_start_matches('0');
622 if t.is_empty() { "0".into() } else { t.into() }
623 };
624 if shift <= 0 {
625 let zeros = "0".repeat((-shift) as usize);
627 alloc::format!("{sign}{}{zeros}", strip(&digits))
628 } else {
629 let shift = shift as usize;
630 let (int_str, frac_str) = if digits.len() > shift {
631 (
632 digits[..digits.len() - shift].to_string(),
633 digits[digits.len() - shift..].to_string(),
634 )
635 } else {
636 (
637 "0".to_string(),
638 alloc::format!("{}{}", "0".repeat(shift - digits.len()), digits),
639 )
640 };
641 alloc::format!("{sign}{}.{frac_str}", strip(&int_str))
642 }
643}
644
645pub fn jsonb_each_text_rows(arg: &Value) -> Result<Vec<(String, Option<String>)>, EvalError> {
659 each_rows(arg, true, "jsonb_each_text")
660}
661
662pub fn each_rows(
668 arg: &Value,
669 as_text: bool,
670 fn_name: &str,
671) -> Result<Vec<(String, Option<String>)>, EvalError> {
672 let src = match arg {
673 Value::Null => return Ok(Vec::new()),
674 Value::Json(s) | Value::Text(s) => s.as_ref(),
675 other => {
676 return Err(EvalError::TypeMismatch {
677 detail: alloc::format!(
678 "{fn_name}: argument must be JSON / JSONB, got {}",
679 crate::conversions::pg_type_name_for_error_opt(other.data_type())
680 ),
681 });
682 }
683 };
684 let parsed = parse(src).map_err(|e| EvalError::TypeMismatch {
685 detail: alloc::format!("{fn_name}: invalid JSON: {e}"),
686 })?;
687 match parsed {
688 JsonValue::Object(entries) => {
689 let mut out: Vec<(String, Option<String>)> = Vec::with_capacity(entries.len());
690 for (k, v) in entries {
691 let text = if as_text {
692 match &v {
693 JsonValue::Null => None,
694 JsonValue::Bool(b) => Some(if *b {
695 "true".to_string()
696 } else {
697 "false".to_string()
698 }),
699 JsonValue::Number(_) | JsonValue::NumberText(_) | JsonValue::String(_) => {
700 Some(v.as_text())
701 }
702 JsonValue::Array(_) | JsonValue::Object(_) => {
703 Some(json_canonical_string(&v))
704 }
705 }
706 } else {
707 Some(json_canonical_string(&v))
708 };
709 out.push((k, text));
710 }
711 Ok(out)
712 }
713 other => Err(EvalError::TypeMismatch {
714 detail: alloc::format!("cannot call {fn_name} on a non-object ({other:?})"),
715 }),
716 }
717}
718
719pub fn array_element_rows(
729 arg: &Value,
730 as_text: bool,
731 fn_name: &str,
732) -> Result<Vec<Option<String>>, EvalError> {
733 let src = match arg {
734 Value::Null => return Ok(Vec::new()),
735 Value::Json(s) | Value::Text(s) => s.as_ref(),
736 other => {
737 return Err(EvalError::TypeMismatch {
738 detail: alloc::format!(
739 "{fn_name}: argument must be JSON / JSONB, got {}",
740 crate::conversions::pg_type_name_for_error_opt(other.data_type())
741 ),
742 });
743 }
744 };
745 let parsed = parse(src).map_err(|e| EvalError::TypeMismatch {
746 detail: alloc::format!("{fn_name}: invalid JSON: {e}"),
747 })?;
748 match parsed {
749 JsonValue::Array(items) => {
750 let mut out: Vec<Option<String>> = Vec::with_capacity(items.len());
751 for v in items {
752 let text = if as_text {
753 match &v {
754 JsonValue::Null => None,
755 JsonValue::Bool(b) => Some(if *b {
756 "true".to_string()
757 } else {
758 "false".to_string()
759 }),
760 JsonValue::Number(_) | JsonValue::NumberText(_) | JsonValue::String(_) => {
761 Some(v.as_text())
762 }
763 JsonValue::Array(_) | JsonValue::Object(_) => {
764 Some(json_canonical_string(&v))
765 }
766 }
767 } else {
768 Some(json_canonical_string(&v))
770 };
771 out.push(text);
772 }
773 Ok(out)
774 }
775 other => Err(EvalError::TypeMismatch {
776 detail: alloc::format!(
777 "cannot extract elements from a non-array ({fn_name}: got {other:?})"
778 ),
779 }),
780 }
781}
782
783pub fn path_walk(lhs: &Value, rhs: &Value, as_text: bool) -> Result<Value<'static>, EvalError> {
785 let src = match lhs {
786 Value::Json(s) | Value::Text(s) => s.as_ref(),
787 Value::Null => return Ok(Value::Null),
788 other => {
789 return Err(EvalError::TypeMismatch {
790 detail: alloc::format!(
791 "JSON path walk: left side must be JSON or TEXT, got {}",
792 crate::conversions::pg_type_name_for_error_opt(other.data_type())
793 ),
794 });
795 }
796 };
797 let owned_steps: Vec<String>;
801 let path: Vec<String> = match rhs {
802 Value::TextArray(items) => {
803 if items.iter().any(Option::is_none) {
804 return Ok(Value::Null);
805 }
806 owned_steps = items.iter().flatten().cloned().collect();
807 owned_steps
808 }
809 Value::Text(s) | Value::Json(s) => parse_text_array(s.as_ref())?,
810 Value::Null => return Ok(Value::Null),
811 other => {
812 return Err(EvalError::TypeMismatch {
813 detail: alloc::format!(
814 "JSON path walk: right side must be TEXT, got {}",
815 crate::conversions::pg_type_name_for_error_opt(other.data_type())
816 ),
817 });
818 }
819 };
820 validate_unless_known_json(lhs, src, "path walk")?;
823 let mut cur: &str = src;
824 for step in &path {
825 let at = skip_ws_at(cur.as_bytes(), 0);
826 let next = match cur.as_bytes().get(at) {
827 Some(b'{') => locate_member(cur, step),
828 Some(b'[') => match step.parse::<i64>() {
829 Ok(idx) => locate_index(cur, idx),
830 Err(_) => return Ok(Value::Null),
831 },
832 _ => return Ok(Value::Null),
833 };
834 cur = match next {
835 None => return Ok(Value::Null),
836 Some(slice) => slice,
837 };
838 }
839 Ok(verbatim_accessor_result(cur, as_text))
840}
841
842pub fn key_exists(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
857 let lhs_text = match lhs {
858 Value::Json(s) | Value::Text(s) => s.as_ref(),
859 Value::Null => return Ok(Value::Null),
860 other => {
861 return Err(EvalError::TypeMismatch {
862 detail: alloc::format!(
863 "JSON ?: left side must be JSON or TEXT, got {}",
864 crate::conversions::pg_type_name_for_error_opt(other.data_type())
865 ),
866 });
867 }
868 };
869 let key = match rhs {
870 Value::Text(s) => s.as_ref(),
871 Value::Null => return Ok(Value::Null),
872 other => {
873 return Err(EvalError::TypeMismatch {
874 detail: alloc::format!(
875 "JSON ?: right side must be TEXT, got {}",
876 crate::conversions::pg_type_name_for_error_opt(other.data_type())
877 ),
878 });
879 }
880 };
881 let doc = parse(lhs_text).map_err(|e| EvalError::TypeMismatch {
882 detail: alloc::format!("invalid JSON on left of ?: {e}"),
883 })?;
884 Ok(Value::Bool(node_has_key(&doc, key)))
885}
886
887fn node_has_key(v: &JsonValue, key: &str) -> bool {
888 match v {
889 JsonValue::Object(members) => members.iter().any(|(k, _)| k == key),
890 JsonValue::Array(items) => items
891 .iter()
892 .any(|item| matches!(item, JsonValue::String(s) if s == key)),
893 JsonValue::String(s) => s == key,
894 _ => false,
895 }
896}
897
898fn collect_keys(v: &Value) -> Result<Option<Vec<String>>, EvalError> {
901 match v {
902 Value::Null => Ok(None),
903 Value::TextArray(items) => Ok(Some(items.iter().filter_map(|x| x.clone()).collect())),
904 Value::Text(s) => Ok(Some(alloc::vec![s.to_string()])),
905 other => Err(EvalError::TypeMismatch {
906 detail: alloc::format!(
907 "JSON ?|/?&: right side must be TEXT[] or TEXT, got {}",
908 crate::conversions::pg_type_name_for_error_opt(other.data_type())
909 ),
910 }),
911 }
912}
913
914pub fn keys_any(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
917 let lhs_text = match lhs {
918 Value::Json(s) | Value::Text(s) => s.as_ref(),
919 Value::Null => return Ok(Value::Null),
920 other => {
921 return Err(EvalError::TypeMismatch {
922 detail: alloc::format!(
923 "JSON ?|: left side must be JSON or TEXT, got {}",
924 crate::conversions::pg_type_name_for_error_opt(other.data_type())
925 ),
926 });
927 }
928 };
929 let Some(keys) = collect_keys(rhs)? else {
930 return Ok(Value::Null);
931 };
932 let doc = parse(lhs_text).map_err(|e| EvalError::TypeMismatch {
933 detail: alloc::format!("invalid JSON on left of ?|: {e}"),
934 })?;
935 Ok(Value::Bool(keys.iter().any(|k| node_has_key(&doc, k))))
936}
937
938pub fn keys_all(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().all(|k| node_has_key(&doc, k))))
960}
961
962pub fn contains(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
963 let lhs_text = match lhs {
964 Value::Json(s) | Value::Text(s) => s.as_ref(),
965 Value::Null => return Ok(Value::Null),
966 other => {
967 return Err(EvalError::TypeMismatch {
968 detail: alloc::format!(
969 "JSON @>: left side must be JSON or TEXT, got {}",
970 crate::conversions::pg_type_name_for_error_opt(other.data_type())
971 ),
972 });
973 }
974 };
975 let rhs_text = match rhs {
976 Value::Json(s) | Value::Text(s) => s.as_ref(),
977 Value::Null => return Ok(Value::Null),
978 other => {
979 return Err(EvalError::TypeMismatch {
980 detail: alloc::format!(
981 "JSON @>: right side must be JSON or TEXT, got {}",
982 crate::conversions::pg_type_name_for_error_opt(other.data_type())
983 ),
984 });
985 }
986 };
987 let lhs_doc = parse(lhs_text).map_err(|e| EvalError::TypeMismatch {
988 detail: alloc::format!("invalid JSON on left of @>: {e}"),
989 })?;
990 let rhs_doc = parse(rhs_text).map_err(|e| EvalError::TypeMismatch {
991 detail: alloc::format!("invalid JSON on right of @>: {e}"),
992 })?;
993 let result = match (&lhs_doc, &rhs_doc) {
1000 (JsonValue::Array(items), scalar)
1001 if !matches!(scalar, JsonValue::Array(_) | JsonValue::Object(_)) =>
1002 {
1003 items.iter().any(|it| json_eq(it, scalar))
1004 }
1005 _ => json_contains(&lhs_doc, &rhs_doc),
1006 };
1007 Ok(Value::Bool(result))
1008}
1009
1010pub fn equals(lhs: &Value, rhs: &Value) -> Result<bool, EvalError> {
1018 let lhs_text = match lhs {
1019 Value::Json(s) | Value::Text(s) => s.as_ref(),
1020 other => {
1021 return Err(EvalError::TypeMismatch {
1022 detail: alloc::format!(
1023 "jsonb =: left side must be JSON or TEXT, got {}",
1024 crate::conversions::pg_type_name_for_error_opt(other.data_type())
1025 ),
1026 });
1027 }
1028 };
1029 let rhs_text = match rhs {
1030 Value::Json(s) | Value::Text(s) => s.as_ref(),
1031 other => {
1032 return Err(EvalError::TypeMismatch {
1033 detail: alloc::format!(
1034 "jsonb =: right side must be JSON or TEXT, got {}",
1035 crate::conversions::pg_type_name_for_error_opt(other.data_type())
1036 ),
1037 });
1038 }
1039 };
1040 let lhs_doc = parse(lhs_text).map_err(|e| EvalError::TypeMismatch {
1041 detail: alloc::format!("invalid JSON on left of =: {e}"),
1042 })?;
1043 let rhs_doc = parse(rhs_text).map_err(|e| EvalError::TypeMismatch {
1044 detail: alloc::format!("invalid JSON on right of =: {e}"),
1045 })?;
1046 Ok(json_eq(&lhs_doc, &rhs_doc))
1047}
1048
1049fn json_contains(lhs: &JsonValue, rhs: &JsonValue) -> bool {
1050 match (lhs, rhs) {
1051 (JsonValue::Object(l), JsonValue::Object(r)) => r
1052 .iter()
1053 .all(|(rk, rv)| l.iter().any(|(lk, lv)| lk == rk && json_contains(lv, rv))),
1054 (JsonValue::Array(l), JsonValue::Array(r)) => {
1055 r.iter().all(|rv| l.iter().any(|lv| json_contains(lv, rv)))
1056 }
1057 _ => json_eq(lhs, rhs),
1058 }
1059}
1060
1061fn json_eq(a: &JsonValue, b: &JsonValue) -> bool {
1062 match (a, b) {
1063 (JsonValue::Null, JsonValue::Null) => true,
1064 (JsonValue::Bool(x), JsonValue::Bool(y)) => x == y,
1065 (JsonValue::String(x), JsonValue::String(y)) => x == y,
1066 (
1071 JsonValue::Number(_) | JsonValue::NumberText(_),
1072 JsonValue::Number(_) | JsonValue::NumberText(_),
1073 ) => json_number_key(a) == json_number_key(b),
1074 (JsonValue::Array(x), JsonValue::Array(y)) => {
1075 x.len() == y.len() && x.iter().zip(y).all(|(a, b)| json_eq(a, b))
1076 }
1077 (JsonValue::Object(x), JsonValue::Object(y)) => {
1078 x.len() == y.len()
1079 && x.iter()
1080 .all(|(k, v)| y.iter().any(|(k2, v2)| k == k2 && json_eq(v, v2)))
1081 }
1082 _ => false,
1083 }
1084}
1085
1086fn numeric_eq_key(lexeme: &str) -> String {
1091 let c = canon_json_number(lexeme);
1092 if c.contains('.') {
1093 c.trim_end_matches('0').trim_end_matches('.').to_string()
1094 } else {
1095 c.into_owned()
1096 }
1097}
1098
1099fn json_number_key(v: &JsonValue) -> Option<String> {
1100 match v {
1101 JsonValue::NumberText(s) => Some(numeric_eq_key(s)),
1102 JsonValue::Number(x) => Some(numeric_eq_key(&alloc::format!("{x}"))),
1103 _ => None,
1104 }
1105}
1106
1107fn parse_text_array(s: &str) -> Result<Vec<String>, EvalError> {
1112 let trimmed = s.trim();
1113 let inner = if let Some(stripped) = trimmed.strip_prefix('{').and_then(|s| s.strip_suffix('}'))
1114 {
1115 stripped
1116 } else {
1117 return Err(EvalError::TypeMismatch {
1118 detail: alloc::format!("path walk: expected PG array literal `{{…}}`, got {s:?}"),
1119 });
1120 };
1121 if inner.trim().is_empty() {
1122 return Ok(Vec::new());
1123 }
1124 let mut out = Vec::new();
1125 let mut cur = String::new();
1126 let mut in_quotes = false;
1127 let mut chars = inner.chars().peekable();
1128 while let Some(c) = chars.next() {
1129 match c {
1130 '"' => in_quotes = !in_quotes,
1131 ',' if !in_quotes => {
1132 out.push(cur.trim().to_string());
1133 cur = String::new();
1134 }
1135 '\\' => {
1136 if let Some(&next) = chars.peek() {
1137 cur.push(next);
1138 chars.next();
1139 }
1140 }
1141 _ => cur.push(c),
1142 }
1143 }
1144 out.push(cur.trim().to_string());
1145 Ok(out)
1146}
1147
1148fn validate_unless_known_json(lhs: &Value, src: &str, what: &str) -> Result<(), EvalError> {
1168 if matches!(lhs, Value::Json(_)) {
1169 return Ok(());
1170 }
1171 parse(src).map(|_| ()).map_err(|e| EvalError::TypeMismatch {
1172 detail: alloc::format!("invalid JSON for {what}: {e}"),
1173 })
1174}
1175
1176pub fn path_get(lhs: &Value, rhs: &Value, as_text: bool) -> Result<Value<'static>, EvalError> {
1177 let src = match lhs {
1178 Value::Json(s) | Value::Text(s) => s.as_ref(),
1179 Value::Null => return Ok(Value::Null),
1180 other => {
1181 return Err(EvalError::TypeMismatch {
1182 detail: alloc::format!(
1183 "JSON path operator: left side must be JSON or TEXT, got {}",
1184 crate::conversions::pg_type_name_for_error_opt(other.data_type())
1185 ),
1186 });
1187 }
1188 };
1189 validate_unless_known_json(lhs, src, "path access")?;
1192 let located = match rhs {
1193 Value::Text(k) => locate_member(src, k),
1194 Value::Int(idx) => locate_index(src, i64::from(*idx)),
1195 Value::BigInt(idx) => locate_index(src, *idx),
1196 Value::Null => return Ok(Value::Null),
1197 _ => None,
1198 };
1199 Ok(located.map_or(Value::Null, |slice| {
1200 verbatim_accessor_result(slice, as_text)
1201 }))
1202}
1203
1204#[derive(Debug)]
1207pub enum ParseError {
1208 Unexpected(char, usize),
1209 Truncated,
1210 InvalidEscape(usize),
1211 InvalidNumber(usize),
1212}
1213
1214impl core::fmt::Display for ParseError {
1215 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1216 match self {
1217 Self::Unexpected(c, p) => write!(f, "unexpected {c:?} at offset {p}"),
1218 Self::Truncated => f.write_str("unexpected end of JSON input"),
1219 Self::InvalidEscape(p) => write!(f, "invalid string escape at offset {p}"),
1220 Self::InvalidNumber(p) => write!(f, "invalid number at offset {p}"),
1221 }
1222 }
1223}
1224
1225pub fn parse(src: &str) -> Result<JsonValue, ParseError> {
1226 let bytes = src.as_bytes();
1227 let mut p = 0;
1228 skip_ws(bytes, &mut p);
1229 let value = parse_value(bytes, &mut p)?;
1230 skip_ws(bytes, &mut p);
1231 if p != bytes.len() {
1232 return Err(ParseError::Unexpected(bytes[p] as char, p));
1233 }
1234 Ok(value)
1235}
1236
1237#[must_use]
1244pub fn jsonb_compare(a: &JsonValue, b: &JsonValue) -> core::cmp::Ordering {
1245 use core::cmp::Ordering;
1246 fn rank(v: &JsonValue) -> u8 {
1247 match v {
1248 JsonValue::Null => 0,
1249 JsonValue::String(_) => 1,
1250 JsonValue::Number(_) | JsonValue::NumberText(_) => 2,
1251 JsonValue::Bool(_) => 3,
1252 JsonValue::Array(_) => 4,
1253 JsonValue::Object(_) => 5,
1254 }
1255 }
1256 fn num(v: &JsonValue) -> f64 {
1257 match v {
1258 JsonValue::Number(x) => *x,
1259 JsonValue::NumberText(s) => s.parse::<f64>().unwrap_or(0.0),
1260 _ => 0.0,
1261 }
1262 }
1263 let (ra, rb) = (rank(a), rank(b));
1264 if ra != rb {
1265 return ra.cmp(&rb);
1266 }
1267 match (a, b) {
1268 (JsonValue::String(x), JsonValue::String(y)) => x.cmp(y),
1269 (JsonValue::Bool(x), JsonValue::Bool(y)) => x.cmp(y),
1270 (
1271 JsonValue::Number(_) | JsonValue::NumberText(_),
1272 JsonValue::Number(_) | JsonValue::NumberText(_),
1273 ) => num(a).partial_cmp(&num(b)).unwrap_or(Ordering::Equal),
1274 (JsonValue::Array(x), JsonValue::Array(y)) => x.len().cmp(&y.len()).then_with(|| {
1275 x.iter()
1276 .zip(y.iter())
1277 .map(|(ea, eb)| jsonb_compare(ea, eb))
1278 .find(|o| *o != Ordering::Equal)
1279 .unwrap_or(Ordering::Equal)
1280 }),
1281 (JsonValue::Object(x), JsonValue::Object(y)) => x.len().cmp(&y.len()).then_with(|| {
1282 x.iter()
1283 .zip(y.iter())
1284 .map(|((ka, va), (kb, vb))| ka.cmp(kb).then_with(|| jsonb_compare(va, vb)))
1285 .find(|o| *o != Ordering::Equal)
1286 .unwrap_or(Ordering::Equal)
1287 }),
1288 _ => Ordering::Equal,
1290 }
1291}
1292
1293fn skip_ws(bytes: &[u8], p: &mut usize) {
1294 while *p < bytes.len() && matches!(bytes[*p], b' ' | b'\t' | b'\n' | b'\r') {
1295 *p += 1;
1296 }
1297}
1298
1299fn parse_value(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
1300 skip_ws(bytes, p);
1301 if *p >= bytes.len() {
1302 return Err(ParseError::Truncated);
1303 }
1304 match bytes[*p] {
1305 b'{' => parse_object(bytes, p),
1306 b'[' => parse_array(bytes, p),
1307 b'"' => parse_string(bytes, p).map(JsonValue::String),
1308 b't' | b'f' => parse_bool(bytes, p),
1309 b'n' => parse_null(bytes, p),
1310 b'-' | b'0'..=b'9' => parse_number(bytes, p),
1311 c => Err(ParseError::Unexpected(c as char, *p)),
1312 }
1313}
1314
1315fn parse_object(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
1316 debug_assert_eq!(bytes[*p], b'{');
1317 *p += 1;
1318 let mut entries = Vec::new();
1319 skip_ws(bytes, p);
1320 if *p < bytes.len() && bytes[*p] == b'}' {
1321 *p += 1;
1322 return Ok(JsonValue::Object(entries));
1323 }
1324 loop {
1325 skip_ws(bytes, p);
1326 if *p >= bytes.len() || bytes[*p] != b'"' {
1327 return Err(ParseError::Unexpected(
1328 bytes.get(*p).copied().unwrap_or(0) as char,
1329 *p,
1330 ));
1331 }
1332 let key = parse_string(bytes, p)?;
1333 skip_ws(bytes, p);
1334 if *p >= bytes.len() || bytes[*p] != b':' {
1335 return Err(ParseError::Unexpected(
1336 bytes.get(*p).copied().unwrap_or(0) as char,
1337 *p,
1338 ));
1339 }
1340 *p += 1;
1341 let value = parse_value(bytes, p)?;
1342 entries.push((key, value));
1343 skip_ws(bytes, p);
1344 if *p >= bytes.len() {
1345 return Err(ParseError::Truncated);
1346 }
1347 match bytes[*p] {
1348 b',' => {
1349 *p += 1;
1350 continue;
1351 }
1352 b'}' => {
1353 *p += 1;
1354 return Ok(JsonValue::Object(entries));
1355 }
1356 c => return Err(ParseError::Unexpected(c as char, *p)),
1357 }
1358 }
1359}
1360
1361fn parse_array(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
1362 debug_assert_eq!(bytes[*p], b'[');
1363 *p += 1;
1364 let mut items = Vec::new();
1365 skip_ws(bytes, p);
1366 if *p < bytes.len() && bytes[*p] == b']' {
1367 *p += 1;
1368 return Ok(JsonValue::Array(items));
1369 }
1370 loop {
1371 items.push(parse_value(bytes, p)?);
1372 skip_ws(bytes, p);
1373 if *p >= bytes.len() {
1374 return Err(ParseError::Truncated);
1375 }
1376 match bytes[*p] {
1377 b',' => {
1378 *p += 1;
1379 continue;
1380 }
1381 b']' => {
1382 *p += 1;
1383 return Ok(JsonValue::Array(items));
1384 }
1385 c => return Err(ParseError::Unexpected(c as char, *p)),
1386 }
1387 }
1388}
1389
1390fn parse_string(bytes: &[u8], p: &mut usize) -> Result<String, ParseError> {
1391 debug_assert_eq!(bytes[*p], b'"');
1392 *p += 1;
1393 let mut out = String::new();
1394 while *p < bytes.len() {
1395 match bytes[*p] {
1396 b'"' => {
1397 *p += 1;
1398 return Ok(out);
1399 }
1400 b'\\' => {
1401 let start = *p;
1402 *p += 1;
1403 if *p >= bytes.len() {
1404 return Err(ParseError::Truncated);
1405 }
1406 match bytes[*p] {
1407 b'"' => {
1408 out.push('"');
1409 *p += 1;
1410 }
1411 b'\\' => {
1412 out.push('\\');
1413 *p += 1;
1414 }
1415 b'/' => {
1416 out.push('/');
1417 *p += 1;
1418 }
1419 b'b' => {
1420 out.push('\u{08}');
1421 *p += 1;
1422 }
1423 b'f' => {
1424 out.push('\u{0c}');
1425 *p += 1;
1426 }
1427 b'n' => {
1428 out.push('\n');
1429 *p += 1;
1430 }
1431 b'r' => {
1432 out.push('\r');
1433 *p += 1;
1434 }
1435 b't' => {
1436 out.push('\t');
1437 *p += 1;
1438 }
1439 b'u' => {
1440 if *p + 5 > bytes.len() {
1441 return Err(ParseError::Truncated);
1442 }
1443 let hex = &bytes[*p + 1..*p + 5];
1444 let n = u32::from_str_radix(
1445 core::str::from_utf8(hex)
1446 .map_err(|_| ParseError::InvalidEscape(start))?,
1447 16,
1448 )
1449 .map_err(|_| ParseError::InvalidEscape(start))?;
1450 out.push(char::from_u32(n).ok_or(ParseError::InvalidEscape(start))?);
1451 *p += 5;
1452 }
1453 _ => return Err(ParseError::InvalidEscape(start)),
1454 }
1455 }
1456 c if c < 0x20 => return Err(ParseError::Unexpected(c as char, *p)),
1457 _ => {
1458 let s = core::str::from_utf8(&bytes[*p..])
1460 .map_err(|_| ParseError::Unexpected(bytes[*p] as char, *p))?;
1461 let c = s.chars().next().unwrap();
1462 out.push(c);
1463 *p += c.len_utf8();
1464 }
1465 }
1466 }
1467 Err(ParseError::Truncated)
1468}
1469
1470fn parse_bool(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
1471 if bytes[*p..].starts_with(b"true") {
1472 *p += 4;
1473 Ok(JsonValue::Bool(true))
1474 } else if bytes[*p..].starts_with(b"false") {
1475 *p += 5;
1476 Ok(JsonValue::Bool(false))
1477 } else {
1478 Err(ParseError::Unexpected(bytes[*p] as char, *p))
1479 }
1480}
1481
1482fn parse_null(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
1483 if bytes[*p..].starts_with(b"null") {
1484 *p += 4;
1485 Ok(JsonValue::Null)
1486 } else {
1487 Err(ParseError::Unexpected(bytes[*p] as char, *p))
1488 }
1489}
1490
1491fn parse_number(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
1492 let start = *p;
1493 if bytes[*p] == b'-' {
1494 *p += 1;
1495 }
1496 while *p < bytes.len() && bytes[*p].is_ascii_digit() {
1497 *p += 1;
1498 }
1499 if *p < bytes.len() && bytes[*p] == b'.' {
1500 *p += 1;
1501 while *p < bytes.len() && bytes[*p].is_ascii_digit() {
1502 *p += 1;
1503 }
1504 }
1505 if *p < bytes.len() && matches!(bytes[*p], b'e' | b'E') {
1506 *p += 1;
1507 if *p < bytes.len() && matches!(bytes[*p], b'+' | b'-') {
1508 *p += 1;
1509 }
1510 while *p < bytes.len() && bytes[*p].is_ascii_digit() {
1511 *p += 1;
1512 }
1513 }
1514 let text = core::str::from_utf8(&bytes[start..*p])
1515 .map_err(|_| ParseError::InvalidNumber(start))?
1516 .to_string();
1517 if text.parse::<f64>().is_err() {
1519 return Err(ParseError::InvalidNumber(start));
1520 }
1521 Ok(JsonValue::NumberText(text))
1522}
1523
1524#[derive(Debug, Clone, Copy)]
1545enum IdxBound {
1546 At(usize),
1547 FromLast(usize),
1548}
1549
1550impl IdxBound {
1551 fn resolve(self, len: usize) -> Option<usize> {
1553 match self {
1554 Self::At(n) => (n < len).then_some(n),
1555 Self::FromLast(off) => len.checked_sub(1 + off),
1556 }
1557 }
1558}
1559
1560#[derive(Debug, Clone, Copy)]
1562enum NumMethod {
1563 Abs,
1564 Floor,
1565 Ceiling,
1566 Double,
1567}
1568
1569#[derive(Debug, Clone)]
1570enum PathStep {
1571 Field(String),
1572 Index(IdxBound),
1573 Wildcard,
1574 Range(IdxBound, IdxBound),
1577 Filter(FilterExpr),
1580 Size,
1582 TypeOf,
1584 Num(NumMethod),
1586 RecursiveAll,
1588}
1589
1590#[derive(Debug, Clone)]
1591struct FilterPred {
1592 path: Vec<String>,
1594 op: FilterOp,
1595 val: FilterVal,
1596 regex_flags: Option<String>,
1600}
1601
1602#[derive(Debug, Clone)]
1604enum FilterExpr {
1605 Cmp(FilterPred),
1606 And(alloc::boxed::Box<FilterExpr>, alloc::boxed::Box<FilterExpr>),
1607 Or(alloc::boxed::Box<FilterExpr>, alloc::boxed::Box<FilterExpr>),
1608}
1609
1610#[derive(Debug, Clone, Copy)]
1611enum FilterOp {
1612 Gt,
1613 Lt,
1614 Ge,
1615 Le,
1616 Eq,
1617 Ne,
1618 StartsWith,
1620 LikeRegex,
1622}
1623
1624#[derive(Debug, Clone)]
1625enum FilterVal {
1626 Num(f64),
1627 Str(String),
1628 Bool(bool),
1629 Null,
1631 Var(String),
1634}
1635
1636fn parse_jsonpath_mode(p: &str) -> Result<(bool, Vec<PathStep>), EvalError> {
1641 let trimmed = p.trim_start();
1642 let (strict, p) = if let Some(rest) = trimmed.strip_prefix("strict") {
1643 (true, rest.trim_start())
1644 } else if let Some(rest) = trimmed.strip_prefix("lax") {
1645 (false, rest.trim_start())
1646 } else {
1647 (false, trimmed)
1648 };
1649 let chars: Vec<char> = p.chars().collect();
1650 let mut i = 0;
1651 if i >= chars.len() || chars[i] != '$' {
1652 return Err(EvalError::TypeMismatch {
1653 detail: alloc::format!("jsonpath must start with '$', got {p:?}"),
1654 });
1655 }
1656 i += 1;
1657 let mut steps: Vec<PathStep> = Vec::new();
1658 while i < chars.len() {
1659 match chars[i] {
1660 '.' => {
1661 i += 1;
1662 if i + 1 < chars.len() && chars[i] == '*' && chars[i + 1] == '*' {
1665 i += 2;
1666 steps.push(PathStep::RecursiveAll);
1667 continue;
1668 }
1669 if i < chars.len() && chars[i] == '"' {
1670 i += 1;
1671 let start = i;
1672 while i < chars.len() && chars[i] != '"' {
1673 i += 1;
1674 }
1675 if i >= chars.len() {
1676 return Err(EvalError::TypeMismatch {
1677 detail: "jsonpath: unterminated quoted field".into(),
1678 });
1679 }
1680 steps.push(PathStep::Field(chars[start..i].iter().collect()));
1681 i += 1;
1682 } else {
1683 let start = i;
1684 while i < chars.len()
1685 && chars[i] != '.'
1686 && chars[i] != '['
1687 && chars[i] != '('
1688 && !chars[i].is_whitespace()
1689 {
1690 i += 1;
1691 }
1692 if start == i {
1693 return Err(EvalError::TypeMismatch {
1694 detail: "jsonpath: missing field name after '.'".into(),
1695 });
1696 }
1697 let name: String = chars[start..i].iter().collect();
1698 if i < chars.len() && chars[i] == '(' {
1700 i += 1;
1701 while i < chars.len() && chars[i] != ')' {
1702 i += 1;
1703 }
1704 if i >= chars.len() {
1705 return Err(EvalError::TypeMismatch {
1706 detail: "jsonpath: unterminated method call".into(),
1707 });
1708 }
1709 i += 1; match name.as_str() {
1711 "size" => steps.push(PathStep::Size),
1712 "type" => steps.push(PathStep::TypeOf),
1713 "abs" => steps.push(PathStep::Num(NumMethod::Abs)),
1715 "floor" => steps.push(PathStep::Num(NumMethod::Floor)),
1716 "ceiling" => steps.push(PathStep::Num(NumMethod::Ceiling)),
1717 "double" => steps.push(PathStep::Num(NumMethod::Double)),
1718 other => {
1719 return Err(EvalError::TypeMismatch {
1720 detail: alloc::format!(
1721 "jsonpath: unsupported method .{other}()"
1722 ),
1723 });
1724 }
1725 }
1726 } else {
1727 steps.push(PathStep::Field(name));
1728 }
1729 }
1730 }
1731 '?' => {
1732 i += 1;
1734 let (pred, ni) = parse_filter_pred(&chars, i)?;
1735 i = ni;
1736 steps.push(PathStep::Filter(pred));
1737 }
1738 '[' => {
1739 i += 1;
1740 if i < chars.len() && chars[i] == '*' {
1741 i += 1;
1742 if i >= chars.len() || chars[i] != ']' {
1743 return Err(EvalError::TypeMismatch {
1744 detail: "jsonpath: expected ']' after '[*'".into(),
1745 });
1746 }
1747 i += 1;
1748 steps.push(PathStep::Wildcard);
1749 } else {
1750 let mut parse_bound = |i: &mut usize| -> Result<IdxBound, EvalError> {
1752 jp_skip_ws(&chars, i);
1753 if chars[*i..].starts_with(&['l', 'a', 's', 't']) {
1754 *i += 4;
1755 jp_skip_ws(&chars, i);
1756 if *i < chars.len() && chars[*i] == '-' {
1757 *i += 1;
1758 jp_skip_ws(&chars, i);
1759 let s = *i;
1760 while *i < chars.len() && chars[*i].is_ascii_digit() {
1761 *i += 1;
1762 }
1763 let off: usize =
1764 chars[s..*i].iter().collect::<String>().parse().map_err(
1765 |_| EvalError::TypeMismatch {
1766 detail: "jsonpath: invalid `last - N` offset".into(),
1767 },
1768 )?;
1769 return Ok(IdxBound::FromLast(off));
1770 }
1771 return Ok(IdxBound::FromLast(0));
1772 }
1773 let s = *i;
1774 while *i < chars.len() && chars[*i].is_ascii_digit() {
1775 *i += 1;
1776 }
1777 if s == *i {
1778 return Err(EvalError::TypeMismatch {
1779 detail: "jsonpath: expected `N`, `last[ - K]` or `*` subscript"
1780 .into(),
1781 });
1782 }
1783 Ok(IdxBound::At(
1784 chars[s..*i]
1785 .iter()
1786 .collect::<String>()
1787 .parse()
1788 .map_err(|_| EvalError::TypeMismatch {
1789 detail: "jsonpath: invalid array index".into(),
1790 })?,
1791 ))
1792 };
1793 let idx = parse_bound(&mut i)?;
1794 while i < chars.len() && chars[i].is_whitespace() {
1796 i += 1;
1797 }
1798 if i + 1 < chars.len() && chars[i] == 't' && chars[i + 1] == 'o' {
1799 i += 2;
1800 let hi = parse_bound(&mut i)?;
1801 while i < chars.len() && chars[i].is_whitespace() {
1802 i += 1;
1803 }
1804 if i >= chars.len() || chars[i] != ']' {
1805 return Err(EvalError::TypeMismatch {
1806 detail: "jsonpath: expected ']' after range".into(),
1807 });
1808 }
1809 i += 1;
1810 steps.push(PathStep::Range(idx, hi));
1811 } else {
1812 if i >= chars.len() || chars[i] != ']' {
1813 return Err(EvalError::TypeMismatch {
1814 detail: "jsonpath: expected ']' after array index".into(),
1815 });
1816 }
1817 i += 1;
1818 steps.push(PathStep::Index(idx));
1819 }
1820 }
1821 }
1822 c if c.is_whitespace() => {
1823 i += 1;
1824 }
1825 c => {
1826 return Err(EvalError::TypeMismatch {
1827 detail: alloc::format!(
1828 "jsonpath: unexpected char '{c}' (supports `$.field`, `[N]`, `[N to M]`, `[*]`, `? (...)`, `.size()`, `.type()`)"
1829 ),
1830 });
1831 }
1832 }
1833 }
1834 Ok((strict, steps))
1835}
1836
1837fn parse_jsonpath(p: &str) -> Result<Vec<PathStep>, EvalError> {
1839 parse_jsonpath_mode(p).map(|(_, steps)| steps)
1840}
1841
1842fn jp_skip_ws(chars: &[char], i: &mut usize) {
1843 while *i < chars.len() && chars[*i].is_whitespace() {
1844 *i += 1;
1845 }
1846}
1847
1848fn parse_filter_pred(chars: &[char], mut i: usize) -> Result<(FilterExpr, usize), EvalError> {
1852 let err = |m: &str| EvalError::TypeMismatch {
1853 detail: alloc::format!("jsonpath filter: {m}"),
1854 };
1855 jp_skip_ws(chars, &mut i);
1856 if i >= chars.len() || chars[i] != '(' {
1857 return Err(err("expected '(' after '?'"));
1858 }
1859 i += 1;
1860 let (expr, ni) = parse_filter_or(chars, i)?;
1861 i = ni;
1862 jp_skip_ws(chars, &mut i);
1863 if i >= chars.len() || chars[i] != ')' {
1864 return Err(err("expected ')' to close the filter"));
1865 }
1866 i += 1;
1867 Ok((expr, i))
1868}
1869
1870fn parse_filter_or(chars: &[char], i: usize) -> Result<(FilterExpr, usize), EvalError> {
1872 let (mut left, mut i) = parse_filter_and(chars, i)?;
1873 loop {
1874 jp_skip_ws(chars, &mut i);
1875 if i + 1 < chars.len() && chars[i] == '|' && chars[i + 1] == '|' {
1876 i += 2;
1877 let (right, ni) = parse_filter_and(chars, i)?;
1878 i = ni;
1879 left = FilterExpr::Or(alloc::boxed::Box::new(left), alloc::boxed::Box::new(right));
1880 } else {
1881 return Ok((left, i));
1882 }
1883 }
1884}
1885
1886fn parse_filter_and(chars: &[char], i: usize) -> Result<(FilterExpr, usize), EvalError> {
1888 let (mut left, mut i) = parse_filter_atom(chars, i)?;
1889 loop {
1890 jp_skip_ws(chars, &mut i);
1891 if i + 1 < chars.len() && chars[i] == '&' && chars[i + 1] == '&' {
1892 i += 2;
1893 let (right, ni) = parse_filter_atom(chars, i)?;
1894 i = ni;
1895 left = FilterExpr::And(alloc::boxed::Box::new(left), alloc::boxed::Box::new(right));
1896 } else {
1897 return Ok((left, i));
1898 }
1899 }
1900}
1901
1902fn parse_filter_atom(chars: &[char], mut i: usize) -> Result<(FilterExpr, usize), EvalError> {
1904 let err = |m: &str| EvalError::TypeMismatch {
1905 detail: alloc::format!("jsonpath filter: {m}"),
1906 };
1907 jp_skip_ws(chars, &mut i);
1908 if i < chars.len() && chars[i] == '(' {
1909 i += 1;
1910 let (expr, ni) = parse_filter_or(chars, i)?;
1911 i = ni;
1912 jp_skip_ws(chars, &mut i);
1913 if i >= chars.len() || chars[i] != ')' {
1914 return Err(err("expected ')' in grouped predicate"));
1915 }
1916 i += 1;
1917 return Ok((expr, i));
1918 }
1919 if i >= chars.len() || chars[i] != '@' {
1920 return Err(err("only `@`-based predicates are supported"));
1921 }
1922 i += 1;
1923 let mut path: Vec<String> = Vec::new();
1924 while i < chars.len() && chars[i] == '.' {
1925 i += 1;
1926 let start = i;
1927 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
1928 i += 1;
1929 }
1930 path.push(chars[start..i].iter().collect());
1931 }
1932 let (op, val, regex_flags, ni) = parse_cmp_and_literal(chars, i)?;
1933 i = ni;
1934 Ok((
1935 FilterExpr::Cmp(FilterPred {
1936 path,
1937 op,
1938 val,
1939 regex_flags,
1940 }),
1941 i,
1942 ))
1943}
1944
1945fn parse_cmp_and_literal(
1949 chars: &[char],
1950 mut i: usize,
1951) -> Result<(FilterOp, FilterVal, Option<String>, usize), EvalError> {
1952 let err = |m: &str| EvalError::TypeMismatch {
1953 detail: alloc::format!("jsonpath predicate: {m}"),
1954 };
1955 while i < chars.len() && chars[i].is_whitespace() {
1956 i += 1;
1957 }
1958 let kw = |i: usize, w: &str| -> bool {
1959 let wc: Vec<char> = w.chars().collect();
1960 chars[i..].starts_with(&wc)
1961 };
1962 let op = if i + 1 < chars.len() && chars[i] == '>' && chars[i + 1] == '=' {
1963 i += 2;
1964 FilterOp::Ge
1965 } else if i + 1 < chars.len() && chars[i] == '<' && chars[i + 1] == '=' {
1966 i += 2;
1967 FilterOp::Le
1968 } else if i + 1 < chars.len() && chars[i] == '=' && chars[i + 1] == '=' {
1969 i += 2;
1970 FilterOp::Eq
1971 } else if i + 1 < chars.len() && chars[i] == '!' && chars[i + 1] == '=' {
1972 i += 2;
1973 FilterOp::Ne
1974 } else if i < chars.len() && chars[i] == '>' {
1975 i += 1;
1976 FilterOp::Gt
1977 } else if i < chars.len() && chars[i] == '<' {
1978 i += 1;
1979 FilterOp::Lt
1980 } else if kw(i, "starts") {
1982 i += 6;
1983 while i < chars.len() && chars[i].is_whitespace() {
1984 i += 1;
1985 }
1986 if !kw(i, "with") {
1987 return Err(err("expected `with` after `starts`"));
1988 }
1989 i += 4;
1990 FilterOp::StartsWith
1991 } else if kw(i, "like_regex") {
1992 i += 10;
1993 FilterOp::LikeRegex
1994 } else {
1995 return Err(err(
1996 "expected a comparison operator (> < >= <= == != starts with like_regex)",
1997 ));
1998 };
1999 while i < chars.len() && chars[i].is_whitespace() {
2000 i += 1;
2001 }
2002 let val = if i < chars.len() && chars[i] == '"' {
2003 i += 1;
2004 let start = i;
2005 while i < chars.len() && chars[i] != '"' {
2006 i += 1;
2007 }
2008 if i >= chars.len() {
2009 return Err(err("unterminated string literal"));
2010 }
2011 let s: String = chars[start..i].iter().collect();
2012 i += 1;
2013 FilterVal::Str(s)
2014 } else if chars[i..].starts_with(&['t', 'r', 'u', 'e']) {
2015 i += 4;
2016 FilterVal::Bool(true)
2017 } else if chars[i..].starts_with(&['f', 'a', 'l', 's', 'e']) {
2018 i += 5;
2019 FilterVal::Bool(false)
2020 } else if chars[i..].starts_with(&['n', 'u', 'l', 'l']) {
2022 i += 4;
2023 FilterVal::Null
2024 } else if i < chars.len() && chars[i] == '$' {
2025 i += 1;
2026 let start = i;
2027 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
2028 i += 1;
2029 }
2030 if start == i {
2031 return Err(err("expected a variable name after '$'"));
2032 }
2033 FilterVal::Var(chars[start..i].iter().collect())
2034 } else {
2035 let start = i;
2036 if i < chars.len() && (chars[i] == '-' || chars[i] == '+') {
2037 i += 1;
2038 }
2039 while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '.') {
2040 i += 1;
2041 }
2042 let num: f64 = chars[start..i]
2043 .iter()
2044 .collect::<String>()
2045 .parse()
2046 .map_err(|_| err("invalid numeric literal"))?;
2047 FilterVal::Num(num)
2048 };
2049 let mut flags: Option<String> = None;
2052 if matches!(op, FilterOp::LikeRegex) {
2053 let mut j = i;
2054 while j < chars.len() && chars[j].is_whitespace() {
2055 j += 1;
2056 }
2057 if chars[j..].starts_with(&['f', 'l', 'a', 'g']) {
2058 j += 4;
2059 while j < chars.len() && chars[j].is_whitespace() {
2060 j += 1;
2061 }
2062 if j < chars.len() && chars[j] == '"' {
2063 j += 1;
2064 let start = j;
2065 while j < chars.len() && chars[j] != '"' {
2066 j += 1;
2067 }
2068 if j < chars.len() {
2069 flags = Some(chars[start..j].iter().collect());
2070 j += 1;
2071 i = j;
2072 }
2073 }
2074 }
2075 }
2076 Ok((op, val, flags, i))
2077}
2078
2079fn json_type_name(v: &JsonValue) -> &'static str {
2081 match v {
2082 JsonValue::Null => "null",
2083 JsonValue::Bool(_) => "boolean",
2084 JsonValue::Number(_) | JsonValue::NumberText(_) => "number",
2085 JsonValue::String(_) => "string",
2086 JsonValue::Array(_) => "array",
2087 JsonValue::Object(_) => "object",
2088 }
2089}
2090
2091fn json_num(v: &JsonValue) -> Option<f64> {
2093 match v {
2094 JsonValue::Number(n) => Some(*n),
2095 JsonValue::NumberText(s) => s.parse().ok(),
2096 _ => None,
2097 }
2098}
2099
2100fn resolve_accessor<'a>(node: &'a JsonValue, path: &[String]) -> Option<&'a JsonValue> {
2102 let mut cur = node;
2103 for key in path {
2104 match cur {
2105 JsonValue::Object(entries) => {
2106 cur = &entries.iter().find(|(k, _)| k == key)?.1;
2107 }
2108 _ => return None,
2109 }
2110 }
2111 Some(cur)
2112}
2113
2114fn filter_matches(node: &JsonValue, pred: &FilterPred, vars: Option<&JsonValue>) -> bool {
2118 let Some(target) = resolve_accessor(node, &pred.path) else {
2119 return false;
2120 };
2121 let resolved;
2123 let val = match &pred.val {
2124 FilterVal::Var(name) => {
2125 let Some(JsonValue::Object(entries)) = vars else {
2126 return false;
2127 };
2128 let Some((_, v)) = entries.iter().find(|(k, _)| k == name) else {
2129 return false;
2130 };
2131 resolved = match v {
2132 JsonValue::Number(n) => FilterVal::Num(*n),
2133 JsonValue::NumberText(s) => match s.parse::<f64>() {
2134 Ok(n) => FilterVal::Num(n),
2135 Err(_) => return false,
2136 },
2137 JsonValue::String(s) => FilterVal::Str(s.clone()),
2138 JsonValue::Bool(b) => FilterVal::Bool(*b),
2139 JsonValue::Null => FilterVal::Null,
2140 _ => return false,
2141 };
2142 &resolved
2143 }
2144 other => other,
2145 };
2146 match val {
2147 FilterVal::Num(rhs) => match json_num(target) {
2148 Some(lhs) => match pred.op {
2149 FilterOp::Gt => lhs > *rhs,
2150 FilterOp::Lt => lhs < *rhs,
2151 FilterOp::Ge => lhs >= *rhs,
2152 FilterOp::Le => lhs <= *rhs,
2153 FilterOp::Eq => lhs == *rhs,
2154 FilterOp::Ne => lhs != *rhs,
2155 FilterOp::StartsWith | FilterOp::LikeRegex => false,
2156 },
2157 None => false,
2158 },
2159 FilterVal::Str(rhs) => match target {
2160 JsonValue::String(lhs) => match pred.op {
2161 FilterOp::Eq => lhs == rhs,
2162 FilterOp::Ne => lhs != rhs,
2163 FilterOp::Gt => lhs.as_str() > rhs.as_str(),
2164 FilterOp::Lt => lhs.as_str() < rhs.as_str(),
2165 FilterOp::Ge => lhs.as_str() >= rhs.as_str(),
2166 FilterOp::Le => lhs.as_str() <= rhs.as_str(),
2167 FilterOp::StartsWith => lhs.starts_with(rhs.as_str()),
2169 FilterOp::LikeRegex => {
2170 if pred.regex_flags.as_deref().is_some_and(|f| f.contains('i')) {
2173 crate::eval::regex_is_match(&rhs.to_lowercase(), &lhs.to_lowercase())
2174 .unwrap_or(false)
2175 } else {
2176 crate::eval::regex_is_match(rhs, lhs).unwrap_or(false)
2177 }
2178 }
2179 },
2180 _ => false,
2181 },
2182 FilterVal::Bool(rhs) => match target {
2183 JsonValue::Bool(lhs) => match pred.op {
2184 FilterOp::Eq => lhs == rhs,
2185 FilterOp::Ne => lhs != rhs,
2186 _ => false,
2187 },
2188 _ => false,
2189 },
2190 FilterVal::Null => match pred.op {
2192 FilterOp::Eq => matches!(target, JsonValue::Null),
2193 FilterOp::Ne => !matches!(target, JsonValue::Null),
2194 _ => false,
2195 },
2196 FilterVal::Var(_) => false, }
2198}
2199
2200fn filter_expr_matches(node: &JsonValue, expr: &FilterExpr, vars: Option<&JsonValue>) -> bool {
2202 match expr {
2203 FilterExpr::Cmp(pred) => filter_matches(node, pred, vars),
2204 FilterExpr::And(a, b) => {
2205 filter_expr_matches(node, a, vars) && filter_expr_matches(node, b, vars)
2206 }
2207 FilterExpr::Or(a, b) => {
2208 filter_expr_matches(node, a, vars) || filter_expr_matches(node, b, vars)
2209 }
2210 }
2211}
2212
2213fn apply_jsonpath(
2215 root: &JsonValue,
2216 steps: &[PathStep],
2217 vars: Option<&JsonValue>,
2218) -> Vec<JsonValue> {
2219 apply_jsonpath_mode(root, steps, vars, false).unwrap_or_default()
2220}
2221
2222fn apply_jsonpath_mode(
2236 root: &JsonValue,
2237 steps: &[PathStep],
2238 vars: Option<&JsonValue>,
2239 strict: bool,
2240) -> Result<Vec<JsonValue>, EvalError> {
2241 let err = |m: alloc::string::String| Err(EvalError::TypeMismatch { detail: m });
2242 let mut cur: Vec<JsonValue> = alloc::vec![root.clone()];
2243 for step in steps {
2244 if !strict {
2246 match step {
2247 PathStep::Field(_) => {
2249 let mut flat: Vec<JsonValue> = Vec::new();
2250 for node in cur {
2251 match node {
2252 JsonValue::Array(items) => flat.extend(items),
2253 other => flat.push(other),
2254 }
2255 }
2256 cur = flat;
2257 }
2258 PathStep::Wildcard | PathStep::Index(_) | PathStep::Range(..) => {
2260 cur = cur
2261 .into_iter()
2262 .map(|n| match n {
2263 arr @ JsonValue::Array(_) => arr,
2264 other => JsonValue::Array(alloc::vec![other]),
2265 })
2266 .collect();
2267 }
2268 _ => {}
2269 }
2270 } else {
2271 for node in &cur {
2273 match step {
2274 PathStep::Field(k) => match node {
2275 JsonValue::Object(entries) => {
2276 if !entries.iter().any(|(name, _)| name == k) {
2277 return err(alloc::format!(
2278 "JSON object does not contain key \"{k}\""
2279 ));
2280 }
2281 }
2282 _ => {
2283 return err(
2284 "jsonpath member accessor can only be applied to an object".into(),
2285 );
2286 }
2287 },
2288 PathStep::Wildcard => {
2289 if !matches!(node, JsonValue::Array(_)) {
2290 return err(
2291 "jsonpath wildcard array accessor can only be applied to an array"
2292 .into(),
2293 );
2294 }
2295 }
2296 PathStep::Index(idx) => match node {
2297 JsonValue::Array(items) => {
2298 if idx.resolve(items.len()).is_none_or(|p| p >= items.len()) {
2299 return err("jsonpath array subscript is out of bounds".into());
2300 }
2301 }
2302 _ => {
2303 return err(
2304 "jsonpath array accessor can only be applied to an array".into()
2305 );
2306 }
2307 },
2308 PathStep::Range(lo, hi) => match node {
2309 JsonValue::Array(items) => {
2310 let n = items.len();
2311 if lo.resolve(n).is_none_or(|p| p >= n)
2312 || hi.resolve(n).is_none_or(|p| p >= n)
2313 {
2314 return err("jsonpath array subscript is out of bounds".into());
2315 }
2316 }
2317 _ => {
2318 return err(
2319 "jsonpath array accessor can only be applied to an array".into()
2320 );
2321 }
2322 },
2323 _ => {}
2324 }
2325 }
2326 }
2327 let mut next: Vec<JsonValue> = Vec::new();
2328 for node in &cur {
2329 match (step, node) {
2330 (PathStep::Field(k), JsonValue::Object(entries)) => {
2331 if let Some((_, v)) = entries.iter().find(|(name, _)| name == k) {
2332 next.push(v.clone());
2333 }
2334 }
2335 (PathStep::Index(idx), JsonValue::Array(items)) => {
2336 if let Some(pos) = idx.resolve(items.len())
2337 && let Some(v) = items.get(pos)
2338 {
2339 next.push(v.clone());
2340 }
2341 }
2342 (PathStep::Wildcard, JsonValue::Array(items)) => {
2343 next.extend(items.iter().cloned());
2344 }
2345 (PathStep::Range(lo, hi), JsonValue::Array(items)) => {
2347 if let (Some(a), Some(b)) = (lo.resolve(items.len()), hi.resolve(items.len())) {
2348 for idx in a..=b {
2349 if let Some(v) = items.get(idx) {
2350 next.push(v.clone());
2351 }
2352 }
2353 }
2354 }
2355 (PathStep::Filter(expr), node) => {
2356 if filter_expr_matches(node, expr, vars) {
2357 next.push(node.clone());
2358 }
2359 }
2360 (PathStep::Size, JsonValue::Array(items)) => {
2361 next.push(JsonValue::Number(items.len() as f64));
2362 }
2363 (PathStep::Size, _) => next.push(JsonValue::Number(1.0)),
2365 (PathStep::TypeOf, node) => {
2366 next.push(JsonValue::String(json_type_name(node).into()));
2367 }
2368 (PathStep::RecursiveAll, node) => {
2371 fn descend(v: &JsonValue, out: &mut Vec<JsonValue>) {
2372 out.push(v.clone());
2373 match v {
2374 JsonValue::Object(entries) => {
2375 for (_, child) in entries {
2376 descend(child, out);
2377 }
2378 }
2379 JsonValue::Array(items) => {
2380 for child in items {
2381 descend(child, out);
2382 }
2383 }
2384 _ => {}
2385 }
2386 }
2387 descend(node, &mut next);
2388 }
2389 (PathStep::Num(m), node) => {
2391 let n = match m {
2392 NumMethod::Double => match node {
2394 JsonValue::String(s) => s.parse::<f64>().ok(),
2395 other => json_num(other),
2396 },
2397 _ => json_num(node),
2398 };
2399 if let Some(x) = n {
2400 let out = match m {
2401 NumMethod::Abs => x.abs(),
2402 NumMethod::Floor => x.floor(),
2403 NumMethod::Ceiling => x.ceil(),
2404 NumMethod::Double => x,
2405 };
2406 next.push(JsonValue::Number(out));
2407 }
2408 }
2409 _ => {} }
2411 }
2412 cur = next;
2413 if cur.is_empty() {
2414 return Ok(Vec::new());
2415 }
2416 }
2417 Ok(cur)
2418}
2419
2420pub fn path_predicate(doc: &Value, path: &Value) -> Result<Option<bool>, EvalError> {
2425 path_predicate_vars(doc, path, None)
2426}
2427
2428pub fn path_predicate_vars(
2430 doc: &Value,
2431 path: &Value,
2432 vars: Option<&JsonValue>,
2433) -> Result<Option<bool>, EvalError> {
2434 let (src, ptext) = match (doc, path) {
2435 (Value::Null, _) | (_, Value::Null) => return Ok(None),
2436 (Value::Json(s) | Value::Text(s), Value::Text(p) | Value::Json(p)) => (s, p),
2437 _ => return Ok(None),
2438 };
2439 let trimmed = ptext.trim();
2441 if let Some(inner) = trimmed
2442 .strip_prefix("exists")
2443 .map(str::trim_start)
2444 .and_then(|r| r.strip_prefix('('))
2445 .and_then(|r| r.strip_suffix(')'))
2446 {
2447 let (strict, steps) = parse_jsonpath_mode(inner.trim())?;
2448 let root = parse(src).map_err(|e| EvalError::TypeMismatch {
2449 detail: alloc::format!("{e}"),
2450 })?;
2451 return Ok(Some(
2452 !apply_jsonpath_mode(&root, &steps, vars, strict)?.is_empty(),
2453 ));
2454 }
2455 let chars: Vec<char> = ptext.chars().collect();
2456 let mut depth = 0i32;
2459 let mut i = 0;
2460 let mut op_at = None;
2461 while i < chars.len() {
2462 match chars[i] {
2463 '(' | '[' => depth += 1,
2464 ')' | ']' => depth -= 1,
2465 '"' => {
2466 i += 1;
2467 while i < chars.len() && chars[i] != '"' {
2468 i += 1;
2469 }
2470 }
2471 '>' | '<' | '=' | '!' if depth == 0 => {
2472 op_at = Some(i);
2473 break;
2474 }
2475 _ => {}
2476 }
2477 i += 1;
2478 }
2479 let Some(pos) = op_at else { return Ok(None) };
2480 let left: String = chars[..pos].iter().collect();
2481 let (strict, steps) = parse_jsonpath_mode(left.trim())?;
2482 let (op, val, regex_flags, _) = parse_cmp_and_literal(&chars, pos)?;
2483 let root = parse(src).map_err(|e| EvalError::TypeMismatch {
2484 detail: alloc::format!("{e}"),
2485 })?;
2486 let results = apply_jsonpath_mode(&root, &steps, vars, strict)?;
2489 let pred = FilterPred {
2490 path: Vec::new(),
2491 op,
2492 val,
2493 regex_flags,
2494 };
2495 Ok(Some(results.iter().any(|v| filter_matches(v, &pred, vars))))
2496}
2497
2498pub fn path_query(doc: &Value, path: &Value) -> Result<Value<'static>, EvalError> {
2502 path_query_vars(doc, path, None)
2503}
2504
2505pub fn parse_path_vars(v: &Value) -> Result<Option<JsonValue>, EvalError> {
2508 match v {
2509 Value::Null => Ok(None),
2510 Value::Json(s) | Value::Text(s) => {
2511 let parsed = parse(s).map_err(|e| EvalError::TypeMismatch {
2512 detail: alloc::format!("invalid jsonpath vars document: {e}"),
2513 })?;
2514 if !matches!(parsed, JsonValue::Object(_)) {
2515 return Err(EvalError::TypeMismatch {
2516 detail: "jsonpath vars must be a JSON object".into(),
2517 });
2518 }
2519 Ok(Some(parsed))
2520 }
2521 other => Err(EvalError::TypeMismatch {
2522 detail: alloc::format!(
2523 "jsonpath vars must be jsonb, got {}",
2524 crate::conversions::pg_type_name_for_error_opt(other.data_type())
2525 ),
2526 }),
2527 }
2528}
2529
2530pub fn path_query_vars(
2532 doc: &Value,
2533 path: &Value,
2534 vars: Option<&JsonValue>,
2535) -> Result<Value<'static>, EvalError> {
2536 let (src, path_text) = match (doc, path) {
2537 (Value::Null, _) | (_, Value::Null) => return Ok(Value::Null),
2538 (Value::Json(s) | Value::Text(s), Value::Text(p) | Value::Json(p)) => (s, p),
2539 _ => {
2540 return Err(EvalError::TypeMismatch {
2541 detail: "jsonb_path_query() expects (JSON, TEXT)".into(),
2542 });
2543 }
2544 };
2545 let root = parse(src).map_err(|e| EvalError::TypeMismatch {
2546 detail: alloc::format!("invalid JSON for jsonb_path_query: {e}"),
2547 })?;
2548 let trimmed = path_text.trim();
2550 if let Some(inner) = trimmed
2551 .strip_prefix("exists")
2552 .map(str::trim_start)
2553 .and_then(|r| r.strip_prefix('('))
2554 .and_then(|r| r.strip_suffix(')'))
2555 {
2556 let steps = parse_jsonpath(inner.trim())?;
2557 let hit = !apply_jsonpath(&root, &steps, vars).is_empty();
2558 return Ok(Value::TextArray(alloc::vec![Some(
2559 if hit { "true" } else { "false" }.into()
2560 )]));
2561 }
2562 let (strict, steps) = parse_jsonpath_mode(path_text)?;
2565 let matches = apply_jsonpath_mode(&root, &steps, vars, strict)?;
2566 let arr: Vec<Option<String>> = matches
2567 .into_iter()
2568 .map(|v| Some(json_canonical_string(&v)))
2569 .collect();
2570 Ok(Value::TextArray(arr))
2571}
2572
2573pub fn path_query_first(doc: &Value, path: &Value) -> Result<Value<'static>, EvalError> {
2576 path_query_first_vars(doc, path, None)
2577}
2578
2579pub fn path_query_first_vars(
2581 doc: &Value,
2582 path: &Value,
2583 vars: Option<&JsonValue>,
2584) -> Result<Value<'static>, EvalError> {
2585 let q = path_query_vars(doc, path, vars)?;
2586 match q {
2587 Value::TextArray(items) => {
2588 if let Some(Some(first)) = items.into_iter().next() {
2589 Ok(Value::json(first))
2590 } else {
2591 Ok(Value::Null)
2592 }
2593 }
2594 other => Ok(other),
2595 }
2596}
2597
2598pub fn path_query_array(doc: &Value, path: &Value) -> Result<Value<'static>, EvalError> {
2601 path_query_array_vars(doc, path, None)
2602}
2603
2604pub fn path_query_array_vars(
2606 doc: &Value,
2607 path: &Value,
2608 vars: Option<&JsonValue>,
2609) -> Result<Value<'static>, EvalError> {
2610 let q = path_query_vars(doc, path, vars)?;
2611 let arr = match q {
2612 Value::TextArray(items) => {
2613 let mut buf = String::from("[");
2614 let mut first = true;
2615 for s in items.into_iter().flatten() {
2616 if !first {
2617 buf.push_str(", ");
2618 }
2619 buf.push_str(&s);
2620 first = false;
2621 }
2622 buf.push(']');
2623 Value::json(buf)
2624 }
2625 other => other,
2626 };
2627 Ok(canonicalize_value(arr))
2631}
2632
2633pub fn jsonpath_canonical(input: &str) -> Result<String, EvalError> {
2660 let trimmed = input.trim();
2661 let (strict, body) = if let Some(rest) = trimmed.strip_prefix("strict ") {
2662 (true, rest.trim_start())
2663 } else if let Some(rest) = trimmed.strip_prefix("lax ") {
2664 (false, rest.trim_start())
2665 } else {
2666 (false, trimmed)
2667 };
2668 let steps = parse_jsonpath(body).map_err(|_| {
2669 let tok: String = body.chars().take(1).collect();
2672 EvalError::TypeMismatch {
2673 detail: alloc::format!("syntax error at or near {tok:?} of jsonpath input"),
2674 }
2675 })?;
2676 let mut out = String::new();
2677 if strict {
2678 out.push_str("strict ");
2679 }
2680 out.push('$');
2681 fn idx(b: &IdxBound, out: &mut String) {
2682 match b {
2683 IdxBound::At(n) => {
2684 let _ = core::fmt::Write::write_fmt(out, format_args!("{n}"));
2685 }
2686 IdxBound::FromLast(0) => out.push_str("last"),
2687 IdxBound::FromLast(k) => {
2688 let _ = core::fmt::Write::write_fmt(out, format_args!("last - {k}"));
2689 }
2690 }
2691 }
2692 fn fval(v: &FilterVal, out: &mut String) {
2693 match v {
2694 FilterVal::Num(x) => {
2695 if x.fract() == 0.0 && x.abs() < 1e15 {
2696 let _ = core::fmt::Write::write_fmt(out, format_args!("{}", *x as i64));
2697 } else {
2698 let _ = core::fmt::Write::write_fmt(out, format_args!("{x}"));
2699 }
2700 }
2701 FilterVal::Str(s) => {
2702 let _ = core::fmt::Write::write_fmt(out, format_args!("{s:?}"));
2703 }
2704 FilterVal::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
2705 FilterVal::Null => out.push_str("null"),
2706 FilterVal::Var(n) => {
2707 let _ = core::fmt::Write::write_fmt(out, format_args!("$\"{n}\""));
2708 }
2709 }
2710 }
2711 fn fexpr(e: &FilterExpr, out: &mut String) {
2712 match e {
2713 FilterExpr::Cmp(p) => {
2714 out.push('@');
2715 for seg in &p.path {
2716 let _ = core::fmt::Write::write_fmt(out, format_args!(".\"{seg}\""));
2717 }
2718 let op = match p.op {
2719 FilterOp::Gt => " > ",
2720 FilterOp::Lt => " < ",
2721 FilterOp::Ge => " >= ",
2722 FilterOp::Le => " <= ",
2723 FilterOp::Eq => " == ",
2724 FilterOp::Ne => " != ",
2725 FilterOp::StartsWith => " starts with ",
2726 FilterOp::LikeRegex => " like_regex ",
2727 };
2728 out.push_str(op);
2729 fval(&p.val, out);
2730 if let Some(f) = &p.regex_flags {
2731 let _ = core::fmt::Write::write_fmt(out, format_args!(" flag \"{f}\""));
2732 }
2733 }
2734 FilterExpr::And(l, r) => {
2735 fexpr(l, out);
2736 out.push_str(" && ");
2737 fexpr(r, out);
2738 }
2739 FilterExpr::Or(l, r) => {
2740 fexpr(l, out);
2741 out.push_str(" || ");
2742 fexpr(r, out);
2743 }
2744 }
2745 }
2746 for st in &steps {
2747 match st {
2748 PathStep::Field(f) => {
2749 let _ = core::fmt::Write::write_fmt(&mut out, format_args!(".\"{f}\""));
2750 }
2751 PathStep::Index(b) => {
2752 out.push('[');
2753 idx(b, &mut out);
2754 out.push(']');
2755 }
2756 PathStep::Wildcard => out.push_str("[*]"),
2757 PathStep::Range(a, b) => {
2758 out.push('[');
2759 idx(a, &mut out);
2760 out.push_str(" to ");
2761 idx(b, &mut out);
2762 out.push(']');
2763 }
2764 PathStep::Filter(e) => {
2765 out.push_str("?(");
2766 fexpr(e, &mut out);
2767 out.push(')');
2768 }
2769 PathStep::Size => out.push_str(".size()"),
2770 PathStep::TypeOf => out.push_str(".type()"),
2771 PathStep::Num(m) => out.push_str(match m {
2772 NumMethod::Abs => ".abs()",
2773 NumMethod::Floor => ".floor()",
2774 NumMethod::Ceiling => ".ceiling()",
2775 NumMethod::Double => ".double()",
2776 }),
2777 PathStep::RecursiveAll => out.push_str(".**"),
2778 }
2779 }
2780 Ok(out)
2781}
2782
2783pub fn value_to_json_text(v: &Value) -> String {
2784 let mut out = String::new();
2785 encode_value_into(v, &mut out);
2786 out
2787}
2788
2789fn encode_value_into(v: &Value, out: &mut String) {
2790 match v {
2791 Value::Null => out.push_str("null"),
2792 Value::Bool(true) => out.push_str("true"),
2793 Value::Bool(false) => out.push_str("false"),
2794 Value::SmallInt(n) => out.push_str(&alloc::format!("{n}")),
2795 Value::Int(n) => out.push_str(&alloc::format!("{n}")),
2796 Value::BigInt(n) => out.push_str(&alloc::format!("{n}")),
2797 Value::Float(x) if !x.is_finite() => {
2800 let txt = if x.is_nan() {
2801 "NaN"
2802 } else if *x > 0.0 {
2803 "Infinity"
2804 } else {
2805 "-Infinity"
2806 };
2807 write_json(&JsonValue::String(txt.into()), out);
2808 }
2809 Value::Float(x) => out.push_str(&alloc::format!("{x}")),
2810 Value::Real(x) if !x.is_finite() => {
2811 let txt = if x.is_nan() {
2812 "NaN"
2813 } else if *x > 0.0 {
2814 "Infinity"
2815 } else {
2816 "-Infinity"
2817 };
2818 write_json(&JsonValue::String(txt.into()), out);
2819 }
2820 Value::Numeric {
2821 scaled,
2822 scale,
2823 kind,
2824 } => {
2825 use spg_storage::NumericKind as NK;
2826 match kind {
2827 NK::NaN => write_json(&JsonValue::String("NaN".into()), out),
2828 NK::PosInf => write_json(&JsonValue::String("Infinity".into()), out),
2829 NK::NegInf => write_json(&JsonValue::String("-Infinity".into()), out),
2830 NK::Finite => out.push_str(&render_numeric(*scaled, *scale)),
2832 }
2833 }
2834 Value::Text(s) => write_json(&JsonValue::String(s.to_string()), out),
2835 Value::Json(s) => {
2836 out.push_str(s);
2840 }
2841 Value::Composite(fields) => {
2844 out.push('{');
2845 for (i, (name, fv)) in fields.iter().enumerate() {
2846 if i > 0 {
2847 out.push(',');
2848 }
2849 write_json(&JsonValue::String(name.clone()), out);
2850 out.push(':');
2851 encode_value_into(fv, out);
2852 }
2853 out.push('}');
2854 }
2855 Value::TextArray(items) => {
2856 out.push('[');
2857 for (i, it) in items.iter().enumerate() {
2858 if i > 0 {
2859 out.push(',');
2860 }
2861 match it {
2862 Some(s) => write_json(&JsonValue::String(s.clone()), out),
2863 None => out.push_str("null"),
2864 }
2865 }
2866 out.push(']');
2867 }
2868 Value::IntArray(items) => {
2869 out.push('[');
2870 for (i, it) in items.iter().enumerate() {
2871 if i > 0 {
2872 out.push(',');
2873 }
2874 match it {
2875 Some(n) => out.push_str(&alloc::format!("{n}")),
2876 None => out.push_str("null"),
2877 }
2878 }
2879 out.push(']');
2880 }
2881 Value::BigIntArray(items) => {
2882 out.push('[');
2883 for (i, it) in items.iter().enumerate() {
2884 if i > 0 {
2885 out.push(',');
2886 }
2887 match it {
2888 Some(n) => out.push_str(&alloc::format!("{n}")),
2889 None => out.push_str("null"),
2890 }
2891 }
2892 out.push(']');
2893 }
2894 Value::Timestamp(_) => {
2898 let txt = crate::eval::values::value_to_text(v).replacen(' ', "T", 1);
2899 write_json(&JsonValue::String(txt), out);
2900 }
2901 other => {
2913 if let Some(elems) = crate::eval::values::array_elements(other) {
2914 out.push('[');
2915 for (i, e) in elems.iter().enumerate() {
2916 if i > 0 {
2917 out.push(',');
2918 }
2919 encode_value_into(e, out);
2920 }
2921 out.push(']');
2922 return;
2923 }
2924 let txt = crate::eval::values::value_to_text(other);
2925 write_json(&JsonValue::String(txt), out);
2926 }
2927 }
2928}
2929
2930fn render_numeric(scaled: i128, scale: u16) -> String {
2931 let neg = scaled < 0;
2932 let mag_str = alloc::format!("{}", scaled.unsigned_abs());
2933 let s = scale as usize;
2934 let body = if s == 0 {
2935 mag_str
2936 } else if mag_str.len() > s {
2937 let p = mag_str.len() - s;
2938 alloc::format!("{}.{}", &mag_str[..p], &mag_str[p..])
2939 } else {
2940 let pad = s - mag_str.len();
2941 alloc::format!("0.{}{}", "0".repeat(pad), mag_str)
2942 };
2943 if neg { alloc::format!("-{body}") } else { body }
2944}
2945
2946pub fn concat(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
2954 concat_inner(lhs, rhs).map(canonicalize_value)
2955}
2956
2957fn concat_inner(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
2958 let (a_src, b_src) = match (lhs, rhs) {
2959 (Value::Null, _) | (_, Value::Null) => return Ok(Value::Null),
2960 (Value::Json(a) | Value::Text(a), Value::Json(b) | Value::Text(b)) => {
2961 (a.as_ref(), b.as_ref())
2962 }
2963 _ => {
2964 return Err(EvalError::TypeMismatch {
2965 detail: "jsonb_concat() expects (JSON, JSON)".into(),
2966 });
2967 }
2968 };
2969 let a = parse(a_src).map_err(|e| EvalError::TypeMismatch {
2970 detail: alloc::format!("invalid JSON lhs for concat: {e}"),
2971 })?;
2972 let b = parse(b_src).map_err(|e| EvalError::TypeMismatch {
2973 detail: alloc::format!("invalid JSON rhs for concat: {e}"),
2974 })?;
2975 let merged = match (a, b) {
2976 (JsonValue::Object(mut ea), JsonValue::Object(eb)) => {
2977 for (k, v) in eb {
2979 if let Some(slot) = ea.iter_mut().find(|(ek, _)| *ek == k) {
2980 slot.1 = v;
2981 } else {
2982 ea.push((k, v));
2983 }
2984 }
2985 JsonValue::Object(ea)
2986 }
2987 (JsonValue::Array(mut ia), JsonValue::Array(ib)) => {
2988 ia.extend(ib);
2989 JsonValue::Array(ia)
2990 }
2991 (JsonValue::Array(mut ia), scalar) => {
2992 ia.push(scalar);
2993 JsonValue::Array(ia)
2994 }
2995 (scalar, JsonValue::Array(ib)) => {
2996 let mut out = alloc::vec![scalar];
2997 out.extend(ib);
2998 JsonValue::Array(out)
2999 }
3000 (sa, sb) => JsonValue::Array(alloc::vec![sa, sb]),
3001 };
3002 Ok(Value::json(merged.to_json_text()))
3003}
3004
3005pub fn delete_key(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
3009 delete_key_inner(lhs, rhs).map(canonicalize_value)
3010}
3011
3012fn delete_key_inner(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
3013 let src = match lhs {
3014 Value::Null => return Ok(Value::Null),
3015 Value::Json(s) | Value::Text(s) => s.as_ref(),
3016 _ => {
3017 return Err(EvalError::TypeMismatch {
3018 detail: "jsonb_delete() expects JSON lhs".into(),
3019 });
3020 }
3021 };
3022 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
3023 detail: alloc::format!("invalid JSON for delete: {e}"),
3024 })?;
3025 let out = match (doc, rhs) {
3026 (_, Value::Null) => return Ok(Value::Null),
3027 (JsonValue::Object(entries), Value::Text(key)) => {
3028 let filtered: Vec<(String, JsonValue)> = entries
3029 .into_iter()
3030 .filter(|(k, _)| k != key.as_ref())
3031 .collect();
3032 JsonValue::Object(filtered)
3033 }
3034 (JsonValue::Object(entries), Value::TextArray(keys)) => {
3036 let filtered: Vec<(String, JsonValue)> = entries
3037 .into_iter()
3038 .filter(|(k, _)| !keys.iter().any(|kk| kk.as_deref() == Some(k.as_str())))
3039 .collect();
3040 JsonValue::Object(filtered)
3041 }
3042 (JsonValue::Array(items), Value::Int(idx)) => {
3043 let n = *idx;
3044 let len = items.len() as i64;
3045 let real = if n >= 0 {
3046 i64::from(n)
3047 } else {
3048 len + i64::from(n)
3049 };
3050 let filtered: Vec<JsonValue> = items
3051 .into_iter()
3052 .enumerate()
3053 .filter(|(i, _)| *i as i64 != real)
3054 .map(|(_, v)| v)
3055 .collect();
3056 JsonValue::Array(filtered)
3057 }
3058 (JsonValue::Object(_), Value::Int(_) | Value::SmallInt(_) | Value::BigInt(_)) => {
3064 return Err(EvalError::TypeMismatch {
3065 detail: "cannot delete from object using integer index".into(),
3066 });
3067 }
3068 (other, _) if !matches!(other, JsonValue::Object(_) | JsonValue::Array(_)) => {
3069 return Err(EvalError::TypeMismatch {
3070 detail: "cannot delete from scalar".into(),
3071 });
3072 }
3073 (other, _) => other,
3076 };
3077 Ok(Value::json(out.to_json_text()))
3078}
3079
3080pub fn build_object(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3081 if !args.len().is_multiple_of(2) {
3082 return Err(EvalError::TypeMismatch {
3083 detail: alloc::format!(
3084 "json_build_object() needs an even number of args, got {}",
3085 args.len()
3086 ),
3087 });
3088 }
3089 let mut out = String::from("{");
3090 let mut first = true;
3091 for pair in args.chunks_exact(2) {
3092 if !first {
3093 out.push_str(", ");
3097 }
3098 first = false;
3099 let key = match &pair[0] {
3100 Value::Null => {
3101 return Err(EvalError::TypeMismatch {
3102 detail: "json_build_object() key cannot be NULL".into(),
3103 });
3104 }
3105 Value::Text(s) | Value::Json(s) => s.to_string(),
3106 other => format_value_as_text(other),
3107 };
3108 write_json(&JsonValue::String(key), &mut out);
3109 out.push_str(" : ");
3110 encode_value_into(&pair[1], &mut out);
3111 }
3112 out.push('}');
3113 Ok(Value::json(out))
3114}
3115
3116pub fn build_array(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3119 let mut out = String::from("[");
3120 for (i, v) in args.iter().enumerate() {
3121 if i > 0 {
3122 out.push_str(", ");
3126 }
3127 encode_value_into(v, &mut out);
3128 }
3129 out.push(']');
3130 Ok(Value::json(out))
3131}
3132
3133fn format_value_as_text(v: &Value) -> String {
3134 match v {
3135 Value::SmallInt(n) => alloc::format!("{n}"),
3136 Value::Int(n) => alloc::format!("{n}"),
3137 Value::BigInt(n) => alloc::format!("{n}"),
3138 Value::Float(x) => alloc::format!("{x}"),
3139 Value::Bool(b) => alloc::format!("{b}"),
3140 other => alloc::format!("{other:?}"),
3141 }
3142}
3143
3144pub fn set(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3153 if !(3..=4).contains(&args.len()) {
3154 return Err(EvalError::TypeMismatch {
3155 detail: alloc::format!("jsonb_set() takes 3 or 4 args, got {}", args.len()),
3156 });
3157 }
3158 if args.iter().take(3).any(|v| matches!(v, Value::Null)) {
3159 return Ok(Value::Null);
3160 }
3161 let create_missing = match args.get(3) {
3162 None | Some(Value::Null) => true,
3163 Some(Value::Bool(b)) => *b,
3164 Some(other) => {
3165 return Err(EvalError::TypeMismatch {
3166 detail: alloc::format!(
3167 "jsonb_set() create_missing must be BOOL, got {}",
3168 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3169 ),
3170 });
3171 }
3172 };
3173 let doc_text = json_text_arg(&args[0], "jsonb_set", "target")?;
3174 let path = path_text_arg(&args[1], "jsonb_set")?;
3175 let new_text = json_text_arg(&args[2], "jsonb_set", "new_value")?;
3176 let mut root = parse(doc_text).map_err(|e| EvalError::TypeMismatch {
3177 detail: alloc::format!("jsonb_set(): invalid JSON target — {e}"),
3178 })?;
3179 let new_val = parse(new_text).map_err(|e| EvalError::TypeMismatch {
3180 detail: alloc::format!("jsonb_set(): invalid JSON new_value — {e}"),
3181 })?;
3182 if path.is_empty() {
3187 return Ok(Value::json(root.to_json_text()));
3188 }
3189 if is_json_scalar(&root) {
3190 return Err(EvalError::TypeMismatch {
3191 detail: "cannot set path in scalar".into(),
3192 });
3193 }
3194 set_at_path(&mut root, &path, new_val, create_missing);
3195 Ok(Value::json(root.to_json_text()))
3196}
3197
3198pub fn delete_path(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3202 delete_path_inner(args).map(canonicalize_value)
3203}
3204
3205fn delete_path_inner(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3206 if args.len() != 2 {
3207 return Err(EvalError::TypeMismatch {
3208 detail: alloc::format!("jsonb_delete_path() takes 2 args, got {}", args.len()),
3209 });
3210 }
3211 if args.iter().any(|v| matches!(v, Value::Null)) {
3212 return Ok(Value::Null);
3213 }
3214 let doc_text = json_text_arg(&args[0], "jsonb_delete_path", "target")?;
3215 let path = path_text_arg(&args[1], "jsonb_delete_path")?;
3216 let mut root = parse(doc_text).map_err(|e| EvalError::TypeMismatch {
3217 detail: alloc::format!("jsonb_delete_path(): invalid JSON target — {e}"),
3218 })?;
3219 if is_json_scalar(&root) && !path.is_empty() {
3222 return Err(EvalError::TypeMismatch {
3223 detail: "cannot delete path in scalar".into(),
3224 });
3225 }
3226 delete_at_path(&mut root, &path);
3227 Ok(Value::json(root.to_json_text()))
3228}
3229
3230fn delete_at_path(node: &mut JsonValue, path: &[String]) {
3231 if path.is_empty() {
3232 return;
3233 }
3234 let step = &path[0];
3235 if path.len() == 1 {
3236 match node {
3238 JsonValue::Object(entries) => {
3239 entries.retain(|(k, _)| k != step);
3240 }
3241 JsonValue::Array(items) => {
3242 if let Ok(idx) = step.parse::<i64>() {
3243 let len = items.len() as i64;
3244 let real = if idx >= 0 { idx } else { len + idx };
3245 if real >= 0 && real < len {
3246 items.remove(real as usize);
3247 }
3248 }
3249 }
3250 _ => {}
3251 }
3252 return;
3253 }
3254 match node {
3256 JsonValue::Object(entries) => {
3257 if let Some((_, child)) = entries.iter_mut().find(|(k, _)| k == step) {
3258 delete_at_path(child, &path[1..]);
3259 }
3260 }
3261 JsonValue::Array(items) => {
3262 if let Ok(idx) = step.parse::<i64>() {
3263 let len = items.len() as i64;
3264 let real = if idx >= 0 { idx } else { len + idx };
3265 if real >= 0 && real < len {
3266 delete_at_path(&mut items[real as usize], &path[1..]);
3267 }
3268 }
3269 }
3270 _ => {}
3271 }
3272}
3273
3274fn set_at_path(node: &mut JsonValue, path: &[String], new_val: JsonValue, create_missing: bool) {
3275 if path.is_empty() {
3276 *node = new_val;
3277 return;
3278 }
3279 let step = &path[0];
3280 let rest = &path[1..];
3281 match node {
3282 JsonValue::Object(entries) => {
3283 if let Some(pos) = entries.iter().position(|(k, _)| k == step) {
3284 if rest.is_empty() {
3285 entries[pos].1 = new_val;
3286 } else {
3287 set_at_path(&mut entries[pos].1, rest, new_val, create_missing);
3288 }
3289 } else if create_missing && rest.is_empty() {
3290 entries.push((step.clone(), new_val));
3291 }
3292 }
3295 JsonValue::Array(items) => {
3296 let Some(idx) = resolve_array_index(step, items.len()) else {
3297 if create_missing && rest.is_empty() {
3298 if let Ok(n) = step.parse::<i64>() {
3300 if n < 0 {
3301 items.insert(0, new_val);
3302 } else {
3303 items.push(new_val);
3304 }
3305 }
3306 }
3307 return;
3308 };
3309 if rest.is_empty() {
3310 items[idx] = new_val;
3311 } else {
3312 set_at_path(&mut items[idx], rest, new_val, create_missing);
3313 }
3314 }
3315 _ => {
3316 }
3318 }
3319}
3320
3321fn resolve_array_index(step: &str, len: usize) -> Option<usize> {
3322 let n = step.parse::<i64>().ok()?;
3323 if n >= 0 {
3324 let i = n as usize;
3325 if i < len { Some(i) } else { None }
3326 } else {
3327 let from_end = len as i64 + n;
3328 if from_end >= 0 {
3329 Some(from_end as usize)
3330 } else {
3331 None
3332 }
3333 }
3334}
3335
3336pub fn insert(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3344 if !(3..=4).contains(&args.len()) {
3345 return Err(EvalError::TypeMismatch {
3346 detail: alloc::format!("jsonb_insert() takes 3 or 4 args, got {}", args.len()),
3347 });
3348 }
3349 if args.iter().take(3).any(|v| matches!(v, Value::Null)) {
3350 return Ok(Value::Null);
3351 }
3352 let insert_after = match args.get(3) {
3353 None | Some(Value::Null) => false,
3354 Some(Value::Bool(b)) => *b,
3355 Some(other) => {
3356 return Err(EvalError::TypeMismatch {
3357 detail: alloc::format!(
3358 "jsonb_insert() insert_after must be BOOL, got {}",
3359 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3360 ),
3361 });
3362 }
3363 };
3364 let doc_text = json_text_arg(&args[0], "jsonb_insert", "target")?;
3365 let path = path_text_arg(&args[1], "jsonb_insert")?;
3366 let new_text = json_text_arg(&args[2], "jsonb_insert", "new_value")?;
3367 let mut root = parse(doc_text).map_err(|e| EvalError::TypeMismatch {
3368 detail: alloc::format!("jsonb_insert(): invalid JSON target — {e}"),
3369 })?;
3370 if path.is_empty() {
3374 return Ok(Value::json(root.to_json_text()));
3375 }
3376 if is_json_scalar(&root) {
3377 return Err(EvalError::TypeMismatch {
3378 detail: "cannot set path in scalar".into(),
3379 });
3380 }
3381 let new_val = parse(new_text).map_err(|e| EvalError::TypeMismatch {
3382 detail: alloc::format!("jsonb_insert(): invalid JSON new_value — {e}"),
3383 })?;
3384 insert_at_path(&mut root, &path, new_val, insert_after)?;
3385 Ok(Value::json(root.to_json_text()))
3386}
3387
3388fn insert_at_path(
3389 node: &mut JsonValue,
3390 path: &[String],
3391 new_val: JsonValue,
3392 insert_after: bool,
3393) -> Result<(), EvalError> {
3394 debug_assert!(!path.is_empty());
3395 if path.len() == 1 {
3396 let step = &path[0];
3397 match node {
3398 JsonValue::Object(entries) => {
3399 if entries.iter().any(|(k, _)| k == step) {
3400 return Err(EvalError::TypeMismatch {
3401 detail: alloc::format!(
3402 "jsonb_insert(): cannot replace existing key {step:?}"
3403 ),
3404 });
3405 }
3406 entries.push((step.clone(), new_val));
3407 Ok(())
3408 }
3409 JsonValue::Array(items) => {
3410 let Ok(n) = step.parse::<i64>() else {
3411 return Err(EvalError::TypeMismatch {
3412 detail: alloc::format!(
3413 "jsonb_insert(): array step must be integer, got {step:?}"
3414 ),
3415 });
3416 };
3417 let mut idx = if n >= 0 {
3418 let i = n as usize;
3419 if i > items.len() { items.len() } else { i }
3420 } else {
3421 let from_end = items.len() as i64 + n;
3422 if from_end < 0 { 0 } else { from_end as usize }
3423 };
3424 if insert_after && idx < items.len() {
3425 idx += 1;
3426 }
3427 items.insert(idx, new_val);
3428 Ok(())
3429 }
3430 _ => Err(EvalError::TypeMismatch {
3431 detail: "jsonb_insert(): parent at path is a scalar".into(),
3432 }),
3433 }
3434 } else {
3435 let step = &path[0];
3436 let rest = &path[1..];
3437 match node {
3438 JsonValue::Object(entries) => {
3439 if let Some(pos) = entries.iter().position(|(k, _)| k == step) {
3440 insert_at_path(&mut entries[pos].1, rest, new_val, insert_after)
3441 } else {
3442 Err(EvalError::TypeMismatch {
3443 detail: alloc::format!("jsonb_insert(): path {step:?} does not exist"),
3444 })
3445 }
3446 }
3447 JsonValue::Array(items) => {
3448 let Some(idx) = resolve_array_index(step, items.len()) else {
3449 return Err(EvalError::TypeMismatch {
3450 detail: alloc::format!("jsonb_insert(): array index {step:?} out of range"),
3451 });
3452 };
3453 insert_at_path(&mut items[idx], rest, new_val, insert_after)
3454 }
3455 _ => Err(EvalError::TypeMismatch {
3456 detail: "jsonb_insert(): parent at path is a scalar".into(),
3457 }),
3458 }
3459 }
3460}
3461
3462fn json_text_arg<'a>(v: &'a Value, fname: &str, role: &str) -> Result<&'a str, EvalError> {
3463 match v {
3464 Value::Json(s) | Value::Text(s) => Ok(s.as_ref()),
3465 other => Err(EvalError::TypeMismatch {
3466 detail: alloc::format!(
3467 "{fname}() {role} must be JSON or TEXT, got {}",
3468 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3469 ),
3470 }),
3471 }
3472}
3473
3474fn path_text_arg(v: &Value, fname: &str) -> Result<Vec<String>, EvalError> {
3475 match v {
3476 Value::Text(s) | Value::Json(s) => parse_text_array(s.as_ref()),
3477 Value::TextArray(items) => Ok(items
3478 .iter()
3479 .map(|o| o.clone().unwrap_or_default())
3480 .collect()),
3481 other => Err(EvalError::TypeMismatch {
3482 detail: alloc::format!(
3483 "{fname}() path must be TEXT[] or TEXT, got {}",
3484 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3485 ),
3486 }),
3487 }
3488}
3489
3490#[cfg(test)]
3491mod tests {
3492 use super::*;
3493
3494 fn canon(s: &str) -> String {
3495 canonicalize_jsonb(s).unwrap()
3496 }
3497
3498 #[test]
3499 fn canon_number_rules() {
3500 assert_eq!(canon_json_number("1.0"), "1.0");
3502 assert_eq!(canon_json_number("1e2"), "100");
3503 assert_eq!(canon_json_number("1.10"), "1.10");
3504 assert_eq!(canon_json_number("100.00"), "100.00");
3505 assert_eq!(canon_json_number("0.5"), "0.5");
3506 assert_eq!(canon_json_number("-0"), "0");
3507 assert_eq!(canon_json_number("1E-3"), "0.001");
3508 assert_eq!(canon_json_number("42"), "42");
3509 assert_eq!(canon_json_number("-2.5"), "-2.5");
3510 assert_eq!(canon_json_number("2.5e3"), "2500");
3511 }
3512
3513 #[test]
3514 fn canon_key_order_dedup_and_whitespace() {
3515 assert_eq!(
3517 canon(r#"{"b":1,"a":2,"aa":3,"":9,"z":4}"#),
3518 r#"{"": 9, "a": 2, "b": 1, "z": 4, "aa": 3}"#
3519 );
3520 assert_eq!(canon(r#"{"a":1,"a":2,"a":3}"#), r#"{"a": 3}"#);
3522 assert_eq!(canon("[3,2,1]"), "[3, 2, 1]");
3524 }
3525
3526 #[test]
3527 fn json_number_equality_by_value() {
3528 let eq = |a: &str, b: &str| json_eq(&parse(a).unwrap(), &parse(b).unwrap());
3529 assert!(eq("1", "1.0"));
3530 assert!(eq("1.50", "1.5"));
3531 assert!(eq("1e3", "1000.00"));
3532 assert!(eq("0", "-0"));
3533 assert!(eq("2.5e3", "2500"));
3534 assert!(!eq("1.5", "1.6"));
3535 assert!(eq("[1, 2.0]", "[1.0, 2]"));
3537 assert!(eq(r#"{"a":1}"#, r#"{"a":1.0}"#));
3538 }
3539
3540 #[test]
3541 fn canon_nested_and_scalars() {
3542 assert_eq!(
3543 canon(r#"{"x":{"b":1,"a":2},"y":[3,{"d":1,"c":2}]}"#),
3544 r#"{"x": {"a": 2, "b": 1}, "y": [3, {"c": 2, "d": 1}]}"#
3545 );
3546 assert_eq!(canon(" true "), "true");
3547 assert_eq!(canon(" 42 "), "42");
3548 assert_eq!(canon("{}"), "{}");
3549 assert_eq!(canon("[]"), "[]");
3550 assert_eq!(
3552 canon(r#"{"e":"café","t":"a\nb"}"#),
3553 r#"{"e": "café", "t": "a\nb"}"#
3554 );
3555 }
3556
3557 #[test]
3558 fn parse_atoms() {
3559 assert_eq!(parse("null").unwrap(), JsonValue::Null);
3560 assert_eq!(parse("true").unwrap(), JsonValue::Bool(true));
3561 assert_eq!(parse("false").unwrap(), JsonValue::Bool(false));
3562 assert_eq!(
3563 parse("\"hello\"").unwrap(),
3564 JsonValue::String("hello".into())
3565 );
3566 assert!(matches!(
3567 parse("42").unwrap(),
3568 JsonValue::NumberText(ref s) if s == "42"
3569 ));
3570 }
3571
3572 #[test]
3573 fn parse_nested() {
3574 let doc = parse(r#"{"a":1,"b":[true,null,"x"]}"#).unwrap();
3575 let JsonValue::Object(entries) = doc else {
3576 panic!("expected object");
3577 };
3578 assert_eq!(entries.len(), 2);
3579 assert_eq!(entries[0].0, "a");
3580 assert_eq!(entries[1].0, "b");
3581 }
3582
3583 #[test]
3584 fn parse_string_escapes() {
3585 let s = parse(r#""he said \"hi\" and\\then\n""#).unwrap();
3586 assert_eq!(s, JsonValue::String("he said \"hi\" and\\then\n".into()));
3587 }
3588
3589 #[test]
3590 fn parse_unicode_escape() {
3591 assert_eq!(parse(r#""é""#).unwrap(), JsonValue::String("é".into()));
3592 }
3593
3594 #[test]
3595 fn path_object_key_returns_value() {
3596 let doc = Value::json::<String>(r#"{"name":"alice","age":30}"#.into());
3597 let key = Value::text("name");
3598 let v = path_get(&doc, &key, true).unwrap();
3599 assert_eq!(v, Value::text("alice"));
3600 let v = path_get(&doc, &key, false).unwrap();
3601 assert_eq!(v, Value::json("\"alice\""));
3602 }
3603
3604 #[test]
3605 fn path_array_index_supports_negative() {
3606 let doc = Value::json("[10,20,30]");
3607 let v = path_get(&doc, &Value::Int(1), true).unwrap();
3608 assert_eq!(v, Value::text("20"));
3609 let v = path_get(&doc, &Value::Int(-1), true).unwrap();
3610 assert_eq!(v, Value::text("30"));
3611 }
3612
3613 #[test]
3614 fn path_missing_key_returns_null() {
3615 let doc = Value::json::<String>(r#"{"a":1}"#.into());
3616 let v = path_get(&doc, &Value::text("missing"), true).unwrap();
3617 assert_eq!(v, Value::Null);
3618 }
3619
3620 #[test]
3621 fn path_get_nested_subtree_is_verbatim() {
3622 let doc = Value::json::<String>(r#"{"k":{"x":[1,2]}}"#.into());
3628 let v = path_get(&doc, &Value::text("k"), false).unwrap();
3629 assert_eq!(v, Value::json::<String>(r#"{"x":[1,2]}"#.into()));
3630
3631 let canon = Value::json::<String>(r#"{"k": {"x": [1, 2]}}"#.into());
3633 let v = path_get(&canon, &Value::text("k"), false).unwrap();
3634 assert_eq!(v, Value::json::<String>(r#"{"x": [1, 2]}"#.into()));
3635
3636 let raw = Value::json::<String>(r#"{"a":{ "y" : 2e2 },"k":1,"k":2}"#.into());
3639 assert_eq!(
3640 path_get(&raw, &Value::text("a"), false).unwrap(),
3641 Value::json::<String>(r#"{ "y" : 2e2 }"#.into())
3642 );
3643 assert_eq!(
3644 path_get(&raw, &Value::text("k"), false).unwrap(),
3645 Value::json::<String>("2".into())
3646 );
3647
3648 let n = Value::json::<String>(r#"{"a":null}"#.into());
3650 assert_eq!(
3651 path_get(&n, &Value::text("a"), false).unwrap(),
3652 Value::json::<String>("null".into())
3653 );
3654 assert_eq!(path_get(&n, &Value::text("a"), true).unwrap(), Value::Null);
3655 }
3656}
3657
3658#[derive(Debug)]
3661pub enum MysqlPathStep {
3662 Key(String),
3663 Index(usize),
3664}
3665
3666pub fn mysql_path_steps(path: &str) -> Result<Vec<MysqlPathStep>, EvalError> {
3671 let chars: Vec<char> = path.trim().chars().collect();
3672 if chars.first() != Some(&'$') {
3673 return Err(EvalError::TypeMismatch {
3674 detail: alloc::format!("invalid JSON path expression (must start with $): {path:?}"),
3675 });
3676 }
3677 let mut steps = Vec::new();
3678 let mut i = 1;
3679 while i < chars.len() {
3680 match chars[i] {
3681 '.' => {
3682 i += 1;
3683 if i < chars.len() && chars[i] == '"' {
3684 i += 1;
3685 let mut key = String::new();
3686 while i < chars.len() && chars[i] != '"' {
3687 if chars[i] == '\\' && i + 1 < chars.len() {
3688 i += 1;
3689 }
3690 key.push(chars[i]);
3691 i += 1;
3692 }
3693 if i >= chars.len() {
3694 return Err(EvalError::TypeMismatch {
3695 detail: alloc::format!(
3696 "invalid JSON path expression (unterminated quote): {path:?}"
3697 ),
3698 });
3699 }
3700 i += 1; steps.push(MysqlPathStep::Key(key));
3702 } else {
3703 let mut key = String::new();
3704 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
3705 key.push(chars[i]);
3706 i += 1;
3707 }
3708 if key.is_empty() {
3709 return Err(EvalError::TypeMismatch {
3710 detail: alloc::format!(
3711 "unsupported JSON path step at position {i} in {path:?} \
3712 (wildcards are not supported)"
3713 ),
3714 });
3715 }
3716 steps.push(MysqlPathStep::Key(key));
3717 }
3718 }
3719 '[' => {
3720 i += 1;
3721 let mut num = String::new();
3722 while i < chars.len() && chars[i] != ']' {
3723 num.push(chars[i]);
3724 i += 1;
3725 }
3726 if i >= chars.len() {
3727 return Err(EvalError::TypeMismatch {
3728 detail: alloc::format!(
3729 "invalid JSON path expression (unterminated bracket): {path:?}"
3730 ),
3731 });
3732 }
3733 i += 1; let idx: usize = num.trim().parse().map_err(|_| EvalError::TypeMismatch {
3735 detail: alloc::format!(
3736 "unsupported JSON path index {num:?} in {path:?} \
3737 (wildcards are not supported)"
3738 ),
3739 })?;
3740 steps.push(MysqlPathStep::Index(idx));
3741 }
3742 other => {
3743 return Err(EvalError::TypeMismatch {
3744 detail: alloc::format!(
3745 "invalid JSON path expression (unexpected {other:?}): {path:?}"
3746 ),
3747 });
3748 }
3749 }
3750 }
3751 Ok(steps)
3752}
3753
3754pub fn mysql_path_get<'a>(doc: &'a JsonValue, steps: &[MysqlPathStep]) -> Option<&'a JsonValue> {
3757 let mut cur = doc;
3758 for step in steps {
3759 match (step, cur) {
3760 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
3761 cur = members.iter().find(|(mk, _)| mk == k).map(|(_, v)| v)?;
3762 }
3763 (MysqlPathStep::Index(idx), JsonValue::Array(items)) => {
3764 cur = items.get(*idx)?;
3765 }
3766 (MysqlPathStep::Index(0), scalar) => {
3769 cur = scalar;
3770 }
3771 _ => return None,
3772 }
3773 }
3774 Some(cur)
3775}
3776
3777pub fn mysql_json_extract(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3782 if args.len() < 2 {
3783 return Err(EvalError::TypeMismatch {
3784 detail: alloc::format!(
3785 "json_extract() takes a document and at least one path, got {} args",
3786 args.len()
3787 ),
3788 });
3789 }
3790 if args.iter().any(|a| matches!(a, Value::Null)) {
3791 return Ok(Value::Null);
3792 }
3793 let src = match &args[0] {
3794 Value::Json(s) | Value::Text(s) => s.as_ref(),
3795 other => {
3796 return Err(EvalError::TypeMismatch {
3797 detail: alloc::format!(
3798 "json_extract() document must be json, got {}",
3799 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3800 ),
3801 });
3802 }
3803 };
3804 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
3805 detail: alloc::format!("json_extract(): invalid JSON: {e}"),
3806 })?;
3807 let mut hits: Vec<String> = Vec::new();
3808 for path_v in &args[1..] {
3809 let Value::Text(p) = path_v else {
3810 return Err(EvalError::TypeMismatch {
3811 detail: alloc::format!(
3812 "json_extract() paths must be text, got {}",
3813 crate::conversions::pg_type_name_for_error_opt(path_v.data_type())
3814 ),
3815 });
3816 };
3817 let steps = mysql_path_steps(p)?;
3818 if let Some(v) = mysql_path_get(&doc, &steps) {
3819 hits.push(v.to_json_text());
3820 }
3821 }
3822 match (args.len() - 1, hits.len()) {
3823 (_, 0) => Ok(Value::Null),
3824 (1, _) => Ok(Value::Json(alloc::borrow::Cow::Owned(
3825 hits.into_iter().next().unwrap(),
3826 ))),
3827 _ => {
3828 let mut out = String::from("[");
3829 for (i, h) in hits.iter().enumerate() {
3830 if i > 0 {
3831 out.push_str(", ");
3832 }
3833 out.push_str(h);
3834 }
3835 out.push(']');
3836 Ok(Value::Json(alloc::borrow::Cow::Owned(out)))
3837 }
3838 }
3839}
3840
3841pub fn mysql_json_contains_path(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
3844 if args.len() < 3 {
3845 return Err(EvalError::TypeMismatch {
3846 detail: alloc::format!(
3847 "json_contains_path() takes a document, one/all, and at least one path, got {} args",
3848 args.len()
3849 ),
3850 });
3851 }
3852 if args.iter().any(|a| matches!(a, Value::Null)) {
3853 return Ok(Value::Null);
3854 }
3855 let src = match &args[0] {
3856 Value::Json(s) | Value::Text(s) => s.as_ref(),
3857 other => {
3858 return Err(EvalError::TypeMismatch {
3859 detail: alloc::format!(
3860 "json_contains_path() document must be json, got {}",
3861 crate::conversions::pg_type_name_for_error_opt(other.data_type())
3862 ),
3863 });
3864 }
3865 };
3866 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
3867 detail: alloc::format!("json_contains_path(): invalid JSON: {e}"),
3868 })?;
3869 let mode = match &args[1] {
3870 Value::Text(m) if m.eq_ignore_ascii_case("one") => false,
3871 Value::Text(m) if m.eq_ignore_ascii_case("all") => true,
3872 other => {
3873 return Err(EvalError::TypeMismatch {
3874 detail: alloc::format!(
3875 "json_contains_path() second arg must be 'one' or 'all', got {other:?}"
3876 ),
3877 });
3878 }
3879 };
3880 let mut found_any = false;
3881 let mut found_all = true;
3882 for path_v in &args[2..] {
3883 let Value::Text(p) = path_v else {
3884 return Err(EvalError::TypeMismatch {
3885 detail: alloc::format!(
3886 "json_contains_path() paths must be text, got {}",
3887 crate::conversions::pg_type_name_for_error_opt(path_v.data_type())
3888 ),
3889 });
3890 };
3891 let steps = mysql_path_steps(p)?;
3892 if mysql_path_get(&doc, &steps).is_some() {
3893 found_any = true;
3894 } else {
3895 found_all = false;
3896 }
3897 }
3898 Ok(Value::Bool(if mode { found_all } else { found_any }))
3899}
3900
3901fn value_to_jsonvalue(v: &Value) -> Result<JsonValue, EvalError> {
3905 Ok(match v {
3906 Value::Null => JsonValue::Null,
3907 Value::Bool(b) => JsonValue::Bool(*b),
3908 Value::Json(s) => parse(s).map_err(|e| EvalError::TypeMismatch {
3909 detail: alloc::format!("invalid JSON value: {e}"),
3910 })?,
3911 Value::Text(s) => JsonValue::String(s.to_string()),
3912 Value::Composite(fields) => {
3915 let mut entries = alloc::vec::Vec::with_capacity(fields.len());
3916 for (name, fv) in fields.iter() {
3917 entries.push((name.clone(), value_to_jsonvalue(fv)?));
3918 }
3919 JsonValue::Object(entries)
3920 }
3921 other => {
3922 let text = value_to_json_text(other);
3925 parse(&text).map_err(|e| EvalError::TypeMismatch {
3926 detail: alloc::format!("invalid JSON value: {e}"),
3927 })?
3928 }
3929 })
3930}
3931
3932#[derive(Clone, Copy, PartialEq, Debug)]
3933enum MutateMode {
3934 Set,
3936 Insert,
3938 Replace,
3940}
3941
3942fn mutate_at(cur: &mut JsonValue, steps: &[MysqlPathStep], mode: MutateMode, newval: &JsonValue) {
3945 match steps {
3946 [] => {
3947 if matches!(mode, MutateMode::Set | MutateMode::Replace) {
3948 *cur = newval.clone();
3949 }
3950 }
3951 [last] => match (last, &mut *cur) {
3952 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
3953 if let Some(slot) = members.iter_mut().find(|(mk, _)| mk == k) {
3954 if matches!(mode, MutateMode::Set | MutateMode::Replace) {
3955 slot.1 = newval.clone();
3956 }
3957 } else if matches!(mode, MutateMode::Set | MutateMode::Insert) {
3958 members.push((k.clone(), newval.clone()));
3959 }
3960 }
3961 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
3962 if *i < items.len() {
3963 if matches!(mode, MutateMode::Set | MutateMode::Replace) {
3964 items[*i] = newval.clone();
3965 }
3966 } else if matches!(mode, MutateMode::Set | MutateMode::Insert) {
3967 items.push(newval.clone());
3969 }
3970 }
3971 (MysqlPathStep::Index(0), scalar) => {
3973 if matches!(mode, MutateMode::Set | MutateMode::Replace) {
3974 *scalar = newval.clone();
3975 }
3976 }
3977 _ => {}
3978 },
3979 [head, rest @ ..] => match (head, cur) {
3980 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
3981 if let Some(slot) = members.iter_mut().find(|(mk, _)| mk == k) {
3982 mutate_at(&mut slot.1, rest, mode, newval);
3983 }
3984 }
3985 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
3986 if let Some(slot) = items.get_mut(*i) {
3987 mutate_at(slot, rest, mode, newval);
3988 }
3989 }
3990 _ => {}
3991 },
3992 }
3993}
3994
3995fn mysql_json_mutate(
3996 args: &[Value<'_>],
3997 mode: MutateMode,
3998 fn_name: &str,
3999) -> Result<Value<'static>, EvalError> {
4000 if args.len() < 3 || args.len() % 2 == 0 {
4001 return Err(EvalError::TypeMismatch {
4002 detail: alloc::format!(
4003 "{fn_name}() takes a document plus (path, value) pairs, got {} args",
4004 args.len()
4005 ),
4006 });
4007 }
4008 if matches!(args[0], Value::Null) {
4009 return Ok(Value::Null);
4010 }
4011 let src = match &args[0] {
4012 Value::Json(s) | Value::Text(s) => s.as_ref(),
4013 other => {
4014 return Err(EvalError::TypeMismatch {
4015 detail: alloc::format!(
4016 "{fn_name}() document must be json, got {}",
4017 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4018 ),
4019 });
4020 }
4021 };
4022 let mut doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4023 detail: alloc::format!("{fn_name}(): invalid JSON: {e}"),
4024 })?;
4025 for pair in args[1..].chunks(2) {
4026 let Value::Text(p) = &pair[0] else {
4027 if matches!(pair[0], Value::Null) {
4028 return Ok(Value::Null);
4029 }
4030 return Err(EvalError::TypeMismatch {
4031 detail: alloc::format!(
4032 "{fn_name}() paths must be text, got {}",
4033 crate::conversions::pg_type_name_for_error_opt(pair[0].data_type())
4034 ),
4035 });
4036 };
4037 let steps = mysql_path_steps(p)?;
4038 let newval = value_to_jsonvalue(&pair[1])?;
4039 mutate_at(&mut doc, &steps, mode, &newval);
4040 }
4041 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4045 doc.to_json_text(),
4046 ))))
4047}
4048
4049pub fn mysql_json_set(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4053 mysql_json_mutate(args, MutateMode::Set, "json_set")
4054}
4055
4056pub fn mysql_json_insert(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4057 mysql_json_mutate(args, MutateMode::Insert, "json_insert")
4058}
4059
4060pub fn mysql_json_replace(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4061 mysql_json_mutate(args, MutateMode::Replace, "json_replace")
4062}
4063
4064pub fn mysql_json_remove(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4067 if args.len() < 2 {
4068 return Err(EvalError::TypeMismatch {
4069 detail: alloc::format!(
4070 "json_remove() takes a document and at least one path, got {} args",
4071 args.len()
4072 ),
4073 });
4074 }
4075 if args.iter().any(|a| matches!(a, Value::Null)) {
4076 return Ok(Value::Null);
4077 }
4078 let src = match &args[0] {
4079 Value::Json(s) | Value::Text(s) => s.as_ref(),
4080 other => {
4081 return Err(EvalError::TypeMismatch {
4082 detail: alloc::format!(
4083 "json_remove() document must be json, got {}",
4084 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4085 ),
4086 });
4087 }
4088 };
4089 let mut doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4090 detail: alloc::format!("json_remove(): invalid JSON: {e}"),
4091 })?;
4092 fn remove_at(cur: &mut JsonValue, steps: &[MysqlPathStep]) {
4093 match steps {
4094 [] => {}
4095 [last] => match (last, cur) {
4096 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4097 members.retain(|(mk, _)| mk != k);
4098 }
4099 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
4100 if *i < items.len() {
4101 items.remove(*i);
4102 }
4103 }
4104 _ => {}
4105 },
4106 [head, rest @ ..] => match (head, cur) {
4107 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4108 if let Some(slot) = members.iter_mut().find(|(mk, _)| mk == k) {
4109 remove_at(&mut slot.1, rest);
4110 }
4111 }
4112 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
4113 if let Some(slot) = items.get_mut(*i) {
4114 remove_at(slot, rest);
4115 }
4116 }
4117 _ => {}
4118 },
4119 }
4120 }
4121 for path_v in &args[1..] {
4122 let Value::Text(p) = path_v else {
4123 return Err(EvalError::TypeMismatch {
4124 detail: alloc::format!(
4125 "json_remove() paths must be text, got {}",
4126 crate::conversions::pg_type_name_for_error_opt(path_v.data_type())
4127 ),
4128 });
4129 };
4130 let steps = mysql_path_steps(p)?;
4131 if steps.is_empty() {
4132 return Err(EvalError::TypeMismatch {
4133 detail: "The path expression '$' is not allowed in this context".into(),
4134 });
4135 }
4136 remove_at(&mut doc, &steps);
4137 }
4138 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4140 doc.to_json_text(),
4141 ))))
4142}
4143
4144fn modify_at(cur: &mut JsonValue, steps: &[MysqlPathStep], f: &mut dyn FnMut(&mut JsonValue)) {
4147 match steps {
4148 [] => f(cur),
4149 [head, rest @ ..] => match (head, cur) {
4150 (MysqlPathStep::Key(k), JsonValue::Object(members)) => {
4151 if let Some(slot) = members.iter_mut().find(|(mk, _)| mk == k) {
4152 modify_at(&mut slot.1, rest, f);
4153 }
4154 }
4155 (MysqlPathStep::Index(i), JsonValue::Array(items)) => {
4156 if let Some(slot) = items.get_mut(*i) {
4157 modify_at(slot, rest, f);
4158 }
4159 }
4160 _ => {}
4161 },
4162 }
4163}
4164
4165fn mysql_doc_and_pairs<'a>(
4167 args: &'a [Value<'_>],
4168 fn_name: &str,
4169) -> Result<Option<(JsonValue, &'a [Value<'a>])>, EvalError> {
4170 if args.len() < 3 || args.len() % 2 == 0 {
4171 return Err(EvalError::TypeMismatch {
4172 detail: alloc::format!(
4173 "{fn_name}() takes a document plus (path, value) pairs, got {} args",
4174 args.len()
4175 ),
4176 });
4177 }
4178 if args.iter().any(|a| matches!(a, Value::Null)) {
4179 return Ok(None);
4180 }
4181 let src = match &args[0] {
4182 Value::Json(s) | Value::Text(s) => s.as_ref(),
4183 other => {
4184 return Err(EvalError::TypeMismatch {
4185 detail: alloc::format!(
4186 "{fn_name}() document must be json, got {}",
4187 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4188 ),
4189 });
4190 }
4191 };
4192 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4193 detail: alloc::format!("{fn_name}(): invalid JSON: {e}"),
4194 })?;
4195 Ok(Some((doc, &args[1..])))
4196}
4197
4198pub fn mysql_json_array_append(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4202 let Some((mut doc, pairs)) = mysql_doc_and_pairs(args, "json_array_append")? else {
4203 return Ok(Value::Null);
4204 };
4205 for pair in pairs.chunks(2) {
4206 let Value::Text(p) = &pair[0] else {
4207 return Err(EvalError::TypeMismatch {
4208 detail: alloc::format!(
4209 "json_array_append() paths must be text, got {}",
4210 crate::conversions::pg_type_name_for_error_opt(pair[0].data_type())
4211 ),
4212 });
4213 };
4214 let steps = mysql_path_steps(p)?;
4215 let newval = value_to_jsonvalue(&pair[1])?;
4216 modify_at(&mut doc, &steps, &mut |v| match v {
4217 JsonValue::Array(items) => items.push(newval.clone()),
4218 other => {
4219 let old = core::mem::replace(other, JsonValue::Null);
4220 *other = JsonValue::Array(alloc::vec![old, newval.clone()]);
4221 }
4222 });
4223 }
4224 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4226 doc.to_json_text(),
4227 ))))
4228}
4229
4230pub fn mysql_json_array_insert(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4235 let Some((mut doc, pairs)) = mysql_doc_and_pairs(args, "json_array_insert")? else {
4236 return Ok(Value::Null);
4237 };
4238 for pair in pairs.chunks(2) {
4239 let Value::Text(p) = &pair[0] else {
4240 return Err(EvalError::TypeMismatch {
4241 detail: alloc::format!(
4242 "json_array_insert() paths must be text, got {}",
4243 crate::conversions::pg_type_name_for_error_opt(pair[0].data_type())
4244 ),
4245 });
4246 };
4247 let steps = mysql_path_steps(p)?;
4248 let Some(MysqlPathStep::Index(idx)) = steps.last() else {
4249 return Err(EvalError::TypeMismatch {
4250 detail: alloc::format!(
4251 "json_array_insert() path must end with an array index: {p:?}"
4252 ),
4253 });
4254 };
4255 let idx = *idx;
4256 let newval = value_to_jsonvalue(&pair[1])?;
4257 modify_at(&mut doc, &steps[..steps.len() - 1], &mut |v| {
4258 if let JsonValue::Array(items) = v {
4259 let at = idx.min(items.len());
4260 items.insert(at, newval.clone());
4261 }
4262 });
4263 }
4264 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4266 doc.to_json_text(),
4267 ))))
4268}
4269
4270fn mysql_contains(target: &JsonValue, cand: &JsonValue) -> bool {
4275 match (target, cand) {
4276 (JsonValue::Object(t), JsonValue::Object(c)) => c
4277 .iter()
4278 .all(|(ck, cv)| t.iter().any(|(tk, tv)| tk == ck && mysql_contains(tv, cv))),
4279 (JsonValue::Array(t), JsonValue::Array(c)) => {
4280 c.iter().all(|cv| t.iter().any(|tv| mysql_contains(tv, cv)))
4281 }
4282 (JsonValue::Array(t), scalar) => t.iter().any(|tv| mysql_contains(tv, scalar)),
4283 (JsonValue::Number(a), JsonValue::NumberText(b))
4285 | (JsonValue::NumberText(b), JsonValue::Number(a)) => {
4286 b.parse::<f64>().map(|x| x == *a).unwrap_or(false)
4287 }
4288 (JsonValue::NumberText(a), JsonValue::NumberText(b)) => {
4289 a == b
4290 || (a.parse::<f64>().ok().zip(b.parse::<f64>().ok()))
4291 .map(|(x, y)| x == y)
4292 .unwrap_or(false)
4293 }
4294 (a, b) => a == b,
4295 }
4296}
4297
4298pub fn mysql_json_contains(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4301 if !matches!(args.len(), 2 | 3) {
4302 return Err(EvalError::TypeMismatch {
4303 detail: alloc::format!("json_contains() takes 2 or 3 args, got {}", args.len()),
4304 });
4305 }
4306 if args.iter().any(|a| matches!(a, Value::Null)) {
4307 return Ok(Value::Null);
4308 }
4309 let parse_arg = |v: &Value<'_>, which: &str| -> Result<JsonValue, EvalError> {
4310 match v {
4311 Value::Json(s) | Value::Text(s) => parse(s).map_err(|e| EvalError::TypeMismatch {
4312 detail: alloc::format!("json_contains(): invalid {which} JSON: {e}"),
4313 }),
4314 other => Err(EvalError::TypeMismatch {
4315 detail: alloc::format!(
4316 "json_contains() {which} must be json, got {}",
4317 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4318 ),
4319 }),
4320 }
4321 };
4322 let target = parse_arg(&args[0], "target")?;
4323 let cand = parse_arg(&args[1], "candidate")?;
4324 let effective = match args.get(2) {
4325 None => Some(&target),
4326 Some(Value::Text(p)) => {
4327 let steps = mysql_path_steps(p)?;
4328 mysql_path_get(&target, &steps)
4329 }
4330 Some(other) => {
4331 return Err(EvalError::TypeMismatch {
4332 detail: alloc::format!(
4333 "json_contains() path must be text, got {}",
4334 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4335 ),
4336 });
4337 }
4338 };
4339 match effective {
4340 None => Ok(Value::Null),
4341 Some(t) => Ok(Value::Bool(mysql_contains(t, &cand))),
4342 }
4343}
4344
4345fn merge_patch(target: JsonValue, patch: JsonValue) -> JsonValue {
4349 let JsonValue::Object(patch_members) = patch else {
4350 return patch;
4351 };
4352 let mut out = match target {
4353 JsonValue::Object(members) => members,
4354 _ => Vec::new(),
4355 };
4356 for (k, v) in patch_members {
4357 if matches!(v, JsonValue::Null) {
4358 out.retain(|(mk, _)| *mk != k);
4359 } else if let Some(slot) = out.iter_mut().find(|(mk, _)| *mk == k) {
4360 let old = core::mem::replace(&mut slot.1, JsonValue::Null);
4361 slot.1 = merge_patch(old, v);
4362 } else {
4363 out.push((k, merge_patch(JsonValue::Null, v)));
4365 }
4366 }
4367 JsonValue::Object(out)
4368}
4369
4370fn merge_preserve(a: JsonValue, b: JsonValue) -> JsonValue {
4375 match (a, b) {
4376 (JsonValue::Object(mut ma), JsonValue::Object(mb)) => {
4377 for (k, v) in mb {
4378 if let Some(pos) = ma.iter().position(|(mk, _)| *mk == k) {
4379 let (_, old) = ma.remove(pos);
4380 ma.insert(pos, (k, merge_preserve(old, v)));
4381 } else {
4382 ma.push((k, v));
4383 }
4384 }
4385 JsonValue::Object(ma)
4386 }
4387 (JsonValue::Array(mut xs), JsonValue::Array(ys)) => {
4388 xs.extend(ys);
4389 JsonValue::Array(xs)
4390 }
4391 (JsonValue::Array(mut xs), scalar) => {
4392 xs.push(scalar);
4393 JsonValue::Array(xs)
4394 }
4395 (scalar, JsonValue::Array(ys)) => {
4396 let mut xs = alloc::vec![scalar];
4397 xs.extend(ys);
4398 JsonValue::Array(xs)
4399 }
4400 (sa, sb) => JsonValue::Array(alloc::vec![sa, sb]),
4401 }
4402}
4403
4404fn mysql_json_merge(
4405 args: &[Value<'_>],
4406 fn_name: &str,
4407 combine: fn(JsonValue, JsonValue) -> JsonValue,
4408) -> Result<Value<'static>, EvalError> {
4409 if args.len() < 2 {
4410 return Err(EvalError::TypeMismatch {
4411 detail: alloc::format!("{fn_name}() takes at least 2 documents, got {}", args.len()),
4412 });
4413 }
4414 if args.iter().any(|a| matches!(a, Value::Null)) {
4415 return Ok(Value::Null);
4416 }
4417 let mut acc: Option<JsonValue> = None;
4418 for arg in args {
4419 let src = match arg {
4420 Value::Json(s) | Value::Text(s) => s.as_ref(),
4421 other => {
4422 return Err(EvalError::TypeMismatch {
4423 detail: alloc::format!(
4424 "{fn_name}() arguments must be json, got {}",
4425 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4426 ),
4427 });
4428 }
4429 };
4430 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4431 detail: alloc::format!("{fn_name}(): invalid JSON: {e}"),
4432 })?;
4433 acc = Some(match acc {
4434 None => doc,
4435 Some(prev) => combine(prev, doc),
4436 });
4437 }
4438 Ok(canonicalize_value(Value::Json(alloc::borrow::Cow::Owned(
4440 acc.unwrap().to_json_text(),
4441 ))))
4442}
4443
4444pub fn mysql_json_merge_patch(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4446 mysql_json_merge(args, "json_merge_patch", merge_patch)
4447}
4448
4449pub fn mysql_json_merge_preserve(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4452 mysql_json_merge(args, "json_merge_preserve", merge_preserve)
4453}
4454
4455pub fn mysql_json_overlaps(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4459 if args.len() != 2 {
4460 return Err(EvalError::TypeMismatch {
4461 detail: alloc::format!("json_overlaps() takes 2 args, got {}", args.len()),
4462 });
4463 }
4464 if args.iter().any(|a| matches!(a, Value::Null)) {
4465 return Ok(Value::Null);
4466 }
4467 let parse_arg = |v: &Value<'_>| -> Result<JsonValue, EvalError> {
4468 match v {
4469 Value::Json(s) | Value::Text(s) => parse(s).map_err(|e| EvalError::TypeMismatch {
4470 detail: alloc::format!("json_overlaps(): invalid JSON: {e}"),
4471 }),
4472 other => Err(EvalError::TypeMismatch {
4473 detail: alloc::format!(
4474 "json_overlaps() arguments must be json, got {}",
4475 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4476 ),
4477 }),
4478 }
4479 };
4480 let a = parse_arg(&args[0])?;
4481 let b = parse_arg(&args[1])?;
4482 let overlaps = match (&a, &b) {
4483 (JsonValue::Array(xs), JsonValue::Array(ys)) => xs.iter().any(|x| {
4484 ys.iter()
4485 .any(|y| mysql_contains(x, y) && mysql_contains(y, x))
4486 }),
4487 (JsonValue::Object(ma), JsonValue::Object(mb)) => ma.iter().any(|(k, v)| {
4488 mb.iter()
4489 .any(|(k2, v2)| k == k2 && mysql_contains(v, v2) && mysql_contains(v2, v))
4490 }),
4491 (JsonValue::Array(xs), scalar) | (scalar, JsonValue::Array(xs)) => xs
4492 .iter()
4493 .any(|x| mysql_contains(x, scalar) && mysql_contains(scalar, x)),
4494 (sa, sb) => mysql_contains(sa, sb) && mysql_contains(sb, sa),
4495 };
4496 Ok(Value::Bool(overlaps))
4497}
4498
4499fn like_match(text: &[char], pat: &[char], escape: char) -> bool {
4502 match pat {
4503 [] => text.is_empty(),
4504 ['%', rest @ ..] => (0..=text.len()).any(|skip| like_match(&text[skip..], rest, escape)),
4505 ['_', rest @ ..] => !text.is_empty() && like_match(&text[1..], rest, escape),
4506 [e, lit, rest @ ..] if *e == escape => {
4507 text.first() == Some(lit) && like_match(&text[1..], rest, escape)
4508 }
4509 [c, rest @ ..] => text.first() == Some(c) && like_match(&text[1..], rest, escape),
4510 }
4511}
4512
4513fn push_path_step(out: &mut String, step_key: Option<&str>, step_idx: Option<usize>) {
4516 if let Some(k) = step_key {
4517 let ident_shaped = !k.is_empty()
4518 && k.chars().all(|c| c.is_alphanumeric() || c == '_')
4519 && !k.chars().next().unwrap().is_numeric();
4520 if ident_shaped {
4521 out.push('.');
4522 out.push_str(k);
4523 } else {
4524 out.push_str(".\"");
4525 for c in k.chars() {
4526 if c == '"' || c == '\\' {
4527 out.push('\\');
4528 }
4529 out.push(c);
4530 }
4531 out.push('"');
4532 }
4533 }
4534 if let Some(i) = step_idx {
4535 out.push('[');
4536 out.push_str(&alloc::format!("{i}"));
4537 out.push(']');
4538 }
4539}
4540
4541pub fn mysql_json_search(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4547 if args.len() < 3 {
4548 return Err(EvalError::TypeMismatch {
4549 detail: alloc::format!(
4550 "json_search() takes doc, one/all, pattern [, escape [, path...]], got {} args",
4551 args.len()
4552 ),
4553 });
4554 }
4555 if args[..3].iter().any(|a| matches!(a, Value::Null)) {
4556 return Ok(Value::Null);
4557 }
4558 let src = match &args[0] {
4559 Value::Json(s) | Value::Text(s) => s.as_ref(),
4560 other => {
4561 return Err(EvalError::TypeMismatch {
4562 detail: alloc::format!(
4563 "json_search() document must be json, got {}",
4564 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4565 ),
4566 });
4567 }
4568 };
4569 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4570 detail: alloc::format!("json_search(): invalid JSON: {e}"),
4571 })?;
4572 let one = match &args[1] {
4573 Value::Text(m) if m.eq_ignore_ascii_case("one") => true,
4574 Value::Text(m) if m.eq_ignore_ascii_case("all") => false,
4575 other => {
4576 return Err(EvalError::TypeMismatch {
4577 detail: alloc::format!(
4578 "json_search() second arg must be 'one' or 'all', got {other:?}"
4579 ),
4580 });
4581 }
4582 };
4583 let Value::Text(pattern) = &args[2] else {
4584 return Err(EvalError::TypeMismatch {
4585 detail: alloc::format!(
4586 "json_search() pattern must be text, got {}",
4587 crate::conversions::pg_type_name_for_error_opt(args[2].data_type())
4588 ),
4589 });
4590 };
4591 let escape = match args.get(3) {
4592 None | Some(Value::Null) => '\\',
4593 Some(Value::Text(e)) if e.chars().count() == 1 => e.chars().next().unwrap(),
4594 Some(other) => {
4595 return Err(EvalError::TypeMismatch {
4596 detail: alloc::format!(
4597 "json_search() escape must be a single character, got {other:?}"
4598 ),
4599 });
4600 }
4601 };
4602 let pat: Vec<char> = pattern.chars().collect();
4603 fn walk(
4604 v: &JsonValue,
4605 path: &str,
4606 pat: &[char],
4607 escape: char,
4608 hits: &mut Vec<String>,
4609 stop_at_one: bool,
4610 ) {
4611 if stop_at_one && !hits.is_empty() {
4612 return;
4613 }
4614 match v {
4615 JsonValue::String(s) => {
4616 let chars: Vec<char> = s.chars().collect();
4617 if like_match(&chars, pat, escape) {
4618 hits.push(path.to_string());
4619 }
4620 }
4621 JsonValue::Object(members) => {
4622 for (k, mv) in members {
4623 let mut p = path.to_string();
4624 push_path_step(&mut p, Some(k), None);
4625 walk(mv, &p, pat, escape, hits, stop_at_one);
4626 }
4627 }
4628 JsonValue::Array(items) => {
4629 for (i, iv) in items.iter().enumerate() {
4630 let mut p = path.to_string();
4631 push_path_step(&mut p, None, Some(i));
4632 walk(iv, &p, pat, escape, hits, stop_at_one);
4633 }
4634 }
4635 _ => {}
4636 }
4637 }
4638 let mut hits: Vec<String> = Vec::new();
4639 let start_paths: Vec<String> = args
4640 .get(4..)
4641 .unwrap_or(&[])
4642 .iter()
4643 .map(|v| match v {
4644 Value::Text(p) => Ok(p.to_string()),
4645 other => Err(EvalError::TypeMismatch {
4646 detail: alloc::format!(
4647 "json_search() paths must be text, got {}",
4648 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4649 ),
4650 }),
4651 })
4652 .collect::<Result<_, _>>()?;
4653 if start_paths.is_empty() {
4654 walk(&doc, "$", &pat, escape, &mut hits, one);
4655 } else {
4656 for p in &start_paths {
4657 let steps = mysql_path_steps(p)?;
4658 if let Some(sub) = mysql_path_get(&doc, &steps) {
4659 walk(sub, p.trim(), &pat, escape, &mut hits, one);
4660 }
4661 }
4662 }
4663 match hits.len() {
4664 0 => Ok(Value::Null),
4665 1 => Ok(Value::Json(alloc::borrow::Cow::Owned(
4666 JsonValue::String(hits.into_iter().next().unwrap()).to_json_text(),
4667 ))),
4668 _ => {
4669 let arr = JsonValue::Array(hits.into_iter().map(JsonValue::String).collect());
4670 Ok(Value::Json(alloc::borrow::Cow::Owned(arr.to_json_text())))
4671 }
4672 }
4673}
4674
4675pub fn mysql_json_value(args: &[Value<'_>]) -> Result<Value<'static>, EvalError> {
4680 if args.len() != 2 {
4681 return Err(EvalError::TypeMismatch {
4682 detail: alloc::format!("json_value() takes 2 args, got {}", args.len()),
4683 });
4684 }
4685 if args.iter().any(|a| matches!(a, Value::Null)) {
4686 return Ok(Value::Null);
4687 }
4688 let src = match &args[0] {
4689 Value::Json(s) | Value::Text(s) => s.as_ref(),
4690 other => {
4691 return Err(EvalError::TypeMismatch {
4692 detail: alloc::format!(
4693 "json_value() document must be json, got {}",
4694 crate::conversions::pg_type_name_for_error_opt(other.data_type())
4695 ),
4696 });
4697 }
4698 };
4699 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
4700 detail: alloc::format!("json_value(): invalid JSON: {e}"),
4701 })?;
4702 let Value::Text(p) = &args[1] else {
4703 return Err(EvalError::TypeMismatch {
4704 detail: alloc::format!(
4705 "json_value() path must be text, got {}",
4706 crate::conversions::pg_type_name_for_error_opt(args[1].data_type())
4707 ),
4708 });
4709 };
4710 let steps = mysql_path_steps(p)?;
4711 match mysql_path_get(&doc, &steps) {
4712 None => Ok(Value::Null),
4713 Some(JsonValue::Null) => Ok(Value::Null),
4714 Some(v) => Ok(Value::text(v.as_text())),
4715 }
4716}
4717
4718fn is_json_scalar(v: &JsonValue) -> bool {
4722 !matches!(v, JsonValue::Object(_) | JsonValue::Array(_))
4723}
4724
4725#[cfg(test)]
4726mod round619_number_fast_path {
4727 use super::*;
4728
4729 #[test]
4733 fn fast_path_agrees_with_the_full_canonicaliser() {
4734 let mut cases: Vec<String> = Vec::new();
4735 for sign in ["", "-"] {
4736 for body in [
4737 "0",
4738 "1",
4739 "7",
4740 "10",
4741 "123",
4742 "0123",
4743 "00",
4744 "000",
4745 "9223372036854775807",
4746 "170141183460469231731687303715884105727",
4747 "1.0",
4748 "1.5",
4749 "0.5",
4750 ".5",
4751 "1.",
4752 "1e3",
4753 "1E3",
4754 "1e-3",
4755 "1.5e2",
4756 "1.50",
4757 "100",
4758 "0.0",
4759 "0.00",
4760 "10.010",
4761 "1e0",
4762 "1e+3",
4763 "0e0",
4764 "12345678901234567890.12345678901234567890",
4765 ] {
4766 cases.push(alloc::format!("{sign}{body}"));
4767 }
4768 }
4769 for c in &cases {
4770 assert_eq!(
4771 canon_json_number(c).as_ref(),
4772 canon_json_number_slow(c).as_str(),
4773 "lexeme {c:?} canonicalises differently through the shortcut"
4774 );
4775 }
4776 }
4777
4778 #[test]
4780 fn one_entry_object_writes_what_the_general_writer_writes() {
4781 for src in [
4782 r#"{"a":1}"#,
4783 r#"{"":1}"#,
4784 r#"{"a":{"b":2}}"#,
4785 r#"{"a":[1,2,3]}"#,
4786 r#"{"a\"b":"c\\d"}"#,
4787 r#"{"日本":"語"}"#,
4788 r#"{"a":null}"#,
4789 r#"{}"#,
4790 ] {
4791 let JsonValue::Object(entries) = parse(src).expect("valid json") else {
4792 panic!("{src} is not an object");
4793 };
4794 let mut fast = String::new();
4795 write_json_canonical(&JsonValue::Object(entries.clone()), &mut fast);
4796 let mut general = String::new();
4797 write_object_general(&entries, &mut general);
4798 assert_eq!(fast, general, "{src}");
4799 }
4800 }
4801}