1use std::hash::{Hash, Hasher};
18use std::sync::Arc;
19
20use arrow::datatypes::DataType;
21use arrow_array::{Array, ArrayRef, BooleanArray, ListArray, MapArray, StructArray};
22use arrow_schema::{Field, Fields, Schema};
23use datafusion_common::Result;
24use datafusion_expr::{
25 ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, TypeSignature, Volatility,
26};
27
28use super::json_udf::expand_args;
29
30pub fn register_lambda_functions(ctx: &datafusion::prelude::SessionContext) {
32 use datafusion_expr::ScalarUDF;
33
34 ctx.register_udf(ScalarUDF::new_from_impl(ArrayTransform::new()));
35 ctx.register_udf(ScalarUDF::new_from_impl(ArrayFilter::new()));
36 ctx.register_udf(ScalarUDF::new_from_impl(ArrayReduce::new()));
37 ctx.register_udf(ScalarUDF::new_from_impl(MapFilter::new()));
38 ctx.register_udf(ScalarUDF::new_from_impl(MapTransformValues::new()));
39}
40
41thread_local! {
42 static LAMBDA_CTX: std::cell::RefCell<Option<datafusion::prelude::SessionContext>> =
50 const { std::cell::RefCell::new(None) };
51}
52
53fn eval_expr_on_batch(sql_expr: &str, batch: &arrow_array::RecordBatch) -> Result<ArrayRef> {
57 let ctx = LAMBDA_CTX.with(|cell| {
60 let mut opt = cell.borrow_mut();
61 opt.get_or_insert_with(datafusion::prelude::SessionContext::new)
62 .clone()
63 });
64
65 let provider =
66 datafusion::datasource::MemTable::try_new(batch.schema(), vec![vec![batch.clone()]])?;
67 let rt = tokio::runtime::Handle::try_current().map_err(|e| {
68 datafusion_common::DataFusionError::Internal(format!(
69 "lambda eval requires tokio runtime: {e}"
70 ))
71 })?;
72 tokio::task::block_in_place(|| {
74 rt.block_on(async {
75 ctx.register_table("__lambda_data", Arc::new(provider))?;
76 let df = ctx
77 .sql(&format!("SELECT {sql_expr} FROM __lambda_data"))
78 .await?;
79 let batches = df.collect().await?;
80 let _ = ctx.deregister_table("__lambda_data");
82 if batches.is_empty() {
83 Err(datafusion_common::DataFusionError::Internal(
84 "lambda expression returned no data".into(),
85 ))
86 } else {
87 let result = arrow::compute::concat_batches(&batches[0].schema(), &batches)?;
89 Ok(result.column(0).clone())
90 }
91 })
92 })
93}
94
95fn scalar_string_value(cv: &ColumnarValue) -> Result<String> {
96 match cv {
97 ColumnarValue::Scalar(s) => {
98 let arr = s.to_array_of_size(1)?;
99 let str_arr = arr
100 .as_any()
101 .downcast_ref::<arrow_array::StringArray>()
102 .ok_or_else(|| {
103 datafusion_common::DataFusionError::Internal("expected Utf8 argument".into())
104 })?;
105 Ok(str_arr.value(0).to_string())
106 }
107 ColumnarValue::Array(arr) => {
108 let str_arr = arr
109 .as_any()
110 .downcast_ref::<arrow_array::StringArray>()
111 .ok_or_else(|| {
112 datafusion_common::DataFusionError::Internal("expected Utf8 argument".into())
113 })?;
114 Ok(str_arr.value(0).to_string())
115 }
116 }
117}
118
119#[derive(Debug)]
125pub struct ArrayTransform {
126 signature: Signature,
127}
128
129impl ArrayTransform {
130 #[must_use]
132 pub fn new() -> Self {
133 Self {
134 signature: Signature::new(TypeSignature::Any(2), Volatility::Immutable),
135 }
136 }
137}
138
139impl Default for ArrayTransform {
140 fn default() -> Self {
141 Self::new()
142 }
143}
144impl PartialEq for ArrayTransform {
145 fn eq(&self, _: &Self) -> bool {
146 true
147 }
148}
149impl Eq for ArrayTransform {}
150impl Hash for ArrayTransform {
151 fn hash<H: Hasher>(&self, s: &mut H) {
152 "array_transform".hash(s);
153 }
154}
155
156impl ScalarUDFImpl for ArrayTransform {
157 fn as_any(&self) -> &dyn std::any::Any {
158 self
159 }
160
161 fn name(&self) -> &'static str {
162 "array_transform"
163 }
164 fn signature(&self) -> &Signature {
165 &self.signature
166 }
167
168 fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
169 match &arg_types[0] {
170 DataType::List(f) => Ok(DataType::List(Arc::clone(f))),
171 _ => Ok(DataType::List(Arc::new(Field::new(
172 "item",
173 DataType::Utf8,
174 true,
175 )))),
176 }
177 }
178
179 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
180 let expanded = expand_args(&args.args)?;
181 let list_arr = expanded[0]
182 .as_any()
183 .downcast_ref::<ListArray>()
184 .ok_or_else(|| {
185 datafusion_common::DataFusionError::Internal(
186 "array_transform: first arg must be List".into(),
187 )
188 })?;
189
190 let lambda_str = scalar_string_value(&args.args[1])?;
191 let flat_values = list_arr.values();
192
193 let schema = Arc::new(Schema::new(vec![Field::new(
194 "x",
195 flat_values.data_type().clone(),
196 true,
197 )]));
198 let batch = arrow_array::RecordBatch::try_new(schema, vec![Arc::clone(flat_values)])?;
199
200 let result_arr = eval_expr_on_batch(&lambda_str, &batch)?;
201
202 let new_field = Arc::new(Field::new("item", result_arr.data_type().clone(), true));
203 let new_list = ListArray::try_new(
204 new_field,
205 list_arr.offsets().clone(),
206 result_arr,
207 list_arr.nulls().cloned(),
208 )?;
209 Ok(ColumnarValue::Array(Arc::new(new_list)))
210 }
211}
212
213#[derive(Debug)]
219pub struct ArrayFilter {
220 signature: Signature,
221}
222
223impl ArrayFilter {
224 #[must_use]
226 pub fn new() -> Self {
227 Self {
228 signature: Signature::new(TypeSignature::Any(2), Volatility::Immutable),
229 }
230 }
231}
232
233impl Default for ArrayFilter {
234 fn default() -> Self {
235 Self::new()
236 }
237}
238impl PartialEq for ArrayFilter {
239 fn eq(&self, _: &Self) -> bool {
240 true
241 }
242}
243impl Eq for ArrayFilter {}
244impl Hash for ArrayFilter {
245 fn hash<H: Hasher>(&self, s: &mut H) {
246 "array_filter".hash(s);
247 }
248}
249
250impl ScalarUDFImpl for ArrayFilter {
251 fn as_any(&self) -> &dyn std::any::Any {
252 self
253 }
254
255 fn name(&self) -> &'static str {
256 "array_filter"
257 }
258 fn signature(&self) -> &Signature {
259 &self.signature
260 }
261
262 fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
263 match &arg_types[0] {
264 DataType::List(f) => Ok(DataType::List(Arc::clone(f))),
265 _ => Ok(DataType::List(Arc::new(Field::new(
266 "item",
267 DataType::Utf8,
268 true,
269 )))),
270 }
271 }
272
273 #[allow(
274 clippy::cast_sign_loss,
275 clippy::cast_possible_wrap,
276 clippy::cast_possible_truncation
277 )]
278 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
279 let expanded = expand_args(&args.args)?;
280 let list_arr = expanded[0]
281 .as_any()
282 .downcast_ref::<ListArray>()
283 .ok_or_else(|| {
284 datafusion_common::DataFusionError::Internal(
285 "array_filter: first arg must be List".into(),
286 )
287 })?;
288
289 let lambda_str = scalar_string_value(&args.args[1])?;
290 let flat_values = list_arr.values();
291 let elem_type = flat_values.data_type().clone();
292
293 let schema = Arc::new(Schema::new(vec![Field::new("x", elem_type.clone(), true)]));
294 let batch = arrow_array::RecordBatch::try_new(schema, vec![Arc::clone(flat_values)])?;
295
296 let mask_arr = eval_expr_on_batch(&lambda_str, &batch)?;
297 let mask = mask_arr
298 .as_any()
299 .downcast_ref::<BooleanArray>()
300 .ok_or_else(|| {
301 datafusion_common::DataFusionError::Internal(
302 "array_filter: lambda must return Boolean".into(),
303 )
304 })?;
305
306 let mut offsets = vec![0i32];
307 let mut filtered_indices: Vec<usize> = Vec::new();
308
309 for row in 0..list_arr.len() {
310 let start = list_arr.value_offsets()[row] as usize;
311 let end = list_arr.value_offsets()[row + 1] as usize;
312
313 for i in start..end {
314 if !mask.is_null(i) && mask.value(i) {
315 filtered_indices.push(i);
316 }
317 }
318 offsets.push(filtered_indices.len() as i32);
319 }
320
321 let indices = arrow_array::UInt32Array::from(
322 filtered_indices
323 .iter()
324 .map(|&i| i as u32)
325 .collect::<Vec<_>>(),
326 );
327 let filtered_values = arrow::compute::take(flat_values.as_ref(), &indices, None)?;
328
329 let new_field = Arc::new(Field::new("item", elem_type, true));
330 let new_offsets =
331 arrow::buffer::OffsetBuffer::new(arrow::buffer::ScalarBuffer::from(offsets));
332 let new_list = ListArray::try_new(
333 new_field,
334 new_offsets,
335 filtered_values,
336 list_arr.nulls().cloned(),
337 )?;
338 Ok(ColumnarValue::Array(Arc::new(new_list)))
339 }
340}
341
342#[derive(Debug)]
348pub struct ArrayReduce {
349 signature: Signature,
350}
351
352impl ArrayReduce {
353 #[must_use]
355 pub fn new() -> Self {
356 Self {
357 signature: Signature::new(TypeSignature::Any(3), Volatility::Immutable),
358 }
359 }
360}
361
362impl Default for ArrayReduce {
363 fn default() -> Self {
364 Self::new()
365 }
366}
367impl PartialEq for ArrayReduce {
368 fn eq(&self, _: &Self) -> bool {
369 true
370 }
371}
372impl Eq for ArrayReduce {}
373impl Hash for ArrayReduce {
374 fn hash<H: Hasher>(&self, s: &mut H) {
375 "array_reduce".hash(s);
376 }
377}
378
379impl ScalarUDFImpl for ArrayReduce {
380 fn as_any(&self) -> &dyn std::any::Any {
381 self
382 }
383
384 fn name(&self) -> &'static str {
385 "array_reduce"
386 }
387 fn signature(&self) -> &Signature {
388 &self.signature
389 }
390
391 fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
392 Ok(arg_types.get(1).cloned().unwrap_or(DataType::Int64))
393 }
394
395 #[allow(clippy::cast_sign_loss)]
396 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
397 let expanded = expand_args(&args.args)?;
398 let list_arr = expanded[0]
399 .as_any()
400 .downcast_ref::<ListArray>()
401 .ok_or_else(|| {
402 datafusion_common::DataFusionError::Internal(
403 "array_reduce: first arg must be List".into(),
404 )
405 })?;
406
407 let init_arr = &expanded[1];
408 let lambda_str = scalar_string_value(&args.args[2])?;
409
410 let elem_type = list_arr.values().data_type().clone();
411 let acc_type = init_arr.data_type().clone();
412
413 let schema = Arc::new(Schema::new(vec![
414 Field::new("acc", acc_type, true),
415 Field::new("x", elem_type, true),
416 ]));
417
418 let mut result_builder: Vec<ArrayRef> = Vec::new();
419
420 for row in 0..list_arr.len() {
421 let start = list_arr.value_offsets()[row] as usize;
422 let end = list_arr.value_offsets()[row + 1] as usize;
423
424 let mut acc: ArrayRef = init_arr.slice(row, 1);
425
426 for i in start..end {
427 let x = list_arr.values().slice(i, 1);
428 let batch = arrow_array::RecordBatch::try_new(
429 Arc::clone(&schema),
430 vec![Arc::clone(&acc), x],
431 )?;
432 let result_col = eval_expr_on_batch(&lambda_str, &batch)?;
433 acc = result_col;
434 }
435
436 result_builder.push(acc);
437 }
438
439 if result_builder.is_empty() {
440 return Ok(ColumnarValue::Array(Arc::clone(init_arr)));
441 }
442
443 let refs: Vec<&dyn Array> = result_builder
444 .iter()
445 .map(std::convert::AsRef::as_ref)
446 .collect();
447 let result = arrow::compute::concat(&refs)?;
448 Ok(ColumnarValue::Array(result))
449 }
450}
451
452#[derive(Debug)]
458pub struct MapFilter {
459 signature: Signature,
460}
461
462impl MapFilter {
463 #[must_use]
465 pub fn new() -> Self {
466 Self {
467 signature: Signature::new(TypeSignature::Any(2), Volatility::Immutable),
468 }
469 }
470}
471
472impl Default for MapFilter {
473 fn default() -> Self {
474 Self::new()
475 }
476}
477impl PartialEq for MapFilter {
478 fn eq(&self, _: &Self) -> bool {
479 true
480 }
481}
482impl Eq for MapFilter {}
483impl Hash for MapFilter {
484 fn hash<H: Hasher>(&self, s: &mut H) {
485 "map_filter".hash(s);
486 }
487}
488
489impl ScalarUDFImpl for MapFilter {
490 fn as_any(&self) -> &dyn std::any::Any {
491 self
492 }
493
494 fn name(&self) -> &'static str {
495 "map_filter"
496 }
497 fn signature(&self) -> &Signature {
498 &self.signature
499 }
500
501 fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
502 Ok(arg_types[0].clone())
503 }
504
505 #[allow(
506 clippy::cast_sign_loss,
507 clippy::cast_possible_wrap,
508 clippy::cast_possible_truncation
509 )]
510 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
511 let expanded = expand_args(&args.args)?;
512 let map_arr = expanded[0]
513 .as_any()
514 .downcast_ref::<MapArray>()
515 .ok_or_else(|| {
516 datafusion_common::DataFusionError::Internal(
517 "map_filter: first arg must be Map".into(),
518 )
519 })?;
520
521 let lambda_str = scalar_string_value(&args.args[1])?;
522
523 let entries = map_arr.entries();
524 let key_col = entries.column(0);
525 let val_col = entries.column(1);
526
527 let key_type = key_col.data_type().clone();
528 let val_type = val_col.data_type().clone();
529
530 let schema = Arc::new(Schema::new(vec![
531 Field::new("k", key_type.clone(), true),
532 Field::new("v", val_type.clone(), true),
533 ]));
534 let batch = arrow_array::RecordBatch::try_new(
535 schema,
536 vec![Arc::clone(key_col), Arc::clone(val_col)],
537 )?;
538
539 let mask_arr = eval_expr_on_batch(&lambda_str, &batch)?;
540 let mask_bool = mask_arr
541 .as_any()
542 .downcast_ref::<BooleanArray>()
543 .ok_or_else(|| {
544 datafusion_common::DataFusionError::Internal(
545 "map_filter: lambda must return Boolean".into(),
546 )
547 })?;
548
549 let mut offsets = vec![0i32];
550 let mut keep_indices: Vec<usize> = Vec::new();
551
552 for row in 0..map_arr.len() {
553 let start = map_arr.value_offsets()[row] as usize;
554 let end = map_arr.value_offsets()[row + 1] as usize;
555
556 for i in start..end {
557 if !mask_bool.is_null(i) && mask_bool.value(i) {
558 keep_indices.push(i);
559 }
560 }
561 offsets.push(keep_indices.len() as i32);
562 }
563
564 let indices = arrow_array::UInt32Array::from(
565 keep_indices.iter().map(|&i| i as u32).collect::<Vec<_>>(),
566 );
567 let new_keys = arrow::compute::take(key_col.as_ref(), &indices, None)?;
568 let new_vals = arrow::compute::take(val_col.as_ref(), &indices, None)?;
569
570 let struct_fields = Fields::from(vec![
571 Field::new("key", key_type, false),
572 Field::new("value", val_type, true),
573 ]);
574 let new_entries = StructArray::try_new(struct_fields, vec![new_keys, new_vals], None)?;
575
576 let entries_field = Field::new("entries", new_entries.data_type().clone(), false);
577 let new_offsets =
578 arrow::buffer::OffsetBuffer::new(arrow::buffer::ScalarBuffer::from(offsets));
579 let new_map = MapArray::try_new(
580 Arc::new(entries_field),
581 new_offsets,
582 new_entries,
583 map_arr.nulls().cloned(),
584 false,
585 )?;
586 Ok(ColumnarValue::Array(Arc::new(new_map)))
587 }
588}
589
590#[derive(Debug)]
596pub struct MapTransformValues {
597 signature: Signature,
598}
599
600impl MapTransformValues {
601 #[must_use]
603 pub fn new() -> Self {
604 Self {
605 signature: Signature::new(TypeSignature::Any(2), Volatility::Immutable),
606 }
607 }
608}
609
610impl Default for MapTransformValues {
611 fn default() -> Self {
612 Self::new()
613 }
614}
615impl PartialEq for MapTransformValues {
616 fn eq(&self, _: &Self) -> bool {
617 true
618 }
619}
620impl Eq for MapTransformValues {}
621impl Hash for MapTransformValues {
622 fn hash<H: Hasher>(&self, s: &mut H) {
623 "map_transform_values".hash(s);
624 }
625}
626
627impl ScalarUDFImpl for MapTransformValues {
628 fn as_any(&self) -> &dyn std::any::Any {
629 self
630 }
631
632 fn name(&self) -> &'static str {
633 "map_transform_values"
634 }
635 fn signature(&self) -> &Signature {
636 &self.signature
637 }
638
639 fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
640 Ok(arg_types[0].clone())
641 }
642
643 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
644 let expanded = expand_args(&args.args)?;
645 let map_arr = expanded[0]
646 .as_any()
647 .downcast_ref::<MapArray>()
648 .ok_or_else(|| {
649 datafusion_common::DataFusionError::Internal(
650 "map_transform_values: first arg must be Map".into(),
651 )
652 })?;
653
654 let lambda_str = scalar_string_value(&args.args[1])?;
655
656 let entries = map_arr.entries();
657 let key_col = entries.column(0);
658 let val_col = entries.column(1);
659
660 let key_type = key_col.data_type().clone();
661
662 let schema = Arc::new(Schema::new(vec![
663 Field::new("k", key_type.clone(), true),
664 Field::new("v", val_col.data_type().clone(), true),
665 ]));
666 let batch = arrow_array::RecordBatch::try_new(
667 schema,
668 vec![Arc::clone(key_col), Arc::clone(val_col)],
669 )?;
670
671 let new_vals = eval_expr_on_batch(&lambda_str, &batch)?;
672
673 let struct_fields = Fields::from(vec![
674 Field::new("key", key_type, false),
675 Field::new("value", new_vals.data_type().clone(), true),
676 ]);
677 let new_entries =
678 StructArray::try_new(struct_fields, vec![Arc::clone(key_col), new_vals], None)?;
679
680 let entries_field = Field::new("entries", new_entries.data_type().clone(), false);
681 let new_map = MapArray::try_new(
682 Arc::new(entries_field),
683 map_arr.offsets().clone(),
684 new_entries,
685 map_arr.nulls().cloned(),
686 false,
687 )?;
688 Ok(ColumnarValue::Array(Arc::new(new_map)))
689 }
690}
691
692#[cfg(test)]
693mod tests {
694 use super::*;
695 use crate::datafusion::create_session_context;
696 use arrow_array::*;
697 use datafusion_common::config::ConfigOptions;
698
699 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
702 async fn test_array_transform_add_one() {
703 let values = Int64Array::from(vec![1, 2, 3, 4, 5, 6]);
704 let offsets =
705 arrow::buffer::OffsetBuffer::new(arrow::buffer::ScalarBuffer::from(vec![0i32, 3, 6]));
706 let list = ListArray::try_new(
707 Arc::new(Field::new("item", DataType::Int64, true)),
708 offsets,
709 Arc::new(values),
710 None,
711 )
712 .unwrap();
713
714 let udf = ArrayTransform::new();
715 let result = udf
716 .invoke_with_args(ScalarFunctionArgs {
717 args: vec![
718 ColumnarValue::Array(Arc::new(list)),
719 ColumnarValue::Scalar(datafusion_common::ScalarValue::Utf8(Some(
720 "x + 1".into(),
721 ))),
722 ],
723 number_rows: 0,
724 arg_fields: vec![],
725 return_field: Arc::new(Field::new(
726 "output",
727 DataType::List(Arc::new(Field::new("item", DataType::Int64, true))),
728 true,
729 )),
730 config_options: Arc::new(ConfigOptions::default()),
731 })
732 .unwrap();
733
734 if let ColumnarValue::Array(arr) = result {
735 let la = arr.as_any().downcast_ref::<ListArray>().unwrap();
736 assert_eq!(la.len(), 2);
737 let row0 = la.value(0);
738 let r0 = row0.as_any().downcast_ref::<Int64Array>().unwrap();
739 assert_eq!(r0.value(0), 2);
740 assert_eq!(r0.value(1), 3);
741 assert_eq!(r0.value(2), 4);
742 } else {
743 panic!("expected Array");
744 }
745 }
746
747 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
750 async fn test_array_filter_positive() {
751 let values = Int64Array::from(vec![-1, 2, -3, 4]);
752 let offsets =
753 arrow::buffer::OffsetBuffer::new(arrow::buffer::ScalarBuffer::from(vec![0i32, 4]));
754 let list = ListArray::try_new(
755 Arc::new(Field::new("item", DataType::Int64, true)),
756 offsets,
757 Arc::new(values),
758 None,
759 )
760 .unwrap();
761
762 let udf = ArrayFilter::new();
763 let result = udf
764 .invoke_with_args(ScalarFunctionArgs {
765 args: vec![
766 ColumnarValue::Array(Arc::new(list)),
767 ColumnarValue::Scalar(datafusion_common::ScalarValue::Utf8(Some(
768 "x > 0".into(),
769 ))),
770 ],
771 number_rows: 0,
772 arg_fields: vec![],
773 return_field: Arc::new(Field::new(
774 "output",
775 DataType::List(Arc::new(Field::new("item", DataType::Int64, true))),
776 true,
777 )),
778 config_options: Arc::new(ConfigOptions::default()),
779 })
780 .unwrap();
781
782 if let ColumnarValue::Array(arr) = result {
783 let la = arr.as_any().downcast_ref::<ListArray>().unwrap();
784 let row0 = la.value(0);
785 let r0 = row0.as_any().downcast_ref::<Int64Array>().unwrap();
786 assert_eq!(r0.len(), 2);
787 assert_eq!(r0.value(0), 2);
788 assert_eq!(r0.value(1), 4);
789 }
790 }
791
792 #[test]
795 fn test_register_lambda_functions() {
796 use datafusion::execution::FunctionRegistry;
797
798 let ctx = create_session_context();
799 register_lambda_functions(&ctx);
800 assert!(ctx.udf("array_transform").is_ok());
801 assert!(ctx.udf("array_filter").is_ok());
802 assert!(ctx.udf("array_reduce").is_ok());
803 assert!(ctx.udf("map_filter").is_ok());
804 assert!(ctx.udf("map_transform_values").is_ok());
805 }
806}