]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Add min() and max() aggregate support for uuid.
authorMasahiko Sawada <msawada@postgresql.org>
Wed, 1 Jul 2026 18:42:54 +0000 (11:42 -0700)
committerMasahiko Sawada <msawada@postgresql.org>
Wed, 1 Jul 2026 18:42:54 +0000 (11:42 -0700)
The uuid type already has a full set of comparison operators and a
btree operator class, so it is totally ordered.  min() and max() were
the only common aggregates missing for it. Add the uuid_larger() and
uuid_smaller() support functions and register the min(uuid) and
max(uuid) aggregates that use them.

uuid values are compared lexicographically over their 128 bits.  For
UUIDv7, whose most significant bits encode a Unix timestamp, this
coincides with chronological order, so min() and max() return the
oldest and newest values.

Bump catalog version.

Author: Tristan Partin <tristan@partin.io>
Reviewed-by: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Reviewed-by: Zsolt Parragi <zsolt.parragi@percona.com>
Reviewed-by: Masahiko Sawada <sawada.mshk@gmail.com>
Discussion: https://postgr.es/m/DJGML0T9FCDV.3VA29JLODXEHZ@partin.io

doc/src/sgml/datatype.sgml
doc/src/sgml/func/func-aggregate.sgml
src/backend/utils/adt/uuid.c
src/include/catalog/catversion.h
src/include/catalog/pg_aggregate.dat
src/include/catalog/pg_proc.dat
src/test/regress/expected/opr_sanity.out
src/test/regress/expected/uuid.out
src/test/regress/sql/uuid.sql

index d8d91678e86d4d5a501e35c78757b6429828f5a1..cc32f2e8165a5e307627fdbc0b1b8a41074736fa 100644 (file)
@@ -4413,6 +4413,10 @@ SELECT to_tsvector( 'postgraduate' ), to_tsquery( 'postgres:*' );
     using the UUIDv4 and UUIDv7 algorithms.  Alternatively, UUID values can be generated
     outside of the database using any algorithm.  The data type <type>uuid</type> can be used
     to store any UUID, regardless of the origin and the UUID version.
+    UUIDs are compared lexicographically on their 128-bit value.  For UUIDv7 values,
+    which embed a Unix timestamp in the most significant bits, this ordering corresponds
+    to chronological order, making them suitable for use with aggregate functions such as
+    <function>min()</function> and <function>max()</function>.
    </para>
 
    <para>
index 8b5eaeb2e94b636c8d5bf226ee2dfb01b84f4cd0..6a6a4d7e32e9bb38c119308b9a49752b2c817046 100644 (file)
         values.  Available for any numeric, string, date/time, or enum type,
         as well as <type>bytea</type>, <type>inet</type>, <type>interval</type>,
         <type>money</type>, <type>oid</type>, <type>oid8</type>,
-        <type>pg_lsn</type>, <type>tid</type>, <type>xid8</type>,
+        <type>pg_lsn</type>, <type>tid</type>, <type>uuid</type>,
+        <type>xid8</type>,
         and also arrays and composite types containing sortable data types.
        </para></entry>
        <entry>Yes</entry>
         values.  Available for any numeric, string, date/time, or enum type,
         as well as <type>bytea</type>, <type>inet</type>, <type>interval</type>,
         <type>money</type>, <type>oid</type>, <type>oid8</type>,
-        <type>pg_lsn</type>, <type>tid</type>, <type>xid8</type>,
+        <type>pg_lsn</type>, <type>tid</type>, <type>uuid</type>,
+        <type>xid8</type>,
         and also arrays and composite types containing sortable data types.
        </para></entry>
        <entry>Yes</entry>
index 6ee3752ac78a78b8bd079ad75dd39f6e9f90eb3f..2d33ba2640bef007a8a8da26b5a73ac41f377eff 100644 (file)
@@ -270,6 +270,24 @@ uuid_cmp(PG_FUNCTION_ARGS)
        PG_RETURN_INT32(uuid_internal_cmp(arg1, arg2));
 }
 
+Datum
+uuid_larger(PG_FUNCTION_ARGS)
+{
+       pg_uuid_t  *arg1 = PG_GETARG_UUID_P(0);
+       pg_uuid_t  *arg2 = PG_GETARG_UUID_P(1);
+
+       PG_RETURN_UUID_P((uuid_internal_cmp(arg1, arg2) > 0) ? arg1 : arg2);
+}
+
+Datum
+uuid_smaller(PG_FUNCTION_ARGS)
+{
+       pg_uuid_t  *arg1 = PG_GETARG_UUID_P(0);
+       pg_uuid_t  *arg2 = PG_GETARG_UUID_P(1);
+
+       PG_RETURN_UUID_P((uuid_internal_cmp(arg1, arg2) < 0) ? arg1 : arg2);
+}
+
 /*
  * Sort support strategy routine
  */
index e06eb2abbcbbc8750105f76aa32aa3178bef383b..227d85762a1ed42c5477da68a125eb9d4519c5a2 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     202606302
+#define CATALOG_VERSION_NO     202607011
 
 #endif
index 5fd38fd4f23eee81e0f75628f23f7d04dd8779ed..7bce36ac9c41201ce96b209f71edd01e52d99aba 100644 (file)
 { aggfnoid => 'max(bytea)', aggtransfn => 'bytea_larger',
   aggcombinefn => 'bytea_larger', aggsortop => '>(bytea,bytea)',
   aggtranstype => 'bytea' },
+{ aggfnoid => 'max(uuid)', aggtransfn => 'uuid_larger',
+  aggcombinefn => 'uuid_larger', aggsortop => '>(uuid,uuid)',
+  aggtranstype => 'uuid' },
 
 # min
 { aggfnoid => 'min(int8)', aggtransfn => 'int8smaller',
 { aggfnoid => 'min(bytea)', aggtransfn => 'bytea_smaller',
   aggcombinefn => 'bytea_smaller', aggsortop => '<(bytea,bytea)',
   aggtranstype => 'bytea' },
+{ aggfnoid => 'min(uuid)', aggtransfn => 'uuid_smaller',
+  aggcombinefn => 'uuid_smaller', aggsortop => '<(uuid,uuid)',
+  aggtranstype => 'uuid' },
 
 # count
 { aggfnoid => 'count(any)', aggtransfn => 'int8inc_any',
index 73bb7fbb4304fddb23aec4faa35e7d2c446f2319..9e24b2f6299d3de60079632fe9bd2de3e02a6148 100644 (file)
 { oid => '6395', descr => 'maximum value of all bytea input values',
   proname => 'max', prokind => 'a', proisstrict => 'f', prorettype => 'bytea',
   proargtypes => 'bytea', prosrc => 'aggregate_dummy' },
+{ oid => '6517', descr => 'maximum value of all UUID input values',
+  proname => 'max', prokind => 'a', proisstrict => 'f', prorettype => 'uuid',
+  proargtypes => 'uuid', prosrc => 'aggregate_dummy' },
 
 { oid => '2131', descr => 'minimum value of all bigint input values',
   proname => 'min', prokind => 'a', proisstrict => 'f', prorettype => 'int8',
 { oid => '6396', descr => 'minimum value of all bytea input values',
   proname => 'min', prokind => 'a', proisstrict => 'f', prorettype => 'bytea',
   proargtypes => 'bytea', prosrc => 'aggregate_dummy' },
+{ oid => '6518', descr => 'minimum value of all UUID input values',
+  proname => 'min', prokind => 'a', proisstrict => 'f', prorettype => 'uuid',
+  proargtypes => 'uuid', prosrc => 'aggregate_dummy' },
 
 # count has two forms: count(any) and count(*)
 { oid => '2147',
 { oid => '2960', descr => 'less-equal-greater',
   proname => 'uuid_cmp', proleakproof => 't', prorettype => 'int4',
   proargtypes => 'uuid uuid', prosrc => 'uuid_cmp' },
+{ oid => '6519', descr => 'larger of the two', proname => 'uuid_larger',
+  proleakproof => 't', prorettype => 'uuid', proargtypes => 'uuid uuid',
+  prosrc => 'uuid_larger' },
+{ oid => '6520', descr => 'smaller of the two', proname => 'uuid_smaller',
+  proleakproof => 't', prorettype => 'uuid', proargtypes => 'uuid uuid',
+  prosrc => 'uuid_smaller' },
 { oid => '3300', descr => 'sort support',
   proname => 'uuid_sortsupport', prorettype => 'void',
   proargtypes => 'internal', prosrc => 'uuid_sortsupport' },
index 9f5a954da92399c2fc6c04e5a7bb45db87609d42..6b519a65cc93057653583808ee1df31eeab6b13c 100644 (file)
@@ -890,6 +890,8 @@ btoid8cmp(oid8,oid8)
 bytea(uuid)
 tid_block(tid)
 tid_offset(tid)
+uuid_larger(uuid,uuid)
+uuid_smaller(uuid,uuid)
 -- Check that functions without argument are not marked as leakproof.
 SELECT p1.oid::regprocedure
 FROM pg_proc p1 JOIN pg_namespace pn
index 9c5dda9e9abb1db7872b455091ed3a6f2d9fbe9c..cd7cd8b711ce208237e0d9139b76bcbda1f8992d 100644 (file)
@@ -130,6 +130,13 @@ SELECT COUNT(*) FROM guid1 WHERE guid_field >= '22222222-2222-2222-2222-22222222
      2
 (1 row)
 
+-- min() and max() aggregate test
+SELECT MIN(guid_field), MAX(guid_field) FROM guid1;
+                 min                  |                 max                  
+--------------------------------------+--------------------------------------
+ 11111111-1111-1111-1111-111111111111 | 3f3e3c3b-3a30-3938-3736-353433a2313e
+(1 row)
+
 -- btree and hash index creation test
 CREATE INDEX guid1_btree ON guid1 USING BTREE (guid_field);
 CREATE INDEX guid1_hash  ON guid1 USING HASH  (guid_field);
index 8cc2ad40614ae1873c6edca0a068f38212b5ec46..d0481a15022513505bd0dda7ca7e5f80e762b435 100644 (file)
@@ -63,6 +63,9 @@ SELECT COUNT(*) FROM guid1 WHERE guid_field > '22222222-2222-2222-2222-222222222
 -- >= operator test
 SELECT COUNT(*) FROM guid1 WHERE guid_field >= '22222222-2222-2222-2222-222222222222';
 
+-- min() and max() aggregate test
+SELECT MIN(guid_field), MAX(guid_field) FROM guid1;
+
 -- btree and hash index creation test
 CREATE INDEX guid1_btree ON guid1 USING BTREE (guid_field);
 CREATE INDEX guid1_hash  ON guid1 USING HASH  (guid_field);