From 3af3f4f6fdab95035172f9a89293a39cfddefbeb Mon Sep 17 00:00:00 2001
From: Sehrope Sarkuni <sehrope@jackdb.com>
Date: Sun, 20 Sep 2026 12:49:56 +0000
Subject: [PATCH 2/2] Add set_byte(bytea, int, int, int) to set a range of
 bytes

Setting several consecutive bytes to the same value currently needs
nested set_byte() calls, each of which copies the whole value.  The new
four-argument form sets count bytes starting at the given offset with
one copy and one memset.

count = 0 is allowed and returns the input unchanged, including with an
offset equal to the length.  Ranges extending past the end and negative
counts are errors.  The bounds check compares count against the
remaining length so offset + count cannot overflow.

Both forms fetch their arguments and call a shared helper.  The new
pg_proc entry gets its own prosrc because opr_sanity rejects entries
that share a prosrc but differ in pronargs.
---
 doc/src/sgml/func/func-binarystring.sgml | 11 +++-
 src/backend/utils/adt/bytea.c            | 54 +++++++++++++++----
 src/include/catalog/pg_proc.dat          |  3 ++
 src/test/regress/expected/strings.out    | 68 ++++++++++++++++++++++++
 src/test/regress/sql/strings.sql         | 18 +++++++
 5 files changed, 141 insertions(+), 13 deletions(-)

diff --git a/doc/src/sgml/func/func-binarystring.sgml b/doc/src/sgml/func/func-binarystring.sgml
index ebbf4dce7c3..f1033efa53c 100644
--- a/doc/src/sgml/func/func-binarystring.sgml
+++ b/doc/src/sgml/func/func-binarystring.sgml
@@ -481,17 +481,24 @@
         </indexterm>
         <function>set_byte</function> ( <parameter>bytes</parameter> <type>bytea</type>,
         <parameter>n</parameter> <type>integer</type>,
-        <parameter>newvalue</parameter> <type>integer</type> )
+        <parameter>newvalue</parameter> <type>integer</type>
+        <optional>, <parameter>count</parameter> <type>integer</type> </optional> )
         <returnvalue>bytea</returnvalue>
        </para>
        <para>
         Sets <link linkend="functions-zerobased-note">n'th</link> byte in
         binary string to <parameter>newvalue</parameter>, which must be
-        between 0 and 255.
+        between 0 and 255.  If <parameter>count</parameter> is specified,
+        sets <parameter>count</parameter> consecutive bytes starting at
+        byte <parameter>n</parameter>; the range must lie within the string.
        </para>
        <para>
         <literal>set_byte('\x1234567890'::bytea, 4, 64)</literal>
         <returnvalue>\x1234567840</returnvalue>
+       </para>
+       <para>
+        <literal>set_byte('\x1234567890'::bytea, 1, 255, 3)</literal>
+        <returnvalue>\x12ffffff90</returnvalue>
        </para></entry>
       </row>
 
diff --git a/src/backend/utils/adt/bytea.c b/src/backend/utils/adt/bytea.c
index 2eb3cbc7229..85f551f7e3d 100644
--- a/src/backend/utils/adt/bytea.c
+++ b/src/backend/utils/adt/bytea.c
@@ -38,6 +38,7 @@ static bytea *bytea_catenate(bytea *t1, bytea *t2);
 static bytea *bytea_substring(Datum str, int S, int L,
 							  bool length_not_specified);
 static bytea *bytea_overlay(bytea *t1, bytea *t2, int sp, int sl);
+static void bytea_set_bytes(bytea *res, int32 n, int32 newByte, int32 count);
 
 typedef struct
 {
@@ -698,7 +699,8 @@ byteaGetBit(PG_FUNCTION_ARGS)
  * byteaSetByte
  *
  * Given an instance of type 'bytea' creates a new one with
- * the Nth byte set to the given value.
+ * the Nth byte set to the given value.  byteaSetByteRange
+ * instead sets 'count' consecutive bytes starting at the Nth.
  *
  *-------------------------------------------------------------
  */
@@ -708,27 +710,57 @@ byteaSetByte(PG_FUNCTION_ARGS)
 	bytea	   *res = PG_GETARG_BYTEA_P_COPY(0);
 	int32		n = PG_GETARG_INT32(1);
 	int32		newByte = PG_GETARG_INT32(2);
-	int			len;
 
-	len = VARSIZE(res) - VARHDRSZ;
+	bytea_set_bytes(res, n, newByte, 1);
 
-	if (n < 0 || n >= len)
+	PG_RETURN_BYTEA_P(res);
+}
+
+Datum
+byteaSetByteRange(PG_FUNCTION_ARGS)
+{
+	bytea	   *res = PG_GETARG_BYTEA_P_COPY(0);
+	int32		n = PG_GETARG_INT32(1);
+	int32		newByte = PG_GETARG_INT32(2);
+	int32		count = PG_GETARG_INT32(3);
+
+	bytea_set_bytes(res, n, newByte, count);
+
+	PG_RETURN_BYTEA_P(res);
+}
+
+/* Validate and set 'count' bytes of 'res' starting at 'n', in place. */
+static void
+bytea_set_bytes(bytea *res, int32 n, int32 newByte, int32 count)
+{
+	int			len = VARSIZE(res) - VARHDRSZ;
+
+	if (count < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("count must not be negative")));
+
+	/* An empty range may start just past the last byte. */
+	if (n < 0 || n > len || (count > 0 && n == len))
 		ereport(ERROR,
 				(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
 				 errmsg("index %d out of valid range, 0..%d",
-						n, len - 1)));
+						n, count == 0 ? len : len - 1)));
+
+	/* n <= len here, so this cannot overflow */
+	if (count > len - n)
+		ereport(ERROR,
+				(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+				 errmsg("bytes %d..%" PRId64 " out of valid range, 0..%d",
+						n, (int64) n + count - 1, len - 1)));
 
 	if (newByte < 0 || newByte > 255)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("new byte must be 0..255")));
 
-	/*
-	 * Now set the byte.
-	 */
-	((unsigned char *) VARDATA(res))[n] = newByte;
-
-	PG_RETURN_BYTEA_P(res);
+	if (count > 0)
+		memset(VARDATA(res) + n, newByte, count);
 }
 
 /*-------------------------------------------------------------
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f46427258e3..45b31f05ddf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1531,6 +1531,9 @@
 { oid => '722', descr => 'set byte',
   proname => 'set_byte', prorettype => 'bytea',
   proargtypes => 'bytea int4 int4', prosrc => 'byteaSetByte' },
+{ oid => '9578', descr => 'set range of bytes',
+  proname => 'set_byte', prorettype => 'bytea',
+  proargtypes => 'bytea int4 int4 int4', prosrc => 'byteaSetByteRange' },
 { oid => '723', descr => 'get bit',
   proname => 'get_bit', prorettype => 'int4', proargtypes => 'bytea int8',
   prosrc => 'byteaGetBit' },
diff --git a/src/test/regress/expected/strings.out b/src/test/regress/expected/strings.out
index ab2b3b99768..173dc6cc9fb 100644
--- a/src/test/regress/expected/strings.out
+++ b/src/test/regress/expected/strings.out
@@ -3273,6 +3273,74 @@ SELECT set_byte('\x1234567890abcdef00'::bytea, 0, 256);  -- error
 ERROR:  new byte must be 0..255
 SELECT set_byte('\x1234567890abcdef00'::bytea, 0, -1);  -- error
 ERROR:  new byte must be 0..255
+SELECT set_byte('\x01020304'::bytea, 1, 255, 1);
+  set_byte  
+------------
+ \x01ff0304
+(1 row)
+
+SELECT set_byte('\x01020304'::bytea, 1, 255, 2);
+  set_byte  
+------------
+ \x01ffff04
+(1 row)
+
+SELECT set_byte('\x01020304'::bytea, 0, 255, 2);
+  set_byte  
+------------
+ \xffff0304
+(1 row)
+
+SELECT set_byte('\x01020304'::bytea, 2, 0, 2);
+  set_byte  
+------------
+ \x01020000
+(1 row)
+
+SELECT set_byte('\x01020304'::bytea, 0, 0, 4);
+  set_byte  
+------------
+ \x00000000
+(1 row)
+
+SELECT set_byte('\x01020304'::bytea, 1, 255, 0);
+  set_byte  
+------------
+ \x01020304
+(1 row)
+
+SELECT set_byte('\x01020304'::bytea, 4, 0, 0);
+  set_byte  
+------------
+ \x01020304
+(1 row)
+
+SELECT set_byte('\x'::bytea, 0, 0, 0);
+ set_byte 
+----------
+ \x
+(1 row)
+
+SELECT set_byte('\x01020304'::bytea, 0, 0, -1);  -- error
+ERROR:  count must not be negative
+SELECT set_byte('\x01020304'::bytea, -1, 0, 1);  -- error
+ERROR:  index -1 out of valid range, 0..3
+SELECT set_byte('\x01020304'::bytea, 5, 0, 0);  -- error
+ERROR:  index 5 out of valid range, 0..4
+SELECT set_byte('\x01020304'::bytea, 4, 0, 1);  -- error
+ERROR:  index 4 out of valid range, 0..3
+SELECT set_byte('\x01020304'::bytea, 3, 0, 2);  -- error
+ERROR:  bytes 3..4 out of valid range, 0..3
+SELECT set_byte('\x01020304'::bytea, 0, 0, 2147483647);  -- error
+ERROR:  bytes 0..2147483646 out of valid range, 0..3
+SELECT set_byte('\x01020304'::bytea, 3, 0, 2147483647);  -- error, end overflows int32
+ERROR:  bytes 3..2147483649 out of valid range, 0..3
+SELECT set_byte('\x01020304'::bytea, 0, 256, 0);  -- error, newvalue checked when count is 0
+ERROR:  new byte must be 0..255
+SELECT set_byte('\x01020304'::bytea, 0, -1, 1);  -- error
+ERROR:  new byte must be 0..255
+SELECT set_byte('\x01020304'::bytea, 0, 256, 1);  -- error
+ERROR:  new byte must be 0..255
 --
 -- conversions between bytea and integer types
 --
diff --git a/src/test/regress/sql/strings.sql b/src/test/regress/sql/strings.sql
index d1c9a64264a..60b83ed1cc6 100644
--- a/src/test/regress/sql/strings.sql
+++ b/src/test/regress/sql/strings.sql
@@ -1118,6 +1118,24 @@ SELECT set_byte('\x1234567890abcdef00'::bytea, 7, 11);
 SELECT set_byte('\x1234567890abcdef00'::bytea, 99, 11);  -- error
 SELECT set_byte('\x1234567890abcdef00'::bytea, 0, 256);  -- error
 SELECT set_byte('\x1234567890abcdef00'::bytea, 0, -1);  -- error
+SELECT set_byte('\x01020304'::bytea, 1, 255, 1);
+SELECT set_byte('\x01020304'::bytea, 1, 255, 2);
+SELECT set_byte('\x01020304'::bytea, 0, 255, 2);
+SELECT set_byte('\x01020304'::bytea, 2, 0, 2);
+SELECT set_byte('\x01020304'::bytea, 0, 0, 4);
+SELECT set_byte('\x01020304'::bytea, 1, 255, 0);
+SELECT set_byte('\x01020304'::bytea, 4, 0, 0);
+SELECT set_byte('\x'::bytea, 0, 0, 0);
+SELECT set_byte('\x01020304'::bytea, 0, 0, -1);  -- error
+SELECT set_byte('\x01020304'::bytea, -1, 0, 1);  -- error
+SELECT set_byte('\x01020304'::bytea, 5, 0, 0);  -- error
+SELECT set_byte('\x01020304'::bytea, 4, 0, 1);  -- error
+SELECT set_byte('\x01020304'::bytea, 3, 0, 2);  -- error
+SELECT set_byte('\x01020304'::bytea, 0, 0, 2147483647);  -- error
+SELECT set_byte('\x01020304'::bytea, 3, 0, 2147483647);  -- error, end overflows int32
+SELECT set_byte('\x01020304'::bytea, 0, 256, 0);  -- error, newvalue checked when count is 0
+SELECT set_byte('\x01020304'::bytea, 0, -1, 1);  -- error
+SELECT set_byte('\x01020304'::bytea, 0, 256, 1);  -- error
 
 --
 -- conversions between bytea and integer types
-- 
2.43.0

