micromegas_datafusion_extensions/jsonb/
list_udfs.rs1use crate::binary_column_accessor::{BinaryColumnAccessor, create_binary_accessor};
10use crate::jsonb::extract::{EntryList, array_elements, object_or_array_entries, path_select_all};
11use datafusion::arrow::array::{
12 Array, ArrayBuilder, ArrayRef, BinaryBuilder, DictionaryArray, GenericBinaryArray, ListBuilder,
13 StringArray, StringBuilder, StructBuilder,
14};
15use datafusion::arrow::compute::take;
16use datafusion::arrow::datatypes::{DataType, Field, Fields, Int32Type};
17use datafusion::common::{Result, exec_err};
18use datafusion::error::DataFusionError;
19use datafusion::logical_expr::{
20 ColumnarValue, ScalarFunctionArgs, ScalarUDF, ScalarUDFImpl, Signature, Volatility,
21};
22use jsonb::jsonpath::{JsonPath, parse_json_path};
23use std::collections::{HashMap, HashSet};
24use std::sync::Arc;
25
26fn entry_struct_fields() -> Vec<Field> {
29 vec![
30 Field::new("key", DataType::Utf8, false),
31 Field::new("value", DataType::Binary, true),
32 ]
33}
34
35fn entries_list_field() -> Arc<Field> {
36 Arc::new(Field::new_list_field(
37 DataType::Struct(Fields::from(entry_struct_fields())),
38 false,
39 ))
40}
41
42fn entries_return_type() -> DataType {
43 DataType::List(entries_list_field())
44}
45
46fn binary_list_field() -> Arc<Field> {
47 Arc::new(Field::new_list_field(DataType::Binary, true))
48}
49
50fn binary_list_return_type() -> DataType {
51 DataType::List(binary_list_field())
52}
53
54fn referenced_value_indices(dict_array: &DictionaryArray<Int32Type>) -> HashSet<i32> {
58 let keys = dict_array.keys();
59 (0..keys.len())
60 .filter(|&i| !keys.is_null(i))
61 .map(|i| keys.value(i))
62 .collect()
63}
64
65fn dict_values_binary_array(
66 dict_array: &DictionaryArray<Int32Type>,
67) -> Result<&GenericBinaryArray<i32>> {
68 dict_array
69 .values()
70 .as_any()
71 .downcast_ref::<GenericBinaryArray<i32>>()
72 .ok_or_else(|| DataFusionError::Internal("dictionary values are not a binary array".into()))
73}
74
75pub(crate) fn binary_dict(array: &ArrayRef) -> Option<&DictionaryArray<Int32Type>> {
81 if let DataType::Dictionary(key, value) = array.data_type()
82 && key.as_ref() == &DataType::Int32
83 && value.as_ref() == &DataType::Binary
84 {
85 array.as_any().downcast_ref::<DictionaryArray<Int32Type>>()
86 } else {
87 None
88 }
89}
90
91pub(crate) fn binary_accessor_or_err(
94 array: &ArrayRef,
95 func_name: &str,
96) -> Result<Box<dyn BinaryColumnAccessor + Send>> {
97 create_binary_accessor(array).map_err(|e| {
98 DataFusionError::Execution(format!(
99 "Invalid input type for {func_name}: {e}. Expected Binary or Dictionary<Int32, Binary>"
100 ))
101 })
102}
103
104pub(crate) fn dict_fast_path<T, F>(
113 dict_array: &DictionaryArray<Int32Type>,
114 mut list_builder: ListBuilder<T>,
115 mut append_slot: F,
116) -> Result<ArrayRef>
117where
118 T: ArrayBuilder,
119 F: FnMut(&mut ListBuilder<T>, &[u8]) -> Result<()>,
120{
121 let binary_values = dict_values_binary_array(dict_array)?;
122 let referenced = referenced_value_indices(dict_array);
123 for idx in 0..binary_values.len() {
124 if referenced.contains(&(idx as i32)) {
125 append_slot(&mut list_builder, binary_values.value(idx))?;
126 } else {
127 list_builder.append(false);
128 }
129 }
130 let list_array = list_builder.finish();
131 let taken = take(&list_array, dict_array.keys(), None)
132 .map_err(|e| DataFusionError::ArrowError(Box::new(e), None))?;
133 Ok(taken)
134}
135
136fn append_binary_slot(list_builder: &mut ListBuilder<BinaryBuilder>, items: Option<Vec<Vec<u8>>>) {
137 match items {
138 Some(values) => {
139 for v in values {
140 list_builder.values().append_value(v);
141 }
142 list_builder.append(true);
143 }
144 None => list_builder.append(false),
145 }
146}
147
148fn append_entries_to_struct_builder(
149 struct_builder: &mut StructBuilder,
150 entries: EntryList,
151) -> Result<()> {
152 for (key, value) in entries {
153 struct_builder
154 .field_builder::<StringBuilder>(0)
155 .ok_or_else(|| {
156 DataFusionError::Internal("failed to get jsonb_entries key builder".into())
157 })?
158 .append_value(&key);
159 struct_builder
160 .field_builder::<BinaryBuilder>(1)
161 .ok_or_else(|| {
162 DataFusionError::Internal("failed to get jsonb_entries value builder".into())
163 })?
164 .append_value(&value);
165 struct_builder.append(true);
166 }
167 Ok(())
168}
169
170fn append_entries_slot(
171 list_builder: &mut ListBuilder<StructBuilder>,
172 entries: Option<EntryList>,
173) -> Result<()> {
174 match entries {
175 Some(entries) => {
176 append_entries_to_struct_builder(list_builder.values(), entries)?;
177 list_builder.append(true);
178 }
179 None => list_builder.append(false),
180 }
181 Ok(())
182}
183
184fn build_binary_list<F>(array: &ArrayRef, func_name: &str, mut extract: F) -> Result<ArrayRef>
187where
188 F: FnMut(&[u8]) -> Result<Option<Vec<Vec<u8>>>>,
189{
190 if let Some(dict_array) = binary_dict(array) {
191 let list_builder = ListBuilder::new(BinaryBuilder::new()).with_field(binary_list_field());
192 return dict_fast_path(dict_array, list_builder, |builder, bytes| {
193 append_binary_slot(builder, extract(bytes)?);
194 Ok(())
195 });
196 }
197
198 let accessor = binary_accessor_or_err(array, func_name)?;
199 let mut list_builder = ListBuilder::new(BinaryBuilder::new()).with_field(binary_list_field());
200 for i in 0..accessor.len() {
201 if accessor.is_null(i) {
202 list_builder.append(false);
203 } else {
204 append_binary_slot(&mut list_builder, extract(accessor.value(i))?);
205 }
206 }
207 Ok(Arc::new(list_builder.finish()))
208}
209
210fn build_entries_list(array: &ArrayRef) -> Result<ArrayRef> {
211 if let Some(dict_array) = binary_dict(array) {
212 let capacity = dict_array.values().len();
213 let list_builder =
214 ListBuilder::new(StructBuilder::from_fields(entry_struct_fields(), capacity))
215 .with_field(entries_list_field());
216 return dict_fast_path(dict_array, list_builder, |builder, bytes| {
217 append_entries_slot(builder, object_or_array_entries(bytes)?)
218 });
219 }
220
221 let accessor = binary_accessor_or_err(array, "jsonb_entries")?;
222 let mut list_builder = ListBuilder::new(StructBuilder::from_fields(
223 entry_struct_fields(),
224 accessor.len(),
225 ))
226 .with_field(entries_list_field());
227 for i in 0..accessor.len() {
228 if accessor.is_null(i) {
229 list_builder.append(false);
230 } else {
231 append_entries_slot(
232 &mut list_builder,
233 object_or_array_entries(accessor.value(i))?,
234 )?;
235 }
236 }
237 Ok(Arc::new(list_builder.finish()))
238}
239
240#[derive(Debug, PartialEq, Eq, Hash)]
249pub struct JsonbEntries {
250 signature: Signature,
251}
252
253impl JsonbEntries {
254 pub fn new() -> Self {
255 Self {
256 signature: Signature::any(1, Volatility::Immutable),
257 }
258 }
259}
260
261impl Default for JsonbEntries {
262 fn default() -> Self {
263 Self::new()
264 }
265}
266
267impl ScalarUDFImpl for JsonbEntries {
268 fn name(&self) -> &str {
269 "jsonb_entries"
270 }
271
272 fn signature(&self) -> &Signature {
273 &self.signature
274 }
275
276 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
277 Ok(entries_return_type())
278 }
279
280 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
281 let args = ColumnarValue::values_to_arrays(&args.args)?;
282 if args.len() != 1 {
283 return exec_err!("wrong number of arguments to jsonb_entries()");
284 }
285 Ok(ColumnarValue::Array(build_entries_list(&args[0])?))
286 }
287}
288
289pub fn make_jsonb_entries_udf() -> ScalarUDF {
291 ScalarUDF::new_from_impl(JsonbEntries::new())
292}
293
294#[derive(Debug, PartialEq, Eq, Hash)]
302pub struct JsonbElements {
303 signature: Signature,
304}
305
306impl JsonbElements {
307 pub fn new() -> Self {
308 Self {
309 signature: Signature::any(1, Volatility::Immutable),
310 }
311 }
312}
313
314impl Default for JsonbElements {
315 fn default() -> Self {
316 Self::new()
317 }
318}
319
320impl ScalarUDFImpl for JsonbElements {
321 fn name(&self) -> &str {
322 "jsonb_elements"
323 }
324
325 fn signature(&self) -> &Signature {
326 &self.signature
327 }
328
329 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
330 Ok(binary_list_return_type())
331 }
332
333 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
334 let args = ColumnarValue::values_to_arrays(&args.args)?;
335 if args.len() != 1 {
336 return exec_err!("wrong number of arguments to jsonb_elements()");
337 }
338 Ok(ColumnarValue::Array(build_binary_list(
339 &args[0],
340 "jsonb_elements",
341 array_elements,
342 )?))
343 }
344}
345
346pub fn make_jsonb_elements_udf() -> ScalarUDF {
348 ScalarUDF::new_from_impl(JsonbElements::new())
349}
350
351fn constant_non_null_path(paths: &StringArray) -> Option<&str> {
358 let mut constant: Option<&str> = None;
359 for i in 0..paths.len() {
360 if paths.is_null(i) {
361 return None;
362 }
363 let v = paths.value(i);
364 match constant {
365 None => constant = Some(v),
366 Some(c) if c == v => {}
367 Some(_) => return None,
368 }
369 }
370 constant
371}
372
373fn dict_fast_path_path_elements<'p>(
379 dict_array: &DictionaryArray<Int32Type>,
380 path_str: &'p str,
381) -> Result<ArrayRef> {
382 let list_builder = ListBuilder::new(BinaryBuilder::new()).with_field(binary_list_field());
383 let mut parsed_path: Option<JsonPath<'p>> = None;
384 dict_fast_path(dict_array, list_builder, |builder, bytes| {
385 if parsed_path.is_none() {
386 let parsed = parse_json_path(path_str.as_bytes()).map_err(|e| {
387 DataFusionError::Execution(format!(
388 "jsonb_path_elements: invalid JSONPath '{path_str}': {e}"
389 ))
390 })?;
391 parsed_path = Some(parsed);
392 }
393 let json_path = parsed_path.as_ref().expect("just set");
394 let items = path_select_all(bytes, json_path)?;
395 for item in items {
396 builder.values().append_value(item);
397 }
398 builder.append(true);
399 Ok(())
400 })
401}
402
403#[derive(Debug, PartialEq, Eq, Hash)]
409pub struct JsonbPathElements {
410 signature: Signature,
411}
412
413impl JsonbPathElements {
414 pub fn new() -> Self {
415 Self {
416 signature: Signature::any(2, Volatility::Immutable),
417 }
418 }
419}
420
421impl Default for JsonbPathElements {
422 fn default() -> Self {
423 Self::new()
424 }
425}
426
427impl ScalarUDFImpl for JsonbPathElements {
428 fn name(&self) -> &str {
429 "jsonb_path_elements"
430 }
431
432 fn signature(&self) -> &Signature {
433 &self.signature
434 }
435
436 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
437 Ok(binary_list_return_type())
438 }
439
440 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
441 let args = ColumnarValue::values_to_arrays(&args.args)?;
442 if args.len() != 2 {
443 return exec_err!("wrong number of arguments to jsonb_path_elements()");
444 }
445
446 let paths = args[1]
447 .as_any()
448 .downcast_ref::<StringArray>()
449 .ok_or_else(|| {
450 DataFusionError::Execution(
451 "second argument to jsonb_path_elements must be a string".into(),
452 )
453 })?;
454
455 if let Some(dict_array) = binary_dict(&args[0])
456 && let Some(constant_path) = constant_non_null_path(paths)
457 {
458 return Ok(ColumnarValue::Array(dict_fast_path_path_elements(
459 dict_array,
460 constant_path,
461 )?));
462 }
463
464 let accessor = binary_accessor_or_err(&args[0], "jsonb_path_elements")?;
465 let mut list_builder =
466 ListBuilder::new(BinaryBuilder::new()).with_field(binary_list_field());
467 let mut path_cache: HashMap<&str, JsonPath> = HashMap::new();
468 for i in 0..accessor.len() {
469 if accessor.is_null(i) || paths.is_null(i) {
470 list_builder.append(false);
471 continue;
472 }
473 let path_str = paths.value(i);
474 if !path_cache.contains_key(path_str) {
475 let parsed = parse_json_path(path_str.as_bytes()).map_err(|e| {
476 DataFusionError::Execution(format!(
477 "jsonb_path_elements: invalid JSONPath '{path_str}': {e}"
478 ))
479 })?;
480 path_cache.insert(path_str, parsed);
481 }
482 let json_path = path_cache.get(path_str).expect("just inserted");
483 let items = path_select_all(accessor.value(i), json_path)?;
484 for item in items {
485 list_builder.values().append_value(item);
486 }
487 list_builder.append(true);
488 }
489 Ok(ColumnarValue::Array(Arc::new(list_builder.finish())))
490 }
491}
492
493pub fn make_jsonb_path_elements_udf() -> ScalarUDF {
495 ScalarUDF::new_from_impl(JsonbPathElements::new())
496}