From 4506127f660e28aba45ce5d69169eaaea1f1c4bb Mon Sep 17 00:00:00 2001
From: Sehrope Sarkuni <sehrope@jackdb.com>
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

