From: Masahiko Sawada Date: Wed, 1 Jul 2026 18:42:54 +0000 (-0700) Subject: Add min() and max() aggregate support for uuid. X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=2e606d75c0bf9c867b51ad228eae384a9d1de21a;p=thirdparty%2Fpostgresql.git Add min() and max() aggregate support for uuid. 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 Reviewed-by: Bharath Rupireddy Reviewed-by: Zsolt Parragi Reviewed-by: Masahiko Sawada Discussion: https://postgr.es/m/DJGML0T9FCDV.3VA29JLODXEHZ@partin.io --- diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index d8d91678e86..cc32f2e8165 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -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 uuid 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 + min() and max(). diff --git a/doc/src/sgml/func/func-aggregate.sgml b/doc/src/sgml/func/func-aggregate.sgml index 8b5eaeb2e94..6a6a4d7e32e 100644 --- a/doc/src/sgml/func/func-aggregate.sgml +++ b/doc/src/sgml/func/func-aggregate.sgml @@ -509,7 +509,8 @@ values. Available for any numeric, string, date/time, or enum type, as well as bytea, inet, interval, money, oid, oid8, - pg_lsn, tid, xid8, + pg_lsn, tid, uuid, + xid8, and also arrays and composite types containing sortable data types. Yes @@ -528,7 +529,8 @@ values. Available for any numeric, string, date/time, or enum type, as well as bytea, inet, interval, money, oid, oid8, - pg_lsn, tid, xid8, + pg_lsn, tid, uuid, + xid8, and also arrays and composite types containing sortable data types. Yes diff --git a/src/backend/utils/adt/uuid.c b/src/backend/utils/adt/uuid.c index 6ee3752ac78..2d33ba2640b 100644 --- a/src/backend/utils/adt/uuid.c +++ b/src/backend/utils/adt/uuid.c @@ -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 */ diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index e06eb2abbcb..227d85762a1 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -57,6 +57,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202606302 +#define CATALOG_VERSION_NO 202607011 #endif diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat index 5fd38fd4f23..7bce36ac9c4 100644 --- a/src/include/catalog/pg_aggregate.dat +++ b/src/include/catalog/pg_aggregate.dat @@ -167,6 +167,9 @@ { 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', @@ -244,6 +247,9 @@ { 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', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 73bb7fbb430..9e24b2f6299 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -7117,6 +7117,9 @@ { 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', @@ -7193,6 +7196,9 @@ { 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', @@ -9623,6 +9629,12 @@ { 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' }, diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 9f5a954da92..6b519a65cc9 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -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 diff --git a/src/test/regress/expected/uuid.out b/src/test/regress/expected/uuid.out index 9c5dda9e9ab..cd7cd8b711c 100644 --- a/src/test/regress/expected/uuid.out +++ b/src/test/regress/expected/uuid.out @@ -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); diff --git a/src/test/regress/sql/uuid.sql b/src/test/regress/sql/uuid.sql index 8cc2ad40614..d0481a15022 100644 --- a/src/test/regress/sql/uuid.sql +++ b/src/test/regress/sql/uuid.sql @@ -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);