Files
duckdb/test/sql/function/string/test_pad.test
T
Louie Duck 0c5dfc1aa4 LPAD and RPAD: copy the padding from a per-call plan instead of decoding it character by character
InsertPadding decoded one pad character per output character, flushing
the pad whenever it ran out, so a 200-character pad cost 200 decodes and
as many one-byte inserts per row. Since the decoder became an out-of-line
call (#26485) and the result size check counted the pad again per row
(#26482), lpad(x, 200, '0') took 1.75x as long on v2.0.0 as on v1.5.5
(#27846), and about ten times as long as repeat('0', 200) || x on both.

The pad's geometry is now counted once per call: its characters, the
whole copies the padding needs and the bytes of the remainder. The size
check uses the same plan instead of counting again, the buffer is
reserved to the result size, and the padding is copied whole, a single
byte at a time for a one-byte pad. LEFT and RIGHT share the function.

On an M-series Mac, one thread, 2M rows (seconds, min of 3):

                         v1.5.5  v2.0.0   main   this
  lpad(i, 18, '0')        0.131   0.151  0.145  0.098
  lpad(i, 50, '0')        0.277   0.347  0.328  0.115
  lpad(i, 200, '0')       0.984   1.227  1.114  0.126
  rpad(i, 200, '0')       0.966   1.212  1.112  0.129
  lpad(i, 200, 'ab😀')    0.979   1.313  1.264  0.628
  repeat('0', 200) || i   0.102   0.085  0.081  0.082

test_pad.test gains one-byte, one-character multibyte and longer-than-
needed pads, a multibyte pad counted in characters, and the empty pad
with and without padding needed. benchmark/micro/string/lpad.benchmark
tracks the one-byte case.
2026-10-25 21:35:30 +01:00

173 lines
3.9 KiB
SQL

# name: test/sql/function/string/test_pad.test
# description: LPAD/RPAD test
# group: [string]
# test lpad on NULLs
query TTTTTTT
select LPAD(NULL, 7, '-'), LPAD('Base', NULL, '-'), LPAD('Base', 7, NULL), LPAD(NULL, NULL, '-'), LPAD(NULL, 7, NULL), LPAD('Base', NULL, NULL), LPAD(NULL, NULL, NULL)
----
NULL NULL NULL NULL NULL NULL NULL
# test rpad on NULLs
query TTTTTTT
select RPAD(NULL, 7, '-'), RPAD('Base', NULL, '-'), RPAD('Base', 7, NULL), RPAD(NULL, NULL, '-'), RPAD(NULL, 7, NULL), RPAD('Base', NULL, NULL), RPAD(NULL, NULL, NULL)
----
NULL NULL NULL NULL NULL NULL NULL
# test lpad/rpad on scalar values
query TTTT
select LPAD('Base', 7, '-'), LPAD('Base', 4, '-'), LPAD('Base', 2, ''), LPAD('Base', -1, '-')
----
---Base Base Ba (empty)
query TTTT
select RPAD('Base', 7, '-'), RPAD('Base', 4, '-'), RPAD('Base', 2, ''), RPAD('Base', -1, '-')
----
Base--- Base Ba (empty)
query TTTT
select LPAD('Base', 7, '-|'), LPAD('Base', 6, '-|'), LPAD('Base', 5, '-|'), LPAD('Base', 4, '-|')
----
-|-Base -|Base -Base Base
query TTTT
select RPAD('Base', 7, '-|'), RPAD('Base', 6, '-|'), RPAD('Base', 5, '-|'), RPAD('Base', 4, '-|')
----
Base-|- Base-| Base- Base
query TTT
select LPAD('MotörHead', 16, 'RÄcks'), LPAD('MotörHead', 12, 'RÄcks'), LPAD('MotörHead', 10, 'RÄcks')
----
RÄcksRÄMotörHead RÄcMotörHead RMotörHead
query TTT
select RPAD('MotörHead', 16, 'RÄcks'), RPAD('MotörHead', 12, 'RÄcks'), RPAD('MotörHead', 10, 'RÄcks')
----
MotörHeadRÄcksRÄ MotörHeadRÄc MotörHeadR
# test on entire tables
statement ok
CREATE TABLE strings(a STRING, b STRING)
statement ok
INSERT INTO strings VALUES ('Hello', 'World'), ('HuLlD', NULL), ('MotörHead','RÄcks')
query TT
select LPAD(a, 16, b), RPAD(a, 16, b) FROM strings
----
WorldWorldWHello HelloWorldWorldW
NULL NULL
RÄcksRÄMotörHead MotörHeadRÄcksRÄ
# test with selection vector
query TTTT
select LPAD(a, 12, b), RPAD(a, 12, b), UCASE(a), LCASE(a) FROM strings WHERE b IS NOT NULL
----
WorldWoHello HelloWorldWo HELLO hello
RÄcMotörHead MotörHeadRÄc MOTÖRHEAD motörhead
# test incorrect usage
statement error
select LPAD()
----
statement error
select LPAD(1)
----
statement error
select LPAD(1, 2)
----
statement error
select LPAD('Hello', 10, '')
----
statement error
select LPAD('a', 100000000000000000, 0)
----
statement error
select RPAD()
----
statement error
select RPAD(1)
----
statement error
select RPAD(1, 2)
----
statement error
select RPAD('Hello', 10, '')
----
statement error
select RPAD('a', 100000000000000000, 0)
----
# results larger than the maximum string size
statement error
select LPAD('', 1100000000, '😀')
----
<REGEX>:.*Out of Range Error.*maximum supported string size.*
statement error
select RPAD('', 1100000000, '😀')
----
<REGEX>:.*Out of Range Error.*maximum supported string size.*
statement error
select LPAD('😀😀', 1073741824, '😀')
----
<REGEX>:.*Out of Range Error.*maximum supported string size.*
statement error
select RPAD('😀😀', 1073741824, '😀')
----
<REGEX>:.*Out of Range Error.*maximum supported string size.*
statement error
select LPAD('abc', 2147483647, 'xyz😀😀')
----
<REGEX>:.*Out of Range Error.*maximum supported string size.*
query I
select LPAD('ab', 7, 'xy😀')
----
xy😀xyab
query I
select RPAD('ab', 7, 'xy😀')
----
abxy😀xy
# a one-byte pad, a one-character multibyte pad, and a pad longer than the padding
query IIII
select LPAD('x', 10, '0'), LPAD('x', 4, '😀'), LPAD('x', 3, '😀ab'), RPAD('x', 3, '😀ab')
----
000000000x 😀😀😀x 😀ax x😀a
# the padding is counted in characters whatever the byte width of the pad
query III
select length(LPAD('', 1000, 'é')), strlen(LPAD('', 1000, 'é')), LPAD('ab', 5, 'é') = 'éééab'
----
1000 2000 true
# no padding needed: the pad is not read
query II
select LPAD('abc', 3, ''), RPAD('abcd', 3, '')
----
abc abc
statement error
select LPAD('abc', 4, '')
----
Insufficient padding in LPAD
statement error
select RPAD('abc', 4, '')
----
Insufficient padding in RPAD