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
54impl JsonValue {
55 fn as_text(&self) -> String {
56 match self {
57 Self::Null => "null".into(),
58 Self::Bool(b) => if *b { "true" } else { "false" }.into(),
59 Self::Number(x) => alloc::format!("{x}"),
60 Self::NumberText(s) | Self::String(s) => s.clone(),
61 Self::Array(_) | Self::Object(_) => self.to_json_text(),
62 }
63 }
64
65 fn to_json_text(&self) -> String {
66 let mut out = String::new();
67 write_json(self, &mut out);
68 out
69 }
70}
71
72fn write_json(v: &JsonValue, out: &mut String) {
73 match v {
74 JsonValue::Null => out.push_str("null"),
75 JsonValue::Bool(true) => out.push_str("true"),
76 JsonValue::Bool(false) => out.push_str("false"),
77 JsonValue::Number(x) => out.push_str(&alloc::format!("{x}")),
78 JsonValue::NumberText(s) => out.push_str(s),
79 JsonValue::String(s) => {
80 out.push('"');
81 for c in s.chars() {
82 match c {
83 '"' => out.push_str("\\\""),
84 '\\' => out.push_str("\\\\"),
85 '\n' => out.push_str("\\n"),
86 '\r' => out.push_str("\\r"),
87 '\t' => out.push_str("\\t"),
88 c if (c as u32) < 0x20 => {
89 out.push_str(&alloc::format!("\\u{:04x}", c as u32));
90 }
91 c => out.push(c),
92 }
93 }
94 out.push('"');
95 }
96 JsonValue::Array(items) => {
97 out.push('[');
98 for (i, it) in items.iter().enumerate() {
99 if i > 0 {
100 out.push(',');
101 }
102 write_json(it, out);
103 }
104 out.push(']');
105 }
106 JsonValue::Object(entries) => {
107 out.push('{');
108 for (i, (k, val)) in entries.iter().enumerate() {
109 if i > 0 {
110 out.push(',');
111 }
112 write_json(&JsonValue::String(k.clone()), out);
113 out.push(':');
114 write_json(val, out);
115 }
116 out.push('}');
117 }
118 }
119}
120
121pub fn jsonb_each_text_rows(arg: &Value) -> Result<Vec<(String, Option<String>)>, EvalError> {
135 let src = match arg {
136 Value::Null => return Ok(Vec::new()),
137 Value::Json(s) | Value::Text(s) => s.as_ref(),
138 other => {
139 return Err(EvalError::TypeMismatch {
140 detail: alloc::format!(
141 "jsonb_each_text: argument must be JSON / JSONB, got {:?}",
142 other.data_type()
143 ),
144 });
145 }
146 };
147 let parsed = parse(src).map_err(|e| EvalError::TypeMismatch {
148 detail: alloc::format!("jsonb_each_text: invalid JSON: {e}"),
149 })?;
150 match parsed {
151 JsonValue::Object(entries) => {
152 let mut out: Vec<(String, Option<String>)> = Vec::with_capacity(entries.len());
153 for (k, v) in entries {
154 let text = match &v {
155 JsonValue::Null => None,
156 JsonValue::Bool(b) => Some(if *b {
157 "true".to_string()
158 } else {
159 "false".to_string()
160 }),
161 JsonValue::Number(_) | JsonValue::NumberText(_) | JsonValue::String(_) => {
162 Some(v.as_text())
163 }
164 JsonValue::Array(_) | JsonValue::Object(_) => Some(v.to_json_text()),
165 };
166 out.push((k, text));
167 }
168 Ok(out)
169 }
170 other => Err(EvalError::TypeMismatch {
171 detail: alloc::format!(
172 "jsonb_each_text: argument must be a JSON object, got {other:?}"
173 ),
174 }),
175 }
176}
177
178pub fn path_walk(lhs: &Value, rhs: &Value, as_text: bool) -> Result<Value<'static>, EvalError> {
180 let src = match lhs {
181 Value::Json(s) | Value::Text(s) => s.as_ref(),
182 Value::Null => return Ok(Value::Null),
183 other => {
184 return Err(EvalError::TypeMismatch {
185 detail: alloc::format!(
186 "JSON path walk: left side must be JSON or TEXT, got {:?}",
187 other.data_type()
188 ),
189 });
190 }
191 };
192 let path_text = match rhs {
193 Value::Text(s) | Value::Json(s) => s.as_ref(),
194 Value::Null => return Ok(Value::Null),
195 other => {
196 return Err(EvalError::TypeMismatch {
197 detail: alloc::format!(
198 "JSON path walk: right side must be TEXT, got {:?}",
199 other.data_type()
200 ),
201 });
202 }
203 };
204 let path = parse_text_array(path_text)?;
205 let mut cur = parse(src).map_err(|e| EvalError::TypeMismatch {
206 detail: alloc::format!("invalid JSON for path walk: {e}"),
207 })?;
208 for step in &path {
209 let next = match (&cur, step.as_str()) {
210 (JsonValue::Object(entries), key) => entries
211 .iter()
212 .find(|(k, _)| k == key)
213 .map(|(_, v)| v.clone()),
214 (JsonValue::Array(items), key) => {
215 let Ok(idx) = key.parse::<i64>() else {
216 return Ok(Value::Null);
217 };
218 if idx >= 0 {
219 items.get(idx as usize).cloned()
220 } else {
221 let from_end = items.len() as i64 + idx;
222 if from_end >= 0 {
223 items.get(from_end as usize).cloned()
224 } else {
225 None
226 }
227 }
228 }
229 _ => return Ok(Value::Null),
230 };
231 cur = match next {
232 None => return Ok(Value::Null),
233 Some(v) => v,
234 };
235 }
236 if matches!(cur, JsonValue::Null) {
237 return Ok(Value::Null);
238 }
239 if as_text {
240 Ok(Value::text(cur.as_text()))
241 } else {
242 Ok(Value::json(cur.to_json_text()))
243 }
244}
245
246pub fn key_exists(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
261 let lhs_text = match lhs {
262 Value::Json(s) | Value::Text(s) => s.as_ref(),
263 Value::Null => return Ok(Value::Null),
264 other => {
265 return Err(EvalError::TypeMismatch {
266 detail: alloc::format!(
267 "JSON ?: left side must be JSON or TEXT, got {:?}",
268 other.data_type()
269 ),
270 });
271 }
272 };
273 let key = match rhs {
274 Value::Text(s) => s.as_ref(),
275 Value::Null => return Ok(Value::Null),
276 other => {
277 return Err(EvalError::TypeMismatch {
278 detail: alloc::format!(
279 "JSON ?: right side must be TEXT, got {:?}",
280 other.data_type()
281 ),
282 });
283 }
284 };
285 let doc = parse(lhs_text).map_err(|e| EvalError::TypeMismatch {
286 detail: alloc::format!("invalid JSON on left of ?: {e}"),
287 })?;
288 Ok(Value::Bool(node_has_key(&doc, key)))
289}
290
291fn node_has_key(v: &JsonValue, key: &str) -> bool {
292 match v {
293 JsonValue::Object(members) => members.iter().any(|(k, _)| k == key),
294 JsonValue::Array(items) => items
295 .iter()
296 .any(|item| matches!(item, JsonValue::String(s) if s == key)),
297 JsonValue::String(s) => s == key,
298 _ => false,
299 }
300}
301
302fn collect_keys(v: &Value) -> Result<Option<Vec<String>>, EvalError> {
305 match v {
306 Value::Null => Ok(None),
307 Value::TextArray(items) => Ok(Some(items.iter().filter_map(|x| x.clone()).collect())),
308 Value::Text(s) => Ok(Some(alloc::vec![s.to_string()])),
309 other => Err(EvalError::TypeMismatch {
310 detail: alloc::format!(
311 "JSON ?|/?&: right side must be TEXT[] or TEXT, got {:?}",
312 other.data_type()
313 ),
314 }),
315 }
316}
317
318pub fn keys_any(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
321 let lhs_text = match lhs {
322 Value::Json(s) | Value::Text(s) => s.as_ref(),
323 Value::Null => return Ok(Value::Null),
324 other => {
325 return Err(EvalError::TypeMismatch {
326 detail: alloc::format!(
327 "JSON ?|: left side must be JSON or TEXT, got {:?}",
328 other.data_type()
329 ),
330 });
331 }
332 };
333 let Some(keys) = collect_keys(rhs)? else {
334 return Ok(Value::Null);
335 };
336 let doc = parse(lhs_text).map_err(|e| EvalError::TypeMismatch {
337 detail: alloc::format!("invalid JSON on left of ?|: {e}"),
338 })?;
339 Ok(Value::Bool(keys.iter().any(|k| node_has_key(&doc, k))))
340}
341
342pub fn keys_all(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
345 let lhs_text = match lhs {
346 Value::Json(s) | Value::Text(s) => s.as_ref(),
347 Value::Null => return Ok(Value::Null),
348 other => {
349 return Err(EvalError::TypeMismatch {
350 detail: alloc::format!(
351 "JSON ?&: left side must be JSON or TEXT, got {:?}",
352 other.data_type()
353 ),
354 });
355 }
356 };
357 let Some(keys) = collect_keys(rhs)? else {
358 return Ok(Value::Null);
359 };
360 let doc = parse(lhs_text).map_err(|e| EvalError::TypeMismatch {
361 detail: alloc::format!("invalid JSON on left of ?&: {e}"),
362 })?;
363 Ok(Value::Bool(keys.iter().all(|k| node_has_key(&doc, k))))
364}
365
366pub fn contains(lhs: &Value, rhs: &Value) -> Result<Value<'static>, EvalError> {
367 let lhs_text = match lhs {
368 Value::Json(s) | Value::Text(s) => s.as_ref(),
369 Value::Null => return Ok(Value::Null),
370 other => {
371 return Err(EvalError::TypeMismatch {
372 detail: alloc::format!(
373 "JSON @>: left side must be JSON or TEXT, got {:?}",
374 other.data_type()
375 ),
376 });
377 }
378 };
379 let rhs_text = match rhs {
380 Value::Json(s) | Value::Text(s) => s.as_ref(),
381 Value::Null => return Ok(Value::Null),
382 other => {
383 return Err(EvalError::TypeMismatch {
384 detail: alloc::format!(
385 "JSON @>: right side must be JSON or TEXT, got {:?}",
386 other.data_type()
387 ),
388 });
389 }
390 };
391 let lhs_doc = parse(lhs_text).map_err(|e| EvalError::TypeMismatch {
392 detail: alloc::format!("invalid JSON on left of @>: {e}"),
393 })?;
394 let rhs_doc = parse(rhs_text).map_err(|e| EvalError::TypeMismatch {
395 detail: alloc::format!("invalid JSON on right of @>: {e}"),
396 })?;
397 Ok(Value::Bool(json_contains(&lhs_doc, &rhs_doc)))
398}
399
400fn json_contains(lhs: &JsonValue, rhs: &JsonValue) -> bool {
401 match (lhs, rhs) {
402 (JsonValue::Object(l), JsonValue::Object(r)) => r
403 .iter()
404 .all(|(rk, rv)| l.iter().any(|(lk, lv)| lk == rk && json_contains(lv, rv))),
405 (JsonValue::Array(l), JsonValue::Array(r)) => {
406 r.iter().all(|rv| l.iter().any(|lv| json_contains(lv, rv)))
407 }
408 _ => json_eq(lhs, rhs),
409 }
410}
411
412fn json_eq(a: &JsonValue, b: &JsonValue) -> bool {
413 match (a, b) {
414 (JsonValue::Null, JsonValue::Null) => true,
415 (JsonValue::Bool(x), JsonValue::Bool(y)) => x == y,
416 (JsonValue::String(x), JsonValue::String(y)) => x == y,
417 (JsonValue::Number(x), JsonValue::Number(y)) => (x - y).abs() < 1e-12,
418 (JsonValue::NumberText(x), JsonValue::NumberText(y)) => x == y,
419 (JsonValue::NumberText(x), JsonValue::Number(y))
420 | (JsonValue::Number(y), JsonValue::NumberText(x)) => {
421 x.parse::<f64>().is_ok_and(|xn| (xn - y).abs() < 1e-12)
422 }
423 (JsonValue::Array(x), JsonValue::Array(y)) => {
424 x.len() == y.len() && x.iter().zip(y).all(|(a, b)| json_eq(a, b))
425 }
426 (JsonValue::Object(x), JsonValue::Object(y)) => {
427 x.len() == y.len()
428 && x.iter()
429 .all(|(k, v)| y.iter().any(|(k2, v2)| k == k2 && json_eq(v, v2)))
430 }
431 _ => false,
432 }
433}
434
435fn parse_text_array(s: &str) -> Result<Vec<String>, EvalError> {
440 let trimmed = s.trim();
441 let inner = if let Some(stripped) = trimmed.strip_prefix('{').and_then(|s| s.strip_suffix('}'))
442 {
443 stripped
444 } else {
445 return Err(EvalError::TypeMismatch {
446 detail: alloc::format!("path walk: expected PG array literal `{{…}}`, got {s:?}"),
447 });
448 };
449 if inner.trim().is_empty() {
450 return Ok(Vec::new());
451 }
452 let mut out = Vec::new();
453 let mut cur = String::new();
454 let mut in_quotes = false;
455 let mut chars = inner.chars().peekable();
456 while let Some(c) = chars.next() {
457 match c {
458 '"' => in_quotes = !in_quotes,
459 ',' if !in_quotes => {
460 out.push(cur.trim().to_string());
461 cur = String::new();
462 }
463 '\\' => {
464 if let Some(&next) = chars.peek() {
465 cur.push(next);
466 chars.next();
467 }
468 }
469 _ => cur.push(c),
470 }
471 }
472 out.push(cur.trim().to_string());
473 Ok(out)
474}
475
476pub fn path_get(lhs: &Value, rhs: &Value, as_text: bool) -> Result<Value<'static>, EvalError> {
481 let src = match lhs {
482 Value::Json(s) | Value::Text(s) => s.as_ref(),
483 Value::Null => return Ok(Value::Null),
484 other => {
485 return Err(EvalError::TypeMismatch {
486 detail: alloc::format!(
487 "JSON path operator: left side must be JSON or TEXT, got {:?}",
488 other.data_type()
489 ),
490 });
491 }
492 };
493 let doc = parse(src).map_err(|e| EvalError::TypeMismatch {
494 detail: alloc::format!("invalid JSON for path access: {e}"),
495 })?;
496 let inner = match (&doc, rhs) {
497 (JsonValue::Object(entries), Value::Text(k)) => entries
498 .iter()
499 .find(|(name, _)| name == k)
500 .map(|(_, v)| v.clone()),
501 (JsonValue::Array(items), Value::Int(idx)) => {
502 let n = *idx;
503 if n >= 0 {
504 items.get(n as usize).cloned()
505 } else {
506 let from_end = items.len() as i64 + i64::from(n);
507 if from_end >= 0 {
508 items.get(from_end as usize).cloned()
509 } else {
510 None
511 }
512 }
513 }
514 (JsonValue::Array(items), Value::BigInt(idx)) => {
515 let n = *idx;
516 if n >= 0 {
517 items.get(n as usize).cloned()
518 } else {
519 let from_end = items.len() as i64 + n;
520 if from_end >= 0 {
521 items.get(from_end as usize).cloned()
522 } else {
523 None
524 }
525 }
526 }
527 (_, Value::Null) => return Ok(Value::Null),
528 _ => None,
529 };
530 match inner {
531 None | Some(JsonValue::Null) => Ok(Value::Null),
532 Some(v) => {
533 if as_text {
534 Ok(Value::text(v.as_text()))
535 } else {
536 Ok(Value::json(v.to_json_text()))
537 }
538 }
539 }
540}
541
542#[derive(Debug)]
545pub enum ParseError {
546 Unexpected(char, usize),
547 Truncated,
548 InvalidEscape(usize),
549 InvalidNumber(usize),
550}
551
552impl core::fmt::Display for ParseError {
553 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
554 match self {
555 Self::Unexpected(c, p) => write!(f, "unexpected {c:?} at offset {p}"),
556 Self::Truncated => f.write_str("unexpected end of JSON input"),
557 Self::InvalidEscape(p) => write!(f, "invalid string escape at offset {p}"),
558 Self::InvalidNumber(p) => write!(f, "invalid number at offset {p}"),
559 }
560 }
561}
562
563pub fn parse(src: &str) -> Result<JsonValue, ParseError> {
564 let bytes = src.as_bytes();
565 let mut p = 0;
566 skip_ws(bytes, &mut p);
567 let value = parse_value(bytes, &mut p)?;
568 skip_ws(bytes, &mut p);
569 if p != bytes.len() {
570 return Err(ParseError::Unexpected(bytes[p] as char, p));
571 }
572 Ok(value)
573}
574
575fn skip_ws(bytes: &[u8], p: &mut usize) {
576 while *p < bytes.len() && matches!(bytes[*p], b' ' | b'\t' | b'\n' | b'\r') {
577 *p += 1;
578 }
579}
580
581fn parse_value(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
582 skip_ws(bytes, p);
583 if *p >= bytes.len() {
584 return Err(ParseError::Truncated);
585 }
586 match bytes[*p] {
587 b'{' => parse_object(bytes, p),
588 b'[' => parse_array(bytes, p),
589 b'"' => parse_string(bytes, p).map(JsonValue::String),
590 b't' | b'f' => parse_bool(bytes, p),
591 b'n' => parse_null(bytes, p),
592 b'-' | b'0'..=b'9' => parse_number(bytes, p),
593 c => Err(ParseError::Unexpected(c as char, *p)),
594 }
595}
596
597fn parse_object(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
598 debug_assert_eq!(bytes[*p], b'{');
599 *p += 1;
600 let mut entries = Vec::new();
601 skip_ws(bytes, p);
602 if *p < bytes.len() && bytes[*p] == b'}' {
603 *p += 1;
604 return Ok(JsonValue::Object(entries));
605 }
606 loop {
607 skip_ws(bytes, p);
608 if *p >= bytes.len() || bytes[*p] != b'"' {
609 return Err(ParseError::Unexpected(
610 bytes.get(*p).copied().unwrap_or(0) as char,
611 *p,
612 ));
613 }
614 let key = parse_string(bytes, p)?;
615 skip_ws(bytes, p);
616 if *p >= bytes.len() || bytes[*p] != b':' {
617 return Err(ParseError::Unexpected(
618 bytes.get(*p).copied().unwrap_or(0) as char,
619 *p,
620 ));
621 }
622 *p += 1;
623 let value = parse_value(bytes, p)?;
624 entries.push((key, value));
625 skip_ws(bytes, p);
626 if *p >= bytes.len() {
627 return Err(ParseError::Truncated);
628 }
629 match bytes[*p] {
630 b',' => {
631 *p += 1;
632 continue;
633 }
634 b'}' => {
635 *p += 1;
636 return Ok(JsonValue::Object(entries));
637 }
638 c => return Err(ParseError::Unexpected(c as char, *p)),
639 }
640 }
641}
642
643fn parse_array(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
644 debug_assert_eq!(bytes[*p], b'[');
645 *p += 1;
646 let mut items = Vec::new();
647 skip_ws(bytes, p);
648 if *p < bytes.len() && bytes[*p] == b']' {
649 *p += 1;
650 return Ok(JsonValue::Array(items));
651 }
652 loop {
653 items.push(parse_value(bytes, p)?);
654 skip_ws(bytes, p);
655 if *p >= bytes.len() {
656 return Err(ParseError::Truncated);
657 }
658 match bytes[*p] {
659 b',' => {
660 *p += 1;
661 continue;
662 }
663 b']' => {
664 *p += 1;
665 return Ok(JsonValue::Array(items));
666 }
667 c => return Err(ParseError::Unexpected(c as char, *p)),
668 }
669 }
670}
671
672fn parse_string(bytes: &[u8], p: &mut usize) -> Result<String, ParseError> {
673 debug_assert_eq!(bytes[*p], b'"');
674 *p += 1;
675 let mut out = String::new();
676 while *p < bytes.len() {
677 match bytes[*p] {
678 b'"' => {
679 *p += 1;
680 return Ok(out);
681 }
682 b'\\' => {
683 let start = *p;
684 *p += 1;
685 if *p >= bytes.len() {
686 return Err(ParseError::Truncated);
687 }
688 match bytes[*p] {
689 b'"' => {
690 out.push('"');
691 *p += 1;
692 }
693 b'\\' => {
694 out.push('\\');
695 *p += 1;
696 }
697 b'/' => {
698 out.push('/');
699 *p += 1;
700 }
701 b'b' => {
702 out.push('\u{08}');
703 *p += 1;
704 }
705 b'f' => {
706 out.push('\u{0c}');
707 *p += 1;
708 }
709 b'n' => {
710 out.push('\n');
711 *p += 1;
712 }
713 b'r' => {
714 out.push('\r');
715 *p += 1;
716 }
717 b't' => {
718 out.push('\t');
719 *p += 1;
720 }
721 b'u' => {
722 if *p + 5 > bytes.len() {
723 return Err(ParseError::Truncated);
724 }
725 let hex = &bytes[*p + 1..*p + 5];
726 let n = u32::from_str_radix(
727 core::str::from_utf8(hex)
728 .map_err(|_| ParseError::InvalidEscape(start))?,
729 16,
730 )
731 .map_err(|_| ParseError::InvalidEscape(start))?;
732 out.push(char::from_u32(n).ok_or(ParseError::InvalidEscape(start))?);
733 *p += 5;
734 }
735 _ => return Err(ParseError::InvalidEscape(start)),
736 }
737 }
738 c if c < 0x20 => return Err(ParseError::Unexpected(c as char, *p)),
739 _ => {
740 let s = core::str::from_utf8(&bytes[*p..])
742 .map_err(|_| ParseError::Unexpected(bytes[*p] as char, *p))?;
743 let c = s.chars().next().unwrap();
744 out.push(c);
745 *p += c.len_utf8();
746 }
747 }
748 }
749 Err(ParseError::Truncated)
750}
751
752fn parse_bool(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
753 if bytes[*p..].starts_with(b"true") {
754 *p += 4;
755 Ok(JsonValue::Bool(true))
756 } else if bytes[*p..].starts_with(b"false") {
757 *p += 5;
758 Ok(JsonValue::Bool(false))
759 } else {
760 Err(ParseError::Unexpected(bytes[*p] as char, *p))
761 }
762}
763
764fn parse_null(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
765 if bytes[*p..].starts_with(b"null") {
766 *p += 4;
767 Ok(JsonValue::Null)
768 } else {
769 Err(ParseError::Unexpected(bytes[*p] as char, *p))
770 }
771}
772
773fn parse_number(bytes: &[u8], p: &mut usize) -> Result<JsonValue, ParseError> {
774 let start = *p;
775 if bytes[*p] == b'-' {
776 *p += 1;
777 }
778 while *p < bytes.len() && bytes[*p].is_ascii_digit() {
779 *p += 1;
780 }
781 if *p < bytes.len() && bytes[*p] == b'.' {
782 *p += 1;
783 while *p < bytes.len() && bytes[*p].is_ascii_digit() {
784 *p += 1;
785 }
786 }
787 if *p < bytes.len() && matches!(bytes[*p], b'e' | b'E') {
788 *p += 1;
789 if *p < bytes.len() && matches!(bytes[*p], b'+' | b'-') {
790 *p += 1;
791 }
792 while *p < bytes.len() && bytes[*p].is_ascii_digit() {
793 *p += 1;
794 }
795 }
796 let text = core::str::from_utf8(&bytes[start..*p])
797 .map_err(|_| ParseError::InvalidNumber(start))?
798 .to_string();
799 if text.parse::<f64>().is_err() {
801 return Err(ParseError::InvalidNumber(start));
802 }
803 Ok(JsonValue::NumberText(text))
804}
805
806#[derive(Debug, Clone)]
825enum PathStep {
826 Field(String),
827 Index(usize),
828 Wildcard,
829}
830
831fn parse_jsonpath(p: &str) -> Result<Vec<PathStep>, EvalError> {
832 let chars: Vec<char> = p.chars().collect();
833 let mut i = 0;
834 if i >= chars.len() || chars[i] != '$' {
835 return Err(EvalError::TypeMismatch {
836 detail: alloc::format!("jsonpath must start with '$', got {p:?}"),
837 });
838 }
839 i += 1;
840 let mut steps: Vec<PathStep> = Vec::new();
841 while i < chars.len() {
842 match chars[i] {
843 '.' => {
844 i += 1;
845 if i < chars.len() && chars[i] == '"' {
846 i += 1;
847 let start = i;
848 while i < chars.len() && chars[i] != '"' {
849 i += 1;
850 }
851 if i >= chars.len() {
852 return Err(EvalError::TypeMismatch {
853 detail: "jsonpath: unterminated quoted field".into(),
854 });
855 }
856 steps.push(PathStep::Field(chars[start..i].iter().collect()));
857 i += 1;
858 } else {
859 let start = i;
860 while i < chars.len()
861 && chars[i] != '.'
862 && chars[i] != '['
863 && !chars[i].is_whitespace()
864 {
865 i += 1;
866 }
867 if start == i {
868 return Err(EvalError::TypeMismatch {
869 detail: "jsonpath: missing field name after '.'".into(),
870 });
871 }
872 steps.push(PathStep::Field(chars[start..i].iter().collect()));
873 }
874 }
875 '[' => {
876 i += 1;
877 if i < chars.len() && chars[i] == '*' {
878 i += 1;
879 if i >= chars.len() || chars[i] != ']' {
880 return Err(EvalError::TypeMismatch {
881 detail: "jsonpath: expected ']' after '[*'".into(),
882 });
883 }
884 i += 1;
885 steps.push(PathStep::Wildcard);
886 } else {
887 let start = i;
888 while i < chars.len() && chars[i].is_ascii_digit() {
889 i += 1;
890 }
891 if start == i {
892 return Err(EvalError::TypeMismatch {
893 detail:
894 "jsonpath: only `[N]` (non-negative) or `[*]` supported in v7.17"
895 .into(),
896 });
897 }
898 let idx: usize =
899 chars[start..i]
900 .iter()
901 .collect::<String>()
902 .parse()
903 .map_err(|_| EvalError::TypeMismatch {
904 detail: "jsonpath: invalid array index".into(),
905 })?;
906 if i >= chars.len() || chars[i] != ']' {
907 return Err(EvalError::TypeMismatch {
908 detail: "jsonpath: expected ']' after array index".into(),
909 });
910 }
911 i += 1;
912 steps.push(PathStep::Index(idx));
913 }
914 }
915 c if c.is_whitespace() => {
916 i += 1;
917 }
918 c => {
919 return Err(EvalError::TypeMismatch {
920 detail: alloc::format!(
921 "jsonpath: unexpected char '{c}' (v7.17 supports `$.field`, `[N]`, `[*]` only)"
922 ),
923 });
924 }
925 }
926 }
927 Ok(steps)
928}
929
930fn apply_jsonpath(root: &JsonValue, steps: &[PathStep]) -> Vec<JsonValue> {
931 let mut cur: Vec<JsonValue> = alloc::vec![root.clone()];
932 for step in steps {
933 let mut next: Vec<JsonValue> = Vec::new();
934 for node in &cur {
935 match (step, node) {
936 (PathStep::Field(k), JsonValue::Object(entries)) => {
937 if let Some((_, v)) = entries.iter().find(|(name, _)| name == k) {
938 next.push(v.clone());
939 }
940 }
941 (PathStep::Index(idx), JsonValue::Array(items)) => {
942 if let Some(v) = items.get(*idx) {
943 next.push(v.clone());
944 }
945 }
946 (PathStep::Wildcard, JsonValue::Array(items)) => {
947 next.extend(items.iter().cloned());
948 }
949 _ => {} }
951 }
952 cur = next;
953 if cur.is_empty() {
954 return Vec::new();
955 }
956 }
957 cur
958}
959
960pub fn path_query(doc: &Value, path: &Value) -> Result<Value<'static>, EvalError> {
964 let (src, path_text) = match (doc, path) {
965 (Value::Null, _) | (_, Value::Null) => return Ok(Value::Null),
966 (Value::Json(s) | Value::Text(s), Value::Text(p) | Value::Json(p)) => (s, p),
967 _ => {
968 return Err(EvalError::TypeMismatch {
969 detail: "jsonb_path_query() expects (JSON, TEXT)".into(),
970 });
971 }
972 };
973 let root = parse(src).map_err(|e| EvalError::TypeMismatch {
974 detail: alloc::format!("invalid JSON for jsonb_path_query: {e}"),
975 })?;
976 let steps = parse_jsonpath(path_text)?;
977 let matches = apply_jsonpath(&root, &steps);
978 let arr: Vec<Option<String>> = matches
979 .into_iter()
980 .map(|v| Some(v.to_json_text()))
981 .collect();
982 Ok(Value::TextArray(arr))
983}
984
985pub fn path_query_first(doc: &Value, path: &Value) -> Result<Value<'static>, EvalError> {
988 let q = path_query(doc, path)?;
989 match q {
990 Value::TextArray(items) => {
991 if let Some(Some(first)) = items.into_iter().next() {
992 Ok(Value::json(first))
993 } else {
994 Ok(Value::Null)
995 }
996 }
997 other => Ok(other),
998 }
999}
1000
1001pub fn path_query_array(doc: &Value, path: &Value) -> Result<Value<'static>, EvalError> {
1004 let q = path_query(doc, path)?;
1005 match q {
1006 Value::TextArray(items) => {
1007 let mut buf = String::from("[");
1008 let mut first = true;
1009 for s in items.into_iter().flatten() {
1010 if !first {
1011 buf.push(',');
1012 }
1013 buf.push_str(&s);
1014 first = false;
1015 }
1016 buf.push(']');
1017 Ok(Value::json(buf))
1018 }
1019 other => Ok(other),
1020 }
1021}
1022
1023pub fn value_to_json_text(v: &Value) -> String {
1044 let mut out = String::new();
1045 encode_value_into(v, &mut out);
1046 out
1047}
1048
1049fn encode_value_into(v: &Value, out: &mut String) {
1050 match v {
1051 Value::Null => out.push_str("null"),
1052 Value::Bool(true) => out.push_str("true"),
1053 Value::Bool(false) => out.push_str("false"),
1054 Value::SmallInt(n) => out.push_str(&alloc::format!("{n}")),
1055 Value::Int(n) => out.push_str(&alloc::format!("{n}")),
1056 Value::BigInt(n) => out.push_str(&alloc::format!("{n}")),
1057 Value::Float(x) => out.push_str(&alloc::format!("{x}")),
1058 Value::Numeric { scaled, scale } => {
1059 out.push_str(&render_numeric(*scaled, *scale));
1061 }
1062 Value::Text(s) => write_json(&JsonValue::String(s.to_string()), out),
1063 Value::Json(s) => {
1064 out.push_str(s);
1068 }
1069 Value::TextArray(items) => {
1070 out.push('[');
1071 for (i, it) in items.iter().enumerate() {
1072 if i > 0 {
1073 out.push(',');
1074 }
1075 match it {
1076 Some(s) => write_json(&JsonValue::String(s.clone()), out),
1077 None => out.push_str("null"),
1078 }
1079 }
1080 out.push(']');
1081 }
1082 Value::IntArray(items) => {
1083 out.push('[');
1084 for (i, it) in items.iter().enumerate() {
1085 if i > 0 {
1086 out.push(',');
1087 }
1088 match it {
1089 Some(n) => out.push_str(&alloc::format!("{n}")),
1090 None => out.push_str("null"),
1091 }
1092 }
1093 out.push(']');
1094 }
1095 Value::BigIntArray(items) => {
1096 out.push('[');
1097 for (i, it) in items.iter().enumerate() {
1098 if i > 0 {
1099 out.push(',');
1100 }
1101 match it {
1102 Some(n) => out.push_str(&alloc::format!("{n}")),
1103 None => out.push_str("null"),
1104 }
1105 }
1106 out.push(']');
1107 }
1108 other => {
1112 let txt = alloc::format!("{other:?}");
1113 write_json(&JsonValue::String(txt), out);
1114 }
1115 }
1116}
1117
1118fn render_numeric(scaled: i128, scale: u8) -> String {
1119 let neg = scaled < 0;
1120 let mag_str = alloc::format!("{}", scaled.unsigned_abs());
1121 let s = scale as usize;
1122 let body = if s == 0 {
1123 mag_str
1124 } else if mag_str.len() > s {
1125 let p = mag_str.len() - s;
1126 alloc::format!("{}.{}", &mag_str[..p], &mag_str[p..])
1127 } else {
1128 let pad = s - mag_str.len();
1129 alloc::format!("0.{}{}", "0".repeat(pad), mag_str)
1130 };
1131 if neg { alloc::format!("-{body}") } else { body }
1132}
1133
1134pub fn build_object(args: &[Value<'static>]) -> Result<Value<'static>, EvalError> {
1138 if !args.len().is_multiple_of(2) {
1139 return Err(EvalError::TypeMismatch {
1140 detail: alloc::format!(
1141 "json_build_object() needs an even number of args, got {}",
1142 args.len()
1143 ),
1144 });
1145 }
1146 let mut out = String::from("{");
1147 let mut first = true;
1148 for pair in args.chunks_exact(2) {
1149 if !first {
1150 out.push(',');
1151 }
1152 first = false;
1153 let key = match &pair[0] {
1154 Value::Null => {
1155 return Err(EvalError::TypeMismatch {
1156 detail: "json_build_object() key cannot be NULL".into(),
1157 });
1158 }
1159 Value::Text(s) | Value::Json(s) => s.to_string(),
1160 other => format_value_as_text(other),
1161 };
1162 write_json(&JsonValue::String(key), &mut out);
1163 out.push(':');
1164 encode_value_into(&pair[1], &mut out);
1165 }
1166 out.push('}');
1167 Ok(Value::json(out))
1168}
1169
1170pub fn build_array(args: &[Value<'static>]) -> Result<Value<'static>, EvalError> {
1173 let mut out = String::from("[");
1174 for (i, v) in args.iter().enumerate() {
1175 if i > 0 {
1176 out.push(',');
1177 }
1178 encode_value_into(v, &mut out);
1179 }
1180 out.push(']');
1181 Ok(Value::json(out))
1182}
1183
1184fn format_value_as_text(v: &Value) -> String {
1185 match v {
1186 Value::SmallInt(n) => alloc::format!("{n}"),
1187 Value::Int(n) => alloc::format!("{n}"),
1188 Value::BigInt(n) => alloc::format!("{n}"),
1189 Value::Float(x) => alloc::format!("{x}"),
1190 Value::Bool(b) => alloc::format!("{b}"),
1191 other => alloc::format!("{other:?}"),
1192 }
1193}
1194
1195pub fn set(args: &[Value<'static>]) -> Result<Value<'static>, EvalError> {
1204 if !(3..=4).contains(&args.len()) {
1205 return Err(EvalError::TypeMismatch {
1206 detail: alloc::format!("jsonb_set() takes 3 or 4 args, got {}", args.len()),
1207 });
1208 }
1209 if args.iter().take(3).any(|v| matches!(v, Value::Null)) {
1210 return Ok(Value::Null);
1211 }
1212 let create_missing = match args.get(3) {
1213 None | Some(Value::Null) => true,
1214 Some(Value::Bool(b)) => *b,
1215 Some(other) => {
1216 return Err(EvalError::TypeMismatch {
1217 detail: alloc::format!(
1218 "jsonb_set() create_missing must be BOOL, got {:?}",
1219 other.data_type()
1220 ),
1221 });
1222 }
1223 };
1224 let doc_text = json_text_arg(&args[0], "jsonb_set", "target")?;
1225 let path = path_text_arg(&args[1], "jsonb_set")?;
1226 let new_text = json_text_arg(&args[2], "jsonb_set", "new_value")?;
1227 let mut root = parse(doc_text).map_err(|e| EvalError::TypeMismatch {
1228 detail: alloc::format!("jsonb_set(): invalid JSON target — {e}"),
1229 })?;
1230 let new_val = parse(new_text).map_err(|e| EvalError::TypeMismatch {
1231 detail: alloc::format!("jsonb_set(): invalid JSON new_value — {e}"),
1232 })?;
1233 set_at_path(&mut root, &path, new_val, create_missing);
1234 Ok(Value::json(root.to_json_text()))
1235}
1236
1237fn set_at_path(node: &mut JsonValue, path: &[String], new_val: JsonValue, create_missing: bool) {
1238 if path.is_empty() {
1239 *node = new_val;
1240 return;
1241 }
1242 let step = &path[0];
1243 let rest = &path[1..];
1244 match node {
1245 JsonValue::Object(entries) => {
1246 if let Some(pos) = entries.iter().position(|(k, _)| k == step) {
1247 if rest.is_empty() {
1248 entries[pos].1 = new_val;
1249 } else {
1250 set_at_path(&mut entries[pos].1, rest, new_val, create_missing);
1251 }
1252 } else if create_missing && rest.is_empty() {
1253 entries.push((step.clone(), new_val));
1254 }
1255 }
1258 JsonValue::Array(items) => {
1259 let Some(idx) = resolve_array_index(step, items.len()) else {
1260 if create_missing && rest.is_empty() {
1261 if let Ok(n) = step.parse::<i64>() {
1263 if n < 0 {
1264 items.insert(0, new_val);
1265 } else {
1266 items.push(new_val);
1267 }
1268 }
1269 }
1270 return;
1271 };
1272 if rest.is_empty() {
1273 items[idx] = new_val;
1274 } else {
1275 set_at_path(&mut items[idx], rest, new_val, create_missing);
1276 }
1277 }
1278 _ => {
1279 }
1281 }
1282}
1283
1284fn resolve_array_index(step: &str, len: usize) -> Option<usize> {
1285 let n = step.parse::<i64>().ok()?;
1286 if n >= 0 {
1287 let i = n as usize;
1288 if i < len { Some(i) } else { None }
1289 } else {
1290 let from_end = len as i64 + n;
1291 if from_end >= 0 {
1292 Some(from_end as usize)
1293 } else {
1294 None
1295 }
1296 }
1297}
1298
1299pub fn insert(args: &[Value<'static>]) -> Result<Value<'static>, EvalError> {
1307 if !(3..=4).contains(&args.len()) {
1308 return Err(EvalError::TypeMismatch {
1309 detail: alloc::format!("jsonb_insert() takes 3 or 4 args, got {}", args.len()),
1310 });
1311 }
1312 if args.iter().take(3).any(|v| matches!(v, Value::Null)) {
1313 return Ok(Value::Null);
1314 }
1315 let insert_after = match args.get(3) {
1316 None | Some(Value::Null) => false,
1317 Some(Value::Bool(b)) => *b,
1318 Some(other) => {
1319 return Err(EvalError::TypeMismatch {
1320 detail: alloc::format!(
1321 "jsonb_insert() insert_after must be BOOL, got {:?}",
1322 other.data_type()
1323 ),
1324 });
1325 }
1326 };
1327 let doc_text = json_text_arg(&args[0], "jsonb_insert", "target")?;
1328 let path = path_text_arg(&args[1], "jsonb_insert")?;
1329 let new_text = json_text_arg(&args[2], "jsonb_insert", "new_value")?;
1330 if path.is_empty() {
1331 return Err(EvalError::TypeMismatch {
1332 detail: "jsonb_insert(): path cannot be empty".into(),
1333 });
1334 }
1335 let mut root = parse(doc_text).map_err(|e| EvalError::TypeMismatch {
1336 detail: alloc::format!("jsonb_insert(): invalid JSON target — {e}"),
1337 })?;
1338 let new_val = parse(new_text).map_err(|e| EvalError::TypeMismatch {
1339 detail: alloc::format!("jsonb_insert(): invalid JSON new_value — {e}"),
1340 })?;
1341 insert_at_path(&mut root, &path, new_val, insert_after)?;
1342 Ok(Value::json(root.to_json_text()))
1343}
1344
1345fn insert_at_path(
1346 node: &mut JsonValue,
1347 path: &[String],
1348 new_val: JsonValue,
1349 insert_after: bool,
1350) -> Result<(), EvalError> {
1351 debug_assert!(!path.is_empty());
1352 if path.len() == 1 {
1353 let step = &path[0];
1354 match node {
1355 JsonValue::Object(entries) => {
1356 if entries.iter().any(|(k, _)| k == step) {
1357 return Err(EvalError::TypeMismatch {
1358 detail: alloc::format!(
1359 "jsonb_insert(): cannot replace existing key {step:?}"
1360 ),
1361 });
1362 }
1363 entries.push((step.clone(), new_val));
1364 Ok(())
1365 }
1366 JsonValue::Array(items) => {
1367 let Ok(n) = step.parse::<i64>() else {
1368 return Err(EvalError::TypeMismatch {
1369 detail: alloc::format!(
1370 "jsonb_insert(): array step must be integer, got {step:?}"
1371 ),
1372 });
1373 };
1374 let mut idx = if n >= 0 {
1375 let i = n as usize;
1376 if i > items.len() { items.len() } else { i }
1377 } else {
1378 let from_end = items.len() as i64 + n;
1379 if from_end < 0 { 0 } else { from_end as usize }
1380 };
1381 if insert_after && idx < items.len() {
1382 idx += 1;
1383 }
1384 items.insert(idx, new_val);
1385 Ok(())
1386 }
1387 _ => Err(EvalError::TypeMismatch {
1388 detail: "jsonb_insert(): parent at path is a scalar".into(),
1389 }),
1390 }
1391 } else {
1392 let step = &path[0];
1393 let rest = &path[1..];
1394 match node {
1395 JsonValue::Object(entries) => {
1396 if let Some(pos) = entries.iter().position(|(k, _)| k == step) {
1397 insert_at_path(&mut entries[pos].1, rest, new_val, insert_after)
1398 } else {
1399 Err(EvalError::TypeMismatch {
1400 detail: alloc::format!("jsonb_insert(): path {step:?} does not exist"),
1401 })
1402 }
1403 }
1404 JsonValue::Array(items) => {
1405 let Some(idx) = resolve_array_index(step, items.len()) else {
1406 return Err(EvalError::TypeMismatch {
1407 detail: alloc::format!("jsonb_insert(): array index {step:?} out of range"),
1408 });
1409 };
1410 insert_at_path(&mut items[idx], rest, new_val, insert_after)
1411 }
1412 _ => Err(EvalError::TypeMismatch {
1413 detail: "jsonb_insert(): parent at path is a scalar".into(),
1414 }),
1415 }
1416 }
1417}
1418
1419fn json_text_arg<'a>(v: &'a Value, fname: &str, role: &str) -> Result<&'a str, EvalError> {
1420 match v {
1421 Value::Json(s) | Value::Text(s) => Ok(s.as_ref()),
1422 other => Err(EvalError::TypeMismatch {
1423 detail: alloc::format!(
1424 "{fname}() {role} must be JSON or TEXT, got {:?}",
1425 other.data_type()
1426 ),
1427 }),
1428 }
1429}
1430
1431fn path_text_arg(v: &Value, fname: &str) -> Result<Vec<String>, EvalError> {
1432 match v {
1433 Value::Text(s) | Value::Json(s) => parse_text_array(s.as_ref()),
1434 Value::TextArray(items) => Ok(items
1435 .iter()
1436 .map(|o| o.clone().unwrap_or_default())
1437 .collect()),
1438 other => Err(EvalError::TypeMismatch {
1439 detail: alloc::format!(
1440 "{fname}() path must be TEXT[] or TEXT, got {:?}",
1441 other.data_type()
1442 ),
1443 }),
1444 }
1445}
1446
1447#[cfg(test)]
1448mod tests {
1449 use super::*;
1450
1451 #[test]
1452 fn parse_atoms() {
1453 assert_eq!(parse("null").unwrap(), JsonValue::Null);
1454 assert_eq!(parse("true").unwrap(), JsonValue::Bool(true));
1455 assert_eq!(parse("false").unwrap(), JsonValue::Bool(false));
1456 assert_eq!(
1457 parse("\"hello\"").unwrap(),
1458 JsonValue::String("hello".into())
1459 );
1460 assert!(matches!(
1461 parse("42").unwrap(),
1462 JsonValue::NumberText(ref s) if s == "42"
1463 ));
1464 }
1465
1466 #[test]
1467 fn parse_nested() {
1468 let doc = parse(r#"{"a":1,"b":[true,null,"x"]}"#).unwrap();
1469 let JsonValue::Object(entries) = doc else {
1470 panic!("expected object");
1471 };
1472 assert_eq!(entries.len(), 2);
1473 assert_eq!(entries[0].0, "a");
1474 assert_eq!(entries[1].0, "b");
1475 }
1476
1477 #[test]
1478 fn parse_string_escapes() {
1479 let s = parse(r#""he said \"hi\" and\\then\n""#).unwrap();
1480 assert_eq!(s, JsonValue::String("he said \"hi\" and\\then\n".into()));
1481 }
1482
1483 #[test]
1484 fn parse_unicode_escape() {
1485 assert_eq!(parse(r#""é""#).unwrap(), JsonValue::String("é".into()));
1486 }
1487
1488 #[test]
1489 fn path_object_key_returns_value() {
1490 let doc = Value::json::<String>(r#"{"name":"alice","age":30}"#.into());
1491 let key = Value::text("name");
1492 let v = path_get(&doc, &key, true).unwrap();
1493 assert_eq!(v, Value::text("alice"));
1494 let v = path_get(&doc, &key, false).unwrap();
1495 assert_eq!(v, Value::json("\"alice\""));
1496 }
1497
1498 #[test]
1499 fn path_array_index_supports_negative() {
1500 let doc = Value::json("[10,20,30]");
1501 let v = path_get(&doc, &Value::Int(1), true).unwrap();
1502 assert_eq!(v, Value::text("20"));
1503 let v = path_get(&doc, &Value::Int(-1), true).unwrap();
1504 assert_eq!(v, Value::text("30"));
1505 }
1506
1507 #[test]
1508 fn path_missing_key_returns_null() {
1509 let doc = Value::json::<String>(r#"{"a":1}"#.into());
1510 let v = path_get(&doc, &Value::text("missing"), true).unwrap();
1511 assert_eq!(v, Value::Null);
1512 }
1513
1514 #[test]
1515 fn path_get_nested_subtree_renders_back() {
1516 let doc = Value::json::<String>(r#"{"k":{"x":[1,2]}}"#.into());
1517 let v = path_get(&doc, &Value::text("k"), false).unwrap();
1518 assert_eq!(v, Value::json::<String>("{\"x\":[1,2]}".into()));
1519 }
1520}