From 4506127f660e28aba45ce5d69169eaaea1f1c4bb Mon Sep 17 00:00:00 2001 From: Sehrope Sarkuni Date: Tue, 22 Sep 2026 13:36:03 +0000 Subject: [PATCH] Use memset() for one-byte padding in lpad() and rpad() A one-byte padding string is that byte repeated, so fill the padding with memset() instead of the per-character loop, which called pg_mblen_range() and memcpy() once per output byte. pg_mblen_range() is still called once on the byte, so a lone lead byte of a multibyte character is rejected as before. The fast path is skipped when there is nothing to pad, so such a byte is still accepted in that case. --- src/backend/utils/adt/oracle_compat.c | 53 +++++++++++++++++++------- src/test/regress/expected/encoding.out | 19 +++++++++ src/test/regress/sql/encoding.sql | 7 ++++ 3 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/backend/utils/adt/oracle_compat.c b/src/backend/utils/adt/oracle_compat.c index 7422a454397..5e9459a364d 100644 --- a/src/backend/utils/adt/oracle_compat.c +++ b/src/backend/utils/adt/oracle_compat.c @@ -213,15 +213,30 @@ lpad(PG_FUNCTION_ARGS) ptr2end = ptr2 + s2len; ptr_ret = VARDATA(ret); - while (m--) + if (s2len == 1 && m > 0) { - int mlen = pg_mblen_range(ptr2, ptr2end); + /* + * A one-byte padding string is a single character repeated m times, + * so fill it in with one memset() rather than one memcpy() per + * character. pg_mblen_range() is still called once so that a lone + * lead byte of a multibyte character is rejected as before. + */ + (void) pg_mblen_range(ptr2, ptr2end); + memset(ptr_ret, *ptr2, m); + ptr_ret += m; + } + else + { + while (m--) + { + int mlen = pg_mblen_range(ptr2, ptr2end); - memcpy(ptr_ret, ptr2, mlen); - ptr_ret += mlen; - ptr2 += mlen; - if (ptr2 == ptr2end) /* wrap around at end of s2 */ - ptr2 = ptr2start; + memcpy(ptr_ret, ptr2, mlen); + ptr_ret += mlen; + ptr2 += mlen; + if (ptr2 == ptr2end) /* wrap around at end of s2 */ + ptr2 = ptr2start; + } } ptr1 = VARDATA_ANY(string1); @@ -323,15 +338,25 @@ rpad(PG_FUNCTION_ARGS) ptr2 = ptr2start = VARDATA_ANY(string2); ptr2end = ptr2 + s2len; - while (m--) + if (s2len == 1 && m > 0) + { + /* Same one-byte padding fast path as in lpad() */ + (void) pg_mblen_range(ptr2, ptr2end); + memset(ptr_ret, *ptr2, m); + ptr_ret += m; + } + else { - int mlen = pg_mblen_range(ptr2, ptr2end); + while (m--) + { + int mlen = pg_mblen_range(ptr2, ptr2end); - memcpy(ptr_ret, ptr2, mlen); - ptr_ret += mlen; - ptr2 += mlen; - if (ptr2 == ptr2end) /* wrap around at end of s2 */ - ptr2 = ptr2start; + memcpy(ptr_ret, ptr2, mlen); + ptr_ret += mlen; + ptr2 += mlen; + if (ptr2 == ptr2end) /* wrap around at end of s2 */ + ptr2 = ptr2start; + } } SET_VARSIZE(ret, ptr_ret - (char *) ret); diff --git a/src/test/regress/expected/encoding.out b/src/test/regress/expected/encoding.out index 0bb72a1df6f..8098599e302 100644 --- a/src/test/regress/expected/encoding.out +++ b/src/test/regress/expected/encoding.out @@ -60,6 +60,25 @@ SELECT reverse(good) FROM regress_encoding; éfac (1 row) +-- padding with a multibyte character +SELECT lpad(good, 7, 'é'), rpad(good, 7, 'é') FROM regress_encoding; + lpad | rpad +---------+--------- + ééécafé | caféééé +(1 row) + +-- padding with a lone lead byte of a multibyte character = error +SELECT lpad(good, 7, test_bytea_to_text('\xc3')) FROM regress_encoding; +ERROR: invalid byte sequence for encoding "UTF8": 0xc3 +SELECT rpad(good, 7, test_bytea_to_text('\xc3')) FROM regress_encoding; +ERROR: invalid byte sequence for encoding "UTF8": 0xc3 +-- no error when no padding is needed +SELECT lpad(good, 4, test_bytea_to_text('\xc3')), rpad(good, 4, test_bytea_to_text('\xc3')) FROM regress_encoding; + lpad | rpad +------+------ + café | café +(1 row) + -- invalid short mb character = error SELECT length(truncated) FROM regress_encoding; ERROR: invalid byte sequence for encoding "UTF8": 0xc3 diff --git a/src/test/regress/sql/encoding.sql b/src/test/regress/sql/encoding.sql index 26caa93a5d5..7121704f790 100644 --- a/src/test/regress/sql/encoding.sql +++ b/src/test/regress/sql/encoding.sql @@ -37,6 +37,13 @@ SELECT substring(good, 3, 1) FROM regress_encoding; SELECT substring(good, 4, 1) FROM regress_encoding; SELECT regexp_replace(good, '^caf(.)$', '\1') FROM regress_encoding; SELECT reverse(good) FROM regress_encoding; +-- padding with a multibyte character +SELECT lpad(good, 7, 'é'), rpad(good, 7, 'é') FROM regress_encoding; +-- padding with a lone lead byte of a multibyte character = error +SELECT lpad(good, 7, test_bytea_to_text('\xc3')) FROM regress_encoding; +SELECT rpad(good, 7, test_bytea_to_text('\xc3')) FROM regress_encoding; +-- no error when no padding is needed +SELECT lpad(good, 4, test_bytea_to_text('\xc3')), rpad(good, 4, test_bytea_to_text('\xc3')) FROM regress_encoding; -- invalid short mb character = error SELECT length(truncated) FROM regress_encoding; -- 2.17.1