From 9e3fdc5f2470ca678773db017ffc0b22775a82d1 Mon Sep 17 00:00:00 2001
From: Aleksander Alekseev <aleksander@timescale.com>
Date: Sat, 23 Mar 2024 13:16:08 +0300
Subject: [PATCH v3] Support min(record) and max(record) aggregates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Aleksander Alekseev, reviewed by Tom Lane, Michael Paquier.
Per report by Viliam Ďurina.
Discussion: https://postgr.es/m/CAO%3DiB8L4WYSNxCJ8GURRjQsrXEQ2-zn3FiCsh2LMqvWq2WcONg%40mail.gmail.com
---
 doc/src/sgml/func.sgml                   |  4 ++--
 src/backend/utils/adt/rowtypes.c         | 17 ++++++++++++++++
 src/include/catalog/pg_aggregate.dat     |  6 ++++++
 src/include/catalog/pg_proc.dat          | 12 ++++++++++++
 src/test/regress/expected/aggregates.out | 25 ++++++++++++++++++++++++
 src/test/regress/sql/aggregates.sql      |  6 ++++++
 6 files changed, 68 insertions(+), 2 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 93ee3d4b60..0a9c759793 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21979,7 +21979,7 @@ SELECT NULLIF(value, '(none)') ...
         as well as <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.
+        and also arrays and records of any of these types.
        </para></entry>
        <entry>Yes</entry>
       </row>
@@ -21998,7 +21998,7 @@ SELECT NULLIF(value, '(none)') ...
         as well as <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.
+        and also arrays and records of any of these types.
        </para></entry>
        <entry>Yes</entry>
       </row>
diff --git a/src/backend/utils/adt/rowtypes.c b/src/backend/utils/adt/rowtypes.c
index 0214c23a1d..42b424eaf8 100644
--- a/src/backend/utils/adt/rowtypes.c
+++ b/src/backend/utils/adt/rowtypes.c
@@ -1315,6 +1315,23 @@ btrecordcmp(PG_FUNCTION_ARGS)
 	PG_RETURN_INT32(record_cmp(fcinfo));
 }
 
+Datum
+record_larger(PG_FUNCTION_ARGS)
+{
+	if (record_cmp(fcinfo) > 0)
+		PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+	else
+		PG_RETURN_DATUM(PG_GETARG_DATUM(1));
+}
+
+Datum
+record_smaller(PG_FUNCTION_ARGS)
+{
+	if (record_cmp(fcinfo) < 0)
+		PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+	else
+		PG_RETURN_DATUM(PG_GETARG_DATUM(1));
+}
 
 /*
  * record_image_cmp :
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 5f13532abc..5467bb17b5 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(record)', aggtransfn => 'record_larger',
+  aggcombinefn => 'record_larger', aggsortop => '>(record,record)',
+  aggtranstype => 'record' },
 
 # 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(record)', aggtransfn => 'record_smaller',
+  aggcombinefn => 'record_smaller', aggsortop => '<(record,record)',
+  aggtranstype => 'record' },
 
 # count
 { aggfnoid => 'count(any)', aggtransfn => 'int8inc_any',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0bf413fe05..4c25897363 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6769,6 +6769,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 => '8595', descr => 'maximum value of all record input values',
+  proname => 'max', prokind => 'a', proisstrict => 'f', prorettype => 'record',
+  proargtypes => 'record', prosrc => 'aggregate_dummy' },
 
 { oid => '2131', descr => 'minimum value of all bigint input values',
   proname => 'min', prokind => 'a', proisstrict => 'f', prorettype => 'int8',
@@ -6839,6 +6842,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 => '8596', descr => 'minimum value of all record input values',
+  proname => 'min', prokind => 'a', proisstrict => 'f', prorettype => 'record',
+  proargtypes => 'record', prosrc => 'aggregate_dummy' },
 
 # count has two forms: count(any) and count(*)
 { oid => '2147',
@@ -10352,6 +10358,12 @@
 { oid => '2987', descr => 'less-equal-greater',
   proname => 'btrecordcmp', prorettype => 'int4',
   proargtypes => 'record record', prosrc => 'btrecordcmp' },
+{ oid => '8597', descr => 'larger of two',
+  proname => 'record_larger', prorettype => 'record', proargtypes => 'record record',
+  prosrc => 'record_larger' },
+{ oid => '8598', descr => 'smaller of two',
+  proname => 'record_smaller', prorettype => 'record', proargtypes => 'record record',
+  prosrc => 'record_smaller' },
 
 { oid => '6192', descr => 'hash',
   proname => 'hash_record', prorettype => 'int4', proargtypes => 'record',
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 1c1ca7573a..a5596ab210 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -269,6 +269,31 @@ SELECT stddev_pop('nan'::numeric), stddev_samp('nan'::numeric);
         NaN |            
 (1 row)
 
+-- verify correct results for min(record) and max(record) aggregates
+SELECT max(row(a,b)) FROM aggtest;
+     max      
+--------------
+ (100,99.097)
+(1 row)
+
+SELECT max(row(b,a)) FROM aggtest;
+     max     
+-------------
+ (324.78,42)
+(1 row)
+
+SELECT min(row(a,b)) FROM aggtest;
+     min     
+-------------
+ (0,0.09561)
+(1 row)
+
+SELECT min(row(b,a)) FROM aggtest;
+     min     
+-------------
+ (0.09561,0)
+(1 row)
+
 -- verify correct results for null and NaN inputs
 select sum(null::int4) from generate_series(1,3);
  sum 
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 1a18ca3d8f..ca6d1bcfb7 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -78,6 +78,12 @@ SELECT stddev_pop('inf'::numeric), stddev_samp('inf'::numeric);
 SELECT var_pop('nan'::numeric), var_samp('nan'::numeric);
 SELECT stddev_pop('nan'::numeric), stddev_samp('nan'::numeric);
 
+-- verify correct results for min(record) and max(record) aggregates
+SELECT max(row(a,b)) FROM aggtest;
+SELECT max(row(b,a)) FROM aggtest;
+SELECT min(row(a,b)) FROM aggtest;
+SELECT min(row(b,a)) FROM aggtest;
+
 -- verify correct results for null and NaN inputs
 select sum(null::int4) from generate_series(1,3);
 select sum(null::int8) from generate_series(1,3);
-- 
2.45.2

