Skip to main content

datafusion_functions/unicode/
reverse.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use 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
100/// Reverses the order of the characters in the string `reverse('abcde') = 'edcba'`.
101/// The implementation uses UTF-8 code points as characters
102fn 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    // Null-preserving: reuse the input null buffer as the output null buffer.
139    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                // SAFETY: `n.is_null(i)` was false in the branch above.
149                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            // SAFETY: no null buffer means every index is valid.
156            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        // reverse bytes directly since ASCII characters are single bytes
173        byte_buf.extend(s.as_bytes());
174        byte_buf.reverse();
175        // SAFETY: input was ASCII, so reversed bytes are still valid UTF-8.
176        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}