Skip to main content

datafusion_functions/unicode/
rpad.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::fmt::Write;
19use std::sync::Arc;
20
21use DataType::{LargeUtf8, Utf8, Utf8View};
22use arrow::array::{
23    ArrayRef, AsArray, GenericStringArray, GenericStringBuilder, Int64Array,
24    OffsetSizeTrait, StringArrayType, StringViewArray,
25};
26use arrow::datatypes::DataType;
27
28use crate::utils::{make_scalar_function, utf8_to_str_type};
29use datafusion_common::cast::as_int64_array;
30use datafusion_common::{Result, exec_err};
31use datafusion_expr::TypeSignature::Exact;
32use datafusion_expr::{
33    ColumnarValue, Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature,
34    Volatility,
35};
36use datafusion_macros::user_doc;
37
38#[user_doc(
39    doc_section(label = "String Functions"),
40    description = "Pads the right side of a string with another string to a specified string length.",
41    syntax_example = "rpad(str, n[, padding_str])",
42    sql_example = r#"```sql
43>  select rpad('datafusion', 20, '_-');
44+-----------------------------------------------+
45| rpad(Utf8("datafusion"),Int64(20),Utf8("_-")) |
46+-----------------------------------------------+
47| datafusion_-_-_-_-_-                          |
48+-----------------------------------------------+
49```"#,
50    standard_argument(name = "str", prefix = "String"),
51    argument(
52        name = "n",
53        description = "String length to pad to. If the input string is longer than this length, it is truncated."
54    ),
55    argument(
56        name = "padding_str",
57        description = "String expression to pad with. Can be a constant, column, or function, and any combination of string operators. _Default is a space._"
58    ),
59    related_udf(name = "lpad")
60)]
61#[derive(Debug, PartialEq, Eq, Hash)]
62pub struct RPadFunc {
63    signature: Signature,
64}
65
66impl Default for RPadFunc {
67    fn default() -> Self {
68        Self::new()
69    }
70}
71
72impl RPadFunc {
73    pub fn new() -> Self {
74        use DataType::*;
75        Self {
76            signature: Signature::one_of(
77                vec![
78                    Exact(vec![Utf8View, Int64]),
79                    Exact(vec![Utf8View, Int64, Utf8View]),
80                    Exact(vec![Utf8View, Int64, Utf8]),
81                    Exact(vec![Utf8View, Int64, LargeUtf8]),
82                    Exact(vec![Utf8, Int64]),
83                    Exact(vec![Utf8, Int64, Utf8View]),
84                    Exact(vec![Utf8, Int64, Utf8]),
85                    Exact(vec![Utf8, Int64, LargeUtf8]),
86                    Exact(vec![LargeUtf8, Int64]),
87                    Exact(vec![LargeUtf8, Int64, Utf8View]),
88                    Exact(vec![LargeUtf8, Int64, Utf8]),
89                    Exact(vec![LargeUtf8, Int64, LargeUtf8]),
90                ],
91                Volatility::Immutable,
92            ),
93        }
94    }
95}
96
97impl ScalarUDFImpl for RPadFunc {
98    fn name(&self) -> &str {
99        "rpad"
100    }
101
102    fn signature(&self) -> &Signature {
103        &self.signature
104    }
105
106    fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
107        utf8_to_str_type(&arg_types[0], "rpad")
108    }
109
110    fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
111        let ScalarFunctionArgs {
112            args, number_rows, ..
113        } = args;
114
115        const MAX_SCALAR_TARGET_LEN: usize = 16384;
116
117        // If target_len and fill (if specified) are constants, use the
118        // scalar fast path.
119        if let Some(target_len) = try_as_scalar_i64(&args[1]) {
120            let target_len: usize = match usize::try_from(target_len) {
121                Ok(n) if n <= i32::MAX as usize => n,
122                Ok(n) => {
123                    return exec_err!(
124                        "rpad requested length {n} too large, maximum allowed length is {}",
125                        i32::MAX
126                    );
127                }
128                Err(_) => 0, // negative → 0
129            };
130
131            let fill_str = if args.len() == 3 {
132                try_as_scalar_str(&args[2])
133            } else {
134                Some(" ")
135            };
136
137            // Skip the fast path for very large `target_len` values to avoid
138            // consuming too much memory. Such large padding values are uncommon
139            // in practice.
140            if target_len <= MAX_SCALAR_TARGET_LEN
141                && let Some(fill) = fill_str
142            {
143                let string_array = args[0].to_array_of_size(number_rows)?;
144                let result = match string_array.data_type() {
145                    Utf8View => rpad_scalar_args::<_, i32>(
146                        string_array.as_string_view(),
147                        target_len,
148                        fill,
149                    ),
150                    Utf8 => rpad_scalar_args::<_, i32>(
151                        string_array.as_string::<i32>(),
152                        target_len,
153                        fill,
154                    ),
155                    LargeUtf8 => rpad_scalar_args::<_, i64>(
156                        string_array.as_string::<i64>(),
157                        target_len,
158                        fill,
159                    ),
160                    other => {
161                        exec_err!("Unsupported data type {other:?} for function rpad")
162                    }
163                }?;
164                return Ok(ColumnarValue::Array(result));
165            }
166        }
167
168        match args[0].data_type() {
169            Utf8 | Utf8View => make_scalar_function(rpad::<i32>, vec![])(&args),
170            LargeUtf8 => make_scalar_function(rpad::<i64>, vec![])(&args),
171            other => exec_err!("Unsupported data type {other:?} for function rpad"),
172        }
173    }
174
175    fn documentation(&self) -> Option<&Documentation> {
176        self.doc()
177    }
178}
179
180use super::common::{
181    StringCharLen, char_count_or_boundary, pad_data_capacity, try_as_scalar_i64,
182    try_as_scalar_str,
183};
184
185/// Optimized rpad for constant target_len and fill arguments.
186fn rpad_scalar_args<'a, V: StringArrayType<'a> + Copy, T: OffsetSizeTrait>(
187    string_array: V,
188    target_len: usize,
189    fill: &str,
190) -> Result<ArrayRef> {
191    if string_array.is_ascii() && fill.is_ascii() {
192        rpad_scalar_ascii::<V, T>(string_array, target_len, fill)
193    } else {
194        rpad_scalar_unicode::<V, T>(string_array, target_len, fill)
195    }
196}
197
198fn rpad_scalar_ascii<'a, V: StringArrayType<'a> + Copy, T: OffsetSizeTrait>(
199    string_array: V,
200    target_len: usize,
201    fill: &str,
202) -> Result<ArrayRef> {
203    // With a scalar `target_len` and `fill`, we can precompute a padding
204    // buffer of `target_len` fill characters repeated cyclically.
205    let padding_buf = if !fill.is_empty() {
206        let mut buf = String::with_capacity(target_len);
207        while buf.len() < target_len {
208            let remaining = target_len - buf.len();
209            if remaining >= fill.len() {
210                buf.push_str(fill);
211            } else {
212                buf.push_str(&fill[..remaining]);
213            }
214        }
215        buf
216    } else {
217        String::new()
218    };
219
220    // Each output row is exactly `target_len` ASCII bytes (string + padding).
221    let data_capacity = string_array.len().saturating_mul(target_len);
222    let mut builder =
223        GenericStringBuilder::<T>::with_capacity(string_array.len(), data_capacity);
224
225    for maybe_string in string_array.iter() {
226        match maybe_string {
227            Some(string) => {
228                let str_len = string.len();
229                if target_len <= str_len {
230                    builder.append_value(&string[..target_len]);
231                } else if fill.is_empty() {
232                    builder.append_value(string);
233                } else {
234                    let pad_needed = target_len - str_len;
235                    builder.write_str(string)?;
236                    builder.write_str(&padding_buf[..pad_needed])?;
237                    builder.append_value("");
238                }
239            }
240            None => builder.append_null(),
241        }
242    }
243
244    Ok(Arc::new(builder.finish()) as ArrayRef)
245}
246
247fn rpad_scalar_unicode<'a, V: StringArrayType<'a> + Copy, T: OffsetSizeTrait>(
248    string_array: V,
249    target_len: usize,
250    fill: &str,
251) -> Result<ArrayRef> {
252    let fill_chars: Vec<char> = fill.chars().collect();
253
254    // With a scalar `target_len` and `fill`, we can precompute a padding buffer
255    // of `target_len` fill characters repeated cyclically. Because Unicode
256    // characters are variable-width, we build a byte-offset table to map from
257    // character count to the corresponding byte position in the padding buffer.
258    let (padding_buf, char_byte_offsets) = if !fill_chars.is_empty() {
259        let mut buf = String::new();
260        let mut offsets = Vec::with_capacity(target_len + 1);
261        offsets.push(0usize);
262        for i in 0..target_len {
263            buf.push(fill_chars[i % fill_chars.len()]);
264            offsets.push(buf.len());
265        }
266        (buf, offsets)
267    } else {
268        (String::new(), vec![0])
269    };
270
271    // Each output row is `target_len` chars; multiply by 4 (max UTF-8 bytes
272    // per char) for an upper bound in bytes.
273    let data_capacity = string_array.len().saturating_mul(target_len * 4);
274    let mut builder =
275        GenericStringBuilder::<T>::with_capacity(string_array.len(), data_capacity);
276
277    for maybe_string in string_array.iter() {
278        match maybe_string {
279            Some(string) => match char_count_or_boundary(string, target_len) {
280                StringCharLen::ByteOffset(offset) => {
281                    builder.append_value(&string[..offset]);
282                }
283                StringCharLen::CharCount(char_count) => {
284                    builder.write_str(string)?;
285                    if !fill_chars.is_empty() {
286                        let pad_chars = target_len - char_count;
287                        let pad_bytes = char_byte_offsets[pad_chars];
288                        builder.write_str(&padding_buf[..pad_bytes])?;
289                    }
290                    builder.append_value("");
291                }
292            },
293            None => builder.append_null(),
294        }
295    }
296
297    Ok(Arc::new(builder.finish()) as ArrayRef)
298}
299
300fn rpad<T: OffsetSizeTrait>(args: &[ArrayRef]) -> Result<ArrayRef> {
301    if args.len() <= 1 || args.len() > 3 {
302        return exec_err!(
303            "rpad was called with {} arguments. It requires at least 2 and at most 3.",
304            args.len()
305        );
306    }
307
308    let length_array = as_int64_array(&args[1])?;
309
310    match (args.len(), args[0].data_type()) {
311        (2, Utf8View) => rpad_impl::<&StringViewArray, &GenericStringArray<i32>, T>(
312            &args[0].as_string_view(),
313            length_array,
314            None,
315        ),
316        (2, Utf8 | LargeUtf8) => rpad_impl::<
317            &GenericStringArray<T>,
318            &GenericStringArray<T>,
319            T,
320        >(&args[0].as_string::<T>(), length_array, None),
321        (3, Utf8View) => rpad_with_replace::<&StringViewArray, T>(
322            &args[0].as_string_view(),
323            length_array,
324            &args[2],
325        ),
326        (3, Utf8 | LargeUtf8) => rpad_with_replace::<&GenericStringArray<T>, T>(
327            &args[0].as_string::<T>(),
328            length_array,
329            &args[2],
330        ),
331        (len, dt) => unreachable!("rpad: unexpected arg count ({len}) or type ({dt})"),
332    }
333}
334
335fn rpad_with_replace<'a, V, T: OffsetSizeTrait>(
336    string_array: &V,
337    length_array: &Int64Array,
338    fill_array: &'a ArrayRef,
339) -> Result<ArrayRef>
340where
341    V: StringArrayType<'a>,
342{
343    match fill_array.data_type() {
344        Utf8View => rpad_impl::<V, &StringViewArray, T>(
345            string_array,
346            length_array,
347            Some(fill_array.as_string_view()),
348        ),
349        LargeUtf8 => rpad_impl::<V, &GenericStringArray<i64>, T>(
350            string_array,
351            length_array,
352            Some(fill_array.as_string::<i64>()),
353        ),
354        Utf8 => rpad_impl::<V, &GenericStringArray<i32>, T>(
355            string_array,
356            length_array,
357            Some(fill_array.as_string::<i32>()),
358        ),
359        other => {
360            exec_err!("Unsupported data type {other:?} for function rpad")
361        }
362    }
363}
364
365fn rpad_impl<'a, V, V2, T>(
366    string_array: &V,
367    length_array: &Int64Array,
368    fill_array: Option<V2>,
369) -> Result<ArrayRef>
370where
371    V: StringArrayType<'a>,
372    V2: StringArrayType<'a>,
373    T: OffsetSizeTrait,
374{
375    let array = if let Some(fill_array) = fill_array {
376        let mut builder: GenericStringBuilder<T> = GenericStringBuilder::with_capacity(
377            string_array.len(),
378            pad_data_capacity(length_array),
379        );
380        let mut fill_chars_buf = Vec::new();
381
382        for ((string, target_len), fill) in string_array
383            .iter()
384            .zip(length_array.iter())
385            .zip(fill_array.iter())
386        {
387            if let (Some(string), Some(target_len), Some(fill)) =
388                (string, target_len, fill)
389            {
390                if target_len > i32::MAX as i64 {
391                    return exec_err!(
392                        "rpad requested length {target_len} too large, maximum allowed length is {}",
393                        i32::MAX
394                    );
395                }
396
397                let target_len = if target_len < 0 {
398                    0
399                } else {
400                    target_len as usize
401                };
402                if target_len == 0 {
403                    builder.append_value("");
404                    continue;
405                }
406
407                if string.is_ascii() && fill.is_ascii() {
408                    // ASCII fast path: byte length == character length.
409                    let str_len = string.len();
410                    if target_len < str_len {
411                        builder.append_value(&string[..target_len]);
412                    } else if fill.is_empty() {
413                        builder.append_value(string);
414                    } else {
415                        let pad_len = target_len - str_len;
416                        let fill_len = fill.len();
417                        let full_reps = pad_len / fill_len;
418                        let remainder = pad_len % fill_len;
419                        builder.write_str(string)?;
420                        for _ in 0..full_reps {
421                            builder.write_str(fill)?;
422                        }
423                        if remainder > 0 {
424                            builder.write_str(&fill[..remainder])?;
425                        }
426                        builder.append_value("");
427                    }
428                } else {
429                    fill_chars_buf.clear();
430                    fill_chars_buf.extend(fill.chars());
431
432                    match char_count_or_boundary(string, target_len) {
433                        StringCharLen::ByteOffset(offset) => {
434                            builder.append_value(&string[..offset]);
435                        }
436                        StringCharLen::CharCount(char_count) => {
437                            builder.write_str(string)?;
438                            if !fill_chars_buf.is_empty() {
439                                for l in 0..target_len - char_count {
440                                    let c = *fill_chars_buf
441                                        .get(l % fill_chars_buf.len())
442                                        .unwrap();
443                                    builder.write_char(c)?;
444                                }
445                            }
446                            builder.append_value("");
447                        }
448                    }
449                }
450            } else {
451                builder.append_null();
452            }
453        }
454
455        builder.finish()
456    } else {
457        let mut builder: GenericStringBuilder<T> = GenericStringBuilder::with_capacity(
458            string_array.len(),
459            pad_data_capacity(length_array),
460        );
461
462        for (string, target_len) in string_array.iter().zip(length_array.iter()) {
463            if let (Some(string), Some(target_len)) = (string, target_len) {
464                if target_len > i32::MAX as i64 {
465                    return exec_err!(
466                        "rpad requested length {target_len} too large, maximum allowed length is {}",
467                        i32::MAX
468                    );
469                }
470
471                let target_len = if target_len < 0 {
472                    0
473                } else {
474                    target_len as usize
475                };
476                if target_len == 0 {
477                    builder.append_value("");
478                    continue;
479                }
480
481                if string.is_ascii() {
482                    // ASCII fast path: byte length == character length
483                    let str_len = string.len();
484                    if target_len < str_len {
485                        builder.append_value(&string[..target_len]);
486                    } else {
487                        builder.write_str(string)?;
488                        for _ in 0..(target_len - str_len) {
489                            builder.write_str(" ")?;
490                        }
491                        builder.append_value("");
492                    }
493                } else {
494                    match char_count_or_boundary(string, target_len) {
495                        StringCharLen::ByteOffset(offset) => {
496                            builder.append_value(&string[..offset]);
497                        }
498                        StringCharLen::CharCount(char_count) => {
499                            builder.write_str(string)?;
500                            for _ in 0..(target_len - char_count) {
501                                builder.write_str(" ")?;
502                            }
503                            builder.append_value("");
504                        }
505                    }
506                }
507            } else {
508                builder.append_null();
509            }
510        }
511
512        builder.finish()
513    };
514
515    Ok(Arc::new(array) as ArrayRef)
516}
517
518#[cfg(test)]
519mod tests {
520    use arrow::array::{Array, StringArray};
521    use arrow::datatypes::DataType::Utf8;
522
523    use datafusion_common::{Result, ScalarValue};
524    use datafusion_expr::{ColumnarValue, ScalarUDFImpl};
525
526    use crate::unicode::rpad::RPadFunc;
527    use crate::utils::test::test_function;
528
529    #[test]
530    fn test_functions() -> Result<()> {
531        test_function!(
532            RPadFunc::new(),
533            vec![
534                ColumnarValue::Scalar(ScalarValue::from("josé")),
535                ColumnarValue::Scalar(ScalarValue::from(5i64)),
536            ],
537            Ok(Some("josé ")),
538            &str,
539            Utf8,
540            StringArray
541        );
542        test_function!(
543            RPadFunc::new(),
544            vec![
545                ColumnarValue::Scalar(ScalarValue::from("hi")),
546                ColumnarValue::Scalar(ScalarValue::from(5i64)),
547            ],
548            Ok(Some("hi   ")),
549            &str,
550            Utf8,
551            StringArray
552        );
553        test_function!(
554            RPadFunc::new(),
555            vec![
556                ColumnarValue::Scalar(ScalarValue::from("hi")),
557                ColumnarValue::Scalar(ScalarValue::from(0i64)),
558            ],
559            Ok(Some("")),
560            &str,
561            Utf8,
562            StringArray
563        );
564        test_function!(
565            RPadFunc::new(),
566            vec![
567                ColumnarValue::Scalar(ScalarValue::from("hi")),
568                ColumnarValue::Scalar(ScalarValue::Int64(None)),
569            ],
570            Ok(None),
571            &str,
572            Utf8,
573            StringArray
574        );
575        test_function!(
576            RPadFunc::new(),
577            vec![
578                ColumnarValue::Scalar(ScalarValue::Utf8(None)),
579                ColumnarValue::Scalar(ScalarValue::from(5i64)),
580            ],
581            Ok(None),
582            &str,
583            Utf8,
584            StringArray
585        );
586        test_function!(
587            RPadFunc::new(),
588            vec![
589                ColumnarValue::Scalar(ScalarValue::from("hi")),
590                ColumnarValue::Scalar(ScalarValue::from(5i64)),
591                ColumnarValue::Scalar(ScalarValue::from("xy")),
592            ],
593            Ok(Some("hixyx")),
594            &str,
595            Utf8,
596            StringArray
597        );
598        test_function!(
599            RPadFunc::new(),
600            vec![
601                ColumnarValue::Scalar(ScalarValue::from("hi")),
602                ColumnarValue::Scalar(ScalarValue::from(21i64)),
603                ColumnarValue::Scalar(ScalarValue::from("abcdef")),
604            ],
605            Ok(Some("hiabcdefabcdefabcdefa")),
606            &str,
607            Utf8,
608            StringArray
609        );
610        test_function!(
611            RPadFunc::new(),
612            vec![
613                ColumnarValue::Scalar(ScalarValue::from("hi")),
614                ColumnarValue::Scalar(ScalarValue::from(5i64)),
615                ColumnarValue::Scalar(ScalarValue::from(" ")),
616            ],
617            Ok(Some("hi   ")),
618            &str,
619            Utf8,
620            StringArray
621        );
622        test_function!(
623            RPadFunc::new(),
624            vec![
625                ColumnarValue::Scalar(ScalarValue::from("hi")),
626                ColumnarValue::Scalar(ScalarValue::from(5i64)),
627                ColumnarValue::Scalar(ScalarValue::from("")),
628            ],
629            Ok(Some("hi")),
630            &str,
631            Utf8,
632            StringArray
633        );
634        test_function!(
635            RPadFunc::new(),
636            vec![
637                ColumnarValue::Scalar(ScalarValue::Utf8(None)),
638                ColumnarValue::Scalar(ScalarValue::from(5i64)),
639                ColumnarValue::Scalar(ScalarValue::from("xy")),
640            ],
641            Ok(None),
642            &str,
643            Utf8,
644            StringArray
645        );
646        test_function!(
647            RPadFunc::new(),
648            vec![
649                ColumnarValue::Scalar(ScalarValue::from("hi")),
650                ColumnarValue::Scalar(ScalarValue::Int64(None)),
651                ColumnarValue::Scalar(ScalarValue::from("xy")),
652            ],
653            Ok(None),
654            &str,
655            Utf8,
656            StringArray
657        );
658        test_function!(
659            RPadFunc::new(),
660            vec![
661                ColumnarValue::Scalar(ScalarValue::from("hi")),
662                ColumnarValue::Scalar(ScalarValue::from(5i64)),
663                ColumnarValue::Scalar(ScalarValue::Utf8(None)),
664            ],
665            Ok(None),
666            &str,
667            Utf8,
668            StringArray
669        );
670        test_function!(
671            RPadFunc::new(),
672            vec![
673                ColumnarValue::Scalar(ScalarValue::from("hello")),
674                ColumnarValue::Scalar(ScalarValue::from(2i64)),
675            ],
676            Ok(Some("he")),
677            &str,
678            Utf8,
679            StringArray
680        );
681        test_function!(
682            RPadFunc::new(),
683            vec![
684                ColumnarValue::Scalar(ScalarValue::from("hi")),
685                ColumnarValue::Scalar(ScalarValue::from(6i64)),
686                ColumnarValue::Scalar(ScalarValue::from("xy")),
687            ],
688            Ok(Some("hixyxy")),
689            &str,
690            Utf8,
691            StringArray
692        );
693        test_function!(
694            RPadFunc::new(),
695            vec![
696                ColumnarValue::Scalar(ScalarValue::from("josé")),
697                ColumnarValue::Scalar(ScalarValue::from(10i64)),
698                ColumnarValue::Scalar(ScalarValue::from("xy")),
699            ],
700            Ok(Some("joséxyxyxy")),
701            &str,
702            Utf8,
703            StringArray
704        );
705        test_function!(
706            RPadFunc::new(),
707            vec![
708                ColumnarValue::Scalar(ScalarValue::from("josé")),
709                ColumnarValue::Scalar(ScalarValue::from(10i64)),
710                ColumnarValue::Scalar(ScalarValue::from("éñ")),
711            ],
712            Ok(Some("josééñéñéñ")),
713            &str,
714            Utf8,
715            StringArray
716        );
717        #[cfg(not(feature = "unicode_expressions"))]
718        test_function!(
719            RPadFunc::new(),
720            &[
721                ColumnarValue::Scalar(ScalarValue::from("josé")),
722                ColumnarValue::Scalar(ScalarValue::from(5i64)),
723            ],
724            internal_err!(
725                "function rpad requires compilation with feature flag: unicode_expressions."
726            ),
727            &str,
728            Utf8,
729            StringArray
730        );
731
732        Ok(())
733    }
734}