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