datafusion_functions/string/
lower.rs1use arrow::datatypes::DataType;
19
20use crate::string::common::to_lower;
21use datafusion_common::Result;
22use datafusion_common::types::logical_string;
23use datafusion_expr::{
24 Coercion, ColumnarValue, Documentation, EncodingPreservation, ScalarFunctionArgs,
25 ScalarUDFImpl, Signature, TypeSignatureClass, Volatility,
26};
27use datafusion_macros::user_doc;
28
29#[user_doc(
30 doc_section(label = "String Functions"),
31 description = "Converts a string to lower-case.",
32 syntax_example = "lower(str)",
33 sql_example = r#"```sql
34> select lower('Ångström');
35+-------------------------+
36| lower(Utf8("Ångström")) |
37+-------------------------+
38| ångström |
39+-------------------------+
40```"#,
41 standard_argument(name = "str", prefix = "String"),
42 related_udf(name = "initcap"),
43 related_udf(name = "upper")
44)]
45#[derive(Debug, PartialEq, Eq, Hash)]
46pub struct LowerFunc {
47 signature: Signature,
48}
49
50impl Default for LowerFunc {
51 fn default() -> Self {
52 Self::new()
53 }
54}
55
56impl LowerFunc {
57 pub fn new() -> Self {
58 Self {
59 signature: Signature::coercible(
60 vec![
61 Coercion::new_exact(TypeSignatureClass::Native(logical_string()))
62 .with_encoding_preservation(EncodingPreservation::dictionary()),
63 ],
64 Volatility::Immutable,
65 ),
66 }
67 }
68}
69
70impl ScalarUDFImpl for LowerFunc {
71 fn name(&self) -> &str {
72 "lower"
73 }
74
75 fn signature(&self) -> &Signature {
76 &self.signature
77 }
78
79 fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
80 Ok(arg_types[0].clone())
81 }
82
83 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
84 to_lower(&args.args, "lower")
85 }
86
87 fn documentation(&self) -> Option<&Documentation> {
88 self.doc()
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use arrow::array::{Array, ArrayRef, StringArray, StringViewArray};
96 use arrow::datatypes::Field;
97 use datafusion_common::config::ConfigOptions;
98 use std::sync::Arc;
99
100 fn invoke_lower(input: ArrayRef) -> Result<ArrayRef> {
101 let func = LowerFunc::new();
102 let data_type = input.data_type().clone();
103 let args = ScalarFunctionArgs {
104 number_rows: input.len(),
105 args: vec![ColumnarValue::Array(input)],
106 arg_fields: vec![Field::new("a", data_type.clone(), true).into()],
107 return_field: Field::new("f", data_type, true).into(),
108 config_options: Arc::new(ConfigOptions::default()),
109 };
110 match func.invoke_with_args(args)? {
111 ColumnarValue::Array(r) => Ok(r),
112 _ => unreachable!("lower"),
113 }
114 }
115
116 fn to_lower(input: ArrayRef, expected: ArrayRef) -> Result<()> {
117 let result = invoke_lower(input)?;
118 assert_eq!(&expected, &result);
119 Ok(())
120 }
121
122 #[test]
123 fn lower_maybe_optimization() -> Result<()> {
124 let input = Arc::new(StringArray::from(vec![
125 Some("农历新年"),
126 None,
127 Some("DATAFUSION"),
128 Some("0123456789"),
129 Some(""),
130 ])) as ArrayRef;
131
132 let expected = Arc::new(StringArray::from(vec![
133 Some("农历新年"),
134 None,
135 Some("datafusion"),
136 Some("0123456789"),
137 Some(""),
138 ])) as ArrayRef;
139
140 to_lower(input, expected)
141 }
142
143 #[test]
144 fn lower_full_optimization() -> Result<()> {
145 let input = Arc::new(StringArray::from(vec![
146 Some("ARROW"),
147 None,
148 Some("DATAFUSION"),
149 Some("0123456789"),
150 Some(""),
151 ])) as ArrayRef;
152
153 let expected = Arc::new(StringArray::from(vec![
154 Some("arrow"),
155 None,
156 Some("datafusion"),
157 Some("0123456789"),
158 Some(""),
159 ])) as ArrayRef;
160
161 to_lower(input, expected)
162 }
163
164 #[test]
165 fn lower_partial_optimization() -> Result<()> {
166 let input = Arc::new(StringArray::from(vec![
167 Some("ARROW"),
168 None,
169 Some("DATAFUSION"),
170 Some("@_"),
171 Some("0123456789"),
172 Some(""),
173 Some("\t\n"),
174 Some("ὈΔΥΣΣΕΎΣ"),
175 Some("TSCHÜSS"),
176 Some("Ⱦ"), Some("农历新年"),
178 ])) as ArrayRef;
179
180 let expected = Arc::new(StringArray::from(vec![
181 Some("arrow"),
182 None,
183 Some("datafusion"),
184 Some("@_"),
185 Some("0123456789"),
186 Some(""),
187 Some("\t\n"),
188 Some("ὀδυσσεύς"),
189 Some("tschüss"),
190 Some("ⱦ"),
191 Some("农历新年"),
192 ])) as ArrayRef;
193
194 to_lower(input, expected)
195 }
196
197 #[test]
198 fn lower_utf8view() -> Result<()> {
199 let input = Arc::new(StringViewArray::from(vec![
200 Some("ARROW"),
201 None,
202 Some("TSCHÜSS"),
203 ])) as ArrayRef;
204
205 let expected = Arc::new(StringViewArray::from(vec![
206 Some("arrow"),
207 None,
208 Some("tschüss"),
209 ])) as ArrayRef;
210
211 to_lower(input, expected)
212 }
213
214 #[test]
215 fn lower_ascii_utf8view() -> Result<()> {
216 let input = Arc::new(StringViewArray::from(vec![
219 Some("ARROW"), None,
221 Some("HELLO WORLD 123"), Some(""),
223 Some("0123456789"), Some("DATAFUSION IS COOL"), ])) as ArrayRef;
226
227 let expected = Arc::new(StringViewArray::from(vec![
228 Some("arrow"),
229 None,
230 Some("hello world 123"),
231 Some(""),
232 Some("0123456789"),
233 Some("datafusion is cool"),
234 ])) as ArrayRef;
235
236 to_lower(input, expected)
237 }
238
239 #[test]
240 fn lower_sliced_ascii_utf8view() -> Result<()> {
241 let parent = Arc::new(StringViewArray::from(vec![
246 Some("农历新年LONG ENOUGH FOR BUFFER"),
247 Some("HELLO WORLD 123"),
248 Some("DATAFUSION ROCKS!"),
249 Some("ZZZZZZZZZZZZZZZZ"),
250 ])) as ArrayRef;
251 let sliced = parent.slice(1, 2);
252 let result = invoke_lower(sliced)?;
253 let result_sv = result.as_any().downcast_ref::<StringViewArray>().unwrap();
254
255 let expected = StringViewArray::from(vec![
256 Some("hello world 123"),
257 Some("datafusion rocks!"),
258 ]);
259 assert_eq!(result_sv, &expected);
260 assert_eq!(result_sv.data_buffers().len(), 1);
264 assert_eq!(result_sv.data_buffers()[0].len(), 32);
265 Ok(())
266 }
267
268 #[test]
269 fn lower_utf8view_inline_only_no_buffers() -> Result<()> {
270 let input = Arc::new(StringViewArray::from(vec![
273 Some("HELLO"),
274 None,
275 Some(""),
276 Some("0123456789ab"), ])) as ArrayRef;
278 let result = invoke_lower(input)?;
279 let result_sv = result.as_any().downcast_ref::<StringViewArray>().unwrap();
280
281 let expected = StringViewArray::from(vec![
282 Some("hello"),
283 None,
284 Some(""),
285 Some("0123456789ab"),
286 ]);
287 assert_eq!(result_sv, &expected);
288 assert_eq!(
289 result_sv.data_buffers().len(),
290 0,
291 "inline-only Utf8View should produce no data buffers"
292 );
293 Ok(())
294 }
295
296 #[test]
297 fn lower_utf8view_long_packs_tight() -> Result<()> {
298 let input = Arc::new(StringViewArray::from(vec![
302 Some("HELLO WORLD 123"), Some("ABC"), None,
305 Some("DATAFUSION ROCKS!"), Some("ANOTHER LONG STRING"), ])) as ArrayRef;
308 let result = invoke_lower(input)?;
309 let result_sv = result.as_any().downcast_ref::<StringViewArray>().unwrap();
310
311 let expected = StringViewArray::from(vec![
312 Some("hello world 123"),
313 Some("abc"),
314 None,
315 Some("datafusion rocks!"),
316 Some("another long string"),
317 ]);
318 assert_eq!(result_sv, &expected);
319 assert_eq!(result_sv.data_buffers().len(), 1);
320 assert_eq!(result_sv.data_buffers()[0].len(), 15 + 17 + 19);
321 Ok(())
322 }
323
324 #[test]
325 fn lower_utf8view_splits_into_multiple_buffers() -> Result<()> {
326 const STR_LEN: usize = 500;
331 const N: usize = 40; let value = "X".repeat(STR_LEN);
333 let inputs: Vec<Option<String>> = (0..N).map(|_| Some(value.clone())).collect();
334 let input = Arc::new(StringViewArray::from(inputs.clone())) as ArrayRef;
335 let result = invoke_lower(input)?;
336 let result_sv = result.as_any().downcast_ref::<StringViewArray>().unwrap();
337
338 let expected_value = "x".repeat(STR_LEN);
339 let expected: Vec<Option<&str>> =
340 (0..N).map(|_| Some(expected_value.as_str())).collect();
341 assert_eq!(result_sv, &StringViewArray::from(expected));
342 assert!(
343 result_sv.data_buffers().len() >= 2,
344 "expected the output to span more than one data buffer, got {}",
345 result_sv.data_buffers().len()
346 );
347 let total: usize = result_sv.data_buffers().iter().map(|b| b.len()).sum();
350 assert_eq!(total, N * STR_LEN);
351 Ok(())
352 }
353
354 #[test]
355 fn lower_sliced_utf8() -> Result<()> {
356 let parent = Arc::new(StringArray::from(vec![
357 Some("AAAAAAAA"),
358 Some("HELLO"),
359 Some("WORLD"),
360 Some(""),
361 Some("ZZZZZZZZ"),
362 ])) as ArrayRef;
363 let sliced = parent.slice(1, 3);
364 let result = invoke_lower(sliced)?;
365 let result_sa = result.as_any().downcast_ref::<StringArray>().unwrap();
366
367 let expected = StringArray::from(vec![Some("hello"), Some("world"), Some("")]);
368 assert_eq!(result_sa, &expected);
369 assert_eq!(result_sa.value_data().len(), 10);
372 Ok(())
373 }
374}