Skip to main content

datafusion_functions_nested/
array_transform.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! [`datafusion_expr::HigherOrderUDF`] definitions for array_transform function.
19
20use 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        //TODO: should metadata be copied into the transformed array?
131
132        // lambda is the resulting field of executing the lambda body
133        // with the parameters returned in lambda_parameters
134        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        // by passing closures, lambda.evaluate can evaluate only those actually needed
164        let values_param = || Ok(Arc::clone(&list_values));
165
166        // call the transforming lambda
167        let transformed_values = lambda
168            .evaluate(&[&values_param], |arrays| {
169                // if any column got captured, we need to adjust it to the values arrays,
170                // duplicating values of list with multitple values and removing values of empty lists
171                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                // since we called list_values above which would return sliced values for
192                // a sliced list, we must adjust the offsets here as otherwise they would be invalid
193                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                // since we called list_values above which would return sliced values for
206                // a sliced list, we must adjust the offsets here as otherwise they would be invalid
207                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                // Have 0 here so if the expression is called on data that it will fail
253                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            // 0 here for one of the values behind null, so if it will be evaluated
277            // it will fail due to divide by 0
278            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}