diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 567eb7fd6ebb42acff259e71235c651f2440a3a6..5d25ce9ec88f82e9c6da5e6e3b13f5b16773ad1c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -431,6 +431,7 @@ CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind,
 		CheckAttributeType(NameStr(tupdesc->attrs[i]->attname),
 						   tupdesc->attrs[i]->atttypid,
 						   tupdesc->attrs[i]->attcollation,
+						   NIL,	/* assume we're creating a new rowtype */
 						   allow_system_table_mods);
 	}
 }
@@ -439,15 +440,25 @@ CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind,
  *		CheckAttributeType
  *
  *		Verify that the proposed datatype of an attribute is legal.
- *		This is needed because there are types (and pseudo-types)
+ *		This is needed mainly because there are types (and pseudo-types)
  *		in the catalogs that we do not support as elements of real tuples.
+ *		We also check some other properties required of a table column.
+ *
+ * If the attribute is being proposed for addition to an existing table or
+ * composite type, pass a one-element list of the rowtype OID as
+ * containing_rowtypes.  When checking a to-be-created rowtype, it's
+ * sufficient to pass NIL, because there could not be any recursive reference
+ * to a not-yet-existing rowtype.
  * --------------------------------
  */
 void
-CheckAttributeType(const char *attname, Oid atttypid, Oid attcollation,
+CheckAttributeType(const char *attname,
+				   Oid atttypid, Oid attcollation,
+				   List *containing_rowtypes,
 				   bool allow_system_table_mods)
 {
 	char		att_typtype = get_typtype(atttypid);
+	Oid			att_typelem;
 
 	if (atttypid == UNKNOWNOID)
 	{
@@ -485,6 +496,20 @@ CheckAttributeType(const char *attname, Oid atttypid, Oid attcollation,
 		TupleDesc	tupdesc;
 		int			i;
 
+		/*
+		 * Check for self-containment.  Eventually we might be able to allow
+		 * this (just return without complaint, if so) but it's not clear how
+		 * many other places would require anti-recursion defenses before it
+		 * would be safe to allow tables to contain their own rowtype.
+		 */
+		if (list_member_oid(containing_rowtypes, atttypid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("composite type %s cannot be made a member of itself",
+							format_type_be(atttypid))));
+
+		containing_rowtypes = lcons_oid(atttypid, containing_rowtypes);
+
 		relation = relation_open(get_typ_typrelid(atttypid), AccessShareLock);
 
 		tupdesc = RelationGetDescr(relation);
@@ -495,19 +520,31 @@ CheckAttributeType(const char *attname, Oid atttypid, Oid attcollation,
 
 			if (attr->attisdropped)
 				continue;
-			CheckAttributeType(NameStr(attr->attname), attr->atttypid, attr->attcollation,
+			CheckAttributeType(NameStr(attr->attname),
+							   attr->atttypid, attr->attcollation,
+							   containing_rowtypes,
 							   allow_system_table_mods);
 		}
 
 		relation_close(relation, AccessShareLock);
+
+		containing_rowtypes = list_delete_first(containing_rowtypes);
+	}
+	else if (OidIsValid((att_typelem = get_element_type(atttypid))))
+	{
+		/*
+		 * Must recurse into array types, too, in case they are composite.
+		 */
+		CheckAttributeType(attname, att_typelem, attcollation,
+						   containing_rowtypes,
+						   allow_system_table_mods);
 	}
 
 	/*
 	 * This might not be strictly invalid per SQL standard, but it is
-	 * pretty useless, and it cannot be dumped, so we must disallow
-	 * it.
+	 * pretty useless, and it cannot be dumped, so we must disallow it.
 	 */
-	if (type_is_collatable(atttypid) && !OidIsValid(attcollation))
+	if (!OidIsValid(attcollation) && type_is_collatable(atttypid))
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("no collation was derived for column \"%s\" with collatable type %s",
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7a2629ecd198afa3b4f485ada071b23ed255199e..679255a199bfc1e13b4bc46068cd0ad8d77b9462 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -403,7 +403,9 @@ ConstructTupleDescriptor(Relation heapRelation,
 			 * whether a table column is of a safe type (which is why we
 			 * needn't check for the non-expression case).
 			 */
-			CheckAttributeType(NameStr(to->attname), to->atttypid, to->attcollation, false);
+			CheckAttributeType(NameStr(to->attname),
+							   to->atttypid, to->attcollation,
+							   NIL, false);
 		}
 
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d7573b8ef50429be24eee8daf12cae5311f1c365..737ab1a7bc8c3335a79b5343cc48430a2e4c2427 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4221,7 +4221,9 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
 	collOid = GetColumnDefCollation(NULL, colDef, typeOid);
 
 	/* make sure datatype is legal for a column */
-	CheckAttributeType(colDef->colname, typeOid, collOid, false);
+	CheckAttributeType(colDef->colname, typeOid, collOid,
+					   list_make1_oid(rel->rd_rel->reltype),
+					   false);
 
 	/* construct new attribute's pg_attribute entry */
 	attribute.attrelid = myrelid;
@@ -6536,7 +6538,9 @@ ATPrepAlterColumnType(List **wqueue,
 	targetcollid = GetColumnDefCollation(NULL, def, targettype);
 
 	/* make sure datatype is legal for a column */
-	CheckAttributeType(colName, targettype, targetcollid, false);
+	CheckAttributeType(colName, targettype, targetcollid,
+					   list_make1_oid(rel->rd_rel->reltype),
+					   false);
 
 	if (tab->relkind == RELKIND_RELATION)
 	{
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 5f9a864be5f0e6922b1be4dd5d5fbb4b7edf4bc3..463aff0358fcc7366bf34c7222c2ec9b164f9c97 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -117,7 +117,9 @@ extern Form_pg_attribute SystemAttributeByName(const char *attname,
 extern void CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind,
 						 bool allow_system_table_mods);
 
-extern void CheckAttributeType(const char *attname, Oid atttypid, Oid attcollation,
+extern void CheckAttributeType(const char *attname,
+				   Oid atttypid, Oid attcollation,
+				   List *containing_rowtypes,
 				   bool allow_system_table_mods);
 
 #endif   /* HEAP_H */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index d5a59d5d1d48cd186414625db831a2ad598e64b5..578f94bc64ce87c8d06eb9406a7d86844a66605e 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1493,6 +1493,18 @@ create table tab1 (a int, b text);
 create table tab2 (x int, y tab1);
 alter table tab1 alter column b type varchar; -- fails
 ERROR:  cannot alter table "tab1" because column "tab2"."y" uses its rowtype
+-- disallow recursive containment of row types
+create temp table recur1 (f1 int);
+alter table recur1 add column f2 recur1; -- fails
+ERROR:  composite type recur1 cannot be made a member of itself
+alter table recur1 add column f2 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
+alter table recur1 add column f2 int;
+alter table recur1 alter column f2 type recur2; -- fails
+ERROR:  composite type recur1 cannot be made a member of itself
 --
 -- lock levels
 --
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 6531a9f162dbe3846847e1accece2d4974c96717..54dbb8eaf9deb0d5072af22c5844b4be8bf05302 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1112,6 +1112,15 @@ create table tab1 (a int, b text);
 create table tab2 (x int, y tab1);
 alter table tab1 alter column b type varchar; -- fails
 
+-- disallow recursive containment of row types
+create temp table recur1 (f1 int);
+alter table recur1 add column f2 recur1; -- fails
+alter table recur1 add column f2 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;
+alter table recur1 alter column f2 type recur2; -- fails
+
 --
 -- lock levels
 --