tablecmds.c/MergeAttributes() cleanup

Started by Peter Eisentrautover 2 years ago32 messages
#1Peter Eisentraut
peter@eisentraut.org
17 attachment(s)

The MergeAttributes() and related code in and around tablecmds.c is huge
and ancient, with many things bolted on over time, and difficult to deal
with. I took some time to make careful incremental updates and
refactorings to make the code easier to follow, more compact, and more
modern in appearance. I also found several pieces of obsolete code
along the way. This resulted in the attached long patch series. Each
patch tries to make a single change and can be considered incrementally.
At the end, the code is shorter, better factored, and I hope easier to
understand. There shouldn't be any change in behavior.

Attachments:

0001-Remove-obsolete-comment-about-OID-support.patchtext/plain; charset=UTF-8; name=0001-Remove-obsolete-comment-about-OID-support.patchDownload
From 60a671aeb03293bdec65fd86f2a393c3aced6eb9 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Tue, 27 Jun 2023 17:10:25 +0200
Subject: [PATCH 01/17] Remove obsolete comment about OID support

---
 src/backend/catalog/heap.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 2a0d82aedd..9196dcd39f 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -843,9 +843,9 @@ AddNewAttributeTuples(Oid new_rel_oid,
 	}
 
 	/*
-	 * Next we add the system attributes.  Skip OID if rel has no OIDs. Skip
-	 * all for a view or type relation.  We don't bother with making datatype
-	 * dependencies here, since presumably all these types are pinned.
+	 * Next we add the system attributes.  Skip all for a view or type
+	 * relation.  We don't bother with making datatype dependencies here,
+	 * since presumably all these types are pinned.
 	 */
 	if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE)
 	{

base-commit: b381d9637030c163c3b1f8a9d3de51dfc1b4ee58
-- 
2.41.0

0002-Remove-ancient-special-case-code-for-adding-oid-colu.patchtext/plain; charset=UTF-8; name=0002-Remove-ancient-special-case-code-for-adding-oid-colu.patchDownload
From e12a69675919fb026c008e37649518f3d52e2a90 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Thu, 22 Jun 2023 12:05:06 +0200
Subject: [PATCH 02/17] Remove ancient special case code for adding oid columns

The special handling of negative attribute numbers in
ATExecAddColumn() was introduced to support SET WITH OIDS (commit
6d1e361852).  But that feature doesn't exist anymore, so we can revert
to the previous, simpler version.  In passing, also remove an obsolete
comment about OID support.
---
 src/backend/commands/tablecmds.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d985278ac6..a5493705aa 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2449,8 +2449,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 
 	/*
 	 * Scan the parents left-to-right, and merge their attributes to form a
-	 * list of inherited attributes (inhSchema).  Also check to see if we need
-	 * to inherit an OID column.
+	 * list of inherited attributes (inhSchema).
 	 */
 	child_attno = 0;
 	foreach(entry, supers)
@@ -6944,7 +6943,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	attribute.attrelid = myrelid;
 	namestrcpy(&(attribute.attname), colDef->colname);
 	attribute.atttypid = typeOid;
-	attribute.attstattarget = (newattnum > 0) ? -1 : 0;
+	attribute.attstattarget = -1;
 	attribute.attlen = tform->typlen;
 	attribute.attnum = newattnum;
 	if (list_length(colDef->typeName->arrayBounds) > PG_INT16_MAX)
@@ -7067,7 +7066,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	 * is certainly not going to touch them.  System attributes don't have
 	 * interesting defaults, either.
 	 */
-	if (RELKIND_HAS_STORAGE(relkind) && attribute.attnum > 0)
+	if (RELKIND_HAS_STORAGE(relkind))
 	{
 		/*
 		 * For an identity column, we can't use build_column_default(),
-- 
2.41.0

0003-Remove-ancient-special-case-code-for-dropping-oid-co.patchtext/plain; charset=UTF-8; name=0003-Remove-ancient-special-case-code-for-dropping-oid-co.patchDownload
From 58af867b3e3990e787a33c5d5023753e623dffe0 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Tue, 27 Jun 2023 14:43:55 +0200
Subject: [PATCH 03/17] Remove ancient special case code for dropping oid
 columns

The special handling of negative attribute numbers in
RemoveAttributeById() was introduced to support SET WITHOUT OIDS
(commit 24614a9880).  But that feature doesn't exist anymore, so we
can revert to the previous, simpler version.
---
 src/backend/catalog/heap.c | 99 +++++++++++++++++---------------------
 1 file changed, 43 insertions(+), 56 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9196dcd39f..4c30c7d461 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1666,68 +1666,56 @@ RemoveAttributeById(Oid relid, AttrNumber attnum)
 			 attnum, relid);
 	attStruct = (Form_pg_attribute) GETSTRUCT(tuple);
 
-	if (attnum < 0)
-	{
-		/* System attribute (probably OID) ... just delete the row */
-
-		CatalogTupleDelete(attr_rel, &tuple->t_self);
-	}
-	else
-	{
-		/* Dropping user attributes is lots harder */
+	/* Mark the attribute as dropped */
+	attStruct->attisdropped = true;
 
-		/* Mark the attribute as dropped */
-		attStruct->attisdropped = true;
-
-		/*
-		 * Set the type OID to invalid.  A dropped attribute's type link
-		 * cannot be relied on (once the attribute is dropped, the type might
-		 * be too). Fortunately we do not need the type row --- the only
-		 * really essential information is the type's typlen and typalign,
-		 * which are preserved in the attribute's attlen and attalign.  We set
-		 * atttypid to zero here as a means of catching code that incorrectly
-		 * expects it to be valid.
-		 */
-		attStruct->atttypid = InvalidOid;
-
-		/* Remove any NOT NULL constraint the column may have */
-		attStruct->attnotnull = false;
+	/*
+	 * Set the type OID to invalid.  A dropped attribute's type link cannot be
+	 * relied on (once the attribute is dropped, the type might be too).
+	 * Fortunately we do not need the type row --- the only really essential
+	 * information is the type's typlen and typalign, which are preserved in
+	 * the attribute's attlen and attalign.  We set atttypid to zero here as a
+	 * means of catching code that incorrectly expects it to be valid.
+	 */
+	attStruct->atttypid = InvalidOid;
 
-		/* We don't want to keep stats for it anymore */
-		attStruct->attstattarget = 0;
+	/* Remove any NOT NULL constraint the column may have */
+	attStruct->attnotnull = false;
 
-		/* Unset this so no one tries to look up the generation expression */
-		attStruct->attgenerated = '\0';
+	/* We don't want to keep stats for it anymore */
+	attStruct->attstattarget = 0;
 
-		/*
-		 * Change the column name to something that isn't likely to conflict
-		 */
-		snprintf(newattname, sizeof(newattname),
-				 "........pg.dropped.%d........", attnum);
-		namestrcpy(&(attStruct->attname), newattname);
+	/* Unset this so no one tries to look up the generation expression */
+	attStruct->attgenerated = '\0';
 
-		/* clear the missing value if any */
-		if (attStruct->atthasmissing)
-		{
-			Datum		valuesAtt[Natts_pg_attribute] = {0};
-			bool		nullsAtt[Natts_pg_attribute] = {0};
-			bool		replacesAtt[Natts_pg_attribute] = {0};
-
-			/* update the tuple - set atthasmissing and attmissingval */
-			valuesAtt[Anum_pg_attribute_atthasmissing - 1] =
-				BoolGetDatum(false);
-			replacesAtt[Anum_pg_attribute_atthasmissing - 1] = true;
-			valuesAtt[Anum_pg_attribute_attmissingval - 1] = (Datum) 0;
-			nullsAtt[Anum_pg_attribute_attmissingval - 1] = true;
-			replacesAtt[Anum_pg_attribute_attmissingval - 1] = true;
-
-			tuple = heap_modify_tuple(tuple, RelationGetDescr(attr_rel),
-									  valuesAtt, nullsAtt, replacesAtt);
-		}
+	/*
+	 * Change the column name to something that isn't likely to conflict
+	 */
+	snprintf(newattname, sizeof(newattname),
+			 "........pg.dropped.%d........", attnum);
+	namestrcpy(&(attStruct->attname), newattname);
 
-		CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple);
+	/* clear the missing value if any */
+	if (attStruct->atthasmissing)
+	{
+		Datum		valuesAtt[Natts_pg_attribute] = {0};
+		bool		nullsAtt[Natts_pg_attribute] = {0};
+		bool		replacesAtt[Natts_pg_attribute] = {0};
+
+		/* update the tuple - set atthasmissing and attmissingval */
+		valuesAtt[Anum_pg_attribute_atthasmissing - 1] =
+			BoolGetDatum(false);
+		replacesAtt[Anum_pg_attribute_atthasmissing - 1] = true;
+		valuesAtt[Anum_pg_attribute_attmissingval - 1] = (Datum) 0;
+		nullsAtt[Anum_pg_attribute_attmissingval - 1] = true;
+		replacesAtt[Anum_pg_attribute_attmissingval - 1] = true;
+
+		tuple = heap_modify_tuple(tuple, RelationGetDescr(attr_rel),
+								  valuesAtt, nullsAtt, replacesAtt);
 	}
 
+	CatalogTupleUpdate(attr_rel, &tuple->t_self, tuple);
+
 	/*
 	 * Because updating the pg_attribute row will trigger a relcache flush for
 	 * the target relation, we need not do anything else to notify other
@@ -1736,8 +1724,7 @@ RemoveAttributeById(Oid relid, AttrNumber attnum)
 
 	table_close(attr_rel, RowExclusiveLock);
 
-	if (attnum > 0)
-		RemoveStatistics(relid, attnum);
+	RemoveStatistics(relid, attnum);
 
 	relation_close(rel, NoLock);
 }
-- 
2.41.0

0004-Make-more-use-of-makeColumnDef.patchtext/plain; charset=UTF-8; name=0004-Make-more-use-of-makeColumnDef.patchDownload
From 2720347760b843ed6dc870270df0e8b43a1aab93 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Thu, 22 Jun 2023 14:17:34 +0200
Subject: [PATCH 04/17] Make more use of makeColumnDef()

Since we already have it, we might as well make full use of it,
instead of assembling ColumnDef by hand in several places.
---
 src/backend/commands/sequence.c    | 29 +++++++----------------
 src/backend/commands/tablecmds.c   | 14 +----------
 src/backend/parser/parse_utilcmd.c | 37 ++++++------------------------
 3 files changed, 16 insertions(+), 64 deletions(-)

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index ef01449678..c6fea33676 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -172,40 +172,27 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	stmt->tableElts = NIL;
 	for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++)
 	{
-		ColumnDef  *coldef = makeNode(ColumnDef);
-
-		coldef->inhcount = 0;
-		coldef->is_local = true;
-		coldef->is_not_null = true;
-		coldef->is_from_type = false;
-		coldef->storage = 0;
-		coldef->raw_default = NULL;
-		coldef->cooked_default = NULL;
-		coldef->collClause = NULL;
-		coldef->collOid = InvalidOid;
-		coldef->constraints = NIL;
-		coldef->location = -1;
-
-		null[i - 1] = false;
+		ColumnDef  *coldef;
 
 		switch (i)
 		{
 			case SEQ_COL_LASTVAL:
-				coldef->typeName = makeTypeNameFromOid(INT8OID, -1);
-				coldef->colname = "last_value";
+				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
 				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
 				break;
 			case SEQ_COL_LOG:
-				coldef->typeName = makeTypeNameFromOid(INT8OID, -1);
-				coldef->colname = "log_cnt";
+				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
 				value[i - 1] = Int64GetDatum((int64) 0);
 				break;
 			case SEQ_COL_CALLED:
-				coldef->typeName = makeTypeNameFromOid(BOOLOID, -1);
-				coldef->colname = "is_called";
+				coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid);
 				value[i - 1] = BoolGetDatum(false);
 				break;
 		}
+
+		coldef->is_not_null = true;
+		null[i - 1] = false;
+
 		stmt->tableElts = lappend(stmt->tableElts, coldef);
 	}
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a5493705aa..a6482a6d72 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2677,27 +2677,15 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				/*
 				 * No, create a new inherited column
 				 */
-				def = makeNode(ColumnDef);
-				def->colname = pstrdup(attributeName);
-				def->typeName = makeTypeNameFromOid(attribute->atttypid,
-													attribute->atttypmod);
+				def = makeColumnDef(attributeName, attribute->atttypid, attribute->atttypmod, attribute->attcollation);
 				def->inhcount = 1;
 				def->is_local = false;
 				def->is_not_null = attribute->attnotnull;
-				def->is_from_type = false;
 				def->storage = attribute->attstorage;
-				def->raw_default = NULL;
-				def->cooked_default = NULL;
 				def->generated = attribute->attgenerated;
-				def->collClause = NULL;
-				def->collOid = attribute->attcollation;
-				def->constraints = NIL;
-				def->location = -1;
 				if (CompressionMethodIsValid(attribute->attcompression))
 					def->compression =
 						pstrdup(GetCompressionMethodName(attribute->attcompression));
-				else
-					def->compression = NULL;
 				inhSchema = lappend(inhSchema, def);
 				newattmap->attnums[parent_attno - 1] = ++child_attno;
 			}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index d67580fc77..53420306ec 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1019,7 +1019,6 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	{
 		Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
 													parent_attno - 1);
-		char	   *attributeName = NameStr(attribute->attname);
 		ColumnDef  *def;
 
 		/*
@@ -1029,26 +1028,15 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 			continue;
 
 		/*
-		 * Create a new column, which is marked as NOT inherited.
-		 *
+		 * Create a new column definition
+		 */
+		def = makeColumnDef(NameStr(attribute->attname), attribute->atttypid, attribute->atttypmod, attribute->attcollation);
+
+		/*
 		 * For constraints, ONLY the NOT NULL constraint is inherited by the
 		 * new column definition per SQL99.
 		 */
-		def = makeNode(ColumnDef);
-		def->colname = pstrdup(attributeName);
-		def->typeName = makeTypeNameFromOid(attribute->atttypid,
-											attribute->atttypmod);
-		def->inhcount = 0;
-		def->is_local = true;
 		def->is_not_null = attribute->attnotnull;
-		def->is_from_type = false;
-		def->storage = 0;
-		def->raw_default = NULL;
-		def->cooked_default = NULL;
-		def->collClause = NULL;
-		def->collOid = attribute->attcollation;
-		def->constraints = NIL;
-		def->location = -1;
 
 		/*
 		 * Add to column list
@@ -1476,20 +1464,9 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 		if (attr->attisdropped)
 			continue;
 
-		n = makeNode(ColumnDef);
-		n->colname = pstrdup(NameStr(attr->attname));
-		n->typeName = makeTypeNameFromOid(attr->atttypid, attr->atttypmod);
-		n->inhcount = 0;
-		n->is_local = true;
-		n->is_not_null = false;
+		n = makeColumnDef(NameStr(attr->attname), attr->atttypid, attr->atttypmod, attr->attcollation);
 		n->is_from_type = true;
-		n->storage = 0;
-		n->raw_default = NULL;
-		n->cooked_default = NULL;
-		n->collClause = NULL;
-		n->collOid = attr->attcollation;
-		n->constraints = NIL;
-		n->location = -1;
+
 		cxt->columns = lappend(cxt->columns, n);
 	}
 	ReleaseTupleDesc(tupdesc);
-- 
2.41.0

0005-Clean-up-MergeAttributesIntoExisting.patchtext/plain; charset=UTF-8; name=0005-Clean-up-MergeAttributesIntoExisting.patchDownload
From fcdd42c9507bd048afd5443f74b1b8bad31b7595 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Thu, 8 Jun 2023 13:13:12 +0200
Subject: [PATCH 05/17] Clean up MergeAttributesIntoExisting()

Make variable naming clearer and more consistent.  Move some variables
to smaller scope.  Remove some unnecessary intermediate variables.
Try to save some vertical space.

Apply analogous changes to nearby MergeConstraintsIntoExisting() and
RemoveInheritance() for consistency.
---
 src/backend/commands/tablecmds.c | 115 +++++++++++--------------------
 1 file changed, 42 insertions(+), 73 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a6482a6d72..62b555aa20 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -15112,84 +15112,67 @@ static void
 MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 {
 	Relation	attrrel;
-	AttrNumber	parent_attno;
-	int			parent_natts;
-	TupleDesc	tupleDesc;
-	HeapTuple	tuple;
-	bool		child_is_partition = false;
+	TupleDesc	parent_desc;
 
 	attrrel = table_open(AttributeRelationId, RowExclusiveLock);
+	parent_desc = RelationGetDescr(parent_rel);
 
-	tupleDesc = RelationGetDescr(parent_rel);
-	parent_natts = tupleDesc->natts;
-
-	/* If parent_rel is a partitioned table, child_rel must be a partition */
-	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-		child_is_partition = true;
-
-	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
+	for (AttrNumber parent_attno = 1; parent_attno <= parent_desc->natts; parent_attno++)
 	{
-		Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
-													parent_attno - 1);
-		char	   *attributeName = NameStr(attribute->attname);
+		Form_pg_attribute parent_att = TupleDescAttr(parent_desc, parent_attno - 1);
+		char	   *parent_attname = NameStr(parent_att->attname);
+		HeapTuple	tuple;
 
 		/* Ignore dropped columns in the parent. */
-		if (attribute->attisdropped)
+		if (parent_att->attisdropped)
 			continue;
 
 		/* Find same column in child (matching on column name). */
-		tuple = SearchSysCacheCopyAttName(RelationGetRelid(child_rel),
-										  attributeName);
+		tuple = SearchSysCacheCopyAttName(RelationGetRelid(child_rel), parent_attname);
 		if (HeapTupleIsValid(tuple))
 		{
-			/* Check they are same type, typmod, and collation */
-			Form_pg_attribute childatt = (Form_pg_attribute) GETSTRUCT(tuple);
+			Form_pg_attribute child_att = (Form_pg_attribute) GETSTRUCT(tuple);
 
-			if (attribute->atttypid != childatt->atttypid ||
-				attribute->atttypmod != childatt->atttypmod)
+			if (parent_att->atttypid != child_att->atttypid ||
+				parent_att->atttypmod != child_att->atttypmod)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("child table \"%s\" has different type for column \"%s\"",
-								RelationGetRelationName(child_rel),
-								attributeName)));
+								RelationGetRelationName(child_rel), parent_attname)));
 
-			if (attribute->attcollation != childatt->attcollation)
+			if (parent_att->attcollation != child_att->attcollation)
 				ereport(ERROR,
 						(errcode(ERRCODE_COLLATION_MISMATCH),
 						 errmsg("child table \"%s\" has different collation for column \"%s\"",
-								RelationGetRelationName(child_rel),
-								attributeName)));
+								RelationGetRelationName(child_rel), parent_attname)));
 
 			/*
 			 * Check child doesn't discard NOT NULL property.  (Other
 			 * constraints are checked elsewhere.)
 			 */
-			if (attribute->attnotnull && !childatt->attnotnull)
+			if (parent_att->attnotnull && !child_att->attnotnull)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("column \"%s\" in child table must be marked NOT NULL",
-								attributeName)));
+						 errmsg("column \"%s\" in child table must be marked NOT NULL", parent_attname)));
 
 			/*
 			 * Child column must be generated if and only if parent column is.
 			 */
-			if (attribute->attgenerated && !childatt->attgenerated)
+			if (parent_att->attgenerated && !child_att->attgenerated)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("column \"%s\" in child table must be a generated column",
-								attributeName)));
-			if (childatt->attgenerated && !attribute->attgenerated)
+						 errmsg("column \"%s\" in child table must be a generated column", parent_attname)));
+			if (child_att->attgenerated && !parent_att->attgenerated)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("column \"%s\" in child table must not be a generated column",
-								attributeName)));
+						 errmsg("column \"%s\" in child table must not be a generated column", parent_attname)));
 
 			/*
 			 * OK, bump the child column's inheritance count.  (If we fail
 			 * later on, this change will just roll back.)
 			 */
-			childatt->attinhcount++;
-			if (childatt->attinhcount < 0)
+			child_att->attinhcount++;
+			if (child_att->attinhcount < 0)
 				ereport(ERROR,
 						errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 						errmsg("too many inheritance parents"));
@@ -15199,10 +15182,10 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 			 * is same in all partitions. (Note: there are only inherited
 			 * attributes in partitions)
 			 */
-			if (child_is_partition)
+			if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			{
-				Assert(childatt->attinhcount == 1);
-				childatt->attislocal = false;
+				Assert(child_att->attinhcount == 1);
+				child_att->attislocal = false;
 			}
 
 			CatalogTupleUpdate(attrrel, &tuple->t_self, tuple);
@@ -15212,8 +15195,7 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 		{
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing column \"%s\"",
-							attributeName)));
+					 errmsg("child table is missing column \"%s\"", parent_attname)));
 		}
 	}
 
@@ -15240,26 +15222,19 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 static void
 MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 {
-	Relation	catalog_relation;
-	TupleDesc	tuple_desc;
+	Relation	constraintrel;
 	SysScanDesc parent_scan;
 	ScanKeyData parent_key;
 	HeapTuple	parent_tuple;
-	bool		child_is_partition = false;
-
-	catalog_relation = table_open(ConstraintRelationId, RowExclusiveLock);
-	tuple_desc = RelationGetDescr(catalog_relation);
 
-	/* If parent_rel is a partitioned table, child_rel must be a partition */
-	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-		child_is_partition = true;
+	constraintrel = table_open(ConstraintRelationId, RowExclusiveLock);
 
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
 				Anum_pg_constraint_conrelid,
 				BTEqualStrategyNumber, F_OIDEQ,
 				ObjectIdGetDatum(RelationGetRelid(parent_rel)));
-	parent_scan = systable_beginscan(catalog_relation, ConstraintRelidTypidNameIndexId,
+	parent_scan = systable_beginscan(constraintrel, ConstraintRelidTypidNameIndexId,
 									 true, NULL, 1, &parent_key);
 
 	while (HeapTupleIsValid(parent_tuple = systable_getnext(parent_scan)))
@@ -15282,7 +15257,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 					Anum_pg_constraint_conrelid,
 					BTEqualStrategyNumber, F_OIDEQ,
 					ObjectIdGetDatum(RelationGetRelid(child_rel)));
-		child_scan = systable_beginscan(catalog_relation, ConstraintRelidTypidNameIndexId,
+		child_scan = systable_beginscan(constraintrel, ConstraintRelidTypidNameIndexId,
 										true, NULL, 1, &child_key);
 
 		while (HeapTupleIsValid(child_tuple = systable_getnext(child_scan)))
@@ -15293,24 +15268,21 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			if (child_con->contype != CONSTRAINT_CHECK)
 				continue;
 
-			if (strcmp(NameStr(parent_con->conname),
-					   NameStr(child_con->conname)) != 0)
+			if (strcmp(NameStr(parent_con->conname), NameStr(child_con->conname)) != 0)
 				continue;
 
-			if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
+			if (!constraints_equivalent(parent_tuple, child_tuple, RelationGetDescr(constraintrel)))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
-								RelationGetRelationName(child_rel),
-								NameStr(parent_con->conname))));
+								RelationGetRelationName(child_rel), NameStr(parent_con->conname))));
 
 			/* If the child constraint is "no inherit" then cannot merge */
 			if (child_con->connoinherit)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
-								NameStr(child_con->conname),
-								RelationGetRelationName(child_rel))));
+								NameStr(child_con->conname), RelationGetRelationName(child_rel))));
 
 			/*
 			 * If the child constraint is "not valid" then cannot merge with a
@@ -15320,8 +15292,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("constraint \"%s\" conflicts with NOT VALID constraint on child table \"%s\"",
-								NameStr(child_con->conname),
-								RelationGetRelationName(child_rel))));
+								NameStr(child_con->conname), RelationGetRelationName(child_rel))));
 
 			/*
 			 * OK, bump the child constraint's inheritance count.  (If we fail
@@ -15340,13 +15311,13 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			 * inherited only once since it cannot have multiple parents and
 			 * it is never considered local.
 			 */
-			if (child_is_partition)
+			if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			{
 				Assert(child_con->coninhcount == 1);
 				child_con->conislocal = false;
 			}
 
-			CatalogTupleUpdate(catalog_relation, &child_copy->t_self, child_copy);
+			CatalogTupleUpdate(constraintrel, &child_copy->t_self, child_copy);
 			heap_freetuple(child_copy);
 
 			found = true;
@@ -15363,7 +15334,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 	}
 
 	systable_endscan(parent_scan);
-	table_close(catalog_relation, RowExclusiveLock);
+	table_close(constraintrel, RowExclusiveLock);
 }
 
 /*
@@ -15506,11 +15477,9 @@ RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached)
 				constraintTuple;
 	List	   *connames;
 	bool		found;
-	bool		child_is_partition = false;
+	bool		is_partitioning;
 
-	/* If parent_rel is a partitioned table, child_rel must be a partition */
-	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-		child_is_partition = true;
+	is_partitioning = (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 
 	found = DeleteInheritsTuple(RelationGetRelid(child_rel),
 								RelationGetRelid(parent_rel),
@@ -15518,7 +15487,7 @@ RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached)
 								RelationGetRelationName(child_rel));
 	if (!found)
 	{
-		if (child_is_partition)
+		if (is_partitioning)
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_TABLE),
 					 errmsg("relation \"%s\" is not a partition of relation \"%s\"",
@@ -15648,7 +15617,7 @@ RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached)
 	drop_parent_dependency(RelationGetRelid(child_rel),
 						   RelationRelationId,
 						   RelationGetRelid(parent_rel),
-						   child_dependency_type(child_is_partition));
+						   child_dependency_type(is_partitioning));
 
 	/*
 	 * Post alter hook of this inherits. Since object_access_hook doesn't take
-- 
2.41.0

0006-Clean-up-MergeCheckConstraint.patchtext/plain; charset=UTF-8; name=0006-Clean-up-MergeCheckConstraint.patchDownload
From 17517897f90eb7b2be100dade5579b55d406334e Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Thu, 8 Jun 2023 13:34:07 +0200
Subject: [PATCH 06/17] Clean up MergeCheckConstraint()

If the constraint is not already in the list, add it ourselves,
instead of making the caller do it.  This makes the interface more
consistent with other "merge" functions in this file.
---
 src/backend/commands/tablecmds.c | 47 ++++++++++++++------------------
 1 file changed, 21 insertions(+), 26 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 62b555aa20..a905140e32 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -351,7 +351,7 @@ static void RangeVarCallbackForTruncate(const RangeVar *relation,
 										Oid relId, Oid oldRelId, void *arg);
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
 							 bool is_partition, List **supconstr);
-static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
+static List *MergeCheckConstraint(List *constraints, const char *name, Node *expr);
 static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
 static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
 static void StoreCatalogInheritance(Oid relationId, List *supers,
@@ -2811,24 +2811,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 									   name,
 									   RelationGetRelationName(relation))));
 
-				/* check for duplicate */
-				if (!MergeCheckConstraint(constraints, name, expr))
-				{
-					/* nope, this is a new one */
-					CookedConstraint *cooked;
-
-					cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
-					cooked->contype = CONSTR_CHECK;
-					cooked->conoid = InvalidOid;	/* until created */
-					cooked->name = pstrdup(name);
-					cooked->attnum = 0; /* not used for constraints */
-					cooked->expr = expr;
-					cooked->skip_validation = false;
-					cooked->is_local = false;
-					cooked->inhcount = 1;
-					cooked->is_no_inherit = false;
-					constraints = lappend(constraints, cooked);
-				}
+				constraints = MergeCheckConstraint(constraints, name, expr);
 			}
 		}
 
@@ -3158,13 +3141,16 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
  *
  * constraints is a list of CookedConstraint structs for previous constraints.
  *
- * Returns true if merged (constraint is a duplicate), or false if it's
- * got a so-far-unique name, or throws error if conflict.
+ * If the constraint is a duplicate, then the existing constraint's
+ * inheritance count is updated.  If the constraint doesn't match or conflict
+ * with an existing one, a new constraint is appended to the list.  If there
+ * is a conflict (same name but different expression), throw an error.
  */
-static bool
-MergeCheckConstraint(List *constraints, char *name, Node *expr)
+static List *
+MergeCheckConstraint(List *constraints, const char *name, Node *expr)
 {
 	ListCell   *lc;
+	CookedConstraint *newcon;
 
 	foreach(lc, constraints)
 	{
@@ -3178,13 +3164,13 @@ MergeCheckConstraint(List *constraints, char *name, Node *expr)
 
 		if (equal(expr, ccon->expr))
 		{
-			/* OK to merge */
+			/* OK to merge constraint with existing */
 			ccon->inhcount++;
 			if (ccon->inhcount < 0)
 				ereport(ERROR,
 						errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 						errmsg("too many inheritance parents"));
-			return true;
+			return constraints;
 		}
 
 		ereport(ERROR,
@@ -3193,7 +3179,16 @@ MergeCheckConstraint(List *constraints, char *name, Node *expr)
 						name)));
 	}
 
-	return false;
+	/*
+	 * Constraint couldn't be merged with an existing one and also didn't
+	 * conflict with an existing one, so add it as a new one to the list.
+	 */
+	newcon = palloc0_object(CookedConstraint);
+	newcon->contype = CONSTR_CHECK;
+	newcon->name = pstrdup(name);
+	newcon->expr = expr;
+	newcon->inhcount = 1;
+	return lappend(constraints, newcon);
 }
 
 
-- 
2.41.0

0007-MergeAttributes-and-related-variable-renaming.patchtext/plain; charset=UTF-8; name=0007-MergeAttributes-and-related-variable-renaming.patchDownload
From 6987b295c86205536cba75ecd9d399c740ce64bb Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Thu, 8 Jun 2023 14:10:12 +0200
Subject: [PATCH 07/17] MergeAttributes() and related variable renaming

Mainly, rename "schema" to "columns" and related changes.  The
previous naming has long been confusing.
---
 src/backend/access/common/tupdesc.c |  10 +--
 src/backend/commands/tablecmds.c    | 109 +++++++++++++---------------
 src/include/access/tupdesc.h        |   4 +-
 3 files changed, 59 insertions(+), 64 deletions(-)

diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 7c5c390503..253d6c86f8 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -782,12 +782,12 @@ TupleDescInitEntryCollation(TupleDesc desc,
 /*
  * BuildDescForRelation
  *
- * Given a relation schema (list of ColumnDef nodes), build a TupleDesc.
+ * Given a list of ColumnDef nodes, build a TupleDesc.
  *
  * Note: tdtypeid will need to be filled in later on.
  */
 TupleDesc
-BuildDescForRelation(List *schema)
+BuildDescForRelation(const List *columns)
 {
 	int			natts;
 	AttrNumber	attnum;
@@ -803,13 +803,13 @@ BuildDescForRelation(List *schema)
 	/*
 	 * allocate a new tuple descriptor
 	 */
-	natts = list_length(schema);
+	natts = list_length(columns);
 	desc = CreateTemplateTupleDesc(natts);
 	has_not_null = false;
 
 	attnum = 0;
 
-	foreach(l, schema)
+	foreach(l, columns)
 	{
 		ColumnDef  *entry = lfirst(l);
 		AclResult	aclresult;
@@ -891,7 +891,7 @@ BuildDescForRelation(List *schema)
  * with functions returning RECORD.
  */
 TupleDesc
-BuildDescFromLists(List *names, List *types, List *typmods, List *collations)
+BuildDescFromLists(const List *names, const List *types, const List *typmods, const List *collations)
 {
 	int			natts;
 	AttrNumber	attnum;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a905140e32..031b2dc423 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -349,7 +349,7 @@ static void truncate_check_perms(Oid relid, Form_pg_class reltuple);
 static void truncate_check_activity(Relation rel);
 static void RangeVarCallbackForTruncate(const RangeVar *relation,
 										Oid relId, Oid oldRelId, void *arg);
-static List *MergeAttributes(List *schema, List *supers, char relpersistence,
+static List *MergeAttributes(List *columns, const List *supers, char relpersistence,
 							 bool is_partition, List **supconstr);
 static List *MergeCheckConstraint(List *constraints, const char *name, Node *expr);
 static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
@@ -359,7 +359,7 @@ static void StoreCatalogInheritance(Oid relationId, List *supers,
 static void StoreCatalogInheritance1(Oid relationId, Oid parentOid,
 									 int32 seqNumber, Relation inhRelation,
 									 bool child_is_partition);
-static int	findAttrByName(const char *attributeName, List *schema);
+static int	findAttrByName(const char *attributeName, const List *columns);
 static void AlterIndexNamespaces(Relation classRel, Relation rel,
 								 Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved);
 static void AlterSeqNamespaces(Relation classRel, Relation rel,
@@ -2289,7 +2289,7 @@ storage_name(char c)
  *		Returns new schema given initial schema and superclasses.
  *
  * Input arguments:
- * 'schema' is the column/attribute definition for the table. (It's a list
+ * 'columns' is the column/attribute definition for the table. (It's a list
  *		of ColumnDef's.) It is destructively changed.
  * 'supers' is a list of OIDs of parent relations, already locked by caller.
  * 'relpersistence' is the persistence type of the table.
@@ -2346,16 +2346,16 @@ storage_name(char c)
  *----------
  */
 static List *
-MergeAttributes(List *schema, List *supers, char relpersistence,
+MergeAttributes(List *columns, const List *supers, char relpersistence,
 				bool is_partition, List **supconstr)
 {
-	List	   *inhSchema = NIL;
+	List	   *inh_columns = NIL;
 	List	   *constraints = NIL;
 	bool		have_bogus_defaults = false;
 	int			child_attno;
 	static Node bogus_marker = {0}; /* marks conflicting defaults */
-	List	   *saved_schema = NIL;
-	ListCell   *entry;
+	List	   *saved_columns = NIL;
+	ListCell   *lc;
 
 	/*
 	 * Check for and reject tables with too many columns. We perform this
@@ -2368,7 +2368,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	 * Note that we also need to check that we do not exceed this figure after
 	 * including columns from inherited relations.
 	 */
-	if (list_length(schema) > MaxHeapAttributeNumber)
+	if (list_length(columns) > MaxHeapAttributeNumber)
 		ereport(ERROR,
 				(errcode(ERRCODE_TOO_MANY_COLUMNS),
 				 errmsg("tables can have at most %d columns",
@@ -2382,15 +2382,15 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	 * sense to assume such conflicts are errors.
 	 *
 	 * We don't use foreach() here because we have two nested loops over the
-	 * schema list, with possible element deletions in the inner one.  If we
+	 * columns list, with possible element deletions in the inner one.  If we
 	 * used foreach_delete_current() it could only fix up the state of one of
 	 * the loops, so it seems cleaner to use looping over list indexes for
 	 * both loops.  Note that any deletion will happen beyond where the outer
 	 * loop is, so its index never needs adjustment.
 	 */
-	for (int coldefpos = 0; coldefpos < list_length(schema); coldefpos++)
+	for (int coldefpos = 0; coldefpos < list_length(columns); coldefpos++)
 	{
-		ColumnDef  *coldef = list_nth_node(ColumnDef, schema, coldefpos);
+		ColumnDef  *coldef = list_nth_node(ColumnDef, columns, coldefpos);
 
 		if (!is_partition && coldef->typeName == NULL)
 		{
@@ -2407,9 +2407,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		}
 
 		/* restpos scans all entries beyond coldef; incr is in loop body */
-		for (int restpos = coldefpos + 1; restpos < list_length(schema);)
+		for (int restpos = coldefpos + 1; restpos < list_length(columns);)
 		{
-			ColumnDef  *restdef = list_nth_node(ColumnDef, schema, restpos);
+			ColumnDef  *restdef = list_nth_node(ColumnDef, columns, restpos);
 
 			if (strcmp(coldef->colname, restdef->colname) == 0)
 			{
@@ -2423,7 +2423,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 					coldef->cooked_default = restdef->cooked_default;
 					coldef->constraints = restdef->constraints;
 					coldef->is_from_type = false;
-					schema = list_delete_nth_cell(schema, restpos);
+					columns = list_delete_nth_cell(columns, restpos);
 				}
 				else
 					ereport(ERROR,
@@ -2443,25 +2443,24 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	 */
 	if (is_partition)
 	{
-		saved_schema = schema;
-		schema = NIL;
+		saved_columns = columns;
+		columns = NIL;
 	}
 
 	/*
 	 * Scan the parents left-to-right, and merge their attributes to form a
-	 * list of inherited attributes (inhSchema).
+	 * list of inherited columns (inh_columns).
 	 */
 	child_attno = 0;
-	foreach(entry, supers)
+	foreach(lc, supers)
 	{
-		Oid			parent = lfirst_oid(entry);
+		Oid			parent = lfirst_oid(lc);
 		Relation	relation;
 		TupleDesc	tupleDesc;
 		TupleConstr *constr;
 		AttrMap    *newattmap;
 		List	   *inherited_defaults;
 		List	   *cols_with_defaults;
-		AttrNumber	parent_attno;
 		ListCell   *lc1;
 		ListCell   *lc2;
 
@@ -2480,8 +2479,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 * We do not allow partitioned tables and partitions to participate in
 		 * regular inheritance.
 		 */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
-			!is_partition)
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from partitioned table \"%s\"",
@@ -2552,7 +2550,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		/* We can't process inherited defaults until newattmap is complete. */
 		inherited_defaults = cols_with_defaults = NIL;
 
-		for (parent_attno = 1; parent_attno <= tupleDesc->natts;
+		for (AttrNumber parent_attno = 1; parent_attno <= tupleDesc->natts;
 			 parent_attno++)
 		{
 			Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
@@ -2570,7 +2568,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			/*
 			 * Does it conflict with some previously inherited column?
 			 */
-			exist_attno = findAttrByName(attributeName, inhSchema);
+			exist_attno = findAttrByName(attributeName, inh_columns);
 			if (exist_attno > 0)
 			{
 				Oid			defTypeId;
@@ -2583,7 +2581,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				ereport(NOTICE,
 						(errmsg("merging multiple inherited definitions of column \"%s\"",
 								attributeName)));
-				def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
+				def = (ColumnDef *) list_nth(inh_columns, exist_attno - 1);
 
 				/*
 				 * Must have the same type and typmod
@@ -2686,7 +2684,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				if (CompressionMethodIsValid(attribute->attcompression))
 					def->compression =
 						pstrdup(GetCompressionMethodName(attribute->attcompression));
-				inhSchema = lappend(inhSchema, def);
+				inh_columns = lappend(inh_columns, def);
 				newattmap->attnums[parent_attno - 1] = ++child_attno;
 			}
 
@@ -2760,7 +2758,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			 * If we already had a default from some prior parent, check to
 			 * see if they are the same.  If so, no problem; if not, mark the
 			 * column as having a bogus default.  Below, we will complain if
-			 * the bogus default isn't overridden by the child schema.
+			 * the bogus default isn't overridden by the child columns.
 			 */
 			Assert(def->raw_default == NULL);
 			if (def->cooked_default == NULL)
@@ -2780,9 +2778,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		if (constr && constr->num_check > 0)
 		{
 			ConstrCheck *check = constr->check;
-			int			i;
 
-			for (i = 0; i < constr->num_check; i++)
+			for (int i = 0; i < constr->num_check; i++)
 			{
 				char	   *name = check[i].ccname;
 				Node	   *expr;
@@ -2826,27 +2823,27 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	}
 
 	/*
-	 * If we had no inherited attributes, the result schema is just the
+	 * If we had no inherited attributes, the result columns are just the
 	 * explicitly declared columns.  Otherwise, we need to merge the declared
-	 * columns into the inherited schema list.  Although, we never have any
+	 * columns into the inherited column list.  Although, we never have any
 	 * explicitly declared columns if the table is a partition.
 	 */
-	if (inhSchema != NIL)
+	if (inh_columns != NIL)
 	{
-		int			schema_attno = 0;
+		int			newcol_attno = 0;
 
-		foreach(entry, schema)
+		foreach(lc, columns)
 		{
-			ColumnDef  *newdef = lfirst(entry);
+			ColumnDef  *newdef = lfirst(lc);
 			char	   *attributeName = newdef->colname;
 			int			exist_attno;
 
-			schema_attno++;
+			newcol_attno++;
 
 			/*
 			 * Does it conflict with some previously inherited column?
 			 */
-			exist_attno = findAttrByName(attributeName, inhSchema);
+			exist_attno = findAttrByName(attributeName, inh_columns);
 			if (exist_attno > 0)
 			{
 				ColumnDef  *def;
@@ -2866,7 +2863,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				/*
 				 * Yes, try to merge the two column definitions.
 				 */
-				if (exist_attno == schema_attno)
+				if (exist_attno == newcol_attno)
 					ereport(NOTICE,
 							(errmsg("merging column \"%s\" with inherited definition",
 									attributeName)));
@@ -2874,7 +2871,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 					ereport(NOTICE,
 							(errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
 							 errdetail("User-specified column moved to the position of the inherited column.")));
-				def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
+				def = (ColumnDef *) list_nth(inh_columns, exist_attno - 1);
 
 				/*
 				 * Must have the same type and typmod
@@ -2999,19 +2996,19 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			else
 			{
 				/*
-				 * No, attach new column to result schema
+				 * No, attach new column to result columns
 				 */
-				inhSchema = lappend(inhSchema, newdef);
+				inh_columns = lappend(inh_columns, newdef);
 			}
 		}
 
-		schema = inhSchema;
+		columns = inh_columns;
 
 		/*
 		 * Check that we haven't exceeded the legal # of columns after merging
 		 * in inherited columns.
 		 */
-		if (list_length(schema) > MaxHeapAttributeNumber)
+		if (list_length(columns) > MaxHeapAttributeNumber)
 			ereport(ERROR,
 					(errcode(ERRCODE_TOO_MANY_COLUMNS),
 					 errmsg("tables can have at most %d columns",
@@ -3026,13 +3023,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	 */
 	if (is_partition)
 	{
-		foreach(entry, saved_schema)
+		foreach(lc, saved_columns)
 		{
-			ColumnDef  *restdef = lfirst(entry);
+			ColumnDef  *restdef = lfirst(lc);
 			bool		found = false;
 			ListCell   *l;
 
-			foreach(l, schema)
+			foreach(l, columns)
 			{
 				ColumnDef  *coldef = lfirst(l);
 
@@ -3105,9 +3102,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	 */
 	if (have_bogus_defaults)
 	{
-		foreach(entry, schema)
+		foreach(lc, columns)
 		{
-			ColumnDef  *def = lfirst(entry);
+			ColumnDef  *def = lfirst(lc);
 
 			if (def->cooked_default == &bogus_marker)
 			{
@@ -3128,7 +3125,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	}
 
 	*supconstr = constraints;
-	return schema;
+	return columns;
 }
 
 
@@ -3282,22 +3279,20 @@ StoreCatalogInheritance1(Oid relationId, Oid parentOid,
 }
 
 /*
- * Look for an existing schema entry with the given name.
+ * Look for an existing column entry with the given name.
  *
- * Returns the index (starting with 1) if attribute already exists in schema,
+ * Returns the index (starting with 1) if attribute already exists in columns,
  * 0 if it doesn't.
  */
 static int
-findAttrByName(const char *attributeName, List *schema)
+findAttrByName(const char *attributeName, const List *columns)
 {
-	ListCell   *s;
+	ListCell   *lc;
 	int			i = 1;
 
-	foreach(s, schema)
+	foreach(lc, columns)
 	{
-		ColumnDef  *def = lfirst(s);
-
-		if (strcmp(attributeName, def->colname) == 0)
+		if (strcmp(attributeName, lfirst_node(ColumnDef, lc)->colname) == 0)
 			return i;
 
 		i++;
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index b4286cf922..f6cc28a661 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -147,8 +147,8 @@ extern void TupleDescInitEntryCollation(TupleDesc desc,
 										AttrNumber attributeNumber,
 										Oid collationid);
 
-extern TupleDesc BuildDescForRelation(List *schema);
+extern TupleDesc BuildDescForRelation(const List *columns);
 
-extern TupleDesc BuildDescFromLists(List *names, List *types, List *typmods, List *collations);
+extern TupleDesc BuildDescFromLists(const List *names, const List *types, const List *typmods, const List *collations);
 
 #endif							/* TUPDESC_H */
-- 
2.41.0

0008-Improve-some-catalog-documentation.patchtext/plain; charset=UTF-8; name=0008-Improve-some-catalog-documentation.patchDownload
From 255e6a3e40ef8ad0194f5b2c233e4a60bea4de1d Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Tue, 27 Jun 2023 09:55:56 +0200
Subject: [PATCH 08/17] Improve some catalog documentation

Point out that typstorage and attstorage are never '\0', even for
fixed-length types.  This is different from attcompression.  For this
reason, some of the handling of these columns in tablecmds.c etc. is
different.  (catalogs.sgml already contained this information in an
indirect way.)
---
 src/include/catalog/pg_attribute.h | 10 +++++-----
 src/include/catalog/pg_type.h      |  3 +++
 2 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index f8b4861b94..a821bb1665 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -108,11 +108,11 @@ CATALOG(pg_attribute,1249,AttributeRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(75,
 	 */
 	char		attalign;
 
-	/*----------
-	 * attstorage tells for VARLENA attributes, what the heap access
-	 * methods can do to it if a given tuple doesn't fit into a page.
-	 * Possible values are as for pg_type.typstorage (see TYPSTORAGE macros).
-	 *----------
+	/*
+	 * attstorage tells for VARLENA attributes, what the heap access methods
+	 * can do to it if a given tuple doesn't fit into a page.  Possible values
+	 * are as for pg_type.typstorage (see TYPSTORAGE macros).  This is never
+	 * '\0', even for fixed-length types.
 	 */
 	char		attstorage;
 
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 519e570c8c..e0a86354ff 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -187,6 +187,9 @@ CATALOG(pg_type,1247,TypeRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(71,TypeRelati
 	 *
 	 * Note that 'm' fields can also be moved out to secondary storage,
 	 * but only as a last resort ('e' and 'x' fields are moved first).
+	 *
+	 * For types that are not variable-length (that is, typlen != -1), this
+	 * must be set to 'p'.
 	 * ----------------
 	 */
 	char		typstorage BKI_DEFAULT(p) BKI_ARRAY_DEFAULT(x);
-- 
2.41.0

0009-Remove-useless-if-condition.patchtext/plain; charset=UTF-8; name=0009-Remove-useless-if-condition.patchDownload
From eea7f17ebddd1635c0dab90ef7e3e818300b6069 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Tue, 27 Jun 2023 11:24:05 +0200
Subject: [PATCH 09/17] Remove useless if condition

This is useless because these fields are not set anywhere before, so
we can assign them unconditionally.  This also makes this more
consistent with ATExecAddColumn().
---
 src/backend/commands/tablecmds.c | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 031b2dc423..5400d37b5e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -927,11 +927,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			attr->atthasdef = true;
 		}
 
-		if (colDef->identity)
-			attr->attidentity = colDef->identity;
-
-		if (colDef->generated)
-			attr->attgenerated = colDef->generated;
+		attr->attidentity = colDef->identity;
+		attr->attgenerated = colDef->generated;
 
 		if (colDef->compression)
 			attr->attcompression = GetAttributeCompression(attr->atttypid,
-- 
2.41.0

0010-Remove-useless-if-condition.patchtext/plain; charset=UTF-8; name=0010-Remove-useless-if-condition.patchDownload
From bc02f8c2ec75b989c894b71b860b3e7ce2cc7029 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Tue, 27 Jun 2023 11:30:05 +0200
Subject: [PATCH 10/17] Remove useless if condition

We can call GetAttributeCompression() with a NULL argument.  It
handles that internally already.  This change makes all the callers of
GetAttributeCompression() uniform.
---
 src/backend/commands/tablecmds.c | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5400d37b5e..4e6310886f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -929,11 +929,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 
 		attr->attidentity = colDef->identity;
 		attr->attgenerated = colDef->generated;
-
-		if (colDef->compression)
-			attr->attcompression = GetAttributeCompression(attr->atttypid,
-														   colDef->compression);
-
+		attr->attcompression = GetAttributeCompression(attr->atttypid, colDef->compression);
 		if (colDef->storage_name)
 			attr->attstorage = GetAttributeStorage(attr->atttypid, colDef->storage_name);
 	}
-- 
2.41.0

0011-Refactor-ATExecAddColumn-to-use-BuildDescForRelation.patchtext/plain; charset=UTF-8; name=0011-Refactor-ATExecAddColumn-to-use-BuildDescForRelation.patchDownload
From 2eda6bc9897d0995a5112e2851c51daf0c35656e Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 14 Jun 2023 17:51:31 +0200
Subject: [PATCH 11/17] Refactor ATExecAddColumn() to use
 BuildDescForRelation()

BuildDescForRelation() has all the knowledge for converting a
ColumnDef into pg_attribute/tuple descriptor.  ATExecAddColumn() can
make use of that, instead of duplicating all that logic.  We just pass
a one-element list of ColumnDef and we get back exactly the data
structure we need.  Note that we don't even need to touch
BuildDescForRelation() to make this work.
---
 src/backend/commands/tablecmds.c | 97 ++++++++++----------------------
 1 file changed, 31 insertions(+), 66 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 4e6310886f..cee4f0186c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -6755,22 +6755,15 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Relation	pgclass,
 				attrdesc;
 	HeapTuple	reltup;
-	FormData_pg_attribute attribute;
+	Form_pg_attribute attribute;
 	int			newattnum;
 	char		relkind;
-	HeapTuple	typeTuple;
-	Oid			typeOid;
-	int32		typmod;
-	Oid			collOid;
-	Form_pg_type tform;
 	Expr	   *defval;
 	List	   *children;
 	ListCell   *child;
 	AlterTableCmd *childcmd;
-	AclResult	aclresult;
 	ObjectAddress address;
 	TupleDesc	tupdesc;
-	FormData_pg_attribute *aattr[] = {&attribute};
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -6892,58 +6885,30 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				 errmsg("tables can have at most %d columns",
 						MaxHeapAttributeNumber)));
 
-	typeTuple = typenameType(NULL, colDef->typeName, &typmod);
-	tform = (Form_pg_type) GETSTRUCT(typeTuple);
-	typeOid = tform->oid;
-
-	aclresult = object_aclcheck(TypeRelationId, typeOid, GetUserId(), ACL_USAGE);
-	if (aclresult != ACLCHECK_OK)
-		aclcheck_error_type(aclresult, typeOid);
+	/*
+	 * Construct new attribute's pg_attribute entry.
+	 */
+	tupdesc = BuildDescForRelation(list_make1(colDef));
 
-	collOid = GetColumnDefCollation(NULL, colDef, typeOid);
+	attribute = TupleDescAttr(tupdesc, 0);
 
-	/* make sure datatype is legal for a column */
-	CheckAttributeType(colDef->colname, typeOid, collOid,
-					   list_make1_oid(rel->rd_rel->reltype),
-					   0);
+	/* Fix up attribute number */
+	attribute->attnum = newattnum;
 
 	/*
-	 * Construct new attribute's pg_attribute entry.  (Variable-length fields
-	 * are handled by InsertPgAttributeTuples().)
+	 * Additional fields not handled by BuildDescForRelation() (mirrors
+	 * DefineRelation())
 	 */
-	attribute.attrelid = myrelid;
-	namestrcpy(&(attribute.attname), colDef->colname);
-	attribute.atttypid = typeOid;
-	attribute.attstattarget = -1;
-	attribute.attlen = tform->typlen;
-	attribute.attnum = newattnum;
-	if (list_length(colDef->typeName->arrayBounds) > PG_INT16_MAX)
-		ereport(ERROR,
-				errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-				errmsg("too many array dimensions"));
-	attribute.attndims = list_length(colDef->typeName->arrayBounds);
-	attribute.atttypmod = typmod;
-	attribute.attbyval = tform->typbyval;
-	attribute.attalign = tform->typalign;
+	attribute->attidentity = colDef->identity;
+	attribute->attgenerated = colDef->generated;
+	attribute->attcompression = GetAttributeCompression(attribute->atttypid, colDef->compression);
 	if (colDef->storage_name)
-		attribute.attstorage = GetAttributeStorage(typeOid, colDef->storage_name);
-	else
-		attribute.attstorage = tform->typstorage;
-	attribute.attcompression = GetAttributeCompression(typeOid,
-													   colDef->compression);
-	attribute.attnotnull = colDef->is_not_null;
-	attribute.atthasdef = false;
-	attribute.atthasmissing = false;
-	attribute.attidentity = colDef->identity;
-	attribute.attgenerated = colDef->generated;
-	attribute.attisdropped = false;
-	attribute.attislocal = colDef->is_local;
-	attribute.attinhcount = colDef->inhcount;
-	attribute.attcollation = collOid;
+		attribute->attstorage = GetAttributeStorage(attribute->atttypid, colDef->storage_name);
 
-	ReleaseSysCache(typeTuple);
-
-	tupdesc = CreateTupleDesc(lengthof(aattr), (FormData_pg_attribute **) &aattr);
+	/* make sure datatype is legal for a column */
+	CheckAttributeType(NameStr(attribute->attname), attribute->atttypid, attribute->attcollation,
+					   list_make1_oid(rel->rd_rel->reltype),
+					   0);
 
 	InsertPgAttributeTuples(attrdesc, tupdesc, myrelid, NULL, NULL);
 
@@ -6974,7 +6939,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		RawColumnDefault *rawEnt;
 
 		rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
-		rawEnt->attnum = attribute.attnum;
+		rawEnt->attnum = attribute->attnum;
 		rawEnt->raw_default = copyObject(colDef->raw_default);
 
 		/*
@@ -7048,7 +7013,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			NextValueExpr *nve = makeNode(NextValueExpr);
 
 			nve->seqid = RangeVarGetRelid(colDef->identitySequence, NoLock, false);
-			nve->typeId = typeOid;
+			nve->typeId = attribute->atttypid;
 
 			defval = (Expr *) nve;
 
@@ -7056,23 +7021,23 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 		}
 		else
-			defval = (Expr *) build_column_default(rel, attribute.attnum);
+			defval = (Expr *) build_column_default(rel, attribute->attnum);
 
-		if (!defval && DomainHasConstraints(typeOid))
+		if (!defval && DomainHasConstraints(attribute->atttypid))
 		{
 			Oid			baseTypeId;
 			int32		baseTypeMod;
 			Oid			baseTypeColl;
 
-			baseTypeMod = typmod;
-			baseTypeId = getBaseTypeAndTypmod(typeOid, &baseTypeMod);
+			baseTypeMod = attribute->atttypmod;
+			baseTypeId = getBaseTypeAndTypmod(attribute->atttypid, &baseTypeMod);
 			baseTypeColl = get_typcollation(baseTypeId);
 			defval = (Expr *) makeNullConst(baseTypeId, baseTypeMod, baseTypeColl);
 			defval = (Expr *) coerce_to_target_type(NULL,
 													(Node *) defval,
 													baseTypeId,
-													typeOid,
-													typmod,
+													attribute->atttypid,
+													attribute->atttypmod,
 													COERCION_ASSIGNMENT,
 													COERCE_IMPLICIT_CAST,
 													-1);
@@ -7085,17 +7050,17 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			NewColumnValue *newval;
 
 			newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
-			newval->attnum = attribute.attnum;
+			newval->attnum = attribute->attnum;
 			newval->expr = expression_planner(defval);
 			newval->is_generated = (colDef->generated != '\0');
 
 			tab->newvals = lappend(tab->newvals, newval);
 		}
 
-		if (DomainHasConstraints(typeOid))
+		if (DomainHasConstraints(attribute->atttypid))
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 
-		if (!TupleDescAttr(rel->rd_att, attribute.attnum - 1)->atthasmissing)
+		if (!TupleDescAttr(rel->rd_att, attribute->attnum - 1)->atthasmissing)
 		{
 			/*
 			 * If the new column is NOT NULL, and there is no missing value,
@@ -7108,8 +7073,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/*
 	 * Add needed dependency entries for the new column.
 	 */
-	add_column_datatype_dependency(myrelid, newattnum, attribute.atttypid);
-	add_column_collation_dependency(myrelid, newattnum, attribute.attcollation);
+	add_column_datatype_dependency(myrelid, newattnum, attribute->atttypid);
+	add_column_collation_dependency(myrelid, newattnum, attribute->attcollation);
 
 	/*
 	 * Propagate to children as appropriate.  Unlike most other ALTER
-- 
2.41.0

0012-Push-attidentity-and-attgenerated-handling-into-Buil.patchtext/plain; charset=UTF-8; name=0012-Push-attidentity-and-attgenerated-handling-into-Buil.patchDownload
From 1d513672df3c01a731d54de0da68a7f3e1c0d289 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 28 Jun 2023 17:17:22 +0200
Subject: [PATCH 12/17] Push attidentity and attgenerated handling into
 BuildDescForRelation()

Previously, this was handled by the callers separately, but it can be
trivially moved into BuildDescForRelation() so that it is handled in a
central place.
---
 src/backend/access/common/tupdesc.c | 2 ++
 src/backend/commands/tablecmds.c    | 4 ----
 2 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 253d6c86f8..bc1b67e6c9 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -856,6 +856,8 @@ BuildDescForRelation(const List *columns)
 		has_not_null |= entry->is_not_null;
 		att->attislocal = entry->is_local;
 		att->attinhcount = entry->inhcount;
+		att->attidentity = entry->identity;
+		att->attgenerated = entry->generated;
 	}
 
 	if (has_not_null)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cee4f0186c..06e4527dd1 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -927,8 +927,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			attr->atthasdef = true;
 		}
 
-		attr->attidentity = colDef->identity;
-		attr->attgenerated = colDef->generated;
 		attr->attcompression = GetAttributeCompression(attr->atttypid, colDef->compression);
 		if (colDef->storage_name)
 			attr->attstorage = GetAttributeStorage(attr->atttypid, colDef->storage_name);
@@ -6899,8 +6897,6 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	 * Additional fields not handled by BuildDescForRelation() (mirrors
 	 * DefineRelation())
 	 */
-	attribute->attidentity = colDef->identity;
-	attribute->attgenerated = colDef->generated;
 	attribute->attcompression = GetAttributeCompression(attribute->atttypid, colDef->compression);
 	if (colDef->storage_name)
 		attribute->attstorage = GetAttributeStorage(attribute->atttypid, colDef->storage_name);
-- 
2.41.0

0013-Move-BuildDescForRelation-from-tupdesc.c-to-tablecmd.patchtext/plain; charset=UTF-8; name=0013-Move-BuildDescForRelation-from-tupdesc.c-to-tablecmd.patchDownload
From 8a81f53b6cd043ed923b521d2917292f6726df64 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 28 Jun 2023 17:16:49 +0200
Subject: [PATCH 13/17] Move BuildDescForRelation() from tupdesc.c to
 tablecmds.c

BuildDescForRelation() main job is to convert ColumnDef lists to
pg_attribute/tuple descriptor arrays, which is really mostly an
internal subroutine of DefineRelation() and some related functions,
which is more the remit of tablecmds.c and doesn't have much to do
with the basic tuple descriptor interfaces in tupdesc.c.  This is also
supported by observing the header includes we can remove in tupdesc.c.
By moving it over, we can also (in the future) make
BuildDescForRelation() use more internals of tablecmds.c that are not
sensible to be exposed in tupdesc.c.
---
 src/backend/access/common/tupdesc.c | 109 +---------------------------
 src/backend/commands/tablecmds.c    | 102 ++++++++++++++++++++++++++
 src/include/access/tupdesc.h        |   2 -
 src/include/commands/tablecmds.h    |   2 +
 4 files changed, 105 insertions(+), 110 deletions(-)

diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index bc1b67e6c9..5d85831339 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -25,9 +25,6 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
 #include "common/hashfn.h"
-#include "miscadmin.h"
-#include "parser/parse_type.h"
-#include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/resowner_private.h"
@@ -778,109 +775,6 @@ TupleDescInitEntryCollation(TupleDesc desc,
 	TupleDescAttr(desc, attributeNumber - 1)->attcollation = collationid;
 }
 
-
-/*
- * BuildDescForRelation
- *
- * Given a list of ColumnDef nodes, build a TupleDesc.
- *
- * Note: tdtypeid will need to be filled in later on.
- */
-TupleDesc
-BuildDescForRelation(const List *columns)
-{
-	int			natts;
-	AttrNumber	attnum;
-	ListCell   *l;
-	TupleDesc	desc;
-	bool		has_not_null;
-	char	   *attname;
-	Oid			atttypid;
-	int32		atttypmod;
-	Oid			attcollation;
-	int			attdim;
-
-	/*
-	 * allocate a new tuple descriptor
-	 */
-	natts = list_length(columns);
-	desc = CreateTemplateTupleDesc(natts);
-	has_not_null = false;
-
-	attnum = 0;
-
-	foreach(l, columns)
-	{
-		ColumnDef  *entry = lfirst(l);
-		AclResult	aclresult;
-		Form_pg_attribute att;
-
-		/*
-		 * for each entry in the list, get the name and type information from
-		 * the list and have TupleDescInitEntry fill in the attribute
-		 * information we need.
-		 */
-		attnum++;
-
-		attname = entry->colname;
-		typenameTypeIdAndMod(NULL, entry->typeName, &atttypid, &atttypmod);
-
-		aclresult = object_aclcheck(TypeRelationId, atttypid, GetUserId(), ACL_USAGE);
-		if (aclresult != ACLCHECK_OK)
-			aclcheck_error_type(aclresult, atttypid);
-
-		attcollation = GetColumnDefCollation(NULL, entry, atttypid);
-		attdim = list_length(entry->typeName->arrayBounds);
-		if (attdim > PG_INT16_MAX)
-			ereport(ERROR,
-					errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-					errmsg("too many array dimensions"));
-
-		if (entry->typeName->setof)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-					 errmsg("column \"%s\" cannot be declared SETOF",
-							attname)));
-
-		TupleDescInitEntry(desc, attnum, attname,
-						   atttypid, atttypmod, attdim);
-		att = TupleDescAttr(desc, attnum - 1);
-
-		/* Override TupleDescInitEntry's settings as requested */
-		TupleDescInitEntryCollation(desc, attnum, attcollation);
-		if (entry->storage)
-			att->attstorage = entry->storage;
-
-		/* Fill in additional stuff not handled by TupleDescInitEntry */
-		att->attnotnull = entry->is_not_null;
-		has_not_null |= entry->is_not_null;
-		att->attislocal = entry->is_local;
-		att->attinhcount = entry->inhcount;
-		att->attidentity = entry->identity;
-		att->attgenerated = entry->generated;
-	}
-
-	if (has_not_null)
-	{
-		TupleConstr *constr = (TupleConstr *) palloc0(sizeof(TupleConstr));
-
-		constr->has_not_null = true;
-		constr->has_generated_stored = false;
-		constr->defval = NULL;
-		constr->missing = NULL;
-		constr->num_defval = 0;
-		constr->check = NULL;
-		constr->num_check = 0;
-		desc->constr = constr;
-	}
-	else
-	{
-		desc->constr = NULL;
-	}
-
-	return desc;
-}
-
 /*
  * BuildDescFromLists
  *
@@ -889,8 +783,7 @@ BuildDescForRelation(const List *columns)
  *
  * No constraints are generated.
  *
- * This is essentially a cut-down version of BuildDescForRelation for use
- * with functions returning RECORD.
+ * This is for use with functions returning RECORD.
  */
 TupleDesc
 BuildDescFromLists(const List *names, const List *types, const List *typmods, const List *collations)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 06e4527dd1..2760b8b111 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1252,6 +1252,108 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	return address;
 }
 
+/*
+ * BuildDescForRelation
+ *
+ * Given a list of ColumnDef nodes, build a TupleDesc.
+ *
+ * Note: tdtypeid will need to be filled in later on.
+ */
+TupleDesc
+BuildDescForRelation(const List *columns)
+{
+	int			natts;
+	AttrNumber	attnum;
+	ListCell   *l;
+	TupleDesc	desc;
+	bool		has_not_null;
+	char	   *attname;
+	Oid			atttypid;
+	int32		atttypmod;
+	Oid			attcollation;
+	int			attdim;
+
+	/*
+	 * allocate a new tuple descriptor
+	 */
+	natts = list_length(columns);
+	desc = CreateTemplateTupleDesc(natts);
+	has_not_null = false;
+
+	attnum = 0;
+
+	foreach(l, columns)
+	{
+		ColumnDef  *entry = lfirst(l);
+		AclResult	aclresult;
+		Form_pg_attribute att;
+
+		/*
+		 * for each entry in the list, get the name and type information from
+		 * the list and have TupleDescInitEntry fill in the attribute
+		 * information we need.
+		 */
+		attnum++;
+
+		attname = entry->colname;
+		typenameTypeIdAndMod(NULL, entry->typeName, &atttypid, &atttypmod);
+
+		aclresult = object_aclcheck(TypeRelationId, atttypid, GetUserId(), ACL_USAGE);
+		if (aclresult != ACLCHECK_OK)
+			aclcheck_error_type(aclresult, atttypid);
+
+		attcollation = GetColumnDefCollation(NULL, entry, atttypid);
+		attdim = list_length(entry->typeName->arrayBounds);
+		if (attdim > PG_INT16_MAX)
+			ereport(ERROR,
+					errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+					errmsg("too many array dimensions"));
+
+		if (entry->typeName->setof)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column \"%s\" cannot be declared SETOF",
+							attname)));
+
+		TupleDescInitEntry(desc, attnum, attname,
+						   atttypid, atttypmod, attdim);
+		att = TupleDescAttr(desc, attnum - 1);
+
+		/* Override TupleDescInitEntry's settings as requested */
+		TupleDescInitEntryCollation(desc, attnum, attcollation);
+		if (entry->storage)
+			att->attstorage = entry->storage;
+
+		/* Fill in additional stuff not handled by TupleDescInitEntry */
+		att->attnotnull = entry->is_not_null;
+		has_not_null |= entry->is_not_null;
+		att->attislocal = entry->is_local;
+		att->attinhcount = entry->inhcount;
+		att->attidentity = entry->identity;
+		att->attgenerated = entry->generated;
+	}
+
+	if (has_not_null)
+	{
+		TupleConstr *constr = (TupleConstr *) palloc0(sizeof(TupleConstr));
+
+		constr->has_not_null = true;
+		constr->has_generated_stored = false;
+		constr->defval = NULL;
+		constr->missing = NULL;
+		constr->num_defval = 0;
+		constr->check = NULL;
+		constr->num_check = 0;
+		desc->constr = constr;
+	}
+	else
+	{
+		desc->constr = NULL;
+	}
+
+	return desc;
+}
+
 /*
  * Emit the right error or warning message for a "DROP" command issued on a
  * non-existent relation
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index f6cc28a661..f61c7cc784 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -147,8 +147,6 @@ extern void TupleDescInitEntryCollation(TupleDesc desc,
 										AttrNumber attributeNumber,
 										Oid collationid);
 
-extern TupleDesc BuildDescForRelation(const List *columns);
-
 extern TupleDesc BuildDescFromLists(const List *names, const List *types, const List *typmods, const List *collations);
 
 #endif							/* TUPDESC_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 250d89ff88..8a8a9cd0ae 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -27,6 +27,8 @@ struct AlterTableUtilityContext;	/* avoid including tcop/utility.h here */
 extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 									ObjectAddress *typaddress, const char *queryString);
 
+extern TupleDesc BuildDescForRelation(const List *columns);
+
 extern void RemoveRelations(DropStmt *drop);
 
 extern Oid	AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode);
-- 
2.41.0

0014-Push-attcompression-and-attstorage-handling-into-Bui.patchtext/plain; charset=UTF-8; name=0014-Push-attcompression-and-attstorage-handling-into-Bui.patchDownload
From dbbf9580358c18d78a2a11492b166818e3c19eec Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Fri, 16 Jun 2023 12:10:23 +0200
Subject: [PATCH 14/17] Push attcompression and attstorage handling into
 BuildDescForRelation()

This was previously handled by the callers but it can be moved into a
common place.
---
 src/backend/commands/tablecmds.c | 19 +++++--------------
 1 file changed, 5 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2760b8b111..c1be6be826 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -926,10 +926,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			cookedDefaults = lappend(cookedDefaults, cooked);
 			attr->atthasdef = true;
 		}
-
-		attr->attcompression = GetAttributeCompression(attr->atttypid, colDef->compression);
-		if (colDef->storage_name)
-			attr->attstorage = GetAttributeStorage(attr->atttypid, colDef->storage_name);
 	}
 
 	/*
@@ -1321,8 +1317,6 @@ BuildDescForRelation(const List *columns)
 
 		/* Override TupleDescInitEntry's settings as requested */
 		TupleDescInitEntryCollation(desc, attnum, attcollation);
-		if (entry->storage)
-			att->attstorage = entry->storage;
 
 		/* Fill in additional stuff not handled by TupleDescInitEntry */
 		att->attnotnull = entry->is_not_null;
@@ -1331,6 +1325,11 @@ BuildDescForRelation(const List *columns)
 		att->attinhcount = entry->inhcount;
 		att->attidentity = entry->identity;
 		att->attgenerated = entry->generated;
+		att->attcompression = GetAttributeCompression(att->atttypid, entry->compression);
+		if (entry->storage)
+			att->attstorage = entry->storage;
+		else if (entry->storage_name)
+			att->attstorage = GetAttributeStorage(att->atttypid, entry->storage_name);
 	}
 
 	if (has_not_null)
@@ -6995,14 +6994,6 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Fix up attribute number */
 	attribute->attnum = newattnum;
 
-	/*
-	 * Additional fields not handled by BuildDescForRelation() (mirrors
-	 * DefineRelation())
-	 */
-	attribute->attcompression = GetAttributeCompression(attribute->atttypid, colDef->compression);
-	if (colDef->storage_name)
-		attribute->attstorage = GetAttributeStorage(attribute->atttypid, colDef->storage_name);
-
 	/* make sure datatype is legal for a column */
 	CheckAttributeType(NameStr(attribute->attname), attribute->atttypid, attribute->attcollation,
 					   list_make1_oid(rel->rd_rel->reltype),
-- 
2.41.0

0015-Add-TupleDescGetDefault.patchtext/plain; charset=UTF-8; name=0015-Add-TupleDescGetDefault.patchDownload
From ec1d40af2d1b890970c416df7a411b4c0af5c288 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Thu, 22 Jun 2023 15:21:17 +0200
Subject: [PATCH 15/17] Add TupleDescGetDefault()

This unifies some repetitive code.

Note: I didn't push the "not found" error message into the new
function, even though all existing callers would be able to make use
of it.  Using the existing error handling as-is would probably require
exposing the Relation type via tupdesc.h, which doesn't seem
desirable.
---
 src/backend/access/common/tupdesc.c  | 25 +++++++++++++++++++++++++
 src/backend/commands/tablecmds.c     | 17 ++---------------
 src/backend/parser/parse_utilcmd.c   | 13 ++-----------
 src/backend/rewrite/rewriteHandler.c | 16 +---------------
 src/include/access/tupdesc.h         |  2 ++
 5 files changed, 32 insertions(+), 41 deletions(-)

diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 5d85831339..d119cfafb5 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -822,3 +822,28 @@ BuildDescFromLists(const List *names, const List *types, const List *typmods, co
 
 	return desc;
 }
+
+/*
+ * Get default expression (or NULL if none) for the given attribute number.
+ */
+Node *
+TupleDescGetDefault(TupleDesc tupdesc, AttrNumber attnum)
+{
+	Node	   *result = NULL;
+
+	if (tupdesc->constr)
+	{
+		AttrDefault *attrdef = tupdesc->constr->defval;
+
+		for (int i = 0; i < tupdesc->constr->num_defval; i++)
+		{
+			if (attrdef[i].adnum == attnum)
+			{
+				result = stringToNode(attrdef[i].adbin);
+				break;
+			}
+		}
+	}
+
+	return result;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c1be6be826..bb0fb8b9af 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2785,22 +2785,9 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 			 */
 			if (attribute->atthasdef)
 			{
-				Node	   *this_default = NULL;
+				Node	   *this_default;
 
-				/* Find default in constraint structure */
-				if (constr != NULL)
-				{
-					AttrDefault *attrdef = constr->defval;
-
-					for (int i = 0; i < constr->num_defval; i++)
-					{
-						if (attrdef[i].adnum == parent_attno)
-						{
-							this_default = stringToNode(attrdef[i].adbin);
-							break;
-						}
-					}
-				}
+				this_default = TupleDescGetDefault(tupleDesc, parent_attno);
 				if (this_default == NULL)
 					elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
 						 parent_attno, RelationGetRelationName(relation));
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 53420306ec..9f80ac40f2 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1248,20 +1248,11 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 				 (table_like_clause->options & CREATE_TABLE_LIKE_GENERATED) :
 				 (table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS)))
 			{
-				Node	   *this_default = NULL;
-				AttrDefault *attrdef = constr->defval;
+				Node	   *this_default;
 				AlterTableCmd *atsubcmd;
 				bool		found_whole_row;
 
-				/* Find default in constraint structure */
-				for (int i = 0; i < constr->num_defval; i++)
-				{
-					if (attrdef[i].adnum == parent_attno)
-					{
-						this_default = stringToNode(attrdef[i].adbin);
-						break;
-					}
-				}
+				this_default = TupleDescGetDefault(tupleDesc, parent_attno);
 				if (this_default == NULL)
 					elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
 						 parent_attno, RelationGetRelationName(relation));
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b486ab559a..41a362310a 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1246,21 +1246,7 @@ build_column_default(Relation rel, int attrno)
 	 */
 	if (att_tup->atthasdef)
 	{
-		if (rd_att->constr && rd_att->constr->num_defval > 0)
-		{
-			AttrDefault *defval = rd_att->constr->defval;
-			int			ndef = rd_att->constr->num_defval;
-
-			while (--ndef >= 0)
-			{
-				if (attrno == defval[ndef].adnum)
-				{
-					/* Found it, convert string representation to node tree. */
-					expr = stringToNode(defval[ndef].adbin);
-					break;
-				}
-			}
-		}
+		expr = TupleDescGetDefault(rd_att, attrno);
 		if (expr == NULL)
 			elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
 				 attrno, RelationGetRelationName(rel));
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index f61c7cc784..d833d5f2e1 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -149,4 +149,6 @@ extern void TupleDescInitEntryCollation(TupleDesc desc,
 
 extern TupleDesc BuildDescFromLists(const List *names, const List *types, const List *typmods, const List *collations);
 
+extern Node *TupleDescGetDefault(TupleDesc tupdesc, AttrNumber attnum);
+
 #endif							/* TUPDESC_H */
-- 
2.41.0

0016-MergeAttributes-convert-pg_attribute-back-to-ColumnD.patchtext/plain; charset=UTF-8; name=0016-MergeAttributes-convert-pg_attribute-back-to-ColumnD.patchDownload
From 0896393c4794574b5bf476fc493842297b47848a Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Thu, 22 Jun 2023 20:17:56 +0200
Subject: [PATCH 16/17] MergeAttributes: convert pg_attribute back to ColumnDef
 before comparing

MergeAttributes() has a loop to merge multiple inheritance parents
into a column column definition.  The merged-so-far definition is
stored in a ColumnDef node.  If we have to merge columns from multiple
inheritance parents (if the name matches), then we have to check
whether various column properties (type, collation, etc.) match.  The
current code extracts the pg_attribute value of the
currently-considered inheritance parent and compares it against the
merge-so-far ColumnDef value.  If the currently considered column
doesn't match any previously inherited column, we make a new ColumnDef
node from the pg_attribute information and add it to the column list.

This patch rearranges this so that we create the ColumnDef node first
in either case.  Then the code that checks the column properties for
compatibility compares ColumnDef against ColumnDef (instead of
ColumnDef against pg_attribute).  This matches the code more symmetric
and easier to follow.  Also, later in MergeAttributes(), there is a
similar but separate loop that merges the new local column definition
with the combination of the inheritance parents established in the
first loop.  That comparison is already ColumnDef-vs-ColumnDef.  With
this change, but of these can use similar looking logic.  (A future
project might be to extract these two sets of code into a common
routine that encodes all the knowledge of whether two column
definitions are compatible.  But this isn't currently straightforward
because we want to give different error messages in the two cases.)
Furthermore, by avoiding the use of Form_pg_attribute here, we make it
easier to make changes in the pg_attribute layout without having to
worry about the local needs of tablecmds.c.

Because MergeAttributes() is hugely long, it's sometimes hard to know
where in the function you are currently looking.  To help with that, I
also renamed some variables to make it clearer where you are currently
looking.  The first look is "prev" vs. "new", the second loop is "inh"
vs. "new".
---
 src/backend/commands/tablecmds.c | 179 ++++++++++++++++---------------
 1 file changed, 93 insertions(+), 86 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bb0fb8b9af..ac7452bcff 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2649,7 +2649,8 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 														parent_attno - 1);
 			char	   *attributeName = NameStr(attribute->attname);
 			int			exist_attno;
-			ColumnDef  *def;
+			ColumnDef  *newdef;
+			ColumnDef  *savedef;
 
 			/*
 			 * Ignore dropped columns in the parent.
@@ -2658,14 +2659,30 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				continue;		/* leave newattmap->attnums entry as zero */
 
 			/*
-			 * Does it conflict with some previously inherited column?
+			 * Create new column definition
+			 */
+			newdef = makeColumnDef(attributeName, attribute->atttypid, attribute->atttypmod, attribute->attcollation);
+			newdef->is_not_null = attribute->attnotnull;
+			newdef->storage = attribute->attstorage;
+			newdef->generated = attribute->attgenerated;
+			if (CompressionMethodIsValid(attribute->attcompression))
+				newdef->compression =
+					pstrdup(GetCompressionMethodName(attribute->attcompression));
+
+			/*
+			 * Does it match some previously considered column from another
+			 * parent?
 			 */
 			exist_attno = findAttrByName(attributeName, inh_columns);
 			if (exist_attno > 0)
 			{
-				Oid			defTypeId;
-				int32		deftypmod;
-				Oid			defCollId;
+				ColumnDef  *prevdef;
+				Oid			prevtypeid,
+							newtypeid;
+				int32		prevtypmod,
+							newtypmod;
+				Oid			prevcollid,
+							newcollid;
 
 				/*
 				 * Yes, try to merge the two column definitions.
@@ -2673,78 +2690,71 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				ereport(NOTICE,
 						(errmsg("merging multiple inherited definitions of column \"%s\"",
 								attributeName)));
-				def = (ColumnDef *) list_nth(inh_columns, exist_attno - 1);
+				prevdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
 
 				/*
 				 * Must have the same type and typmod
 				 */
-				typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
-				if (defTypeId != attribute->atttypid ||
-					deftypmod != attribute->atttypmod)
+				typenameTypeIdAndMod(NULL, prevdef->typeName, &prevtypeid, &prevtypmod);
+				typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
+				if (prevtypeid != newtypeid || prevtypmod != newtypmod)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("inherited column \"%s\" has a type conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   format_type_with_typemod(defTypeId,
-																deftypmod),
-									   format_type_with_typemod(attribute->atttypid,
-																attribute->atttypmod))));
+									   format_type_with_typemod(prevtypeid, prevtypmod),
+									   format_type_with_typemod(newtypeid, newtypmod))));
 
 				/*
 				 * Must have the same collation
 				 */
-				defCollId = GetColumnDefCollation(NULL, def, defTypeId);
-				if (defCollId != attribute->attcollation)
+				prevcollid = GetColumnDefCollation(NULL, prevdef, prevtypeid);
+				newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
+				if (prevcollid != newcollid)
 					ereport(ERROR,
 							(errcode(ERRCODE_COLLATION_MISMATCH),
 							 errmsg("inherited column \"%s\" has a collation conflict",
 									attributeName),
 							 errdetail("\"%s\" versus \"%s\"",
-									   get_collation_name(defCollId),
-									   get_collation_name(attribute->attcollation))));
+									   get_collation_name(prevcollid),
+									   get_collation_name(newcollid))));
 
 				/*
 				 * Copy/check storage parameter
 				 */
-				if (def->storage == 0)
-					def->storage = attribute->attstorage;
-				else if (def->storage != attribute->attstorage)
+				if (prevdef->storage == 0)
+					prevdef->storage = newdef->storage;
+				else if (prevdef->storage != newdef->storage)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("inherited column \"%s\" has a storage parameter conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   storage_name(def->storage),
-									   storage_name(attribute->attstorage))));
+									   storage_name(prevdef->storage),
+									   storage_name(newdef->storage))));
 
 				/*
 				 * Copy/check compression parameter
 				 */
-				if (CompressionMethodIsValid(attribute->attcompression))
-				{
-					const char *compression =
-						GetCompressionMethodName(attribute->attcompression);
-
-					if (def->compression == NULL)
-						def->compression = pstrdup(compression);
-					else if (strcmp(def->compression, compression) != 0)
-						ereport(ERROR,
-								(errcode(ERRCODE_DATATYPE_MISMATCH),
-								 errmsg("column \"%s\" has a compression method conflict",
-										attributeName),
-								 errdetail("%s versus %s", def->compression, compression)));
-				}
+				if (prevdef->compression == NULL)
+					prevdef->compression = newdef->compression;
+				else if (strcmp(prevdef->compression, newdef->compression) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("column \"%s\" has a compression method conflict",
+									attributeName),
+							 errdetail("%s versus %s", prevdef->compression, newdef->compression)));
 
 				/*
 				 * Merge of NOT NULL constraints = OR 'em together
 				 */
-				def->is_not_null |= attribute->attnotnull;
+				prevdef->is_not_null |= newdef->is_not_null;
 
 				/*
 				 * Check for GENERATED conflicts
 				 */
-				if (def->generated != attribute->attgenerated)
+				if (prevdef->generated != newdef->generated)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("inherited column \"%s\" has a generation conflict",
@@ -2754,30 +2764,29 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 * Default and other constraints are handled below
 				 */
 
-				def->inhcount++;
-				if (def->inhcount < 0)
+				prevdef->inhcount++;
+				if (prevdef->inhcount < 0)
 					ereport(ERROR,
 							errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 							errmsg("too many inheritance parents"));
 
 				newattmap->attnums[parent_attno - 1] = exist_attno;
+
+				/* remember for default processing below */
+				savedef = prevdef;
 			}
 			else
 			{
 				/*
 				 * No, create a new inherited column
 				 */
-				def = makeColumnDef(attributeName, attribute->atttypid, attribute->atttypmod, attribute->attcollation);
-				def->inhcount = 1;
-				def->is_local = false;
-				def->is_not_null = attribute->attnotnull;
-				def->storage = attribute->attstorage;
-				def->generated = attribute->attgenerated;
-				if (CompressionMethodIsValid(attribute->attcompression))
-					def->compression =
-						pstrdup(GetCompressionMethodName(attribute->attcompression));
-				inh_columns = lappend(inh_columns, def);
+				newdef->inhcount = 1;
+				newdef->is_local = false;
+				inh_columns = lappend(inh_columns, newdef);
 				newattmap->attnums[parent_attno - 1] = ++child_attno;
+
+				/* remember for default processing below */
+				savedef = newdef;
 			}
 
 			/*
@@ -2799,7 +2808,7 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 * all the inherited default expressions for the moment.
 				 */
 				inherited_defaults = lappend(inherited_defaults, this_default);
-				cols_with_defaults = lappend(cols_with_defaults, def);
+				cols_with_defaults = lappend(cols_with_defaults, savedef);
 			}
 		}
 
@@ -2920,17 +2929,17 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 			newcol_attno++;
 
 			/*
-			 * Does it conflict with some previously inherited column?
+			 * Does it match some inherited column?
 			 */
 			exist_attno = findAttrByName(attributeName, inh_columns);
 			if (exist_attno > 0)
 			{
-				ColumnDef  *def;
-				Oid			defTypeId,
-							newTypeId;
-				int32		deftypmod,
+				ColumnDef  *inhdef;
+				Oid			inhtypeid,
+							newtypeid;
+				int32		inhtypmod,
 							newtypmod;
-				Oid			defcollid,
+				Oid			inhcollid,
 							newcollid;
 
 				/*
@@ -2950,77 +2959,75 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 					ereport(NOTICE,
 							(errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
 							 errdetail("User-specified column moved to the position of the inherited column.")));
-				def = (ColumnDef *) list_nth(inh_columns, exist_attno - 1);
+				inhdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
 
 				/*
 				 * Must have the same type and typmod
 				 */
-				typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
-				typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
-				if (defTypeId != newTypeId || deftypmod != newtypmod)
+				typenameTypeIdAndMod(NULL, inhdef->typeName, &inhtypeid, &inhtypmod);
+				typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
+				if (inhtypeid != newtypeid || inhtypmod != newtypmod)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("column \"%s\" has a type conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   format_type_with_typemod(defTypeId,
-																deftypmod),
-									   format_type_with_typemod(newTypeId,
-																newtypmod))));
+									   format_type_with_typemod(inhtypeid, inhtypmod),
+									   format_type_with_typemod(newtypeid, newtypmod))));
 
 				/*
 				 * Must have the same collation
 				 */
-				defcollid = GetColumnDefCollation(NULL, def, defTypeId);
-				newcollid = GetColumnDefCollation(NULL, newdef, newTypeId);
-				if (defcollid != newcollid)
+				inhcollid = GetColumnDefCollation(NULL, inhdef, inhtypeid);
+				newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
+				if (inhcollid != newcollid)
 					ereport(ERROR,
 							(errcode(ERRCODE_COLLATION_MISMATCH),
 							 errmsg("column \"%s\" has a collation conflict",
 									attributeName),
 							 errdetail("\"%s\" versus \"%s\"",
-									   get_collation_name(defcollid),
+									   get_collation_name(inhcollid),
 									   get_collation_name(newcollid))));
 
 				/*
 				 * Identity is never inherited.  The new column can have an
 				 * identity definition, so we always just take that one.
 				 */
-				def->identity = newdef->identity;
+				inhdef->identity = newdef->identity;
 
 				/*
 				 * Copy storage parameter
 				 */
-				if (def->storage == 0)
-					def->storage = newdef->storage;
-				else if (newdef->storage != 0 && def->storage != newdef->storage)
+				if (inhdef->storage == 0)
+					inhdef->storage = newdef->storage;
+				else if (newdef->storage != 0 && inhdef->storage != newdef->storage)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("column \"%s\" has a storage parameter conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   storage_name(def->storage),
+									   storage_name(inhdef->storage),
 									   storage_name(newdef->storage))));
 
 				/*
 				 * Copy compression parameter
 				 */
-				if (def->compression == NULL)
-					def->compression = newdef->compression;
+				if (inhdef->compression == NULL)
+					inhdef->compression = newdef->compression;
 				else if (newdef->compression != NULL)
 				{
-					if (strcmp(def->compression, newdef->compression) != 0)
+					if (strcmp(inhdef->compression, newdef->compression) != 0)
 						ereport(ERROR,
 								(errcode(ERRCODE_DATATYPE_MISMATCH),
 								 errmsg("column \"%s\" has a compression method conflict",
 										attributeName),
-								 errdetail("%s versus %s", def->compression, newdef->compression)));
+								 errdetail("%s versus %s", inhdef->compression, newdef->compression)));
 				}
 
 				/*
 				 * Merge of NOT NULL constraints = OR 'em together
 				 */
-				def->is_not_null |= newdef->is_not_null;
+				inhdef->is_not_null |= newdef->is_not_null;
 
 				/*
 				 * Check for conflicts related to generated columns.
@@ -3037,18 +3044,18 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 * it results in being able to override the generation
 				 * expression via UPDATEs through the parent.)
 				 */
-				if (def->generated)
+				if (inhdef->generated)
 				{
 					if (newdef->raw_default && !newdef->generated)
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
 								 errmsg("column \"%s\" inherits from generated column but specifies default",
-										def->colname)));
+										inhdef->colname)));
 					if (newdef->identity)
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
 								 errmsg("column \"%s\" inherits from generated column but specifies identity",
-										def->colname)));
+										inhdef->colname)));
 				}
 				else
 				{
@@ -3056,7 +3063,7 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
 								 errmsg("child column \"%s\" specifies generation expression",
-										def->colname),
+										inhdef->colname),
 								 errhint("A child table column cannot be generated unless its parent column is.")));
 				}
 
@@ -3065,12 +3072,12 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 */
 				if (newdef->raw_default != NULL)
 				{
-					def->raw_default = newdef->raw_default;
-					def->cooked_default = newdef->cooked_default;
+					inhdef->raw_default = newdef->raw_default;
+					inhdef->cooked_default = newdef->cooked_default;
 				}
 
 				/* Mark the column as locally defined */
-				def->is_local = true;
+				inhdef->is_local = true;
 			}
 			else
 			{
-- 
2.41.0

0017-Add-some-const-decorations.patchtext/plain; charset=UTF-8; name=0017-Add-some-const-decorations.patchDownload
From 87ed2951471e3fe6dc1bc318e1dd8e3ea4eab95f Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Tue, 27 Jun 2023 11:58:51 +0200
Subject: [PATCH 17/17] Add some const decorations

---
 src/backend/commands/tablecmds.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ac7452bcff..0476110372 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -636,7 +636,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
-static char GetAttributeCompression(Oid atttypid, char *compression);
+static char GetAttributeCompression(Oid atttypid, const char *compression);
 static char GetAttributeStorage(Oid atttypid, const char *storagemode);
 
 
@@ -19310,7 +19310,7 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
  * resolve column compression specification to compression method.
  */
 static char
-GetAttributeCompression(Oid atttypid, char *compression)
+GetAttributeCompression(Oid atttypid, const char *compression)
 {
 	char		cmethod;
 
-- 
2.41.0

#2Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Peter Eisentraut (#1)
Re: tablecmds.c/MergeAttributes() cleanup

On 2023-Jun-28, Peter Eisentraut wrote:

The MergeAttributes() and related code in and around tablecmds.c is huge and
ancient, with many things bolted on over time, and difficult to deal with.
I took some time to make careful incremental updates and refactorings to
make the code easier to follow, more compact, and more modern in appearance.
I also found several pieces of obsolete code along the way. This resulted
in the attached long patch series. Each patch tries to make a single change
and can be considered incrementally. At the end, the code is shorter,
better factored, and I hope easier to understand. There shouldn't be any
change in behavior.

I request to leave this alone for now. I have enough things to juggle
with in the NOTNULLs patch; this patchset looks like it will cause me
messy merge conflicts. 0004 for instance looks problematic, as does
0007 and 0016.

FWIW for the most part that patch is working and I intend to re-submit
shortly, but the relevant pg_upgrade code is really brittle, so it's
taken me much more than I expected to get it in good shape for all
cases.

Thanks

--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/

#3Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Peter Eisentraut (#1)
Re: tablecmds.c/MergeAttributes() cleanup

On 2023-Jun-28, Peter Eisentraut wrote:

The MergeAttributes() and related code in and around tablecmds.c is huge and
ancient, with many things bolted on over time, and difficult to deal with.
I took some time to make careful incremental updates and refactorings to
make the code easier to follow, more compact, and more modern in appearance.
I also found several pieces of obsolete code along the way. This resulted
in the attached long patch series. Each patch tries to make a single change
and can be considered incrementally. At the end, the code is shorter,
better factored, and I hope easier to understand. There shouldn't be any
change in behavior.

I spent a few minutes doing a test merge of this to my branch with NOT
NULL changes. Here's a quick review.

Subject: [PATCH 01/17] Remove obsolete comment about OID support

Obvious, trivial. +1

Subject: [PATCH 02/17] Remove ancient special case code for adding oid columns

LGTM; deletes dead code.

Subject: [PATCH 03/17] Remove ancient special case code for dropping oid
columns

Hmm, interesting. Yay for more dead code removal. Didn't verify it.

Subject: [PATCH 04/17] Make more use of makeColumnDef()

Good idea, +1. Some lines (almost all makeColumnDef callsites) end up
too long. This is the first patch that actually conflicts with the NOT
NULLs one, and the conflicts are easy to solve, so I won't complain if
you want to get it pushed soon.

Subject: [PATCH 05/17] Clean up MergeAttributesIntoExisting()

I don't disagree with this in principle, but this one has more
conflicts than the previous ones.

Subject: [PATCH 06/17] Clean up MergeCheckConstraint()

Looks a reasonable change as far as this patch goes.

However, reading it I noticed that CookedConstraint->inhcount is int
and is tested for wraparound, but pg_constraint.coninhcount is int16.
This is probably bogus already. ColumnDef->inhcount is also int. These
should be narrowed to match the catalog definitions.

Subject: [PATCH 07/17] MergeAttributes() and related variable renaming

I think this makes sense, but there's a bunch of conflicts to NOT NULLs.
I guess we can come back to this one later.

Subject: [PATCH 08/17] Improve some catalog documentation

Point out that typstorage and attstorage are never '\0', even for
fixed-length types. This is different from attcompression. For this
reason, some of the handling of these columns in tablecmds.c etc. is
different. (catalogs.sgml already contained this information in an
indirect way.)

I don't understand why we must point out that they're never '\0'. I
mean, if we're doing that, why not say that they can never be \xFF?
The valid values are already listed. The other parts of this patch look
okay.

Subject: [PATCH 09/17] Remove useless if condition

This is useless because these fields are not set anywhere before, so
we can assign them unconditionally. This also makes this more
consistent with ATExecAddColumn().

Makes sense.

Subject: [PATCH 10/17] Remove useless if condition

We can call GetAttributeCompression() with a NULL argument. It
handles that internally already. This change makes all the callers of
GetAttributeCompression() uniform.

I agree, +1.

From 2eda6bc9897d0995a5112e2851c51daf0c35656e Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 14 Jun 2023 17:51:31 +0200
Subject: [PATCH 11/17] Refactor ATExecAddColumn() to use
BuildDescForRelation()

BuildDescForRelation() has all the knowledge for converting a
ColumnDef into pg_attribute/tuple descriptor. ATExecAddColumn() can
make use of that, instead of duplicating all that logic. We just pass
a one-element list of ColumnDef and we get back exactly the data
structure we need. Note that we don't even need to touch
BuildDescForRelation() to make this work.

Hmm, crazy. I'm not sure I like this, because it seems much too clever.
The number of lines that are deleted is alluring, though.

Maybe it'd be better to create a separate routine that takes a single
ColumnDef and returns the Form_pg_attribute element for it; then use
that in both BuildDescForRelation and ATExecAddColumn.

Subject: [PATCH 12/17] Push attidentity and attgenerated handling into
BuildDescForRelation()

Previously, this was handled by the callers separately, but it can be
trivially moved into BuildDescForRelation() so that it is handled in a
central place.

Looks reasonable.

I think the last few patches are the more innovative (interesting,
useful) of the bunch. Let's get the first few out of the way.

--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/

#4Peter Eisentraut
peter@eisentraut.org
In reply to: Alvaro Herrera (#3)
Re: tablecmds.c/MergeAttributes() cleanup

On 11.07.23 20:17, Alvaro Herrera wrote:

I spent a few minutes doing a test merge of this to my branch with NOT
NULL changes. Here's a quick review.

Subject: [PATCH 01/17] Remove obsolete comment about OID support

Obvious, trivial. +1

Subject: [PATCH 02/17] Remove ancient special case code for adding oid columns

LGTM; deletes dead code.

Subject: [PATCH 03/17] Remove ancient special case code for dropping oid
columns

Hmm, interesting. Yay for more dead code removal. Didn't verify it.

I have committed these first three. I'll leave it at that for now.

Subject: [PATCH 08/17] Improve some catalog documentation

Point out that typstorage and attstorage are never '\0', even for
fixed-length types. This is different from attcompression. For this
reason, some of the handling of these columns in tablecmds.c etc. is
different. (catalogs.sgml already contained this information in an
indirect way.)

I don't understand why we must point out that they're never '\0'. I
mean, if we're doing that, why not say that they can never be \xFF?
The valid values are already listed. The other parts of this patch look
okay.

While working through the storage and compression handling, which look
similar, I was momentarily puzzled by this. While attcompression can be
0 to mean, use default, this is not possible/allowed for attstorage, but
it took looking around three corners to verify this. It could be more
explicit, I thought.

#5Peter Eisentraut
peter@eisentraut.org
In reply to: Peter Eisentraut (#4)
10 attachment(s)
Re: tablecmds.c/MergeAttributes() cleanup

On 12.07.23 16:29, Peter Eisentraut wrote:

On 11.07.23 20:17, Alvaro Herrera wrote:

I spent a few minutes doing a test merge of this to my branch with NOT
NULL changes.  Here's a quick review.

Subject: [PATCH 01/17] Remove obsolete comment about OID support

Obvious, trivial.  +1

Subject: [PATCH 02/17] Remove ancient special case code for adding
oid columns

LGTM; deletes dead code.

Subject: [PATCH 03/17] Remove ancient special case code for dropping oid
  columns

Hmm, interesting.  Yay for more dead code removal.  Didn't verify it.

I have committed these first three.  I'll leave it at that for now.

I have committed a few more patches from this series that were already
agreed upon. The remaining ones are rebased and reordered a bit, attached.

There was some doubt about the patch "Refactor ATExecAddColumn() to use
BuildDescForRelation()" (now 0009), whether it's too clever to build a
fake one-item tuple descriptor. I am working on another patch, which I
hope to post this week, that proposes to replace the use of tuple
descriptors there with a List of something. That would then allow maybe
rewriting this in a less-clever way. That patch incidentally also wants
to move BuildDescForRelation from tupdesc.c to tablecmds.c (patch 0007
here).

Attachments:

v2-0010-MergeAttributes-convert-pg_attribute-back-to-Colu.patchtext/plain; charset=UTF-8; name=v2-0010-MergeAttributes-convert-pg_attribute-back-to-Colu.patchDownload
From ce3a0ad830ac167c01809a2288e4a9b9a9de62de Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:35 +0200
Subject: [PATCH v2 10/10] MergeAttributes: convert pg_attribute back to
 ColumnDef before comparing

MergeAttributes() has a loop to merge multiple inheritance parents
into a column column definition.  The merged-so-far definition is
stored in a ColumnDef node.  If we have to merge columns from multiple
inheritance parents (if the name matches), then we have to check
whether various column properties (type, collation, etc.) match.  The
current code extracts the pg_attribute value of the
currently-considered inheritance parent and compares it against the
merge-so-far ColumnDef value.  If the currently considered column
doesn't match any previously inherited column, we make a new ColumnDef
node from the pg_attribute information and add it to the column list.

This patch rearranges this so that we create the ColumnDef node first
in either case.  Then the code that checks the column properties for
compatibility compares ColumnDef against ColumnDef (instead of
ColumnDef against pg_attribute).  This matches the code more symmetric
and easier to follow.  Also, later in MergeAttributes(), there is a
similar but separate loop that merges the new local column definition
with the combination of the inheritance parents established in the
first loop.  That comparison is already ColumnDef-vs-ColumnDef.  With
this change, but of these can use similar looking logic.  (A future
project might be to extract these two sets of code into a common
routine that encodes all the knowledge of whether two column
definitions are compatible.  But this isn't currently straightforward
because we want to give different error messages in the two cases.)
Furthermore, by avoiding the use of Form_pg_attribute here, we make it
easier to make changes in the pg_attribute layout without having to
worry about the local needs of tablecmds.c.

Because MergeAttributes() is hugely long, it's sometimes hard to know
where in the function you are currently looking.  To help with that, I
also renamed some variables to make it clearer where you are currently
looking.  The first look is "prev" vs. "new", the second loop is "inh"
vs. "new".
---
 src/backend/commands/tablecmds.c | 181 ++++++++++++++++---------------
 1 file changed, 94 insertions(+), 87 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c70cf7af5..16e4a9e5fd 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2698,7 +2698,8 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 														parent_attno - 1);
 			char	   *attributeName = NameStr(attribute->attname);
 			int			exist_attno;
-			ColumnDef  *def;
+			ColumnDef  *newdef;
+			ColumnDef  *savedef;
 
 			/*
 			 * Ignore dropped columns in the parent.
@@ -2707,14 +2708,30 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				continue;		/* leave newattmap->attnums entry as zero */
 
 			/*
-			 * Does it conflict with some previously inherited column?
+			 * Create new column definition
+			 */
+			newdef = makeColumnDef(attributeName, attribute->atttypid,
+								   attribute->atttypmod, attribute->attcollation);
+			newdef->storage = attribute->attstorage;
+			newdef->generated = attribute->attgenerated;
+			if (CompressionMethodIsValid(attribute->attcompression))
+				newdef->compression =
+					pstrdup(GetCompressionMethodName(attribute->attcompression));
+
+			/*
+			 * Does it match some previously considered column from another
+			 * parent?
 			 */
 			exist_attno = findAttrByName(attributeName, inh_columns);
 			if (exist_attno > 0)
 			{
-				Oid			defTypeId;
-				int32		deftypmod;
-				Oid			defCollId;
+				ColumnDef  *prevdef;
+				Oid			prevtypeid,
+							newtypeid;
+				int32		prevtypmod,
+							newtypmod;
+				Oid			prevcollid,
+							newcollid;
 
 				/*
 				 * Yes, try to merge the two column definitions.
@@ -2722,68 +2739,61 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				ereport(NOTICE,
 						(errmsg("merging multiple inherited definitions of column \"%s\"",
 								attributeName)));
-				def = (ColumnDef *) list_nth(inh_columns, exist_attno - 1);
+				prevdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
 
 				/*
 				 * Must have the same type and typmod
 				 */
-				typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
-				if (defTypeId != attribute->atttypid ||
-					deftypmod != attribute->atttypmod)
+				typenameTypeIdAndMod(NULL, prevdef->typeName, &prevtypeid, &prevtypmod);
+				typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
+				if (prevtypeid != newtypeid || prevtypmod != newtypmod)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("inherited column \"%s\" has a type conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   format_type_with_typemod(defTypeId,
-																deftypmod),
-									   format_type_with_typemod(attribute->atttypid,
-																attribute->atttypmod))));
+									   format_type_with_typemod(prevtypeid, prevtypmod),
+									   format_type_with_typemod(newtypeid, newtypmod))));
 
 				/*
 				 * Must have the same collation
 				 */
-				defCollId = GetColumnDefCollation(NULL, def, defTypeId);
-				if (defCollId != attribute->attcollation)
+				prevcollid = GetColumnDefCollation(NULL, prevdef, prevtypeid);
+				newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
+				if (prevcollid != newcollid)
 					ereport(ERROR,
 							(errcode(ERRCODE_COLLATION_MISMATCH),
 							 errmsg("inherited column \"%s\" has a collation conflict",
 									attributeName),
 							 errdetail("\"%s\" versus \"%s\"",
-									   get_collation_name(defCollId),
-									   get_collation_name(attribute->attcollation))));
+									   get_collation_name(prevcollid),
+									   get_collation_name(newcollid))));
 
 				/*
 				 * Copy/check storage parameter
 				 */
-				if (def->storage == 0)
-					def->storage = attribute->attstorage;
-				else if (def->storage != attribute->attstorage)
+				if (prevdef->storage == 0)
+					prevdef->storage = newdef->storage;
+				else if (prevdef->storage != newdef->storage)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("inherited column \"%s\" has a storage parameter conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   storage_name(def->storage),
-									   storage_name(attribute->attstorage))));
+									   storage_name(prevdef->storage),
+									   storage_name(newdef->storage))));
 
 				/*
 				 * Copy/check compression parameter
 				 */
-				if (CompressionMethodIsValid(attribute->attcompression))
-				{
-					const char *compression =
-						GetCompressionMethodName(attribute->attcompression);
-
-					if (def->compression == NULL)
-						def->compression = pstrdup(compression);
-					else if (strcmp(def->compression, compression) != 0)
-						ereport(ERROR,
-								(errcode(ERRCODE_DATATYPE_MISMATCH),
-								 errmsg("column \"%s\" has a compression method conflict",
-										attributeName),
-								 errdetail("%s versus %s", def->compression, compression)));
-				}
+				if (prevdef->compression == NULL)
+					prevdef->compression = newdef->compression;
+				else if (strcmp(prevdef->compression, newdef->compression) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("column \"%s\" has a compression method conflict",
+									attributeName),
+							 errdetail("%s versus %s", prevdef->compression, newdef->compression)));
 
 				/*
 				 * In regular inheritance, columns in the parent's primary key
@@ -2817,12 +2827,12 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				if (bms_is_member(parent_attno, nncols) ||
 					bms_is_member(parent_attno - FirstLowInvalidHeapAttributeNumber,
 								  pkattrs))
-					def->is_not_null = true;
+					newdef->is_not_null = true;
 
 				/*
 				 * Check for GENERATED conflicts
 				 */
-				if (def->generated != attribute->attgenerated)
+				if (prevdef->generated != newdef->generated)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("inherited column \"%s\" has a generation conflict",
@@ -2832,34 +2842,30 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 * Default and other constraints are handled below
 				 */
 
-				def->inhcount++;
-				if (def->inhcount < 0)
+				prevdef->inhcount++;
+				if (prevdef->inhcount < 0)
 					ereport(ERROR,
 							errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 							errmsg("too many inheritance parents"));
 
 				newattmap->attnums[parent_attno - 1] = exist_attno;
+
+				/* remember for default processing below */
+				savedef = prevdef;
 			}
 			else
 			{
 				/*
 				 * No, create a new inherited column
 				 */
-				def = makeColumnDef(attributeName, attribute->atttypid,
-									attribute->atttypmod, attribute->attcollation);
-				def->inhcount = 1;
-				def->is_local = false;
+				newdef->inhcount = 1;
+				newdef->is_local = false;
 				/* mark attnotnull if parent has it and it's not NO INHERIT */
 				if (bms_is_member(parent_attno, nncols) ||
 					bms_is_member(parent_attno - FirstLowInvalidHeapAttributeNumber,
 								  pkattrs))
-					def->is_not_null = true;
-				def->storage = attribute->attstorage;
-				def->generated = attribute->attgenerated;
-				if (CompressionMethodIsValid(attribute->attcompression))
-					def->compression =
-						pstrdup(GetCompressionMethodName(attribute->attcompression));
-				inh_columns = lappend(inh_columns, def);
+					newdef->is_not_null = true;
+				inh_columns = lappend(inh_columns, newdef);
 				newattmap->attnums[parent_attno - 1] = ++child_attno;
 
 				/*
@@ -2888,6 +2894,9 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 
 					nnconstraints = lappend(nnconstraints, nn);
 				}
+
+				/* remember for default processing below */
+				savedef = newdef;
 			}
 
 			/*
@@ -2909,7 +2918,7 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 * all the inherited default expressions for the moment.
 				 */
 				inherited_defaults = lappend(inherited_defaults, this_default);
-				cols_with_defaults = lappend(cols_with_defaults, def);
+				cols_with_defaults = lappend(cols_with_defaults, savedef);
 			}
 		}
 
@@ -3047,17 +3056,17 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 			newcol_attno++;
 
 			/*
-			 * Does it conflict with some previously inherited column?
+			 * Does it match some inherited column?
 			 */
 			exist_attno = findAttrByName(attributeName, inh_columns);
 			if (exist_attno > 0)
 			{
-				ColumnDef  *def;
-				Oid			defTypeId,
-							newTypeId;
-				int32		deftypmod,
+				ColumnDef  *inhdef;
+				Oid			inhtypeid,
+							newtypeid;
+				int32		inhtypmod,
 							newtypmod;
-				Oid			defcollid,
+				Oid			inhcollid,
 							newcollid;
 
 				/*
@@ -3077,77 +3086,75 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 					ereport(NOTICE,
 							(errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
 							 errdetail("User-specified column moved to the position of the inherited column.")));
-				def = (ColumnDef *) list_nth(inh_columns, exist_attno - 1);
+				inhdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
 
 				/*
 				 * Must have the same type and typmod
 				 */
-				typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
-				typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
-				if (defTypeId != newTypeId || deftypmod != newtypmod)
+				typenameTypeIdAndMod(NULL, inhdef->typeName, &inhtypeid, &inhtypmod);
+				typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
+				if (inhtypeid != newtypeid || inhtypmod != newtypmod)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("column \"%s\" has a type conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   format_type_with_typemod(defTypeId,
-																deftypmod),
-									   format_type_with_typemod(newTypeId,
-																newtypmod))));
+									   format_type_with_typemod(inhtypeid, inhtypmod),
+									   format_type_with_typemod(newtypeid, newtypmod))));
 
 				/*
 				 * Must have the same collation
 				 */
-				defcollid = GetColumnDefCollation(NULL, def, defTypeId);
-				newcollid = GetColumnDefCollation(NULL, newdef, newTypeId);
-				if (defcollid != newcollid)
+				inhcollid = GetColumnDefCollation(NULL, inhdef, inhtypeid);
+				newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
+				if (inhcollid != newcollid)
 					ereport(ERROR,
 							(errcode(ERRCODE_COLLATION_MISMATCH),
 							 errmsg("column \"%s\" has a collation conflict",
 									attributeName),
 							 errdetail("\"%s\" versus \"%s\"",
-									   get_collation_name(defcollid),
+									   get_collation_name(inhcollid),
 									   get_collation_name(newcollid))));
 
 				/*
 				 * Identity is never inherited.  The new column can have an
 				 * identity definition, so we always just take that one.
 				 */
-				def->identity = newdef->identity;
+				inhdef->identity = newdef->identity;
 
 				/*
 				 * Copy storage parameter
 				 */
-				if (def->storage == 0)
-					def->storage = newdef->storage;
-				else if (newdef->storage != 0 && def->storage != newdef->storage)
+				if (inhdef->storage == 0)
+					inhdef->storage = newdef->storage;
+				else if (newdef->storage != 0 && inhdef->storage != newdef->storage)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("column \"%s\" has a storage parameter conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   storage_name(def->storage),
+									   storage_name(inhdef->storage),
 									   storage_name(newdef->storage))));
 
 				/*
 				 * Copy compression parameter
 				 */
-				if (def->compression == NULL)
-					def->compression = newdef->compression;
+				if (inhdef->compression == NULL)
+					inhdef->compression = newdef->compression;
 				else if (newdef->compression != NULL)
 				{
-					if (strcmp(def->compression, newdef->compression) != 0)
+					if (strcmp(inhdef->compression, newdef->compression) != 0)
 						ereport(ERROR,
 								(errcode(ERRCODE_DATATYPE_MISMATCH),
 								 errmsg("column \"%s\" has a compression method conflict",
 										attributeName),
-								 errdetail("%s versus %s", def->compression, newdef->compression)));
+								 errdetail("%s versus %s", inhdef->compression, newdef->compression)));
 				}
 
 				/*
 				 * Merge of not-null constraints = OR 'em together
 				 */
-				def->is_not_null |= newdef->is_not_null;
+				inhdef->is_not_null |= newdef->is_not_null;
 
 				/*
 				 * Check for conflicts related to generated columns.
@@ -3164,18 +3171,18 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 * it results in being able to override the generation
 				 * expression via UPDATEs through the parent.)
 				 */
-				if (def->generated)
+				if (inhdef->generated)
 				{
 					if (newdef->raw_default && !newdef->generated)
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
 								 errmsg("column \"%s\" inherits from generated column but specifies default",
-										def->colname)));
+										inhdef->colname)));
 					if (newdef->identity)
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
 								 errmsg("column \"%s\" inherits from generated column but specifies identity",
-										def->colname)));
+										inhdef->colname)));
 				}
 				else
 				{
@@ -3183,7 +3190,7 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
 								 errmsg("child column \"%s\" specifies generation expression",
-										def->colname),
+										inhdef->colname),
 								 errhint("A child table column cannot be generated unless its parent column is.")));
 				}
 
@@ -3192,12 +3199,12 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 */
 				if (newdef->raw_default != NULL)
 				{
-					def->raw_default = newdef->raw_default;
-					def->cooked_default = newdef->cooked_default;
+					inhdef->raw_default = newdef->raw_default;
+					inhdef->cooked_default = newdef->cooked_default;
 				}
 
 				/* Mark the column as locally defined */
-				def->is_local = true;
+				inhdef->is_local = true;
 			}
 			else
 			{
-- 
2.41.0

v2-0001-Clean-up-MergeAttributesIntoExisting.patchtext/plain; charset=UTF-8; name=v2-0001-Clean-up-MergeAttributesIntoExisting.patchDownload
From 4d01d244305c63a5a602558267d0021910e200b6 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:34 +0200
Subject: [PATCH v2 01/10] Clean up MergeAttributesIntoExisting()

Make variable naming clearer and more consistent.  Move some variables
to smaller scope.  Remove some unnecessary intermediate variables.
Try to save some vertical space.

Apply analogous changes to nearby MergeConstraintsIntoExisting() and
RemoveInheritance() for consistency.
---
 src/backend/commands/tablecmds.c | 123 ++++++++++++-------------------
 1 file changed, 48 insertions(+), 75 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d097da3c78..e7846178b3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -15691,92 +15691,76 @@ static void
 MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 {
 	Relation	attrrel;
-	AttrNumber	parent_attno;
-	int			parent_natts;
-	TupleDesc	tupleDesc;
-	HeapTuple	tuple;
-	bool		child_is_partition = false;
+	TupleDesc	parent_desc;
 
 	attrrel = table_open(AttributeRelationId, RowExclusiveLock);
+	parent_desc = RelationGetDescr(parent_rel);
 
-	tupleDesc = RelationGetDescr(parent_rel);
-	parent_natts = tupleDesc->natts;
-
-	/* If parent_rel is a partitioned table, child_rel must be a partition */
-	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-		child_is_partition = true;
-
-	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
+	for (AttrNumber parent_attno = 1; parent_attno <= parent_desc->natts; parent_attno++)
 	{
-		Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
-													parent_attno - 1);
-		char	   *attributeName = NameStr(attribute->attname);
+		Form_pg_attribute parent_att = TupleDescAttr(parent_desc, parent_attno - 1);
+		char	   *parent_attname = NameStr(parent_att->attname);
+		HeapTuple	tuple;
 
 		/* Ignore dropped columns in the parent. */
-		if (attribute->attisdropped)
+		if (parent_att->attisdropped)
 			continue;
 
 		/* Find same column in child (matching on column name). */
-		tuple = SearchSysCacheCopyAttName(RelationGetRelid(child_rel),
-										  attributeName);
+		tuple = SearchSysCacheCopyAttName(RelationGetRelid(child_rel), parent_attname);
 		if (HeapTupleIsValid(tuple))
 		{
-			/* Check they are same type, typmod, and collation */
-			Form_pg_attribute childatt = (Form_pg_attribute) GETSTRUCT(tuple);
+			Form_pg_attribute child_att = (Form_pg_attribute) GETSTRUCT(tuple);
 
-			if (attribute->atttypid != childatt->atttypid ||
-				attribute->atttypmod != childatt->atttypmod)
+			if (parent_att->atttypid != child_att->atttypid ||
+				parent_att->atttypmod != child_att->atttypmod)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("child table \"%s\" has different type for column \"%s\"",
-								RelationGetRelationName(child_rel),
-								attributeName)));
+								RelationGetRelationName(child_rel), parent_attname)));
 
-			if (attribute->attcollation != childatt->attcollation)
+			if (parent_att->attcollation != child_att->attcollation)
 				ereport(ERROR,
 						(errcode(ERRCODE_COLLATION_MISMATCH),
 						 errmsg("child table \"%s\" has different collation for column \"%s\"",
-								RelationGetRelationName(child_rel),
-								attributeName)));
+								RelationGetRelationName(child_rel), parent_attname)));
 
 			/*
 			 * Check child doesn't discard NOT NULL property.  (Other
 			 * constraints are checked elsewhere.)  However, if the constraint
 			 * is NO INHERIT in the parent, this is allowed.
 			 */
-			if (attribute->attnotnull && !childatt->attnotnull)
+			if (parent_att->attnotnull && !child_att->attnotnull)
 			{
 				HeapTuple	contup;
 
 				contup = findNotNullConstraintAttnum(RelationGetRelid(parent_rel),
-													 attribute->attnum);
+													 parent_att->attnum);
 				if (!((Form_pg_constraint) GETSTRUCT(contup))->connoinherit)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("column \"%s\" in child table must be marked NOT NULL",
-									attributeName)));
+									parent_attname)));
 			}
 
 			/*
 			 * Child column must be generated if and only if parent column is.
 			 */
-			if (attribute->attgenerated && !childatt->attgenerated)
+			if (parent_att->attgenerated && !child_att->attgenerated)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("column \"%s\" in child table must be a generated column",
-								attributeName)));
-			if (childatt->attgenerated && !attribute->attgenerated)
+						 errmsg("column \"%s\" in child table must be a generated column", parent_attname)));
+			if (child_att->attgenerated && !parent_att->attgenerated)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("column \"%s\" in child table must not be a generated column",
-								attributeName)));
+						 errmsg("column \"%s\" in child table must not be a generated column", parent_attname)));
 
 			/*
 			 * OK, bump the child column's inheritance count.  (If we fail
 			 * later on, this change will just roll back.)
 			 */
-			childatt->attinhcount++;
-			if (childatt->attinhcount < 0)
+			child_att->attinhcount++;
+			if (child_att->attinhcount < 0)
 				ereport(ERROR,
 						errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 						errmsg("too many inheritance parents"));
@@ -15786,10 +15770,10 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 			 * is same in all partitions. (Note: there are only inherited
 			 * attributes in partitions)
 			 */
-			if (child_is_partition)
+			if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			{
-				Assert(childatt->attinhcount == 1);
-				childatt->attislocal = false;
+				Assert(child_att->attinhcount == 1);
+				child_att->attislocal = false;
 			}
 
 			CatalogTupleUpdate(attrrel, &tuple->t_self, tuple);
@@ -15799,8 +15783,7 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 		{
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing column \"%s\"",
-							attributeName)));
+					 errmsg("child table is missing column \"%s\"", parent_attname)));
 		}
 	}
 
@@ -15827,27 +15810,20 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 static void
 MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 {
-	Relation	catalog_relation;
-	TupleDesc	tuple_desc;
+	Relation	constraintrel;
 	SysScanDesc parent_scan;
 	ScanKeyData parent_key;
 	HeapTuple	parent_tuple;
 	Oid			parent_relid = RelationGetRelid(parent_rel);
-	bool		child_is_partition = false;
-
-	catalog_relation = table_open(ConstraintRelationId, RowExclusiveLock);
-	tuple_desc = RelationGetDescr(catalog_relation);
 
-	/* If parent_rel is a partitioned table, child_rel must be a partition */
-	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-		child_is_partition = true;
+	constraintrel = table_open(ConstraintRelationId, RowExclusiveLock);
 
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
 				Anum_pg_constraint_conrelid,
 				BTEqualStrategyNumber, F_OIDEQ,
 				ObjectIdGetDatum(parent_relid));
-	parent_scan = systable_beginscan(catalog_relation, ConstraintRelidTypidNameIndexId,
+	parent_scan = systable_beginscan(constraintrel, ConstraintRelidTypidNameIndexId,
 									 true, NULL, 1, &parent_key);
 
 	while (HeapTupleIsValid(parent_tuple = systable_getnext(parent_scan)))
@@ -15871,7 +15847,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 					Anum_pg_constraint_conrelid,
 					BTEqualStrategyNumber, F_OIDEQ,
 					ObjectIdGetDatum(RelationGetRelid(child_rel)));
-		child_scan = systable_beginscan(catalog_relation, ConstraintRelidTypidNameIndexId,
+		child_scan = systable_beginscan(constraintrel, ConstraintRelidTypidNameIndexId,
 										true, NULL, 1, &child_key);
 
 		while (HeapTupleIsValid(child_tuple = systable_getnext(child_scan)))
@@ -15886,10 +15862,12 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			 * CHECK constraint are matched by name, NOT NULL ones by
 			 * attribute number
 			 */
-			if (child_con->contype == CONSTRAINT_CHECK &&
-				strcmp(NameStr(parent_con->conname),
-					   NameStr(child_con->conname)) != 0)
-				continue;
+			if (child_con->contype == CONSTRAINT_CHECK)
+			{
+				if (strcmp(NameStr(parent_con->conname),
+						   NameStr(child_con->conname)) != 0)
+					continue;
+			}
 			else if (child_con->contype == CONSTRAINT_NOTNULL)
 			{
 				AttrNumber	parent_attno = extractNotNullColumn(parent_tuple);
@@ -15902,12 +15880,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			}
 
 			if (child_con->contype == CONSTRAINT_CHECK &&
-				!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
+				!constraints_equivalent(parent_tuple, child_tuple, RelationGetDescr(constraintrel)))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
-								RelationGetRelationName(child_rel),
-								NameStr(parent_con->conname))));
+								RelationGetRelationName(child_rel), NameStr(parent_con->conname))));
 
 			/*
 			 * If the CHECK child constraint is "no inherit" then cannot
@@ -15925,8 +15902,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
-								NameStr(child_con->conname),
-								RelationGetRelationName(child_rel))));
+								NameStr(child_con->conname), RelationGetRelationName(child_rel))));
 
 			/*
 			 * If the child constraint is "not valid" then cannot merge with a
@@ -15936,8 +15912,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("constraint \"%s\" conflicts with NOT VALID constraint on child table \"%s\"",
-								NameStr(child_con->conname),
-								RelationGetRelationName(child_rel))));
+								NameStr(child_con->conname), RelationGetRelationName(child_rel))));
 
 			/*
 			 * OK, bump the child constraint's inheritance count.  (If we fail
@@ -15959,13 +15934,13 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			 * inherited only once since it cannot have multiple parents and
 			 * it is never considered local.
 			 */
-			if (child_is_partition)
+			if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			{
 				Assert(child_con->coninhcount == 1);
 				child_con->conislocal = false;
 			}
 
-			CatalogTupleUpdate(catalog_relation, &child_copy->t_self, child_copy);
+			CatalogTupleUpdate(constraintrel, &child_copy->t_self, child_copy);
 			heap_freetuple(child_copy);
 
 			found = true;
@@ -15982,7 +15957,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 	}
 
 	systable_endscan(parent_scan);
-	table_close(catalog_relation, RowExclusiveLock);
+	table_close(constraintrel, RowExclusiveLock);
 }
 
 /*
@@ -16138,11 +16113,9 @@ RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached)
 	List	   *connames;
 	List	   *nncolumns;
 	bool		found;
-	bool		child_is_partition = false;
+	bool		is_partitioning;
 
-	/* If parent_rel is a partitioned table, child_rel must be a partition */
-	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-		child_is_partition = true;
+	is_partitioning = (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 
 	found = DeleteInheritsTuple(RelationGetRelid(child_rel),
 								RelationGetRelid(parent_rel),
@@ -16150,7 +16123,7 @@ RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached)
 								RelationGetRelationName(child_rel));
 	if (!found)
 	{
-		if (child_is_partition)
+		if (is_partitioning)
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_TABLE),
 					 errmsg("relation \"%s\" is not a partition of relation \"%s\"",
@@ -16304,7 +16277,7 @@ RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached)
 	drop_parent_dependency(RelationGetRelid(child_rel),
 						   RelationRelationId,
 						   RelationGetRelid(parent_rel),
-						   child_dependency_type(child_is_partition));
+						   child_dependency_type(is_partitioning));
 
 	/*
 	 * Post alter hook of this inherits. Since object_access_hook doesn't take

base-commit: 6844d3275ac6b3c35d824f49362d3fe59b30f26b
-- 
2.41.0

v2-0002-Clean-up-MergeCheckConstraint.patchtext/plain; charset=UTF-8; name=v2-0002-Clean-up-MergeCheckConstraint.patchDownload
From cbf88a4e5ee9e5d0190a69c5ac81737501185254 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:34 +0200
Subject: [PATCH v2 02/10] Clean up MergeCheckConstraint()

If the constraint is not already in the list, add it ourselves,
instead of making the caller do it.  This makes the interface more
consistent with other "merge" functions in this file.
---
 src/backend/commands/tablecmds.c | 47 ++++++++++++++------------------
 1 file changed, 21 insertions(+), 26 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e7846178b3..c37161aefa 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -353,7 +353,7 @@ static void RangeVarCallbackForTruncate(const RangeVar *relation,
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
 							 bool is_partition, List **supconstr,
 							 List **supnotnulls);
-static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
+static List *MergeCheckConstraint(List *constraints, const char *name, Node *expr);
 static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
 static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
 static void StoreCatalogInheritance(Oid relationId, List *supers,
@@ -2914,24 +2914,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 									   name,
 									   RelationGetRelationName(relation))));
 
-				/* check for duplicate */
-				if (!MergeCheckConstraint(constraints, name, expr))
-				{
-					/* nope, this is a new one */
-					CookedConstraint *cooked;
-
-					cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
-					cooked->contype = CONSTR_CHECK;
-					cooked->conoid = InvalidOid;	/* until created */
-					cooked->name = pstrdup(name);
-					cooked->attnum = 0; /* not used for constraints */
-					cooked->expr = expr;
-					cooked->skip_validation = false;
-					cooked->is_local = false;
-					cooked->inhcount = 1;
-					cooked->is_no_inherit = false;
-					constraints = lappend(constraints, cooked);
-				}
+				constraints = MergeCheckConstraint(constraints, name, expr);
 			}
 		}
 
@@ -3278,13 +3261,16 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
  *
  * constraints is a list of CookedConstraint structs for previous constraints.
  *
- * Returns true if merged (constraint is a duplicate), or false if it's
- * got a so-far-unique name, or throws error if conflict.
+ * If the constraint is a duplicate, then the existing constraint's
+ * inheritance count is updated.  If the constraint doesn't match or conflict
+ * with an existing one, a new constraint is appended to the list.  If there
+ * is a conflict (same name but different expression), throw an error.
  */
-static bool
-MergeCheckConstraint(List *constraints, char *name, Node *expr)
+static List *
+MergeCheckConstraint(List *constraints, const char *name, Node *expr)
 {
 	ListCell   *lc;
+	CookedConstraint *newcon;
 
 	foreach(lc, constraints)
 	{
@@ -3298,13 +3284,13 @@ MergeCheckConstraint(List *constraints, char *name, Node *expr)
 
 		if (equal(expr, ccon->expr))
 		{
-			/* OK to merge */
+			/* OK to merge constraint with existing */
 			ccon->inhcount++;
 			if (ccon->inhcount < 0)
 				ereport(ERROR,
 						errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 						errmsg("too many inheritance parents"));
-			return true;
+			return constraints;
 		}
 
 		ereport(ERROR,
@@ -3313,7 +3299,16 @@ MergeCheckConstraint(List *constraints, char *name, Node *expr)
 						name)));
 	}
 
-	return false;
+	/*
+	 * Constraint couldn't be merged with an existing one and also didn't
+	 * conflict with an existing one, so add it as a new one to the list.
+	 */
+	newcon = palloc0_object(CookedConstraint);
+	newcon->contype = CONSTR_CHECK;
+	newcon->name = pstrdup(name);
+	newcon->expr = expr;
+	newcon->inhcount = 1;
+	return lappend(constraints, newcon);
 }
 
 
-- 
2.41.0

v2-0003-MergeAttributes-and-related-variable-renaming.patchtext/plain; charset=UTF-8; name=v2-0003-MergeAttributes-and-related-variable-renaming.patchDownload
From 1820aff805349ab2a780829944c8e64b9352efb3 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:34 +0200
Subject: [PATCH v2 03/10] MergeAttributes() and related variable renaming

Mainly, rename "schema" to "columns" and related changes.  The
previous naming has long been confusing.
---
 src/backend/access/common/tupdesc.c |  10 +--
 src/backend/commands/tablecmds.c    | 109 +++++++++++++---------------
 src/include/access/tupdesc.h        |   4 +-
 3 files changed, 59 insertions(+), 64 deletions(-)

diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 7c5c390503..253d6c86f8 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -782,12 +782,12 @@ TupleDescInitEntryCollation(TupleDesc desc,
 /*
  * BuildDescForRelation
  *
- * Given a relation schema (list of ColumnDef nodes), build a TupleDesc.
+ * Given a list of ColumnDef nodes, build a TupleDesc.
  *
  * Note: tdtypeid will need to be filled in later on.
  */
 TupleDesc
-BuildDescForRelation(List *schema)
+BuildDescForRelation(const List *columns)
 {
 	int			natts;
 	AttrNumber	attnum;
@@ -803,13 +803,13 @@ BuildDescForRelation(List *schema)
 	/*
 	 * allocate a new tuple descriptor
 	 */
-	natts = list_length(schema);
+	natts = list_length(columns);
 	desc = CreateTemplateTupleDesc(natts);
 	has_not_null = false;
 
 	attnum = 0;
 
-	foreach(l, schema)
+	foreach(l, columns)
 	{
 		ColumnDef  *entry = lfirst(l);
 		AclResult	aclresult;
@@ -891,7 +891,7 @@ BuildDescForRelation(List *schema)
  * with functions returning RECORD.
  */
 TupleDesc
-BuildDescFromLists(List *names, List *types, List *typmods, List *collations)
+BuildDescFromLists(const List *names, const List *types, const List *typmods, const List *collations)
 {
 	int			natts;
 	AttrNumber	attnum;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c37161aefa..fb6d1b90dc 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -350,7 +350,7 @@ static void truncate_check_perms(Oid relid, Form_pg_class reltuple);
 static void truncate_check_activity(Relation rel);
 static void RangeVarCallbackForTruncate(const RangeVar *relation,
 										Oid relId, Oid oldRelId, void *arg);
-static List *MergeAttributes(List *schema, List *supers, char relpersistence,
+static List *MergeAttributes(List *columns, const List *supers, char relpersistence,
 							 bool is_partition, List **supconstr,
 							 List **supnotnulls);
 static List *MergeCheckConstraint(List *constraints, const char *name, Node *expr);
@@ -361,7 +361,7 @@ static void StoreCatalogInheritance(Oid relationId, List *supers,
 static void StoreCatalogInheritance1(Oid relationId, Oid parentOid,
 									 int32 seqNumber, Relation inhRelation,
 									 bool child_is_partition);
-static int	findAttrByName(const char *attributeName, List *schema);
+static int	findAttrByName(const char *attributeName, const List *columns);
 static void AlterIndexNamespaces(Relation classRel, Relation rel,
 								 Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved);
 static void AlterSeqNamespaces(Relation classRel, Relation rel,
@@ -2308,7 +2308,7 @@ storage_name(char c)
  *		Returns new schema given initial schema and superclasses.
  *
  * Input arguments:
- * 'schema' is the column/attribute definition for the table. (It's a list
+ * 'columns' is the column/attribute definition for the table. (It's a list
  *		of ColumnDef's.) It is destructively changed.
  * 'supers' is a list of OIDs of parent relations, already locked by caller.
  * 'relpersistence' is the persistence type of the table.
@@ -2370,17 +2370,17 @@ storage_name(char c)
  *----------
  */
 static List *
-MergeAttributes(List *schema, List *supers, char relpersistence,
+MergeAttributes(List *columns, const List *supers, char relpersistence,
 				bool is_partition, List **supconstr, List **supnotnulls)
 {
-	List	   *inhSchema = NIL;
+	List	   *inh_columns = NIL;
 	List	   *constraints = NIL;
 	List	   *nnconstraints = NIL;
 	bool		have_bogus_defaults = false;
 	int			child_attno;
 	static Node bogus_marker = {0}; /* marks conflicting defaults */
-	List	   *saved_schema = NIL;
-	ListCell   *entry;
+	List	   *saved_columns = NIL;
+	ListCell   *lc;
 
 	/*
 	 * Check for and reject tables with too many columns. We perform this
@@ -2393,7 +2393,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	 * Note that we also need to check that we do not exceed this figure after
 	 * including columns from inherited relations.
 	 */
-	if (list_length(schema) > MaxHeapAttributeNumber)
+	if (list_length(columns) > MaxHeapAttributeNumber)
 		ereport(ERROR,
 				(errcode(ERRCODE_TOO_MANY_COLUMNS),
 				 errmsg("tables can have at most %d columns",
@@ -2407,15 +2407,15 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	 * sense to assume such conflicts are errors.
 	 *
 	 * We don't use foreach() here because we have two nested loops over the
-	 * schema list, with possible element deletions in the inner one.  If we
+	 * columns list, with possible element deletions in the inner one.  If we
 	 * used foreach_delete_current() it could only fix up the state of one of
 	 * the loops, so it seems cleaner to use looping over list indexes for
 	 * both loops.  Note that any deletion will happen beyond where the outer
 	 * loop is, so its index never needs adjustment.
 	 */
-	for (int coldefpos = 0; coldefpos < list_length(schema); coldefpos++)
+	for (int coldefpos = 0; coldefpos < list_length(columns); coldefpos++)
 	{
-		ColumnDef  *coldef = list_nth_node(ColumnDef, schema, coldefpos);
+		ColumnDef  *coldef = list_nth_node(ColumnDef, columns, coldefpos);
 
 		if (!is_partition && coldef->typeName == NULL)
 		{
@@ -2432,9 +2432,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		}
 
 		/* restpos scans all entries beyond coldef; incr is in loop body */
-		for (int restpos = coldefpos + 1; restpos < list_length(schema);)
+		for (int restpos = coldefpos + 1; restpos < list_length(columns);)
 		{
-			ColumnDef  *restdef = list_nth_node(ColumnDef, schema, restpos);
+			ColumnDef  *restdef = list_nth_node(ColumnDef, columns, restpos);
 
 			if (strcmp(coldef->colname, restdef->colname) == 0)
 			{
@@ -2448,7 +2448,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 					coldef->cooked_default = restdef->cooked_default;
 					coldef->constraints = restdef->constraints;
 					coldef->is_from_type = false;
-					schema = list_delete_nth_cell(schema, restpos);
+					columns = list_delete_nth_cell(columns, restpos);
 				}
 				else
 					ereport(ERROR,
@@ -2468,18 +2468,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	 */
 	if (is_partition)
 	{
-		saved_schema = schema;
-		schema = NIL;
+		saved_columns = columns;
+		columns = NIL;
 	}
 
 	/*
 	 * Scan the parents left-to-right, and merge their attributes to form a
-	 * list of inherited attributes (inhSchema).
+	 * list of inherited columns (inh_columns).
 	 */
 	child_attno = 0;
-	foreach(entry, supers)
+	foreach(lc, supers)
 	{
-		Oid			parent = lfirst_oid(entry);
+		Oid			parent = lfirst_oid(lc);
 		Relation	relation;
 		TupleDesc	tupleDesc;
 		TupleConstr *constr;
@@ -2487,7 +2487,6 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		List	   *inherited_defaults;
 		List	   *cols_with_defaults;
 		List	   *nnconstrs;
-		AttrNumber	parent_attno;
 		ListCell   *lc1;
 		ListCell   *lc2;
 		Bitmapset  *pkattrs;
@@ -2508,8 +2507,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 * We do not allow partitioned tables and partitions to participate in
 		 * regular inheritance.
 		 */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
-			!is_partition)
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from partitioned table \"%s\"",
@@ -2594,7 +2592,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			nncols = bms_add_member(nncols,
 									((CookedConstraint *) lfirst(lc1))->attnum);
 
-		for (parent_attno = 1; parent_attno <= tupleDesc->natts;
+		for (AttrNumber parent_attno = 1; parent_attno <= tupleDesc->natts;
 			 parent_attno++)
 		{
 			Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
@@ -2612,7 +2610,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			/*
 			 * Does it conflict with some previously inherited column?
 			 */
-			exist_attno = findAttrByName(attributeName, inhSchema);
+			exist_attno = findAttrByName(attributeName, inh_columns);
 			if (exist_attno > 0)
 			{
 				Oid			defTypeId;
@@ -2625,7 +2623,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				ereport(NOTICE,
 						(errmsg("merging multiple inherited definitions of column \"%s\"",
 								attributeName)));
-				def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
+				def = (ColumnDef *) list_nth(inh_columns, exist_attno - 1);
 
 				/*
 				 * Must have the same type and typmod
@@ -2762,7 +2760,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				if (CompressionMethodIsValid(attribute->attcompression))
 					def->compression =
 						pstrdup(GetCompressionMethodName(attribute->attcompression));
-				inhSchema = lappend(inhSchema, def);
+				inh_columns = lappend(inh_columns, def);
 				newattmap->attnums[parent_attno - 1] = ++child_attno;
 
 				/*
@@ -2863,7 +2861,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			 * If we already had a default from some prior parent, check to
 			 * see if they are the same.  If so, no problem; if not, mark the
 			 * column as having a bogus default.  Below, we will complain if
-			 * the bogus default isn't overridden by the child schema.
+			 * the bogus default isn't overridden by the child columns.
 			 */
 			Assert(def->raw_default == NULL);
 			if (def->cooked_default == NULL)
@@ -2883,9 +2881,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		if (constr && constr->num_check > 0)
 		{
 			ConstrCheck *check = constr->check;
-			int			i;
 
-			for (i = 0; i < constr->num_check; i++)
+			for (int i = 0; i < constr->num_check; i++)
 			{
 				char	   *name = check[i].ccname;
 				Node	   *expr;
@@ -2946,27 +2943,27 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	}
 
 	/*
-	 * If we had no inherited attributes, the result schema is just the
+	 * If we had no inherited attributes, the result columns are just the
 	 * explicitly declared columns.  Otherwise, we need to merge the declared
-	 * columns into the inherited schema list.  Although, we never have any
+	 * columns into the inherited column list.  Although, we never have any
 	 * explicitly declared columns if the table is a partition.
 	 */
-	if (inhSchema != NIL)
+	if (inh_columns != NIL)
 	{
-		int			schema_attno = 0;
+		int			newcol_attno = 0;
 
-		foreach(entry, schema)
+		foreach(lc, columns)
 		{
-			ColumnDef  *newdef = lfirst(entry);
+			ColumnDef  *newdef = lfirst(lc);
 			char	   *attributeName = newdef->colname;
 			int			exist_attno;
 
-			schema_attno++;
+			newcol_attno++;
 
 			/*
 			 * Does it conflict with some previously inherited column?
 			 */
-			exist_attno = findAttrByName(attributeName, inhSchema);
+			exist_attno = findAttrByName(attributeName, inh_columns);
 			if (exist_attno > 0)
 			{
 				ColumnDef  *def;
@@ -2986,7 +2983,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				/*
 				 * Yes, try to merge the two column definitions.
 				 */
-				if (exist_attno == schema_attno)
+				if (exist_attno == newcol_attno)
 					ereport(NOTICE,
 							(errmsg("merging column \"%s\" with inherited definition",
 									attributeName)));
@@ -2994,7 +2991,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 					ereport(NOTICE,
 							(errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
 							 errdetail("User-specified column moved to the position of the inherited column.")));
-				def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
+				def = (ColumnDef *) list_nth(inh_columns, exist_attno - 1);
 
 				/*
 				 * Must have the same type and typmod
@@ -3119,19 +3116,19 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			else
 			{
 				/*
-				 * No, attach new column to result schema
+				 * No, attach new column to result columns
 				 */
-				inhSchema = lappend(inhSchema, newdef);
+				inh_columns = lappend(inh_columns, newdef);
 			}
 		}
 
-		schema = inhSchema;
+		columns = inh_columns;
 
 		/*
 		 * Check that we haven't exceeded the legal # of columns after merging
 		 * in inherited columns.
 		 */
-		if (list_length(schema) > MaxHeapAttributeNumber)
+		if (list_length(columns) > MaxHeapAttributeNumber)
 			ereport(ERROR,
 					(errcode(ERRCODE_TOO_MANY_COLUMNS),
 					 errmsg("tables can have at most %d columns",
@@ -3145,13 +3142,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	 */
 	if (is_partition)
 	{
-		foreach(entry, saved_schema)
+		foreach(lc, saved_columns)
 		{
-			ColumnDef  *restdef = lfirst(entry);
+			ColumnDef  *restdef = lfirst(lc);
 			bool		found = false;
 			ListCell   *l;
 
-			foreach(l, schema)
+			foreach(l, columns)
 			{
 				ColumnDef  *coldef = lfirst(l);
 
@@ -3223,9 +3220,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	 */
 	if (have_bogus_defaults)
 	{
-		foreach(entry, schema)
+		foreach(lc, columns)
 		{
-			ColumnDef  *def = lfirst(entry);
+			ColumnDef  *def = lfirst(lc);
 
 			if (def->cooked_default == &bogus_marker)
 			{
@@ -3248,7 +3245,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	*supconstr = constraints;
 	*supnotnulls = nnconstraints;
 
-	return schema;
+	return columns;
 }
 
 
@@ -3402,22 +3399,20 @@ StoreCatalogInheritance1(Oid relationId, Oid parentOid,
 }
 
 /*
- * Look for an existing schema entry with the given name.
+ * Look for an existing column entry with the given name.
  *
- * Returns the index (starting with 1) if attribute already exists in schema,
+ * Returns the index (starting with 1) if attribute already exists in columns,
  * 0 if it doesn't.
  */
 static int
-findAttrByName(const char *attributeName, List *schema)
+findAttrByName(const char *attributeName, const List *columns)
 {
-	ListCell   *s;
+	ListCell   *lc;
 	int			i = 1;
 
-	foreach(s, schema)
+	foreach(lc, columns)
 	{
-		ColumnDef  *def = lfirst(s);
-
-		if (strcmp(attributeName, def->colname) == 0)
+		if (strcmp(attributeName, lfirst_node(ColumnDef, lc)->colname) == 0)
 			return i;
 
 		i++;
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index b4286cf922..f6cc28a661 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -147,8 +147,8 @@ extern void TupleDescInitEntryCollation(TupleDesc desc,
 										AttrNumber attributeNumber,
 										Oid collationid);
 
-extern TupleDesc BuildDescForRelation(List *schema);
+extern TupleDesc BuildDescForRelation(const List *columns);
 
-extern TupleDesc BuildDescFromLists(List *names, List *types, List *typmods, List *collations);
+extern TupleDesc BuildDescFromLists(const List *names, const List *types, const List *typmods, const List *collations);
 
 #endif							/* TUPDESC_H */
-- 
2.41.0

v2-0004-Add-TupleDescGetDefault.patchtext/plain; charset=UTF-8; name=v2-0004-Add-TupleDescGetDefault.patchDownload
From 471fda80c41fae835ecbe63ae8505526a37487a9 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:35 +0200
Subject: [PATCH v2 04/10] Add TupleDescGetDefault()

This unifies some repetitive code.

Note: I didn't push the "not found" error message into the new
function, even though all existing callers would be able to make use
of it.  Using the existing error handling as-is would probably require
exposing the Relation type via tupdesc.h, which doesn't seem
desirable.
---
 src/backend/access/common/tupdesc.c  | 25 +++++++++++++++++++++++++
 src/backend/commands/tablecmds.c     | 17 ++---------------
 src/backend/parser/parse_utilcmd.c   | 13 ++-----------
 src/backend/rewrite/rewriteHandler.c | 16 +---------------
 src/include/access/tupdesc.h         |  2 ++
 5 files changed, 32 insertions(+), 41 deletions(-)

diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 253d6c86f8..ce2c7bce85 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -927,3 +927,28 @@ BuildDescFromLists(const List *names, const List *types, const List *typmods, co
 
 	return desc;
 }
+
+/*
+ * Get default expression (or NULL if none) for the given attribute number.
+ */
+Node *
+TupleDescGetDefault(TupleDesc tupdesc, AttrNumber attnum)
+{
+	Node	   *result = NULL;
+
+	if (tupdesc->constr)
+	{
+		AttrDefault *attrdef = tupdesc->constr->defval;
+
+		for (int i = 0; i < tupdesc->constr->num_defval; i++)
+		{
+			if (attrdef[i].adnum == attnum)
+			{
+				result = stringToNode(attrdef[i].adbin);
+				break;
+			}
+		}
+	}
+
+	return result;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fb6d1b90dc..0a7101dadc 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2796,22 +2796,9 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 			 */
 			if (attribute->atthasdef)
 			{
-				Node	   *this_default = NULL;
+				Node	   *this_default;
 
-				/* Find default in constraint structure */
-				if (constr != NULL)
-				{
-					AttrDefault *attrdef = constr->defval;
-
-					for (int i = 0; i < constr->num_defval; i++)
-					{
-						if (attrdef[i].adnum == parent_attno)
-						{
-							this_default = stringToNode(attrdef[i].adbin);
-							break;
-						}
-					}
-				}
+				this_default = TupleDescGetDefault(tupleDesc, parent_attno);
 				if (this_default == NULL)
 					elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
 						 parent_attno, RelationGetRelationName(relation));
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 55c315f0e2..cf0d432ab1 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1358,20 +1358,11 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 				 (table_like_clause->options & CREATE_TABLE_LIKE_GENERATED) :
 				 (table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS)))
 			{
-				Node	   *this_default = NULL;
-				AttrDefault *attrdef = constr->defval;
+				Node	   *this_default;
 				AlterTableCmd *atsubcmd;
 				bool		found_whole_row;
 
-				/* Find default in constraint structure */
-				for (int i = 0; i < constr->num_defval; i++)
-				{
-					if (attrdef[i].adnum == parent_attno)
-					{
-						this_default = stringToNode(attrdef[i].adbin);
-						break;
-					}
-				}
+				this_default = TupleDescGetDefault(tupleDesc, parent_attno);
 				if (this_default == NULL)
 					elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
 						 parent_attno, RelationGetRelationName(relation));
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b486ab559a..41a362310a 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1246,21 +1246,7 @@ build_column_default(Relation rel, int attrno)
 	 */
 	if (att_tup->atthasdef)
 	{
-		if (rd_att->constr && rd_att->constr->num_defval > 0)
-		{
-			AttrDefault *defval = rd_att->constr->defval;
-			int			ndef = rd_att->constr->num_defval;
-
-			while (--ndef >= 0)
-			{
-				if (attrno == defval[ndef].adnum)
-				{
-					/* Found it, convert string representation to node tree. */
-					expr = stringToNode(defval[ndef].adbin);
-					break;
-				}
-			}
-		}
+		expr = TupleDescGetDefault(rd_att, attrno);
 		if (expr == NULL)
 			elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
 				 attrno, RelationGetRelationName(rel));
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index f6cc28a661..ffd2874ee3 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -151,4 +151,6 @@ extern TupleDesc BuildDescForRelation(const List *columns);
 
 extern TupleDesc BuildDescFromLists(const List *names, const List *types, const List *typmods, const List *collations);
 
+extern Node *TupleDescGetDefault(TupleDesc tupdesc, AttrNumber attnum);
+
 #endif							/* TUPDESC_H */
-- 
2.41.0

v2-0005-Improve-some-catalog-documentation.patchtext/plain; charset=UTF-8; name=v2-0005-Improve-some-catalog-documentation.patchDownload
From 2c1c802be10ac574abf33d6293c8db7309d6b157 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:35 +0200
Subject: [PATCH v2 05/10] Improve some catalog documentation

Point out that typstorage and attstorage are never '\0', even for
fixed-length types.  This is different from attcompression.  For this
reason, some of the handling of these columns in tablecmds.c etc. is
different.  (catalogs.sgml already contained this information in an
indirect way.)
---
 src/include/catalog/pg_attribute.h | 10 +++++-----
 src/include/catalog/pg_type.h      |  3 +++
 2 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index f00df488ce..9b7442493a 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -108,11 +108,11 @@ CATALOG(pg_attribute,1249,AttributeRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(75,
 	 */
 	char		attalign;
 
-	/*----------
-	 * attstorage tells for VARLENA attributes, what the heap access
-	 * methods can do to it if a given tuple doesn't fit into a page.
-	 * Possible values are as for pg_type.typstorage (see TYPSTORAGE macros).
-	 *----------
+	/*
+	 * attstorage tells for VARLENA attributes, what the heap access methods
+	 * can do to it if a given tuple doesn't fit into a page.  Possible values
+	 * are as for pg_type.typstorage (see TYPSTORAGE macros).  This is never
+	 * '\0', even for fixed-length types.
 	 */
 	char		attstorage;
 
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 519e570c8c..e0a86354ff 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -187,6 +187,9 @@ CATALOG(pg_type,1247,TypeRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(71,TypeRelati
 	 *
 	 * Note that 'm' fields can also be moved out to secondary storage,
 	 * but only as a last resort ('e' and 'x' fields are moved first).
+	 *
+	 * For types that are not variable-length (that is, typlen != -1), this
+	 * must be set to 'p'.
 	 * ----------------
 	 */
 	char		typstorage BKI_DEFAULT(p) BKI_ARRAY_DEFAULT(x);
-- 
2.41.0

v2-0006-Push-attidentity-and-attgenerated-handling-into-B.patchtext/plain; charset=UTF-8; name=v2-0006-Push-attidentity-and-attgenerated-handling-into-B.patchDownload
From 622b51c51f5f0d52abea5fc08931811ca6b994df Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:35 +0200
Subject: [PATCH v2 06/10] Push attidentity and attgenerated handling into
 BuildDescForRelation()

Previously, this was handled by the callers separately, but it can be
trivially moved into BuildDescForRelation() so that it is handled in a
central place.
---
 src/backend/access/common/tupdesc.c | 2 ++
 src/backend/commands/tablecmds.c    | 2 --
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index ce2c7bce85..c2e7b14c31 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -856,6 +856,8 @@ BuildDescForRelation(const List *columns)
 		has_not_null |= entry->is_not_null;
 		att->attislocal = entry->is_local;
 		att->attinhcount = entry->inhcount;
+		att->attidentity = entry->identity;
+		att->attgenerated = entry->generated;
 	}
 
 	if (has_not_null)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0a7101dadc..c3a8cd58dd 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -942,8 +942,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			attr->atthasdef = true;
 		}
 
-		attr->attidentity = colDef->identity;
-		attr->attgenerated = colDef->generated;
 		attr->attcompression = GetAttributeCompression(attr->atttypid, colDef->compression);
 		if (colDef->storage_name)
 			attr->attstorage = GetAttributeStorage(attr->atttypid, colDef->storage_name);
-- 
2.41.0

v2-0007-Move-BuildDescForRelation-from-tupdesc.c-to-table.patchtext/plain; charset=UTF-8; name=v2-0007-Move-BuildDescForRelation-from-tupdesc.c-to-table.patchDownload
From 762ee0c108b267ae8a20fa7c8e2ece0f40e99b5f Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:35 +0200
Subject: [PATCH v2 07/10] Move BuildDescForRelation() from tupdesc.c to
 tablecmds.c

BuildDescForRelation() main job is to convert ColumnDef lists to
pg_attribute/tuple descriptor arrays, which is really mostly an
internal subroutine of DefineRelation() and some related functions,
which is more the remit of tablecmds.c and doesn't have much to do
with the basic tuple descriptor interfaces in tupdesc.c.  This is also
supported by observing the header includes we can remove in tupdesc.c.
By moving it over, we can also (in the future) make
BuildDescForRelation() use more internals of tablecmds.c that are not
sensible to be exposed in tupdesc.c.
---
 src/backend/access/common/tupdesc.c | 109 +---------------------------
 src/backend/commands/tablecmds.c    | 102 ++++++++++++++++++++++++++
 src/include/access/tupdesc.h        |   2 -
 src/include/commands/tablecmds.h    |   2 +
 4 files changed, 105 insertions(+), 110 deletions(-)

diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index c2e7b14c31..d119cfafb5 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -25,9 +25,6 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
 #include "common/hashfn.h"
-#include "miscadmin.h"
-#include "parser/parse_type.h"
-#include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/resowner_private.h"
@@ -778,109 +775,6 @@ TupleDescInitEntryCollation(TupleDesc desc,
 	TupleDescAttr(desc, attributeNumber - 1)->attcollation = collationid;
 }
 
-
-/*
- * BuildDescForRelation
- *
- * Given a list of ColumnDef nodes, build a TupleDesc.
- *
- * Note: tdtypeid will need to be filled in later on.
- */
-TupleDesc
-BuildDescForRelation(const List *columns)
-{
-	int			natts;
-	AttrNumber	attnum;
-	ListCell   *l;
-	TupleDesc	desc;
-	bool		has_not_null;
-	char	   *attname;
-	Oid			atttypid;
-	int32		atttypmod;
-	Oid			attcollation;
-	int			attdim;
-
-	/*
-	 * allocate a new tuple descriptor
-	 */
-	natts = list_length(columns);
-	desc = CreateTemplateTupleDesc(natts);
-	has_not_null = false;
-
-	attnum = 0;
-
-	foreach(l, columns)
-	{
-		ColumnDef  *entry = lfirst(l);
-		AclResult	aclresult;
-		Form_pg_attribute att;
-
-		/*
-		 * for each entry in the list, get the name and type information from
-		 * the list and have TupleDescInitEntry fill in the attribute
-		 * information we need.
-		 */
-		attnum++;
-
-		attname = entry->colname;
-		typenameTypeIdAndMod(NULL, entry->typeName, &atttypid, &atttypmod);
-
-		aclresult = object_aclcheck(TypeRelationId, atttypid, GetUserId(), ACL_USAGE);
-		if (aclresult != ACLCHECK_OK)
-			aclcheck_error_type(aclresult, atttypid);
-
-		attcollation = GetColumnDefCollation(NULL, entry, atttypid);
-		attdim = list_length(entry->typeName->arrayBounds);
-		if (attdim > PG_INT16_MAX)
-			ereport(ERROR,
-					errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-					errmsg("too many array dimensions"));
-
-		if (entry->typeName->setof)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-					 errmsg("column \"%s\" cannot be declared SETOF",
-							attname)));
-
-		TupleDescInitEntry(desc, attnum, attname,
-						   atttypid, atttypmod, attdim);
-		att = TupleDescAttr(desc, attnum - 1);
-
-		/* Override TupleDescInitEntry's settings as requested */
-		TupleDescInitEntryCollation(desc, attnum, attcollation);
-		if (entry->storage)
-			att->attstorage = entry->storage;
-
-		/* Fill in additional stuff not handled by TupleDescInitEntry */
-		att->attnotnull = entry->is_not_null;
-		has_not_null |= entry->is_not_null;
-		att->attislocal = entry->is_local;
-		att->attinhcount = entry->inhcount;
-		att->attidentity = entry->identity;
-		att->attgenerated = entry->generated;
-	}
-
-	if (has_not_null)
-	{
-		TupleConstr *constr = (TupleConstr *) palloc0(sizeof(TupleConstr));
-
-		constr->has_not_null = true;
-		constr->has_generated_stored = false;
-		constr->defval = NULL;
-		constr->missing = NULL;
-		constr->num_defval = 0;
-		constr->check = NULL;
-		constr->num_check = 0;
-		desc->constr = constr;
-	}
-	else
-	{
-		desc->constr = NULL;
-	}
-
-	return desc;
-}
-
 /*
  * BuildDescFromLists
  *
@@ -889,8 +783,7 @@ BuildDescForRelation(const List *columns)
  *
  * No constraints are generated.
  *
- * This is essentially a cut-down version of BuildDescForRelation for use
- * with functions returning RECORD.
+ * This is for use with functions returning RECORD.
  */
 TupleDesc
 BuildDescFromLists(const List *names, const List *types, const List *typmods, const List *collations)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c3a8cd58dd..0577e29230 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1278,6 +1278,108 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	return address;
 }
 
+/*
+ * BuildDescForRelation
+ *
+ * Given a list of ColumnDef nodes, build a TupleDesc.
+ *
+ * Note: tdtypeid will need to be filled in later on.
+ */
+TupleDesc
+BuildDescForRelation(const List *columns)
+{
+	int			natts;
+	AttrNumber	attnum;
+	ListCell   *l;
+	TupleDesc	desc;
+	bool		has_not_null;
+	char	   *attname;
+	Oid			atttypid;
+	int32		atttypmod;
+	Oid			attcollation;
+	int			attdim;
+
+	/*
+	 * allocate a new tuple descriptor
+	 */
+	natts = list_length(columns);
+	desc = CreateTemplateTupleDesc(natts);
+	has_not_null = false;
+
+	attnum = 0;
+
+	foreach(l, columns)
+	{
+		ColumnDef  *entry = lfirst(l);
+		AclResult	aclresult;
+		Form_pg_attribute att;
+
+		/*
+		 * for each entry in the list, get the name and type information from
+		 * the list and have TupleDescInitEntry fill in the attribute
+		 * information we need.
+		 */
+		attnum++;
+
+		attname = entry->colname;
+		typenameTypeIdAndMod(NULL, entry->typeName, &atttypid, &atttypmod);
+
+		aclresult = object_aclcheck(TypeRelationId, atttypid, GetUserId(), ACL_USAGE);
+		if (aclresult != ACLCHECK_OK)
+			aclcheck_error_type(aclresult, atttypid);
+
+		attcollation = GetColumnDefCollation(NULL, entry, atttypid);
+		attdim = list_length(entry->typeName->arrayBounds);
+		if (attdim > PG_INT16_MAX)
+			ereport(ERROR,
+					errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+					errmsg("too many array dimensions"));
+
+		if (entry->typeName->setof)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column \"%s\" cannot be declared SETOF",
+							attname)));
+
+		TupleDescInitEntry(desc, attnum, attname,
+						   atttypid, atttypmod, attdim);
+		att = TupleDescAttr(desc, attnum - 1);
+
+		/* Override TupleDescInitEntry's settings as requested */
+		TupleDescInitEntryCollation(desc, attnum, attcollation);
+		if (entry->storage)
+			att->attstorage = entry->storage;
+
+		/* Fill in additional stuff not handled by TupleDescInitEntry */
+		att->attnotnull = entry->is_not_null;
+		has_not_null |= entry->is_not_null;
+		att->attislocal = entry->is_local;
+		att->attinhcount = entry->inhcount;
+		att->attidentity = entry->identity;
+		att->attgenerated = entry->generated;
+	}
+
+	if (has_not_null)
+	{
+		TupleConstr *constr = (TupleConstr *) palloc0(sizeof(TupleConstr));
+
+		constr->has_not_null = true;
+		constr->has_generated_stored = false;
+		constr->defval = NULL;
+		constr->missing = NULL;
+		constr->num_defval = 0;
+		constr->check = NULL;
+		constr->num_check = 0;
+		desc->constr = constr;
+	}
+	else
+	{
+		desc->constr = NULL;
+	}
+
+	return desc;
+}
+
 /*
  * Emit the right error or warning message for a "DROP" command issued on a
  * non-existent relation
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index ffd2874ee3..d833d5f2e1 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -147,8 +147,6 @@ extern void TupleDescInitEntryCollation(TupleDesc desc,
 										AttrNumber attributeNumber,
 										Oid collationid);
 
-extern TupleDesc BuildDescForRelation(const List *columns);
-
 extern TupleDesc BuildDescFromLists(const List *names, const List *types, const List *typmods, const List *collations);
 
 extern Node *TupleDescGetDefault(TupleDesc tupdesc, AttrNumber attnum);
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 16b6126669..a9c6825601 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -27,6 +27,8 @@ struct AlterTableUtilityContext;	/* avoid including tcop/utility.h here */
 extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 									ObjectAddress *typaddress, const char *queryString);
 
+extern TupleDesc BuildDescForRelation(const List *columns);
+
 extern void RemoveRelations(DropStmt *drop);
 
 extern Oid	AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode);
-- 
2.41.0

v2-0008-Push-attcompression-and-attstorage-handling-into-.patchtext/plain; charset=UTF-8; name=v2-0008-Push-attcompression-and-attstorage-handling-into-.patchDownload
From 25a061ebb79c1ff21076976f55ecbb4f14ba28ce Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:35 +0200
Subject: [PATCH v2 08/10] Push attcompression and attstorage handling into
 BuildDescForRelation()

This was previously handled by the callers but it can be moved into a
common place.
---
 src/backend/commands/tablecmds.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0577e29230..cd6c46e866 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -941,10 +941,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			cookedDefaults = lappend(cookedDefaults, cooked);
 			attr->atthasdef = true;
 		}
-
-		attr->attcompression = GetAttributeCompression(attr->atttypid, colDef->compression);
-		if (colDef->storage_name)
-			attr->attstorage = GetAttributeStorage(attr->atttypid, colDef->storage_name);
 	}
 
 	/*
@@ -1347,8 +1343,6 @@ BuildDescForRelation(const List *columns)
 
 		/* Override TupleDescInitEntry's settings as requested */
 		TupleDescInitEntryCollation(desc, attnum, attcollation);
-		if (entry->storage)
-			att->attstorage = entry->storage;
 
 		/* Fill in additional stuff not handled by TupleDescInitEntry */
 		att->attnotnull = entry->is_not_null;
@@ -1357,6 +1351,11 @@ BuildDescForRelation(const List *columns)
 		att->attinhcount = entry->inhcount;
 		att->attidentity = entry->identity;
 		att->attgenerated = entry->generated;
+		att->attcompression = GetAttributeCompression(att->atttypid, entry->compression);
+		if (entry->storage)
+			att->attstorage = entry->storage;
+		else if (entry->storage_name)
+			att->attstorage = GetAttributeStorage(att->atttypid, entry->storage_name);
 	}
 
 	if (has_not_null)
-- 
2.41.0

v2-0009-Refactor-ATExecAddColumn-to-use-BuildDescForRelat.patchtext/plain; charset=UTF-8; name=v2-0009-Refactor-ATExecAddColumn-to-use-BuildDescForRelat.patchDownload
From 1dec488260cd85a2723acf6fafef5f7b93373534 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:35 +0200
Subject: [PATCH v2 09/10] Refactor ATExecAddColumn() to use
 BuildDescForRelation()

BuildDescForRelation() has all the knowledge for converting a
ColumnDef into pg_attribute/tuple descriptor.  ATExecAddColumn() can
make use of that, instead of duplicating all that logic.  We just pass
a one-element list of ColumnDef and we get back exactly the data
structure we need.  Note that we don't even need to touch
BuildDescForRelation() to make this work.
---
 src/backend/commands/tablecmds.c | 89 ++++++++------------------------
 1 file changed, 22 insertions(+), 67 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cd6c46e866..1c70cf7af5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -6968,22 +6968,15 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Relation	pgclass,
 				attrdesc;
 	HeapTuple	reltup;
-	FormData_pg_attribute attribute;
+	Form_pg_attribute attribute;
 	int			newattnum;
 	char		relkind;
-	HeapTuple	typeTuple;
-	Oid			typeOid;
-	int32		typmod;
-	Oid			collOid;
-	Form_pg_type tform;
 	Expr	   *defval;
 	List	   *children;
 	ListCell   *child;
 	AlterTableCmd *childcmd;
-	AclResult	aclresult;
 	ObjectAddress address;
 	TupleDesc	tupdesc;
-	FormData_pg_attribute *aattr[] = {&attribute};
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -7105,59 +7098,21 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				 errmsg("tables can have at most %d columns",
 						MaxHeapAttributeNumber)));
 
-	typeTuple = typenameType(NULL, colDef->typeName, &typmod);
-	tform = (Form_pg_type) GETSTRUCT(typeTuple);
-	typeOid = tform->oid;
+	/*
+	 * Construct new attribute's pg_attribute entry.
+	 */
+	tupdesc = BuildDescForRelation(list_make1(colDef));
 
-	aclresult = object_aclcheck(TypeRelationId, typeOid, GetUserId(), ACL_USAGE);
-	if (aclresult != ACLCHECK_OK)
-		aclcheck_error_type(aclresult, typeOid);
+	attribute = TupleDescAttr(tupdesc, 0);
 
-	collOid = GetColumnDefCollation(NULL, colDef, typeOid);
+	/* Fix up attribute number */
+	attribute->attnum = newattnum;
 
 	/* make sure datatype is legal for a column */
-	CheckAttributeType(colDef->colname, typeOid, collOid,
+	CheckAttributeType(NameStr(attribute->attname), attribute->atttypid, attribute->attcollation,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   0);
 
-	/*
-	 * Construct new attribute's pg_attribute entry.  (Variable-length fields
-	 * are handled by InsertPgAttributeTuples().)
-	 */
-	attribute.attrelid = myrelid;
-	namestrcpy(&(attribute.attname), colDef->colname);
-	attribute.atttypid = typeOid;
-	attribute.attstattarget = -1;
-	attribute.attlen = tform->typlen;
-	attribute.attnum = newattnum;
-	if (list_length(colDef->typeName->arrayBounds) > PG_INT16_MAX)
-		ereport(ERROR,
-				errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-				errmsg("too many array dimensions"));
-	attribute.attndims = list_length(colDef->typeName->arrayBounds);
-	attribute.atttypmod = typmod;
-	attribute.attbyval = tform->typbyval;
-	attribute.attalign = tform->typalign;
-	if (colDef->storage_name)
-		attribute.attstorage = GetAttributeStorage(typeOid, colDef->storage_name);
-	else
-		attribute.attstorage = tform->typstorage;
-	attribute.attcompression = GetAttributeCompression(typeOid,
-													   colDef->compression);
-	attribute.attnotnull = colDef->is_not_null;
-	attribute.atthasdef = false;
-	attribute.atthasmissing = false;
-	attribute.attidentity = colDef->identity;
-	attribute.attgenerated = colDef->generated;
-	attribute.attisdropped = false;
-	attribute.attislocal = colDef->is_local;
-	attribute.attinhcount = colDef->inhcount;
-	attribute.attcollation = collOid;
-
-	ReleaseSysCache(typeTuple);
-
-	tupdesc = CreateTupleDesc(lengthof(aattr), (FormData_pg_attribute **) &aattr);
-
 	InsertPgAttributeTuples(attrdesc, tupdesc, myrelid, NULL, NULL);
 
 	table_close(attrdesc, RowExclusiveLock);
@@ -7187,7 +7142,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		RawColumnDefault *rawEnt;
 
 		rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
-		rawEnt->attnum = attribute.attnum;
+		rawEnt->attnum = attribute->attnum;
 		rawEnt->raw_default = copyObject(colDef->raw_default);
 
 		/*
@@ -7261,7 +7216,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			NextValueExpr *nve = makeNode(NextValueExpr);
 
 			nve->seqid = RangeVarGetRelid(colDef->identitySequence, NoLock, false);
-			nve->typeId = typeOid;
+			nve->typeId = attribute->atttypid;
 
 			defval = (Expr *) nve;
 
@@ -7269,23 +7224,23 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 		}
 		else
-			defval = (Expr *) build_column_default(rel, attribute.attnum);
+			defval = (Expr *) build_column_default(rel, attribute->attnum);
 
-		if (!defval && DomainHasConstraints(typeOid))
+		if (!defval && DomainHasConstraints(attribute->atttypid))
 		{
 			Oid			baseTypeId;
 			int32		baseTypeMod;
 			Oid			baseTypeColl;
 
-			baseTypeMod = typmod;
-			baseTypeId = getBaseTypeAndTypmod(typeOid, &baseTypeMod);
+			baseTypeMod = attribute->atttypmod;
+			baseTypeId = getBaseTypeAndTypmod(attribute->atttypid, &baseTypeMod);
 			baseTypeColl = get_typcollation(baseTypeId);
 			defval = (Expr *) makeNullConst(baseTypeId, baseTypeMod, baseTypeColl);
 			defval = (Expr *) coerce_to_target_type(NULL,
 													(Node *) defval,
 													baseTypeId,
-													typeOid,
-													typmod,
+													attribute->atttypid,
+													attribute->atttypmod,
 													COERCION_ASSIGNMENT,
 													COERCE_IMPLICIT_CAST,
 													-1);
@@ -7298,17 +7253,17 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			NewColumnValue *newval;
 
 			newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
-			newval->attnum = attribute.attnum;
+			newval->attnum = attribute->attnum;
 			newval->expr = expression_planner(defval);
 			newval->is_generated = (colDef->generated != '\0');
 
 			tab->newvals = lappend(tab->newvals, newval);
 		}
 
-		if (DomainHasConstraints(typeOid))
+		if (DomainHasConstraints(attribute->atttypid))
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 
-		if (!TupleDescAttr(rel->rd_att, attribute.attnum - 1)->atthasmissing)
+		if (!TupleDescAttr(rel->rd_att, attribute->attnum - 1)->atthasmissing)
 		{
 			/*
 			 * If the new column is NOT NULL, and there is no missing value,
@@ -7321,8 +7276,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/*
 	 * Add needed dependency entries for the new column.
 	 */
-	add_column_datatype_dependency(myrelid, newattnum, attribute.atttypid);
-	add_column_collation_dependency(myrelid, newattnum, attribute.attcollation);
+	add_column_datatype_dependency(myrelid, newattnum, attribute->atttypid);
+	add_column_collation_dependency(myrelid, newattnum, attribute->attcollation);
 
 	/*
 	 * Propagate to children as appropriate.  Unlike most other ALTER
-- 
2.41.0

#6Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Peter Eisentraut (#5)
Re: tablecmds.c/MergeAttributes() cleanup

On 2023-Aug-29, Peter Eisentraut wrote:

Regarding this hunk in 0002,

@@ -3278,13 +3261,16 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*
* constraints is a list of CookedConstraint structs for previous constraints.
*
- * Returns true if merged (constraint is a duplicate), or false if it's
- * got a so-far-unique name, or throws error if conflict.
+ * If the constraint is a duplicate, then the existing constraint's
+ * inheritance count is updated.  If the constraint doesn't match or conflict
+ * with an existing one, a new constraint is appended to the list.  If there
+ * is a conflict (same name but different expression), throw an error.

This wording confused me:

"If the constraint doesn't match or conflict with an existing one, a new
constraint is appended to the list."

I first read it as "doesn't match or conflicts with ..." (i.e., the
negation only applied to the first verb, not both) which would have been
surprising (== broken) behavior.

I think it's clearer if you say "doesn't match nor conflict", but I'm
not sure if this is grammatically correct.

--
Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/

#7Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Peter Eisentraut (#5)
Re: tablecmds.c/MergeAttributes() cleanup

On 2023-Aug-29, Peter Eisentraut wrote:

From 471fda80c41fae835ecbe63ae8505526a37487a9 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:35 +0200
Subject: [PATCH v2 04/10] Add TupleDescGetDefault()

This unifies some repetitive code.

Note: I didn't push the "not found" error message into the new
function, even though all existing callers would be able to make use
of it. Using the existing error handling as-is would probably require
exposing the Relation type via tupdesc.h, which doesn't seem
desirable.

Note that all errors here are elog(ERROR), not user-facing, so ISTM
it would be OK to change them to report the relation OID rather than the
name.

--
Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
"In fact, the basic problem with Perl 5's subroutines is that they're not
crufty enough, so the cruft leaks out into user-defined code instead, by
the Conservation of Cruft Principle." (Larry Wall, Apocalypse 6)

#8Nathan Bossart
nathandbossart@gmail.com
In reply to: Peter Eisentraut (#5)
Re: tablecmds.c/MergeAttributes() cleanup

On Tue, Aug 29, 2023 at 10:43:39AM +0200, Peter Eisentraut wrote:

I have committed a few more patches from this series that were already
agreed upon. The remaining ones are rebased and reordered a bit, attached.

My compiler is complaining about 1fa9241b:

../postgresql/src/backend/commands/sequence.c: In function ‘DefineSequence’:
../postgresql/src/backend/commands/sequence.c:196:21: error: ‘coldef’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
196 | stmt->tableElts = lappend(stmt->tableElts, coldef);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This went away when I added a default case that ERROR'd or initialized
coldef to NULL.

--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com

#9Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Nathan Bossart (#8)
Re: tablecmds.c/MergeAttributes() cleanup

On 2023-Aug-29, Nathan Bossart wrote:

On Tue, Aug 29, 2023 at 10:43:39AM +0200, Peter Eisentraut wrote:

I have committed a few more patches from this series that were already
agreed upon. The remaining ones are rebased and reordered a bit, attached.

My compiler is complaining about 1fa9241b:

../postgresql/src/backend/commands/sequence.c: In function ‘DefineSequence’:
../postgresql/src/backend/commands/sequence.c:196:21: error: ‘coldef’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
196 | stmt->tableElts = lappend(stmt->tableElts, coldef);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This went away when I added a default case that ERROR'd or initialized
coldef to NULL.

Makes sense. However, maybe we should replace those ugly defines and
their hardcoded values in DefineSequence with a proper array with their
names and datatypes.

--
Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
"Los trabajadores menos efectivos son sistematicamente llevados al lugar
donde pueden hacer el menor daño posible: gerencia." (El principio Dilbert)

#10Nathan Bossart
nathandbossart@gmail.com
In reply to: Alvaro Herrera (#9)
Re: tablecmds.c/MergeAttributes() cleanup

On Tue, Aug 29, 2023 at 08:44:02PM +0200, Alvaro Herrera wrote:

On 2023-Aug-29, Nathan Bossart wrote:

My compiler is complaining about 1fa9241b:

../postgresql/src/backend/commands/sequence.c: In function ‘DefineSequence’:
../postgresql/src/backend/commands/sequence.c:196:21: error: ‘coldef’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
196 | stmt->tableElts = lappend(stmt->tableElts, coldef);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This went away when I added a default case that ERROR'd or initialized
coldef to NULL.

Makes sense. However, maybe we should replace those ugly defines and
their hardcoded values in DefineSequence with a proper array with their
names and datatypes.

That might be an improvement, but IIUC you'd still need to enumerate all of
the columns or data types to make sure you use the right get-Datum
function. It doesn't help that last_value uses Int64GetDatumFast and
log_cnt uses Int64GetDatum. I could be missing something, though.

--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com

#11Peter Eisentraut
peter@eisentraut.org
In reply to: Nathan Bossart (#8)
Re: tablecmds.c/MergeAttributes() cleanup

On 29.08.23 19:45, Nathan Bossart wrote:

On Tue, Aug 29, 2023 at 10:43:39AM +0200, Peter Eisentraut wrote:

I have committed a few more patches from this series that were already
agreed upon. The remaining ones are rebased and reordered a bit, attached.

My compiler is complaining about 1fa9241b:

../postgresql/src/backend/commands/sequence.c: In function ‘DefineSequence’:
../postgresql/src/backend/commands/sequence.c:196:21: error: ‘coldef’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
196 | stmt->tableElts = lappend(stmt->tableElts, coldef);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This went away when I added a default case that ERROR'd or initialized
coldef to NULL.

fixed

#12Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Nathan Bossart (#10)
1 attachment(s)
Re: tablecmds.c/MergeAttributes() cleanup

On 2023-Aug-29, Nathan Bossart wrote:

On Tue, Aug 29, 2023 at 08:44:02PM +0200, Alvaro Herrera wrote:

Makes sense. However, maybe we should replace those ugly defines and
their hardcoded values in DefineSequence with a proper array with their
names and datatypes.

That might be an improvement, but IIUC you'd still need to enumerate all of
the columns or data types to make sure you use the right get-Datum
function. It doesn't help that last_value uses Int64GetDatumFast and
log_cnt uses Int64GetDatum. I could be missing something, though.

Well, for sure I meant to enumerate everything that was needed,
including the initializer for the value. Like in the attached patch.

However, now that I've actually written it, I don't find it so pretty
anymore, but maybe that's just because I don't know how to write the
array assignment as a single statement instead of a separate statement
for each column.

But this should silence the warnings, anyway.

--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/

Attachments:

seq-warnings.patchtext/x-diff; charset=utf-8Download
commit 50ac445d1e1ad84282d59c09a3937c221d94b968
Author:     Alvaro Herrera <alvherre@alvh.no-ip.org> [Álvaro Herrera <alvherre@alvh.no-ip.org>]
AuthorDate: Wed Aug 30 16:09:52 2023 +0200
CommitDate: Wed Aug 30 16:41:06 2023 +0200

    seq-warnings

diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0b0003fcc8..4798c9cd20 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -129,11 +129,16 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 	Relation	rel;
 	HeapTuple	tuple;
 	TupleDesc	tupDesc;
-	Datum		value[SEQ_COL_LASTCOL];
-	bool		null[SEQ_COL_LASTCOL];
 	Datum		pgs_values[Natts_pg_sequence];
 	bool		pgs_nulls[Natts_pg_sequence];
-	int			i;
+	struct SequenceColumn
+	{
+		const char *name;
+		Oid			type;
+		Datum		init_value;
+	}			sequence_cols[3];
+	Datum		value[3];
+	bool		null[3];
 
 	/*
 	 * If if_not_exists was given and a relation with the same name already
@@ -166,32 +171,39 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
 				&seqform, &seqdataform,
 				&need_seq_rewrite, &owned_by);
 
+	sequence_cols[0] = (struct SequenceColumn)
+	{
+		.name = "last_value",
+			.type = INT8OID,
+			.init_value = Int64GetDatumFast(seqdataform.last_value)
+	};
+	sequence_cols[1] = (struct SequenceColumn)
+	{
+		.name = "log_cnt",
+			.type = INT8OID,
+			.init_value = Int64GetDatum((int64) 0)
+	};
+	sequence_cols[2] = (struct SequenceColumn)
+	{
+		.name = "is_called",
+			.type = BOOLOID,
+			.init_value = BoolGetDatum(false)
+	};
+
 	/*
 	 * Create relation (and fill value[] and null[] for the tuple)
 	 */
 	stmt->tableElts = NIL;
-	for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++)
+	for (int i = 0; i < lengthof(sequence_cols); i++)
 	{
 		ColumnDef  *coldef;
 
-		switch (i)
-		{
-			case SEQ_COL_LASTVAL:
-				coldef = makeColumnDef("last_value", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatumFast(seqdataform.last_value);
-				break;
-			case SEQ_COL_LOG:
-				coldef = makeColumnDef("log_cnt", INT8OID, -1, InvalidOid);
-				value[i - 1] = Int64GetDatum((int64) 0);
-				break;
-			case SEQ_COL_CALLED:
-				coldef = makeColumnDef("is_called", BOOLOID, -1, InvalidOid);
-				value[i - 1] = BoolGetDatum(false);
-				break;
-		}
-
+		coldef = makeColumnDef(sequence_cols[i].name, sequence_cols[i].type, -1,
+							   InvalidOid);
 		coldef->is_not_null = true;
-		null[i - 1] = false;
+
+		value[i] = sequence_cols[i].init_value;
+		null[i] = false;
 
 		stmt->tableElts = lappend(stmt->tableElts, coldef);
 	}
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 7db7b3da7b..784280b26d 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -31,17 +31,6 @@ typedef struct FormData_pg_sequence_data
 
 typedef FormData_pg_sequence_data *Form_pg_sequence_data;
 
-/*
- * Columns of a sequence relation
- */
-
-#define SEQ_COL_LASTVAL			1
-#define SEQ_COL_LOG				2
-#define SEQ_COL_CALLED			3
-
-#define SEQ_COL_FIRSTCOL		SEQ_COL_LASTVAL
-#define SEQ_COL_LASTCOL			SEQ_COL_CALLED
-
 /* XLOG stuff */
 #define XLOG_SEQ_LOG			0x00
 
#13Peter Eisentraut
peter@eisentraut.org
In reply to: Alvaro Herrera (#6)
9 attachment(s)
Re: tablecmds.c/MergeAttributes() cleanup

On 29.08.23 13:20, Alvaro Herrera wrote:

On 2023-Aug-29, Peter Eisentraut wrote:

@@ -3278,13 +3261,16 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*
* constraints is a list of CookedConstraint structs for previous constraints.
*
- * Returns true if merged (constraint is a duplicate), or false if it's
- * got a so-far-unique name, or throws error if conflict.
+ * If the constraint is a duplicate, then the existing constraint's
+ * inheritance count is updated.  If the constraint doesn't match or conflict
+ * with an existing one, a new constraint is appended to the list.  If there
+ * is a conflict (same name but different expression), throw an error.

This wording confused me:

"If the constraint doesn't match or conflict with an existing one, a new
constraint is appended to the list."

I first read it as "doesn't match or conflicts with ..." (i.e., the
negation only applied to the first verb, not both) which would have been
surprising (== broken) behavior.

I think it's clearer if you say "doesn't match nor conflict", but I'm
not sure if this is grammatically correct.

Here is an updated version of this patch set. I resolved some conflicts
and addressed this comment of yours. I also dropped the one patch with
some catalog header edits that people didn't seem to particularly like.

The patches that are now 0001 through 0004 were previously reviewed and
just held for the not-null constraint patches, I think, so I'll commit
them in a few days if there are no objections.

Patches 0005 through 0007 are also ready in my opinion, but they haven't
really been reviewed, so this would be something for reviewers to focus
on. (0005 and 0007 are trivial, but they go to together with 0006.)

The remaining 0008 and 0009 were still under discussion and contemplation.

Attachments:

v3-0001-Clean-up-MergeAttributesIntoExisting.patchtext/plain; charset=UTF-8; name=v3-0001-Clean-up-MergeAttributesIntoExisting.patchDownload
From 28e4dbba35fc3162c13f5896551921896cf30d1c Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:34 +0200
Subject: [PATCH v3 1/9] Clean up MergeAttributesIntoExisting()

Make variable naming clearer and more consistent.  Move some variables
to smaller scope.  Remove some unnecessary intermediate variables.
Try to save some vertical space.

Apply analogous changes to nearby MergeConstraintsIntoExisting() and
RemoveInheritance() for consistency.
---
 src/backend/commands/tablecmds.c | 123 ++++++++++++-------------------
 1 file changed, 48 insertions(+), 75 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8a2c671b66..ff001f5ceb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -15695,53 +15695,39 @@ static void
 MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 {
 	Relation	attrrel;
-	AttrNumber	parent_attno;
-	int			parent_natts;
-	TupleDesc	tupleDesc;
-	HeapTuple	tuple;
-	bool		child_is_partition = false;
+	TupleDesc	parent_desc;
 
 	attrrel = table_open(AttributeRelationId, RowExclusiveLock);
+	parent_desc = RelationGetDescr(parent_rel);
 
-	tupleDesc = RelationGetDescr(parent_rel);
-	parent_natts = tupleDesc->natts;
-
-	/* If parent_rel is a partitioned table, child_rel must be a partition */
-	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-		child_is_partition = true;
-
-	for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
+	for (AttrNumber parent_attno = 1; parent_attno <= parent_desc->natts; parent_attno++)
 	{
-		Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
-													parent_attno - 1);
-		char	   *attributeName = NameStr(attribute->attname);
+		Form_pg_attribute parent_att = TupleDescAttr(parent_desc, parent_attno - 1);
+		char	   *parent_attname = NameStr(parent_att->attname);
+		HeapTuple	tuple;
 
 		/* Ignore dropped columns in the parent. */
-		if (attribute->attisdropped)
+		if (parent_att->attisdropped)
 			continue;
 
 		/* Find same column in child (matching on column name). */
-		tuple = SearchSysCacheCopyAttName(RelationGetRelid(child_rel),
-										  attributeName);
+		tuple = SearchSysCacheCopyAttName(RelationGetRelid(child_rel), parent_attname);
 		if (HeapTupleIsValid(tuple))
 		{
-			/* Check they are same type, typmod, and collation */
-			Form_pg_attribute childatt = (Form_pg_attribute) GETSTRUCT(tuple);
+			Form_pg_attribute child_att = (Form_pg_attribute) GETSTRUCT(tuple);
 
-			if (attribute->atttypid != childatt->atttypid ||
-				attribute->atttypmod != childatt->atttypmod)
+			if (parent_att->atttypid != child_att->atttypid ||
+				parent_att->atttypmod != child_att->atttypmod)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("child table \"%s\" has different type for column \"%s\"",
-								RelationGetRelationName(child_rel),
-								attributeName)));
+								RelationGetRelationName(child_rel), parent_attname)));
 
-			if (attribute->attcollation != childatt->attcollation)
+			if (parent_att->attcollation != child_att->attcollation)
 				ereport(ERROR,
 						(errcode(ERRCODE_COLLATION_MISMATCH),
 						 errmsg("child table \"%s\" has different collation for column \"%s\"",
-								RelationGetRelationName(child_rel),
-								attributeName)));
+								RelationGetRelationName(child_rel), parent_attname)));
 
 			/*
 			 * If the parent has a not-null constraint that's not NO INHERIT,
@@ -15749,40 +15735,38 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 			 *
 			 * Other constraints are checked elsewhere.
 			 */
-			if (attribute->attnotnull && !childatt->attnotnull)
+			if (parent_att->attnotnull && !child_att->attnotnull)
 			{
 				HeapTuple	contup;
 
 				contup = findNotNullConstraintAttnum(RelationGetRelid(parent_rel),
-													 attribute->attnum);
+													 parent_att->attnum);
 				if (HeapTupleIsValid(contup) &&
 					!((Form_pg_constraint) GETSTRUCT(contup))->connoinherit)
 					ereport(ERROR,
 							errcode(ERRCODE_DATATYPE_MISMATCH),
 							errmsg("column \"%s\" in child table must be marked NOT NULL",
-								   attributeName));
+								   parent_attname));
 			}
 
 			/*
 			 * Child column must be generated if and only if parent column is.
 			 */
-			if (attribute->attgenerated && !childatt->attgenerated)
+			if (parent_att->attgenerated && !child_att->attgenerated)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("column \"%s\" in child table must be a generated column",
-								attributeName)));
-			if (childatt->attgenerated && !attribute->attgenerated)
+						 errmsg("column \"%s\" in child table must be a generated column", parent_attname)));
+			if (child_att->attgenerated && !parent_att->attgenerated)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("column \"%s\" in child table must not be a generated column",
-								attributeName)));
+						 errmsg("column \"%s\" in child table must not be a generated column", parent_attname)));
 
 			/*
 			 * OK, bump the child column's inheritance count.  (If we fail
 			 * later on, this change will just roll back.)
 			 */
-			childatt->attinhcount++;
-			if (childatt->attinhcount < 0)
+			child_att->attinhcount++;
+			if (child_att->attinhcount < 0)
 				ereport(ERROR,
 						errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 						errmsg("too many inheritance parents"));
@@ -15792,10 +15776,10 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 			 * is same in all partitions. (Note: there are only inherited
 			 * attributes in partitions)
 			 */
-			if (child_is_partition)
+			if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			{
-				Assert(childatt->attinhcount == 1);
-				childatt->attislocal = false;
+				Assert(child_att->attinhcount == 1);
+				child_att->attislocal = false;
 			}
 
 			CatalogTupleUpdate(attrrel, &tuple->t_self, tuple);
@@ -15805,8 +15789,7 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 		{
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("child table is missing column \"%s\"",
-							attributeName)));
+					 errmsg("child table is missing column \"%s\"", parent_attname)));
 		}
 	}
 
@@ -15833,27 +15816,20 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
 static void
 MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 {
-	Relation	catalog_relation;
-	TupleDesc	tuple_desc;
+	Relation	constraintrel;
 	SysScanDesc parent_scan;
 	ScanKeyData parent_key;
 	HeapTuple	parent_tuple;
 	Oid			parent_relid = RelationGetRelid(parent_rel);
-	bool		child_is_partition = false;
 
-	catalog_relation = table_open(ConstraintRelationId, RowExclusiveLock);
-	tuple_desc = RelationGetDescr(catalog_relation);
-
-	/* If parent_rel is a partitioned table, child_rel must be a partition */
-	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-		child_is_partition = true;
+	constraintrel = table_open(ConstraintRelationId, RowExclusiveLock);
 
 	/* Outer loop scans through the parent's constraint definitions */
 	ScanKeyInit(&parent_key,
 				Anum_pg_constraint_conrelid,
 				BTEqualStrategyNumber, F_OIDEQ,
 				ObjectIdGetDatum(parent_relid));
-	parent_scan = systable_beginscan(catalog_relation, ConstraintRelidTypidNameIndexId,
+	parent_scan = systable_beginscan(constraintrel, ConstraintRelidTypidNameIndexId,
 									 true, NULL, 1, &parent_key);
 
 	while (HeapTupleIsValid(parent_tuple = systable_getnext(parent_scan)))
@@ -15877,7 +15853,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 					Anum_pg_constraint_conrelid,
 					BTEqualStrategyNumber, F_OIDEQ,
 					ObjectIdGetDatum(RelationGetRelid(child_rel)));
-		child_scan = systable_beginscan(catalog_relation, ConstraintRelidTypidNameIndexId,
+		child_scan = systable_beginscan(constraintrel, ConstraintRelidTypidNameIndexId,
 										true, NULL, 1, &child_key);
 
 		while (HeapTupleIsValid(child_tuple = systable_getnext(child_scan)))
@@ -15892,10 +15868,12 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			 * CHECK constraint are matched by name, NOT NULL ones by
 			 * attribute number
 			 */
-			if (child_con->contype == CONSTRAINT_CHECK &&
-				strcmp(NameStr(parent_con->conname),
-					   NameStr(child_con->conname)) != 0)
-				continue;
+			if (child_con->contype == CONSTRAINT_CHECK)
+			{
+				if (strcmp(NameStr(parent_con->conname),
+						   NameStr(child_con->conname)) != 0)
+					continue;
+			}
 			else if (child_con->contype == CONSTRAINT_NOTNULL)
 			{
 				AttrNumber	parent_attno = extractNotNullColumn(parent_tuple);
@@ -15908,12 +15886,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			}
 
 			if (child_con->contype == CONSTRAINT_CHECK &&
-				!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
+				!constraints_equivalent(parent_tuple, child_tuple, RelationGetDescr(constraintrel)))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
 						 errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
-								RelationGetRelationName(child_rel),
-								NameStr(parent_con->conname))));
+								RelationGetRelationName(child_rel), NameStr(parent_con->conname))));
 
 			/*
 			 * If the CHECK child constraint is "no inherit" then cannot
@@ -15931,8 +15908,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
-								NameStr(child_con->conname),
-								RelationGetRelationName(child_rel))));
+								NameStr(child_con->conname), RelationGetRelationName(child_rel))));
 
 			/*
 			 * If the child constraint is "not valid" then cannot merge with a
@@ -15942,8 +15918,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("constraint \"%s\" conflicts with NOT VALID constraint on child table \"%s\"",
-								NameStr(child_con->conname),
-								RelationGetRelationName(child_rel))));
+								NameStr(child_con->conname), RelationGetRelationName(child_rel))));
 
 			/*
 			 * OK, bump the child constraint's inheritance count.  (If we fail
@@ -15965,13 +15940,13 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 			 * inherited only once since it cannot have multiple parents and
 			 * it is never considered local.
 			 */
-			if (child_is_partition)
+			if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			{
 				Assert(child_con->coninhcount == 1);
 				child_con->conislocal = false;
 			}
 
-			CatalogTupleUpdate(catalog_relation, &child_copy->t_self, child_copy);
+			CatalogTupleUpdate(constraintrel, &child_copy->t_self, child_copy);
 			heap_freetuple(child_copy);
 
 			found = true;
@@ -15998,7 +15973,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
 	}
 
 	systable_endscan(parent_scan);
-	table_close(catalog_relation, RowExclusiveLock);
+	table_close(constraintrel, RowExclusiveLock);
 }
 
 /*
@@ -16154,11 +16129,9 @@ RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached)
 	List	   *connames;
 	List	   *nncolumns;
 	bool		found;
-	bool		child_is_partition = false;
+	bool		is_partitioning;
 
-	/* If parent_rel is a partitioned table, child_rel must be a partition */
-	if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-		child_is_partition = true;
+	is_partitioning = (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 
 	found = DeleteInheritsTuple(RelationGetRelid(child_rel),
 								RelationGetRelid(parent_rel),
@@ -16166,7 +16139,7 @@ RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached)
 								RelationGetRelationName(child_rel));
 	if (!found)
 	{
-		if (child_is_partition)
+		if (is_partitioning)
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_TABLE),
 					 errmsg("relation \"%s\" is not a partition of relation \"%s\"",
@@ -16320,7 +16293,7 @@ RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached)
 	drop_parent_dependency(RelationGetRelid(child_rel),
 						   RelationRelationId,
 						   RelationGetRelid(parent_rel),
-						   child_dependency_type(child_is_partition));
+						   child_dependency_type(is_partitioning));
 
 	/*
 	 * Post alter hook of this inherits. Since object_access_hook doesn't take
-- 
2.42.0

v3-0002-Clean-up-MergeCheckConstraint.patchtext/plain; charset=UTF-8; name=v3-0002-Clean-up-MergeCheckConstraint.patchDownload
From c5653b746bd8611c7297fb43013ab4f8adee8b40 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:34 +0200
Subject: [PATCH v3 2/9] Clean up MergeCheckConstraint()

If the constraint is not already in the list, add it ourselves,
instead of making the caller do it.  This makes the interface more
consistent with other "merge" functions in this file.
---
 src/backend/commands/tablecmds.c | 48 +++++++++++++++-----------------
 1 file changed, 22 insertions(+), 26 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ff001f5ceb..b73f4c96a4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -353,7 +353,7 @@ static void RangeVarCallbackForTruncate(const RangeVar *relation,
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
 							 bool is_partition, List **supconstr,
 							 List **supnotnulls);
-static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
+static List *MergeCheckConstraint(List *constraints, const char *name, Node *expr);
 static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
 static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
 static void StoreCatalogInheritance(Oid relationId, List *supers,
@@ -2913,24 +2913,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 									   name,
 									   RelationGetRelationName(relation))));
 
-				/* check for duplicate */
-				if (!MergeCheckConstraint(constraints, name, expr))
-				{
-					/* nope, this is a new one */
-					CookedConstraint *cooked;
-
-					cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
-					cooked->contype = CONSTR_CHECK;
-					cooked->conoid = InvalidOid;	/* until created */
-					cooked->name = pstrdup(name);
-					cooked->attnum = 0; /* not used for constraints */
-					cooked->expr = expr;
-					cooked->skip_validation = false;
-					cooked->is_local = false;
-					cooked->inhcount = 1;
-					cooked->is_no_inherit = false;
-					constraints = lappend(constraints, cooked);
-				}
+				constraints = MergeCheckConstraint(constraints, name, expr);
 			}
 		}
 
@@ -3277,13 +3260,17 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
  *
  * constraints is a list of CookedConstraint structs for previous constraints.
  *
- * Returns true if merged (constraint is a duplicate), or false if it's
- * got a so-far-unique name, or throws error if conflict.
+ * If the new constraint matches an existing one, then the existing
+ * constraint's inheritance count is updated.  If there is a conflict (same
+ * name but different expression), throw an error.  If the constraint neither
+ * matches nor conflicts with an existing one, a new constraint is appended to
+ * the list.
  */
-static bool
-MergeCheckConstraint(List *constraints, char *name, Node *expr)
+static List *
+MergeCheckConstraint(List *constraints, const char *name, Node *expr)
 {
 	ListCell   *lc;
+	CookedConstraint *newcon;
 
 	foreach(lc, constraints)
 	{
@@ -3297,13 +3284,13 @@ MergeCheckConstraint(List *constraints, char *name, Node *expr)
 
 		if (equal(expr, ccon->expr))
 		{
-			/* OK to merge */
+			/* OK to merge constraint with existing */
 			ccon->inhcount++;
 			if (ccon->inhcount < 0)
 				ereport(ERROR,
 						errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 						errmsg("too many inheritance parents"));
-			return true;
+			return constraints;
 		}
 
 		ereport(ERROR,
@@ -3312,7 +3299,16 @@ MergeCheckConstraint(List *constraints, char *name, Node *expr)
 						name)));
 	}
 
-	return false;
+	/*
+	 * Constraint couldn't be merged with an existing one and also didn't
+	 * conflict with an existing one, so add it as a new one to the list.
+	 */
+	newcon = palloc0_object(CookedConstraint);
+	newcon->contype = CONSTR_CHECK;
+	newcon->name = pstrdup(name);
+	newcon->expr = expr;
+	newcon->inhcount = 1;
+	return lappend(constraints, newcon);
 }
 
 
-- 
2.42.0

v3-0003-MergeAttributes-and-related-variable-renaming.patchtext/plain; charset=UTF-8; name=v3-0003-MergeAttributes-and-related-variable-renaming.patchDownload
From b558f35954be5c232d4ff2bb23313d6ce0cceb49 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:34 +0200
Subject: [PATCH v3 3/9] MergeAttributes() and related variable renaming

Mainly, rename "schema" to "columns" and related changes.  The
previous naming has long been confusing.
---
 src/backend/access/common/tupdesc.c |  10 +--
 src/backend/commands/tablecmds.c    | 109 +++++++++++++---------------
 src/include/access/tupdesc.h        |   4 +-
 3 files changed, 59 insertions(+), 64 deletions(-)

diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 7c5c390503..253d6c86f8 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -782,12 +782,12 @@ TupleDescInitEntryCollation(TupleDesc desc,
 /*
  * BuildDescForRelation
  *
- * Given a relation schema (list of ColumnDef nodes), build a TupleDesc.
+ * Given a list of ColumnDef nodes, build a TupleDesc.
  *
  * Note: tdtypeid will need to be filled in later on.
  */
 TupleDesc
-BuildDescForRelation(List *schema)
+BuildDescForRelation(const List *columns)
 {
 	int			natts;
 	AttrNumber	attnum;
@@ -803,13 +803,13 @@ BuildDescForRelation(List *schema)
 	/*
 	 * allocate a new tuple descriptor
 	 */
-	natts = list_length(schema);
+	natts = list_length(columns);
 	desc = CreateTemplateTupleDesc(natts);
 	has_not_null = false;
 
 	attnum = 0;
 
-	foreach(l, schema)
+	foreach(l, columns)
 	{
 		ColumnDef  *entry = lfirst(l);
 		AclResult	aclresult;
@@ -891,7 +891,7 @@ BuildDescForRelation(List *schema)
  * with functions returning RECORD.
  */
 TupleDesc
-BuildDescFromLists(List *names, List *types, List *typmods, List *collations)
+BuildDescFromLists(const List *names, const List *types, const List *typmods, const List *collations)
 {
 	int			natts;
 	AttrNumber	attnum;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b73f4c96a4..ad398e837d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -350,7 +350,7 @@ static void truncate_check_perms(Oid relid, Form_pg_class reltuple);
 static void truncate_check_activity(Relation rel);
 static void RangeVarCallbackForTruncate(const RangeVar *relation,
 										Oid relId, Oid oldRelId, void *arg);
-static List *MergeAttributes(List *schema, List *supers, char relpersistence,
+static List *MergeAttributes(List *columns, const List *supers, char relpersistence,
 							 bool is_partition, List **supconstr,
 							 List **supnotnulls);
 static List *MergeCheckConstraint(List *constraints, const char *name, Node *expr);
@@ -361,7 +361,7 @@ static void StoreCatalogInheritance(Oid relationId, List *supers,
 static void StoreCatalogInheritance1(Oid relationId, Oid parentOid,
 									 int32 seqNumber, Relation inhRelation,
 									 bool child_is_partition);
-static int	findAttrByName(const char *attributeName, List *schema);
+static int	findAttrByName(const char *attributeName, const List *columns);
 static void AlterIndexNamespaces(Relation classRel, Relation rel,
 								 Oid oldNspOid, Oid newNspOid, ObjectAddresses *objsMoved);
 static void AlterSeqNamespaces(Relation classRel, Relation rel,
@@ -2307,7 +2307,7 @@ storage_name(char c)
  *		Returns new schema given initial schema and superclasses.
  *
  * Input arguments:
- * 'schema' is the column/attribute definition for the table. (It's a list
+ * 'columns' is the column/attribute definition for the table. (It's a list
  *		of ColumnDef's.) It is destructively changed.
  * 'supers' is a list of OIDs of parent relations, already locked by caller.
  * 'relpersistence' is the persistence type of the table.
@@ -2369,17 +2369,17 @@ storage_name(char c)
  *----------
  */
 static List *
-MergeAttributes(List *schema, List *supers, char relpersistence,
+MergeAttributes(List *columns, const List *supers, char relpersistence,
 				bool is_partition, List **supconstr, List **supnotnulls)
 {
-	List	   *inhSchema = NIL;
+	List	   *inh_columns = NIL;
 	List	   *constraints = NIL;
 	List	   *nnconstraints = NIL;
 	bool		have_bogus_defaults = false;
 	int			child_attno;
 	static Node bogus_marker = {0}; /* marks conflicting defaults */
-	List	   *saved_schema = NIL;
-	ListCell   *entry;
+	List	   *saved_columns = NIL;
+	ListCell   *lc;
 
 	/*
 	 * Check for and reject tables with too many columns. We perform this
@@ -2392,7 +2392,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	 * Note that we also need to check that we do not exceed this figure after
 	 * including columns from inherited relations.
 	 */
-	if (list_length(schema) > MaxHeapAttributeNumber)
+	if (list_length(columns) > MaxHeapAttributeNumber)
 		ereport(ERROR,
 				(errcode(ERRCODE_TOO_MANY_COLUMNS),
 				 errmsg("tables can have at most %d columns",
@@ -2406,15 +2406,15 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	 * sense to assume such conflicts are errors.
 	 *
 	 * We don't use foreach() here because we have two nested loops over the
-	 * schema list, with possible element deletions in the inner one.  If we
+	 * columns list, with possible element deletions in the inner one.  If we
 	 * used foreach_delete_current() it could only fix up the state of one of
 	 * the loops, so it seems cleaner to use looping over list indexes for
 	 * both loops.  Note that any deletion will happen beyond where the outer
 	 * loop is, so its index never needs adjustment.
 	 */
-	for (int coldefpos = 0; coldefpos < list_length(schema); coldefpos++)
+	for (int coldefpos = 0; coldefpos < list_length(columns); coldefpos++)
 	{
-		ColumnDef  *coldef = list_nth_node(ColumnDef, schema, coldefpos);
+		ColumnDef  *coldef = list_nth_node(ColumnDef, columns, coldefpos);
 
 		if (!is_partition && coldef->typeName == NULL)
 		{
@@ -2431,9 +2431,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		}
 
 		/* restpos scans all entries beyond coldef; incr is in loop body */
-		for (int restpos = coldefpos + 1; restpos < list_length(schema);)
+		for (int restpos = coldefpos + 1; restpos < list_length(columns);)
 		{
-			ColumnDef  *restdef = list_nth_node(ColumnDef, schema, restpos);
+			ColumnDef  *restdef = list_nth_node(ColumnDef, columns, restpos);
 
 			if (strcmp(coldef->colname, restdef->colname) == 0)
 			{
@@ -2447,7 +2447,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 					coldef->cooked_default = restdef->cooked_default;
 					coldef->constraints = restdef->constraints;
 					coldef->is_from_type = false;
-					schema = list_delete_nth_cell(schema, restpos);
+					columns = list_delete_nth_cell(columns, restpos);
 				}
 				else
 					ereport(ERROR,
@@ -2467,18 +2467,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	 */
 	if (is_partition)
 	{
-		saved_schema = schema;
-		schema = NIL;
+		saved_columns = columns;
+		columns = NIL;
 	}
 
 	/*
 	 * Scan the parents left-to-right, and merge their attributes to form a
-	 * list of inherited attributes (inhSchema).
+	 * list of inherited columns (inh_columns).
 	 */
 	child_attno = 0;
-	foreach(entry, supers)
+	foreach(lc, supers)
 	{
-		Oid			parent = lfirst_oid(entry);
+		Oid			parent = lfirst_oid(lc);
 		Relation	relation;
 		TupleDesc	tupleDesc;
 		TupleConstr *constr;
@@ -2486,7 +2486,6 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		List	   *inherited_defaults;
 		List	   *cols_with_defaults;
 		List	   *nnconstrs;
-		AttrNumber	parent_attno;
 		ListCell   *lc1;
 		ListCell   *lc2;
 		Bitmapset  *pkattrs;
@@ -2507,8 +2506,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		 * We do not allow partitioned tables and partitions to participate in
 		 * regular inheritance.
 		 */
-		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
-			!is_partition)
+		if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && !is_partition)
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot inherit from partitioned table \"%s\"",
@@ -2593,7 +2591,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			nncols = bms_add_member(nncols,
 									((CookedConstraint *) lfirst(lc1))->attnum);
 
-		for (parent_attno = 1; parent_attno <= tupleDesc->natts;
+		for (AttrNumber parent_attno = 1; parent_attno <= tupleDesc->natts;
 			 parent_attno++)
 		{
 			Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
@@ -2611,7 +2609,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			/*
 			 * Does it conflict with some previously inherited column?
 			 */
-			exist_attno = findAttrByName(attributeName, inhSchema);
+			exist_attno = findAttrByName(attributeName, inh_columns);
 			if (exist_attno > 0)
 			{
 				Oid			defTypeId;
@@ -2624,7 +2622,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				ereport(NOTICE,
 						(errmsg("merging multiple inherited definitions of column \"%s\"",
 								attributeName)));
-				def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
+				def = (ColumnDef *) list_nth(inh_columns, exist_attno - 1);
 
 				/*
 				 * Must have the same type and typmod
@@ -2761,7 +2759,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				if (CompressionMethodIsValid(attribute->attcompression))
 					def->compression =
 						pstrdup(GetCompressionMethodName(attribute->attcompression));
-				inhSchema = lappend(inhSchema, def);
+				inh_columns = lappend(inh_columns, def);
 				newattmap->attnums[parent_attno - 1] = ++child_attno;
 
 				/*
@@ -2862,7 +2860,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			 * If we already had a default from some prior parent, check to
 			 * see if they are the same.  If so, no problem; if not, mark the
 			 * column as having a bogus default.  Below, we will complain if
-			 * the bogus default isn't overridden by the child schema.
+			 * the bogus default isn't overridden by the child columns.
 			 */
 			Assert(def->raw_default == NULL);
 			if (def->cooked_default == NULL)
@@ -2882,9 +2880,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		if (constr && constr->num_check > 0)
 		{
 			ConstrCheck *check = constr->check;
-			int			i;
 
-			for (i = 0; i < constr->num_check; i++)
+			for (int i = 0; i < constr->num_check; i++)
 			{
 				char	   *name = check[i].ccname;
 				Node	   *expr;
@@ -2945,27 +2942,27 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	}
 
 	/*
-	 * If we had no inherited attributes, the result schema is just the
+	 * If we had no inherited attributes, the result columns are just the
 	 * explicitly declared columns.  Otherwise, we need to merge the declared
-	 * columns into the inherited schema list.  Although, we never have any
+	 * columns into the inherited column list.  Although, we never have any
 	 * explicitly declared columns if the table is a partition.
 	 */
-	if (inhSchema != NIL)
+	if (inh_columns != NIL)
 	{
-		int			schema_attno = 0;
+		int			newcol_attno = 0;
 
-		foreach(entry, schema)
+		foreach(lc, columns)
 		{
-			ColumnDef  *newdef = lfirst(entry);
+			ColumnDef  *newdef = lfirst(lc);
 			char	   *attributeName = newdef->colname;
 			int			exist_attno;
 
-			schema_attno++;
+			newcol_attno++;
 
 			/*
 			 * Does it conflict with some previously inherited column?
 			 */
-			exist_attno = findAttrByName(attributeName, inhSchema);
+			exist_attno = findAttrByName(attributeName, inh_columns);
 			if (exist_attno > 0)
 			{
 				ColumnDef  *def;
@@ -2985,7 +2982,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				/*
 				 * Yes, try to merge the two column definitions.
 				 */
-				if (exist_attno == schema_attno)
+				if (exist_attno == newcol_attno)
 					ereport(NOTICE,
 							(errmsg("merging column \"%s\" with inherited definition",
 									attributeName)));
@@ -2993,7 +2990,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 					ereport(NOTICE,
 							(errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
 							 errdetail("User-specified column moved to the position of the inherited column.")));
-				def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
+				def = (ColumnDef *) list_nth(inh_columns, exist_attno - 1);
 
 				/*
 				 * Must have the same type and typmod
@@ -3118,19 +3115,19 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			else
 			{
 				/*
-				 * No, attach new column to result schema
+				 * No, attach new column to result columns
 				 */
-				inhSchema = lappend(inhSchema, newdef);
+				inh_columns = lappend(inh_columns, newdef);
 			}
 		}
 
-		schema = inhSchema;
+		columns = inh_columns;
 
 		/*
 		 * Check that we haven't exceeded the legal # of columns after merging
 		 * in inherited columns.
 		 */
-		if (list_length(schema) > MaxHeapAttributeNumber)
+		if (list_length(columns) > MaxHeapAttributeNumber)
 			ereport(ERROR,
 					(errcode(ERRCODE_TOO_MANY_COLUMNS),
 					 errmsg("tables can have at most %d columns",
@@ -3144,13 +3141,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	 */
 	if (is_partition)
 	{
-		foreach(entry, saved_schema)
+		foreach(lc, saved_columns)
 		{
-			ColumnDef  *restdef = lfirst(entry);
+			ColumnDef  *restdef = lfirst(lc);
 			bool		found = false;
 			ListCell   *l;
 
-			foreach(l, schema)
+			foreach(l, columns)
 			{
 				ColumnDef  *coldef = lfirst(l);
 
@@ -3222,9 +3219,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	 */
 	if (have_bogus_defaults)
 	{
-		foreach(entry, schema)
+		foreach(lc, columns)
 		{
-			ColumnDef  *def = lfirst(entry);
+			ColumnDef  *def = lfirst(lc);
 
 			if (def->cooked_default == &bogus_marker)
 			{
@@ -3247,7 +3244,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	*supconstr = constraints;
 	*supnotnulls = nnconstraints;
 
-	return schema;
+	return columns;
 }
 
 
@@ -3402,22 +3399,20 @@ StoreCatalogInheritance1(Oid relationId, Oid parentOid,
 }
 
 /*
- * Look for an existing schema entry with the given name.
+ * Look for an existing column entry with the given name.
  *
- * Returns the index (starting with 1) if attribute already exists in schema,
+ * Returns the index (starting with 1) if attribute already exists in columns,
  * 0 if it doesn't.
  */
 static int
-findAttrByName(const char *attributeName, List *schema)
+findAttrByName(const char *attributeName, const List *columns)
 {
-	ListCell   *s;
+	ListCell   *lc;
 	int			i = 1;
 
-	foreach(s, schema)
+	foreach(lc, columns)
 	{
-		ColumnDef  *def = lfirst(s);
-
-		if (strcmp(attributeName, def->colname) == 0)
+		if (strcmp(attributeName, lfirst_node(ColumnDef, lc)->colname) == 0)
 			return i;
 
 		i++;
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index b4286cf922..f6cc28a661 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -147,8 +147,8 @@ extern void TupleDescInitEntryCollation(TupleDesc desc,
 										AttrNumber attributeNumber,
 										Oid collationid);
 
-extern TupleDesc BuildDescForRelation(List *schema);
+extern TupleDesc BuildDescForRelation(const List *columns);
 
-extern TupleDesc BuildDescFromLists(List *names, List *types, List *typmods, List *collations);
+extern TupleDesc BuildDescFromLists(const List *names, const List *types, const List *typmods, const List *collations);
 
 #endif							/* TUPDESC_H */
-- 
2.42.0

v3-0004-Add-TupleDescGetDefault.patchtext/plain; charset=UTF-8; name=v3-0004-Add-TupleDescGetDefault.patchDownload
From 0a5593983555e669a9fab8e366c6a36b26ebac14 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:35 +0200
Subject: [PATCH v3 4/9] Add TupleDescGetDefault()

This unifies some repetitive code.

Note: I didn't push the "not found" error message into the new
function, even though all existing callers would be able to make use
of it.  Using the existing error handling as-is would probably require
exposing the Relation type via tupdesc.h, which doesn't seem
desirable.
---
 src/backend/access/common/tupdesc.c  | 25 +++++++++++++++++++++++++
 src/backend/commands/tablecmds.c     | 17 ++---------------
 src/backend/parser/parse_utilcmd.c   | 13 ++-----------
 src/backend/rewrite/rewriteHandler.c | 16 +---------------
 src/include/access/tupdesc.h         |  2 ++
 5 files changed, 32 insertions(+), 41 deletions(-)

diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 253d6c86f8..ce2c7bce85 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -927,3 +927,28 @@ BuildDescFromLists(const List *names, const List *types, const List *typmods, co
 
 	return desc;
 }
+
+/*
+ * Get default expression (or NULL if none) for the given attribute number.
+ */
+Node *
+TupleDescGetDefault(TupleDesc tupdesc, AttrNumber attnum)
+{
+	Node	   *result = NULL;
+
+	if (tupdesc->constr)
+	{
+		AttrDefault *attrdef = tupdesc->constr->defval;
+
+		for (int i = 0; i < tupdesc->constr->num_defval; i++)
+		{
+			if (attrdef[i].adnum == attnum)
+			{
+				result = stringToNode(attrdef[i].adbin);
+				break;
+			}
+		}
+	}
+
+	return result;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ad398e837d..73b8dea81c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2795,22 +2795,9 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 			 */
 			if (attribute->atthasdef)
 			{
-				Node	   *this_default = NULL;
+				Node	   *this_default;
 
-				/* Find default in constraint structure */
-				if (constr != NULL)
-				{
-					AttrDefault *attrdef = constr->defval;
-
-					for (int i = 0; i < constr->num_defval; i++)
-					{
-						if (attrdef[i].adnum == parent_attno)
-						{
-							this_default = stringToNode(attrdef[i].adbin);
-							break;
-						}
-					}
-				}
+				this_default = TupleDescGetDefault(tupleDesc, parent_attno);
 				if (this_default == NULL)
 					elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
 						 parent_attno, RelationGetRelationName(relation));
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 55c315f0e2..cf0d432ab1 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1358,20 +1358,11 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 				 (table_like_clause->options & CREATE_TABLE_LIKE_GENERATED) :
 				 (table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS)))
 			{
-				Node	   *this_default = NULL;
-				AttrDefault *attrdef = constr->defval;
+				Node	   *this_default;
 				AlterTableCmd *atsubcmd;
 				bool		found_whole_row;
 
-				/* Find default in constraint structure */
-				for (int i = 0; i < constr->num_defval; i++)
-				{
-					if (attrdef[i].adnum == parent_attno)
-					{
-						this_default = stringToNode(attrdef[i].adbin);
-						break;
-					}
-				}
+				this_default = TupleDescGetDefault(tupleDesc, parent_attno);
 				if (this_default == NULL)
 					elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
 						 parent_attno, RelationGetRelationName(relation));
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b486ab559a..41a362310a 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1246,21 +1246,7 @@ build_column_default(Relation rel, int attrno)
 	 */
 	if (att_tup->atthasdef)
 	{
-		if (rd_att->constr && rd_att->constr->num_defval > 0)
-		{
-			AttrDefault *defval = rd_att->constr->defval;
-			int			ndef = rd_att->constr->num_defval;
-
-			while (--ndef >= 0)
-			{
-				if (attrno == defval[ndef].adnum)
-				{
-					/* Found it, convert string representation to node tree. */
-					expr = stringToNode(defval[ndef].adbin);
-					break;
-				}
-			}
-		}
+		expr = TupleDescGetDefault(rd_att, attrno);
 		if (expr == NULL)
 			elog(ERROR, "default expression not found for attribute %d of relation \"%s\"",
 				 attrno, RelationGetRelationName(rel));
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index f6cc28a661..ffd2874ee3 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -151,4 +151,6 @@ extern TupleDesc BuildDescForRelation(const List *columns);
 
 extern TupleDesc BuildDescFromLists(const List *names, const List *types, const List *typmods, const List *collations);
 
+extern Node *TupleDescGetDefault(TupleDesc tupdesc, AttrNumber attnum);
+
 #endif							/* TUPDESC_H */
-- 
2.42.0

v3-0005-Push-attidentity-and-attgenerated-handling-into-B.patchtext/plain; charset=UTF-8; name=v3-0005-Push-attidentity-and-attgenerated-handling-into-B.patchDownload
From 402174806f36bb1a240f5afbeacad3ffd9292a9a Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:35 +0200
Subject: [PATCH v3 5/9] Push attidentity and attgenerated handling into
 BuildDescForRelation()

Previously, this was handled by the callers separately, but it can be
trivially moved into BuildDescForRelation() so that it is handled in a
central place.
---
 src/backend/access/common/tupdesc.c | 2 ++
 src/backend/commands/tablecmds.c    | 2 --
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index ce2c7bce85..c2e7b14c31 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -856,6 +856,8 @@ BuildDescForRelation(const List *columns)
 		has_not_null |= entry->is_not_null;
 		att->attislocal = entry->is_local;
 		att->attinhcount = entry->inhcount;
+		att->attidentity = entry->identity;
+		att->attgenerated = entry->generated;
 	}
 
 	if (has_not_null)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 73b8dea81c..60ede984e0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -941,8 +941,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			attr->atthasdef = true;
 		}
 
-		attr->attidentity = colDef->identity;
-		attr->attgenerated = colDef->generated;
 		attr->attcompression = GetAttributeCompression(attr->atttypid, colDef->compression);
 		if (colDef->storage_name)
 			attr->attstorage = GetAttributeStorage(attr->atttypid, colDef->storage_name);
-- 
2.42.0

v3-0006-Move-BuildDescForRelation-from-tupdesc.c-to-table.patchtext/plain; charset=UTF-8; name=v3-0006-Move-BuildDescForRelation-from-tupdesc.c-to-table.patchDownload
From ad08dbbb459683da6206afb5cdc11164bbb94fc2 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:35 +0200
Subject: [PATCH v3 6/9] Move BuildDescForRelation() from tupdesc.c to
 tablecmds.c

BuildDescForRelation() main job is to convert ColumnDef lists to
pg_attribute/tuple descriptor arrays, which is really mostly an
internal subroutine of DefineRelation() and some related functions,
which is more the remit of tablecmds.c and doesn't have much to do
with the basic tuple descriptor interfaces in tupdesc.c.  This is also
supported by observing the header includes we can remove in tupdesc.c.
By moving it over, we can also (in the future) make
BuildDescForRelation() use more internals of tablecmds.c that are not
sensible to be exposed in tupdesc.c.
---
 src/backend/access/common/tupdesc.c | 109 +---------------------------
 src/backend/commands/tablecmds.c    | 102 ++++++++++++++++++++++++++
 src/include/access/tupdesc.h        |   2 -
 src/include/commands/tablecmds.h    |   2 +
 4 files changed, 105 insertions(+), 110 deletions(-)

diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index c2e7b14c31..d119cfafb5 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -25,9 +25,6 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
 #include "common/hashfn.h"
-#include "miscadmin.h"
-#include "parser/parse_type.h"
-#include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/resowner_private.h"
@@ -778,109 +775,6 @@ TupleDescInitEntryCollation(TupleDesc desc,
 	TupleDescAttr(desc, attributeNumber - 1)->attcollation = collationid;
 }
 
-
-/*
- * BuildDescForRelation
- *
- * Given a list of ColumnDef nodes, build a TupleDesc.
- *
- * Note: tdtypeid will need to be filled in later on.
- */
-TupleDesc
-BuildDescForRelation(const List *columns)
-{
-	int			natts;
-	AttrNumber	attnum;
-	ListCell   *l;
-	TupleDesc	desc;
-	bool		has_not_null;
-	char	   *attname;
-	Oid			atttypid;
-	int32		atttypmod;
-	Oid			attcollation;
-	int			attdim;
-
-	/*
-	 * allocate a new tuple descriptor
-	 */
-	natts = list_length(columns);
-	desc = CreateTemplateTupleDesc(natts);
-	has_not_null = false;
-
-	attnum = 0;
-
-	foreach(l, columns)
-	{
-		ColumnDef  *entry = lfirst(l);
-		AclResult	aclresult;
-		Form_pg_attribute att;
-
-		/*
-		 * for each entry in the list, get the name and type information from
-		 * the list and have TupleDescInitEntry fill in the attribute
-		 * information we need.
-		 */
-		attnum++;
-
-		attname = entry->colname;
-		typenameTypeIdAndMod(NULL, entry->typeName, &atttypid, &atttypmod);
-
-		aclresult = object_aclcheck(TypeRelationId, atttypid, GetUserId(), ACL_USAGE);
-		if (aclresult != ACLCHECK_OK)
-			aclcheck_error_type(aclresult, atttypid);
-
-		attcollation = GetColumnDefCollation(NULL, entry, atttypid);
-		attdim = list_length(entry->typeName->arrayBounds);
-		if (attdim > PG_INT16_MAX)
-			ereport(ERROR,
-					errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-					errmsg("too many array dimensions"));
-
-		if (entry->typeName->setof)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-					 errmsg("column \"%s\" cannot be declared SETOF",
-							attname)));
-
-		TupleDescInitEntry(desc, attnum, attname,
-						   atttypid, atttypmod, attdim);
-		att = TupleDescAttr(desc, attnum - 1);
-
-		/* Override TupleDescInitEntry's settings as requested */
-		TupleDescInitEntryCollation(desc, attnum, attcollation);
-		if (entry->storage)
-			att->attstorage = entry->storage;
-
-		/* Fill in additional stuff not handled by TupleDescInitEntry */
-		att->attnotnull = entry->is_not_null;
-		has_not_null |= entry->is_not_null;
-		att->attislocal = entry->is_local;
-		att->attinhcount = entry->inhcount;
-		att->attidentity = entry->identity;
-		att->attgenerated = entry->generated;
-	}
-
-	if (has_not_null)
-	{
-		TupleConstr *constr = (TupleConstr *) palloc0(sizeof(TupleConstr));
-
-		constr->has_not_null = true;
-		constr->has_generated_stored = false;
-		constr->defval = NULL;
-		constr->missing = NULL;
-		constr->num_defval = 0;
-		constr->check = NULL;
-		constr->num_check = 0;
-		desc->constr = constr;
-	}
-	else
-	{
-		desc->constr = NULL;
-	}
-
-	return desc;
-}
-
 /*
  * BuildDescFromLists
  *
@@ -889,8 +783,7 @@ BuildDescForRelation(const List *columns)
  *
  * No constraints are generated.
  *
- * This is essentially a cut-down version of BuildDescForRelation for use
- * with functions returning RECORD.
+ * This is for use with functions returning RECORD.
  */
 TupleDesc
 BuildDescFromLists(const List *names, const List *types, const List *typmods, const List *collations)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 60ede984e0..7bd73eb379 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1277,6 +1277,108 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	return address;
 }
 
+/*
+ * BuildDescForRelation
+ *
+ * Given a list of ColumnDef nodes, build a TupleDesc.
+ *
+ * Note: tdtypeid will need to be filled in later on.
+ */
+TupleDesc
+BuildDescForRelation(const List *columns)
+{
+	int			natts;
+	AttrNumber	attnum;
+	ListCell   *l;
+	TupleDesc	desc;
+	bool		has_not_null;
+	char	   *attname;
+	Oid			atttypid;
+	int32		atttypmod;
+	Oid			attcollation;
+	int			attdim;
+
+	/*
+	 * allocate a new tuple descriptor
+	 */
+	natts = list_length(columns);
+	desc = CreateTemplateTupleDesc(natts);
+	has_not_null = false;
+
+	attnum = 0;
+
+	foreach(l, columns)
+	{
+		ColumnDef  *entry = lfirst(l);
+		AclResult	aclresult;
+		Form_pg_attribute att;
+
+		/*
+		 * for each entry in the list, get the name and type information from
+		 * the list and have TupleDescInitEntry fill in the attribute
+		 * information we need.
+		 */
+		attnum++;
+
+		attname = entry->colname;
+		typenameTypeIdAndMod(NULL, entry->typeName, &atttypid, &atttypmod);
+
+		aclresult = object_aclcheck(TypeRelationId, atttypid, GetUserId(), ACL_USAGE);
+		if (aclresult != ACLCHECK_OK)
+			aclcheck_error_type(aclresult, atttypid);
+
+		attcollation = GetColumnDefCollation(NULL, entry, atttypid);
+		attdim = list_length(entry->typeName->arrayBounds);
+		if (attdim > PG_INT16_MAX)
+			ereport(ERROR,
+					errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+					errmsg("too many array dimensions"));
+
+		if (entry->typeName->setof)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("column \"%s\" cannot be declared SETOF",
+							attname)));
+
+		TupleDescInitEntry(desc, attnum, attname,
+						   atttypid, atttypmod, attdim);
+		att = TupleDescAttr(desc, attnum - 1);
+
+		/* Override TupleDescInitEntry's settings as requested */
+		TupleDescInitEntryCollation(desc, attnum, attcollation);
+		if (entry->storage)
+			att->attstorage = entry->storage;
+
+		/* Fill in additional stuff not handled by TupleDescInitEntry */
+		att->attnotnull = entry->is_not_null;
+		has_not_null |= entry->is_not_null;
+		att->attislocal = entry->is_local;
+		att->attinhcount = entry->inhcount;
+		att->attidentity = entry->identity;
+		att->attgenerated = entry->generated;
+	}
+
+	if (has_not_null)
+	{
+		TupleConstr *constr = (TupleConstr *) palloc0(sizeof(TupleConstr));
+
+		constr->has_not_null = true;
+		constr->has_generated_stored = false;
+		constr->defval = NULL;
+		constr->missing = NULL;
+		constr->num_defval = 0;
+		constr->check = NULL;
+		constr->num_check = 0;
+		desc->constr = constr;
+	}
+	else
+	{
+		desc->constr = NULL;
+	}
+
+	return desc;
+}
+
 /*
  * Emit the right error or warning message for a "DROP" command issued on a
  * non-existent relation
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index ffd2874ee3..d833d5f2e1 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -147,8 +147,6 @@ extern void TupleDescInitEntryCollation(TupleDesc desc,
 										AttrNumber attributeNumber,
 										Oid collationid);
 
-extern TupleDesc BuildDescForRelation(const List *columns);
-
 extern TupleDesc BuildDescFromLists(const List *names, const List *types, const List *typmods, const List *collations);
 
 extern Node *TupleDescGetDefault(TupleDesc tupdesc, AttrNumber attnum);
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 16b6126669..a9c6825601 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -27,6 +27,8 @@ struct AlterTableUtilityContext;	/* avoid including tcop/utility.h here */
 extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 									ObjectAddress *typaddress, const char *queryString);
 
+extern TupleDesc BuildDescForRelation(const List *columns);
+
 extern void RemoveRelations(DropStmt *drop);
 
 extern Oid	AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode);
-- 
2.42.0

v3-0007-Push-attcompression-and-attstorage-handling-into-.patchtext/plain; charset=UTF-8; name=v3-0007-Push-attcompression-and-attstorage-handling-into-.patchDownload
From 65d2d76d52b049b2e13a16018291225d844cefe0 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:35 +0200
Subject: [PATCH v3 7/9] Push attcompression and attstorage handling into
 BuildDescForRelation()

This was previously handled by the callers but it can be moved into a
common place.
---
 src/backend/commands/tablecmds.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7bd73eb379..8820738d91 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -940,10 +940,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			cookedDefaults = lappend(cookedDefaults, cooked);
 			attr->atthasdef = true;
 		}
-
-		attr->attcompression = GetAttributeCompression(attr->atttypid, colDef->compression);
-		if (colDef->storage_name)
-			attr->attstorage = GetAttributeStorage(attr->atttypid, colDef->storage_name);
 	}
 
 	/*
@@ -1346,8 +1342,6 @@ BuildDescForRelation(const List *columns)
 
 		/* Override TupleDescInitEntry's settings as requested */
 		TupleDescInitEntryCollation(desc, attnum, attcollation);
-		if (entry->storage)
-			att->attstorage = entry->storage;
 
 		/* Fill in additional stuff not handled by TupleDescInitEntry */
 		att->attnotnull = entry->is_not_null;
@@ -1356,6 +1350,11 @@ BuildDescForRelation(const List *columns)
 		att->attinhcount = entry->inhcount;
 		att->attidentity = entry->identity;
 		att->attgenerated = entry->generated;
+		att->attcompression = GetAttributeCompression(att->atttypid, entry->compression);
+		if (entry->storage)
+			att->attstorage = entry->storage;
+		else if (entry->storage_name)
+			att->attstorage = GetAttributeStorage(att->atttypid, entry->storage_name);
 	}
 
 	if (has_not_null)
-- 
2.42.0

v3-0008-Refactor-ATExecAddColumn-to-use-BuildDescForRelat.patchtext/plain; charset=UTF-8; name=v3-0008-Refactor-ATExecAddColumn-to-use-BuildDescForRelat.patchDownload
From 5b8ffd08e58eda3176f6c6caf5b64c74a902cda4 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:35 +0200
Subject: [PATCH v3 8/9] Refactor ATExecAddColumn() to use
 BuildDescForRelation()

BuildDescForRelation() has all the knowledge for converting a
ColumnDef into pg_attribute/tuple descriptor.  ATExecAddColumn() can
make use of that, instead of duplicating all that logic.  We just pass
a one-element list of ColumnDef and we get back exactly the data
structure we need.  Note that we don't even need to touch
BuildDescForRelation() to make this work.
---
 src/backend/commands/tablecmds.c | 89 ++++++++------------------------
 1 file changed, 22 insertions(+), 67 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8820738d91..d8d7ba904d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -6968,22 +6968,15 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Relation	pgclass,
 				attrdesc;
 	HeapTuple	reltup;
-	FormData_pg_attribute attribute;
+	Form_pg_attribute attribute;
 	int			newattnum;
 	char		relkind;
-	HeapTuple	typeTuple;
-	Oid			typeOid;
-	int32		typmod;
-	Oid			collOid;
-	Form_pg_type tform;
 	Expr	   *defval;
 	List	   *children;
 	ListCell   *child;
 	AlterTableCmd *childcmd;
-	AclResult	aclresult;
 	ObjectAddress address;
 	TupleDesc	tupdesc;
-	FormData_pg_attribute *aattr[] = {&attribute};
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -7105,59 +7098,21 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				 errmsg("tables can have at most %d columns",
 						MaxHeapAttributeNumber)));
 
-	typeTuple = typenameType(NULL, colDef->typeName, &typmod);
-	tform = (Form_pg_type) GETSTRUCT(typeTuple);
-	typeOid = tform->oid;
+	/*
+	 * Construct new attribute's pg_attribute entry.
+	 */
+	tupdesc = BuildDescForRelation(list_make1(colDef));
 
-	aclresult = object_aclcheck(TypeRelationId, typeOid, GetUserId(), ACL_USAGE);
-	if (aclresult != ACLCHECK_OK)
-		aclcheck_error_type(aclresult, typeOid);
+	attribute = TupleDescAttr(tupdesc, 0);
 
-	collOid = GetColumnDefCollation(NULL, colDef, typeOid);
+	/* Fix up attribute number */
+	attribute->attnum = newattnum;
 
 	/* make sure datatype is legal for a column */
-	CheckAttributeType(colDef->colname, typeOid, collOid,
+	CheckAttributeType(NameStr(attribute->attname), attribute->atttypid, attribute->attcollation,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   0);
 
-	/*
-	 * Construct new attribute's pg_attribute entry.  (Variable-length fields
-	 * are handled by InsertPgAttributeTuples().)
-	 */
-	attribute.attrelid = myrelid;
-	namestrcpy(&(attribute.attname), colDef->colname);
-	attribute.atttypid = typeOid;
-	attribute.attstattarget = -1;
-	attribute.attlen = tform->typlen;
-	attribute.attnum = newattnum;
-	if (list_length(colDef->typeName->arrayBounds) > PG_INT16_MAX)
-		ereport(ERROR,
-				errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-				errmsg("too many array dimensions"));
-	attribute.attndims = list_length(colDef->typeName->arrayBounds);
-	attribute.atttypmod = typmod;
-	attribute.attbyval = tform->typbyval;
-	attribute.attalign = tform->typalign;
-	if (colDef->storage_name)
-		attribute.attstorage = GetAttributeStorage(typeOid, colDef->storage_name);
-	else
-		attribute.attstorage = tform->typstorage;
-	attribute.attcompression = GetAttributeCompression(typeOid,
-													   colDef->compression);
-	attribute.attnotnull = colDef->is_not_null;
-	attribute.atthasdef = false;
-	attribute.atthasmissing = false;
-	attribute.attidentity = colDef->identity;
-	attribute.attgenerated = colDef->generated;
-	attribute.attisdropped = false;
-	attribute.attislocal = colDef->is_local;
-	attribute.attinhcount = colDef->inhcount;
-	attribute.attcollation = collOid;
-
-	ReleaseSysCache(typeTuple);
-
-	tupdesc = CreateTupleDesc(lengthof(aattr), (FormData_pg_attribute **) &aattr);
-
 	InsertPgAttributeTuples(attrdesc, tupdesc, myrelid, NULL, NULL);
 
 	table_close(attrdesc, RowExclusiveLock);
@@ -7187,7 +7142,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		RawColumnDefault *rawEnt;
 
 		rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
-		rawEnt->attnum = attribute.attnum;
+		rawEnt->attnum = attribute->attnum;
 		rawEnt->raw_default = copyObject(colDef->raw_default);
 
 		/*
@@ -7261,7 +7216,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			NextValueExpr *nve = makeNode(NextValueExpr);
 
 			nve->seqid = RangeVarGetRelid(colDef->identitySequence, NoLock, false);
-			nve->typeId = typeOid;
+			nve->typeId = attribute->atttypid;
 
 			defval = (Expr *) nve;
 
@@ -7269,23 +7224,23 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 		}
 		else
-			defval = (Expr *) build_column_default(rel, attribute.attnum);
+			defval = (Expr *) build_column_default(rel, attribute->attnum);
 
-		if (!defval && DomainHasConstraints(typeOid))
+		if (!defval && DomainHasConstraints(attribute->atttypid))
 		{
 			Oid			baseTypeId;
 			int32		baseTypeMod;
 			Oid			baseTypeColl;
 
-			baseTypeMod = typmod;
-			baseTypeId = getBaseTypeAndTypmod(typeOid, &baseTypeMod);
+			baseTypeMod = attribute->atttypmod;
+			baseTypeId = getBaseTypeAndTypmod(attribute->atttypid, &baseTypeMod);
 			baseTypeColl = get_typcollation(baseTypeId);
 			defval = (Expr *) makeNullConst(baseTypeId, baseTypeMod, baseTypeColl);
 			defval = (Expr *) coerce_to_target_type(NULL,
 													(Node *) defval,
 													baseTypeId,
-													typeOid,
-													typmod,
+													attribute->atttypid,
+													attribute->atttypmod,
 													COERCION_ASSIGNMENT,
 													COERCE_IMPLICIT_CAST,
 													-1);
@@ -7298,17 +7253,17 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			NewColumnValue *newval;
 
 			newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
-			newval->attnum = attribute.attnum;
+			newval->attnum = attribute->attnum;
 			newval->expr = expression_planner(defval);
 			newval->is_generated = (colDef->generated != '\0');
 
 			tab->newvals = lappend(tab->newvals, newval);
 		}
 
-		if (DomainHasConstraints(typeOid))
+		if (DomainHasConstraints(attribute->atttypid))
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 
-		if (!TupleDescAttr(rel->rd_att, attribute.attnum - 1)->atthasmissing)
+		if (!TupleDescAttr(rel->rd_att, attribute->attnum - 1)->atthasmissing)
 		{
 			/*
 			 * If the new column is NOT NULL, and there is no missing value,
@@ -7321,8 +7276,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/*
 	 * Add needed dependency entries for the new column.
 	 */
-	add_column_datatype_dependency(myrelid, newattnum, attribute.atttypid);
-	add_column_collation_dependency(myrelid, newattnum, attribute.attcollation);
+	add_column_datatype_dependency(myrelid, newattnum, attribute->atttypid);
+	add_column_collation_dependency(myrelid, newattnum, attribute->attcollation);
 
 	/*
 	 * Propagate to children as appropriate.  Unlike most other ALTER
-- 
2.42.0

v3-0009-MergeAttributes-convert-pg_attribute-back-to-Colu.patchtext/plain; charset=UTF-8; name=v3-0009-MergeAttributes-convert-pg_attribute-back-to-Colu.patchDownload
From a1000260efb736f2c1e6c161a1475676228a8be8 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 12 Jul 2023 16:12:35 +0200
Subject: [PATCH v3 9/9] MergeAttributes: convert pg_attribute back to
 ColumnDef before comparing

MergeAttributes() has a loop to merge multiple inheritance parents
into a column column definition.  The merged-so-far definition is
stored in a ColumnDef node.  If we have to merge columns from multiple
inheritance parents (if the name matches), then we have to check
whether various column properties (type, collation, etc.) match.  The
current code extracts the pg_attribute value of the
currently-considered inheritance parent and compares it against the
merge-so-far ColumnDef value.  If the currently considered column
doesn't match any previously inherited column, we make a new ColumnDef
node from the pg_attribute information and add it to the column list.

This patch rearranges this so that we create the ColumnDef node first
in either case.  Then the code that checks the column properties for
compatibility compares ColumnDef against ColumnDef (instead of
ColumnDef against pg_attribute).  This matches the code more symmetric
and easier to follow.  Also, later in MergeAttributes(), there is a
similar but separate loop that merges the new local column definition
with the combination of the inheritance parents established in the
first loop.  That comparison is already ColumnDef-vs-ColumnDef.  With
this change, but of these can use similar looking logic.  (A future
project might be to extract these two sets of code into a common
routine that encodes all the knowledge of whether two column
definitions are compatible.  But this isn't currently straightforward
because we want to give different error messages in the two cases.)
Furthermore, by avoiding the use of Form_pg_attribute here, we make it
easier to make changes in the pg_attribute layout without having to
worry about the local needs of tablecmds.c.

Because MergeAttributes() is hugely long, it's sometimes hard to know
where in the function you are currently looking.  To help with that, I
also renamed some variables to make it clearer where you are currently
looking.  The first look is "prev" vs. "new", the second loop is "inh"
vs. "new".
---
 src/backend/commands/tablecmds.c | 181 ++++++++++++++++---------------
 1 file changed, 94 insertions(+), 87 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d8d7ba904d..9b9b5ad5e8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2697,7 +2697,8 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 														parent_attno - 1);
 			char	   *attributeName = NameStr(attribute->attname);
 			int			exist_attno;
-			ColumnDef  *def;
+			ColumnDef  *newdef;
+			ColumnDef  *savedef;
 
 			/*
 			 * Ignore dropped columns in the parent.
@@ -2706,14 +2707,30 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				continue;		/* leave newattmap->attnums entry as zero */
 
 			/*
-			 * Does it conflict with some previously inherited column?
+			 * Create new column definition
+			 */
+			newdef = makeColumnDef(attributeName, attribute->atttypid,
+								   attribute->atttypmod, attribute->attcollation);
+			newdef->storage = attribute->attstorage;
+			newdef->generated = attribute->attgenerated;
+			if (CompressionMethodIsValid(attribute->attcompression))
+				newdef->compression =
+					pstrdup(GetCompressionMethodName(attribute->attcompression));
+
+			/*
+			 * Does it match some previously considered column from another
+			 * parent?
 			 */
 			exist_attno = findAttrByName(attributeName, inh_columns);
 			if (exist_attno > 0)
 			{
-				Oid			defTypeId;
-				int32		deftypmod;
-				Oid			defCollId;
+				ColumnDef  *prevdef;
+				Oid			prevtypeid,
+							newtypeid;
+				int32		prevtypmod,
+							newtypmod;
+				Oid			prevcollid,
+							newcollid;
 
 				/*
 				 * Yes, try to merge the two column definitions.
@@ -2721,68 +2738,61 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				ereport(NOTICE,
 						(errmsg("merging multiple inherited definitions of column \"%s\"",
 								attributeName)));
-				def = (ColumnDef *) list_nth(inh_columns, exist_attno - 1);
+				prevdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
 
 				/*
 				 * Must have the same type and typmod
 				 */
-				typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
-				if (defTypeId != attribute->atttypid ||
-					deftypmod != attribute->atttypmod)
+				typenameTypeIdAndMod(NULL, prevdef->typeName, &prevtypeid, &prevtypmod);
+				typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
+				if (prevtypeid != newtypeid || prevtypmod != newtypmod)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("inherited column \"%s\" has a type conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   format_type_with_typemod(defTypeId,
-																deftypmod),
-									   format_type_with_typemod(attribute->atttypid,
-																attribute->atttypmod))));
+									   format_type_with_typemod(prevtypeid, prevtypmod),
+									   format_type_with_typemod(newtypeid, newtypmod))));
 
 				/*
 				 * Must have the same collation
 				 */
-				defCollId = GetColumnDefCollation(NULL, def, defTypeId);
-				if (defCollId != attribute->attcollation)
+				prevcollid = GetColumnDefCollation(NULL, prevdef, prevtypeid);
+				newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
+				if (prevcollid != newcollid)
 					ereport(ERROR,
 							(errcode(ERRCODE_COLLATION_MISMATCH),
 							 errmsg("inherited column \"%s\" has a collation conflict",
 									attributeName),
 							 errdetail("\"%s\" versus \"%s\"",
-									   get_collation_name(defCollId),
-									   get_collation_name(attribute->attcollation))));
+									   get_collation_name(prevcollid),
+									   get_collation_name(newcollid))));
 
 				/*
 				 * Copy/check storage parameter
 				 */
-				if (def->storage == 0)
-					def->storage = attribute->attstorage;
-				else if (def->storage != attribute->attstorage)
+				if (prevdef->storage == 0)
+					prevdef->storage = newdef->storage;
+				else if (prevdef->storage != newdef->storage)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("inherited column \"%s\" has a storage parameter conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   storage_name(def->storage),
-									   storage_name(attribute->attstorage))));
+									   storage_name(prevdef->storage),
+									   storage_name(newdef->storage))));
 
 				/*
 				 * Copy/check compression parameter
 				 */
-				if (CompressionMethodIsValid(attribute->attcompression))
-				{
-					const char *compression =
-						GetCompressionMethodName(attribute->attcompression);
-
-					if (def->compression == NULL)
-						def->compression = pstrdup(compression);
-					else if (strcmp(def->compression, compression) != 0)
-						ereport(ERROR,
-								(errcode(ERRCODE_DATATYPE_MISMATCH),
-								 errmsg("column \"%s\" has a compression method conflict",
-										attributeName),
-								 errdetail("%s versus %s", def->compression, compression)));
-				}
+				if (prevdef->compression == NULL)
+					prevdef->compression = newdef->compression;
+				else if (strcmp(prevdef->compression, newdef->compression) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("column \"%s\" has a compression method conflict",
+									attributeName),
+							 errdetail("%s versus %s", prevdef->compression, newdef->compression)));
 
 				/*
 				 * In regular inheritance, columns in the parent's primary key
@@ -2816,12 +2826,12 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				if (bms_is_member(parent_attno, nncols) ||
 					bms_is_member(parent_attno - FirstLowInvalidHeapAttributeNumber,
 								  pkattrs))
-					def->is_not_null = true;
+					newdef->is_not_null = true;
 
 				/*
 				 * Check for GENERATED conflicts
 				 */
-				if (def->generated != attribute->attgenerated)
+				if (prevdef->generated != newdef->generated)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("inherited column \"%s\" has a generation conflict",
@@ -2831,34 +2841,30 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 * Default and other constraints are handled below
 				 */
 
-				def->inhcount++;
-				if (def->inhcount < 0)
+				prevdef->inhcount++;
+				if (prevdef->inhcount < 0)
 					ereport(ERROR,
 							errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 							errmsg("too many inheritance parents"));
 
 				newattmap->attnums[parent_attno - 1] = exist_attno;
+
+				/* remember for default processing below */
+				savedef = prevdef;
 			}
 			else
 			{
 				/*
 				 * No, create a new inherited column
 				 */
-				def = makeColumnDef(attributeName, attribute->atttypid,
-									attribute->atttypmod, attribute->attcollation);
-				def->inhcount = 1;
-				def->is_local = false;
+				newdef->inhcount = 1;
+				newdef->is_local = false;
 				/* mark attnotnull if parent has it and it's not NO INHERIT */
 				if (bms_is_member(parent_attno, nncols) ||
 					bms_is_member(parent_attno - FirstLowInvalidHeapAttributeNumber,
 								  pkattrs))
-					def->is_not_null = true;
-				def->storage = attribute->attstorage;
-				def->generated = attribute->attgenerated;
-				if (CompressionMethodIsValid(attribute->attcompression))
-					def->compression =
-						pstrdup(GetCompressionMethodName(attribute->attcompression));
-				inh_columns = lappend(inh_columns, def);
+					newdef->is_not_null = true;
+				inh_columns = lappend(inh_columns, newdef);
 				newattmap->attnums[parent_attno - 1] = ++child_attno;
 
 				/*
@@ -2887,6 +2893,9 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 
 					nnconstraints = lappend(nnconstraints, nn);
 				}
+
+				/* remember for default processing below */
+				savedef = newdef;
 			}
 
 			/*
@@ -2908,7 +2917,7 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 * all the inherited default expressions for the moment.
 				 */
 				inherited_defaults = lappend(inherited_defaults, this_default);
-				cols_with_defaults = lappend(cols_with_defaults, def);
+				cols_with_defaults = lappend(cols_with_defaults, savedef);
 			}
 		}
 
@@ -3046,17 +3055,17 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 			newcol_attno++;
 
 			/*
-			 * Does it conflict with some previously inherited column?
+			 * Does it match some inherited column?
 			 */
 			exist_attno = findAttrByName(attributeName, inh_columns);
 			if (exist_attno > 0)
 			{
-				ColumnDef  *def;
-				Oid			defTypeId,
-							newTypeId;
-				int32		deftypmod,
+				ColumnDef  *inhdef;
+				Oid			inhtypeid,
+							newtypeid;
+				int32		inhtypmod,
 							newtypmod;
-				Oid			defcollid,
+				Oid			inhcollid,
 							newcollid;
 
 				/*
@@ -3076,77 +3085,75 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 					ereport(NOTICE,
 							(errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
 							 errdetail("User-specified column moved to the position of the inherited column.")));
-				def = (ColumnDef *) list_nth(inh_columns, exist_attno - 1);
+				inhdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
 
 				/*
 				 * Must have the same type and typmod
 				 */
-				typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
-				typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
-				if (defTypeId != newTypeId || deftypmod != newtypmod)
+				typenameTypeIdAndMod(NULL, inhdef->typeName, &inhtypeid, &inhtypmod);
+				typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
+				if (inhtypeid != newtypeid || inhtypmod != newtypmod)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("column \"%s\" has a type conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   format_type_with_typemod(defTypeId,
-																deftypmod),
-									   format_type_with_typemod(newTypeId,
-																newtypmod))));
+									   format_type_with_typemod(inhtypeid, inhtypmod),
+									   format_type_with_typemod(newtypeid, newtypmod))));
 
 				/*
 				 * Must have the same collation
 				 */
-				defcollid = GetColumnDefCollation(NULL, def, defTypeId);
-				newcollid = GetColumnDefCollation(NULL, newdef, newTypeId);
-				if (defcollid != newcollid)
+				inhcollid = GetColumnDefCollation(NULL, inhdef, inhtypeid);
+				newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
+				if (inhcollid != newcollid)
 					ereport(ERROR,
 							(errcode(ERRCODE_COLLATION_MISMATCH),
 							 errmsg("column \"%s\" has a collation conflict",
 									attributeName),
 							 errdetail("\"%s\" versus \"%s\"",
-									   get_collation_name(defcollid),
+									   get_collation_name(inhcollid),
 									   get_collation_name(newcollid))));
 
 				/*
 				 * Identity is never inherited.  The new column can have an
 				 * identity definition, so we always just take that one.
 				 */
-				def->identity = newdef->identity;
+				inhdef->identity = newdef->identity;
 
 				/*
 				 * Copy storage parameter
 				 */
-				if (def->storage == 0)
-					def->storage = newdef->storage;
-				else if (newdef->storage != 0 && def->storage != newdef->storage)
+				if (inhdef->storage == 0)
+					inhdef->storage = newdef->storage;
+				else if (newdef->storage != 0 && inhdef->storage != newdef->storage)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("column \"%s\" has a storage parameter conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   storage_name(def->storage),
+									   storage_name(inhdef->storage),
 									   storage_name(newdef->storage))));
 
 				/*
 				 * Copy compression parameter
 				 */
-				if (def->compression == NULL)
-					def->compression = newdef->compression;
+				if (inhdef->compression == NULL)
+					inhdef->compression = newdef->compression;
 				else if (newdef->compression != NULL)
 				{
-					if (strcmp(def->compression, newdef->compression) != 0)
+					if (strcmp(inhdef->compression, newdef->compression) != 0)
 						ereport(ERROR,
 								(errcode(ERRCODE_DATATYPE_MISMATCH),
 								 errmsg("column \"%s\" has a compression method conflict",
 										attributeName),
-								 errdetail("%s versus %s", def->compression, newdef->compression)));
+								 errdetail("%s versus %s", inhdef->compression, newdef->compression)));
 				}
 
 				/*
 				 * Merge of not-null constraints = OR 'em together
 				 */
-				def->is_not_null |= newdef->is_not_null;
+				inhdef->is_not_null |= newdef->is_not_null;
 
 				/*
 				 * Check for conflicts related to generated columns.
@@ -3163,18 +3170,18 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 * it results in being able to override the generation
 				 * expression via UPDATEs through the parent.)
 				 */
-				if (def->generated)
+				if (inhdef->generated)
 				{
 					if (newdef->raw_default && !newdef->generated)
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
 								 errmsg("column \"%s\" inherits from generated column but specifies default",
-										def->colname)));
+										inhdef->colname)));
 					if (newdef->identity)
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
 								 errmsg("column \"%s\" inherits from generated column but specifies identity",
-										def->colname)));
+										inhdef->colname)));
 				}
 				else
 				{
@@ -3182,7 +3189,7 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
 								 errmsg("child column \"%s\" specifies generation expression",
-										def->colname),
+										inhdef->colname),
 								 errhint("A child table column cannot be generated unless its parent column is.")));
 				}
 
@@ -3191,12 +3198,12 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 */
 				if (newdef->raw_default != NULL)
 				{
-					def->raw_default = newdef->raw_default;
-					def->cooked_default = newdef->cooked_default;
+					inhdef->raw_default = newdef->raw_default;
+					inhdef->cooked_default = newdef->cooked_default;
 				}
 
 				/* Mark the column as locally defined */
-				def->is_local = true;
+				inhdef->is_local = true;
 			}
 			else
 			{
-- 
2.42.0

#14Peter Eisentraut
peter@eisentraut.org
In reply to: Peter Eisentraut (#13)
Re: tablecmds.c/MergeAttributes() cleanup

On 19.09.23 15:11, Peter Eisentraut wrote:

Here is an updated version of this patch set.  I resolved some conflicts
and addressed this comment of yours.  I also dropped the one patch with
some catalog header edits that people didn't seem to particularly like.

The patches that are now 0001 through 0004 were previously reviewed and
just held for the not-null constraint patches, I think, so I'll commit
them in a few days if there are no objections.

Patches 0005 through 0007 are also ready in my opinion, but they haven't
really been reviewed, so this would be something for reviewers to focus
on.  (0005 and 0007 are trivial, but they go to together with 0006.)

The remaining 0008 and 0009 were still under discussion and contemplation.

I have committed through 0007, and I'll now close this patch set as
"Committed", and I will (probably) bring back the rest (especially 0008)
as part of a different patch set soon.

#15Peter Eisentraut
peter@eisentraut.org
In reply to: Peter Eisentraut (#14)
2 attachment(s)
Re: tablecmds.c/MergeAttributes() cleanup

On 05.10.23 17:49, Peter Eisentraut wrote:

On 19.09.23 15:11, Peter Eisentraut wrote:

Here is an updated version of this patch set.  I resolved some
conflicts and addressed this comment of yours.  I also dropped the one
patch with some catalog header edits that people didn't seem to
particularly like.

The patches that are now 0001 through 0004 were previously reviewed
and just held for the not-null constraint patches, I think, so I'll
commit them in a few days if there are no objections.

Patches 0005 through 0007 are also ready in my opinion, but they
haven't really been reviewed, so this would be something for reviewers
to focus on.  (0005 and 0007 are trivial, but they go to together with
0006.)

The remaining 0008 and 0009 were still under discussion and
contemplation.

I have committed through 0007, and I'll now close this patch set as
"Committed", and I will (probably) bring back the rest (especially 0008)
as part of a different patch set soon.

After playing with this for, well, 2 months, and considering various
other approaches, I would like to bring back the remaining two patches
in unchanged form.

Especially the (now) first patch "Refactor ATExecAddColumn() to use
BuildDescForRelation()" would be very helpful for facilitating further
refactoring in this area, because it avoids having two essentially
duplicate pieces of code responsible for converting a ColumnDef node
into internal form.

One of your (Álvaro's) comments about this earlier was

Hmm, crazy. I'm not sure I like this, because it seems much too clever.
The number of lines that are deleted is alluring, though.

Maybe it'd be better to create a separate routine that takes a single
ColumnDef and returns the Form_pg_attribute element for it; then use
that in both BuildDescForRelation and ATExecAddColumn.

which was also my thought at the beginning. However, this wouldn't
quite work that way, for several reasons. For instance,
BuildDescForRelation() also needs to keep track of the has_not_null
property across all fields, and so if you split that function up, you
would have to somehow make that an output argument and have the caller
keep track of it. Also, the output of BuildDescForRelation() in
ATExecAddColumn() is passed into InsertPgAttributeTuples(), which
requires a tuple descriptor anyway, so splitting this up into a
per-attribute function would then require ATExecAddColumn() to
re-assemble a tuple descriptor anyway, so this wouldn't save anything.
Also note that ATExecAddColumn() could in theory be enhanced to add more
than one column in one go, so having this code structure in place isn't
inconsistent with that.

The main hackish thing, I suppose, is that we have to fix up the
attribute number after returning from BuildDescForRelation(). I suppose
we could address that by passing in a starting attribute number (or
alternatively maximum existing attribute number) into
BuildDescForRelation(). I think that would be okay; it would probably
be about a wash in terms of code added versus saved.

The (now) second patch is also still of interest to me, but I have since
noticed that I think [0]/messages/by-id/24656cec-d6ef-4d15-8b5b-e8dfc9c833a7@eisentraut.org should be fixed first, but to make that fix
simpler, I would like the first patch from here.

[0]: /messages/by-id/24656cec-d6ef-4d15-8b5b-e8dfc9c833a7@eisentraut.org
/messages/by-id/24656cec-d6ef-4d15-8b5b-e8dfc9c833a7@eisentraut.org

Attachments:

v4-0001-Refactor-ATExecAddColumn-to-use-BuildDescForRelat.patchtext/plain; charset=UTF-8; name=v4-0001-Refactor-ATExecAddColumn-to-use-BuildDescForRelat.patchDownload
From 42615f4c523920c7ae916ba0b1819cc6a5e622b1 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Thu, 5 Oct 2023 16:17:16 +0200
Subject: [PATCH v4 1/2] Refactor ATExecAddColumn() to use
 BuildDescForRelation()

BuildDescForRelation() has all the knowledge for converting a
ColumnDef into pg_attribute/tuple descriptor.  ATExecAddColumn() can
make use of that, instead of duplicating all that logic.  We just pass
a one-element list of ColumnDef and we get back exactly the data
structure we need.  Note that we don't even need to touch
BuildDescForRelation() to make this work.

Discussion: https://www.postgresql.org/message-id/flat/52a125e4-ff9a-95f5-9f61-b87cf447e4da@eisentraut.org
---
 src/backend/commands/tablecmds.c | 89 ++++++++------------------------
 1 file changed, 22 insertions(+), 67 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 6b0a20010e..875cfeaa5a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -6965,22 +6965,15 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Relation	pgclass,
 				attrdesc;
 	HeapTuple	reltup;
-	FormData_pg_attribute attribute;
+	Form_pg_attribute attribute;
 	int			newattnum;
 	char		relkind;
-	HeapTuple	typeTuple;
-	Oid			typeOid;
-	int32		typmod;
-	Oid			collOid;
-	Form_pg_type tform;
 	Expr	   *defval;
 	List	   *children;
 	ListCell   *child;
 	AlterTableCmd *childcmd;
-	AclResult	aclresult;
 	ObjectAddress address;
 	TupleDesc	tupdesc;
-	FormData_pg_attribute *aattr[] = {&attribute};
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
@@ -7102,59 +7095,21 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				 errmsg("tables can have at most %d columns",
 						MaxHeapAttributeNumber)));
 
-	typeTuple = typenameType(NULL, colDef->typeName, &typmod);
-	tform = (Form_pg_type) GETSTRUCT(typeTuple);
-	typeOid = tform->oid;
+	/*
+	 * Construct new attribute's pg_attribute entry.
+	 */
+	tupdesc = BuildDescForRelation(list_make1(colDef));
 
-	aclresult = object_aclcheck(TypeRelationId, typeOid, GetUserId(), ACL_USAGE);
-	if (aclresult != ACLCHECK_OK)
-		aclcheck_error_type(aclresult, typeOid);
+	attribute = TupleDescAttr(tupdesc, 0);
 
-	collOid = GetColumnDefCollation(NULL, colDef, typeOid);
+	/* Fix up attribute number */
+	attribute->attnum = newattnum;
 
 	/* make sure datatype is legal for a column */
-	CheckAttributeType(colDef->colname, typeOid, collOid,
+	CheckAttributeType(NameStr(attribute->attname), attribute->atttypid, attribute->attcollation,
 					   list_make1_oid(rel->rd_rel->reltype),
 					   0);
 
-	/*
-	 * Construct new attribute's pg_attribute entry.  (Variable-length fields
-	 * are handled by InsertPgAttributeTuples().)
-	 */
-	attribute.attrelid = myrelid;
-	namestrcpy(&(attribute.attname), colDef->colname);
-	attribute.atttypid = typeOid;
-	attribute.attstattarget = -1;
-	attribute.attlen = tform->typlen;
-	attribute.attnum = newattnum;
-	if (list_length(colDef->typeName->arrayBounds) > PG_INT16_MAX)
-		ereport(ERROR,
-				errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-				errmsg("too many array dimensions"));
-	attribute.attndims = list_length(colDef->typeName->arrayBounds);
-	attribute.atttypmod = typmod;
-	attribute.attbyval = tform->typbyval;
-	attribute.attalign = tform->typalign;
-	if (colDef->storage_name)
-		attribute.attstorage = GetAttributeStorage(typeOid, colDef->storage_name);
-	else
-		attribute.attstorage = tform->typstorage;
-	attribute.attcompression = GetAttributeCompression(typeOid,
-													   colDef->compression);
-	attribute.attnotnull = colDef->is_not_null;
-	attribute.atthasdef = false;
-	attribute.atthasmissing = false;
-	attribute.attidentity = colDef->identity;
-	attribute.attgenerated = colDef->generated;
-	attribute.attisdropped = false;
-	attribute.attislocal = colDef->is_local;
-	attribute.attinhcount = colDef->inhcount;
-	attribute.attcollation = collOid;
-
-	ReleaseSysCache(typeTuple);
-
-	tupdesc = CreateTupleDesc(lengthof(aattr), (FormData_pg_attribute **) &aattr);
-
 	InsertPgAttributeTuples(attrdesc, tupdesc, myrelid, NULL, NULL);
 
 	table_close(attrdesc, RowExclusiveLock);
@@ -7184,7 +7139,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		RawColumnDefault *rawEnt;
 
 		rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
-		rawEnt->attnum = attribute.attnum;
+		rawEnt->attnum = attribute->attnum;
 		rawEnt->raw_default = copyObject(colDef->raw_default);
 
 		/*
@@ -7258,7 +7213,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			NextValueExpr *nve = makeNode(NextValueExpr);
 
 			nve->seqid = RangeVarGetRelid(colDef->identitySequence, NoLock, false);
-			nve->typeId = typeOid;
+			nve->typeId = attribute->atttypid;
 
 			defval = (Expr *) nve;
 
@@ -7266,23 +7221,23 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 		}
 		else
-			defval = (Expr *) build_column_default(rel, attribute.attnum);
+			defval = (Expr *) build_column_default(rel, attribute->attnum);
 
-		if (!defval && DomainHasConstraints(typeOid))
+		if (!defval && DomainHasConstraints(attribute->atttypid))
 		{
 			Oid			baseTypeId;
 			int32		baseTypeMod;
 			Oid			baseTypeColl;
 
-			baseTypeMod = typmod;
-			baseTypeId = getBaseTypeAndTypmod(typeOid, &baseTypeMod);
+			baseTypeMod = attribute->atttypmod;
+			baseTypeId = getBaseTypeAndTypmod(attribute->atttypid, &baseTypeMod);
 			baseTypeColl = get_typcollation(baseTypeId);
 			defval = (Expr *) makeNullConst(baseTypeId, baseTypeMod, baseTypeColl);
 			defval = (Expr *) coerce_to_target_type(NULL,
 													(Node *) defval,
 													baseTypeId,
-													typeOid,
-													typmod,
+													attribute->atttypid,
+													attribute->atttypmod,
 													COERCION_ASSIGNMENT,
 													COERCE_IMPLICIT_CAST,
 													-1);
@@ -7295,17 +7250,17 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			NewColumnValue *newval;
 
 			newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
-			newval->attnum = attribute.attnum;
+			newval->attnum = attribute->attnum;
 			newval->expr = expression_planner(defval);
 			newval->is_generated = (colDef->generated != '\0');
 
 			tab->newvals = lappend(tab->newvals, newval);
 		}
 
-		if (DomainHasConstraints(typeOid))
+		if (DomainHasConstraints(attribute->atttypid))
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 
-		if (!TupleDescAttr(rel->rd_att, attribute.attnum - 1)->atthasmissing)
+		if (!TupleDescAttr(rel->rd_att, attribute->attnum - 1)->atthasmissing)
 		{
 			/*
 			 * If the new column is NOT NULL, and there is no missing value,
@@ -7318,8 +7273,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/*
 	 * Add needed dependency entries for the new column.
 	 */
-	add_column_datatype_dependency(myrelid, newattnum, attribute.atttypid);
-	add_column_collation_dependency(myrelid, newattnum, attribute.attcollation);
+	add_column_datatype_dependency(myrelid, newattnum, attribute->atttypid);
+	add_column_collation_dependency(myrelid, newattnum, attribute->attcollation);
 
 	/*
 	 * Propagate to children as appropriate.  Unlike most other ALTER

base-commit: 7636725b922c8cd68f21d040f3542d3bce9c68a4
-- 
2.43.0

v4-0002-MergeAttributes-convert-pg_attribute-back-to-Colu.patchtext/plain; charset=UTF-8; name=v4-0002-MergeAttributes-convert-pg_attribute-back-to-Colu.patchDownload
From 8fe0e8157d932c33ab06e0eccdfa0e2634e27935 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Thu, 5 Oct 2023 16:17:16 +0200
Subject: [PATCH v4 2/2] MergeAttributes: convert pg_attribute back to
 ColumnDef before comparing

MergeAttributes() has a loop to merge multiple inheritance parents
into a column column definition.  The merged-so-far definition is
stored in a ColumnDef node.  If we have to merge columns from multiple
inheritance parents (if the name matches), then we have to check
whether various column properties (type, collation, etc.) match.  The
current code extracts the pg_attribute value of the
currently-considered inheritance parent and compares it against the
merge-so-far ColumnDef value.  If the currently considered column
doesn't match any previously inherited column, we make a new ColumnDef
node from the pg_attribute information and add it to the column list.

This patch rearranges this so that we create the ColumnDef node first
in either case.  Then the code that checks the column properties for
compatibility compares ColumnDef against ColumnDef (instead of
ColumnDef against pg_attribute).  This matches the code more symmetric
and easier to follow.  Also, later in MergeAttributes(), there is a
similar but separate loop that merges the new local column definition
with the combination of the inheritance parents established in the
first loop.  That comparison is already ColumnDef-vs-ColumnDef.  With
this change, but of these can use similar looking logic.  (A future
project might be to extract these two sets of code into a common
routine that encodes all the knowledge of whether two column
definitions are compatible.  But this isn't currently straightforward
because we want to give different error messages in the two cases.)
Furthermore, by avoiding the use of Form_pg_attribute here, we make it
easier to make changes in the pg_attribute layout without having to
worry about the local needs of tablecmds.c.

Because MergeAttributes() is hugely long, it's sometimes hard to know
where in the function you are currently looking.  To help with that, I
also renamed some variables to make it clearer where you are currently
looking.  The first look is "prev" vs. "new", the second loop is "inh"
vs. "new".

Discussion: https://www.postgresql.org/message-id/flat/52a125e4-ff9a-95f5-9f61-b87cf447e4da@eisentraut.org
---
 src/backend/commands/tablecmds.c | 181 ++++++++++++++++---------------
 1 file changed, 94 insertions(+), 87 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 875cfeaa5a..2cd8aefb35 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2694,7 +2694,8 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 														parent_attno - 1);
 			char	   *attributeName = NameStr(attribute->attname);
 			int			exist_attno;
-			ColumnDef  *def;
+			ColumnDef  *newdef;
+			ColumnDef  *savedef;
 
 			/*
 			 * Ignore dropped columns in the parent.
@@ -2703,14 +2704,30 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				continue;		/* leave newattmap->attnums entry as zero */
 
 			/*
-			 * Does it conflict with some previously inherited column?
+			 * Create new column definition
+			 */
+			newdef = makeColumnDef(attributeName, attribute->atttypid,
+								   attribute->atttypmod, attribute->attcollation);
+			newdef->storage = attribute->attstorage;
+			newdef->generated = attribute->attgenerated;
+			if (CompressionMethodIsValid(attribute->attcompression))
+				newdef->compression =
+					pstrdup(GetCompressionMethodName(attribute->attcompression));
+
+			/*
+			 * Does it match some previously considered column from another
+			 * parent?
 			 */
 			exist_attno = findAttrByName(attributeName, inh_columns);
 			if (exist_attno > 0)
 			{
-				Oid			defTypeId;
-				int32		deftypmod;
-				Oid			defCollId;
+				ColumnDef  *prevdef;
+				Oid			prevtypeid,
+							newtypeid;
+				int32		prevtypmod,
+							newtypmod;
+				Oid			prevcollid,
+							newcollid;
 
 				/*
 				 * Yes, try to merge the two column definitions.
@@ -2718,68 +2735,61 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				ereport(NOTICE,
 						(errmsg("merging multiple inherited definitions of column \"%s\"",
 								attributeName)));
-				def = (ColumnDef *) list_nth(inh_columns, exist_attno - 1);
+				prevdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
 
 				/*
 				 * Must have the same type and typmod
 				 */
-				typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
-				if (defTypeId != attribute->atttypid ||
-					deftypmod != attribute->atttypmod)
+				typenameTypeIdAndMod(NULL, prevdef->typeName, &prevtypeid, &prevtypmod);
+				typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
+				if (prevtypeid != newtypeid || prevtypmod != newtypmod)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("inherited column \"%s\" has a type conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   format_type_with_typemod(defTypeId,
-																deftypmod),
-									   format_type_with_typemod(attribute->atttypid,
-																attribute->atttypmod))));
+									   format_type_with_typemod(prevtypeid, prevtypmod),
+									   format_type_with_typemod(newtypeid, newtypmod))));
 
 				/*
 				 * Must have the same collation
 				 */
-				defCollId = GetColumnDefCollation(NULL, def, defTypeId);
-				if (defCollId != attribute->attcollation)
+				prevcollid = GetColumnDefCollation(NULL, prevdef, prevtypeid);
+				newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
+				if (prevcollid != newcollid)
 					ereport(ERROR,
 							(errcode(ERRCODE_COLLATION_MISMATCH),
 							 errmsg("inherited column \"%s\" has a collation conflict",
 									attributeName),
 							 errdetail("\"%s\" versus \"%s\"",
-									   get_collation_name(defCollId),
-									   get_collation_name(attribute->attcollation))));
+									   get_collation_name(prevcollid),
+									   get_collation_name(newcollid))));
 
 				/*
 				 * Copy/check storage parameter
 				 */
-				if (def->storage == 0)
-					def->storage = attribute->attstorage;
-				else if (def->storage != attribute->attstorage)
+				if (prevdef->storage == 0)
+					prevdef->storage = newdef->storage;
+				else if (prevdef->storage != newdef->storage)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("inherited column \"%s\" has a storage parameter conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   storage_name(def->storage),
-									   storage_name(attribute->attstorage))));
+									   storage_name(prevdef->storage),
+									   storage_name(newdef->storage))));
 
 				/*
 				 * Copy/check compression parameter
 				 */
-				if (CompressionMethodIsValid(attribute->attcompression))
-				{
-					const char *compression =
-						GetCompressionMethodName(attribute->attcompression);
-
-					if (def->compression == NULL)
-						def->compression = pstrdup(compression);
-					else if (strcmp(def->compression, compression) != 0)
-						ereport(ERROR,
-								(errcode(ERRCODE_DATATYPE_MISMATCH),
-								 errmsg("column \"%s\" has a compression method conflict",
-										attributeName),
-								 errdetail("%s versus %s", def->compression, compression)));
-				}
+				if (prevdef->compression == NULL)
+					prevdef->compression = newdef->compression;
+				else if (strcmp(prevdef->compression, newdef->compression) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("column \"%s\" has a compression method conflict",
+									attributeName),
+							 errdetail("%s versus %s", prevdef->compression, newdef->compression)));
 
 				/*
 				 * In regular inheritance, columns in the parent's primary key
@@ -2813,12 +2823,12 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				if (bms_is_member(parent_attno, nncols) ||
 					bms_is_member(parent_attno - FirstLowInvalidHeapAttributeNumber,
 								  pkattrs))
-					def->is_not_null = true;
+					newdef->is_not_null = true;
 
 				/*
 				 * Check for GENERATED conflicts
 				 */
-				if (def->generated != attribute->attgenerated)
+				if (prevdef->generated != newdef->generated)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("inherited column \"%s\" has a generation conflict",
@@ -2828,34 +2838,30 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 * Default and other constraints are handled below
 				 */
 
-				def->inhcount++;
-				if (def->inhcount < 0)
+				prevdef->inhcount++;
+				if (prevdef->inhcount < 0)
 					ereport(ERROR,
 							errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 							errmsg("too many inheritance parents"));
 
 				newattmap->attnums[parent_attno - 1] = exist_attno;
+
+				/* remember for default processing below */
+				savedef = prevdef;
 			}
 			else
 			{
 				/*
 				 * No, create a new inherited column
 				 */
-				def = makeColumnDef(attributeName, attribute->atttypid,
-									attribute->atttypmod, attribute->attcollation);
-				def->inhcount = 1;
-				def->is_local = false;
+				newdef->inhcount = 1;
+				newdef->is_local = false;
 				/* mark attnotnull if parent has it and it's not NO INHERIT */
 				if (bms_is_member(parent_attno, nncols) ||
 					bms_is_member(parent_attno - FirstLowInvalidHeapAttributeNumber,
 								  pkattrs))
-					def->is_not_null = true;
-				def->storage = attribute->attstorage;
-				def->generated = attribute->attgenerated;
-				if (CompressionMethodIsValid(attribute->attcompression))
-					def->compression =
-						pstrdup(GetCompressionMethodName(attribute->attcompression));
-				inh_columns = lappend(inh_columns, def);
+					newdef->is_not_null = true;
+				inh_columns = lappend(inh_columns, newdef);
 				newattmap->attnums[parent_attno - 1] = ++child_attno;
 
 				/*
@@ -2884,6 +2890,9 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 
 					nnconstraints = lappend(nnconstraints, nn);
 				}
+
+				/* remember for default processing below */
+				savedef = newdef;
 			}
 
 			/*
@@ -2905,7 +2914,7 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 * all the inherited default expressions for the moment.
 				 */
 				inherited_defaults = lappend(inherited_defaults, this_default);
-				cols_with_defaults = lappend(cols_with_defaults, def);
+				cols_with_defaults = lappend(cols_with_defaults, savedef);
 			}
 		}
 
@@ -3043,17 +3052,17 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 			newcol_attno++;
 
 			/*
-			 * Does it conflict with some previously inherited column?
+			 * Does it match some inherited column?
 			 */
 			exist_attno = findAttrByName(attributeName, inh_columns);
 			if (exist_attno > 0)
 			{
-				ColumnDef  *def;
-				Oid			defTypeId,
-							newTypeId;
-				int32		deftypmod,
+				ColumnDef  *inhdef;
+				Oid			inhtypeid,
+							newtypeid;
+				int32		inhtypmod,
 							newtypmod;
-				Oid			defcollid,
+				Oid			inhcollid,
 							newcollid;
 
 				/*
@@ -3073,77 +3082,75 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 					ereport(NOTICE,
 							(errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
 							 errdetail("User-specified column moved to the position of the inherited column.")));
-				def = (ColumnDef *) list_nth(inh_columns, exist_attno - 1);
+				inhdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
 
 				/*
 				 * Must have the same type and typmod
 				 */
-				typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
-				typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
-				if (defTypeId != newTypeId || deftypmod != newtypmod)
+				typenameTypeIdAndMod(NULL, inhdef->typeName, &inhtypeid, &inhtypmod);
+				typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
+				if (inhtypeid != newtypeid || inhtypmod != newtypmod)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("column \"%s\" has a type conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   format_type_with_typemod(defTypeId,
-																deftypmod),
-									   format_type_with_typemod(newTypeId,
-																newtypmod))));
+									   format_type_with_typemod(inhtypeid, inhtypmod),
+									   format_type_with_typemod(newtypeid, newtypmod))));
 
 				/*
 				 * Must have the same collation
 				 */
-				defcollid = GetColumnDefCollation(NULL, def, defTypeId);
-				newcollid = GetColumnDefCollation(NULL, newdef, newTypeId);
-				if (defcollid != newcollid)
+				inhcollid = GetColumnDefCollation(NULL, inhdef, inhtypeid);
+				newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
+				if (inhcollid != newcollid)
 					ereport(ERROR,
 							(errcode(ERRCODE_COLLATION_MISMATCH),
 							 errmsg("column \"%s\" has a collation conflict",
 									attributeName),
 							 errdetail("\"%s\" versus \"%s\"",
-									   get_collation_name(defcollid),
+									   get_collation_name(inhcollid),
 									   get_collation_name(newcollid))));
 
 				/*
 				 * Identity is never inherited.  The new column can have an
 				 * identity definition, so we always just take that one.
 				 */
-				def->identity = newdef->identity;
+				inhdef->identity = newdef->identity;
 
 				/*
 				 * Copy storage parameter
 				 */
-				if (def->storage == 0)
-					def->storage = newdef->storage;
-				else if (newdef->storage != 0 && def->storage != newdef->storage)
+				if (inhdef->storage == 0)
+					inhdef->storage = newdef->storage;
+				else if (newdef->storage != 0 && inhdef->storage != newdef->storage)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("column \"%s\" has a storage parameter conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   storage_name(def->storage),
+									   storage_name(inhdef->storage),
 									   storage_name(newdef->storage))));
 
 				/*
 				 * Copy compression parameter
 				 */
-				if (def->compression == NULL)
-					def->compression = newdef->compression;
+				if (inhdef->compression == NULL)
+					inhdef->compression = newdef->compression;
 				else if (newdef->compression != NULL)
 				{
-					if (strcmp(def->compression, newdef->compression) != 0)
+					if (strcmp(inhdef->compression, newdef->compression) != 0)
 						ereport(ERROR,
 								(errcode(ERRCODE_DATATYPE_MISMATCH),
 								 errmsg("column \"%s\" has a compression method conflict",
 										attributeName),
-								 errdetail("%s versus %s", def->compression, newdef->compression)));
+								 errdetail("%s versus %s", inhdef->compression, newdef->compression)));
 				}
 
 				/*
 				 * Merge of not-null constraints = OR 'em together
 				 */
-				def->is_not_null |= newdef->is_not_null;
+				inhdef->is_not_null |= newdef->is_not_null;
 
 				/*
 				 * Check for conflicts related to generated columns.
@@ -3160,18 +3167,18 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 * it results in being able to override the generation
 				 * expression via UPDATEs through the parent.)
 				 */
-				if (def->generated)
+				if (inhdef->generated)
 				{
 					if (newdef->raw_default && !newdef->generated)
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
 								 errmsg("column \"%s\" inherits from generated column but specifies default",
-										def->colname)));
+										inhdef->colname)));
 					if (newdef->identity)
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
 								 errmsg("column \"%s\" inherits from generated column but specifies identity",
-										def->colname)));
+										inhdef->colname)));
 				}
 				else
 				{
@@ -3179,7 +3186,7 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
 								 errmsg("child column \"%s\" specifies generation expression",
-										def->colname),
+										inhdef->colname),
 								 errhint("A child table column cannot be generated unless its parent column is.")));
 				}
 
@@ -3188,12 +3195,12 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 */
 				if (newdef->raw_default != NULL)
 				{
-					def->raw_default = newdef->raw_default;
-					def->cooked_default = newdef->cooked_default;
+					inhdef->raw_default = newdef->raw_default;
+					inhdef->cooked_default = newdef->cooked_default;
 				}
 
 				/* Mark the column as locally defined */
-				def->is_local = true;
+				inhdef->is_local = true;
 			}
 			else
 			{
-- 
2.43.0

#16Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Peter Eisentraut (#15)
Re: tablecmds.c/MergeAttributes() cleanup

On 2023-Dec-06, Peter Eisentraut wrote:

One of your (Álvaro's) comments about this earlier was

Hmm, crazy. I'm not sure I like this, because it seems much too clever.
The number of lines that are deleted is alluring, though.

Maybe it'd be better to create a separate routine that takes a single
ColumnDef and returns the Form_pg_attribute element for it; then use
that in both BuildDescForRelation and ATExecAddColumn.

which was also my thought at the beginning. However, this wouldn't quite
work that way, for several reasons. For instance, BuildDescForRelation()
also needs to keep track of the has_not_null property across all fields, and
so if you split that function up, you would have to somehow make that an
output argument and have the caller keep track of it. Also, the output of
BuildDescForRelation() in ATExecAddColumn() is passed into
InsertPgAttributeTuples(), which requires a tuple descriptor anyway, so
splitting this up into a per-attribute function would then require
ATExecAddColumn() to re-assemble a tuple descriptor anyway, so this wouldn't
save anything.

Hmm. Well, if this state of affairs is useful to you, then I withdraw
my objection, because with this patch we're not really adding any new
weirdness, just moving around already-existing weirdness. So let's
press ahead with 0001. (I didn't look at 0002 this time, since
apparently you'd like to process the other patch first and then come
back here.)

If you look closely at InsertPgAttributeTuples and accompanying code, it
all looks a bit archaic. They seem to be treating TupleDesc as a
glorified array of Form_pg_attribute elements in a convenient packaging.
It's probably cleaner to change these APIs so that they deal with a
Form_pg_attribute array, and not TupleDesc anymore. But we can hack on
that some other day.

--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
"Pensar que el espectro que vemos es ilusorio no lo despoja de espanto,
sólo le suma el nuevo terror de la locura" (Perelandra, C.S. Lewis)

#17Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Alvaro Herrera (#16)
Re: tablecmds.c/MergeAttributes() cleanup

On 2024-Jan-11, Alvaro Herrera wrote:

If you look closely at InsertPgAttributeTuples and accompanying code, it
all looks a bit archaic. They seem to be treating TupleDesc as a
glorified array of Form_pg_attribute elements in a convenient packaging.
It's probably cleaner to change these APIs so that they deal with a
Form_pg_attribute array, and not TupleDesc anymore. But we can hack on
that some other day.

In addition, it also occurs to me now that maybe it would make sense to
change the TupleDesc implementation to use a List of Form_pg_attribute
instead of an array, and do away with ->natts. This would let us change
all "for ( ... natts ...)" loops into foreach_ptr() loops ... there are
only five hundred of them or so --rolls eyes--.

--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
"El sudor es la mejor cura para un pensamiento enfermo" (Bardia)

#18Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#17)
Re: tablecmds.c/MergeAttributes() cleanup

On Fri, Jan 12, 2024 at 5:32 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:

On 2024-Jan-11, Alvaro Herrera wrote:

If you look closely at InsertPgAttributeTuples and accompanying code, it
all looks a bit archaic. They seem to be treating TupleDesc as a
glorified array of Form_pg_attribute elements in a convenient packaging.
It's probably cleaner to change these APIs so that they deal with a
Form_pg_attribute array, and not TupleDesc anymore. But we can hack on
that some other day.

In addition, it also occurs to me now that maybe it would make sense to
change the TupleDesc implementation to use a List of Form_pg_attribute
instead of an array, and do away with ->natts. This would let us change
all "for ( ... natts ...)" loops into foreach_ptr() loops ... there are
only five hundred of them or so --rolls eyes--.

Open-coding stuff like this can easily work out to a loss, and I
personally think we're overly dependent on List. It's not a
particularly good abstraction, IMHO, and if we do a lot of work to
start using it everywhere because a list is really an array, then what
happens when somebody decides that a list really ought to be a
skip-list, or a hash table, or some other crazy thing?

--
Robert Haas
EDB: http://www.enterprisedb.com

#19Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#18)
Re: tablecmds.c/MergeAttributes() cleanup

Robert Haas <robertmhaas@gmail.com> writes:

On Fri, Jan 12, 2024 at 5:32 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:

In addition, it also occurs to me now that maybe it would make sense to
change the TupleDesc implementation to use a List of Form_pg_attribute
instead of an array, and do away with ->natts. This would let us change
all "for ( ... natts ...)" loops into foreach_ptr() loops ... there are
only five hundred of them or so --rolls eyes--.

Open-coding stuff like this can easily work out to a loss, and I
personally think we're overly dependent on List. It's not a
particularly good abstraction, IMHO, and if we do a lot of work to
start using it everywhere because a list is really an array, then what
happens when somebody decides that a list really ought to be a
skip-list, or a hash table, or some other crazy thing?

Without getting into opinions on whether List is a good abstraction,
I'm -1 on this idea. It would be a large amount of code churn, with
attendant back-patching pain, and I just don't see that there is
much to be gained.

regards, tom lane

#20Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#18)
Re: tablecmds.c/MergeAttributes() cleanup

On Fri, Jan 12, 2024 at 10:09 AM Robert Haas <robertmhaas@gmail.com> wrote:

Open-coding stuff like this can easily work out to a loss, and I
personally think we're overly dependent on List. It's not a
particularly good abstraction, IMHO, and if we do a lot of work to
start using it everywhere because a list is really an array, then what
happens when somebody decides that a list really ought to be a
skip-list, or a hash table, or some other crazy thing?

This paragraph was a bit garbled. I meant to say that open-coding can
be better than relying on a canned abstraction, but it came out the
other way around.

--
Robert Haas
EDB: http://www.enterprisedb.com

#21Peter Eisentraut
peter@eisentraut.org
In reply to: Peter Eisentraut (#15)
1 attachment(s)
Re: tablecmds.c/MergeAttributes() cleanup

On 06.12.23 09:23, Peter Eisentraut wrote:

The (now) second patch is also still of interest to me, but I have since
noticed that I think [0] should be fixed first, but to make that fix
simpler, I would like the first patch from here.

[0]:
/messages/by-id/24656cec-d6ef-4d15-8b5b-e8dfc9c833a7@eisentraut.org

The remaining patch in this series needed a rebase and adjustment.

The above precondition still applies.

Attachments:

v5-0001-MergeAttributes-convert-pg_attribute-back-to-Colu.patchtext/plain; charset=UTF-8; name=v5-0001-MergeAttributes-convert-pg_attribute-back-to-Colu.patchDownload
From c4c4462fa58c73fc383c4935c8d2753b7a0b4c9d Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 22 Jan 2024 13:23:43 +0100
Subject: [PATCH v5] MergeAttributes: convert pg_attribute back to ColumnDef
 before comparing

MergeAttributes() has a loop to merge multiple inheritance parents
into a column column definition.  The merged-so-far definition is
stored in a ColumnDef node.  If we have to merge columns from multiple
inheritance parents (if the name matches), then we have to check
whether various column properties (type, collation, etc.) match.  The
current code extracts the pg_attribute value of the
currently-considered inheritance parent and compares it against the
merged-so-far ColumnDef value.  If the currently considered column
doesn't match any previously inherited column, we make a new ColumnDef
node from the pg_attribute information and add it to the column list.

This patch rearranges this so that we create the ColumnDef node first
in either case.  Then the code that checks the column properties for
compatibility compares ColumnDef against ColumnDef (instead of
ColumnDef against pg_attribute).  This makes the code more symmetric
and easier to follow.  Also, later in MergeAttributes(), there is a
similar but separate loop that merges the new local column definition
with the combination of the inheritance parents established in the
first loop.  That comparison is already ColumnDef-vs-ColumnDef.  With
this change, both of these can use similar-looking logic.  (A future
project might be to extract these two sets of code into a common
routine that encodes all the knowledge of whether two column
definitions are compatible.  But this isn't currently straightforward
because we want to give different error messages in the two cases.)
Furthermore, by avoiding the use of Form_pg_attribute here, we make it
easier to make changes in the pg_attribute layout without having to
worry about the local needs of tablecmds.c.

Because MergeAttributes() is hugely long, it's sometimes hard to know
where in the function you are currently looking.  To help with that, I
also renamed some variables to make it clearer where you are currently
looking.  The first look is "prev" vs. "new", the second loop is "inh"
vs. "new".

Discussion: https://www.postgresql.org/message-id/flat/52a125e4-ff9a-95f5-9f61-b87cf447e4da@eisentraut.org
---
 src/backend/commands/tablecmds.c | 198 ++++++++++++++++---------------
 1 file changed, 102 insertions(+), 96 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2a56a4357c9..04e674bbdae 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2704,7 +2704,8 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 														parent_attno - 1);
 			char	   *attributeName = NameStr(attribute->attname);
 			int			exist_attno;
-			ColumnDef  *def;
+			ColumnDef  *newdef;
+			ColumnDef  *savedef;
 
 			/*
 			 * Ignore dropped columns in the parent.
@@ -2713,14 +2714,38 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				continue;		/* leave newattmap->attnums entry as zero */
 
 			/*
-			 * Does it conflict with some previously inherited column?
+			 * Create new column definition
+			 */
+			newdef = makeColumnDef(attributeName, attribute->atttypid,
+								   attribute->atttypmod, attribute->attcollation);
+			newdef->storage = attribute->attstorage;
+			newdef->generated = attribute->attgenerated;
+			if (CompressionMethodIsValid(attribute->attcompression))
+				newdef->compression =
+					pstrdup(GetCompressionMethodName(attribute->attcompression));
+
+			/*
+			 * Regular inheritance children are independent enough not to
+			 * inherit identity columns.  But partitions are integral part
+			 * of a partitioned table and inherit identity column.
+			 */
+			if (is_partition)
+				newdef->identity = attribute->attidentity;
+
+			/*
+			 * Does it match some previously considered column from another
+			 * parent?
 			 */
 			exist_attno = findAttrByName(attributeName, inh_columns);
 			if (exist_attno > 0)
 			{
-				Oid			defTypeId;
-				int32		deftypmod;
-				Oid			defCollId;
+				ColumnDef  *prevdef;
+				Oid			prevtypeid,
+							newtypeid;
+				int32		prevtypmod,
+							newtypmod;
+				Oid			prevcollid,
+							newcollid;
 
 				/*
 				 * Partitions have only one parent and have no column
@@ -2734,68 +2759,61 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				ereport(NOTICE,
 						(errmsg("merging multiple inherited definitions of column \"%s\"",
 								attributeName)));
-				def = (ColumnDef *) list_nth(inh_columns, exist_attno - 1);
+				prevdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
 
 				/*
 				 * Must have the same type and typmod
 				 */
-				typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
-				if (defTypeId != attribute->atttypid ||
-					deftypmod != attribute->atttypmod)
+				typenameTypeIdAndMod(NULL, prevdef->typeName, &prevtypeid, &prevtypmod);
+				typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
+				if (prevtypeid != newtypeid || prevtypmod != newtypmod)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("inherited column \"%s\" has a type conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   format_type_with_typemod(defTypeId,
-																deftypmod),
-									   format_type_with_typemod(attribute->atttypid,
-																attribute->atttypmod))));
+									   format_type_with_typemod(prevtypeid, prevtypmod),
+									   format_type_with_typemod(newtypeid, newtypmod))));
 
 				/*
 				 * Must have the same collation
 				 */
-				defCollId = GetColumnDefCollation(NULL, def, defTypeId);
-				if (defCollId != attribute->attcollation)
+				prevcollid = GetColumnDefCollation(NULL, prevdef, prevtypeid);
+				newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
+				if (prevcollid != newcollid)
 					ereport(ERROR,
 							(errcode(ERRCODE_COLLATION_MISMATCH),
 							 errmsg("inherited column \"%s\" has a collation conflict",
 									attributeName),
 							 errdetail("\"%s\" versus \"%s\"",
-									   get_collation_name(defCollId),
-									   get_collation_name(attribute->attcollation))));
+									   get_collation_name(prevcollid),
+									   get_collation_name(newcollid))));
 
 				/*
 				 * Copy/check storage parameter
 				 */
-				if (def->storage == 0)
-					def->storage = attribute->attstorage;
-				else if (def->storage != attribute->attstorage)
+				if (prevdef->storage == 0)
+					prevdef->storage = newdef->storage;
+				else if (prevdef->storage != newdef->storage)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("inherited column \"%s\" has a storage parameter conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   storage_name(def->storage),
-									   storage_name(attribute->attstorage))));
+									   storage_name(prevdef->storage),
+									   storage_name(newdef->storage))));
 
 				/*
 				 * Copy/check compression parameter
 				 */
-				if (CompressionMethodIsValid(attribute->attcompression))
-				{
-					const char *compression =
-						GetCompressionMethodName(attribute->attcompression);
-
-					if (def->compression == NULL)
-						def->compression = pstrdup(compression);
-					else if (strcmp(def->compression, compression) != 0)
-						ereport(ERROR,
-								(errcode(ERRCODE_DATATYPE_MISMATCH),
-								 errmsg("column \"%s\" has a compression method conflict",
-										attributeName),
-								 errdetail("%s versus %s", def->compression, compression)));
-				}
+				if (prevdef->compression == NULL)
+					prevdef->compression = newdef->compression;
+				else if (strcmp(prevdef->compression, newdef->compression) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("column \"%s\" has a compression method conflict",
+									attributeName),
+							 errdetail("%s versus %s", prevdef->compression, newdef->compression)));
 
 				/*
 				 * In regular inheritance, columns in the parent's primary key
@@ -2826,12 +2844,12 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				if (bms_is_member(parent_attno, nncols) ||
 					bms_is_member(parent_attno - FirstLowInvalidHeapAttributeNumber,
 								  pkattrs))
-					def->is_not_null = true;
+					newdef->is_not_null = true;
 
 				/*
 				 * Check for GENERATED conflicts
 				 */
-				if (def->generated != attribute->attgenerated)
+				if (prevdef->generated != newdef->generated)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("inherited column \"%s\" has a generation conflict",
@@ -2841,43 +2859,30 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 * Default and other constraints are handled below
 				 */
 
-				def->inhcount++;
-				if (def->inhcount < 0)
+				prevdef->inhcount++;
+				if (prevdef->inhcount < 0)
 					ereport(ERROR,
 							errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 							errmsg("too many inheritance parents"));
 
 				newattmap->attnums[parent_attno - 1] = exist_attno;
+
+				/* remember for default processing below */
+				savedef = prevdef;
 			}
 			else
 			{
 				/*
 				 * No, create a new inherited column
 				 */
-				def = makeColumnDef(attributeName, attribute->atttypid,
-									attribute->atttypmod, attribute->attcollation);
-				def->inhcount = 1;
-				def->is_local = false;
+				newdef->inhcount = 1;
+				newdef->is_local = false;
 				/* mark attnotnull if parent has it and it's not NO INHERIT */
 				if (bms_is_member(parent_attno, nncols) ||
 					bms_is_member(parent_attno - FirstLowInvalidHeapAttributeNumber,
 								  pkattrs))
-					def->is_not_null = true;
-				def->storage = attribute->attstorage;
-				def->generated = attribute->attgenerated;
-
-				/*
-				 * Regular inheritance children are independent enough not to
-				 * inherit identity columns.  But partitions are integral part
-				 * of a partitioned table and inherit identity column.
-				 */
-				if (is_partition)
-					def->identity = attribute->attidentity;
-
-				if (CompressionMethodIsValid(attribute->attcompression))
-					def->compression =
-						pstrdup(GetCompressionMethodName(attribute->attcompression));
-				inh_columns = lappend(inh_columns, def);
+					newdef->is_not_null = true;
+				inh_columns = lappend(inh_columns, newdef);
 				newattmap->attnums[parent_attno - 1] = ++child_attno;
 
 				/*
@@ -2906,6 +2911,9 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 
 					nnconstraints = lappend(nnconstraints, nn);
 				}
+
+				/* remember for default processing below */
+				savedef = newdef;
 			}
 
 			/*
@@ -2927,7 +2935,7 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 * all the inherited default expressions for the moment.
 				 */
 				inherited_defaults = lappend(inherited_defaults, this_default);
-				cols_with_defaults = lappend(cols_with_defaults, def);
+				cols_with_defaults = lappend(cols_with_defaults, savedef);
 			}
 		}
 
@@ -3065,17 +3073,17 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 			newcol_attno++;
 
 			/*
-			 * Does it conflict with some previously inherited column?
+			 * Does it match some inherited column?
 			 */
 			exist_attno = findAttrByName(attributeName, inh_columns);
 			if (exist_attno > 0)
 			{
-				ColumnDef  *def;
-				Oid			defTypeId,
-							newTypeId;
-				int32		deftypmod,
+				ColumnDef  *inhdef;
+				Oid			inhtypeid,
+							newtypeid;
+				int32		inhtypmod,
 							newtypmod;
-				Oid			defcollid,
+				Oid			inhcollid,
 							newcollid;
 
 				/*
@@ -3095,77 +3103,75 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 					ereport(NOTICE,
 							(errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
 							 errdetail("User-specified column moved to the position of the inherited column.")));
-				def = (ColumnDef *) list_nth(inh_columns, exist_attno - 1);
+				inhdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
 
 				/*
 				 * Must have the same type and typmod
 				 */
-				typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
-				typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
-				if (defTypeId != newTypeId || deftypmod != newtypmod)
+				typenameTypeIdAndMod(NULL, inhdef->typeName, &inhtypeid, &inhtypmod);
+				typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
+				if (inhtypeid != newtypeid || inhtypmod != newtypmod)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("column \"%s\" has a type conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   format_type_with_typemod(defTypeId,
-																deftypmod),
-									   format_type_with_typemod(newTypeId,
-																newtypmod))));
+									   format_type_with_typemod(inhtypeid, inhtypmod),
+									   format_type_with_typemod(newtypeid, newtypmod))));
 
 				/*
 				 * Must have the same collation
 				 */
-				defcollid = GetColumnDefCollation(NULL, def, defTypeId);
-				newcollid = GetColumnDefCollation(NULL, newdef, newTypeId);
-				if (defcollid != newcollid)
+				inhcollid = GetColumnDefCollation(NULL, inhdef, inhtypeid);
+				newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
+				if (inhcollid != newcollid)
 					ereport(ERROR,
 							(errcode(ERRCODE_COLLATION_MISMATCH),
 							 errmsg("column \"%s\" has a collation conflict",
 									attributeName),
 							 errdetail("\"%s\" versus \"%s\"",
-									   get_collation_name(defcollid),
+									   get_collation_name(inhcollid),
 									   get_collation_name(newcollid))));
 
 				/*
 				 * Identity is never inherited.  The new column can have an
 				 * identity definition, so we always just take that one.
 				 */
-				def->identity = newdef->identity;
+				inhdef->identity = newdef->identity;
 
 				/*
 				 * Copy storage parameter
 				 */
-				if (def->storage == 0)
-					def->storage = newdef->storage;
-				else if (newdef->storage != 0 && def->storage != newdef->storage)
+				if (inhdef->storage == 0)
+					inhdef->storage = newdef->storage;
+				else if (newdef->storage != 0 && inhdef->storage != newdef->storage)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("column \"%s\" has a storage parameter conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   storage_name(def->storage),
+									   storage_name(inhdef->storage),
 									   storage_name(newdef->storage))));
 
 				/*
 				 * Copy compression parameter
 				 */
-				if (def->compression == NULL)
-					def->compression = newdef->compression;
+				if (inhdef->compression == NULL)
+					inhdef->compression = newdef->compression;
 				else if (newdef->compression != NULL)
 				{
-					if (strcmp(def->compression, newdef->compression) != 0)
+					if (strcmp(inhdef->compression, newdef->compression) != 0)
 						ereport(ERROR,
 								(errcode(ERRCODE_DATATYPE_MISMATCH),
 								 errmsg("column \"%s\" has a compression method conflict",
 										attributeName),
-								 errdetail("%s versus %s", def->compression, newdef->compression)));
+								 errdetail("%s versus %s", inhdef->compression, newdef->compression)));
 				}
 
 				/*
 				 * Merge of not-null constraints = OR 'em together
 				 */
-				def->is_not_null |= newdef->is_not_null;
+				inhdef->is_not_null |= newdef->is_not_null;
 
 				/*
 				 * Check for conflicts related to generated columns.
@@ -3182,18 +3188,18 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 * it results in being able to override the generation
 				 * expression via UPDATEs through the parent.)
 				 */
-				if (def->generated)
+				if (inhdef->generated)
 				{
 					if (newdef->raw_default && !newdef->generated)
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
 								 errmsg("column \"%s\" inherits from generated column but specifies default",
-										def->colname)));
+										inhdef->colname)));
 					if (newdef->identity)
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
 								 errmsg("column \"%s\" inherits from generated column but specifies identity",
-										def->colname)));
+										inhdef->colname)));
 				}
 				else
 				{
@@ -3201,7 +3207,7 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
 								 errmsg("child column \"%s\" specifies generation expression",
-										def->colname),
+										inhdef->colname),
 								 errhint("A child table column cannot be generated unless its parent column is.")));
 				}
 
@@ -3210,12 +3216,12 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 */
 				if (newdef->raw_default != NULL)
 				{
-					def->raw_default = newdef->raw_default;
-					def->cooked_default = newdef->cooked_default;
+					inhdef->raw_default = newdef->raw_default;
+					inhdef->cooked_default = newdef->cooked_default;
 				}
 
 				/* Mark the column as locally defined */
-				def->is_local = true;
+				inhdef->is_local = true;
 			}
 			else
 			{
-- 
2.43.0

#22Ashutosh Bapat
ashutosh.bapat.oss@gmail.com
In reply to: Peter Eisentraut (#21)
4 attachment(s)
Re: tablecmds.c/MergeAttributes() cleanup

Hi Peter,

On Mon, Jan 22, 2024 at 6:13 PM Peter Eisentraut <peter@eisentraut.org> wrote:

On 06.12.23 09:23, Peter Eisentraut wrote:

The (now) second patch is also still of interest to me, but I have since
noticed that I think [0] should be fixed first, but to make that fix
simpler, I would like the first patch from here.

[0]:
/messages/by-id/24656cec-d6ef-4d15-8b5b-e8dfc9c833a7@eisentraut.org

The remaining patch in this series needed a rebase and adjustment.

The above precondition still applies.

While working on identity support and now while looking at the
compression problem you referred to, I found MergeAttribute() to be
hard to read. It's hard to follow high level logic in that function
since the function is not modular. I took a stab at modularising a
part of it. Attached is the resulting patch series.

0001 is your patch as is
0002 is pgindent fix and also fixing what I think is a typo/thinko
from 0001. If you are fine with the changes, 0002 should be merged
into 0003.
0003 separates the part of code merging a child attribute to the
corresponding inherited attribute into its own function.
0004 does the same for code merging inherited attributes incrementally.

I have kept 0003 and 0004 separate in case we pick one and not the
other. But they can be committed as a single commit.

The two new functions have some common code and some differences.
Common code is not surprising since merging attributes whether from
child definition or from inheritance parents will have common rules.
Differences are expected in cases when child attributes need to be
treated differently. But the differences may point us to some
yet-unknown bugs; compression being one of those differences. I think
the next step should combine these functions into one so that all the
logic to merge one attribute is at one place. I haven't attempted it;
wanted to propose the idea first.

I can see that this moduralization will lead to another and we will be
able to reduce MergeAttribute() to a set of function calls reflecting
its high level logic and push the detailed implementation into minion
functions like this.

--
Best Wishes,
Ashutosh Bapat

Attachments:

0002-Mark-NULL-constraint-in-merged-definition-i-20240124.patchtext/x-patch; charset=US-ASCII; name=0002-Mark-NULL-constraint-in-merged-definition-i-20240124.patchDownload
From 2553d082b16db2c54f2e1e4a66f1a57ecabf3c1e Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Wed, 24 Jan 2024 11:15:15 +0530
Subject: [PATCH 2/4] Mark NULL constraint in merged definition instead of new
 definition

This is a typo/thinko in previous commit.

Also fix pgindent issue.

Ashutosh Bapat
---
 src/backend/commands/tablecmds.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 04e674bbda..cde524083e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2726,8 +2726,8 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 
 			/*
 			 * Regular inheritance children are independent enough not to
-			 * inherit identity columns.  But partitions are integral part
-			 * of a partitioned table and inherit identity column.
+			 * inherit identity columns.  But partitions are integral part of
+			 * a partitioned table and inherit identity column.
 			 */
 			if (is_partition)
 				newdef->identity = attribute->attidentity;
@@ -2844,7 +2844,7 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				if (bms_is_member(parent_attno, nncols) ||
 					bms_is_member(parent_attno - FirstLowInvalidHeapAttributeNumber,
 								  pkattrs))
-					newdef->is_not_null = true;
+					prevdef->is_not_null = true;
 
 				/*
 				 * Check for GENERATED conflicts
-- 
2.25.1

0004-Separate-function-to-merge-next-parent-attr-20240124.patchtext/x-patch; charset=US-ASCII; name=0004-Separate-function-to-merge-next-parent-attr-20240124.patchDownload
From 092828a7d7274b48bf33c88863a97585bff80ebb Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Tue, 23 Jan 2024 17:00:43 +0530
Subject: [PATCH 4/4] Separate function to merge next parent attribute

Partition inherit from only a single parent.  The logic to merge an
attribute from the next parent into the corresponding attribute
inherited from previous parents in MergeAttribute() is only applicable
to regular inheritance children.  This code is isolated enough that it
can be separate into a function by itself.  This separation makes
MergeAttributes() more readable making it easy to follow high level
logic without getting entangled into details.

This separation revealed that the code handling NOT NULL constraints is
duplicated in blocks merging the attribute definition incrementally.
Deduplicate that code.

Author: Ashutosh Bapat
---
 src/backend/commands/tablecmds.c | 352 ++++++++++++++++---------------
 1 file changed, 178 insertions(+), 174 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 843cc3bff6..3f0066b435 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -360,6 +360,8 @@ static List *MergeAttributes(List *columns, const List *supers, char relpersiste
 							 List **supnotnulls);
 static List *MergeCheckConstraint(List *constraints, const char *name, Node *expr);
 static bool MergeChildAttribute(ColumnDef *newdef, int newcol_attno, List *inh_columns);
+static ColumnDef *MergeInheritedAttribute(ColumnDef *newdef, int parent_attno,
+										  List *inh_columns, AttrMap *newattmap);
 static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, bool ispartition);
 static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
 static void StoreCatalogInheritance(Oid relationId, List *supers,
@@ -2704,9 +2706,8 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 			Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
 														parent_attno - 1);
 			char	   *attributeName = NameStr(attribute->attname);
-			int			exist_attno;
 			ColumnDef  *newdef;
-			ColumnDef  *savedef;
+			ColumnDef  *mergedef;
 
 			/*
 			 * Ignore dropped columns in the parent.
@@ -2727,194 +2728,64 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 
 			/*
 			 * Regular inheritance children are independent enough not to
-			 * inherit identity columns.  But partitions are integral part of
-			 * a partitioned table and inherit identity column.
+			 * inherit identity columns. But partitions are integral part of a
+			 * partitioned table and inherit identity column.
 			 */
 			if (is_partition)
 				newdef->identity = attribute->attidentity;
 
+			mergedef = MergeInheritedAttribute(newdef, parent_attno,
+											   inh_columns, newattmap);
+
 			/*
-			 * Does it match some previously considered column from another
-			 * parent?
+			 * Partitions have only one parent, so conflict should never
+			 * occur.
 			 */
-			exist_attno = findAttrByName(attributeName, inh_columns);
-			if (exist_attno > 0)
-			{
-				ColumnDef  *prevdef;
-				Oid			prevtypeid,
-							newtypeid;
-				int32		prevtypmod,
-							newtypmod;
-				Oid			prevcollid,
-							newcollid;
-
-				/*
-				 * Partitions have only one parent and have no column
-				 * definitions of their own, so conflict should never occur.
-				 */
-				Assert(!is_partition);
-
-				/*
-				 * Yes, try to merge the two column definitions.
-				 */
-				ereport(NOTICE,
-						(errmsg("merging multiple inherited definitions of column \"%s\"",
-								attributeName)));
-				prevdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
-
-				/*
-				 * Must have the same type and typmod
-				 */
-				typenameTypeIdAndMod(NULL, prevdef->typeName, &prevtypeid, &prevtypmod);
-				typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
-				if (prevtypeid != newtypeid || prevtypmod != newtypmod)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("inherited column \"%s\" has a type conflict",
-									attributeName),
-							 errdetail("%s versus %s",
-									   format_type_with_typemod(prevtypeid, prevtypmod),
-									   format_type_with_typemod(newtypeid, newtypmod))));
-
-				/*
-				 * Must have the same collation
-				 */
-				prevcollid = GetColumnDefCollation(NULL, prevdef, prevtypeid);
-				newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
-				if (prevcollid != newcollid)
-					ereport(ERROR,
-							(errcode(ERRCODE_COLLATION_MISMATCH),
-							 errmsg("inherited column \"%s\" has a collation conflict",
-									attributeName),
-							 errdetail("\"%s\" versus \"%s\"",
-									   get_collation_name(prevcollid),
-									   get_collation_name(newcollid))));
+			Assert(mergedef == NULL || !is_partition);
 
-				/*
-				 * Copy/check storage parameter
-				 */
-				if (prevdef->storage == 0)
-					prevdef->storage = newdef->storage;
-				else if (prevdef->storage != newdef->storage)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("inherited column \"%s\" has a storage parameter conflict",
-									attributeName),
-							 errdetail("%s versus %s",
-									   storage_name(prevdef->storage),
-									   storage_name(newdef->storage))));
-
-				/*
-				 * Copy/check compression parameter
-				 */
-				if (prevdef->compression == NULL)
-					prevdef->compression = newdef->compression;
-				else if (strcmp(prevdef->compression, newdef->compression) != 0)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("column \"%s\" has a compression method conflict",
-									attributeName),
-							 errdetail("%s versus %s", prevdef->compression, newdef->compression)));
-
-				/*
-				 * In regular inheritance, columns in the parent's primary key
-				 * get an extra not-null constraint.
-				 */
-				if (bms_is_member(parent_attno - FirstLowInvalidHeapAttributeNumber,
-								  pkattrs))
-				{
-					CookedConstraint *nn;
-
-					nn = palloc(sizeof(CookedConstraint));
-					nn->contype = CONSTR_NOTNULL;
-					nn->conoid = InvalidOid;
-					nn->name = NULL;
-					nn->attnum = exist_attno;
-					nn->expr = NULL;
-					nn->skip_validation = false;
-					nn->is_local = false;
-					nn->inhcount = 1;
-					nn->is_no_inherit = false;
-
-					nnconstraints = lappend(nnconstraints, nn);
-				}
-
-				/*
-				 * mark attnotnull if parent has it and it's not NO INHERIT
-				 */
-				if (bms_is_member(parent_attno, nncols) ||
-					bms_is_member(parent_attno - FirstLowInvalidHeapAttributeNumber,
-								  pkattrs))
-					prevdef->is_not_null = true;
-
-				/*
-				 * Check for GENERATED conflicts
-				 */
-				if (prevdef->generated != newdef->generated)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("inherited column \"%s\" has a generation conflict",
-									attributeName)));
-
-				/*
-				 * Default and other constraints are handled below
-				 */
-
-				prevdef->inhcount++;
-				if (prevdef->inhcount < 0)
-					ereport(ERROR,
-							errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-							errmsg("too many inheritance parents"));
-
-				newattmap->attnums[parent_attno - 1] = exist_attno;
-
-				/* remember for default processing below */
-				savedef = prevdef;
-			}
-			else
+			if (mergedef == NULL)
 			{
 				/*
 				 * No, create a new inherited column
 				 */
 				newdef->inhcount = 1;
 				newdef->is_local = false;
-				/* mark attnotnull if parent has it and it's not NO INHERIT */
-				if (bms_is_member(parent_attno, nncols) ||
-					bms_is_member(parent_attno - FirstLowInvalidHeapAttributeNumber,
-								  pkattrs))
-					newdef->is_not_null = true;
 				inh_columns = lappend(inh_columns, newdef);
 				newattmap->attnums[parent_attno - 1] = ++child_attno;
 
-				/*
-				 * In regular inheritance, columns in the parent's primary key
-				 * get an extra not-null constraint.  Partitioning doesn't
-				 * need this, because the PK itself is going to be cloned to
-				 * the partition.
-				 */
-				if (!is_partition &&
-					bms_is_member(parent_attno -
-								  FirstLowInvalidHeapAttributeNumber,
-								  pkattrs))
-				{
-					CookedConstraint *nn;
-
-					nn = palloc(sizeof(CookedConstraint));
-					nn->contype = CONSTR_NOTNULL;
-					nn->conoid = InvalidOid;
-					nn->name = NULL;
-					nn->attnum = newattmap->attnums[parent_attno - 1];
-					nn->expr = NULL;
-					nn->skip_validation = false;
-					nn->is_local = false;
-					nn->inhcount = 1;
-					nn->is_no_inherit = false;
-
-					nnconstraints = lappend(nnconstraints, nn);
-				}
+				mergedef = newdef;
+			}
+
+			/* mark attnotnull if parent has it and it's not NO INHERIT */
+			if (bms_is_member(parent_attno, nncols) ||
+				bms_is_member(parent_attno - FirstLowInvalidHeapAttributeNumber,
+							  pkattrs))
+				mergedef->is_not_null = true;
 
-				/* remember for default processing below */
-				savedef = newdef;
+			/*
+			 * In regular inheritance, columns in the parent's primary key get
+			 * an extra not-null constraint.  Partitioning doesn't need this,
+			 * because the PK itself is going to be cloned to the partition.
+			 */
+			if (!is_partition &&
+				bms_is_member(parent_attno -
+							  FirstLowInvalidHeapAttributeNumber,
+							  pkattrs))
+			{
+				CookedConstraint *nn;
+
+				nn = palloc(sizeof(CookedConstraint));
+				nn->contype = CONSTR_NOTNULL;
+				nn->conoid = InvalidOid;
+				nn->name = NULL;
+				nn->attnum = newattmap->attnums[parent_attno - 1];
+				nn->expr = NULL;
+				nn->skip_validation = false;
+				nn->is_local = false;
+				nn->inhcount = 1;
+				nn->is_no_inherit = false;
+
+				nnconstraints = lappend(nnconstraints, nn);
 			}
 
 			/*
@@ -2936,7 +2807,7 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 * all the inherited default expressions for the moment.
 				 */
 				inherited_defaults = lappend(inherited_defaults, this_default);
-				cols_with_defaults = lappend(cols_with_defaults, savedef);
+				cols_with_defaults = lappend(cols_with_defaults, mergedef);
 			}
 		}
 
@@ -3451,6 +3322,139 @@ MergeChildAttribute(ColumnDef *newdef, int newcol_attno, List *inh_columns)
 	return true;
 }
 
+/*
+ * MergeInheritedAttribute
+ *		Merge given parent attribute definition into any attribute, with
+ *  the same name, inherited from the previous parents.
+ *
+ * Input arguments:
+ * 'newdef' is the new parent column/attribute definition to be merged.
+ * 'parent_attno' is the attribute number in parent table's schema definition
+ * 'inh_columns' is the list of previously inherited ColumnDefs.
+ * 'newattmap' is attribute map.
+ *
+ * Return value:
+ * True if the given attribute is merged into a previously inherited attribute
+ * with the same name. False if no matching inherited attribute is found. When
+ * returning true, matching ColumnDef in 'inh_columns' list is modified.
+ * 'newattmap' is updated with matching inherited attribute's position. New
+ * attribute's ColumnDef remains unchanged.
+ *
+ * Notes:
+ *  (1) The attribute is merged according to the rules laid out in the prologue
+ *  of MergeAttributes().
+ *  (2) If matching inherited attribute exists but the new attribute can not
+ *  be merged into it, the function throws respective errors.
+ *  (3) A partition inherits from only a single parent. Hence this
+ *  function is applicable only to a regular inheritance.
+ */
+static ColumnDef *
+MergeInheritedAttribute(ColumnDef *newdef, int parent_attno, List *inh_columns,
+						AttrMap *newattmap)
+{
+	char	   *attributeName = newdef->colname;
+	int			exist_attno;
+	ColumnDef  *prevdef;
+	Oid			prevtypeid,
+				newtypeid;
+	int32		prevtypmod,
+				newtypmod;
+	Oid			prevcollid,
+				newcollid;
+
+	/*
+	 * Does it match some previously considered column from another parent?
+	 */
+	exist_attno = findAttrByName(attributeName, inh_columns);
+	if (exist_attno <= 0)
+		return NULL;
+
+	/*
+	 * Yes, try to merge the two column definitions.
+	 */
+	ereport(NOTICE,
+			(errmsg("merging multiple inherited definitions of column \"%s\"",
+					attributeName)));
+	prevdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
+
+	/*
+	 * Must have the same type and typmod
+	 */
+	typenameTypeIdAndMod(NULL, prevdef->typeName, &prevtypeid, &prevtypmod);
+	typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
+	if (prevtypeid != newtypeid || prevtypmod != newtypmod)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("inherited column \"%s\" has a type conflict",
+						attributeName),
+				 errdetail("%s versus %s",
+						   format_type_with_typemod(prevtypeid, prevtypmod),
+						   format_type_with_typemod(newtypeid, newtypmod))));
+
+	/*
+	 * Must have the same collation
+	 */
+	prevcollid = GetColumnDefCollation(NULL, prevdef, prevtypeid);
+	newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
+	if (prevcollid != newcollid)
+		ereport(ERROR,
+				(errcode(ERRCODE_COLLATION_MISMATCH),
+				 errmsg("inherited column \"%s\" has a collation conflict",
+						attributeName),
+				 errdetail("\"%s\" versus \"%s\"",
+						   get_collation_name(prevcollid),
+						   get_collation_name(newcollid))));
+
+	/*
+	 * Copy/check storage parameter
+	 */
+	if (prevdef->storage == 0)
+		prevdef->storage = newdef->storage;
+	else if (prevdef->storage != newdef->storage)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("inherited column \"%s\" has a storage parameter conflict",
+						attributeName),
+				 errdetail("%s versus %s",
+						   storage_name(prevdef->storage),
+						   storage_name(newdef->storage))));
+
+	/*
+	 * Copy/check compression parameter
+	 */
+	if (prevdef->compression == NULL)
+		prevdef->compression = newdef->compression;
+	else if (strcmp(prevdef->compression, newdef->compression) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("column \"%s\" has a compression method conflict",
+						attributeName),
+				 errdetail("%s versus %s", prevdef->compression, newdef->compression)));
+
+	/*
+	 * Check for GENERATED conflicts
+	 */
+	if (prevdef->generated != newdef->generated)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("inherited column \"%s\" has a generation conflict",
+						attributeName)));
+
+	/*
+	 * Default and other constraints are handled by the caller.
+	 */
+
+	prevdef->inhcount++;
+	if (prevdef->inhcount < 0)
+		ereport(ERROR,
+				errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+				errmsg("too many inheritance parents"));
+
+	newattmap->attnums[parent_attno - 1] = exist_attno;
+
+	return prevdef;
+}
+
 /*
  * StoreCatalogInheritance
  *		Updates the system catalogs with proper inheritance information.
-- 
2.25.1

0001-MergeAttributes-convert-pg_attribute-back-t-20240124.patchtext/x-patch; charset=US-ASCII; name=0001-MergeAttributes-convert-pg_attribute-back-t-20240124.patchDownload
From 6d6c85d9f08a1d631e4429e9d2c8b3e97b3e1337 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 22 Jan 2024 13:23:43 +0100
Subject: [PATCH 1/4] MergeAttributes: convert pg_attribute back to ColumnDef
 before comparing

MergeAttributes() has a loop to merge multiple inheritance parents
into a column column definition.  The merged-so-far definition is
stored in a ColumnDef node.  If we have to merge columns from multiple
inheritance parents (if the name matches), then we have to check
whether various column properties (type, collation, etc.) match.  The
current code extracts the pg_attribute value of the
currently-considered inheritance parent and compares it against the
merged-so-far ColumnDef value.  If the currently considered column
doesn't match any previously inherited column, we make a new ColumnDef
node from the pg_attribute information and add it to the column list.

This patch rearranges this so that we create the ColumnDef node first
in either case.  Then the code that checks the column properties for
compatibility compares ColumnDef against ColumnDef (instead of
ColumnDef against pg_attribute).  This makes the code more symmetric
and easier to follow.  Also, later in MergeAttributes(), there is a
similar but separate loop that merges the new local column definition
with the combination of the inheritance parents established in the
first loop.  That comparison is already ColumnDef-vs-ColumnDef.  With
this change, both of these can use similar-looking logic.  (A future
project might be to extract these two sets of code into a common
routine that encodes all the knowledge of whether two column
definitions are compatible.  But this isn't currently straightforward
because we want to give different error messages in the two cases.)
Furthermore, by avoiding the use of Form_pg_attribute here, we make it
easier to make changes in the pg_attribute layout without having to
worry about the local needs of tablecmds.c.

Because MergeAttributes() is hugely long, it's sometimes hard to know
where in the function you are currently looking.  To help with that, I
also renamed some variables to make it clearer where you are currently
looking.  The first look is "prev" vs. "new", the second loop is "inh"
vs. "new".

Discussion: https://www.postgresql.org/message-id/flat/52a125e4-ff9a-95f5-9f61-b87cf447e4da@eisentraut.org
---
 src/backend/commands/tablecmds.c | 198 ++++++++++++++++---------------
 1 file changed, 102 insertions(+), 96 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2a56a4357c..04e674bbda 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2704,7 +2704,8 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 														parent_attno - 1);
 			char	   *attributeName = NameStr(attribute->attname);
 			int			exist_attno;
-			ColumnDef  *def;
+			ColumnDef  *newdef;
+			ColumnDef  *savedef;
 
 			/*
 			 * Ignore dropped columns in the parent.
@@ -2713,14 +2714,38 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				continue;		/* leave newattmap->attnums entry as zero */
 
 			/*
-			 * Does it conflict with some previously inherited column?
+			 * Create new column definition
+			 */
+			newdef = makeColumnDef(attributeName, attribute->atttypid,
+								   attribute->atttypmod, attribute->attcollation);
+			newdef->storage = attribute->attstorage;
+			newdef->generated = attribute->attgenerated;
+			if (CompressionMethodIsValid(attribute->attcompression))
+				newdef->compression =
+					pstrdup(GetCompressionMethodName(attribute->attcompression));
+
+			/*
+			 * Regular inheritance children are independent enough not to
+			 * inherit identity columns.  But partitions are integral part
+			 * of a partitioned table and inherit identity column.
+			 */
+			if (is_partition)
+				newdef->identity = attribute->attidentity;
+
+			/*
+			 * Does it match some previously considered column from another
+			 * parent?
 			 */
 			exist_attno = findAttrByName(attributeName, inh_columns);
 			if (exist_attno > 0)
 			{
-				Oid			defTypeId;
-				int32		deftypmod;
-				Oid			defCollId;
+				ColumnDef  *prevdef;
+				Oid			prevtypeid,
+							newtypeid;
+				int32		prevtypmod,
+							newtypmod;
+				Oid			prevcollid,
+							newcollid;
 
 				/*
 				 * Partitions have only one parent and have no column
@@ -2734,68 +2759,61 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				ereport(NOTICE,
 						(errmsg("merging multiple inherited definitions of column \"%s\"",
 								attributeName)));
-				def = (ColumnDef *) list_nth(inh_columns, exist_attno - 1);
+				prevdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
 
 				/*
 				 * Must have the same type and typmod
 				 */
-				typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
-				if (defTypeId != attribute->atttypid ||
-					deftypmod != attribute->atttypmod)
+				typenameTypeIdAndMod(NULL, prevdef->typeName, &prevtypeid, &prevtypmod);
+				typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
+				if (prevtypeid != newtypeid || prevtypmod != newtypmod)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("inherited column \"%s\" has a type conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   format_type_with_typemod(defTypeId,
-																deftypmod),
-									   format_type_with_typemod(attribute->atttypid,
-																attribute->atttypmod))));
+									   format_type_with_typemod(prevtypeid, prevtypmod),
+									   format_type_with_typemod(newtypeid, newtypmod))));
 
 				/*
 				 * Must have the same collation
 				 */
-				defCollId = GetColumnDefCollation(NULL, def, defTypeId);
-				if (defCollId != attribute->attcollation)
+				prevcollid = GetColumnDefCollation(NULL, prevdef, prevtypeid);
+				newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
+				if (prevcollid != newcollid)
 					ereport(ERROR,
 							(errcode(ERRCODE_COLLATION_MISMATCH),
 							 errmsg("inherited column \"%s\" has a collation conflict",
 									attributeName),
 							 errdetail("\"%s\" versus \"%s\"",
-									   get_collation_name(defCollId),
-									   get_collation_name(attribute->attcollation))));
+									   get_collation_name(prevcollid),
+									   get_collation_name(newcollid))));
 
 				/*
 				 * Copy/check storage parameter
 				 */
-				if (def->storage == 0)
-					def->storage = attribute->attstorage;
-				else if (def->storage != attribute->attstorage)
+				if (prevdef->storage == 0)
+					prevdef->storage = newdef->storage;
+				else if (prevdef->storage != newdef->storage)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("inherited column \"%s\" has a storage parameter conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   storage_name(def->storage),
-									   storage_name(attribute->attstorage))));
+									   storage_name(prevdef->storage),
+									   storage_name(newdef->storage))));
 
 				/*
 				 * Copy/check compression parameter
 				 */
-				if (CompressionMethodIsValid(attribute->attcompression))
-				{
-					const char *compression =
-						GetCompressionMethodName(attribute->attcompression);
-
-					if (def->compression == NULL)
-						def->compression = pstrdup(compression);
-					else if (strcmp(def->compression, compression) != 0)
-						ereport(ERROR,
-								(errcode(ERRCODE_DATATYPE_MISMATCH),
-								 errmsg("column \"%s\" has a compression method conflict",
-										attributeName),
-								 errdetail("%s versus %s", def->compression, compression)));
-				}
+				if (prevdef->compression == NULL)
+					prevdef->compression = newdef->compression;
+				else if (strcmp(prevdef->compression, newdef->compression) != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("column \"%s\" has a compression method conflict",
+									attributeName),
+							 errdetail("%s versus %s", prevdef->compression, newdef->compression)));
 
 				/*
 				 * In regular inheritance, columns in the parent's primary key
@@ -2826,12 +2844,12 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				if (bms_is_member(parent_attno, nncols) ||
 					bms_is_member(parent_attno - FirstLowInvalidHeapAttributeNumber,
 								  pkattrs))
-					def->is_not_null = true;
+					newdef->is_not_null = true;
 
 				/*
 				 * Check for GENERATED conflicts
 				 */
-				if (def->generated != attribute->attgenerated)
+				if (prevdef->generated != newdef->generated)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("inherited column \"%s\" has a generation conflict",
@@ -2841,43 +2859,30 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 * Default and other constraints are handled below
 				 */
 
-				def->inhcount++;
-				if (def->inhcount < 0)
+				prevdef->inhcount++;
+				if (prevdef->inhcount < 0)
 					ereport(ERROR,
 							errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 							errmsg("too many inheritance parents"));
 
 				newattmap->attnums[parent_attno - 1] = exist_attno;
+
+				/* remember for default processing below */
+				savedef = prevdef;
 			}
 			else
 			{
 				/*
 				 * No, create a new inherited column
 				 */
-				def = makeColumnDef(attributeName, attribute->atttypid,
-									attribute->atttypmod, attribute->attcollation);
-				def->inhcount = 1;
-				def->is_local = false;
+				newdef->inhcount = 1;
+				newdef->is_local = false;
 				/* mark attnotnull if parent has it and it's not NO INHERIT */
 				if (bms_is_member(parent_attno, nncols) ||
 					bms_is_member(parent_attno - FirstLowInvalidHeapAttributeNumber,
 								  pkattrs))
-					def->is_not_null = true;
-				def->storage = attribute->attstorage;
-				def->generated = attribute->attgenerated;
-
-				/*
-				 * Regular inheritance children are independent enough not to
-				 * inherit identity columns.  But partitions are integral part
-				 * of a partitioned table and inherit identity column.
-				 */
-				if (is_partition)
-					def->identity = attribute->attidentity;
-
-				if (CompressionMethodIsValid(attribute->attcompression))
-					def->compression =
-						pstrdup(GetCompressionMethodName(attribute->attcompression));
-				inh_columns = lappend(inh_columns, def);
+					newdef->is_not_null = true;
+				inh_columns = lappend(inh_columns, newdef);
 				newattmap->attnums[parent_attno - 1] = ++child_attno;
 
 				/*
@@ -2906,6 +2911,9 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 
 					nnconstraints = lappend(nnconstraints, nn);
 				}
+
+				/* remember for default processing below */
+				savedef = newdef;
 			}
 
 			/*
@@ -2927,7 +2935,7 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 * all the inherited default expressions for the moment.
 				 */
 				inherited_defaults = lappend(inherited_defaults, this_default);
-				cols_with_defaults = lappend(cols_with_defaults, def);
+				cols_with_defaults = lappend(cols_with_defaults, savedef);
 			}
 		}
 
@@ -3065,17 +3073,17 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 			newcol_attno++;
 
 			/*
-			 * Does it conflict with some previously inherited column?
+			 * Does it match some inherited column?
 			 */
 			exist_attno = findAttrByName(attributeName, inh_columns);
 			if (exist_attno > 0)
 			{
-				ColumnDef  *def;
-				Oid			defTypeId,
-							newTypeId;
-				int32		deftypmod,
+				ColumnDef  *inhdef;
+				Oid			inhtypeid,
+							newtypeid;
+				int32		inhtypmod,
 							newtypmod;
-				Oid			defcollid,
+				Oid			inhcollid,
 							newcollid;
 
 				/*
@@ -3095,77 +3103,75 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 					ereport(NOTICE,
 							(errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
 							 errdetail("User-specified column moved to the position of the inherited column.")));
-				def = (ColumnDef *) list_nth(inh_columns, exist_attno - 1);
+				inhdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
 
 				/*
 				 * Must have the same type and typmod
 				 */
-				typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
-				typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
-				if (defTypeId != newTypeId || deftypmod != newtypmod)
+				typenameTypeIdAndMod(NULL, inhdef->typeName, &inhtypeid, &inhtypmod);
+				typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
+				if (inhtypeid != newtypeid || inhtypmod != newtypmod)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("column \"%s\" has a type conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   format_type_with_typemod(defTypeId,
-																deftypmod),
-									   format_type_with_typemod(newTypeId,
-																newtypmod))));
+									   format_type_with_typemod(inhtypeid, inhtypmod),
+									   format_type_with_typemod(newtypeid, newtypmod))));
 
 				/*
 				 * Must have the same collation
 				 */
-				defcollid = GetColumnDefCollation(NULL, def, defTypeId);
-				newcollid = GetColumnDefCollation(NULL, newdef, newTypeId);
-				if (defcollid != newcollid)
+				inhcollid = GetColumnDefCollation(NULL, inhdef, inhtypeid);
+				newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
+				if (inhcollid != newcollid)
 					ereport(ERROR,
 							(errcode(ERRCODE_COLLATION_MISMATCH),
 							 errmsg("column \"%s\" has a collation conflict",
 									attributeName),
 							 errdetail("\"%s\" versus \"%s\"",
-									   get_collation_name(defcollid),
+									   get_collation_name(inhcollid),
 									   get_collation_name(newcollid))));
 
 				/*
 				 * Identity is never inherited.  The new column can have an
 				 * identity definition, so we always just take that one.
 				 */
-				def->identity = newdef->identity;
+				inhdef->identity = newdef->identity;
 
 				/*
 				 * Copy storage parameter
 				 */
-				if (def->storage == 0)
-					def->storage = newdef->storage;
-				else if (newdef->storage != 0 && def->storage != newdef->storage)
+				if (inhdef->storage == 0)
+					inhdef->storage = newdef->storage;
+				else if (newdef->storage != 0 && inhdef->storage != newdef->storage)
 					ereport(ERROR,
 							(errcode(ERRCODE_DATATYPE_MISMATCH),
 							 errmsg("column \"%s\" has a storage parameter conflict",
 									attributeName),
 							 errdetail("%s versus %s",
-									   storage_name(def->storage),
+									   storage_name(inhdef->storage),
 									   storage_name(newdef->storage))));
 
 				/*
 				 * Copy compression parameter
 				 */
-				if (def->compression == NULL)
-					def->compression = newdef->compression;
+				if (inhdef->compression == NULL)
+					inhdef->compression = newdef->compression;
 				else if (newdef->compression != NULL)
 				{
-					if (strcmp(def->compression, newdef->compression) != 0)
+					if (strcmp(inhdef->compression, newdef->compression) != 0)
 						ereport(ERROR,
 								(errcode(ERRCODE_DATATYPE_MISMATCH),
 								 errmsg("column \"%s\" has a compression method conflict",
 										attributeName),
-								 errdetail("%s versus %s", def->compression, newdef->compression)));
+								 errdetail("%s versus %s", inhdef->compression, newdef->compression)));
 				}
 
 				/*
 				 * Merge of not-null constraints = OR 'em together
 				 */
-				def->is_not_null |= newdef->is_not_null;
+				inhdef->is_not_null |= newdef->is_not_null;
 
 				/*
 				 * Check for conflicts related to generated columns.
@@ -3182,18 +3188,18 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 * it results in being able to override the generation
 				 * expression via UPDATEs through the parent.)
 				 */
-				if (def->generated)
+				if (inhdef->generated)
 				{
 					if (newdef->raw_default && !newdef->generated)
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
 								 errmsg("column \"%s\" inherits from generated column but specifies default",
-										def->colname)));
+										inhdef->colname)));
 					if (newdef->identity)
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
 								 errmsg("column \"%s\" inherits from generated column but specifies identity",
-										def->colname)));
+										inhdef->colname)));
 				}
 				else
 				{
@@ -3201,7 +3207,7 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 						ereport(ERROR,
 								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
 								 errmsg("child column \"%s\" specifies generation expression",
-										def->colname),
+										inhdef->colname),
 								 errhint("A child table column cannot be generated unless its parent column is.")));
 				}
 
@@ -3210,12 +3216,12 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 				 */
 				if (newdef->raw_default != NULL)
 				{
-					def->raw_default = newdef->raw_default;
-					def->cooked_default = newdef->cooked_default;
+					inhdef->raw_default = newdef->raw_default;
+					inhdef->cooked_default = newdef->cooked_default;
 				}
 
 				/* Mark the column as locally defined */
-				def->is_local = true;
+				inhdef->is_local = true;
 			}
 			else
 			{
-- 
2.25.1

0003-Separate-function-to-merge-a-child-attribut-20240124.patchtext/x-patch; charset=US-ASCII; name=0003-Separate-function-to-merge-a-child-attribut-20240124.patchDownload
From e8993ab2e00db5f8260ef9d9cafe9fbe069d54a2 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Tue, 23 Jan 2024 12:47:46 +0530
Subject: [PATCH 3/4] Separate function to merge a child attribute into
 matching inherited attribute

The logic to merge a child attribute into matching inherited attribute
in MergeAttribute() is only applicable to regular inheritance child. The
code is isolated and coherent enough that it can be separated into a
function of its own. This separation also makes MergeAttribute() more
readable by making it easier to follow high level logic without getting
entangled into details.

Author: Ashutosh Bapat
---
 src/backend/commands/tablecmds.c | 337 +++++++++++++++++--------------
 1 file changed, 184 insertions(+), 153 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cde524083e..843cc3bff6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -359,6 +359,7 @@ static List *MergeAttributes(List *columns, const List *supers, char relpersiste
 							 bool is_partition, List **supconstr,
 							 List **supnotnulls);
 static List *MergeCheckConstraint(List *constraints, const char *name, Node *expr);
+static bool MergeChildAttribute(ColumnDef *newdef, int newcol_attno, List *inh_columns);
 static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, bool ispartition);
 static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
 static void StoreCatalogInheritance(Oid relationId, List *supers,
@@ -3066,167 +3067,21 @@ MergeAttributes(List *columns, const List *supers, char relpersistence,
 
 		foreach(lc, columns)
 		{
-			ColumnDef  *newdef = lfirst(lc);
-			char	   *attributeName = newdef->colname;
-			int			exist_attno;
+			ColumnDef  *newdef = lfirst_node(ColumnDef, lc);
 
 			newcol_attno++;
 
 			/*
-			 * Does it match some inherited column?
+			 * Partitions have only one parent and have no column definitions
+			 * of their own, so conflict should never occur.
 			 */
-			exist_attno = findAttrByName(attributeName, inh_columns);
-			if (exist_attno > 0)
-			{
-				ColumnDef  *inhdef;
-				Oid			inhtypeid,
-							newtypeid;
-				int32		inhtypmod,
-							newtypmod;
-				Oid			inhcollid,
-							newcollid;
-
-				/*
-				 * Partitions have only one parent and have no column
-				 * definitions of their own, so conflict should never occur.
-				 */
-				Assert(!is_partition);
-
-				/*
-				 * Yes, try to merge the two column definitions.
-				 */
-				if (exist_attno == newcol_attno)
-					ereport(NOTICE,
-							(errmsg("merging column \"%s\" with inherited definition",
-									attributeName)));
-				else
-					ereport(NOTICE,
-							(errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
-							 errdetail("User-specified column moved to the position of the inherited column.")));
-				inhdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
-
-				/*
-				 * Must have the same type and typmod
-				 */
-				typenameTypeIdAndMod(NULL, inhdef->typeName, &inhtypeid, &inhtypmod);
-				typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
-				if (inhtypeid != newtypeid || inhtypmod != newtypmod)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("column \"%s\" has a type conflict",
-									attributeName),
-							 errdetail("%s versus %s",
-									   format_type_with_typemod(inhtypeid, inhtypmod),
-									   format_type_with_typemod(newtypeid, newtypmod))));
-
-				/*
-				 * Must have the same collation
-				 */
-				inhcollid = GetColumnDefCollation(NULL, inhdef, inhtypeid);
-				newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
-				if (inhcollid != newcollid)
-					ereport(ERROR,
-							(errcode(ERRCODE_COLLATION_MISMATCH),
-							 errmsg("column \"%s\" has a collation conflict",
-									attributeName),
-							 errdetail("\"%s\" versus \"%s\"",
-									   get_collation_name(inhcollid),
-									   get_collation_name(newcollid))));
-
-				/*
-				 * Identity is never inherited.  The new column can have an
-				 * identity definition, so we always just take that one.
-				 */
-				inhdef->identity = newdef->identity;
-
-				/*
-				 * Copy storage parameter
-				 */
-				if (inhdef->storage == 0)
-					inhdef->storage = newdef->storage;
-				else if (newdef->storage != 0 && inhdef->storage != newdef->storage)
-					ereport(ERROR,
-							(errcode(ERRCODE_DATATYPE_MISMATCH),
-							 errmsg("column \"%s\" has a storage parameter conflict",
-									attributeName),
-							 errdetail("%s versus %s",
-									   storage_name(inhdef->storage),
-									   storage_name(newdef->storage))));
-
-				/*
-				 * Copy compression parameter
-				 */
-				if (inhdef->compression == NULL)
-					inhdef->compression = newdef->compression;
-				else if (newdef->compression != NULL)
-				{
-					if (strcmp(inhdef->compression, newdef->compression) != 0)
-						ereport(ERROR,
-								(errcode(ERRCODE_DATATYPE_MISMATCH),
-								 errmsg("column \"%s\" has a compression method conflict",
-										attributeName),
-								 errdetail("%s versus %s", inhdef->compression, newdef->compression)));
-				}
-
-				/*
-				 * Merge of not-null constraints = OR 'em together
-				 */
-				inhdef->is_not_null |= newdef->is_not_null;
-
-				/*
-				 * Check for conflicts related to generated columns.
-				 *
-				 * If the parent column is generated, the child column will be
-				 * made a generated column if it isn't already.  If it is a
-				 * generated column, we'll take its generation expression in
-				 * preference to the parent's.  We must check that the child
-				 * column doesn't specify a default value or identity, which
-				 * matches the rules for a single column in parse_utilcmd.c.
-				 *
-				 * Conversely, if the parent column is not generated, the
-				 * child column can't be either.  (We used to allow that, but
-				 * it results in being able to override the generation
-				 * expression via UPDATEs through the parent.)
-				 */
-				if (inhdef->generated)
-				{
-					if (newdef->raw_default && !newdef->generated)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
-								 errmsg("column \"%s\" inherits from generated column but specifies default",
-										inhdef->colname)));
-					if (newdef->identity)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
-								 errmsg("column \"%s\" inherits from generated column but specifies identity",
-										inhdef->colname)));
-				}
-				else
-				{
-					if (newdef->generated)
-						ereport(ERROR,
-								(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
-								 errmsg("child column \"%s\" specifies generation expression",
-										inhdef->colname),
-								 errhint("A child table column cannot be generated unless its parent column is.")));
-				}
-
-				/*
-				 * If new def has a default, override previous default
-				 */
-				if (newdef->raw_default != NULL)
-				{
-					inhdef->raw_default = newdef->raw_default;
-					inhdef->cooked_default = newdef->cooked_default;
-				}
+			Assert(!is_partition);
 
-				/* Mark the column as locally defined */
-				inhdef->is_local = true;
-			}
-			else
+			if (!MergeChildAttribute(newdef, newcol_attno, inh_columns))
 			{
 				/*
-				 * No, attach new column to result columns
+				 * No inherited attribute, attach new column to result
+				 * columns.
 				 */
 				inh_columns = lappend(inh_columns, newdef);
 			}
@@ -3419,6 +3274,182 @@ MergeCheckConstraint(List *constraints, const char *name, Node *expr)
 	return lappend(constraints, newcon);
 }
 
+/*
+ * MergeChildAttribute
+ *		Merge given child attribute definition into any inherited attribute with
+ *  the same name.
+ *
+ * Input arguments:
+ * 'newdef' is the column/attribute definition from the child table.
+ * 'newcol_attno' is the attribute number in child table's schema definition
+ * 'inh_columns' is the list of inherited ColumnDefs.
+ *
+ * Return value:
+ * True if the given attribute is merged into an inherited attribute with the
+ * same name. False if no matching inherited attribute is found. When returning
+ * true, matching ColumnDef in 'inh_columns' list is modified. Child
+ * attribute's ColumnDef remains unchanged.
+ *
+ * Notes:
+ *  (1) The attribute is merged according to the rules laid out in the prologue
+ *  of MergeAttributes().
+ *  (2) If matching inherited attribute exists but the child attribute can not
+ *  be merged into it, the function throws respective errors.
+ *  (3) A partition can not have its own column definitions. Hence this
+ *  function is applicable only to a regular inheritance child.
+ */
+static bool
+MergeChildAttribute(ColumnDef *newdef, int newcol_attno, List *inh_columns)
+{
+	char	   *attributeName = newdef->colname;
+	int			exist_attno;
+	ColumnDef  *inhdef;
+	Oid			inhtypeid,
+				newtypeid;
+	int32		inhtypmod,
+				newtypmod;
+	Oid			inhcollid,
+				newcollid;
+
+	/*
+	 * Does it match some inherited column?
+	 */
+	exist_attno = findAttrByName(attributeName, inh_columns);
+	if (exist_attno <= 0)
+		return false;
+
+	/*
+	 * Yes, try to merge the two column definitions.
+	 */
+	if (exist_attno == newcol_attno)
+		ereport(NOTICE,
+				(errmsg("merging column \"%s\" with inherited definition",
+						attributeName)));
+	else
+		ereport(NOTICE,
+				(errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
+				 errdetail("User-specified column moved to the position of the inherited column.")));
+	inhdef = list_nth_node(ColumnDef, inh_columns, exist_attno - 1);
+
+	/*
+	 * Must have the same type and typmod
+	 */
+	typenameTypeIdAndMod(NULL, inhdef->typeName, &inhtypeid, &inhtypmod);
+	typenameTypeIdAndMod(NULL, newdef->typeName, &newtypeid, &newtypmod);
+	if (inhtypeid != newtypeid || inhtypmod != newtypmod)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("column \"%s\" has a type conflict",
+						attributeName),
+				 errdetail("%s versus %s",
+						   format_type_with_typemod(inhtypeid, inhtypmod),
+						   format_type_with_typemod(newtypeid, newtypmod))));
+
+	/*
+	 * Must have the same collation
+	 */
+	inhcollid = GetColumnDefCollation(NULL, inhdef, inhtypeid);
+	newcollid = GetColumnDefCollation(NULL, newdef, newtypeid);
+	if (inhcollid != newcollid)
+		ereport(ERROR,
+				(errcode(ERRCODE_COLLATION_MISMATCH),
+				 errmsg("column \"%s\" has a collation conflict",
+						attributeName),
+				 errdetail("\"%s\" versus \"%s\"",
+						   get_collation_name(inhcollid),
+						   get_collation_name(newcollid))));
+
+	/*
+	 * Identity is never inherited by a regular inheritance child. Pick
+	 * child's identity definition if there's one.
+	 */
+	inhdef->identity = newdef->identity;
+
+	/*
+	 * Copy storage parameter
+	 */
+	if (inhdef->storage == 0)
+		inhdef->storage = newdef->storage;
+	else if (newdef->storage != 0 && inhdef->storage != newdef->storage)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("column \"%s\" has a storage parameter conflict",
+						attributeName),
+				 errdetail("%s versus %s",
+						   storage_name(inhdef->storage),
+						   storage_name(newdef->storage))));
+
+	/*
+	 * Copy compression parameter
+	 */
+	if (inhdef->compression == NULL)
+		inhdef->compression = newdef->compression;
+	else if (newdef->compression != NULL)
+	{
+		if (strcmp(inhdef->compression, newdef->compression) != 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("column \"%s\" has a compression method conflict",
+							attributeName),
+					 errdetail("%s versus %s", inhdef->compression, newdef->compression)));
+	}
+
+	/*
+	 * Merge of not-null constraints = OR 'em together
+	 */
+	inhdef->is_not_null |= newdef->is_not_null;
+
+	/*
+	 * Check for conflicts related to generated columns.
+	 *
+	 * If the parent column is generated, the child column will be made a
+	 * generated column if it isn't already.  If it is a generated column,
+	 * we'll take its generation expression in preference to the parent's.  We
+	 * must check that the child column doesn't specify a default value or
+	 * identity, which matches the rules for a single column in
+	 * parse_utilcmd.c.
+	 *
+	 * Conversely, if the parent column is not generated, the child column
+	 * can't be either.  (We used to allow that, but it results in being able
+	 * to override the generation expression via UPDATEs through the parent.)
+	 */
+	if (inhdef->generated)
+	{
+		if (newdef->raw_default && !newdef->generated)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
+					 errmsg("column \"%s\" inherits from generated column but specifies default",
+							inhdef->colname)));
+		if (newdef->identity)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
+					 errmsg("column \"%s\" inherits from generated column but specifies identity",
+							inhdef->colname)));
+	}
+	else
+	{
+		if (newdef->generated)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
+					 errmsg("child column \"%s\" specifies generation expression",
+							inhdef->colname),
+					 errhint("A child table column cannot be generated unless its parent column is.")));
+	}
+
+	/*
+	 * If new def has a default, override previous default
+	 */
+	if (newdef->raw_default != NULL)
+	{
+		inhdef->raw_default = newdef->raw_default;
+		inhdef->cooked_default = newdef->cooked_default;
+	}
+
+	/* Mark the column as locally defined */
+	inhdef->is_local = true;
+
+	return true;
+}
 
 /*
  * StoreCatalogInheritance
-- 
2.25.1

#23Peter Eisentraut
peter@eisentraut.org
In reply to: Ashutosh Bapat (#22)
Re: tablecmds.c/MergeAttributes() cleanup

On 24.01.24 07:27, Ashutosh Bapat wrote:

While working on identity support and now while looking at the
compression problem you referred to, I found MergeAttribute() to be
hard to read. It's hard to follow high level logic in that function
since the function is not modular. I took a stab at modularising a
part of it. Attached is the resulting patch series.

0001 is your patch as is
0002 is pgindent fix and also fixing what I think is a typo/thinko
from 0001. If you are fine with the changes, 0002 should be merged
into 0003.
0003 separates the part of code merging a child attribute to the
corresponding inherited attribute into its own function.
0004 does the same for code merging inherited attributes incrementally.

I have kept 0003 and 0004 separate in case we pick one and not the
other. But they can be committed as a single commit.

I have committed all this. These are great improvements.

(One little change I made to your 0003 and 0004 patches is that I kept
the check whether the new column matches an existing one by name in
MergeAttributes(). I found that pushing that down made the logic in
MergeAttributes() too hard to follow. But it's pretty much the same.)

#24Alexander Lakhin
exclusion@gmail.com
In reply to: Peter Eisentraut (#23)
Re: tablecmds.c/MergeAttributes() cleanup

Hello Peter,

26.01.2024 16:42, Peter Eisentraut wrote:

I have committed all this.  These are great improvements.

Please look at the segmentation fault triggered by the following query since
4d969b2f8:
CREATE TABLE t1(a text COMPRESSION pglz);
CREATE TABLE t2(a text);
CREATE TABLE t3() INHERITS(t1, t2);
NOTICE:  merging multiple inherited definitions of column "a"
server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.

Core was generated by `postgres: law regression [local] CREATE TABLE                                 '.
Program terminated with signal SIGSEGV, Segmentation fault.

(gdb) bt
#0  __strcmp_avx2 () at ../sysdeps/x86_64/multiarch/strcmp-avx2.S:116
#1  0x00005606fbcc9d52 in MergeAttributes (columns=0x0, supers=supers@entry=0x5606fe293d30, relpersistence=112 'p',
is_partition=false, supconstr=supconstr@entry=0x7fff4046d410, supnotnulls=supnotnulls@entry=0x7fff4046d418)
    at tablecmds.c:2811
#2  0x00005606fbccd764 in DefineRelation (stmt=stmt@entry=0x5606fe26a130, relkind=relkind@entry=114 'r', ownerId=10,
ownerId@entry=0, typaddress=typaddress@entry=0x0,
    queryString=queryString@entry=0x5606fe2695c0 "CREATE TABLE t3() INHERITS(t1, t2);") at tablecmds.c:885
...

Best regards,
Alexander

#25Ashutosh Bapat
ashutosh.bapat.oss@gmail.com
In reply to: Alexander Lakhin (#24)
Re: tablecmds.c/MergeAttributes() cleanup

Hi Alexander,

On Sun, Jan 28, 2024 at 1:30 PM Alexander Lakhin <exclusion@gmail.com> wrote:

Hello Peter,

26.01.2024 16:42, Peter Eisentraut wrote:

I have committed all this. These are great improvements.

Please look at the segmentation fault triggered by the following query since
4d969b2f8:
CREATE TABLE t1(a text COMPRESSION pglz);
CREATE TABLE t2(a text);
CREATE TABLE t3() INHERITS(t1, t2);
NOTICE: merging multiple inherited definitions of column "a"
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.

Core was generated by `postgres: law regression [local] CREATE TABLE '.
Program terminated with signal SIGSEGV, Segmentation fault.

(gdb) bt
#0 __strcmp_avx2 () at ../sysdeps/x86_64/multiarch/strcmp-avx2.S:116
#1 0x00005606fbcc9d52 in MergeAttributes (columns=0x0, supers=supers@entry=0x5606fe293d30, relpersistence=112 'p',
is_partition=false, supconstr=supconstr@entry=0x7fff4046d410, supnotnulls=supnotnulls@entry=0x7fff4046d418)
at tablecmds.c:2811
#2 0x00005606fbccd764 in DefineRelation (stmt=stmt@entry=0x5606fe26a130, relkind=relkind@entry=114 'r', ownerId=10,
ownerId@entry=0, typaddress=typaddress@entry=0x0,
queryString=queryString@entry=0x5606fe2695c0 "CREATE TABLE t3() INHERITS(t1, t2);") at tablecmds.c:885

This bug existed even before the refactoring.Happens because strcmp()
is called on NULL input (t2's compression is NULL). I already have a
fix for this and will be posting it in [1]/messages/by-id/24656cec-d6ef-4d15-8b5b-e8dfc9c833a7@eisentraut.org.

[1]: /messages/by-id/24656cec-d6ef-4d15-8b5b-e8dfc9c833a7@eisentraut.org

--
Best Wishes,
Ashutosh Bapat

#26Alexander Lakhin
exclusion@gmail.com
In reply to: Ashutosh Bapat (#25)
Re: tablecmds.c/MergeAttributes() cleanup

Hello,

30.01.2024 09:22, Ashutosh Bapat wrote:

Please look at the segmentation fault triggered by the following query since
4d969b2f8:
CREATE TABLE t1(a text COMPRESSION pglz);
CREATE TABLE t2(a text);
CREATE TABLE t3() INHERITS(t1, t2);
NOTICE: merging multiple inherited definitions of column "a"
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.

Core was generated by `postgres: law regression [local] CREATE TABLE '.
Program terminated with signal SIGSEGV, Segmentation fault.

(gdb) bt
#0 __strcmp_avx2 () at ../sysdeps/x86_64/multiarch/strcmp-avx2.S:116
#1 0x00005606fbcc9d52 in MergeAttributes (columns=0x0, supers=supers@entry=0x5606fe293d30, relpersistence=112 'p',
is_partition=false, supconstr=supconstr@entry=0x7fff4046d410, supnotnulls=supnotnulls@entry=0x7fff4046d418)
at tablecmds.c:2811
#2 0x00005606fbccd764 in DefineRelation (stmt=stmt@entry=0x5606fe26a130, relkind=relkind@entry=114 'r', ownerId=10,
ownerId@entry=0, typaddress=typaddress@entry=0x0,
queryString=queryString@entry=0x5606fe2695c0 "CREATE TABLE t3() INHERITS(t1, t2);") at tablecmds.c:885

This bug existed even before the refactoring.Happens because strcmp()
is called on NULL input (t2's compression is NULL). I already have a
fix for this and will be posting it in [1].

[1] /messages/by-id/24656cec-d6ef-4d15-8b5b-e8dfc9c833a7@eisentraut.org

Now that that fix is closed with RwF [1], shouldn't this crash issue be
added to Open Items for v17?
(I couldn't reproduce the crash on 4d969b2f8~1 nor on REL_16_STABLE.)

https://commitfest.postgresql.org/47/4813/

Best regards,
Alexander

#27Ashutosh Bapat
ashutosh.bapat.oss@gmail.com
In reply to: Alexander Lakhin (#26)
Re: tablecmds.c/MergeAttributes() cleanup

On Sat, Apr 20, 2024 at 9:30 AM Alexander Lakhin <exclusion@gmail.com>
wrote:

Hello,

30.01.2024 09:22, Ashutosh Bapat wrote:

Please look at the segmentation fault triggered by the following query

since

4d969b2f8:
CREATE TABLE t1(a text COMPRESSION pglz);
CREATE TABLE t2(a text);
CREATE TABLE t3() INHERITS(t1, t2);
NOTICE: merging multiple inherited definitions of column "a"
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.

Core was generated by `postgres: law regression [local] CREATE TABLE

'.

Program terminated with signal SIGSEGV, Segmentation fault.

(gdb) bt
#0 __strcmp_avx2 () at ../sysdeps/x86_64/multiarch/strcmp-avx2.S:116
#1 0x00005606fbcc9d52 in MergeAttributes (columns=0x0,

supers=supers@entry=0x5606fe293d30, relpersistence=112 'p',

is_partition=false, supconstr=supconstr@entry=0x7fff4046d410,

supnotnulls=supnotnulls@entry=0x7fff4046d418)

at tablecmds.c:2811
#2 0x00005606fbccd764 in DefineRelation (stmt=stmt@entry=0x5606fe26a130,

relkind=relkind@entry=114 'r', ownerId=10,

ownerId@entry=0, typaddress=typaddress@entry=0x0,
queryString=queryString@entry=0x5606fe2695c0 "CREATE TABLE t3()

INHERITS(t1, t2);") at tablecmds.c:885

This bug existed even before the refactoring.Happens because strcmp()
is called on NULL input (t2's compression is NULL). I already have a
fix for this and will be posting it in [1].

[1]

/messages/by-id/24656cec-d6ef-4d15-8b5b-e8dfc9c833a7@eisentraut.org

Now that that fix is closed with RwF [1], shouldn't this crash issue be
added to Open Items for v17?
(I couldn't reproduce the crash on 4d969b2f8~1 nor on REL_16_STABLE.)

https://commitfest.postgresql.org/47/4813/

Yes please. Probably this issue surfaced again after we reverted
compression and storage fix? Please If that's the case, please add it to
the open items.

--
Best Wishes,
Ashutosh Bapat

#28Robert Haas
robertmhaas@gmail.com
In reply to: Ashutosh Bapat (#27)
Re: tablecmds.c/MergeAttributes() cleanup

On Sat, Apr 20, 2024 at 12:17 AM Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:

Yes please. Probably this issue surfaced again after we reverted compression and storage fix? Please If that's the case, please add it to the open items.

This is still on the open items list and I'm not clear who, if anyone,
is working on fixing it.

It would be good if someone fixed it. :-)

--
Robert Haas
EDB: http://www.enterprisedb.com

#29Ashutosh Bapat
ashutosh.bapat.oss@gmail.com
In reply to: Robert Haas (#28)
1 attachment(s)
Re: tablecmds.c/MergeAttributes() cleanup

On Mon, Apr 29, 2024 at 6:46 PM Robert Haas <robertmhaas@gmail.com> wrote:

On Sat, Apr 20, 2024 at 12:17 AM Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:

Yes please. Probably this issue surfaced again after we reverted

compression and storage fix? Please If that's the case, please add it to
the open items.

This is still on the open items list and I'm not clear who, if anyone,
is working on fixing it.

It would be good if someone fixed it. :-)

Here's a patch fixing it.

I have added the reproducer provided by Alexander as a test. I thought of
improving that test further to test the compression of the inherited table
but did not implement it since we haven't documented the behaviour of
compression with inheritance. Defining and implementing compression
behaviour for inherited tables was the goal
of 0413a556990ba628a3de8a0b58be020fd9a14ed0, which has been reverted.

--
Best Wishes,
Ashutosh Bapat

Attachments:

0001-Fix-segmentation-fault-in-MergeInheritedAtt-20240430.patchtext/x-patch; charset=US-ASCII; name=0001-Fix-segmentation-fault-in-MergeInheritedAtt-20240430.patchDownload
From 7c1ff7b17933eef9523486a2c0a054836db9cf24 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Tue, 30 Apr 2024 11:19:43 +0530
Subject: [PATCH] Fix segmentation fault in MergeInheritedAttribute()

While converting a pg_attribute tuple into a ColumnDef, ColumnDef::compression
remains NULL if there is no compression method set fot the attribute. Calling
strcmp() with NULL ColumnDef::compression, when comparing compression methods
of parents, causes segmentation fault in MergeInheritedAttribute(). Skip
comparing compression methods if either of them is NULL.

Author: Ashutosh Bapat
Discussion: https://www.postgresql.org/message-id/b22a6834-aacb-7b18-0424-a3f5fe889667%40gmail.com
---
 src/backend/commands/tablecmds.c          | 16 ++++++++++------
 src/test/regress/expected/compression.out | 10 +++++++---
 src/test/regress/sql/compression.sql      |  8 +++++---
 3 files changed, 22 insertions(+), 12 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3556240c8e..e29f96e357 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -3430,12 +3430,16 @@ MergeInheritedAttribute(List *inh_columns,
 	 */
 	if (prevdef->compression == NULL)
 		prevdef->compression = newdef->compression;
-	else if (strcmp(prevdef->compression, newdef->compression) != 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATATYPE_MISMATCH),
-				 errmsg("column \"%s\" has a compression method conflict",
-						attributeName),
-				 errdetail("%s versus %s", prevdef->compression, newdef->compression)));
+	else if (newdef->compression != NULL)
+	{
+		if (strcmp(prevdef->compression, newdef->compression) != 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("column \"%s\" has a compression method conflict",
+							attributeName),
+					 errdetail("%s versus %s",
+							   prevdef->compression, newdef->compression)));
+	}
 
 	/*
 	 * Check for GENERATED conflicts
diff --git a/src/test/regress/expected/compression.out b/src/test/regress/expected/compression.out
index 834b7555cb..4dd9ee7200 100644
--- a/src/test/regress/expected/compression.out
+++ b/src/test/regress/expected/compression.out
@@ -223,15 +223,18 @@ SELECT pg_column_compression(f1) FROM cmpart2;
  pglz
 (1 row)
 
--- test compression with inheritance, error
-CREATE TABLE cminh() INHERITS(cmdata, cmdata1);
+-- test compression with inheritance
+CREATE TABLE cminh() INHERITS(cmdata, cmdata1); -- error
 NOTICE:  merging multiple inherited definitions of column "f1"
 ERROR:  column "f1" has a compression method conflict
 DETAIL:  pglz versus lz4
-CREATE TABLE cminh(f1 TEXT COMPRESSION lz4) INHERITS(cmdata);
+CREATE TABLE cminh(f1 TEXT COMPRESSION lz4) INHERITS(cmdata); -- error
 NOTICE:  merging column "f1" with inherited definition
 ERROR:  column "f1" has a compression method conflict
 DETAIL:  pglz versus lz4
+CREATE TABLE cmdata3(f1 text);
+CREATE TABLE cminh() INHERITS (cmdata, cmdata3);
+NOTICE:  merging multiple inherited definitions of column "f1"
 -- test default_toast_compression GUC
 SET default_toast_compression = '';
 ERROR:  invalid value for parameter "default_toast_compression": ""
@@ -251,6 +254,7 @@ INSERT INTO cmdata VALUES (repeat('123456789', 4004));
  f1     | text |           |          |         | extended | lz4         |              | 
 Indexes:
     "idx" btree (f1)
+Child tables: cminh
 
 SELECT pg_column_compression(f1) FROM cmdata;
  pg_column_compression 
diff --git a/src/test/regress/sql/compression.sql b/src/test/regress/sql/compression.sql
index 7179a5002e..490595fcfb 100644
--- a/src/test/regress/sql/compression.sql
+++ b/src/test/regress/sql/compression.sql
@@ -93,9 +93,11 @@ INSERT INTO cmpart VALUES (repeat('123456789', 4004));
 SELECT pg_column_compression(f1) FROM cmpart1;
 SELECT pg_column_compression(f1) FROM cmpart2;
 
--- test compression with inheritance, error
-CREATE TABLE cminh() INHERITS(cmdata, cmdata1);
-CREATE TABLE cminh(f1 TEXT COMPRESSION lz4) INHERITS(cmdata);
+-- test compression with inheritance
+CREATE TABLE cminh() INHERITS(cmdata, cmdata1); -- error
+CREATE TABLE cminh(f1 TEXT COMPRESSION lz4) INHERITS(cmdata); -- error
+CREATE TABLE cmdata3(f1 text);
+CREATE TABLE cminh() INHERITS (cmdata, cmdata3);
 
 -- test default_toast_compression GUC
 SET default_toast_compression = '';
-- 
2.34.1

#30Robert Haas
robertmhaas@gmail.com
In reply to: Ashutosh Bapat (#29)
Re: tablecmds.c/MergeAttributes() cleanup

On Tue, Apr 30, 2024 at 2:19 AM Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:

On Mon, Apr 29, 2024 at 6:46 PM Robert Haas <robertmhaas@gmail.com> wrote:

On Sat, Apr 20, 2024 at 12:17 AM Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:

Yes please. Probably this issue surfaced again after we reverted compression and storage fix? Please If that's the case, please add it to the open items.

This is still on the open items list and I'm not clear who, if anyone,
is working on fixing it.

It would be good if someone fixed it. :-)

Here's a patch fixing it.

I have added the reproducer provided by Alexander as a test. I thought of improving that test further to test the compression of the inherited table but did not implement it since we haven't documented the behaviour of compression with inheritance. Defining and implementing compression behaviour for inherited tables was the goal of 0413a556990ba628a3de8a0b58be020fd9a14ed0, which has been reverted.

I took a look at this patch. Currently this case crashes:

CREATE TABLE cmdata(f1 text COMPRESSION pglz);
CREATE TABLE cmdata3(f1 text);
CREATE TABLE cminh() INHERITS (cmdata, cmdata3);

The patch makes this succeed, but I was initially unclear why it
didn't make it fail with an error instead: you can argue that cmdata
has pglz and cmdata3 has default and those are different. It seems
that prior precedent goes both ways -- we treat the absence of a
STORAGE specification as STORAGE EXTENDED and it conflicts with an
explicit storage specification on some other inheritance parent - but
on the other hand, we treat the absence of a default as compatible
with any explicit default, similar to what happens here. But I
eventually realized that you're just putting back behavior that we had
in previous releases: pre-v17, the code already works the way this
patch makes it do, and MergeChildAttribute() is already coded similar
to this. As Alexander Lakhin said upthread, 4d969b2f8 seems to have
broken this.

So now I think this is committable, but I can't do it now because I
won't be around for the next few hours in case the buildfarm blows up.
I can do it tomorrow, or perhaps Peter would like to handle it since
it seems to have been his commit that introduced the issue.

--
Robert Haas
EDB: http://www.enterprisedb.com

#31Peter Eisentraut
peter@eisentraut.org
In reply to: Robert Haas (#30)
Re: tablecmds.c/MergeAttributes() cleanup

On 30.04.24 21:48, Robert Haas wrote:

I took a look at this patch. Currently this case crashes:

CREATE TABLE cmdata(f1 text COMPRESSION pglz);
CREATE TABLE cmdata3(f1 text);
CREATE TABLE cminh() INHERITS (cmdata, cmdata3);

The patch makes this succeed, but I was initially unclear why it
didn't make it fail with an error instead: you can argue that cmdata
has pglz and cmdata3 has default and those are different. It seems
that prior precedent goes both ways -- we treat the absence of a
STORAGE specification as STORAGE EXTENDED and it conflicts with an
explicit storage specification on some other inheritance parent - but
on the other hand, we treat the absence of a default as compatible
with any explicit default, similar to what happens here.

The actual behavior here is arguably not ideal. It was the purpose of
the other thread mentioned upthread to improve that, but that was not
successful for the time being.

So now I think this is committable, but I can't do it now because I
won't be around for the next few hours in case the buildfarm blows up.
I can do it tomorrow, or perhaps Peter would like to handle it since
it seems to have been his commit that introduced the issue.

I have committed it now.

#32Ashutosh Bapat
ashutosh.bapat.oss@gmail.com
In reply to: Peter Eisentraut (#31)
Re: tablecmds.c/MergeAttributes() cleanup

On Fri, May 3, 2024 at 2:47 PM Peter Eisentraut <peter@eisentraut.org>
wrote:

On 30.04.24 21:48, Robert Haas wrote:

I took a look at this patch. Currently this case crashes:

CREATE TABLE cmdata(f1 text COMPRESSION pglz);
CREATE TABLE cmdata3(f1 text);
CREATE TABLE cminh() INHERITS (cmdata, cmdata3);

The patch makes this succeed, but I was initially unclear why it
didn't make it fail with an error instead: you can argue that cmdata
has pglz and cmdata3 has default and those are different. It seems
that prior precedent goes both ways -- we treat the absence of a
STORAGE specification as STORAGE EXTENDED and it conflicts with an
explicit storage specification on some other inheritance parent - but
on the other hand, we treat the absence of a default as compatible
with any explicit default, similar to what happens here.

The actual behavior here is arguably not ideal. It was the purpose of
the other thread mentioned upthread to improve that, but that was not
successful for the time being.

So now I think this is committable, but I can't do it now because I
won't be around for the next few hours in case the buildfarm blows up.
I can do it tomorrow, or perhaps Peter would like to handle it since
it seems to have been his commit that introduced the issue.

I have committed it now.

Thanks Peter.

--
Best Wishes,
Ashutosh Bapat