From 50eecdd6477279785616bb4945b31b243265904e Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 22 Apr 2026 22:10:06 +0300
Subject: [PATCH 1/2] Don't allow composite type to be member of itself via
 multirange

CheckAttributeType() checks that a composite type is not made a member
of itself with ALTER TABLE ADD COLUMN or ALTER TYPE ADD ATTRIBUTE,
even indirectly via a domain, array, another composite type or a range
type. But it missed checking for multiranges. That was a simple
oversight when multiranges were added.

Discussion: xxx
Backpatch-through: 14
---
 src/backend/catalog/heap.c                | 10 ++++++++++
 src/test/regress/expected/alter_table.out |  9 +++++++++
 src/test/regress/expected/rangetypes.out  |  4 +++-
 src/test/regress/sql/alter_table.sql      |  3 +++
 src/test/regress/sql/rangetypes.sql       |  3 ++-
 5 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index ae6b7cda3dd..4a71f6a33b4 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -654,6 +654,16 @@ CheckAttributeType(const char *attname,
 						   containing_rowtypes,
 						   flags);
 	}
+	else if (att_typtype == TYPTYPE_MULTIRANGE)
+	{
+		/*
+		 * If it's a multirange, recurse to check its plain range type.
+		 */
+		CheckAttributeType(attname, get_multirange_range(atttypid),
+						   get_range_collation(atttypid),
+						   containing_rowtypes,
+						   flags);
+	}
 	else if (OidIsValid((att_typelem = get_element_type(atttypid))))
 	{
 		/*
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index dad9d36937e..fc27e22023b 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2335,6 +2335,15 @@ ERROR:  composite type recur1 cannot be made a member of itself
 create domain array_of_recur1 as recur1[];
 alter table recur1 add column f2 array_of_recur1; -- fails
 ERROR:  composite type recur1 cannot be made a member of itself
+create type range_of_recur1 as range (subtype=recur1, multirange_type_name='multirange_of_recur1');
+NOTICE:  function "range_of_recur1" will be effectively temporary
+DETAIL:  It depends on temporary type recur1.
+NOTICE:  function "range_of_recur1" will be effectively temporary
+DETAIL:  It depends on temporary type recur1.
+alter table recur1 add column f2 range_of_recur1; -- fails
+ERROR:  composite type recur1 cannot be made a member of itself
+alter table recur1 add column f2 multirange_of_recur1; -- fails
+ERROR:  composite type recur1 cannot be made a member of itself
 create temp table recur2 (f1 int, f2 recur1);
 alter table recur1 add column f2 recur2; -- fails
 ERROR:  composite type recur1 cannot be made a member of itself
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index e062a4e5c2c..b2a753fd179 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -1810,9 +1810,11 @@ select *, row_to_json(upper(t)) as u from
  ["(5,6)","(7,8)") | {"a":7,"b":8}
 (2 rows)
 
--- this must be rejected to avoid self-inclusion issues:
+-- these must be rejected to avoid self-inclusion issues:
 alter type two_ints add attribute c two_ints_range;
 ERROR:  composite type two_ints cannot be made a member of itself
+alter type two_ints add attribute c two_ints_multirange;
+ERROR:  composite type two_ints cannot be made a member of itself
 drop type two_ints cascade;
 NOTICE:  drop cascades to type two_ints_range
 --
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index f5f13bbd3e7..6aaa33b6e34 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1535,6 +1535,9 @@ alter table recur1 add column f2 recur1; -- fails
 alter table recur1 add column f2 recur1[]; -- fails
 create domain array_of_recur1 as recur1[];
 alter table recur1 add column f2 array_of_recur1; -- fails
+create type range_of_recur1 as range (subtype=recur1, multirange_type_name='multirange_of_recur1');
+alter table recur1 add column f2 range_of_recur1; -- fails
+alter table recur1 add column f2 multirange_of_recur1; -- fails
 create temp table recur2 (f1 int, f2 recur1);
 alter table recur1 add column f2 recur2; -- fails
 alter table recur1 add column f2 int;
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 5c4b0337b7a..f0354736fe5 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -576,8 +576,9 @@ select *, row_to_json(upper(t)) as u from
   (values (two_ints_range(row(1,2), row(3,4))),
           (two_ints_range(row(5,6), row(7,8)))) v(t);
 
--- this must be rejected to avoid self-inclusion issues:
+-- these must be rejected to avoid self-inclusion issues:
 alter type two_ints add attribute c two_ints_range;
+alter type two_ints add attribute c two_ints_multirange;
 
 drop type two_ints cascade;
 
-- 
2.47.3

