datafusion_functions/string/
ascii.rs1use crate::utils::transform_leaf_type_preserving_encoding;
19use arrow::array::{ArrayRef, AsArray, Int32Array, StringArrayType};
20use arrow::datatypes::DataType;
21use arrow::error::ArrowError;
22use datafusion_common::types::logical_string;
23use datafusion_common::utils::take_function_args;
24use datafusion_common::{Result, ScalarValue, internal_err};
25use datafusion_expr::{
26 ColumnarValue, Documentation, EncodingPreservation, TypeSignatureClass,
27};
28use datafusion_expr::{ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility};
29use datafusion_expr_common::signature::Coercion;
30use datafusion_macros::user_doc;
31use std::sync::Arc;
32
33#[user_doc(
34 doc_section(label = "String Functions"),
35 description = "Returns the first Unicode scalar value of a string.",
36 syntax_example = "ascii(str)",
37 sql_example = r#"```sql
38> select ascii('abc');
39+--------------------+
40| ascii(Utf8("abc")) |
41+--------------------+
42| 97 |
43+--------------------+
44> select ascii('🚀');
45+-------------------+
46| ascii(Utf8("🚀")) |
47+-------------------+
48| 128640 |
49+-------------------+
50```"#,
51 standard_argument(name = "str", prefix = "String"),
52 related_udf(name = "chr")
53)]
54#[derive(Debug, PartialEq, Eq, Hash)]
55pub struct AsciiFunc {
56 signature: Signature,
57}
58
59impl Default for AsciiFunc {
60 fn default() -> Self {
61 Self::new()
62 }
63}
64
65impl AsciiFunc {
66 pub fn new() -> Self {
67 Self {
68 signature: Signature::coercible(
69 vec![
70 Coercion::new_exact(TypeSignatureClass::Native(logical_string()))
71 .with_encoding_preservation(EncodingPreservation::dictionary()),
72 ],
73 Volatility::Immutable,
74 ),
75 }
76 }
77}
78
79impl ScalarUDFImpl for AsciiFunc {
80 fn name(&self) -> &str {
81 "ascii"
82 }
83
84 fn signature(&self) -> &Signature {
85 &self.signature
86 }
87
88 fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
89 transform_leaf_type_preserving_encoding(&arg_types[0], &|_| Ok(DataType::Int32))
90 }
91
92 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
93 let [arg] = take_function_args(self.name(), args.args)?;
94
95 match arg {
96 ColumnarValue::Scalar(scalar) => {
97 Ok(ColumnarValue::Scalar(ascii_scalar(&scalar)?))
98 }
99 ColumnarValue::Array(array) => Ok(ColumnarValue::Array(ascii(&[array])?)),
100 }
101 }
102
103 fn documentation(&self) -> Option<&Documentation> {
104 self.doc()
105 }
106}
107
108fn ascii_scalar(scalar: &ScalarValue) -> Result<ScalarValue> {
109 match scalar {
110 ScalarValue::Utf8(value)
111 | ScalarValue::LargeUtf8(value)
112 | ScalarValue::Utf8View(value) => {
113 Ok(ScalarValue::Int32(value.as_deref().map(first_char_code)))
114 }
115 ScalarValue::Dictionary(key_type, value) => Ok(ScalarValue::Dictionary(
116 key_type.clone(),
117 Box::new(ascii_scalar(value)?),
118 )),
119 _ => internal_err!(
120 "Unexpected data type {:?} for function ascii",
121 scalar.data_type()
122 ),
123 }
124}
125
126#[inline]
130fn first_char_code(s: &str) -> i32 {
131 match s.as_bytes().first() {
132 None => 0,
133 Some(&b) if b < 0x80 => b as i32,
135 Some(_) => s.chars().next().map_or(0, |c| c as i32),
137 }
138}
139
140fn calculate_ascii<'a, V>(array: &V) -> Result<ArrayRef, ArrowError>
141where
142 V: StringArrayType<'a, Item = &'a str>,
143{
144 let len = array.len();
145 let nulls = array.nulls().cloned();
146
147 let values: Vec<i32> = match nulls {
151 Some(ref n) => (0..len)
152 .map(|i| {
153 if n.is_null(i) {
154 0
155 } else {
156 let s = unsafe { array.value_unchecked(i) };
159 first_char_code(s)
160 }
161 })
162 .collect(),
163 None => (0..len)
164 .map(|i| {
165 let s = unsafe { array.value_unchecked(i) };
167 first_char_code(s)
168 })
169 .collect(),
170 };
171
172 let array = Int32Array::new(values.into(), nulls);
173
174 Ok(Arc::new(array))
175}
176
177pub fn ascii(args: &[ArrayRef]) -> Result<ArrayRef> {
179 match args[0].data_type() {
180 DataType::Utf8 => {
181 let string_array = args[0].as_string::<i32>();
182 Ok(calculate_ascii(&string_array)?)
183 }
184 DataType::LargeUtf8 => {
185 let string_array = args[0].as_string::<i64>();
186 Ok(calculate_ascii(&string_array)?)
187 }
188 DataType::Utf8View => {
189 let string_array = args[0].as_string_view();
190 Ok(calculate_ascii(&string_array)?)
191 }
192 DataType::Dictionary(_, _) => {
193 let dictionary = args[0].as_any_dictionary();
194 let converted = ascii(&[Arc::clone(dictionary.values())])?;
195 Ok(dictionary.with_values(converted))
196 }
197 _ => internal_err!("Unsupported data type"),
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use crate::string::ascii::AsciiFunc;
204 use crate::utils::test::test_function;
205 use arrow::array::{Array, Int32Array};
206 use arrow::datatypes::DataType::Int32;
207 use datafusion_common::{Result, ScalarValue};
208 use datafusion_expr::{ColumnarValue, ScalarUDFImpl};
209
210 macro_rules! test_ascii {
211 ($INPUT:expr, $EXPECTED:expr) => {
212 test_function!(
213 AsciiFunc::new(),
214 vec![ColumnarValue::Scalar(ScalarValue::Utf8($INPUT))],
215 $EXPECTED,
216 i32,
217 Int32,
218 Int32Array
219 );
220
221 test_function!(
222 AsciiFunc::new(),
223 vec![ColumnarValue::Scalar(ScalarValue::LargeUtf8($INPUT))],
224 $EXPECTED,
225 i32,
226 Int32,
227 Int32Array
228 );
229
230 test_function!(
231 AsciiFunc::new(),
232 vec![ColumnarValue::Scalar(ScalarValue::Utf8View($INPUT))],
233 $EXPECTED,
234 i32,
235 Int32,
236 Int32Array
237 );
238 };
239 }
240
241 #[test]
242 fn test_functions() -> Result<()> {
243 test_ascii!(Some(String::from("x")), Ok(Some(120)));
244 test_ascii!(Some(String::from("a")), Ok(Some(97)));
245 test_ascii!(Some(String::from("")), Ok(Some(0)));
246 test_ascii!(Some(String::from("🚀")), Ok(Some(128640)));
247 test_ascii!(Some(String::from("\n")), Ok(Some(10)));
248 test_ascii!(Some(String::from("\t")), Ok(Some(9)));
249 test_ascii!(None, Ok(None));
250 Ok(())
251 }
252}