Subject: Re: [PATCH] Add min/max aggregate functions to BYTEA

From: Marat Buharov <[email-masked]>
Date: Wed, 3 Jul 2024 17:17:39 +0300

V2 patch with fixed tests

--

With best regards,
Marat Bukharov

ср, 3 июл. 2024 г. в 16:03, Marat Buharov <[email-masked]>:
>
> Hello. BYTEA type has the ability to use comparison operations. But it
> is absent of min/max aggregate functions. They are nice to have to
> provide consistency with the TEXT type.
>
>
> --
>
> With best regards,
> Marat Bukharov
From f6895fd61696f65bdf7a1b7e2508228468aa5caf Mon Sep 17 00:00:00 2001
From: Marat Bukharov <[email-masked]>
Date: Wed, 3 Jul 2024 17:11:11 +0300
Subject: [PATCH v2] add bytea agg funcs

---
 doc/src/sgml/func.sgml                   |  4 +--
 src/backend/utils/adt/varlena.c          | 38 ++++++++++++++++++++++++
 src/include/catalog/pg_aggregate.dat     |  6 ++++
 src/include/catalog/pg_proc.dat          | 13 ++++++++
 src/test/regress/expected/aggregates.out | 14 ++++++++-
 src/test/regress/expected/opr_sanity.out |  2 ++
 src/test/regress/sql/aggregates.sql      |  5 +++-
 7 files changed, 78 insertions(+), 4 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f1f22a1960..891f7a4259 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21976,7 +21976,7 @@ SELECT NULLIF(value, '(none)') ...
        <para>
         Computes the maximum of the non-null input
         values.  Available for any numeric, string, date/time, or enum type,
-        as well as <type>inet</type>, <type>interval</type>,
+        as well as <type>bytea</type>, <type>inet</type>, <type>interval</type>,
         <type>money</type>, <type>oid</type>, <type>pg_lsn</type>,
         <type>tid</type>, <type>xid8</type>,
         and arrays of any of these types.
@@ -21995,7 +21995,7 @@ SELECT NULLIF(value, '(none)') ...
        <para>
         Computes the minimum of the non-null input
         values.  Available for any numeric, string, date/time, or enum type,
-        as well as <type>inet</type>, <type>interval</type>,
+        as well as <type>bytea</type>, <type>inet</type>, <type>interval</type>,
         <type>money</type>, <type>oid</type>, <type>pg_lsn</type>,
         <type>tid</type>, <type>xid8</type>,
         and arrays of any of these types.
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index d2e2e9bbba..408fff0aff 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -3956,6 +3956,44 @@ byteacmp(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(cmp);
 }
 
+Datum
+bytea_larger(PG_FUNCTION_ARGS)
+{
+	bytea	   *arg1 = PG_GETARG_BYTEA_PP(0);
+	bytea	   *arg2 = PG_GETARG_BYTEA_PP(1);
+	bytea	   *result;
+	int			len1,
+				len2;
+	int			cmp;
+
+	len1 = VARSIZE_ANY_EXHDR(arg1);
+	len2 = VARSIZE_ANY_EXHDR(arg2);
+
+	cmp = memcmp(VARDATA_ANY(arg1), VARDATA_ANY(arg2), Min(len1, len2));
+	result = ((cmp > 0) || ((cmp == 0) && (len1 >= len2)) ? arg1 : arg2);
+
+	PG_RETURN_BYTEA_P(result);
+}
+
+Datum
+bytea_smaller(PG_FUNCTION_ARGS)
+{
+	bytea	   *arg1 = PG_GETARG_TEXT_PP(0);
+	bytea	   *arg2 = PG_GETARG_TEXT_PP(1);
+	bytea	   *result;
+	int			len1,
+				len2;
+	int			cmp;
+
+	len1 = VARSIZE_ANY_EXHDR(arg1);
+	len2 = VARSIZE_ANY_EXHDR(arg2);
+
+	cmp = memcmp(VARDATA_ANY(arg1), VARDATA_ANY(arg2), Min(len1, len2));
+	result = ((cmp < 0) || ((cmp == 0) && (len1 <= len2)) ? arg1 : arg2);
+
+	PG_RETURN_BYTEA_P(result);
+}
+
 Datum
 bytea_sortsupport(PG_FUNCTION_ARGS)
 {
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 5f13532abc..970d9a70fd 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -158,6 +158,9 @@
 { aggfnoid => 'max(xid8)', aggtransfn => 'xid8_larger',
   aggcombinefn => 'xid8_larger', aggsortop => '>(xid8,xid8)',
   aggtranstype => 'xid8' },
+{ aggfnoid => 'max(bytea)', aggtransfn => 'bytea_larger',
+  aggcombinefn => 'bytea_larger', aggsortop => '>(bytea,bytea)',
+  aggtranstype => 'bytea' },
 
 # min
 { aggfnoid => 'min(int8)', aggtransfn => 'int8smaller',
@@ -226,6 +229,9 @@
 { aggfnoid => 'min(xid8)', aggtransfn => 'xid8_smaller',
   aggcombinefn => 'xid8_smaller', aggsortop => '<(xid8,xid8)',
   aggtranstype => 'xid8' },
+{ aggfnoid => 'min(bytea)', aggtransfn => 'bytea_smaller',
+  aggcombinefn => 'bytea_smaller', aggsortop => '<(bytea,bytea)',
+  aggtranstype => 'bytea' },
 
 # count
 { aggfnoid => 'count(any)', aggtransfn => 'int8inc_any',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d4ac578ae6..f16f17ef3a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1278,6 +1278,13 @@
   proname => 'text_smaller', proleakproof => 't', prorettype => 'text',
   proargtypes => 'text text', prosrc => 'text_smaller' },
 
+{ oid => '6347', descr => 'larger of two',
+  proname => 'bytea_larger', proleakproof => 't', prorettype => 'bytea',
+  proargtypes => 'bytea bytea', prosrc => 'bytea_larger' },
+{ oid => '6348', descr => 'smaller of two',
+  proname => 'bytea_smaller', proleakproof => 't', prorettype => 'bytea',
+  proargtypes => 'bytea bytea', prosrc => 'bytea_smaller' },
+
 { oid => '460', descr => 'I/O',
   proname => 'int8in', prorettype => 'int8', proargtypes => 'cstring',
   prosrc => 'int8in' },
@@ -6764,6 +6771,9 @@
 { oid => '5099', descr => 'maximum value of all xid8 input values',
   proname => 'max', prokind => 'a', proisstrict => 'f', prorettype => 'xid8',
   proargtypes => 'xid8', prosrc => 'aggregate_dummy' },
+{ oid => '6349', descr => 'maximum value of all bytea input values',
+  proname => 'max', prokind => 'a', proisstrict => 'f', prorettype => 'bytea',
+  proargtypes => 'bytea', prosrc => 'aggregate_dummy' },
 
 { oid => '2131', descr => 'minimum value of all bigint input values',
   proname => 'min', prokind => 'a', proisstrict => 'f', prorettype => 'int8',
@@ -6834,6 +6844,9 @@
 { oid => '5100', descr => 'minimum value of all xid8 input values',
   proname => 'min', prokind => 'a', proisstrict => 'f', prorettype => 'xid8',
   proargtypes => 'xid8', prosrc => 'aggregate_dummy' },
+{ oid => '6350', descr => 'minimum value of all bytea input values',
+  proname => 'min', prokind => 'a', proisstrict => 'f', prorettype => 'bytea',
+  proargtypes => 'bytea', prosrc => 'aggregate_dummy' },
 
 # count has two forms: count(any) and count(*)
 { oid => '2147',
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 1c1ca7573a..db741d0c36 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1925,7 +1925,7 @@ select string_agg(distinct f1::text, ',' order by f1::text) from varchar_tbl;  -
  a,ab,abcd
 (1 row)
 
--- string_agg bytea tests
+-- string_agg, min, max bytea tests
 create table bytea_test_table(v bytea);
 select string_agg(v, '') from bytea_test_table;
  string_agg 
@@ -1959,6 +1959,18 @@ select string_agg(v, decode('ee', 'hex')) from bytea_test_table;
  \xffeeaa
 (1 row)
 
+select min(v) from bytea_test_table;
+ min  
+------
+ \xaa
+(1 row)
+
+select max(v) from bytea_test_table;
+ max  
+------
+ \xff
+(1 row)
+
 drop table bytea_test_table;
 -- Test parallel string_agg and array_agg
 create table pagg_test (x int, y int) with (autovacuum_enabled = off);
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 9d047b21b8..087e349894 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -874,6 +874,8 @@ xid8ne(xid8,xid8)
 xid8cmp(xid8,xid8)
 uuid_extract_timestamp(uuid)
 uuid_extract_version(uuid)
+bytea_larger(bytea,bytea)
+bytea_smaller(bytea,bytea)
 -- restore normal output mode
 \a\t
 -- List of functions used by libpq's fe-lobj.c
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 1a18ca3d8f..1f438dbf79 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -741,7 +741,7 @@ select string_agg(distinct f1::text, ',' order by f1) from varchar_tbl;  -- not
 select string_agg(distinct f1, ',' order by f1::text) from varchar_tbl;  -- not ok
 select string_agg(distinct f1::text, ',' order by f1::text) from varchar_tbl;  -- ok
 
--- string_agg bytea tests
+-- string_agg, min, max bytea tests
 create table bytea_test_table(v bytea);
 
 select string_agg(v, '') from bytea_test_table;
@@ -756,6 +756,9 @@ select string_agg(v, '') from bytea_test_table;
 select string_agg(v, NULL) from bytea_test_table;
 select string_agg(v, decode('ee', 'hex')) from bytea_test_table;
 
+select min(v) from bytea_test_table;
+select max(v) from bytea_test_table;
+
 drop table bytea_test_table;
 
 -- Test parallel string_agg and array_agg
-- 
2.44.2