1use std::fmt::Write;
19use std::sync::Arc;
20
21use DataType::{LargeUtf8, Utf8, Utf8View};
22use arrow::array::{
23 Array, 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 left side of a string with another string to a specified string length.",
41 syntax_example = "lpad(str, n[, padding_str])",
42 sql_example = r#"```sql
43> select lpad('Dolly', 10, 'hello');
44+---------------------------------------------+
45| lpad(Utf8("Dolly"),Int64(10),Utf8("hello")) |
46+---------------------------------------------+
47| helloDolly |
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 (on the right)."
54 ),
55 argument(
56 name = "padding_str",
57 description = "Optional 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 = "rpad")
60)]
61#[derive(Debug, PartialEq, Eq, Hash)]
62pub struct LPadFunc {
63 signature: Signature,
64}
65
66impl Default for LPadFunc {
67 fn default() -> Self {
68 Self::new()
69 }
70}
71
72impl LPadFunc {
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 LPadFunc {
98 fn name(&self) -> &str {
99 "lpad"
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], "lpad")
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 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 "lpad requested length {n} too large, maximum allowed length is {}",
125 i32::MAX
126 );
127 }
128 Err(_) => 0, };
130
131 let fill_str = if args.len() == 3 {
132 try_as_scalar_str(&args[2])
133 } else {
134 Some(" ")
135 };
136
137 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 => lpad_scalar_args::<_, i32>(
146 string_array.as_string_view(),
147 target_len,
148 fill,
149 ),
150 Utf8 => lpad_scalar_args::<_, i32>(
151 string_array.as_string::<i32>(),
152 target_len,
153 fill,
154 ),
155 LargeUtf8 => lpad_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 lpad")
162 }
163 }?;
164 return Ok(ColumnarValue::Array(result));
165 }
166 }
167
168 match args[0].data_type() {
169 Utf8 | Utf8View => make_scalar_function(lpad::<i32>, vec![])(&args),
170 LargeUtf8 => make_scalar_function(lpad::<i64>, vec![])(&args),
171 other => exec_err!("Unsupported data type {other:?} for function lpad"),
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
185fn lpad_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 lpad_scalar_ascii::<V, T>(string_array, target_len, fill)
193 } else {
194 lpad_scalar_unicode::<V, T>(string_array, target_len, fill)
195 }
196}
197
198fn lpad_scalar_ascii<'a, V: StringArrayType<'a> + Copy, T: OffsetSizeTrait>(
199 string_array: V,
200 target_len: usize,
201 fill: &str,
202) -> Result<ArrayRef> {
203 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 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(&padding_buf[..pad_needed])?;
236 builder.append_value(string);
237 }
238 }
239 None => builder.append_null(),
240 }
241 }
242
243 Ok(Arc::new(builder.finish()) as ArrayRef)
244}
245
246fn lpad_scalar_unicode<'a, V: StringArrayType<'a> + Copy, T: OffsetSizeTrait>(
247 string_array: V,
248 target_len: usize,
249 fill: &str,
250) -> Result<ArrayRef> {
251 let fill_chars: Vec<char> = fill.chars().collect();
252
253 let (padding_buf, char_byte_offsets) = if !fill_chars.is_empty() {
258 let mut buf = String::new();
259 let mut offsets = Vec::with_capacity(target_len + 1);
260 offsets.push(0usize);
261 for i in 0..target_len {
262 buf.push(fill_chars[i % fill_chars.len()]);
263 offsets.push(buf.len());
264 }
265 (buf, offsets)
266 } else {
267 (String::new(), vec![0])
268 };
269
270 let data_capacity = string_array.len().saturating_mul(target_len * 4);
273 let mut builder =
274 GenericStringBuilder::<T>::with_capacity(string_array.len(), data_capacity);
275
276 for maybe_string in string_array.iter() {
277 match maybe_string {
278 Some(string) => match char_count_or_boundary(string, target_len) {
279 StringCharLen::ByteOffset(offset) => {
280 builder.append_value(&string[..offset]);
281 }
282 StringCharLen::CharCount(char_count) => {
283 if !fill_chars.is_empty() {
284 let pad_chars = target_len - char_count;
285 let pad_bytes = char_byte_offsets[pad_chars];
286 builder.write_str(&padding_buf[..pad_bytes])?;
287 }
288 builder.append_value(string);
289 }
290 },
291 None => builder.append_null(),
292 }
293 }
294
295 Ok(Arc::new(builder.finish()) as ArrayRef)
296}
297
298fn lpad<T: OffsetSizeTrait>(args: &[ArrayRef]) -> Result<ArrayRef> {
302 if args.len() <= 1 || args.len() > 3 {
303 return exec_err!(
304 "lpad was called with {} arguments. It requires at least 2 and at most 3.",
305 args.len()
306 );
307 }
308
309 let length_array = as_int64_array(&args[1])?;
310
311 match (args.len(), args[0].data_type()) {
312 (2, Utf8View) => lpad_impl::<&StringViewArray, &GenericStringArray<i32>, T>(
313 &args[0].as_string_view(),
314 length_array,
315 None,
316 ),
317 (2, Utf8 | LargeUtf8) => lpad_impl::<
318 &GenericStringArray<T>,
319 &GenericStringArray<T>,
320 T,
321 >(&args[0].as_string::<T>(), length_array, None),
322 (3, Utf8View) => lpad_with_replace::<&StringViewArray, T>(
323 &args[0].as_string_view(),
324 length_array,
325 &args[2],
326 ),
327 (3, Utf8 | LargeUtf8) => lpad_with_replace::<&GenericStringArray<T>, T>(
328 &args[0].as_string::<T>(),
329 length_array,
330 &args[2],
331 ),
332 (len, dt) => unreachable!("lpad: unexpected arg count ({len}) or type ({dt})"),
333 }
334}
335
336fn lpad_with_replace<'a, V, T: OffsetSizeTrait>(
337 string_array: &V,
338 length_array: &Int64Array,
339 fill_array: &'a ArrayRef,
340) -> Result<ArrayRef>
341where
342 V: StringArrayType<'a>,
343{
344 match fill_array.data_type() {
345 Utf8View => lpad_impl::<V, &StringViewArray, T>(
346 string_array,
347 length_array,
348 Some(fill_array.as_string_view()),
349 ),
350 LargeUtf8 => lpad_impl::<V, &GenericStringArray<i64>, T>(
351 string_array,
352 length_array,
353 Some(fill_array.as_string::<i64>()),
354 ),
355 Utf8 => lpad_impl::<V, &GenericStringArray<i32>, T>(
356 string_array,
357 length_array,
358 Some(fill_array.as_string::<i32>()),
359 ),
360 other => {
361 exec_err!("Unsupported data type {other:?} for function lpad")
362 }
363 }
364}
365
366fn lpad_impl<'a, V, V2, T>(
367 string_array: &V,
368 length_array: &Int64Array,
369 fill_array: Option<V2>,
370) -> Result<ArrayRef>
371where
372 V: StringArrayType<'a>,
373 V2: StringArrayType<'a>,
374 T: OffsetSizeTrait,
375{
376 let array = if let Some(fill_array) = fill_array {
377 let mut builder: GenericStringBuilder<T> = GenericStringBuilder::with_capacity(
378 string_array.len(),
379 pad_data_capacity(length_array),
380 );
381 let mut fill_chars_buf = Vec::new();
382
383 for ((string, target_len), fill) in string_array
384 .iter()
385 .zip(length_array.iter())
386 .zip(fill_array.iter())
387 {
388 if let (Some(string), Some(target_len), Some(fill)) =
389 (string, target_len, fill)
390 {
391 if target_len > i32::MAX as i64 {
392 return exec_err!(
393 "lpad requested length {target_len} too large, maximum allowed length is {}",
394 i32::MAX
395 );
396 }
397
398 let target_len = if target_len < 0 {
399 0
400 } else {
401 target_len as usize
402 };
403 if target_len == 0 {
404 builder.append_value("");
405 continue;
406 }
407
408 if string.is_ascii() && fill.is_ascii() {
409 let str_len = string.len();
411 if target_len < str_len {
412 builder.append_value(&string[..target_len]);
413 } else if fill.is_empty() {
414 builder.append_value(string);
415 } else {
416 let pad_len = target_len - str_len;
417 let fill_len = fill.len();
418 let full_reps = pad_len / fill_len;
419 let remainder = pad_len % fill_len;
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(string);
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 if !fill_chars_buf.is_empty() {
438 for l in 0..target_len - char_count {
439 let c = *fill_chars_buf
440 .get(l % fill_chars_buf.len())
441 .unwrap();
442 builder.write_char(c)?;
443 }
444 }
445 builder.append_value(string);
446 }
447 }
448 }
449 } else {
450 builder.append_null();
451 }
452 }
453
454 builder.finish()
455 } else {
456 let mut builder: GenericStringBuilder<T> = GenericStringBuilder::with_capacity(
457 string_array.len(),
458 pad_data_capacity(length_array),
459 );
460
461 for (string, target_len) in string_array.iter().zip(length_array.iter()) {
462 if let (Some(string), Some(target_len)) = (string, target_len) {
463 if target_len > i32::MAX as i64 {
464 return exec_err!(
465 "lpad requested length {target_len} too large, maximum allowed length is {}",
466 i32::MAX
467 );
468 }
469
470 let target_len = if target_len < 0 {
471 0
472 } else {
473 target_len as usize
474 };
475 if target_len == 0 {
476 builder.append_value("");
477 continue;
478 }
479
480 if string.is_ascii() {
481 let str_len = string.len();
483 if target_len < str_len {
484 builder.append_value(&string[..target_len]);
485 } else {
486 for _ in 0..(target_len - str_len) {
487 builder.write_str(" ")?;
488 }
489 builder.append_value(string);
490 }
491 } else {
492 match char_count_or_boundary(string, target_len) {
493 StringCharLen::ByteOffset(offset) => {
494 builder.append_value(&string[..offset]);
495 }
496 StringCharLen::CharCount(char_count) => {
497 for _ in 0..(target_len - char_count) {
498 builder.write_str(" ")?;
499 }
500 builder.append_value(string);
501 }
502 }
503 }
504 } else {
505 builder.append_null();
506 }
507 }
508
509 builder.finish()
510 };
511
512 Ok(Arc::new(array) as ArrayRef)
513}
514
515#[cfg(test)]
516mod tests {
517 use crate::unicode::lpad::LPadFunc;
518 use crate::utils::test::test_function;
519
520 use arrow::array::{Array, LargeStringArray, StringArray};
521 use arrow::datatypes::DataType::{LargeUtf8, Utf8};
522
523 use datafusion_common::{Result, ScalarValue};
524 use datafusion_expr::{ColumnarValue, ScalarUDFImpl};
525
526 macro_rules! test_lpad {
527 ($INPUT:expr, $LENGTH:expr, $EXPECTED:expr) => {
528 test_function!(
529 LPadFunc::new(),
530 vec![
531 ColumnarValue::Scalar(ScalarValue::Utf8($INPUT)),
532 ColumnarValue::Scalar($LENGTH)
533 ],
534 $EXPECTED,
535 &str,
536 Utf8,
537 StringArray
538 );
539
540 test_function!(
541 LPadFunc::new(),
542 vec![
543 ColumnarValue::Scalar(ScalarValue::LargeUtf8($INPUT)),
544 ColumnarValue::Scalar($LENGTH)
545 ],
546 $EXPECTED,
547 &str,
548 LargeUtf8,
549 LargeStringArray
550 );
551
552 test_function!(
553 LPadFunc::new(),
554 vec![
555 ColumnarValue::Scalar(ScalarValue::Utf8View($INPUT)),
556 ColumnarValue::Scalar($LENGTH)
557 ],
558 $EXPECTED,
559 &str,
560 Utf8,
561 StringArray
562 );
563 };
564
565 ($INPUT:expr, $LENGTH:expr, $REPLACE:expr, $EXPECTED:expr) => {
566 test_function!(
568 LPadFunc::new(),
569 vec![
570 ColumnarValue::Scalar(ScalarValue::Utf8($INPUT)),
571 ColumnarValue::Scalar($LENGTH),
572 ColumnarValue::Scalar(ScalarValue::Utf8($REPLACE))
573 ],
574 $EXPECTED,
575 &str,
576 Utf8,
577 StringArray
578 );
579 test_function!(
581 LPadFunc::new(),
582 vec![
583 ColumnarValue::Scalar(ScalarValue::Utf8($INPUT)),
584 ColumnarValue::Scalar($LENGTH),
585 ColumnarValue::Scalar(ScalarValue::LargeUtf8($REPLACE))
586 ],
587 $EXPECTED,
588 &str,
589 Utf8,
590 StringArray
591 );
592 test_function!(
594 LPadFunc::new(),
595 vec![
596 ColumnarValue::Scalar(ScalarValue::Utf8($INPUT)),
597 ColumnarValue::Scalar($LENGTH),
598 ColumnarValue::Scalar(ScalarValue::Utf8View($REPLACE))
599 ],
600 $EXPECTED,
601 &str,
602 Utf8,
603 StringArray
604 );
605
606 test_function!(
608 LPadFunc::new(),
609 vec![
610 ColumnarValue::Scalar(ScalarValue::LargeUtf8($INPUT)),
611 ColumnarValue::Scalar($LENGTH),
612 ColumnarValue::Scalar(ScalarValue::Utf8($REPLACE))
613 ],
614 $EXPECTED,
615 &str,
616 LargeUtf8,
617 LargeStringArray
618 );
619 test_function!(
621 LPadFunc::new(),
622 vec![
623 ColumnarValue::Scalar(ScalarValue::LargeUtf8($INPUT)),
624 ColumnarValue::Scalar($LENGTH),
625 ColumnarValue::Scalar(ScalarValue::LargeUtf8($REPLACE))
626 ],
627 $EXPECTED,
628 &str,
629 LargeUtf8,
630 LargeStringArray
631 );
632 test_function!(
634 LPadFunc::new(),
635 vec![
636 ColumnarValue::Scalar(ScalarValue::LargeUtf8($INPUT)),
637 ColumnarValue::Scalar($LENGTH),
638 ColumnarValue::Scalar(ScalarValue::Utf8View($REPLACE))
639 ],
640 $EXPECTED,
641 &str,
642 LargeUtf8,
643 LargeStringArray
644 );
645
646 test_function!(
648 LPadFunc::new(),
649 vec![
650 ColumnarValue::Scalar(ScalarValue::Utf8View($INPUT)),
651 ColumnarValue::Scalar($LENGTH),
652 ColumnarValue::Scalar(ScalarValue::Utf8($REPLACE))
653 ],
654 $EXPECTED,
655 &str,
656 Utf8,
657 StringArray
658 );
659 test_function!(
661 LPadFunc::new(),
662 vec![
663 ColumnarValue::Scalar(ScalarValue::Utf8View($INPUT)),
664 ColumnarValue::Scalar($LENGTH),
665 ColumnarValue::Scalar(ScalarValue::LargeUtf8($REPLACE))
666 ],
667 $EXPECTED,
668 &str,
669 Utf8,
670 StringArray
671 );
672 test_function!(
674 LPadFunc::new(),
675 vec![
676 ColumnarValue::Scalar(ScalarValue::Utf8View($INPUT)),
677 ColumnarValue::Scalar($LENGTH),
678 ColumnarValue::Scalar(ScalarValue::Utf8View($REPLACE))
679 ],
680 $EXPECTED,
681 &str,
682 Utf8,
683 StringArray
684 );
685 };
686 }
687
688 #[test]
689 fn test_functions() -> Result<()> {
690 test_lpad!(
691 Some("josé".into()),
692 ScalarValue::Int64(Some(5i64)),
693 Ok(Some(" josé"))
694 );
695 test_lpad!(
696 Some("hi".into()),
697 ScalarValue::Int64(Some(5i64)),
698 Ok(Some(" hi"))
699 );
700 test_lpad!(
701 Some("hi".into()),
702 ScalarValue::Int64(Some(0i64)),
703 Ok(Some(""))
704 );
705 test_lpad!(Some("hi".into()), ScalarValue::Int64(None), Ok(None));
706 test_lpad!(None, ScalarValue::Int64(Some(5i64)), Ok(None));
707 test_lpad!(
708 Some("hi".into()),
709 ScalarValue::Int64(Some(5i64)),
710 Some("xy".into()),
711 Ok(Some("xyxhi"))
712 );
713 test_lpad!(
714 Some("hi".into()),
715 ScalarValue::Int64(Some(21i64)),
716 Some("abcdef".into()),
717 Ok(Some("abcdefabcdefabcdefahi"))
718 );
719 test_lpad!(
720 Some("hi".into()),
721 ScalarValue::Int64(Some(5i64)),
722 Some(" ".into()),
723 Ok(Some(" hi"))
724 );
725 test_lpad!(
726 Some("hi".into()),
727 ScalarValue::Int64(Some(5i64)),
728 Some("".into()),
729 Ok(Some("hi"))
730 );
731 test_lpad!(
732 None,
733 ScalarValue::Int64(Some(5i64)),
734 Some("xy".into()),
735 Ok(None)
736 );
737 test_lpad!(
738 Some("hi".into()),
739 ScalarValue::Int64(None),
740 Some("xy".into()),
741 Ok(None)
742 );
743 test_lpad!(
744 Some("hi".into()),
745 ScalarValue::Int64(Some(5i64)),
746 None,
747 Ok(None)
748 );
749 test_lpad!(
750 Some("hello".into()),
751 ScalarValue::Int64(Some(2i64)),
752 Ok(Some("he"))
753 );
754 test_lpad!(
755 Some("hi".into()),
756 ScalarValue::Int64(Some(6i64)),
757 Some("xy".into()),
758 Ok(Some("xyxyhi"))
759 );
760 test_lpad!(
761 Some("josé".into()),
762 ScalarValue::Int64(Some(10i64)),
763 Some("xy".into()),
764 Ok(Some("xyxyxyjosé"))
765 );
766 test_lpad!(
767 Some("josé".into()),
768 ScalarValue::Int64(Some(10i64)),
769 Some("éñ".into()),
770 Ok(Some("éñéñéñjosé"))
771 );
772
773 #[cfg(not(feature = "unicode_expressions"))]
774 test_lpad!(
775 Some("josé".into()),
776 ScalarValue::Int64(Some(5i64)),
777 internal_err!(
778 "function lpad requires compilation with feature flag: unicode_expressions."
779 )
780 );
781
782 Ok(())
783 }
784}