blob: beebd160cf06027dc494599eea183d79f08382d8 [file]
// 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(())
}
}