datafusion_functions/unicode/
reverse.rs1use std::sync::Arc;
19
20use crate::strings::{
21 BulkNullStringArrayBuilder, GenericStringArrayBuilder, StringViewArrayBuilder,
22};
23use crate::utils::make_scalar_function;
24use DataType::{LargeUtf8, Utf8, Utf8View};
25use arrow::array::{Array, ArrayRef, AsArray, StringArrayType};
26use arrow::datatypes::DataType;
27use datafusion_common::Result;
28use datafusion_common::types::{NativeType, logical_string};
29use datafusion_expr::{
30 Coercion, ColumnarValue, Documentation, EncodingPreservation, ScalarFunctionArgs,
31 ScalarUDFImpl, Signature, TypeSignatureClass, Volatility,
32};
33use datafusion_macros::user_doc;
34
35#[user_doc(
36 doc_section(label = "String Functions"),
37 description = "Reverses the character order of a string.",
38 syntax_example = "reverse(str)",
39 sql_example = r#"```sql
40> select reverse('datafusion');
41+-----------------------------+
42| reverse(Utf8("datafusion")) |
43+-----------------------------+
44| noisufatad |
45+-----------------------------+
46```"#,
47 standard_argument(name = "str", prefix = "String")
48)]
49#[derive(Debug, PartialEq, Eq, Hash)]
50pub struct ReverseFunc {
51 signature: Signature,
52}
53
54impl Default for ReverseFunc {
55 fn default() -> Self {
56 Self::new()
57 }
58}
59
60impl ReverseFunc {
61 pub fn new() -> Self {
62 Self {
63 signature: Signature::coercible(
64 vec![
65 Coercion::new_implicit(
66 TypeSignatureClass::Native(logical_string()),
67 vec![TypeSignatureClass::Any],
68 NativeType::String,
69 )
70 .with_encoding_preservation(EncodingPreservation::dictionary()),
71 ],
72 Volatility::Immutable,
73 ),
74 }
75 }
76}
77
78impl ScalarUDFImpl for ReverseFunc {
79 fn name(&self) -> &str {
80 "reverse"
81 }
82
83 fn signature(&self) -> &Signature {
84 &self.signature
85 }
86
87 fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
88 Ok(arg_types[0].clone())
89 }
90
91 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
92 make_scalar_function(reverse, vec![])(&args.args)
93 }
94
95 fn documentation(&self) -> Option<&Documentation> {
96 self.doc()
97 }
98}
99
100fn reverse(args: &[ArrayRef]) -> Result<ArrayRef> {
103 let len = args[0].len();
104
105 match args[0].data_type() {
106 LargeUtf8 => reverse_impl(
107 &args[0].as_string::<i64>(),
108 GenericStringArrayBuilder::<i64>::with_capacity(len, 1024),
109 ),
110 Utf8 => reverse_impl(
111 &args[0].as_string::<i32>(),
112 GenericStringArrayBuilder::<i32>::with_capacity(len, 1024),
113 ),
114 Utf8View => reverse_impl(
115 &args[0].as_string_view(),
116 StringViewArrayBuilder::with_capacity(len),
117 ),
118 DataType::Dictionary(_, _) => {
119 let dictionary = args[0].as_any_dictionary();
120 let converted = reverse(&[Arc::clone(dictionary.values())])?;
121 Ok(dictionary.with_values(converted))
122 }
123 _ => unreachable!(
124 "Reverse can only be applied to Utf8View, Utf8 and LargeUtf8 types"
125 ),
126 }
127}
128
129fn reverse_impl<'a, StringArrType, B>(
130 string_array: &StringArrType,
131 mut array_builder: B,
132) -> Result<ArrayRef>
133where
134 StringArrType: StringArrayType<'a>,
135 B: BulkNullStringArrayBuilder,
136{
137 let item_len = string_array.len();
138 let nulls = string_array.nulls().cloned();
140 let mut string_buf = String::new();
141 let mut byte_buf = Vec::<u8>::new();
142
143 if let Some(ref n) = nulls {
144 for i in 0..item_len {
145 if n.is_null(i) {
146 array_builder.append_placeholder();
147 } else {
148 let s = unsafe { string_array.value_unchecked(i) };
150 append_reversed(s, &mut array_builder, &mut byte_buf, &mut string_buf);
151 }
152 }
153 } else {
154 for i in 0..item_len {
155 let s = unsafe { string_array.value_unchecked(i) };
157 append_reversed(s, &mut array_builder, &mut byte_buf, &mut string_buf);
158 }
159 }
160
161 array_builder.finish(nulls)
162}
163
164#[inline]
165fn append_reversed<B: BulkNullStringArrayBuilder>(
166 s: &str,
167 builder: &mut B,
168 byte_buf: &mut Vec<u8>,
169 string_buf: &mut String,
170) {
171 if s.is_ascii() {
172 byte_buf.extend(s.as_bytes());
174 byte_buf.reverse();
175 let reversed = unsafe { std::str::from_utf8_unchecked(byte_buf) };
177 builder.append_value(reversed);
178 byte_buf.clear();
179 } else {
180 string_buf.extend(s.chars().rev());
181 builder.append_value(string_buf);
182 string_buf.clear();
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use arrow::array::{Array, LargeStringArray, StringArray, StringViewArray};
189 use arrow::datatypes::DataType::{LargeUtf8, Utf8, Utf8View};
190
191 use datafusion_common::{Result, ScalarValue};
192 use datafusion_expr::{ColumnarValue, ScalarUDFImpl};
193
194 use crate::unicode::reverse::ReverseFunc;
195 use crate::utils::test::test_function;
196
197 macro_rules! test_reverse {
198 ($INPUT:expr, $EXPECTED:expr) => {
199 test_function!(
200 ReverseFunc::new(),
201 vec![ColumnarValue::Scalar(ScalarValue::Utf8($INPUT))],
202 $EXPECTED,
203 &str,
204 Utf8,
205 StringArray
206 );
207
208 test_function!(
209 ReverseFunc::new(),
210 vec![ColumnarValue::Scalar(ScalarValue::LargeUtf8($INPUT))],
211 $EXPECTED,
212 &str,
213 LargeUtf8,
214 LargeStringArray
215 );
216
217 test_function!(
218 ReverseFunc::new(),
219 vec![ColumnarValue::Scalar(ScalarValue::Utf8View($INPUT))],
220 $EXPECTED,
221 &str,
222 Utf8View,
223 StringViewArray
224 );
225 };
226 }
227
228 #[test]
229 fn test_functions() -> Result<()> {
230 test_reverse!(Some("abcde".into()), Ok(Some("edcba")));
231 test_reverse!(Some("loẅks".into()), Ok(Some("sk̈wol")));
232 test_reverse!(Some("loẅks".into()), Ok(Some("sk̈wol")));
233 test_reverse!(None, Ok(None));
234 #[cfg(not(feature = "unicode_expressions"))]
235 test_reverse!(
236 Some("abcde".into()),
237 internal_err!(
238 "function reverse requires compilation with feature flag: unicode_expressions."
239 ),
240 );
241
242 Ok(())
243 }
244
245 #[test]
246 fn test_array_with_nulls() {
247 use crate::unicode::reverse::reverse;
248 use arrow::array::ArrayRef;
249 use std::sync::Arc;
250
251 let input_values = vec![Some("abcd"), None, Some("XYZ"), Some("héllo"), None];
252 let expected: Vec<Option<&str>> =
253 vec![Some("dcba"), None, Some("ZYX"), Some("olléh"), None];
254
255 let cases: Vec<(&str, ArrayRef)> = vec![
256 (
257 "StringArray",
258 Arc::new(StringArray::from(input_values.clone())),
259 ),
260 (
261 "LargeStringArray",
262 Arc::new(LargeStringArray::from(input_values.clone())),
263 ),
264 (
265 "StringViewArray",
266 Arc::new(StringViewArray::from(input_values.clone())),
267 ),
268 ];
269
270 for (label, input) in cases {
271 let out = reverse(&[input]).unwrap();
272 assert_eq!(out.len(), expected.len(), "{label}: length mismatch");
273
274 let actual: Vec<Option<&str>> = match out.data_type() {
275 Utf8 => out
276 .as_any()
277 .downcast_ref::<StringArray>()
278 .unwrap()
279 .iter()
280 .collect(),
281 LargeUtf8 => out
282 .as_any()
283 .downcast_ref::<LargeStringArray>()
284 .unwrap()
285 .iter()
286 .collect(),
287 Utf8View => out
288 .as_any()
289 .downcast_ref::<StringViewArray>()
290 .unwrap()
291 .iter()
292 .collect(),
293 other => panic!("{label}: unexpected output type {other:?}"),
294 };
295 assert_eq!(actual, expected, "{label}: value mismatch");
296 }
297 }
298}