From 20631f194e9820ba6629892b566f5d0d7a401560 Mon Sep 17 00:00:00 2001
From: Aleksander Alekseev <aleksander@timescale.com>
Date: Wed, 14 Aug 2024 12:11:47 +0300
Subject: [PATCH v3 2/2] Add get_bytes() and set_bytes() functions.

The new functions provide a convenient way of extracting integers from
bytea's and putting integer values into bytea's. Previously there were only
get_byte() and set_byte() which operate with a single byte.

Aleksander Alekseev, reviewed by Joel Jacobson, Yugo Nagata
Discussion: https://postgr.es/m/CAJ7c6TPtOp6%2BkFX5QX3fH1SVr7v65uHr-7yEJ%3DGMGQi5uhGtcA%40mail.gmail.com

BUMP CATVERSION
---
 doc/src/sgml/func.sgml                | 46 ++++++++++++++-
 src/backend/utils/adt/varlena.c       | 80 +++++++++++++++++++++++++++
 src/include/catalog/pg_proc.dat       |  6 ++
 src/test/regress/expected/strings.out | 36 ++++++++++++
 src/test/regress/sql/strings.sql      | 10 ++++
 5 files changed, 177 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 461fc3f437..8f7f65b954 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -4562,6 +4562,27 @@ SELECT format('Testing %3$s, %2$s, %s', 'one', 'two', 'three');
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>get_bytes</primary>
+        </indexterm>
+        <function>get_bytes</function> ( <parameter>bytes</parameter> <type>bytea</type>,
+        <parameter>offset</parameter> <type>integer</type>,
+        <parameter>size</parameter> <type>integer</type> )
+        <returnvalue>bigint</returnvalue>
+       </para>
+       <para>
+        Extracts <parameter>size</parameter> bytes at a given
+        <link linkend="functions-zerobased-note">offset</link>
+        from binary string.
+       </para>
+       <para>
+        <literal>get_bytes('\x0123456789ABCDEF'::bytea, 5, 2)</literal>
+        <returnvalue>43981</returnvalue>
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
@@ -4662,6 +4683,28 @@ SELECT format('Testing %3$s, %2$s, %s', 'one', 'two', 'three');
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>set_bytes</primary>
+        </indexterm>
+        <function>set_bytes</function> ( <parameter>bytes</parameter> <type>bytea</type>,
+        <parameter>offset</parameter> <type>integer</type>,
+        <parameter>size</parameter> <type>integer</type>,
+        <parameter>newvalue</parameter> <type>bigint</type> )
+        <returnvalue>bytea</returnvalue>
+       </para>
+       <para>
+        Sets <parameter>size</parameter> bytes at a given
+        <link linkend="functions-zerobased-note">offset</link>
+        in binary string to <parameter>newvalue</parameter>.
+       </para>
+       <para>
+        <literal>set_bytes('\x0123456789abcdef'::bytea, 5, 2, 0x1122)</literal>
+        <returnvalue>\x01234567891122ef</returnvalue>
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
@@ -4761,7 +4804,8 @@ SELECT format('Testing %3$s, %2$s, %s', 'one', 'two', 'three');
   </table>
 
   <para id="functions-zerobased-note">
-   Functions <function>get_byte</function> and <function>set_byte</function>
+   Functions <function>get_byte</function>, <function>set_byte</function>,
+   <function>get_bytes</function> and <function>set_bytes</function>
    number the first byte of a binary string as byte 0.
    Functions <function>get_bit</function> and <function>set_bit</function>
    number bits from the right within each byte; for example bit 0 is the least
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 7c6391a276..ff26591f63 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -3206,6 +3206,46 @@ byteaGetByte(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(byte);
 }
 
+/*-------------------------------------------------------------
+ * byteaGetBytes
+ *
+ * This routine treats "bytea" as an array of bytes.
+ * It returns the N bytes at a given offset as a bigint value.
+ *-------------------------------------------------------------
+ */
+Datum
+byteaGetBytes(PG_FUNCTION_ARGS)
+{
+	bytea	   *v = PG_GETARG_BYTEA_PP(0);
+	int32		offset = PG_GETARG_INT32(1);
+	int32		size = PG_GETARG_INT32(2);
+	int64		result = 0;
+	int			len;
+
+	if (size < 1 || size > 8)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("size %d out of valid range, 1..8",
+						size)));
+
+	len = VARSIZE_ANY_EXHDR(v);
+
+	if (offset < 0 || offset > (len-size))
+		ereport(ERROR,
+				(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+				 errmsg("index %d out of valid range, 0..%d",
+						offset, len - size)));
+
+	while (size--)
+	{
+		result = result << 8;
+		result |= ((unsigned char *) VARDATA_ANY(v))[offset];
+		offset++;
+	}
+
+	PG_RETURN_INT64(result);
+}
+
 /*-------------------------------------------------------------
  * byteaGetBit
  *
@@ -3276,6 +3316,46 @@ byteaSetByte(PG_FUNCTION_ARGS)
 	PG_RETURN_BYTEA_P(res);
 }
 
+/*-------------------------------------------------------------
+ * byteaSetBytes
+ *
+ * Given an instance of type 'bytea' creates a new one with
+ * the N bytes at a given offset set to the provided bigint value.
+ *
+ *-------------------------------------------------------------
+ */
+Datum
+byteaSetBytes(PG_FUNCTION_ARGS)
+{
+	bytea	   *res = PG_GETARG_BYTEA_P_COPY(0);
+	int32		offset = PG_GETARG_INT32(1);
+	int32		size = PG_GETARG_INT32(2);
+	int64		newValue = PG_GETARG_INT64(3);
+	int			len;
+
+	if (size < 1 || size > 8)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("size %d out of valid range, 1..8",
+						size)));
+
+	len = VARSIZE_ANY_EXHDR(res);
+
+	if (offset < 0 || offset > (len-size))
+		ereport(ERROR,
+				(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+				 errmsg("index %d out of valid range, 0..%d",
+						offset, len - size)));
+	while (size)
+	{
+		((unsigned char*) VARDATA_ANY(res))[offset+size-1] = newValue & 0xFF;
+		newValue = newValue >> 8;
+		size--;
+	}
+
+	PG_RETURN_BYTEA_P(res);
+}
+
 /*-------------------------------------------------------------
  * byteaSetBit
  *
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b8640218b0..3424c903e0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1459,6 +1459,12 @@
 { oid => '722', descr => 'set byte',
   proname => 'set_byte', prorettype => 'bytea',
   proargtypes => 'bytea int4 int4', prosrc => 'byteaSetByte' },
+{ oid => '8573', descr => 'get bytes',
+  proname => 'get_bytes', prorettype => 'int8', proargtypes => 'bytea int4 int4',
+  prosrc => 'byteaGetBytes' },
+{ oid => '8574', descr => 'set bytes',
+  proname => 'set_bytes', prorettype => 'bytea',
+  proargtypes => 'bytea int4 int4 int8', prosrc => 'byteaSetBytes' },
 { 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 709f58ad7f..c58fc9b918 100644
--- a/src/test/regress/expected/strings.out
+++ b/src/test/regress/expected/strings.out
@@ -2358,6 +2358,42 @@ SELECT set_byte('\x1234567890abcdef00'::bytea, 7, 11);
 
 SELECT set_byte('\x1234567890abcdef00'::bytea, 99, 11);  -- error
 ERROR:  index 99 out of valid range, 0..8
+SELECT get_bytes('\x1122334455667788'::bytea, 0, 8) = 0x1122334455667788;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT get_bytes('\x1122334455667788'::bytea, 1, 2) = 0x2233;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT get_bytes('\x1122334455667788'::bytea, 0, 0); -- error
+ERROR:  size 0 out of valid range, 1..8
+SELECT get_bytes('\x1122334455667788'::bytea, 0, 9); -- error
+ERROR:  size 9 out of valid range, 1..8
+SELECT get_bytes('\x1122334455667788'::bytea, 1, 8); -- error
+ERROR:  index 1 out of valid range, 0..0
+SELECT set_bytes('\x0123456789abcdef'::bytea, 0, 8, 0x1122334455667788);
+     set_bytes      
+--------------------
+ \x1122334455667788
+(1 row)
+
+SELECT set_bytes('\x1122334455667788'::bytea, 1, 2, 0xAABB);
+     set_bytes      
+--------------------
+ \x11aabb4455667788
+(1 row)
+
+SELECT set_bytes('\x0123456789abcdef'::bytea, 0, 0, 123); -- error
+ERROR:  size 0 out of valid range, 1..8
+SELECT set_bytes('\x0123456789abcdef'::bytea, 0, 9, 123); -- error
+ERROR:  size 9 out of valid range, 1..8
+SELECT set_bytes('\x0123456789abcdef'::bytea, 1, 8, 123); -- error
+ERROR:  index 1 out of valid range, 0..0
 --
 -- test behavior of escape_string_warning and standard_conforming_strings options
 --
diff --git a/src/test/regress/sql/strings.sql b/src/test/regress/sql/strings.sql
index b8858c9aea..82d677562a 100644
--- a/src/test/regress/sql/strings.sql
+++ b/src/test/regress/sql/strings.sql
@@ -750,6 +750,16 @@ SELECT get_byte('\x1234567890abcdef00'::bytea, 3);
 SELECT get_byte('\x1234567890abcdef00'::bytea, 99);  -- error
 SELECT set_byte('\x1234567890abcdef00'::bytea, 7, 11);
 SELECT set_byte('\x1234567890abcdef00'::bytea, 99, 11);  -- error
+SELECT get_bytes('\x1122334455667788'::bytea, 0, 8) = 0x1122334455667788;
+SELECT get_bytes('\x1122334455667788'::bytea, 1, 2) = 0x2233;
+SELECT get_bytes('\x1122334455667788'::bytea, 0, 0); -- error
+SELECT get_bytes('\x1122334455667788'::bytea, 0, 9); -- error
+SELECT get_bytes('\x1122334455667788'::bytea, 1, 8); -- error
+SELECT set_bytes('\x0123456789abcdef'::bytea, 0, 8, 0x1122334455667788);
+SELECT set_bytes('\x1122334455667788'::bytea, 1, 2, 0xAABB);
+SELECT set_bytes('\x0123456789abcdef'::bytea, 0, 0, 123); -- error
+SELECT set_bytes('\x0123456789abcdef'::bytea, 0, 9, 123); -- error
+SELECT set_bytes('\x0123456789abcdef'::bytea, 1, 8, 123); -- error
 
 --
 -- test behavior of escape_string_warning and standard_conforming_strings options
-- 
2.46.0

