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