datafusion_functions_nested/
array_scale.rs1use crate::utils::make_scalar_function;
21use arrow::array::{Array, ArrayRef, Float64Array, GenericListArray, OffsetSizeTrait};
22use arrow::buffer::{NullBuffer, OffsetBuffer};
23use arrow::datatypes::{
24 DataType,
25 DataType::{FixedSizeList, LargeList, List, Null},
26 Field,
27};
28use datafusion_common::cast::{as_float64_array, as_generic_list_array};
29use datafusion_common::utils::{ListCoercion, coerced_type_with_base_type_only};
30use datafusion_common::{Result, internal_err, plan_err, utils::take_function_args};
31use datafusion_expr::{
32 ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature,
33 Volatility,
34};
35use datafusion_macros::user_doc;
36use std::sync::Arc;
37
38make_udf_expr_and_func!(
39 ArrayScale,
40 array_scale,
41 array scalar,
42 "scales each element of a numeric array by a scalar.",
43 array_scale_udf
44);
45
46#[user_doc(
47 doc_section(label = "Array Functions"),
48 description = "Returns a new array with each element of the input array multiplied by a scalar value, computed as `array[i] * scalar`. Returns NULL if the input row is NULL or the scalar is NULL. If a NULL element appears in the input array at position `i`, the result element at position `i` is NULL. Returns an empty array for an empty input array.",
49 syntax_example = "array_scale(array, scalar)",
50 sql_example = r#"```sql
51> select array_scale([1.0, 2.0, 3.0], 2.0);
52+----------------------------------+
53| array_scale(List([1.0,2.0,3.0]),Float64(2.0)) |
54+----------------------------------+
55| [2.0, 4.0, 6.0] |
56+----------------------------------+
57```"#,
58 argument(
59 name = "array",
60 description = "Array expression. Can be a constant, column, or function, and any combination of array operators."
61 ),
62 argument(
63 name = "scalar",
64 description = "Numeric scalar to multiply each element by. Can be a constant or column expression."
65 )
66)]
67#[derive(Debug, PartialEq, Eq, Hash)]
68pub struct ArrayScale {
69 signature: Signature,
70 aliases: Vec<String>,
71}
72
73impl Default for ArrayScale {
74 fn default() -> Self {
75 Self::new()
76 }
77}
78
79impl ArrayScale {
80 pub fn new() -> Self {
81 Self {
82 signature: Signature::user_defined(Volatility::Immutable),
83 aliases: vec!["list_scale".to_string()],
84 }
85 }
86}
87
88impl ScalarUDFImpl for ArrayScale {
89 fn name(&self) -> &str {
90 "array_scale"
91 }
92
93 fn signature(&self) -> &Signature {
94 &self.signature
95 }
96
97 fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
98 Ok(arg_types[0].clone())
100 }
101
102 fn coerce_types(&self, arg_types: &[DataType]) -> Result<Vec<DataType>> {
103 let [array_type, scalar_type] = take_function_args(self.name(), arg_types)?;
104 let coercion = Some(&ListCoercion::FixedSizedListToList);
105
106 if !matches!(
107 array_type,
108 Null | List(_) | LargeList(_) | FixedSizeList(..)
109 ) {
110 return plan_err!(
111 "{} first argument must be a list type, got {array_type}",
112 self.name()
113 );
114 }
115
116 if !scalar_type.is_numeric() && !matches!(scalar_type, Null) {
117 return plan_err!(
118 "{} second argument must be numeric, got {scalar_type}",
119 self.name()
120 );
121 }
122
123 let coerced_array = if matches!(array_type, Null) {
124 List(Arc::new(Field::new_list_field(DataType::Float64, true)))
125 } else {
126 coerced_type_with_base_type_only(array_type, &DataType::Float64, coercion)
127 };
128
129 Ok(vec![coerced_array, DataType::Float64])
130 }
131
132 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
133 make_scalar_function(array_scale_inner)(&args.args)
134 }
135
136 fn aliases(&self) -> &[String] {
137 &self.aliases
138 }
139
140 fn documentation(&self) -> Option<&Documentation> {
141 self.doc()
142 }
143}
144
145fn array_scale_inner(args: &[ArrayRef]) -> Result<ArrayRef> {
146 let [array, scalar] = take_function_args("array_scale", args)?;
147 match array.data_type() {
148 List(_) => general_array_scale::<i32>(array, scalar),
149 LargeList(_) => general_array_scale::<i64>(array, scalar),
150 arg_type => internal_err!(
151 "array_scale received unexpected type after coercion: {arg_type}"
152 ),
153 }
154}
155
156fn general_array_scale<O: OffsetSizeTrait>(
157 array: &ArrayRef,
158 scalar: &ArrayRef,
159) -> Result<ArrayRef> {
160 let list_array = as_generic_list_array::<O>(array)?;
161 let scalar_array = as_float64_array(scalar)?;
162
163 let values = as_float64_array(list_array.values())?;
164 let offsets = list_array.value_offsets();
165
166 let row_nulls = NullBuffer::union(list_array.nulls(), scalar_array.nulls());
171
172 let mut value_builder = Float64Array::builder(values.len());
173 let mut new_offsets = Vec::<O>::with_capacity(list_array.len() + 1);
174 new_offsets.push(O::zero());
175
176 for row in 0..list_array.len() {
177 if row_nulls.as_ref().is_some_and(|nb| nb.is_null(row)) {
178 new_offsets.push(new_offsets[row]);
179 continue;
180 }
181
182 let start = offsets[row].as_usize();
183 let end = offsets[row + 1].as_usize();
184 let len = end - start;
185 let scalar_val = scalar_array.value(row);
186
187 let slice = values.slice(start, len);
188
189 for i in 0..len {
191 if slice.is_null(i) {
192 value_builder.append_null();
193 } else {
194 value_builder.append_value(slice.value(i) * scalar_val);
195 }
196 }
197
198 new_offsets.push(new_offsets[row] + O::usize_as(len));
199 }
200
201 let values_array = Arc::new(value_builder.finish());
202
203 let field = match list_array.data_type() {
207 List(f) | LargeList(f) => Arc::clone(f),
208 other => {
209 return internal_err!("array_scale unexpected list type: {other}");
210 }
211 };
212
213 Ok(Arc::new(GenericListArray::<O>::try_new(
214 field,
215 OffsetBuffer::new(new_offsets.into()),
216 values_array,
217 row_nulls,
218 )?))
219}