| // Licensed to the Apache Software Foundation (ASF) under one or more | |
| // contributor license agreements. See the NOTICE file distributed with | |
| // this work for additional information regarding copyright ownership. | |
| // The ASF licenses this file to You under the Apache License, Version 2.0 | |
| // (the "License"); you may not use this file except in compliance with | |
| // the License. You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| use std::sync::Arc; | |
| use arrow::array::{ArrayRef, StringArray}; | |
| use datafusion::{ | |
| common::{Result, ScalarValue, cast::as_string_array}, | |
| logical_expr::ColumnarValue, | |
| }; | |
| use datafusion_ext_commons::df_execution_err; | |
| pub fn string_initcap(args: &[ColumnarValue]) -> Result<ColumnarValue> { | |
| match &args[0] { | |
| ColumnarValue::Array(array) => { | |
| let input_array = as_string_array(array)?; | |
| let output_array = | |
| StringArray::from_iter(input_array.into_iter().map(|s| s.map(initcap))); | |
| Ok(ColumnarValue::Array(Arc::new(output_array) as ArrayRef)) | |
| } | |
| ColumnarValue::Scalar(ScalarValue::Utf8(Some(str))) => { | |
| Ok(ColumnarValue::Scalar(ScalarValue::Utf8(Some(initcap(str))))) | |
| } | |
| _ => df_execution_err!("Unsupported args {args:?} for `string_initcap`"), | |
| } | |
| } | |
| fn initcap(input: &str) -> String { | |
| let mut out = String::with_capacity(input.len()); | |
| let mut prev_is_space = true; // i == 0 or chars[i-1] == ' ' | |
| if input.is_ascii() { | |
| // ASCII | |
| for ch in input.chars() { | |
| if prev_is_space && ch.is_ascii_alphanumeric() { | |
| out.push(ch.to_ascii_uppercase()); | |
| } else { | |
| out.push(ch.to_ascii_lowercase()); | |
| }; | |
| prev_is_space = ch == ' '; | |
| } | |
| } else { | |
| // Non-ASCII | |
| for ch in input.chars() { | |
| if prev_is_space && ch.is_alphabetic() { | |
| out.extend(ch.to_uppercase()); | |
| } else { | |
| out.extend(ch.to_lowercase()); | |
| } | |
| prev_is_space = ch == ' '; | |
| } | |
| } | |
| out | |
| } | |
| #[cfg(test)] | |
| mod test { | |
| use std::sync::Arc; | |
| use arrow::array::{ArrayRef, StringArray}; | |
| use datafusion::{ | |
| common::{Result, ScalarValue}, | |
| physical_plan::ColumnarValue, | |
| }; | |
| use crate::spark_initcap::string_initcap; | |
| #[test] | |
| fn test_initcap_array() -> Result<()> { | |
| let input_data = vec![ | |
| None, | |
| Some(""), | |
| Some("hI THOmAS"), | |
| Some("James-Smith"), | |
| Some("michael rose"), | |
| Some("a1b2 c3D4"), | |
| Some(" ---abc--- ABC --ABC-- a-b A B eB Ac c d"), | |
| Some(" 世 界 世界 "), | |
| ]; | |
| let input_columnar_value = ColumnarValue::Array(Arc::new(StringArray::from(input_data))); | |
| let result = string_initcap(&vec![input_columnar_value])?.into_array(6)?; | |
| let expected_data = vec![ | |
| None, | |
| Some(""), | |
| Some("Hi Thomas"), | |
| Some("James-smith"), | |
| Some("Michael Rose"), | |
| Some("A1b2 C3d4"), | |
| Some(" ---abc--- Abc --abc-- A-b A B Eb Ac C D"), | |
| Some(" 世 界 世界 "), | |
| ]; | |
| let expected: ArrayRef = Arc::new(StringArray::from(expected_data)); | |
| assert_eq!(&result, &expected); | |
| Ok(()) | |
| } | |
| #[test] | |
| fn test_initcap_scalar() -> Result<()> { | |
| let input_columnar_value = ColumnarValue::Scalar(ScalarValue::from("abC c3D4")); | |
| let result = string_initcap(&vec![input_columnar_value])?.into_array(1)?; | |
| let expected: ArrayRef = Arc::new(StringArray::from(vec![Some("Abc C3d4")])); | |
| assert_eq!(&result, &expected); | |
| Ok(()) | |
| } | |
| } |