datafusion_functions/unicode/
initcap.rs1use std::sync::Arc;
19
20use arrow::array::{Array, ArrayRef, AsArray, GenericStringArray, OffsetSizeTrait};
21use arrow::buffer::Buffer;
22use arrow::datatypes::DataType;
23
24use crate::strings::{GenericStringArrayBuilder, StringViewArrayBuilder};
25use datafusion_common::cast::{as_generic_string_array, as_string_view_array};
26use datafusion_common::types::logical_string;
27use datafusion_common::{Result, ScalarValue, exec_err};
28use datafusion_expr::{
29 Coercion, ColumnarValue, Documentation, EncodingPreservation, ScalarFunctionArgs,
30 ScalarUDFImpl, Signature, TypeSignatureClass, Volatility,
31};
32use datafusion_macros::user_doc;
33
34#[user_doc(
35 doc_section(label = "String Functions"),
36 description = "Capitalizes the first character in each word in the input string. \
37 Words are delimited by non-alphanumeric characters.",
38 syntax_example = "initcap(str)",
39 sql_example = r#"```sql
40> select initcap('apache datafusion');
41+------------------------------------+
42| initcap(Utf8("apache datafusion")) |
43+------------------------------------+
44| Apache Datafusion |
45+------------------------------------+
46```"#,
47 standard_argument(name = "str", prefix = "String"),
48 related_udf(name = "lower"),
49 related_udf(name = "upper")
50)]
51#[derive(Debug, PartialEq, Eq, Hash)]
52pub struct InitcapFunc {
53 signature: Signature,
54}
55
56impl Default for InitcapFunc {
57 fn default() -> Self {
58 InitcapFunc::new()
59 }
60}
61
62impl InitcapFunc {
63 pub fn new() -> Self {
64 Self {
65 signature: Signature::coercible(
66 vec![
67 Coercion::new_exact(TypeSignatureClass::Native(logical_string()))
68 .with_encoding_preservation(EncodingPreservation::dictionary()),
69 ],
70 Volatility::Immutable,
71 ),
72 }
73 }
74}
75
76impl ScalarUDFImpl for InitcapFunc {
77 fn name(&self) -> &str {
78 "initcap"
79 }
80
81 fn signature(&self) -> &Signature {
82 &self.signature
83 }
84
85 fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
86 Ok(arg_types[0].clone())
87 }
88
89 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
90 match &args.args[0] {
91 ColumnarValue::Scalar(scalar) => {
92 Ok(ColumnarValue::Scalar(initcap_scalar(scalar)?))
93 }
94 ColumnarValue::Array(array) => {
95 Ok(ColumnarValue::Array(initcap_array(array)?))
96 }
97 }
98 }
99
100 fn documentation(&self) -> Option<&Documentation> {
101 self.doc()
102 }
103}
104
105fn initcap_scalar(scalar: &ScalarValue) -> Result<ScalarValue> {
106 match scalar {
107 ScalarValue::Utf8(None)
108 | ScalarValue::LargeUtf8(None)
109 | ScalarValue::Utf8View(None) => Ok(scalar.clone()),
110 ScalarValue::Utf8(Some(s)) => {
111 let mut result = String::new();
112 initcap_string(s, &mut result);
113 Ok(ScalarValue::Utf8(Some(result)))
114 }
115 ScalarValue::LargeUtf8(Some(s)) => {
116 let mut result = String::new();
117 initcap_string(s, &mut result);
118 Ok(ScalarValue::LargeUtf8(Some(result)))
119 }
120 ScalarValue::Utf8View(Some(s)) => {
121 let mut result = String::new();
122 initcap_string(s, &mut result);
123 Ok(ScalarValue::Utf8View(Some(result)))
124 }
125 ScalarValue::Dictionary(key_type, value) => Ok(ScalarValue::Dictionary(
126 key_type.clone(),
127 Box::new(initcap_scalar(value)?),
128 )),
129 other => {
130 exec_err!(
131 "Unsupported data type {:?} for function `initcap`",
132 other.data_type()
133 )
134 }
135 }
136}
137
138fn initcap_array(array: &ArrayRef) -> Result<ArrayRef> {
139 match array.data_type() {
140 DataType::Utf8 => initcap::<i32>(&[Arc::clone(array)]),
141 DataType::LargeUtf8 => initcap::<i64>(&[Arc::clone(array)]),
142 DataType::Utf8View => initcap_utf8view(&[Arc::clone(array)]),
143 DataType::Dictionary(_, _) => {
144 let dictionary = array.as_any_dictionary();
145 let converted = initcap_array(dictionary.values())?;
146 Ok(dictionary.with_values(converted))
147 }
148 other => {
149 exec_err!("Unsupported data type {other:?} for function `initcap`")
150 }
151 }
152}
153
154fn initcap<T: OffsetSizeTrait>(args: &[ArrayRef]) -> Result<ArrayRef> {
163 let string_array = as_generic_string_array::<T>(&args[0])?;
164
165 if string_array.is_ascii() {
166 return Ok(initcap_ascii_array(string_array));
167 }
168
169 let len = string_array.len();
170 let mut builder = GenericStringArrayBuilder::<T>::with_capacity(
171 len,
172 string_array.value_data().len(),
173 );
174
175 let mut container = String::new();
176 let nulls = string_array.nulls().cloned();
177 if let Some(ref n) = nulls {
178 for i in 0..len {
179 if n.is_null(i) {
180 builder.try_append_placeholder()?;
181 } else {
182 let s = unsafe { string_array.value_unchecked(i) };
184 initcap_string(s, &mut container);
185 builder.try_append_value(&container)?;
186 }
187 }
188 } else {
189 for i in 0..len {
190 let s = unsafe { string_array.value_unchecked(i) };
192 initcap_string(s, &mut container);
193 builder.try_append_value(&container)?;
194 }
195 }
196
197 Ok(Arc::new(builder.finish(nulls)?) as ArrayRef)
198}
199
200fn initcap_ascii_array<T: OffsetSizeTrait>(
203 string_array: &GenericStringArray<T>,
204) -> ArrayRef {
205 let offsets = string_array.offsets();
206 let src = string_array.value_data();
207 let first_offset = offsets.first().unwrap().as_usize();
208 let last_offset = offsets.last().unwrap().as_usize();
209
210 let mut out = Vec::with_capacity(last_offset - first_offset);
213
214 for window in offsets.windows(2) {
215 let start = window[0].as_usize();
216 let end = window[1].as_usize();
217
218 let mut prev_is_alnum = false;
219 for &b in &src[start..end] {
220 let converted = if prev_is_alnum {
221 b.to_ascii_lowercase()
222 } else {
223 b.to_ascii_uppercase()
224 };
225 out.push(converted);
226 prev_is_alnum = b.is_ascii_alphanumeric();
227 }
228 }
229
230 let values = Buffer::from_vec(out);
231
232 let out_offsets = offsets.clone().subtract(offsets[0]);
235
236 Arc::new(unsafe {
241 GenericStringArray::<T>::new_unchecked(
242 out_offsets,
243 values,
244 string_array.nulls().cloned(),
245 )
246 })
247}
248
249fn initcap_utf8view(args: &[ArrayRef]) -> Result<ArrayRef> {
250 let string_view_array = as_string_view_array(&args[0])?;
251 let len = string_view_array.len();
252 let mut builder = StringViewArrayBuilder::with_capacity(len);
253 let mut container = String::new();
254
255 let nulls = string_view_array.nulls().cloned();
256 if let Some(ref n) = nulls {
257 for i in 0..len {
258 if n.is_null(i) {
259 builder.append_placeholder();
260 } else {
261 let s = unsafe { string_view_array.value_unchecked(i) };
263 initcap_string(s, &mut container);
264 builder.append_value(&container);
265 }
266 }
267 } else {
268 for i in 0..len {
269 let s = unsafe { string_view_array.value_unchecked(i) };
271 initcap_string(s, &mut container);
272 builder.append_value(&container);
273 }
274 }
275
276 Ok(Arc::new(builder.finish(nulls)?) as ArrayRef)
277}
278
279fn initcap_string(input: &str, container: &mut String) {
280 container.clear();
281 let mut prev_is_alphanumeric = false;
282
283 if input.is_ascii() {
284 container.reserve(input.len());
285 let out = unsafe { container.as_mut_vec() };
287 for &b in input.as_bytes() {
288 if prev_is_alphanumeric {
289 out.push(b.to_ascii_lowercase());
290 } else {
291 out.push(b.to_ascii_uppercase());
292 }
293 prev_is_alphanumeric = b.is_ascii_alphanumeric();
294 }
295 } else {
296 for c in input.chars() {
297 if prev_is_alphanumeric {
298 container.extend(c.to_lowercase());
299 } else {
300 container.extend(c.to_uppercase());
301 }
302 prev_is_alphanumeric = c.is_alphanumeric();
303 }
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use crate::unicode::initcap::InitcapFunc;
310 use crate::utils::test::test_function;
311 use arrow::array::{Array, ArrayRef, LargeStringArray, StringArray, StringViewArray};
312 use arrow::datatypes::DataType::{Utf8, Utf8View};
313 use datafusion_common::{Result, ScalarValue};
314 use datafusion_expr::{ColumnarValue, ScalarUDFImpl};
315 use std::sync::Arc;
316
317 #[test]
318 fn test_functions() -> Result<()> {
319 test_function!(
320 InitcapFunc::new(),
321 vec![ColumnarValue::Scalar(ScalarValue::from("hi THOMAS"))],
322 Ok(Some("Hi Thomas")),
323 &str,
324 Utf8,
325 StringArray
326 );
327 test_function!(
328 InitcapFunc::new(),
329 vec![ColumnarValue::Scalar(ScalarValue::Utf8(Some(
330 "êM ả ñAnDÚ ÁrBOL ОлЕГ ИвАНОВИч ÍslENsku ÞjóðaRiNNaR εΛλΗΝΙκΉ"
331 .to_string()
332 )))],
333 Ok(Some(
334 "Êm Ả Ñandú Árbol Олег Иванович Íslensku Þjóðarinnar Ελληνική"
335 )),
336 &str,
337 Utf8,
338 StringArray
339 );
340 test_function!(
341 InitcapFunc::new(),
342 vec![ColumnarValue::Scalar(ScalarValue::from(""))],
343 Ok(Some("")),
344 &str,
345 Utf8,
346 StringArray
347 );
348 test_function!(
349 InitcapFunc::new(),
350 vec![ColumnarValue::Scalar(ScalarValue::from(""))],
351 Ok(Some("")),
352 &str,
353 Utf8,
354 StringArray
355 );
356 test_function!(
357 InitcapFunc::new(),
358 vec![ColumnarValue::Scalar(ScalarValue::Utf8(None))],
359 Ok(None),
360 &str,
361 Utf8,
362 StringArray
363 );
364
365 test_function!(
366 InitcapFunc::new(),
367 vec![ColumnarValue::Scalar(ScalarValue::Utf8View(Some(
368 "hi THOMAS".to_string()
369 )))],
370 Ok(Some("Hi Thomas")),
371 &str,
372 Utf8View,
373 StringViewArray
374 );
375 test_function!(
376 InitcapFunc::new(),
377 vec![ColumnarValue::Scalar(ScalarValue::Utf8View(Some(
378 "hi THOMAS wIth M0re ThAN 12 ChaRs".to_string()
379 )))],
380 Ok(Some("Hi Thomas With M0re Than 12 Chars")),
381 &str,
382 Utf8View,
383 StringViewArray
384 );
385 test_function!(
386 InitcapFunc::new(),
387 vec![ColumnarValue::Scalar(ScalarValue::Utf8View(Some(
388 "đẸp đẼ êM ả ñAnDÚ ÁrBOL ОлЕГ ИвАНОВИч ÍslENsku ÞjóðaRiNNaR εΛλΗΝΙκΉ"
389 .to_string()
390 )))],
391 Ok(Some(
392 "Đẹp Đẽ Êm Ả Ñandú Árbol Олег Иванович Íslensku Þjóðarinnar Ελληνική"
393 )),
394 &str,
395 Utf8View,
396 StringViewArray
397 );
398 test_function!(
399 InitcapFunc::new(),
400 vec![ColumnarValue::Scalar(ScalarValue::Utf8View(Some(
401 "".to_string()
402 )))],
403 Ok(Some("")),
404 &str,
405 Utf8View,
406 StringViewArray
407 );
408 test_function!(
409 InitcapFunc::new(),
410 vec![ColumnarValue::Scalar(ScalarValue::Utf8View(None))],
411 Ok(None),
412 &str,
413 Utf8View,
414 StringViewArray
415 );
416
417 Ok(())
418 }
419
420 #[test]
421 fn test_initcap_ascii_array() -> Result<()> {
422 let array = StringArray::from(vec![
423 Some("hello world"),
424 None,
425 Some("foo-bar_baz/baX"),
426 Some(""),
427 Some("123 abc 456DEF"),
428 Some("ALL CAPS"),
429 Some("already correct"),
430 ]);
431 let args: Vec<ArrayRef> = vec![Arc::new(array)];
432 let result = super::initcap::<i32>(&args)?;
433 let result = result.as_any().downcast_ref::<StringArray>().unwrap();
434
435 assert_eq!(result.len(), 7);
436 assert_eq!(result.value(0), "Hello World");
437 assert!(result.is_null(1));
438 assert_eq!(result.value(2), "Foo-Bar_Baz/Bax");
439 assert_eq!(result.value(3), "");
440 assert_eq!(result.value(4), "123 Abc 456def");
441 assert_eq!(result.value(5), "All Caps");
442 assert_eq!(result.value(6), "Already Correct");
443 Ok(())
444 }
445
446 #[test]
447 fn test_initcap_ascii_large_array() -> Result<()> {
448 let array = LargeStringArray::from(vec![
449 Some("hello world"),
450 None,
451 Some("foo-bar_baz/baX"),
452 Some(""),
453 Some("123 abc 456DEF"),
454 Some("ALL CAPS"),
455 Some("already correct"),
456 ]);
457 let args: Vec<ArrayRef> = vec![Arc::new(array)];
458 let result = super::initcap::<i64>(&args)?;
459 let result = result.as_any().downcast_ref::<LargeStringArray>().unwrap();
460
461 assert_eq!(result.len(), 7);
462 assert_eq!(result.value(0), "Hello World");
463 assert!(result.is_null(1));
464 assert_eq!(result.value(2), "Foo-Bar_Baz/Bax");
465 assert_eq!(result.value(3), "");
466 assert_eq!(result.value(4), "123 Abc 456def");
467 assert_eq!(result.value(5), "All Caps");
468 assert_eq!(result.value(6), "Already Correct");
469 Ok(())
470 }
471
472 #[test]
474 fn test_initcap_sliced_ascii_array() -> Result<()> {
475 let array = StringArray::from(vec![
476 Some("hello world"),
477 Some("foo bar"),
478 Some("baz qux"),
479 ]);
480 let sliced = array.slice(1, 2);
484 let args: Vec<ArrayRef> = vec![Arc::new(sliced)];
485 let result = super::initcap::<i32>(&args)?;
486 let result = result.as_any().downcast_ref::<StringArray>().unwrap();
487
488 assert_eq!(result.len(), 2);
489 assert_eq!(result.value(0), "Foo Bar");
490 assert_eq!(result.value(1), "Baz Qux");
491
492 assert_eq!(*result.offsets().first().unwrap(), 0);
494 assert_eq!(
495 result.value_data().len(),
496 *result.offsets().last().unwrap() as usize
497 );
498 Ok(())
499 }
500
501 #[test]
503 fn test_initcap_sliced_ascii_large_array() -> Result<()> {
504 let array = LargeStringArray::from(vec![
505 Some("hello world"),
506 Some("foo bar"),
507 Some("baz qux"),
508 ]);
509 let sliced = array.slice(1, 2);
513 let args: Vec<ArrayRef> = vec![Arc::new(sliced)];
514 let result = super::initcap::<i64>(&args)?;
515 let result = result.as_any().downcast_ref::<LargeStringArray>().unwrap();
516
517 assert_eq!(result.len(), 2);
518 assert_eq!(result.value(0), "Foo Bar");
519 assert_eq!(result.value(1), "Baz Qux");
520
521 assert_eq!(*result.offsets().first().unwrap(), 0);
523 assert_eq!(
524 result.value_data().len(),
525 *result.offsets().last().unwrap() as usize
526 );
527 Ok(())
528 }
529}