micromegas_datafusion_extensions/jsonb/
path_query.rs1use crate::binary_column_accessor::create_binary_accessor;
2use datafusion::arrow::array::{Array, BinaryDictionaryBuilder, StringArray};
3use datafusion::arrow::datatypes::{DataType, Int32Type};
4use datafusion::common::{Result, internal_err};
5use datafusion::error::DataFusionError;
6use datafusion::logical_expr::{
7 ColumnarValue, ScalarFunctionArgs, ScalarUDF, ScalarUDFImpl, Signature, Volatility,
8};
9use jsonb::RawJsonb;
10use jsonb::jsonpath::parse_json_path;
11use std::collections::HashMap;
12use std::sync::Arc;
13
14#[derive(Clone, Copy)]
15enum PathQueryMode {
16 First,
17 All,
18}
19
20fn eval_jsonb_path_query(
21 func_name: &str,
22 args: ScalarFunctionArgs,
23 mode: PathQueryMode,
24) -> Result<ColumnarValue> {
25 let args = ColumnarValue::values_to_arrays(&args.args)?;
26 if args.len() != 2 {
27 return internal_err!("wrong number of arguments to {func_name}");
28 }
29
30 let accessor = create_binary_accessor(&args[0]).map_err(|e| {
31 DataFusionError::Execution(format!(
32 "Invalid input type for {func_name}: {e}. Expected Binary or Dictionary<Int32, Binary>"
33 ))
34 })?;
35
36 let paths = args[1]
37 .as_any()
38 .downcast_ref::<StringArray>()
39 .ok_or_else(|| {
40 DataFusionError::Execution(format!("second argument to {func_name} must be a string"))
41 })?;
42
43 let mut builder = BinaryDictionaryBuilder::<Int32Type>::new();
44 let mut path_cache: HashMap<&str, _> = HashMap::new();
45
46 for i in 0..accessor.len() {
47 if accessor.is_null(i) || paths.is_null(i) {
48 builder.append_null();
49 } else {
50 let path_str = paths.value(i);
51 if !path_cache.contains_key(path_str) {
52 let parsed = parse_json_path(path_str.as_bytes()).map_err(|e| {
53 DataFusionError::Execution(format!(
54 "{func_name}: invalid JSONPath '{path_str}': {e}"
55 ))
56 })?;
57 path_cache.insert(path_str, parsed);
58 }
59 let json_path = path_cache.get(path_str).expect("just inserted");
60 let raw = RawJsonb::new(accessor.value(i));
61 match mode {
62 PathQueryMode::First => match raw.select_first_by_path(json_path) {
63 Ok(Some(value)) => builder.append_value(value.as_ref()),
64 Ok(None) => builder.append_null(),
65 Err(e) => return Err(DataFusionError::External(e.into())),
66 },
67 PathQueryMode::All => match raw.select_array_by_path(json_path) {
68 Ok(value) => builder.append_value(value.as_ref()),
69 Err(e) => return Err(DataFusionError::External(e.into())),
70 },
71 }
72 }
73 }
74
75 Ok(ColumnarValue::Array(Arc::new(builder.finish())))
76}
77
78#[derive(Debug, PartialEq, Eq, Hash)]
84pub struct JsonbPathQueryFirst {
85 signature: Signature,
86}
87
88impl JsonbPathQueryFirst {
89 pub fn new() -> Self {
90 Self {
91 signature: Signature::any(2, Volatility::Immutable),
92 }
93 }
94}
95
96impl Default for JsonbPathQueryFirst {
97 fn default() -> Self {
98 Self::new()
99 }
100}
101
102impl ScalarUDFImpl for JsonbPathQueryFirst {
103 fn name(&self) -> &str {
104 "jsonb_path_query_first"
105 }
106
107 fn signature(&self) -> &Signature {
108 &self.signature
109 }
110
111 fn return_type(&self, _args: &[DataType]) -> Result<DataType> {
112 Ok(DataType::Dictionary(
113 Box::new(DataType::Int32),
114 Box::new(DataType::Binary),
115 ))
116 }
117
118 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
119 eval_jsonb_path_query("jsonb_path_query_first", args, PathQueryMode::First)
120 }
121}
122
123pub fn make_jsonb_path_query_first_udf() -> ScalarUDF {
125 ScalarUDF::new_from_impl(JsonbPathQueryFirst::new())
126}
127
128#[derive(Debug, PartialEq, Eq, Hash)]
134pub struct JsonbPathQuery {
135 signature: Signature,
136}
137
138impl JsonbPathQuery {
139 pub fn new() -> Self {
140 Self {
141 signature: Signature::any(2, Volatility::Immutable),
142 }
143 }
144}
145
146impl Default for JsonbPathQuery {
147 fn default() -> Self {
148 Self::new()
149 }
150}
151
152impl ScalarUDFImpl for JsonbPathQuery {
153 fn name(&self) -> &str {
154 "jsonb_path_query"
155 }
156
157 fn signature(&self) -> &Signature {
158 &self.signature
159 }
160
161 fn return_type(&self, _args: &[DataType]) -> Result<DataType> {
162 Ok(DataType::Dictionary(
163 Box::new(DataType::Int32),
164 Box::new(DataType::Binary),
165 ))
166 }
167
168 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
169 eval_jsonb_path_query("jsonb_path_query", args, PathQueryMode::All)
170 }
171}
172
173pub fn make_jsonb_path_query_udf() -> ScalarUDF {
175 ScalarUDF::new_from_impl(JsonbPathQuery::new())
176}