1use std::hash::{Hash, Hasher};
15use std::sync::Arc;
16
17use arrow::datatypes::DataType;
18use arrow_array::{Array, BooleanArray, LargeBinaryArray};
19use datafusion_common::Result;
20use datafusion_expr::{
21 ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, TypeSignature, Volatility,
22};
23use parking_lot::Mutex;
24
25use super::json_types;
26
27type PathCache = Mutex<Option<(String, CompiledJsonPath)>>;
32
33#[derive(Debug, Clone, PartialEq)]
37pub enum JsonPathStep {
38 Root,
40 Member(String),
42 ArrayIndex(i64),
44 ArrayWildcard,
46}
47
48#[derive(Debug, Clone, PartialEq)]
53pub struct CompiledJsonPath {
54 pub steps: Vec<JsonPathStep>,
56}
57
58impl CompiledJsonPath {
59 pub fn compile(path_str: &str) -> std::result::Result<Self, String> {
73 let path_str = path_str.trim();
74 if path_str.is_empty() {
75 return Err("empty path expression".into());
76 }
77
78 let mut steps = Vec::new();
79 let chars: Vec<char> = path_str.chars().collect();
80 let mut pos = 0;
81
82 if chars.first() != Some(&'$') {
84 return Err(format!(
85 "path must start with '$', got '{}'",
86 chars.first().unwrap_or(&' ')
87 ));
88 }
89 steps.push(JsonPathStep::Root);
90 pos += 1;
91
92 while pos < chars.len() {
93 match chars[pos] {
94 '.' => {
95 pos += 1;
96 let start = pos;
98 while pos < chars.len()
99 && chars[pos] != '.'
100 && chars[pos] != '['
101 && !chars[pos].is_whitespace()
102 {
103 pos += 1;
104 }
105 if pos == start {
106 return Err(format!("empty member name at position {start}"));
107 }
108 let name: String = chars[start..pos].iter().collect();
109 steps.push(JsonPathStep::Member(name));
110 }
111 '[' => {
112 pos += 1;
113 while pos < chars.len() && chars[pos].is_whitespace() {
115 pos += 1;
116 }
117 if pos >= chars.len() {
118 return Err("unclosed bracket".into());
119 }
120 if chars[pos] == '*' {
121 steps.push(JsonPathStep::ArrayWildcard);
122 pos += 1;
123 } else if chars[pos] == '"' || chars[pos] == '\'' {
124 let quote = chars[pos];
126 pos += 1;
127 let start = pos;
128 while pos < chars.len() && chars[pos] != quote {
129 pos += 1;
130 }
131 if pos >= chars.len() {
132 return Err("unclosed quoted member".into());
133 }
134 let name: String = chars[start..pos].iter().collect();
135 steps.push(JsonPathStep::Member(name));
136 pos += 1; } else {
138 let start = pos;
140 let mut negative = false;
141 if pos < chars.len() && chars[pos] == '-' {
142 negative = true;
143 pos += 1;
144 }
145 while pos < chars.len() && chars[pos].is_ascii_digit() {
146 pos += 1;
147 }
148 if pos == start || (negative && pos == start + 1) {
149 return Err(format!("expected array index or '*' at position {start}"));
150 }
151 let idx_str: String = chars[start..pos].iter().collect();
152 let idx: i64 = idx_str
153 .parse()
154 .map_err(|_| format!("invalid array index: '{idx_str}'"))?;
155 steps.push(JsonPathStep::ArrayIndex(idx));
156 }
157 while pos < chars.len() && chars[pos].is_whitespace() {
159 pos += 1;
160 }
161 if pos >= chars.len() || chars[pos] != ']' {
162 return Err(format!("expected ']' at position {pos}"));
163 }
164 pos += 1;
165 }
166 c if c.is_whitespace() => {
167 pos += 1;
168 }
169 c => {
170 return Err(format!("unexpected character '{c}' at position {pos}"));
171 }
172 }
173 }
174
175 Ok(Self { steps })
176 }
177
178 #[must_use]
182 pub fn evaluate<'a>(&self, jsonb: &'a [u8]) -> Vec<&'a [u8]> {
183 if jsonb.is_empty() {
184 return Vec::new();
185 }
186 let mut current = vec![jsonb];
187
188 for step in &self.steps {
189 match step {
190 JsonPathStep::Root => {
191 }
193 JsonPathStep::Member(name) => {
194 let mut next = Vec::new();
195 for data in ¤t {
196 if let Some(val) = json_types::jsonb_get_field(data, name) {
197 next.push(val);
198 }
199 }
200 current = next;
201 }
202 JsonPathStep::ArrayIndex(idx) => {
203 let mut next = Vec::new();
204 for data in ¤t {
205 if *idx >= 0 {
206 if let Ok(i) = usize::try_from(*idx) {
207 if let Some(val) = json_types::jsonb_array_get(data, i) {
208 next.push(val);
209 }
210 }
211 }
212 }
213 current = next;
214 }
215 JsonPathStep::ArrayWildcard => {
216 let mut next = Vec::new();
217 for data in ¤t {
218 if !data.is_empty() && data[0] == 0x06 {
219 if data.len() >= 5 {
221 let count = u32::from_le_bytes([data[1], data[2], data[3], data[4]])
222 as usize;
223 for i in 0..count {
224 if let Some(val) = json_types::jsonb_array_get(data, i) {
225 next.push(val);
226 }
227 }
228 }
229 }
230 }
231 current = next;
232 }
233 }
234 }
235
236 current
237 }
238
239 #[must_use]
242 pub fn exists(&self, jsonb: &[u8]) -> bool {
243 !self.evaluate(jsonb).is_empty()
244 }
245}
246
247fn compile_cached(
253 cache: &PathCache,
254 path_str: &str,
255) -> std::result::Result<CompiledJsonPath, String> {
256 let mut guard = cache.lock();
257 if let Some((cached_str, cached_path)) = guard.as_ref() {
258 if cached_str == path_str {
259 return Ok(cached_path.clone());
260 }
261 }
262 let compiled = CompiledJsonPath::compile(path_str)?;
263 *guard = Some((path_str.to_owned(), compiled.clone()));
264 Ok(compiled)
265}
266
267#[derive(Debug)]
269pub struct JsonbPathExistsUdf {
270 signature: Signature,
271 path_cache: PathCache,
272}
273
274impl Default for JsonbPathExistsUdf {
275 fn default() -> Self {
276 Self::new()
277 }
278}
279
280impl JsonbPathExistsUdf {
281 #[must_use]
283 pub fn new() -> Self {
284 Self {
285 signature: Signature::new(
286 TypeSignature::Exact(vec![DataType::LargeBinary, DataType::Utf8]),
287 Volatility::Immutable,
288 ),
289 path_cache: Mutex::new(None),
290 }
291 }
292}
293
294impl PartialEq for JsonbPathExistsUdf {
295 fn eq(&self, _other: &Self) -> bool {
296 true
297 }
298}
299
300impl Eq for JsonbPathExistsUdf {}
301
302impl Hash for JsonbPathExistsUdf {
303 fn hash<H: Hasher>(&self, state: &mut H) {
304 "jsonb_path_exists".hash(state);
305 }
306}
307
308impl ScalarUDFImpl for JsonbPathExistsUdf {
309 fn as_any(&self) -> &dyn std::any::Any {
310 self
311 }
312
313 fn name(&self) -> &'static str {
314 "jsonb_path_exists"
315 }
316
317 fn signature(&self) -> &Signature {
318 &self.signature
319 }
320
321 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
322 Ok(DataType::Boolean)
323 }
324
325 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
326 let first = &args.args[0];
327 let second = &args.args[1];
328
329 match (first, second) {
330 (
331 ColumnarValue::Array(bin_arr),
332 ColumnarValue::Scalar(datafusion_common::ScalarValue::Utf8(Some(path_str))),
333 ) => {
334 let compiled = compile_cached(&self.path_cache, path_str)
335 .map_err(datafusion_common::DataFusionError::Execution)?;
336 let binary = bin_arr
337 .as_any()
338 .downcast_ref::<LargeBinaryArray>()
339 .ok_or_else(|| {
340 datafusion_common::DataFusionError::Execution(
341 "expected LargeBinary array".into(),
342 )
343 })?;
344 let results: BooleanArray = (0..binary.len())
345 .map(|i| {
346 if binary.is_null(i) {
347 None
348 } else {
349 Some(compiled.exists(binary.value(i)))
350 }
351 })
352 .collect();
353 Ok(ColumnarValue::Array(Arc::new(results)))
354 }
355 (
356 ColumnarValue::Scalar(datafusion_common::ScalarValue::LargeBinary(Some(bytes))),
357 ColumnarValue::Scalar(datafusion_common::ScalarValue::Utf8(Some(path_str))),
358 ) => {
359 let compiled = compile_cached(&self.path_cache, path_str)
360 .map_err(datafusion_common::DataFusionError::Execution)?;
361 let result = compiled.exists(bytes);
362 Ok(ColumnarValue::Scalar(
363 datafusion_common::ScalarValue::Boolean(Some(result)),
364 ))
365 }
366 _ => Ok(ColumnarValue::Scalar(
367 datafusion_common::ScalarValue::Boolean(None),
368 )),
369 }
370 }
371}
372
373#[derive(Debug)]
381pub struct JsonbPathMatchUdf {
382 signature: Signature,
383 path_cache: PathCache,
384}
385
386impl Default for JsonbPathMatchUdf {
387 fn default() -> Self {
388 Self::new()
389 }
390}
391
392impl JsonbPathMatchUdf {
393 #[must_use]
395 pub fn new() -> Self {
396 Self {
397 signature: Signature::new(
398 TypeSignature::Exact(vec![DataType::LargeBinary, DataType::Utf8]),
399 Volatility::Immutable,
400 ),
401 path_cache: Mutex::new(None),
402 }
403 }
404}
405
406impl PartialEq for JsonbPathMatchUdf {
407 fn eq(&self, _other: &Self) -> bool {
408 true
409 }
410}
411
412impl Eq for JsonbPathMatchUdf {}
413
414impl Hash for JsonbPathMatchUdf {
415 fn hash<H: Hasher>(&self, state: &mut H) {
416 "jsonb_path_match".hash(state);
417 }
418}
419
420fn jsonb_is_true(data: &[u8]) -> Option<bool> {
422 if data.is_empty() {
423 return None;
424 }
425 match data[0] {
426 0x01 => Some(false), 0x02 => Some(true), _ => None, }
430}
431
432impl ScalarUDFImpl for JsonbPathMatchUdf {
433 fn as_any(&self) -> &dyn std::any::Any {
434 self
435 }
436
437 fn name(&self) -> &'static str {
438 "jsonb_path_match"
439 }
440
441 fn signature(&self) -> &Signature {
442 &self.signature
443 }
444
445 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
446 Ok(DataType::Boolean)
447 }
448
449 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
450 let first = &args.args[0];
451 let second = &args.args[1];
452
453 match (first, second) {
454 (
455 ColumnarValue::Array(bin_arr),
456 ColumnarValue::Scalar(datafusion_common::ScalarValue::Utf8(Some(path_str))),
457 ) => {
458 let compiled = compile_cached(&self.path_cache, path_str)
459 .map_err(datafusion_common::DataFusionError::Execution)?;
460 let binary = bin_arr
461 .as_any()
462 .downcast_ref::<LargeBinaryArray>()
463 .ok_or_else(|| {
464 datafusion_common::DataFusionError::Execution(
465 "expected LargeBinary array".into(),
466 )
467 })?;
468 let results: BooleanArray = (0..binary.len())
469 .map(|i| {
470 if binary.is_null(i) {
471 None
472 } else {
473 let matched = compiled.evaluate(binary.value(i));
474 if matched.len() == 1 {
475 jsonb_is_true(matched[0])
476 } else {
477 None
478 }
479 }
480 })
481 .collect();
482 Ok(ColumnarValue::Array(Arc::new(results)))
483 }
484 (
485 ColumnarValue::Scalar(datafusion_common::ScalarValue::LargeBinary(Some(bytes))),
486 ColumnarValue::Scalar(datafusion_common::ScalarValue::Utf8(Some(path_str))),
487 ) => {
488 let compiled = compile_cached(&self.path_cache, path_str)
489 .map_err(datafusion_common::DataFusionError::Execution)?;
490 let matched = compiled.evaluate(bytes);
491 let result = if matched.len() == 1 {
492 jsonb_is_true(matched[0])
493 } else {
494 None
495 };
496 Ok(ColumnarValue::Scalar(
497 datafusion_common::ScalarValue::Boolean(result),
498 ))
499 }
500 _ => Ok(ColumnarValue::Scalar(
501 datafusion_common::ScalarValue::Boolean(None),
502 )),
503 }
504 }
505}
506
507#[cfg(test)]
508mod tests {
509 use super::*;
510
511 #[test]
514 fn test_compile_root_only() {
515 let path = CompiledJsonPath::compile("$").unwrap();
516 assert_eq!(path.steps, vec![JsonPathStep::Root]);
517 }
518
519 #[test]
520 fn test_compile_member_access() {
521 let path = CompiledJsonPath::compile("$.name").unwrap();
522 assert_eq!(
523 path.steps,
524 vec![JsonPathStep::Root, JsonPathStep::Member("name".into()),]
525 );
526 }
527
528 #[test]
529 fn test_compile_nested_members() {
530 let path = CompiledJsonPath::compile("$.user.address.city").unwrap();
531 assert_eq!(
532 path.steps,
533 vec![
534 JsonPathStep::Root,
535 JsonPathStep::Member("user".into()),
536 JsonPathStep::Member("address".into()),
537 JsonPathStep::Member("city".into()),
538 ]
539 );
540 }
541
542 #[test]
543 fn test_compile_array_index() {
544 let path = CompiledJsonPath::compile("$.items[0]").unwrap();
545 assert_eq!(
546 path.steps,
547 vec![
548 JsonPathStep::Root,
549 JsonPathStep::Member("items".into()),
550 JsonPathStep::ArrayIndex(0),
551 ]
552 );
553 }
554
555 #[test]
556 fn test_compile_wildcard() {
557 let path = CompiledJsonPath::compile("$.items[*].price").unwrap();
558 assert_eq!(
559 path.steps,
560 vec![
561 JsonPathStep::Root,
562 JsonPathStep::Member("items".into()),
563 JsonPathStep::ArrayWildcard,
564 JsonPathStep::Member("price".into()),
565 ]
566 );
567 }
568
569 #[test]
570 fn test_compile_quoted_member() {
571 let path = CompiledJsonPath::compile("$[\"spaced key\"]").unwrap();
572 assert_eq!(
573 path.steps,
574 vec![
575 JsonPathStep::Root,
576 JsonPathStep::Member("spaced key".into()),
577 ]
578 );
579 }
580
581 #[test]
582 fn test_compile_empty_path_error() {
583 assert!(CompiledJsonPath::compile("").is_err());
584 }
585
586 #[test]
587 fn test_compile_no_root_error() {
588 assert!(CompiledJsonPath::compile("name").is_err());
589 }
590
591 #[test]
594 fn test_evaluate_root() {
595 let json: serde_json::Value = serde_json::from_str(r#"{"a": 1}"#).unwrap();
596 let jsonb = json_types::encode_jsonb(&json);
597 let path = CompiledJsonPath::compile("$").unwrap();
598 let results = path.evaluate(&jsonb);
599 assert_eq!(results.len(), 1);
600 }
601
602 #[test]
603 fn test_evaluate_member() {
604 let json: serde_json::Value = serde_json::from_str(r#"{"name": "Alice"}"#).unwrap();
605 let jsonb = json_types::encode_jsonb(&json);
606 let path = CompiledJsonPath::compile("$.name").unwrap();
607 let results = path.evaluate(&jsonb);
608 assert_eq!(results.len(), 1);
609 assert_eq!(
611 json_types::jsonb_to_text(results[0]),
612 Some("Alice".to_string())
613 );
614 }
615
616 #[test]
617 fn test_evaluate_missing_member() {
618 let json: serde_json::Value = serde_json::from_str(r#"{"name": "Alice"}"#).unwrap();
619 let jsonb = json_types::encode_jsonb(&json);
620 let path = CompiledJsonPath::compile("$.age").unwrap();
621 let results = path.evaluate(&jsonb);
622 assert!(results.is_empty());
623 }
624
625 #[test]
626 fn test_evaluate_array_index() {
627 let json: serde_json::Value = serde_json::from_str(r#"{"items": [10, 20, 30]}"#).unwrap();
628 let jsonb = json_types::encode_jsonb(&json);
629 let path = CompiledJsonPath::compile("$.items[1]").unwrap();
630 let results = path.evaluate(&jsonb);
631 assert_eq!(results.len(), 1);
632 assert_eq!(
633 json_types::jsonb_to_text(results[0]),
634 Some("20".to_string())
635 );
636 }
637
638 #[test]
639 fn test_evaluate_wildcard() {
640 let json: serde_json::Value =
641 serde_json::from_str(r#"{"items": [{"price": 10}, {"price": 20}]}"#).unwrap();
642 let jsonb = json_types::encode_jsonb(&json);
643 let path = CompiledJsonPath::compile("$.items[*].price").unwrap();
644 let results = path.evaluate(&jsonb);
645 assert_eq!(results.len(), 2);
646 }
647
648 #[test]
649 fn test_exists_true() {
650 let json: serde_json::Value = serde_json::from_str(r#"{"users": [{"age": 30}]}"#).unwrap();
651 let jsonb = json_types::encode_jsonb(&json);
652 let path = CompiledJsonPath::compile("$.users[0].age").unwrap();
653 assert!(path.exists(&jsonb));
654 }
655
656 #[test]
657 fn test_exists_false() {
658 let json: serde_json::Value = serde_json::from_str(r#"{"users": [{"age": 30}]}"#).unwrap();
659 let jsonb = json_types::encode_jsonb(&json);
660 let path = CompiledJsonPath::compile("$.users[0].email").unwrap();
661 assert!(!path.exists(&jsonb));
662 }
663
664 #[test]
667 fn test_path_exists_udf_scalar() {
668 let udf = JsonbPathExistsUdf::new();
669 assert_eq!(udf.name(), "jsonb_path_exists");
670 assert_eq!(udf.return_type(&[]).unwrap(), DataType::Boolean);
671 }
672
673 #[test]
674 fn test_path_match_udf_scalar() {
675 let udf = JsonbPathMatchUdf::new();
676 assert_eq!(udf.name(), "jsonb_path_match");
677 assert_eq!(udf.return_type(&[]).unwrap(), DataType::Boolean);
678 }
679}