1use arrow::{
21 array::{Array, ArrayRef, AsArray, LargeListArray, ListArray},
22 compute::take_arrays,
23 datatypes::{DataType, Field, FieldRef},
24};
25use datafusion_common::{
26 Result, exec_err, plan_err,
27 utils::{adjust_offsets_for_slice, list_values_row_number, take_function_args},
28};
29use datafusion_expr::{
30 ColumnarValue, Documentation, HigherOrderFunctionArgs, HigherOrderReturnFieldArgs,
31 HigherOrderSignature, HigherOrderUDFImpl, LambdaParametersProgress, ValueOrLambda,
32 Volatility,
33};
34use datafusion_macros::user_doc;
35use std::sync::Arc;
36
37use crate::lambda_utils::{
38 ListValuesResult, coerce_single_list_arg, extract_list_values,
39 single_list_lambda_parameters,
40};
41
42make_higher_order_function_expr_and_func!(
43 ArrayTransform,
44 array_transform,
45 array lambda,
46 "transforms the values of an array",
47 array_transform_higher_order_function
48);
49
50#[user_doc(
51 doc_section(label = "Array Functions"),
52 description = "transforms the values of an array",
53 syntax_example = "array_transform(array, lambda)",
54 sql_example = r#"```sql
55> select array_transform([1, 2, 3, 4, 5], x -> x*2);
56+-------------------------------------------+
57| array_transform([1, 2, 3, 4, 5], x -> x*2) |
58+-------------------------------------------+
59| [2, 4, 6, 8, 10] |
60+-------------------------------------------+
61```"#,
62 argument(
63 name = "array",
64 description = "Array expression. Can be a constant, column, or function, and any combination of array operators."
65 ),
66 argument(
67 name = "lambda",
68 description = "The lambda function used to transform each value of the array."
69 )
70)]
71#[derive(Debug, PartialEq, Eq, Hash)]
72pub struct ArrayTransform {
73 signature: HigherOrderSignature,
74 aliases: Vec<String>,
75}
76
77impl Default for ArrayTransform {
78 fn default() -> Self {
79 Self::new()
80 }
81}
82
83impl ArrayTransform {
84 pub fn new() -> Self {
85 Self {
86 signature: HigherOrderSignature::exact(
87 vec![ValueOrLambda::Value(()), ValueOrLambda::Lambda(())],
88 Volatility::Immutable,
89 ),
90 aliases: vec![String::from("list_transform")],
91 }
92 }
93}
94
95impl HigherOrderUDFImpl for ArrayTransform {
96 fn name(&self) -> &str {
97 "array_transform"
98 }
99
100 fn aliases(&self) -> &[String] {
101 &self.aliases
102 }
103
104 fn signature(&self) -> &HigherOrderSignature {
105 &self.signature
106 }
107
108 fn coerce_value_types(&self, arg_types: &[DataType]) -> Result<Vec<DataType>> {
109 coerce_single_list_arg(self.name(), arg_types)
110 }
111
112 fn lambda_parameters(
113 &self,
114 _step: usize,
115 fields: &[ValueOrLambda<FieldRef, Option<FieldRef>>],
116 ) -> Result<LambdaParametersProgress> {
117 single_list_lambda_parameters(self.name(), fields)
118 }
119
120 fn return_field_from_args(
121 &self,
122 args: HigherOrderReturnFieldArgs,
123 ) -> Result<Arc<Field>> {
124 let [ValueOrLambda::Value(list), ValueOrLambda::Lambda(lambda)] =
125 take_function_args(self.name(), args.arg_fields)?
126 else {
127 return plan_err!("{} expects a value followed by a lambda", self.name());
128 };
129
130 let field = Arc::new(Field::new(
135 Field::LIST_FIELD_DEFAULT_NAME,
136 lambda.data_type().clone(),
137 lambda.is_nullable(),
138 ));
139
140 let return_type = match list.data_type() {
141 DataType::List(_) => DataType::List(field),
142 DataType::LargeList(_) => DataType::LargeList(field),
143 other => plan_err!("expected list, got {other}")?,
144 };
145
146 Ok(Arc::new(Field::new("", return_type, list.is_nullable())))
147 }
148
149 fn invoke_with_args(&self, args: HigherOrderFunctionArgs) -> Result<ColumnarValue> {
150 let [list, lambda] = take_function_args(self.name(), &args.args)?;
151 let (ValueOrLambda::Value(list), ValueOrLambda::Lambda(lambda)) = (list, lambda)
152 else {
153 return plan_err!("{} expects a value followed by a lambda", self.name());
154 };
155
156 let list_array = list.to_array(args.number_rows)?;
157
158 let list_values = match extract_list_values(&list_array, args.return_type())? {
159 ListValuesResult::EarlyReturn(v) => return Ok(v),
160 ListValuesResult::Values(v) => v,
161 };
162
163 let values_param = || Ok(Arc::clone(&list_values));
165
166 let transformed_values = lambda
168 .evaluate(&[&values_param], |arrays| {
169 let indices = list_values_row_number(&list_array)?;
172 Ok(take_arrays(arrays, &indices, None)?)
173 })?
174 .into_array(list_values.len())?;
175
176 let field = match args.return_field.data_type() {
177 DataType::List(field) | DataType::LargeList(field) => Arc::clone(field),
178 _ => {
179 return exec_err!(
180 "{} expected ScalarFunctionArgs.return_field to be a list, got {}",
181 self.name(),
182 args.return_field
183 );
184 }
185 };
186
187 let transformed_list = match list_array.data_type() {
188 DataType::List(_) => {
189 let list = list_array.as_list();
190
191 let adjusted_offsets = adjust_offsets_for_slice(list);
194
195 Arc::new(ListArray::new(
196 field,
197 adjusted_offsets,
198 transformed_values,
199 list.nulls().cloned(),
200 )) as ArrayRef
201 }
202 DataType::LargeList(_) => {
203 let large_list = list_array.as_list();
204
205 let adjusted_offsets = adjust_offsets_for_slice(large_list);
208
209 Arc::new(LargeListArray::new(
210 field,
211 adjusted_offsets,
212 transformed_values,
213 large_list.nulls().cloned(),
214 ))
215 }
216 other => exec_err!("expected list, got {other}")?,
217 };
218
219 Ok(ColumnarValue::Array(transformed_list))
220 }
221
222 fn documentation(&self) -> Option<&Documentation> {
223 self.doc()
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use arrow::{
230 array::{Array, AsArray},
231 buffer::{NullBuffer, OffsetBuffer},
232 };
233
234 use crate::array_transform::array_transform_higher_order_function;
235 use crate::lambda_utils::test_utils::{create_i32_list, eval_hof_on_i32_list, v};
236 use datafusion_expr::lit;
237
238 fn divide_100_by(
239 list: impl Array + Clone + 'static,
240 ) -> datafusion_common::Result<arrow::array::ArrayRef> {
241 eval_hof_on_i32_list(
242 array_transform_higher_order_function(),
243 list,
244 lit(100i32) / v(),
245 )
246 }
247
248 #[test]
249 fn transform_on_sliced_list_should_not_evaluate_on_unreachable_values() {
250 let list = create_i32_list(
251 vec![
252 0, 4, 100, 25, 20, 5, 2, 1, 10,
254 ],
255 OffsetBuffer::<i32>::from_lengths(vec![1, 3, 4, 1]),
256 None,
257 )
258 .slice(1, 3);
259
260 let res = divide_100_by(list).unwrap();
261
262 let actual_list = res.as_list::<i32>();
263
264 let expected_list = create_i32_list(
265 vec![25, 1, 4, 5, 20, 50, 100, 10],
266 OffsetBuffer::<i32>::from_lengths(vec![3, 4, 1]),
267 None,
268 );
269
270 assert_eq!(actual_list, &expected_list);
271 }
272
273 #[test]
274 fn transform_function_should_not_be_evaluated_on_values_underlying_null() {
275 let list = create_i32_list(
276 vec![100, 20, 10, 0, 1, 2, 0, 1, 50],
279 OffsetBuffer::<i32>::from_lengths(vec![3, 4, 2]),
280 Some(NullBuffer::from(vec![true, false, true])),
281 );
282
283 let res = divide_100_by(list).unwrap();
284
285 let actual_list = res.as_list::<i32>();
286
287 let expected_list = create_i32_list(
288 vec![1, 5, 10, 100, 2],
289 OffsetBuffer::<i32>::from_lengths(vec![3, 0, 2]),
290 Some(NullBuffer::from(vec![true, false, true])),
291 );
292
293 assert_eq!(actual_list.data_type(), expected_list.data_type());
294 assert_eq!(actual_list, &expected_list);
295 }
296}