public inbox for [email protected]help / color / mirror / Atom feed
[PATCH 2/7] Write WAL for empty nbtree index build 29+ messages / 6 participants [nested] [flat]
* [PATCH 2/7] Write WAL for empty nbtree index build @ 2018-10-11 01:03 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 29+ messages in thread From: Kyotaro Horiguchi @ 2018-10-11 01:03 UTC (permalink / raw) After relation truncation indexes are also rebuild. It doesn't emit WAL in minimal mode and if truncation happened within its creation transaction, crash recovery leaves an empty index heap, which is considered broken. This patch forces to emit WAL when an index_build turns into empty nbtree index. --- src/backend/access/nbtree/nbtsort.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c index 9ac4c1e1c0..a31d58025f 100644 --- a/src/backend/access/nbtree/nbtsort.c +++ b/src/backend/access/nbtree/nbtsort.c @@ -654,8 +654,16 @@ _bt_blwritepage(BTWriteState *wstate, Page page, BlockNumber blkno) /* Ensure rd_smgr is open (could have been closed by relcache flush!) */ RelationOpenSmgr(wstate->index); - /* XLOG stuff */ - if (wstate->btws_use_wal) + /* XLOG stuff + * + * Even when wal_level is minimal, WAL is required here if truncation + * happened after being created in the same transaction. This is hacky but + * we cannot use BufferNeedsWAL() stuff for nbtree since it can emit + * atomic WAL records on multiple buffers. + */ + if (wstate->btws_use_wal || + (RelationNeedsWAL(wstate->index) && + (blkno == BTREE_METAPAGE && BTPageGetMeta(page)->btm_root == 0))) { /* We use the heap NEWPAGE record type for this */ log_newpage(&wstate->index->rd_node, MAIN_FORKNUM, blkno, page, true); -- 2.16.3 ----Next_Part(Fri_Apr_05_12_55_20_2019_986)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v11-0003-Move-XLOG-stuff-from-heap_insert-and-heap_delete.patch" ^ permalink raw reply [nested|flat] 29+ messages in thread
* [PATCH 2/8] Write WAL for empty nbtree index build @ 2018-10-11 01:03 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 29+ messages in thread From: Kyotaro Horiguchi @ 2018-10-11 01:03 UTC (permalink / raw) After relation truncation indexes are also rebuild. It doesn't emit WAL in minimal mode and if truncation happened within its creation transaction, crash recovery leaves an empty index heap, which is considered broken. This patch forces to emit WAL when an index_build turns into empty nbtree index. --- src/backend/access/nbtree/nbtsort.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c index 46e0831834..e65d4aab0f 100644 --- a/src/backend/access/nbtree/nbtsort.c +++ b/src/backend/access/nbtree/nbtsort.c @@ -622,8 +622,15 @@ _bt_blwritepage(BTWriteState *wstate, Page page, BlockNumber blkno) /* Ensure rd_smgr is open (could have been closed by relcache flush!) */ RelationOpenSmgr(wstate->index); - /* XLOG stuff */ - if (wstate->btws_use_wal) + /* XLOG stuff + * + * Even if minimal mode, WAL is required here if truncation happened after + * being created in the same transaction. It is not needed otherwise but + * we don't bother identifying the case precisely. + */ + if (wstate->btws_use_wal || + (RelationNeedsWAL(wstate->index) && + (blkno == BTREE_METAPAGE && BTPageGetMeta(page)->btm_root == 0))) { /* We use the heap NEWPAGE record type for this */ log_newpage(&wstate->index->rd_node, MAIN_FORKNUM, blkno, page, true); -- 2.16.3 ----Next_Part(Tue_Mar_26_16_35_07_2019_128)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v9-0003-Move-XLOG-stuff-from-heap_insert-and-heap_delete.patch" ^ permalink raw reply [nested|flat] 29+ messages in thread
* [PATCH 2/7] Write WAL for empty nbtree index build @ 2018-10-11 01:03 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 29+ messages in thread From: Kyotaro Horiguchi @ 2018-10-11 01:03 UTC (permalink / raw) After relation truncation indexes are also rebuild. It doesn't emit WAL in minimal mode and if truncation happened within its creation transaction, crash recovery leaves an empty index heap, which is considered broken. This patch forces to emit WAL when an index_build turns into empty nbtree index. --- src/backend/access/nbtree/nbtsort.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c index 2762a2d548..70fe3bec32 100644 --- a/src/backend/access/nbtree/nbtsort.c +++ b/src/backend/access/nbtree/nbtsort.c @@ -622,8 +622,15 @@ _bt_blwritepage(BTWriteState *wstate, Page page, BlockNumber blkno) /* Ensure rd_smgr is open (could have been closed by relcache flush!) */ RelationOpenSmgr(wstate->index); - /* XLOG stuff */ - if (wstate->btws_use_wal) + /* XLOG stuff + * + * Even if minimal mode, WAL is required here if truncation happened after + * being created in the same transaction. It is not needed otherwise but + * we don't bother identifying the case precisely. + */ + if (wstate->btws_use_wal || + (RelationNeedsWAL(wstate->index) && + (blkno == BTREE_METAPAGE && BTPageGetMeta(page)->btm_root == 0))) { /* We use the heap NEWPAGE record type for this */ log_newpage(&wstate->index->rd_node, MAIN_FORKNUM, blkno, page, true); -- 2.16.3 ----Next_Part(Mon_Mar_25_21_32_04_2019_838)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v8-0003-Move-XLOG-stuff-from-heap_insert-and-heap_delete.patch" ^ permalink raw reply [nested|flat] 29+ messages in thread
* [PATCH 2/7] Write WAL for empty nbtree index build @ 2018-10-11 01:03 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 29+ messages in thread From: Kyotaro Horiguchi @ 2018-10-11 01:03 UTC (permalink / raw) After relation truncation indexes are also rebuild. It doesn't emit WAL in minimal mode and if truncation happened within its creation transaction, crash recovery leaves an empty index heap, which is considered broken. This patch forces to emit WAL when an index_build turns into empty nbtree index. --- src/backend/access/nbtree/nbtsort.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c index 14d9545768..5551a9c227 100644 --- a/src/backend/access/nbtree/nbtsort.c +++ b/src/backend/access/nbtree/nbtsort.c @@ -622,8 +622,16 @@ _bt_blwritepage(BTWriteState *wstate, Page page, BlockNumber blkno) /* Ensure rd_smgr is open (could have been closed by relcache flush!) */ RelationOpenSmgr(wstate->index); - /* XLOG stuff */ - if (wstate->btws_use_wal) + /* XLOG stuff + * + * Even when wal_level is minimal, WAL is required here if truncation + * happened after being created in the same transaction. This is hacky but + * we cannot use BufferNeedsWAL() stuff for nbtree since it can emit + * atomic WAL records on multiple buffers. + */ + if (wstate->btws_use_wal || + (RelationNeedsWAL(wstate->index) && + (blkno == BTREE_METAPAGE && BTPageGetMeta(page)->btm_root == 0))) { /* We use the heap NEWPAGE record type for this */ log_newpage(&wstate->index->rd_node, MAIN_FORKNUM, blkno, page, true); -- 2.16.3 ----Next_Part(Tue_Apr_02_19_54_06_2019_801)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v10-0003-Move-XLOG-stuff-from-heap_insert-and-heap_delete.patch" ^ permalink raw reply [nested|flat] 29+ messages in thread
* [PATCH 2/4] Write WAL for empty nbtree index build @ 2018-10-11 01:03 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 29+ messages in thread From: Kyotaro Horiguchi @ 2018-10-11 01:03 UTC (permalink / raw) After relation truncation indexes are also rebuild. It doesn't emit WAL in minimal mode and if truncation happened within its creation transaction, crash recovery leaves an empty index heap, which is considered broken. This patch forces to emit WAL when an index_build turns into empty nbtree index. --- src/backend/access/nbtree/nbtsort.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c index dc398e1186..70d4380533 100644 --- a/src/backend/access/nbtree/nbtsort.c +++ b/src/backend/access/nbtree/nbtsort.c @@ -611,8 +611,14 @@ _bt_blwritepage(BTWriteState *wstate, Page page, BlockNumber blkno) /* Ensure rd_smgr is open (could have been closed by relcache flush!) */ RelationOpenSmgr(wstate->index); - /* XLOG stuff */ - if (wstate->btws_use_wal) + /* XLOG stuff + * + * Even if minimal mode, WAL is required here if truncation happened after + * being created in the same transaction. It is not needed otherwise but + * we don't bother identifying the case precisely. + */ + if (wstate->btws_use_wal || + (blkno == BTREE_METAPAGE && BTPageGetMeta(page)->btm_root == 0)) { /* We use the heap NEWPAGE record type for this */ log_newpage(&wstate->index->rd_node, MAIN_FORKNUM, blkno, page, true); @@ -1056,6 +1062,11 @@ _bt_uppershutdown(BTWriteState *wstate, BTPageState *state) * set to point to "P_NONE"). This changes the index to the "valid" state * by filling in a valid magic number in the metapage. */ + /* + * If no tuple was inserted, it's possible that we are truncating a + * relation. We need to emit WAL for the metapage in the case. However it + * is not required elsewise, + */ metapage = (Page) palloc(BLCKSZ); _bt_initmetapage(metapage, rootblkno, rootlevel); _bt_blwritepage(wstate, metapage, BTREE_METAPAGE); -- 2.16.3 ----Next_Part(Mon_Mar_04_12_24_48_2019_788)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v7-0003-Add-infrastructure-to-WAL-logging-skip-feature.patch" ^ permalink raw reply [nested|flat] 29+ messages in thread
* Re: tablecmds.c/MergeAttributes() cleanup @ 2023-09-19 13:11 Peter Eisentraut <[email protected]> 0 siblings, 1 reply; 29+ messages in thread From: Peter Eisentraut @ 2023-09-19 13:11 UTC (permalink / raw) To: Alvaro Herrera <[email protected]>; +Cc: pgsql-hackers 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. From 28e4dbba35fc3162c13f5896551921896cf30d1c Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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 From c5653b746bd8611c7297fb43013ab4f8adee8b40 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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 From b558f35954be5c232d4ff2bb23313d6ce0cceb49 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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 From 0a5593983555e669a9fab8e366c6a36b26ebac14 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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 From 402174806f36bb1a240f5afbeacad3ffd9292a9a Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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 From ad08dbbb459683da6206afb5cdc11164bbb94fc2 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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 From 65d2d76d52b049b2e13a16018291225d844cefe0 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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 From 5b8ffd08e58eda3176f6c6caf5b64c74a902cda4 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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 From a1000260efb736f2c1e6c161a1475676228a8be8 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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 Attachments: [text/plain] v3-0001-Clean-up-MergeAttributesIntoExisting.patch (12.4K, ../../[email protected]/2-v3-0001-Clean-up-MergeAttributesIntoExisting.patch) download | inline diff: From 28e4dbba35fc3162c13f5896551921896cf30d1c Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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 [text/plain] v3-0002-Clean-up-MergeCheckConstraint.patch (3.9K, ../../[email protected]/3-v3-0002-Clean-up-MergeCheckConstraint.patch) download | inline diff: From c5653b746bd8611c7297fb43013ab4f8adee8b40 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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 [text/plain] v3-0003-MergeAttributes-and-related-variable-renaming.patch (15.2K, ../../[email protected]/4-v3-0003-MergeAttributes-and-related-variable-renaming.patch) download | inline diff: From b558f35954be5c232d4ff2bb23313d6ce0cceb49 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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 [text/plain] v3-0004-Add-TupleDescGetDefault.patch (5.0K, ../../[email protected]/5-v3-0004-Add-TupleDescGetDefault.patch) download | inline diff: From 0a5593983555e669a9fab8e366c6a36b26ebac14 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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 [text/plain] v3-0005-Push-attidentity-and-attgenerated-handling-into-B.patch (1.6K, ../../[email protected]/6-v3-0005-Push-attidentity-and-attgenerated-handling-into-B.patch) download | inline diff: From 402174806f36bb1a240f5afbeacad3ffd9292a9a Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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 [text/plain] v3-0006-Move-BuildDescForRelation-from-tupdesc.c-to-table.patch (8.7K, ../../[email protected]/7-v3-0006-Move-BuildDescForRelation-from-tupdesc.c-to-table.patch) download | inline diff: From ad08dbbb459683da6206afb5cdc11164bbb94fc2 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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 [text/plain] v3-0007-Push-attcompression-and-attstorage-handling-into-.patch (1.8K, ../../[email protected]/8-v3-0007-Push-attcompression-and-attstorage-handling-into-.patch) download | inline diff: From 65d2d76d52b049b2e13a16018291225d844cefe0 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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 [text/plain] v3-0008-Refactor-ATExecAddColumn-to-use-BuildDescForRelat.patch (7.1K, ../../[email protected]/9-v3-0008-Refactor-ATExecAddColumn-to-use-BuildDescForRelat.patch) download | inline diff: From 5b8ffd08e58eda3176f6c6caf5b64c74a902cda4 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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 [text/plain] v3-0009-MergeAttributes-convert-pg_attribute-back-to-Colu.patch (16.4K, ../../[email protected]/10-v3-0009-MergeAttributes-convert-pg_attribute-back-to-Colu.patch) download | inline diff: From a1000260efb736f2c1e6c161a1475676228a8be8 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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 ^ permalink raw reply [nested|flat] 29+ messages in thread
* Re: tablecmds.c/MergeAttributes() cleanup @ 2023-10-05 15:49 Peter Eisentraut <[email protected]> parent: Peter Eisentraut <[email protected]> 0 siblings, 1 reply; 29+ messages in thread From: Peter Eisentraut @ 2023-10-05 15:49 UTC (permalink / raw) To: Alvaro Herrera <[email protected]>; +Cc: pgsql-hackers 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. ^ permalink raw reply [nested|flat] 29+ messages in thread
* Re: tablecmds.c/MergeAttributes() cleanup @ 2023-12-06 08:23 Peter Eisentraut <[email protected]> parent: Peter Eisentraut <[email protected]> 0 siblings, 1 reply; 29+ messages in thread From: Peter Eisentraut @ 2023-12-06 08:23 UTC (permalink / raw) To: Alvaro Herrera <[email protected]>; +Cc: pgsql-hackers 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] should be fixed first, but to make that fix simpler, I would like the first patch from here. [0]: https://www.postgresql.org/message-id/flat/24656cec-d6ef-4d15-8b5b-e8dfc9c833a7%40eisentraut.org From 42615f4c523920c7ae916ba0b1819cc6a5e622b1 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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/[email protected] --- 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 From 8fe0e8157d932c33ab06e0eccdfa0e2634e27935 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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/[email protected] --- 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 Attachments: [text/plain] v4-0001-Refactor-ATExecAddColumn-to-use-BuildDescForRelat.patch (7.2K, ../../[email protected]/2-v4-0001-Refactor-ATExecAddColumn-to-use-BuildDescForRelat.patch) download | inline diff: From 42615f4c523920c7ae916ba0b1819cc6a5e622b1 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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/[email protected] --- 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 [text/plain] v4-0002-MergeAttributes-convert-pg_attribute-back-to-Colu.patch (16.6K, ../../[email protected]/3-v4-0002-MergeAttributes-convert-pg_attribute-back-to-Colu.patch) download | inline diff: From 8fe0e8157d932c33ab06e0eccdfa0e2634e27935 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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/[email protected] --- 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 ^ permalink raw reply [nested|flat] 29+ messages in thread
* Re: tablecmds.c/MergeAttributes() cleanup @ 2024-01-22 12:43 Peter Eisentraut <[email protected]> parent: Peter Eisentraut <[email protected]> 0 siblings, 1 reply; 29+ messages in thread From: Peter Eisentraut @ 2024-01-22 12:43 UTC (permalink / raw) To: pgsql-hackers; +Cc: Alvaro Herrera <[email protected]> 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]: > https://www.postgresql.org/message-id/flat/24656cec-d6ef-4d15-8b5b-e8dfc9c833a7%40eisentraut.org The remaining patch in this series needed a rebase and adjustment. The above precondition still applies. From c4c4462fa58c73fc383c4935c8d2753b7a0b4c9d Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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/[email protected] --- 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 Attachments: [text/plain] v5-0001-MergeAttributes-convert-pg_attribute-back-to-Colu.patch (17.1K, ../../[email protected]/2-v5-0001-MergeAttributes-convert-pg_attribute-back-to-Colu.patch) download | inline diff: From c4c4462fa58c73fc383c4935c8d2753b7a0b4c9d Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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/[email protected] --- 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 ^ permalink raw reply [nested|flat] 29+ messages in thread
* Re: tablecmds.c/MergeAttributes() cleanup @ 2024-01-24 06:27 Ashutosh Bapat <[email protected]> parent: Peter Eisentraut <[email protected]> 0 siblings, 1 reply; 29+ messages in thread From: Ashutosh Bapat @ 2024-01-24 06:27 UTC (permalink / raw) To: Peter Eisentraut <[email protected]>; +Cc: pgsql-hackers; Alvaro Herrera <[email protected]> Hi Peter, On Mon, Jan 22, 2024 at 6:13 PM Peter Eisentraut <[email protected]> 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]: > > https://www.postgresql.org/message-id/flat/24656cec-d6ef-4d15-8b5b-e8dfc9c833a7%40eisentraut.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: [text/x-patch] 0002-Mark-NULL-constraint-in-merged-definition-i-20240124.patch (1.5K, ../../CAExHW5vz7A-skzt05=4frFx9-VPjfjK4jKQZT7ufRNh4J7=xmQ@mail.gmail.com/2-0002-Mark-NULL-constraint-in-merged-definition-i-20240124.patch) download | inline diff: From 2553d082b16db2c54f2e1e4a66f1a57ecabf3c1e Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <[email protected]> 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 [text/x-patch] 0004-Separate-function-to-merge-next-parent-attr-20240124.patch (14.5K, ../../CAExHW5vz7A-skzt05=4frFx9-VPjfjK4jKQZT7ufRNh4J7=xmQ@mail.gmail.com/3-0004-Separate-function-to-merge-next-parent-attr-20240124.patch) download | inline diff: From 092828a7d7274b48bf33c88863a97585bff80ebb Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <[email protected]> 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 [text/x-patch] 0001-MergeAttributes-convert-pg_attribute-back-t-20240124.patch (17.1K, ../../CAExHW5vz7A-skzt05=4frFx9-VPjfjK4jKQZT7ufRNh4J7=xmQ@mail.gmail.com/4-0001-MergeAttributes-convert-pg_attribute-back-t-20240124.patch) download | inline diff: From 6d6c85d9f08a1d631e4429e9d2c8b3e97b3e1337 Mon Sep 17 00:00:00 2001 From: Peter Eisentraut <[email protected]> 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/[email protected] --- 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 [text/x-patch] 0003-Separate-function-to-merge-a-child-attribut-20240124.patch (13.4K, ../../CAExHW5vz7A-skzt05=4frFx9-VPjfjK4jKQZT7ufRNh4J7=xmQ@mail.gmail.com/5-0003-Separate-function-to-merge-a-child-attribut-20240124.patch) download | inline diff: From e8993ab2e00db5f8260ef9d9cafe9fbe069d54a2 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <[email protected]> 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 ^ permalink raw reply [nested|flat] 29+ messages in thread
* Re: tablecmds.c/MergeAttributes() cleanup @ 2024-01-26 13:42 Peter Eisentraut <[email protected]> parent: Ashutosh Bapat <[email protected]> 0 siblings, 1 reply; 29+ messages in thread From: Peter Eisentraut @ 2024-01-26 13:42 UTC (permalink / raw) To: Ashutosh Bapat <[email protected]>; +Cc: pgsql-hackers; Alvaro Herrera <[email protected]> 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.) ^ permalink raw reply [nested|flat] 29+ messages in thread
* Re: tablecmds.c/MergeAttributes() cleanup @ 2024-01-28 08:00 Alexander Lakhin <[email protected]> parent: Peter Eisentraut <[email protected]> 0 siblings, 1 reply; 29+ messages in thread From: Alexander Lakhin @ 2024-01-28 08:00 UTC (permalink / raw) To: Peter Eisentraut <[email protected]>; Ashutosh Bapat <[email protected]>; +Cc: pgsql-hackers; Alvaro Herrera <[email protected]> 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 ^ permalink raw reply [nested|flat] 29+ messages in thread
* Re: tablecmds.c/MergeAttributes() cleanup @ 2024-01-30 06:22 Ashutosh Bapat <[email protected]> parent: Alexander Lakhin <[email protected]> 0 siblings, 1 reply; 29+ messages in thread From: Ashutosh Bapat @ 2024-01-30 06:22 UTC (permalink / raw) To: Alexander Lakhin <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; pgsql-hackers; Alvaro Herrera <[email protected]> Hi Alexander, On Sun, Jan 28, 2024 at 1:30 PM Alexander Lakhin <[email protected]> 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]. [1] https://www.postgresql.org/message-id/flat/24656cec-d6ef-4d15-8b5b-e8dfc9c833a7%40eisentraut.org -- Best Wishes, Ashutosh Bapat ^ permalink raw reply [nested|flat] 29+ messages in thread
* [PATCH v16] Add CopyFromRoutine/CopyToRountine @ 2024-03-04 04:52 Sutou Kouhei <[email protected]> 0 siblings, 0 replies; 29+ messages in thread From: Sutou Kouhei @ 2024-03-04 04:52 UTC (permalink / raw) They are for implementing custom COPY FROM/TO format. But this is not enough to implement custom COPY FROM/TO format yet. We'll export some APIs to receive/send data and add "format" option to COPY FROM/TO later. Existing text/csv/binary format implementations don't use CopyFromRoutine/CopyToRoutine for now. We have a patch for it but we defer it. Because there are some mysterious profile results in spite of we get faster runtimes. See [1] for details. [1] https://www.postgresql.org/message-id/ZdbtQJ-p5H1_EDwE%40paquier.xyz Note that this doesn't change existing text/csv/binary format implementations. There are many diffs for CopyOneRowTo() but they're caused by indentation. They don't change implementations. --- src/backend/commands/copyfrom.c | 22 ++++- src/backend/commands/copyfromparse.c | 5 ++ src/backend/commands/copyto.c | 21 +++++ src/include/commands/copyapi.h | 100 +++++++++++++++++++++++ src/include/commands/copyfrom_internal.h | 4 + src/tools/pgindent/typedefs.list | 2 + 6 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 src/include/commands/copyapi.h diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c index c3bc897028..9bf2f6497e 100644 --- a/src/backend/commands/copyfrom.c +++ b/src/backend/commands/copyfrom.c @@ -1623,12 +1623,22 @@ BeginCopyFrom(ParseState *pstate, /* Fetch the input function and typioparam info */ if (cstate->opts.binary) + { getTypeBinaryInputInfo(att->atttypid, &in_func_oid, &typioparams[attnum - 1]); + fmgr_info(in_func_oid, &in_functions[attnum - 1]); + } + else if (cstate->routine) + cstate->routine->CopyFromInFunc(cstate, att->atttypid, + &in_functions[attnum - 1], + &typioparams[attnum - 1]); + else + { getTypeInputInfo(att->atttypid, &in_func_oid, &typioparams[attnum - 1]); fmgr_info(in_func_oid, &in_functions[attnum - 1]); + } /* Get default info if available */ defexprs[attnum - 1] = NULL; @@ -1768,10 +1778,13 @@ BeginCopyFrom(ParseState *pstate, /* Read and verify binary header */ ReceiveCopyBinaryHeader(cstate); } - - /* create workspace for CopyReadAttributes results */ - if (!cstate->opts.binary) + else if (cstate->routine) { + cstate->routine->CopyFromStart(cstate, tupDesc); + } + else + { + /* create workspace for CopyReadAttributes results */ AttrNumber attr_count = list_length(cstate->attnumlist); cstate->max_fields = attr_count; @@ -1789,6 +1802,9 @@ BeginCopyFrom(ParseState *pstate, void EndCopyFrom(CopyFromState cstate) { + if (cstate->routine) + cstate->routine->CopyFromEnd(cstate); + /* No COPY FROM related resources except memory. */ if (cstate->is_program) { diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c index 7cacd0b752..8b15080585 100644 --- a/src/backend/commands/copyfromparse.c +++ b/src/backend/commands/copyfromparse.c @@ -978,6 +978,11 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext, Assert(fieldno == attr_count); } + else if (cstate->routine) + { + if (!cstate->routine->CopyFromOneRow(cstate, econtext, values, nulls)) + return false; + } else { /* binary */ diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index 20ffc90363..6080627c83 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -24,6 +24,7 @@ #include "access/xact.h" #include "access/xlog.h" #include "commands/copy.h" +#include "commands/copyapi.h" #include "commands/progress.h" #include "executor/execdesc.h" #include "executor/executor.h" @@ -71,6 +72,9 @@ typedef enum CopyDest */ typedef struct CopyToStateData { + /* format routine */ + const CopyToRoutine *routine; + /* low-level state data */ CopyDest copy_dest; /* type of copy source/destination */ FILE *copy_file; /* used if copy_dest == COPY_FILE */ @@ -777,15 +781,23 @@ DoCopyTo(CopyToState cstate) Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1); if (cstate->opts.binary) + { getTypeBinaryOutputInfo(attr->atttypid, &out_func_oid, &isvarlena); + fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + } + else if (cstate->routine) + cstate->routine->CopyToOutFunc(cstate, attr->atttypid, + &cstate->out_functions[attnum - 1]); else + { getTypeOutputInfo(attr->atttypid, &out_func_oid, &isvarlena); fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); } + } /* * Create a temporary memory context that we can reset once per row to @@ -811,6 +823,8 @@ DoCopyTo(CopyToState cstate) tmp = 0; CopySendInt32(cstate, tmp); } + else if (cstate->routine) + cstate->routine->CopyToStart(cstate, tupDesc); else { /* @@ -892,6 +906,8 @@ DoCopyTo(CopyToState cstate) /* Need to flush out the trailer */ CopySendEndOfRow(cstate); } + else if (cstate->routine) + cstate->routine->CopyToEnd(cstate); MemoryContextDelete(cstate->rowcontext); @@ -916,6 +932,10 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot) MemoryContextReset(cstate->rowcontext); oldcontext = MemoryContextSwitchTo(cstate->rowcontext); + if (cstate->routine) + cstate->routine->CopyToOneRow(cstate, slot); + else + { if (cstate->opts.binary) { /* Binary per-tuple header */ @@ -971,6 +991,7 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot) } CopySendEndOfRow(cstate); + } MemoryContextSwitchTo(oldcontext); } diff --git a/src/include/commands/copyapi.h b/src/include/commands/copyapi.h new file mode 100644 index 0000000000..635c4cbff2 --- /dev/null +++ b/src/include/commands/copyapi.h @@ -0,0 +1,100 @@ +/*------------------------------------------------------------------------- + * + * copyapi.h + * API for COPY TO/FROM handlers + * + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/copyapi.h + * + *------------------------------------------------------------------------- + */ +#ifndef COPYAPI_H +#define COPYAPI_H + +#include "executor/tuptable.h" +#include "nodes/execnodes.h" + +/* These are private in commands/copy[from|to].c */ +typedef struct CopyFromStateData *CopyFromState; +typedef struct CopyToStateData *CopyToState; + +/* + * API structure for a COPY FROM format implementation. Note this must be + * allocated in a server-lifetime manner, typically as a static const struct. + */ +typedef struct CopyFromRoutine +{ + /* + * Called when COPY FROM is started to set up the input functions + * associated to the relation's attributes writing to. `finfo` can be + * optionally filled to provide the catalog information of the input + * function. `typioparam` can be optionally filled to define the OID of + * the type to pass to the input function. `atttypid` is the OID of data + * type used by the relation's attribute. + */ + void (*CopyFromInFunc) (CopyFromState cstate, Oid atttypid, + FmgrInfo *finfo, Oid *typioparam); + + /* + * Called when COPY FROM is started. + * + * `tupDesc` is the tuple descriptor of the relation where the data needs + * to be copied. This can be used for any initialization steps required + * by a format. + */ + void (*CopyFromStart) (CopyFromState cstate, TupleDesc tupDesc); + + /* + * Copy one row to a set of `values` and `nulls` of size tupDesc->natts. + * + * 'econtext' is used to evaluate default expression for each column that + * is either not read from the file or is using the DEFAULT option of COPY + * FROM. It is NULL if no default values are used. + * + * Returns false if there are no more tuples to copy. + */ + bool (*CopyFromOneRow) (CopyFromState cstate, ExprContext *econtext, + Datum *values, bool *nulls); + + /* Called when COPY FROM has ended. */ + void (*CopyFromEnd) (CopyFromState cstate); +} CopyFromRoutine; + +/* + * API structure for a COPY TO format implementation. Note this must be + * allocated in a server-lifetime manner, typically as a static const struct. + */ +typedef struct CopyToRoutine +{ + /* + * Called when COPY TO is started to set up the output functions + * associated to the relation's attributes reading from. `finfo` can be + * optionally filled. `atttypid` is the OID of data type used by the + * relation's attribute. + */ + void (*CopyToOutFunc) (CopyToState cstate, Oid atttypid, + FmgrInfo *finfo); + + /* + * Called when COPY TO is started. + * + * `tupDesc` is the tuple descriptor of the relation from where the data + * is read. + */ + void (*CopyToStart) (CopyToState cstate, TupleDesc tupDesc); + + /* + * Copy one row for COPY TO. + * + * `slot` is the tuple slot where the data is emitted. + */ + void (*CopyToOneRow) (CopyToState cstate, TupleTableSlot *slot); + + /* Called when COPY TO has ended */ + void (*CopyToEnd) (CopyToState cstate); +} CopyToRoutine; + +#endif /* COPYAPI_H */ diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h index cad52fcc78..509b9e92a1 100644 --- a/src/include/commands/copyfrom_internal.h +++ b/src/include/commands/copyfrom_internal.h @@ -15,6 +15,7 @@ #define COPYFROM_INTERNAL_H #include "commands/copy.h" +#include "commands/copyapi.h" #include "commands/trigger.h" #include "nodes/miscnodes.h" @@ -58,6 +59,9 @@ typedef enum CopyInsertMethod */ typedef struct CopyFromStateData { + /* format routine */ + const CopyFromRoutine *routine; + /* low-level state data */ CopySource copy_src; /* type of copy source */ FILE *copy_file; /* used if copy_src == COPY_FILE */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index ee40a341d3..a5ae161ca5 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -475,6 +475,7 @@ ConvertRowtypeExpr CookedConstraint CopyDest CopyFormatOptions +CopyFromRoutine CopyFromState CopyFromStateData CopyHeaderChoice @@ -484,6 +485,7 @@ CopyMultiInsertInfo CopyOnErrorChoice CopySource CopyStmt +CopyToRoutine CopyToState CopyToStateData Cost -- 2.43.0 ----Next_Part(Mon_Mar__4_14_11_08_2024_631)---- ^ permalink raw reply [nested|flat] 29+ messages in thread
* [PATCH v17] Add CopyFromRoutine/CopyToRountine @ 2024-03-04 04:52 Sutou Kouhei <[email protected]> 0 siblings, 0 replies; 29+ messages in thread From: Sutou Kouhei @ 2024-03-04 04:52 UTC (permalink / raw) They are for implementing custom COPY FROM/TO format. But this is not enough to implement custom COPY FROM/TO format yet. We'll export some APIs to receive/send data and add "format" option to COPY FROM/TO later. Existing text/csv/binary format implementations don't use CopyFromRoutine/CopyToRoutine for now. We have a patch for it but we defer it. Because there are some mysterious profile results in spite of we get faster runtimes. See [1] for details. [1] https://www.postgresql.org/message-id/ZdbtQJ-p5H1_EDwE%40paquier.xyz Note that this doesn't change existing text/csv/binary format implementations. --- src/backend/commands/copyfrom.c | 24 +++++- src/backend/commands/copyfromparse.c | 5 ++ src/backend/commands/copyto.c | 25 +++++- src/include/commands/copyapi.h | 100 +++++++++++++++++++++++ src/include/commands/copyfrom_internal.h | 4 + src/tools/pgindent/typedefs.list | 2 + 6 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 src/include/commands/copyapi.h diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c index c3bc897028..9bf2f6497e 100644 --- a/src/backend/commands/copyfrom.c +++ b/src/backend/commands/copyfrom.c @@ -1623,12 +1623,22 @@ BeginCopyFrom(ParseState *pstate, /* Fetch the input function and typioparam info */ if (cstate->opts.binary) + { getTypeBinaryInputInfo(att->atttypid, &in_func_oid, &typioparams[attnum - 1]); + fmgr_info(in_func_oid, &in_functions[attnum - 1]); + } + else if (cstate->routine) + cstate->routine->CopyFromInFunc(cstate, att->atttypid, + &in_functions[attnum - 1], + &typioparams[attnum - 1]); + else + { getTypeInputInfo(att->atttypid, &in_func_oid, &typioparams[attnum - 1]); - fmgr_info(in_func_oid, &in_functions[attnum - 1]); + fmgr_info(in_func_oid, &in_functions[attnum - 1]); + } /* Get default info if available */ defexprs[attnum - 1] = NULL; @@ -1768,10 +1778,13 @@ BeginCopyFrom(ParseState *pstate, /* Read and verify binary header */ ReceiveCopyBinaryHeader(cstate); } - - /* create workspace for CopyReadAttributes results */ - if (!cstate->opts.binary) + else if (cstate->routine) { + cstate->routine->CopyFromStart(cstate, tupDesc); + } + else + { + /* create workspace for CopyReadAttributes results */ AttrNumber attr_count = list_length(cstate->attnumlist); cstate->max_fields = attr_count; @@ -1789,6 +1802,9 @@ BeginCopyFrom(ParseState *pstate, void EndCopyFrom(CopyFromState cstate) { + if (cstate->routine) + cstate->routine->CopyFromEnd(cstate); + /* No COPY FROM related resources except memory. */ if (cstate->is_program) { diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c index 7cacd0b752..8b15080585 100644 --- a/src/backend/commands/copyfromparse.c +++ b/src/backend/commands/copyfromparse.c @@ -978,6 +978,11 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext, Assert(fieldno == attr_count); } + else if (cstate->routine) + { + if (!cstate->routine->CopyFromOneRow(cstate, econtext, values, nulls)) + return false; + } else { /* binary */ diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index 20ffc90363..b4a7c9c8b9 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -24,6 +24,7 @@ #include "access/xact.h" #include "access/xlog.h" #include "commands/copy.h" +#include "commands/copyapi.h" #include "commands/progress.h" #include "executor/execdesc.h" #include "executor/executor.h" @@ -71,6 +72,9 @@ typedef enum CopyDest */ typedef struct CopyToStateData { + /* format routine */ + const CopyToRoutine *routine; + /* low-level state data */ CopyDest copy_dest; /* type of copy source/destination */ FILE *copy_file; /* used if copy_dest == COPY_FILE */ @@ -777,14 +781,22 @@ DoCopyTo(CopyToState cstate) Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1); if (cstate->opts.binary) + { getTypeBinaryOutputInfo(attr->atttypid, &out_func_oid, &isvarlena); + fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + } + else if (cstate->routine) + cstate->routine->CopyToOutFunc(cstate, attr->atttypid, + &cstate->out_functions[attnum - 1]); else + { getTypeOutputInfo(attr->atttypid, &out_func_oid, &isvarlena); - fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + } } /* @@ -811,6 +823,8 @@ DoCopyTo(CopyToState cstate) tmp = 0; CopySendInt32(cstate, tmp); } + else if (cstate->routine) + cstate->routine->CopyToStart(cstate, tupDesc); else { /* @@ -892,6 +906,8 @@ DoCopyTo(CopyToState cstate) /* Need to flush out the trailer */ CopySendEndOfRow(cstate); } + else if (cstate->routine) + cstate->routine->CopyToEnd(cstate); MemoryContextDelete(cstate->rowcontext); @@ -916,6 +932,13 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot) MemoryContextReset(cstate->rowcontext); oldcontext = MemoryContextSwitchTo(cstate->rowcontext); + if (cstate->routine) + { + cstate->routine->CopyToOneRow(cstate, slot); + MemoryContextSwitchTo(oldcontext); + return; + } + if (cstate->opts.binary) { /* Binary per-tuple header */ diff --git a/src/include/commands/copyapi.h b/src/include/commands/copyapi.h new file mode 100644 index 0000000000..635c4cbff2 --- /dev/null +++ b/src/include/commands/copyapi.h @@ -0,0 +1,100 @@ +/*------------------------------------------------------------------------- + * + * copyapi.h + * API for COPY TO/FROM handlers + * + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/copyapi.h + * + *------------------------------------------------------------------------- + */ +#ifndef COPYAPI_H +#define COPYAPI_H + +#include "executor/tuptable.h" +#include "nodes/execnodes.h" + +/* These are private in commands/copy[from|to].c */ +typedef struct CopyFromStateData *CopyFromState; +typedef struct CopyToStateData *CopyToState; + +/* + * API structure for a COPY FROM format implementation. Note this must be + * allocated in a server-lifetime manner, typically as a static const struct. + */ +typedef struct CopyFromRoutine +{ + /* + * Called when COPY FROM is started to set up the input functions + * associated to the relation's attributes writing to. `finfo` can be + * optionally filled to provide the catalog information of the input + * function. `typioparam` can be optionally filled to define the OID of + * the type to pass to the input function. `atttypid` is the OID of data + * type used by the relation's attribute. + */ + void (*CopyFromInFunc) (CopyFromState cstate, Oid atttypid, + FmgrInfo *finfo, Oid *typioparam); + + /* + * Called when COPY FROM is started. + * + * `tupDesc` is the tuple descriptor of the relation where the data needs + * to be copied. This can be used for any initialization steps required + * by a format. + */ + void (*CopyFromStart) (CopyFromState cstate, TupleDesc tupDesc); + + /* + * Copy one row to a set of `values` and `nulls` of size tupDesc->natts. + * + * 'econtext' is used to evaluate default expression for each column that + * is either not read from the file or is using the DEFAULT option of COPY + * FROM. It is NULL if no default values are used. + * + * Returns false if there are no more tuples to copy. + */ + bool (*CopyFromOneRow) (CopyFromState cstate, ExprContext *econtext, + Datum *values, bool *nulls); + + /* Called when COPY FROM has ended. */ + void (*CopyFromEnd) (CopyFromState cstate); +} CopyFromRoutine; + +/* + * API structure for a COPY TO format implementation. Note this must be + * allocated in a server-lifetime manner, typically as a static const struct. + */ +typedef struct CopyToRoutine +{ + /* + * Called when COPY TO is started to set up the output functions + * associated to the relation's attributes reading from. `finfo` can be + * optionally filled. `atttypid` is the OID of data type used by the + * relation's attribute. + */ + void (*CopyToOutFunc) (CopyToState cstate, Oid atttypid, + FmgrInfo *finfo); + + /* + * Called when COPY TO is started. + * + * `tupDesc` is the tuple descriptor of the relation from where the data + * is read. + */ + void (*CopyToStart) (CopyToState cstate, TupleDesc tupDesc); + + /* + * Copy one row for COPY TO. + * + * `slot` is the tuple slot where the data is emitted. + */ + void (*CopyToOneRow) (CopyToState cstate, TupleTableSlot *slot); + + /* Called when COPY TO has ended */ + void (*CopyToEnd) (CopyToState cstate); +} CopyToRoutine; + +#endif /* COPYAPI_H */ diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h index cad52fcc78..509b9e92a1 100644 --- a/src/include/commands/copyfrom_internal.h +++ b/src/include/commands/copyfrom_internal.h @@ -15,6 +15,7 @@ #define COPYFROM_INTERNAL_H #include "commands/copy.h" +#include "commands/copyapi.h" #include "commands/trigger.h" #include "nodes/miscnodes.h" @@ -58,6 +59,9 @@ typedef enum CopyInsertMethod */ typedef struct CopyFromStateData { + /* format routine */ + const CopyFromRoutine *routine; + /* low-level state data */ CopySource copy_src; /* type of copy source */ FILE *copy_file; /* used if copy_src == COPY_FILE */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index ee40a341d3..a5ae161ca5 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -475,6 +475,7 @@ ConvertRowtypeExpr CookedConstraint CopyDest CopyFormatOptions +CopyFromRoutine CopyFromState CopyFromStateData CopyHeaderChoice @@ -484,6 +485,7 @@ CopyMultiInsertInfo CopyOnErrorChoice CopySource CopyStmt +CopyToRoutine CopyToState CopyToStateData Cost -- 2.43.0 ----Next_Part(Tue_Mar__5_17_18_08_2024_468)---- ^ permalink raw reply [nested|flat] 29+ messages in thread
* [PATCH v16] Add CopyFromRoutine/CopyToRountine @ 2024-03-04 04:52 Sutou Kouhei <[email protected]> 0 siblings, 0 replies; 29+ messages in thread From: Sutou Kouhei @ 2024-03-04 04:52 UTC (permalink / raw) They are for implementing custom COPY FROM/TO format. But this is not enough to implement custom COPY FROM/TO format yet. We'll export some APIs to receive/send data and add "format" option to COPY FROM/TO later. Existing text/csv/binary format implementations don't use CopyFromRoutine/CopyToRoutine for now. We have a patch for it but we defer it. Because there are some mysterious profile results in spite of we get faster runtimes. See [1] for details. [1] https://www.postgresql.org/message-id/ZdbtQJ-p5H1_EDwE%40paquier.xyz Note that this doesn't change existing text/csv/binary format implementations. There are many diffs for CopyOneRowTo() but they're caused by indentation. They don't change implementations. --- src/backend/commands/copyfrom.c | 24 +++++- src/backend/commands/copyfromparse.c | 5 ++ src/backend/commands/copyto.c | 103 ++++++++++++++--------- src/include/commands/copyapi.h | 100 ++++++++++++++++++++++ src/include/commands/copyfrom_internal.h | 4 + src/tools/pgindent/typedefs.list | 2 + 6 files changed, 193 insertions(+), 45 deletions(-) create mode 100644 src/include/commands/copyapi.h diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c index c3bc897028..9bf2f6497e 100644 --- a/src/backend/commands/copyfrom.c +++ b/src/backend/commands/copyfrom.c @@ -1623,12 +1623,22 @@ BeginCopyFrom(ParseState *pstate, /* Fetch the input function and typioparam info */ if (cstate->opts.binary) + { getTypeBinaryInputInfo(att->atttypid, &in_func_oid, &typioparams[attnum - 1]); + fmgr_info(in_func_oid, &in_functions[attnum - 1]); + } + else if (cstate->routine) + cstate->routine->CopyFromInFunc(cstate, att->atttypid, + &in_functions[attnum - 1], + &typioparams[attnum - 1]); + else + { getTypeInputInfo(att->atttypid, &in_func_oid, &typioparams[attnum - 1]); - fmgr_info(in_func_oid, &in_functions[attnum - 1]); + fmgr_info(in_func_oid, &in_functions[attnum - 1]); + } /* Get default info if available */ defexprs[attnum - 1] = NULL; @@ -1768,10 +1778,13 @@ BeginCopyFrom(ParseState *pstate, /* Read and verify binary header */ ReceiveCopyBinaryHeader(cstate); } - - /* create workspace for CopyReadAttributes results */ - if (!cstate->opts.binary) + else if (cstate->routine) { + cstate->routine->CopyFromStart(cstate, tupDesc); + } + else + { + /* create workspace for CopyReadAttributes results */ AttrNumber attr_count = list_length(cstate->attnumlist); cstate->max_fields = attr_count; @@ -1789,6 +1802,9 @@ BeginCopyFrom(ParseState *pstate, void EndCopyFrom(CopyFromState cstate) { + if (cstate->routine) + cstate->routine->CopyFromEnd(cstate); + /* No COPY FROM related resources except memory. */ if (cstate->is_program) { diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c index 7cacd0b752..8b15080585 100644 --- a/src/backend/commands/copyfromparse.c +++ b/src/backend/commands/copyfromparse.c @@ -978,6 +978,11 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext, Assert(fieldno == attr_count); } + else if (cstate->routine) + { + if (!cstate->routine->CopyFromOneRow(cstate, econtext, values, nulls)) + return false; + } else { /* binary */ diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index 20ffc90363..6080627c83 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -24,6 +24,7 @@ #include "access/xact.h" #include "access/xlog.h" #include "commands/copy.h" +#include "commands/copyapi.h" #include "commands/progress.h" #include "executor/execdesc.h" #include "executor/executor.h" @@ -71,6 +72,9 @@ typedef enum CopyDest */ typedef struct CopyToStateData { + /* format routine */ + const CopyToRoutine *routine; + /* low-level state data */ CopyDest copy_dest; /* type of copy source/destination */ FILE *copy_file; /* used if copy_dest == COPY_FILE */ @@ -777,14 +781,22 @@ DoCopyTo(CopyToState cstate) Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1); if (cstate->opts.binary) + { getTypeBinaryOutputInfo(attr->atttypid, &out_func_oid, &isvarlena); + fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + } + else if (cstate->routine) + cstate->routine->CopyToOutFunc(cstate, attr->atttypid, + &cstate->out_functions[attnum - 1]); else + { getTypeOutputInfo(attr->atttypid, &out_func_oid, &isvarlena); - fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + } } /* @@ -811,6 +823,8 @@ DoCopyTo(CopyToState cstate) tmp = 0; CopySendInt32(cstate, tmp); } + else if (cstate->routine) + cstate->routine->CopyToStart(cstate, tupDesc); else { /* @@ -892,6 +906,8 @@ DoCopyTo(CopyToState cstate) /* Need to flush out the trailer */ CopySendEndOfRow(cstate); } + else if (cstate->routine) + cstate->routine->CopyToEnd(cstate); MemoryContextDelete(cstate->rowcontext); @@ -916,61 +932,66 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot) MemoryContextReset(cstate->rowcontext); oldcontext = MemoryContextSwitchTo(cstate->rowcontext); - if (cstate->opts.binary) + if (cstate->routine) + cstate->routine->CopyToOneRow(cstate, slot); + else { - /* Binary per-tuple header */ - CopySendInt16(cstate, list_length(cstate->attnumlist)); - } - - /* Make sure the tuple is fully deconstructed */ - slot_getallattrs(slot); - - foreach(cur, cstate->attnumlist) - { - int attnum = lfirst_int(cur); - Datum value = slot->tts_values[attnum - 1]; - bool isnull = slot->tts_isnull[attnum - 1]; - - if (!cstate->opts.binary) + if (cstate->opts.binary) { - if (need_delim) - CopySendChar(cstate, cstate->opts.delim[0]); - need_delim = true; + /* Binary per-tuple header */ + CopySendInt16(cstate, list_length(cstate->attnumlist)); } - if (isnull) - { - if (!cstate->opts.binary) - CopySendString(cstate, cstate->opts.null_print_client); - else - CopySendInt32(cstate, -1); - } - else + /* Make sure the tuple is fully deconstructed */ + slot_getallattrs(slot); + + foreach(cur, cstate->attnumlist) { + int attnum = lfirst_int(cur); + Datum value = slot->tts_values[attnum - 1]; + bool isnull = slot->tts_isnull[attnum - 1]; + if (!cstate->opts.binary) { - string = OutputFunctionCall(&out_functions[attnum - 1], - value); - if (cstate->opts.csv_mode) - CopyAttributeOutCSV(cstate, string, - cstate->opts.force_quote_flags[attnum - 1]); + if (need_delim) + CopySendChar(cstate, cstate->opts.delim[0]); + need_delim = true; + } + + if (isnull) + { + if (!cstate->opts.binary) + CopySendString(cstate, cstate->opts.null_print_client); else - CopyAttributeOutText(cstate, string); + CopySendInt32(cstate, -1); } else { - bytea *outputbytes; + if (!cstate->opts.binary) + { + string = OutputFunctionCall(&out_functions[attnum - 1], + value); + if (cstate->opts.csv_mode) + CopyAttributeOutCSV(cstate, string, + cstate->opts.force_quote_flags[attnum - 1]); + else + CopyAttributeOutText(cstate, string); + } + else + { + bytea *outputbytes; - outputbytes = SendFunctionCall(&out_functions[attnum - 1], - value); - CopySendInt32(cstate, VARSIZE(outputbytes) - VARHDRSZ); - CopySendData(cstate, VARDATA(outputbytes), - VARSIZE(outputbytes) - VARHDRSZ); + outputbytes = SendFunctionCall(&out_functions[attnum - 1], + value); + CopySendInt32(cstate, VARSIZE(outputbytes) - VARHDRSZ); + CopySendData(cstate, VARDATA(outputbytes), + VARSIZE(outputbytes) - VARHDRSZ); + } } } - } - CopySendEndOfRow(cstate); + CopySendEndOfRow(cstate); + } MemoryContextSwitchTo(oldcontext); } diff --git a/src/include/commands/copyapi.h b/src/include/commands/copyapi.h new file mode 100644 index 0000000000..635c4cbff2 --- /dev/null +++ b/src/include/commands/copyapi.h @@ -0,0 +1,100 @@ +/*------------------------------------------------------------------------- + * + * copyapi.h + * API for COPY TO/FROM handlers + * + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/copyapi.h + * + *------------------------------------------------------------------------- + */ +#ifndef COPYAPI_H +#define COPYAPI_H + +#include "executor/tuptable.h" +#include "nodes/execnodes.h" + +/* These are private in commands/copy[from|to].c */ +typedef struct CopyFromStateData *CopyFromState; +typedef struct CopyToStateData *CopyToState; + +/* + * API structure for a COPY FROM format implementation. Note this must be + * allocated in a server-lifetime manner, typically as a static const struct. + */ +typedef struct CopyFromRoutine +{ + /* + * Called when COPY FROM is started to set up the input functions + * associated to the relation's attributes writing to. `finfo` can be + * optionally filled to provide the catalog information of the input + * function. `typioparam` can be optionally filled to define the OID of + * the type to pass to the input function. `atttypid` is the OID of data + * type used by the relation's attribute. + */ + void (*CopyFromInFunc) (CopyFromState cstate, Oid atttypid, + FmgrInfo *finfo, Oid *typioparam); + + /* + * Called when COPY FROM is started. + * + * `tupDesc` is the tuple descriptor of the relation where the data needs + * to be copied. This can be used for any initialization steps required + * by a format. + */ + void (*CopyFromStart) (CopyFromState cstate, TupleDesc tupDesc); + + /* + * Copy one row to a set of `values` and `nulls` of size tupDesc->natts. + * + * 'econtext' is used to evaluate default expression for each column that + * is either not read from the file or is using the DEFAULT option of COPY + * FROM. It is NULL if no default values are used. + * + * Returns false if there are no more tuples to copy. + */ + bool (*CopyFromOneRow) (CopyFromState cstate, ExprContext *econtext, + Datum *values, bool *nulls); + + /* Called when COPY FROM has ended. */ + void (*CopyFromEnd) (CopyFromState cstate); +} CopyFromRoutine; + +/* + * API structure for a COPY TO format implementation. Note this must be + * allocated in a server-lifetime manner, typically as a static const struct. + */ +typedef struct CopyToRoutine +{ + /* + * Called when COPY TO is started to set up the output functions + * associated to the relation's attributes reading from. `finfo` can be + * optionally filled. `atttypid` is the OID of data type used by the + * relation's attribute. + */ + void (*CopyToOutFunc) (CopyToState cstate, Oid atttypid, + FmgrInfo *finfo); + + /* + * Called when COPY TO is started. + * + * `tupDesc` is the tuple descriptor of the relation from where the data + * is read. + */ + void (*CopyToStart) (CopyToState cstate, TupleDesc tupDesc); + + /* + * Copy one row for COPY TO. + * + * `slot` is the tuple slot where the data is emitted. + */ + void (*CopyToOneRow) (CopyToState cstate, TupleTableSlot *slot); + + /* Called when COPY TO has ended */ + void (*CopyToEnd) (CopyToState cstate); +} CopyToRoutine; + +#endif /* COPYAPI_H */ diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h index cad52fcc78..509b9e92a1 100644 --- a/src/include/commands/copyfrom_internal.h +++ b/src/include/commands/copyfrom_internal.h @@ -15,6 +15,7 @@ #define COPYFROM_INTERNAL_H #include "commands/copy.h" +#include "commands/copyapi.h" #include "commands/trigger.h" #include "nodes/miscnodes.h" @@ -58,6 +59,9 @@ typedef enum CopyInsertMethod */ typedef struct CopyFromStateData { + /* format routine */ + const CopyFromRoutine *routine; + /* low-level state data */ CopySource copy_src; /* type of copy source */ FILE *copy_file; /* used if copy_src == COPY_FILE */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index ee40a341d3..a5ae161ca5 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -475,6 +475,7 @@ ConvertRowtypeExpr CookedConstraint CopyDest CopyFormatOptions +CopyFromRoutine CopyFromState CopyFromStateData CopyHeaderChoice @@ -484,6 +485,7 @@ CopyMultiInsertInfo CopyOnErrorChoice CopySource CopyStmt +CopyToRoutine CopyToState CopyToStateData Cost -- 2.43.0 ----Next_Part(Mon_Mar__4_14_11_08_2024_631)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v16-0001-Add-CopyFromRoutine-CopyToRountine-w.patch" ^ permalink raw reply [nested|flat] 29+ messages in thread
* [PATCH v16] Add CopyFromRoutine/CopyToRountine @ 2024-03-04 04:52 Sutou Kouhei <[email protected]> 0 siblings, 0 replies; 29+ messages in thread From: Sutou Kouhei @ 2024-03-04 04:52 UTC (permalink / raw) They are for implementing custom COPY FROM/TO format. But this is not enough to implement custom COPY FROM/TO format yet. We'll export some APIs to receive/send data and add "format" option to COPY FROM/TO later. Existing text/csv/binary format implementations don't use CopyFromRoutine/CopyToRoutine for now. We have a patch for it but we defer it. Because there are some mysterious profile results in spite of we get faster runtimes. See [1] for details. [1] https://www.postgresql.org/message-id/ZdbtQJ-p5H1_EDwE%40paquier.xyz Note that this doesn't change existing text/csv/binary format implementations. There are many diffs for CopyOneRowTo() but they're caused by indentation. They don't change implementations. --- src/backend/commands/copyfrom.c | 22 ++++- src/backend/commands/copyfromparse.c | 5 ++ src/backend/commands/copyto.c | 21 +++++ src/include/commands/copyapi.h | 100 +++++++++++++++++++++++ src/include/commands/copyfrom_internal.h | 4 + src/tools/pgindent/typedefs.list | 2 + 6 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 src/include/commands/copyapi.h diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c index c3bc897028..9bf2f6497e 100644 --- a/src/backend/commands/copyfrom.c +++ b/src/backend/commands/copyfrom.c @@ -1623,12 +1623,22 @@ BeginCopyFrom(ParseState *pstate, /* Fetch the input function and typioparam info */ if (cstate->opts.binary) + { getTypeBinaryInputInfo(att->atttypid, &in_func_oid, &typioparams[attnum - 1]); + fmgr_info(in_func_oid, &in_functions[attnum - 1]); + } + else if (cstate->routine) + cstate->routine->CopyFromInFunc(cstate, att->atttypid, + &in_functions[attnum - 1], + &typioparams[attnum - 1]); + else + { getTypeInputInfo(att->atttypid, &in_func_oid, &typioparams[attnum - 1]); fmgr_info(in_func_oid, &in_functions[attnum - 1]); + } /* Get default info if available */ defexprs[attnum - 1] = NULL; @@ -1768,10 +1778,13 @@ BeginCopyFrom(ParseState *pstate, /* Read and verify binary header */ ReceiveCopyBinaryHeader(cstate); } - - /* create workspace for CopyReadAttributes results */ - if (!cstate->opts.binary) + else if (cstate->routine) { + cstate->routine->CopyFromStart(cstate, tupDesc); + } + else + { + /* create workspace for CopyReadAttributes results */ AttrNumber attr_count = list_length(cstate->attnumlist); cstate->max_fields = attr_count; @@ -1789,6 +1802,9 @@ BeginCopyFrom(ParseState *pstate, void EndCopyFrom(CopyFromState cstate) { + if (cstate->routine) + cstate->routine->CopyFromEnd(cstate); + /* No COPY FROM related resources except memory. */ if (cstate->is_program) { diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c index 7cacd0b752..8b15080585 100644 --- a/src/backend/commands/copyfromparse.c +++ b/src/backend/commands/copyfromparse.c @@ -978,6 +978,11 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext, Assert(fieldno == attr_count); } + else if (cstate->routine) + { + if (!cstate->routine->CopyFromOneRow(cstate, econtext, values, nulls)) + return false; + } else { /* binary */ diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index 20ffc90363..6080627c83 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -24,6 +24,7 @@ #include "access/xact.h" #include "access/xlog.h" #include "commands/copy.h" +#include "commands/copyapi.h" #include "commands/progress.h" #include "executor/execdesc.h" #include "executor/executor.h" @@ -71,6 +72,9 @@ typedef enum CopyDest */ typedef struct CopyToStateData { + /* format routine */ + const CopyToRoutine *routine; + /* low-level state data */ CopyDest copy_dest; /* type of copy source/destination */ FILE *copy_file; /* used if copy_dest == COPY_FILE */ @@ -777,15 +781,23 @@ DoCopyTo(CopyToState cstate) Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1); if (cstate->opts.binary) + { getTypeBinaryOutputInfo(attr->atttypid, &out_func_oid, &isvarlena); + fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + } + else if (cstate->routine) + cstate->routine->CopyToOutFunc(cstate, attr->atttypid, + &cstate->out_functions[attnum - 1]); else + { getTypeOutputInfo(attr->atttypid, &out_func_oid, &isvarlena); fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); } + } /* * Create a temporary memory context that we can reset once per row to @@ -811,6 +823,8 @@ DoCopyTo(CopyToState cstate) tmp = 0; CopySendInt32(cstate, tmp); } + else if (cstate->routine) + cstate->routine->CopyToStart(cstate, tupDesc); else { /* @@ -892,6 +906,8 @@ DoCopyTo(CopyToState cstate) /* Need to flush out the trailer */ CopySendEndOfRow(cstate); } + else if (cstate->routine) + cstate->routine->CopyToEnd(cstate); MemoryContextDelete(cstate->rowcontext); @@ -916,6 +932,10 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot) MemoryContextReset(cstate->rowcontext); oldcontext = MemoryContextSwitchTo(cstate->rowcontext); + if (cstate->routine) + cstate->routine->CopyToOneRow(cstate, slot); + else + { if (cstate->opts.binary) { /* Binary per-tuple header */ @@ -971,6 +991,7 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot) } CopySendEndOfRow(cstate); + } MemoryContextSwitchTo(oldcontext); } diff --git a/src/include/commands/copyapi.h b/src/include/commands/copyapi.h new file mode 100644 index 0000000000..635c4cbff2 --- /dev/null +++ b/src/include/commands/copyapi.h @@ -0,0 +1,100 @@ +/*------------------------------------------------------------------------- + * + * copyapi.h + * API for COPY TO/FROM handlers + * + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/copyapi.h + * + *------------------------------------------------------------------------- + */ +#ifndef COPYAPI_H +#define COPYAPI_H + +#include "executor/tuptable.h" +#include "nodes/execnodes.h" + +/* These are private in commands/copy[from|to].c */ +typedef struct CopyFromStateData *CopyFromState; +typedef struct CopyToStateData *CopyToState; + +/* + * API structure for a COPY FROM format implementation. Note this must be + * allocated in a server-lifetime manner, typically as a static const struct. + */ +typedef struct CopyFromRoutine +{ + /* + * Called when COPY FROM is started to set up the input functions + * associated to the relation's attributes writing to. `finfo` can be + * optionally filled to provide the catalog information of the input + * function. `typioparam` can be optionally filled to define the OID of + * the type to pass to the input function. `atttypid` is the OID of data + * type used by the relation's attribute. + */ + void (*CopyFromInFunc) (CopyFromState cstate, Oid atttypid, + FmgrInfo *finfo, Oid *typioparam); + + /* + * Called when COPY FROM is started. + * + * `tupDesc` is the tuple descriptor of the relation where the data needs + * to be copied. This can be used for any initialization steps required + * by a format. + */ + void (*CopyFromStart) (CopyFromState cstate, TupleDesc tupDesc); + + /* + * Copy one row to a set of `values` and `nulls` of size tupDesc->natts. + * + * 'econtext' is used to evaluate default expression for each column that + * is either not read from the file or is using the DEFAULT option of COPY + * FROM. It is NULL if no default values are used. + * + * Returns false if there are no more tuples to copy. + */ + bool (*CopyFromOneRow) (CopyFromState cstate, ExprContext *econtext, + Datum *values, bool *nulls); + + /* Called when COPY FROM has ended. */ + void (*CopyFromEnd) (CopyFromState cstate); +} CopyFromRoutine; + +/* + * API structure for a COPY TO format implementation. Note this must be + * allocated in a server-lifetime manner, typically as a static const struct. + */ +typedef struct CopyToRoutine +{ + /* + * Called when COPY TO is started to set up the output functions + * associated to the relation's attributes reading from. `finfo` can be + * optionally filled. `atttypid` is the OID of data type used by the + * relation's attribute. + */ + void (*CopyToOutFunc) (CopyToState cstate, Oid atttypid, + FmgrInfo *finfo); + + /* + * Called when COPY TO is started. + * + * `tupDesc` is the tuple descriptor of the relation from where the data + * is read. + */ + void (*CopyToStart) (CopyToState cstate, TupleDesc tupDesc); + + /* + * Copy one row for COPY TO. + * + * `slot` is the tuple slot where the data is emitted. + */ + void (*CopyToOneRow) (CopyToState cstate, TupleTableSlot *slot); + + /* Called when COPY TO has ended */ + void (*CopyToEnd) (CopyToState cstate); +} CopyToRoutine; + +#endif /* COPYAPI_H */ diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h index cad52fcc78..509b9e92a1 100644 --- a/src/include/commands/copyfrom_internal.h +++ b/src/include/commands/copyfrom_internal.h @@ -15,6 +15,7 @@ #define COPYFROM_INTERNAL_H #include "commands/copy.h" +#include "commands/copyapi.h" #include "commands/trigger.h" #include "nodes/miscnodes.h" @@ -58,6 +59,9 @@ typedef enum CopyInsertMethod */ typedef struct CopyFromStateData { + /* format routine */ + const CopyFromRoutine *routine; + /* low-level state data */ CopySource copy_src; /* type of copy source */ FILE *copy_file; /* used if copy_src == COPY_FILE */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index ee40a341d3..a5ae161ca5 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -475,6 +475,7 @@ ConvertRowtypeExpr CookedConstraint CopyDest CopyFormatOptions +CopyFromRoutine CopyFromState CopyFromStateData CopyHeaderChoice @@ -484,6 +485,7 @@ CopyMultiInsertInfo CopyOnErrorChoice CopySource CopyStmt +CopyToRoutine CopyToState CopyToStateData Cost -- 2.43.0 ----Next_Part(Mon_Mar__4_14_11_08_2024_631)---- ^ permalink raw reply [nested|flat] 29+ messages in thread
* [PATCH v17] Add CopyFromRoutine/CopyToRountine @ 2024-03-04 04:52 Sutou Kouhei <[email protected]> 0 siblings, 0 replies; 29+ messages in thread From: Sutou Kouhei @ 2024-03-04 04:52 UTC (permalink / raw) They are for implementing custom COPY FROM/TO format. But this is not enough to implement custom COPY FROM/TO format yet. We'll export some APIs to receive/send data and add "format" option to COPY FROM/TO later. Existing text/csv/binary format implementations don't use CopyFromRoutine/CopyToRoutine for now. We have a patch for it but we defer it. Because there are some mysterious profile results in spite of we get faster runtimes. See [1] for details. [1] https://www.postgresql.org/message-id/ZdbtQJ-p5H1_EDwE%40paquier.xyz Note that this doesn't change existing text/csv/binary format implementations. --- src/backend/commands/copyfrom.c | 24 +++++- src/backend/commands/copyfromparse.c | 5 ++ src/backend/commands/copyto.c | 25 +++++- src/include/commands/copyapi.h | 100 +++++++++++++++++++++++ src/include/commands/copyfrom_internal.h | 4 + src/tools/pgindent/typedefs.list | 2 + 6 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 src/include/commands/copyapi.h diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c index c3bc897028..9bf2f6497e 100644 --- a/src/backend/commands/copyfrom.c +++ b/src/backend/commands/copyfrom.c @@ -1623,12 +1623,22 @@ BeginCopyFrom(ParseState *pstate, /* Fetch the input function and typioparam info */ if (cstate->opts.binary) + { getTypeBinaryInputInfo(att->atttypid, &in_func_oid, &typioparams[attnum - 1]); + fmgr_info(in_func_oid, &in_functions[attnum - 1]); + } + else if (cstate->routine) + cstate->routine->CopyFromInFunc(cstate, att->atttypid, + &in_functions[attnum - 1], + &typioparams[attnum - 1]); + else + { getTypeInputInfo(att->atttypid, &in_func_oid, &typioparams[attnum - 1]); - fmgr_info(in_func_oid, &in_functions[attnum - 1]); + fmgr_info(in_func_oid, &in_functions[attnum - 1]); + } /* Get default info if available */ defexprs[attnum - 1] = NULL; @@ -1768,10 +1778,13 @@ BeginCopyFrom(ParseState *pstate, /* Read and verify binary header */ ReceiveCopyBinaryHeader(cstate); } - - /* create workspace for CopyReadAttributes results */ - if (!cstate->opts.binary) + else if (cstate->routine) { + cstate->routine->CopyFromStart(cstate, tupDesc); + } + else + { + /* create workspace for CopyReadAttributes results */ AttrNumber attr_count = list_length(cstate->attnumlist); cstate->max_fields = attr_count; @@ -1789,6 +1802,9 @@ BeginCopyFrom(ParseState *pstate, void EndCopyFrom(CopyFromState cstate) { + if (cstate->routine) + cstate->routine->CopyFromEnd(cstate); + /* No COPY FROM related resources except memory. */ if (cstate->is_program) { diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c index 7cacd0b752..8b15080585 100644 --- a/src/backend/commands/copyfromparse.c +++ b/src/backend/commands/copyfromparse.c @@ -978,6 +978,11 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext, Assert(fieldno == attr_count); } + else if (cstate->routine) + { + if (!cstate->routine->CopyFromOneRow(cstate, econtext, values, nulls)) + return false; + } else { /* binary */ diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index 20ffc90363..b4a7c9c8b9 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -24,6 +24,7 @@ #include "access/xact.h" #include "access/xlog.h" #include "commands/copy.h" +#include "commands/copyapi.h" #include "commands/progress.h" #include "executor/execdesc.h" #include "executor/executor.h" @@ -71,6 +72,9 @@ typedef enum CopyDest */ typedef struct CopyToStateData { + /* format routine */ + const CopyToRoutine *routine; + /* low-level state data */ CopyDest copy_dest; /* type of copy source/destination */ FILE *copy_file; /* used if copy_dest == COPY_FILE */ @@ -777,14 +781,22 @@ DoCopyTo(CopyToState cstate) Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1); if (cstate->opts.binary) + { getTypeBinaryOutputInfo(attr->atttypid, &out_func_oid, &isvarlena); + fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + } + else if (cstate->routine) + cstate->routine->CopyToOutFunc(cstate, attr->atttypid, + &cstate->out_functions[attnum - 1]); else + { getTypeOutputInfo(attr->atttypid, &out_func_oid, &isvarlena); - fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + } } /* @@ -811,6 +823,8 @@ DoCopyTo(CopyToState cstate) tmp = 0; CopySendInt32(cstate, tmp); } + else if (cstate->routine) + cstate->routine->CopyToStart(cstate, tupDesc); else { /* @@ -892,6 +906,8 @@ DoCopyTo(CopyToState cstate) /* Need to flush out the trailer */ CopySendEndOfRow(cstate); } + else if (cstate->routine) + cstate->routine->CopyToEnd(cstate); MemoryContextDelete(cstate->rowcontext); @@ -916,6 +932,13 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot) MemoryContextReset(cstate->rowcontext); oldcontext = MemoryContextSwitchTo(cstate->rowcontext); + if (cstate->routine) + { + cstate->routine->CopyToOneRow(cstate, slot); + MemoryContextSwitchTo(oldcontext); + return; + } + if (cstate->opts.binary) { /* Binary per-tuple header */ diff --git a/src/include/commands/copyapi.h b/src/include/commands/copyapi.h new file mode 100644 index 0000000000..635c4cbff2 --- /dev/null +++ b/src/include/commands/copyapi.h @@ -0,0 +1,100 @@ +/*------------------------------------------------------------------------- + * + * copyapi.h + * API for COPY TO/FROM handlers + * + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/copyapi.h + * + *------------------------------------------------------------------------- + */ +#ifndef COPYAPI_H +#define COPYAPI_H + +#include "executor/tuptable.h" +#include "nodes/execnodes.h" + +/* These are private in commands/copy[from|to].c */ +typedef struct CopyFromStateData *CopyFromState; +typedef struct CopyToStateData *CopyToState; + +/* + * API structure for a COPY FROM format implementation. Note this must be + * allocated in a server-lifetime manner, typically as a static const struct. + */ +typedef struct CopyFromRoutine +{ + /* + * Called when COPY FROM is started to set up the input functions + * associated to the relation's attributes writing to. `finfo` can be + * optionally filled to provide the catalog information of the input + * function. `typioparam` can be optionally filled to define the OID of + * the type to pass to the input function. `atttypid` is the OID of data + * type used by the relation's attribute. + */ + void (*CopyFromInFunc) (CopyFromState cstate, Oid atttypid, + FmgrInfo *finfo, Oid *typioparam); + + /* + * Called when COPY FROM is started. + * + * `tupDesc` is the tuple descriptor of the relation where the data needs + * to be copied. This can be used for any initialization steps required + * by a format. + */ + void (*CopyFromStart) (CopyFromState cstate, TupleDesc tupDesc); + + /* + * Copy one row to a set of `values` and `nulls` of size tupDesc->natts. + * + * 'econtext' is used to evaluate default expression for each column that + * is either not read from the file or is using the DEFAULT option of COPY + * FROM. It is NULL if no default values are used. + * + * Returns false if there are no more tuples to copy. + */ + bool (*CopyFromOneRow) (CopyFromState cstate, ExprContext *econtext, + Datum *values, bool *nulls); + + /* Called when COPY FROM has ended. */ + void (*CopyFromEnd) (CopyFromState cstate); +} CopyFromRoutine; + +/* + * API structure for a COPY TO format implementation. Note this must be + * allocated in a server-lifetime manner, typically as a static const struct. + */ +typedef struct CopyToRoutine +{ + /* + * Called when COPY TO is started to set up the output functions + * associated to the relation's attributes reading from. `finfo` can be + * optionally filled. `atttypid` is the OID of data type used by the + * relation's attribute. + */ + void (*CopyToOutFunc) (CopyToState cstate, Oid atttypid, + FmgrInfo *finfo); + + /* + * Called when COPY TO is started. + * + * `tupDesc` is the tuple descriptor of the relation from where the data + * is read. + */ + void (*CopyToStart) (CopyToState cstate, TupleDesc tupDesc); + + /* + * Copy one row for COPY TO. + * + * `slot` is the tuple slot where the data is emitted. + */ + void (*CopyToOneRow) (CopyToState cstate, TupleTableSlot *slot); + + /* Called when COPY TO has ended */ + void (*CopyToEnd) (CopyToState cstate); +} CopyToRoutine; + +#endif /* COPYAPI_H */ diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h index cad52fcc78..509b9e92a1 100644 --- a/src/include/commands/copyfrom_internal.h +++ b/src/include/commands/copyfrom_internal.h @@ -15,6 +15,7 @@ #define COPYFROM_INTERNAL_H #include "commands/copy.h" +#include "commands/copyapi.h" #include "commands/trigger.h" #include "nodes/miscnodes.h" @@ -58,6 +59,9 @@ typedef enum CopyInsertMethod */ typedef struct CopyFromStateData { + /* format routine */ + const CopyFromRoutine *routine; + /* low-level state data */ CopySource copy_src; /* type of copy source */ FILE *copy_file; /* used if copy_src == COPY_FILE */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index ee40a341d3..a5ae161ca5 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -475,6 +475,7 @@ ConvertRowtypeExpr CookedConstraint CopyDest CopyFormatOptions +CopyFromRoutine CopyFromState CopyFromStateData CopyHeaderChoice @@ -484,6 +485,7 @@ CopyMultiInsertInfo CopyOnErrorChoice CopySource CopyStmt +CopyToRoutine CopyToState CopyToStateData Cost -- 2.43.0 ----Next_Part(Tue_Mar__5_17_18_08_2024_468)---- ^ permalink raw reply [nested|flat] 29+ messages in thread
* [PATCH v16] Add CopyFromRoutine/CopyToRountine @ 2024-03-04 04:52 Sutou Kouhei <[email protected]> 0 siblings, 0 replies; 29+ messages in thread From: Sutou Kouhei @ 2024-03-04 04:52 UTC (permalink / raw) They are for implementing custom COPY FROM/TO format. But this is not enough to implement custom COPY FROM/TO format yet. We'll export some APIs to receive/send data and add "format" option to COPY FROM/TO later. Existing text/csv/binary format implementations don't use CopyFromRoutine/CopyToRoutine for now. We have a patch for it but we defer it. Because there are some mysterious profile results in spite of we get faster runtimes. See [1] for details. [1] https://www.postgresql.org/message-id/ZdbtQJ-p5H1_EDwE%40paquier.xyz Note that this doesn't change existing text/csv/binary format implementations. There are many diffs for CopyOneRowTo() but they're caused by indentation. They don't change implementations. --- src/backend/commands/copyfrom.c | 24 +++++- src/backend/commands/copyfromparse.c | 5 ++ src/backend/commands/copyto.c | 103 ++++++++++++++--------- src/include/commands/copyapi.h | 100 ++++++++++++++++++++++ src/include/commands/copyfrom_internal.h | 4 + src/tools/pgindent/typedefs.list | 2 + 6 files changed, 193 insertions(+), 45 deletions(-) create mode 100644 src/include/commands/copyapi.h diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c index c3bc897028..9bf2f6497e 100644 --- a/src/backend/commands/copyfrom.c +++ b/src/backend/commands/copyfrom.c @@ -1623,12 +1623,22 @@ BeginCopyFrom(ParseState *pstate, /* Fetch the input function and typioparam info */ if (cstate->opts.binary) + { getTypeBinaryInputInfo(att->atttypid, &in_func_oid, &typioparams[attnum - 1]); + fmgr_info(in_func_oid, &in_functions[attnum - 1]); + } + else if (cstate->routine) + cstate->routine->CopyFromInFunc(cstate, att->atttypid, + &in_functions[attnum - 1], + &typioparams[attnum - 1]); + else + { getTypeInputInfo(att->atttypid, &in_func_oid, &typioparams[attnum - 1]); - fmgr_info(in_func_oid, &in_functions[attnum - 1]); + fmgr_info(in_func_oid, &in_functions[attnum - 1]); + } /* Get default info if available */ defexprs[attnum - 1] = NULL; @@ -1768,10 +1778,13 @@ BeginCopyFrom(ParseState *pstate, /* Read and verify binary header */ ReceiveCopyBinaryHeader(cstate); } - - /* create workspace for CopyReadAttributes results */ - if (!cstate->opts.binary) + else if (cstate->routine) { + cstate->routine->CopyFromStart(cstate, tupDesc); + } + else + { + /* create workspace for CopyReadAttributes results */ AttrNumber attr_count = list_length(cstate->attnumlist); cstate->max_fields = attr_count; @@ -1789,6 +1802,9 @@ BeginCopyFrom(ParseState *pstate, void EndCopyFrom(CopyFromState cstate) { + if (cstate->routine) + cstate->routine->CopyFromEnd(cstate); + /* No COPY FROM related resources except memory. */ if (cstate->is_program) { diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c index 7cacd0b752..8b15080585 100644 --- a/src/backend/commands/copyfromparse.c +++ b/src/backend/commands/copyfromparse.c @@ -978,6 +978,11 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext, Assert(fieldno == attr_count); } + else if (cstate->routine) + { + if (!cstate->routine->CopyFromOneRow(cstate, econtext, values, nulls)) + return false; + } else { /* binary */ diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index 20ffc90363..6080627c83 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -24,6 +24,7 @@ #include "access/xact.h" #include "access/xlog.h" #include "commands/copy.h" +#include "commands/copyapi.h" #include "commands/progress.h" #include "executor/execdesc.h" #include "executor/executor.h" @@ -71,6 +72,9 @@ typedef enum CopyDest */ typedef struct CopyToStateData { + /* format routine */ + const CopyToRoutine *routine; + /* low-level state data */ CopyDest copy_dest; /* type of copy source/destination */ FILE *copy_file; /* used if copy_dest == COPY_FILE */ @@ -777,14 +781,22 @@ DoCopyTo(CopyToState cstate) Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1); if (cstate->opts.binary) + { getTypeBinaryOutputInfo(attr->atttypid, &out_func_oid, &isvarlena); + fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + } + else if (cstate->routine) + cstate->routine->CopyToOutFunc(cstate, attr->atttypid, + &cstate->out_functions[attnum - 1]); else + { getTypeOutputInfo(attr->atttypid, &out_func_oid, &isvarlena); - fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + } } /* @@ -811,6 +823,8 @@ DoCopyTo(CopyToState cstate) tmp = 0; CopySendInt32(cstate, tmp); } + else if (cstate->routine) + cstate->routine->CopyToStart(cstate, tupDesc); else { /* @@ -892,6 +906,8 @@ DoCopyTo(CopyToState cstate) /* Need to flush out the trailer */ CopySendEndOfRow(cstate); } + else if (cstate->routine) + cstate->routine->CopyToEnd(cstate); MemoryContextDelete(cstate->rowcontext); @@ -916,61 +932,66 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot) MemoryContextReset(cstate->rowcontext); oldcontext = MemoryContextSwitchTo(cstate->rowcontext); - if (cstate->opts.binary) + if (cstate->routine) + cstate->routine->CopyToOneRow(cstate, slot); + else { - /* Binary per-tuple header */ - CopySendInt16(cstate, list_length(cstate->attnumlist)); - } - - /* Make sure the tuple is fully deconstructed */ - slot_getallattrs(slot); - - foreach(cur, cstate->attnumlist) - { - int attnum = lfirst_int(cur); - Datum value = slot->tts_values[attnum - 1]; - bool isnull = slot->tts_isnull[attnum - 1]; - - if (!cstate->opts.binary) + if (cstate->opts.binary) { - if (need_delim) - CopySendChar(cstate, cstate->opts.delim[0]); - need_delim = true; + /* Binary per-tuple header */ + CopySendInt16(cstate, list_length(cstate->attnumlist)); } - if (isnull) - { - if (!cstate->opts.binary) - CopySendString(cstate, cstate->opts.null_print_client); - else - CopySendInt32(cstate, -1); - } - else + /* Make sure the tuple is fully deconstructed */ + slot_getallattrs(slot); + + foreach(cur, cstate->attnumlist) { + int attnum = lfirst_int(cur); + Datum value = slot->tts_values[attnum - 1]; + bool isnull = slot->tts_isnull[attnum - 1]; + if (!cstate->opts.binary) { - string = OutputFunctionCall(&out_functions[attnum - 1], - value); - if (cstate->opts.csv_mode) - CopyAttributeOutCSV(cstate, string, - cstate->opts.force_quote_flags[attnum - 1]); + if (need_delim) + CopySendChar(cstate, cstate->opts.delim[0]); + need_delim = true; + } + + if (isnull) + { + if (!cstate->opts.binary) + CopySendString(cstate, cstate->opts.null_print_client); else - CopyAttributeOutText(cstate, string); + CopySendInt32(cstate, -1); } else { - bytea *outputbytes; + if (!cstate->opts.binary) + { + string = OutputFunctionCall(&out_functions[attnum - 1], + value); + if (cstate->opts.csv_mode) + CopyAttributeOutCSV(cstate, string, + cstate->opts.force_quote_flags[attnum - 1]); + else + CopyAttributeOutText(cstate, string); + } + else + { + bytea *outputbytes; - outputbytes = SendFunctionCall(&out_functions[attnum - 1], - value); - CopySendInt32(cstate, VARSIZE(outputbytes) - VARHDRSZ); - CopySendData(cstate, VARDATA(outputbytes), - VARSIZE(outputbytes) - VARHDRSZ); + outputbytes = SendFunctionCall(&out_functions[attnum - 1], + value); + CopySendInt32(cstate, VARSIZE(outputbytes) - VARHDRSZ); + CopySendData(cstate, VARDATA(outputbytes), + VARSIZE(outputbytes) - VARHDRSZ); + } } } - } - CopySendEndOfRow(cstate); + CopySendEndOfRow(cstate); + } MemoryContextSwitchTo(oldcontext); } diff --git a/src/include/commands/copyapi.h b/src/include/commands/copyapi.h new file mode 100644 index 0000000000..635c4cbff2 --- /dev/null +++ b/src/include/commands/copyapi.h @@ -0,0 +1,100 @@ +/*------------------------------------------------------------------------- + * + * copyapi.h + * API for COPY TO/FROM handlers + * + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/copyapi.h + * + *------------------------------------------------------------------------- + */ +#ifndef COPYAPI_H +#define COPYAPI_H + +#include "executor/tuptable.h" +#include "nodes/execnodes.h" + +/* These are private in commands/copy[from|to].c */ +typedef struct CopyFromStateData *CopyFromState; +typedef struct CopyToStateData *CopyToState; + +/* + * API structure for a COPY FROM format implementation. Note this must be + * allocated in a server-lifetime manner, typically as a static const struct. + */ +typedef struct CopyFromRoutine +{ + /* + * Called when COPY FROM is started to set up the input functions + * associated to the relation's attributes writing to. `finfo` can be + * optionally filled to provide the catalog information of the input + * function. `typioparam` can be optionally filled to define the OID of + * the type to pass to the input function. `atttypid` is the OID of data + * type used by the relation's attribute. + */ + void (*CopyFromInFunc) (CopyFromState cstate, Oid atttypid, + FmgrInfo *finfo, Oid *typioparam); + + /* + * Called when COPY FROM is started. + * + * `tupDesc` is the tuple descriptor of the relation where the data needs + * to be copied. This can be used for any initialization steps required + * by a format. + */ + void (*CopyFromStart) (CopyFromState cstate, TupleDesc tupDesc); + + /* + * Copy one row to a set of `values` and `nulls` of size tupDesc->natts. + * + * 'econtext' is used to evaluate default expression for each column that + * is either not read from the file or is using the DEFAULT option of COPY + * FROM. It is NULL if no default values are used. + * + * Returns false if there are no more tuples to copy. + */ + bool (*CopyFromOneRow) (CopyFromState cstate, ExprContext *econtext, + Datum *values, bool *nulls); + + /* Called when COPY FROM has ended. */ + void (*CopyFromEnd) (CopyFromState cstate); +} CopyFromRoutine; + +/* + * API structure for a COPY TO format implementation. Note this must be + * allocated in a server-lifetime manner, typically as a static const struct. + */ +typedef struct CopyToRoutine +{ + /* + * Called when COPY TO is started to set up the output functions + * associated to the relation's attributes reading from. `finfo` can be + * optionally filled. `atttypid` is the OID of data type used by the + * relation's attribute. + */ + void (*CopyToOutFunc) (CopyToState cstate, Oid atttypid, + FmgrInfo *finfo); + + /* + * Called when COPY TO is started. + * + * `tupDesc` is the tuple descriptor of the relation from where the data + * is read. + */ + void (*CopyToStart) (CopyToState cstate, TupleDesc tupDesc); + + /* + * Copy one row for COPY TO. + * + * `slot` is the tuple slot where the data is emitted. + */ + void (*CopyToOneRow) (CopyToState cstate, TupleTableSlot *slot); + + /* Called when COPY TO has ended */ + void (*CopyToEnd) (CopyToState cstate); +} CopyToRoutine; + +#endif /* COPYAPI_H */ diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h index cad52fcc78..509b9e92a1 100644 --- a/src/include/commands/copyfrom_internal.h +++ b/src/include/commands/copyfrom_internal.h @@ -15,6 +15,7 @@ #define COPYFROM_INTERNAL_H #include "commands/copy.h" +#include "commands/copyapi.h" #include "commands/trigger.h" #include "nodes/miscnodes.h" @@ -58,6 +59,9 @@ typedef enum CopyInsertMethod */ typedef struct CopyFromStateData { + /* format routine */ + const CopyFromRoutine *routine; + /* low-level state data */ CopySource copy_src; /* type of copy source */ FILE *copy_file; /* used if copy_src == COPY_FILE */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index ee40a341d3..a5ae161ca5 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -475,6 +475,7 @@ ConvertRowtypeExpr CookedConstraint CopyDest CopyFormatOptions +CopyFromRoutine CopyFromState CopyFromStateData CopyHeaderChoice @@ -484,6 +485,7 @@ CopyMultiInsertInfo CopyOnErrorChoice CopySource CopyStmt +CopyToRoutine CopyToState CopyToStateData Cost -- 2.43.0 ----Next_Part(Mon_Mar__4_14_11_08_2024_631)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v16-0001-Add-CopyFromRoutine-CopyToRountine-w.patch" ^ permalink raw reply [nested|flat] 29+ messages in thread
* [PATCH v16] Add CopyFromRoutine/CopyToRountine @ 2024-03-04 04:52 Sutou Kouhei <[email protected]> 0 siblings, 0 replies; 29+ messages in thread From: Sutou Kouhei @ 2024-03-04 04:52 UTC (permalink / raw) They are for implementing custom COPY FROM/TO format. But this is not enough to implement custom COPY FROM/TO format yet. We'll export some APIs to receive/send data and add "format" option to COPY FROM/TO later. Existing text/csv/binary format implementations don't use CopyFromRoutine/CopyToRoutine for now. We have a patch for it but we defer it. Because there are some mysterious profile results in spite of we get faster runtimes. See [1] for details. [1] https://www.postgresql.org/message-id/ZdbtQJ-p5H1_EDwE%40paquier.xyz Note that this doesn't change existing text/csv/binary format implementations. There are many diffs for CopyOneRowTo() but they're caused by indentation. They don't change implementations. --- src/backend/commands/copyfrom.c | 22 ++++- src/backend/commands/copyfromparse.c | 5 ++ src/backend/commands/copyto.c | 21 +++++ src/include/commands/copyapi.h | 100 +++++++++++++++++++++++ src/include/commands/copyfrom_internal.h | 4 + src/tools/pgindent/typedefs.list | 2 + 6 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 src/include/commands/copyapi.h diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c index c3bc897028..9bf2f6497e 100644 --- a/src/backend/commands/copyfrom.c +++ b/src/backend/commands/copyfrom.c @@ -1623,12 +1623,22 @@ BeginCopyFrom(ParseState *pstate, /* Fetch the input function and typioparam info */ if (cstate->opts.binary) + { getTypeBinaryInputInfo(att->atttypid, &in_func_oid, &typioparams[attnum - 1]); + fmgr_info(in_func_oid, &in_functions[attnum - 1]); + } + else if (cstate->routine) + cstate->routine->CopyFromInFunc(cstate, att->atttypid, + &in_functions[attnum - 1], + &typioparams[attnum - 1]); + else + { getTypeInputInfo(att->atttypid, &in_func_oid, &typioparams[attnum - 1]); fmgr_info(in_func_oid, &in_functions[attnum - 1]); + } /* Get default info if available */ defexprs[attnum - 1] = NULL; @@ -1768,10 +1778,13 @@ BeginCopyFrom(ParseState *pstate, /* Read and verify binary header */ ReceiveCopyBinaryHeader(cstate); } - - /* create workspace for CopyReadAttributes results */ - if (!cstate->opts.binary) + else if (cstate->routine) { + cstate->routine->CopyFromStart(cstate, tupDesc); + } + else + { + /* create workspace for CopyReadAttributes results */ AttrNumber attr_count = list_length(cstate->attnumlist); cstate->max_fields = attr_count; @@ -1789,6 +1802,9 @@ BeginCopyFrom(ParseState *pstate, void EndCopyFrom(CopyFromState cstate) { + if (cstate->routine) + cstate->routine->CopyFromEnd(cstate); + /* No COPY FROM related resources except memory. */ if (cstate->is_program) { diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c index 7cacd0b752..8b15080585 100644 --- a/src/backend/commands/copyfromparse.c +++ b/src/backend/commands/copyfromparse.c @@ -978,6 +978,11 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext, Assert(fieldno == attr_count); } + else if (cstate->routine) + { + if (!cstate->routine->CopyFromOneRow(cstate, econtext, values, nulls)) + return false; + } else { /* binary */ diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index 20ffc90363..6080627c83 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -24,6 +24,7 @@ #include "access/xact.h" #include "access/xlog.h" #include "commands/copy.h" +#include "commands/copyapi.h" #include "commands/progress.h" #include "executor/execdesc.h" #include "executor/executor.h" @@ -71,6 +72,9 @@ typedef enum CopyDest */ typedef struct CopyToStateData { + /* format routine */ + const CopyToRoutine *routine; + /* low-level state data */ CopyDest copy_dest; /* type of copy source/destination */ FILE *copy_file; /* used if copy_dest == COPY_FILE */ @@ -777,15 +781,23 @@ DoCopyTo(CopyToState cstate) Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1); if (cstate->opts.binary) + { getTypeBinaryOutputInfo(attr->atttypid, &out_func_oid, &isvarlena); + fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + } + else if (cstate->routine) + cstate->routine->CopyToOutFunc(cstate, attr->atttypid, + &cstate->out_functions[attnum - 1]); else + { getTypeOutputInfo(attr->atttypid, &out_func_oid, &isvarlena); fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); } + } /* * Create a temporary memory context that we can reset once per row to @@ -811,6 +823,8 @@ DoCopyTo(CopyToState cstate) tmp = 0; CopySendInt32(cstate, tmp); } + else if (cstate->routine) + cstate->routine->CopyToStart(cstate, tupDesc); else { /* @@ -892,6 +906,8 @@ DoCopyTo(CopyToState cstate) /* Need to flush out the trailer */ CopySendEndOfRow(cstate); } + else if (cstate->routine) + cstate->routine->CopyToEnd(cstate); MemoryContextDelete(cstate->rowcontext); @@ -916,6 +932,10 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot) MemoryContextReset(cstate->rowcontext); oldcontext = MemoryContextSwitchTo(cstate->rowcontext); + if (cstate->routine) + cstate->routine->CopyToOneRow(cstate, slot); + else + { if (cstate->opts.binary) { /* Binary per-tuple header */ @@ -971,6 +991,7 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot) } CopySendEndOfRow(cstate); + } MemoryContextSwitchTo(oldcontext); } diff --git a/src/include/commands/copyapi.h b/src/include/commands/copyapi.h new file mode 100644 index 0000000000..635c4cbff2 --- /dev/null +++ b/src/include/commands/copyapi.h @@ -0,0 +1,100 @@ +/*------------------------------------------------------------------------- + * + * copyapi.h + * API for COPY TO/FROM handlers + * + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/copyapi.h + * + *------------------------------------------------------------------------- + */ +#ifndef COPYAPI_H +#define COPYAPI_H + +#include "executor/tuptable.h" +#include "nodes/execnodes.h" + +/* These are private in commands/copy[from|to].c */ +typedef struct CopyFromStateData *CopyFromState; +typedef struct CopyToStateData *CopyToState; + +/* + * API structure for a COPY FROM format implementation. Note this must be + * allocated in a server-lifetime manner, typically as a static const struct. + */ +typedef struct CopyFromRoutine +{ + /* + * Called when COPY FROM is started to set up the input functions + * associated to the relation's attributes writing to. `finfo` can be + * optionally filled to provide the catalog information of the input + * function. `typioparam` can be optionally filled to define the OID of + * the type to pass to the input function. `atttypid` is the OID of data + * type used by the relation's attribute. + */ + void (*CopyFromInFunc) (CopyFromState cstate, Oid atttypid, + FmgrInfo *finfo, Oid *typioparam); + + /* + * Called when COPY FROM is started. + * + * `tupDesc` is the tuple descriptor of the relation where the data needs + * to be copied. This can be used for any initialization steps required + * by a format. + */ + void (*CopyFromStart) (CopyFromState cstate, TupleDesc tupDesc); + + /* + * Copy one row to a set of `values` and `nulls` of size tupDesc->natts. + * + * 'econtext' is used to evaluate default expression for each column that + * is either not read from the file or is using the DEFAULT option of COPY + * FROM. It is NULL if no default values are used. + * + * Returns false if there are no more tuples to copy. + */ + bool (*CopyFromOneRow) (CopyFromState cstate, ExprContext *econtext, + Datum *values, bool *nulls); + + /* Called when COPY FROM has ended. */ + void (*CopyFromEnd) (CopyFromState cstate); +} CopyFromRoutine; + +/* + * API structure for a COPY TO format implementation. Note this must be + * allocated in a server-lifetime manner, typically as a static const struct. + */ +typedef struct CopyToRoutine +{ + /* + * Called when COPY TO is started to set up the output functions + * associated to the relation's attributes reading from. `finfo` can be + * optionally filled. `atttypid` is the OID of data type used by the + * relation's attribute. + */ + void (*CopyToOutFunc) (CopyToState cstate, Oid atttypid, + FmgrInfo *finfo); + + /* + * Called when COPY TO is started. + * + * `tupDesc` is the tuple descriptor of the relation from where the data + * is read. + */ + void (*CopyToStart) (CopyToState cstate, TupleDesc tupDesc); + + /* + * Copy one row for COPY TO. + * + * `slot` is the tuple slot where the data is emitted. + */ + void (*CopyToOneRow) (CopyToState cstate, TupleTableSlot *slot); + + /* Called when COPY TO has ended */ + void (*CopyToEnd) (CopyToState cstate); +} CopyToRoutine; + +#endif /* COPYAPI_H */ diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h index cad52fcc78..509b9e92a1 100644 --- a/src/include/commands/copyfrom_internal.h +++ b/src/include/commands/copyfrom_internal.h @@ -15,6 +15,7 @@ #define COPYFROM_INTERNAL_H #include "commands/copy.h" +#include "commands/copyapi.h" #include "commands/trigger.h" #include "nodes/miscnodes.h" @@ -58,6 +59,9 @@ typedef enum CopyInsertMethod */ typedef struct CopyFromStateData { + /* format routine */ + const CopyFromRoutine *routine; + /* low-level state data */ CopySource copy_src; /* type of copy source */ FILE *copy_file; /* used if copy_src == COPY_FILE */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index ee40a341d3..a5ae161ca5 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -475,6 +475,7 @@ ConvertRowtypeExpr CookedConstraint CopyDest CopyFormatOptions +CopyFromRoutine CopyFromState CopyFromStateData CopyHeaderChoice @@ -484,6 +485,7 @@ CopyMultiInsertInfo CopyOnErrorChoice CopySource CopyStmt +CopyToRoutine CopyToState CopyToStateData Cost -- 2.43.0 ----Next_Part(Mon_Mar__4_14_11_08_2024_631)---- ^ permalink raw reply [nested|flat] 29+ messages in thread
* [PATCH v17] Add CopyFromRoutine/CopyToRountine @ 2024-03-04 04:52 Sutou Kouhei <[email protected]> 0 siblings, 0 replies; 29+ messages in thread From: Sutou Kouhei @ 2024-03-04 04:52 UTC (permalink / raw) They are for implementing custom COPY FROM/TO format. But this is not enough to implement custom COPY FROM/TO format yet. We'll export some APIs to receive/send data and add "format" option to COPY FROM/TO later. Existing text/csv/binary format implementations don't use CopyFromRoutine/CopyToRoutine for now. We have a patch for it but we defer it. Because there are some mysterious profile results in spite of we get faster runtimes. See [1] for details. [1] https://www.postgresql.org/message-id/ZdbtQJ-p5H1_EDwE%40paquier.xyz Note that this doesn't change existing text/csv/binary format implementations. --- src/backend/commands/copyfrom.c | 24 +++++- src/backend/commands/copyfromparse.c | 5 ++ src/backend/commands/copyto.c | 25 +++++- src/include/commands/copyapi.h | 100 +++++++++++++++++++++++ src/include/commands/copyfrom_internal.h | 4 + src/tools/pgindent/typedefs.list | 2 + 6 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 src/include/commands/copyapi.h diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c index c3bc897028..9bf2f6497e 100644 --- a/src/backend/commands/copyfrom.c +++ b/src/backend/commands/copyfrom.c @@ -1623,12 +1623,22 @@ BeginCopyFrom(ParseState *pstate, /* Fetch the input function and typioparam info */ if (cstate->opts.binary) + { getTypeBinaryInputInfo(att->atttypid, &in_func_oid, &typioparams[attnum - 1]); + fmgr_info(in_func_oid, &in_functions[attnum - 1]); + } + else if (cstate->routine) + cstate->routine->CopyFromInFunc(cstate, att->atttypid, + &in_functions[attnum - 1], + &typioparams[attnum - 1]); + else + { getTypeInputInfo(att->atttypid, &in_func_oid, &typioparams[attnum - 1]); - fmgr_info(in_func_oid, &in_functions[attnum - 1]); + fmgr_info(in_func_oid, &in_functions[attnum - 1]); + } /* Get default info if available */ defexprs[attnum - 1] = NULL; @@ -1768,10 +1778,13 @@ BeginCopyFrom(ParseState *pstate, /* Read and verify binary header */ ReceiveCopyBinaryHeader(cstate); } - - /* create workspace for CopyReadAttributes results */ - if (!cstate->opts.binary) + else if (cstate->routine) { + cstate->routine->CopyFromStart(cstate, tupDesc); + } + else + { + /* create workspace for CopyReadAttributes results */ AttrNumber attr_count = list_length(cstate->attnumlist); cstate->max_fields = attr_count; @@ -1789,6 +1802,9 @@ BeginCopyFrom(ParseState *pstate, void EndCopyFrom(CopyFromState cstate) { + if (cstate->routine) + cstate->routine->CopyFromEnd(cstate); + /* No COPY FROM related resources except memory. */ if (cstate->is_program) { diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c index 7cacd0b752..8b15080585 100644 --- a/src/backend/commands/copyfromparse.c +++ b/src/backend/commands/copyfromparse.c @@ -978,6 +978,11 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext, Assert(fieldno == attr_count); } + else if (cstate->routine) + { + if (!cstate->routine->CopyFromOneRow(cstate, econtext, values, nulls)) + return false; + } else { /* binary */ diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index 20ffc90363..b4a7c9c8b9 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -24,6 +24,7 @@ #include "access/xact.h" #include "access/xlog.h" #include "commands/copy.h" +#include "commands/copyapi.h" #include "commands/progress.h" #include "executor/execdesc.h" #include "executor/executor.h" @@ -71,6 +72,9 @@ typedef enum CopyDest */ typedef struct CopyToStateData { + /* format routine */ + const CopyToRoutine *routine; + /* low-level state data */ CopyDest copy_dest; /* type of copy source/destination */ FILE *copy_file; /* used if copy_dest == COPY_FILE */ @@ -777,14 +781,22 @@ DoCopyTo(CopyToState cstate) Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1); if (cstate->opts.binary) + { getTypeBinaryOutputInfo(attr->atttypid, &out_func_oid, &isvarlena); + fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + } + else if (cstate->routine) + cstate->routine->CopyToOutFunc(cstate, attr->atttypid, + &cstate->out_functions[attnum - 1]); else + { getTypeOutputInfo(attr->atttypid, &out_func_oid, &isvarlena); - fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + } } /* @@ -811,6 +823,8 @@ DoCopyTo(CopyToState cstate) tmp = 0; CopySendInt32(cstate, tmp); } + else if (cstate->routine) + cstate->routine->CopyToStart(cstate, tupDesc); else { /* @@ -892,6 +906,8 @@ DoCopyTo(CopyToState cstate) /* Need to flush out the trailer */ CopySendEndOfRow(cstate); } + else if (cstate->routine) + cstate->routine->CopyToEnd(cstate); MemoryContextDelete(cstate->rowcontext); @@ -916,6 +932,13 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot) MemoryContextReset(cstate->rowcontext); oldcontext = MemoryContextSwitchTo(cstate->rowcontext); + if (cstate->routine) + { + cstate->routine->CopyToOneRow(cstate, slot); + MemoryContextSwitchTo(oldcontext); + return; + } + if (cstate->opts.binary) { /* Binary per-tuple header */ diff --git a/src/include/commands/copyapi.h b/src/include/commands/copyapi.h new file mode 100644 index 0000000000..635c4cbff2 --- /dev/null +++ b/src/include/commands/copyapi.h @@ -0,0 +1,100 @@ +/*------------------------------------------------------------------------- + * + * copyapi.h + * API for COPY TO/FROM handlers + * + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/copyapi.h + * + *------------------------------------------------------------------------- + */ +#ifndef COPYAPI_H +#define COPYAPI_H + +#include "executor/tuptable.h" +#include "nodes/execnodes.h" + +/* These are private in commands/copy[from|to].c */ +typedef struct CopyFromStateData *CopyFromState; +typedef struct CopyToStateData *CopyToState; + +/* + * API structure for a COPY FROM format implementation. Note this must be + * allocated in a server-lifetime manner, typically as a static const struct. + */ +typedef struct CopyFromRoutine +{ + /* + * Called when COPY FROM is started to set up the input functions + * associated to the relation's attributes writing to. `finfo` can be + * optionally filled to provide the catalog information of the input + * function. `typioparam` can be optionally filled to define the OID of + * the type to pass to the input function. `atttypid` is the OID of data + * type used by the relation's attribute. + */ + void (*CopyFromInFunc) (CopyFromState cstate, Oid atttypid, + FmgrInfo *finfo, Oid *typioparam); + + /* + * Called when COPY FROM is started. + * + * `tupDesc` is the tuple descriptor of the relation where the data needs + * to be copied. This can be used for any initialization steps required + * by a format. + */ + void (*CopyFromStart) (CopyFromState cstate, TupleDesc tupDesc); + + /* + * Copy one row to a set of `values` and `nulls` of size tupDesc->natts. + * + * 'econtext' is used to evaluate default expression for each column that + * is either not read from the file or is using the DEFAULT option of COPY + * FROM. It is NULL if no default values are used. + * + * Returns false if there are no more tuples to copy. + */ + bool (*CopyFromOneRow) (CopyFromState cstate, ExprContext *econtext, + Datum *values, bool *nulls); + + /* Called when COPY FROM has ended. */ + void (*CopyFromEnd) (CopyFromState cstate); +} CopyFromRoutine; + +/* + * API structure for a COPY TO format implementation. Note this must be + * allocated in a server-lifetime manner, typically as a static const struct. + */ +typedef struct CopyToRoutine +{ + /* + * Called when COPY TO is started to set up the output functions + * associated to the relation's attributes reading from. `finfo` can be + * optionally filled. `atttypid` is the OID of data type used by the + * relation's attribute. + */ + void (*CopyToOutFunc) (CopyToState cstate, Oid atttypid, + FmgrInfo *finfo); + + /* + * Called when COPY TO is started. + * + * `tupDesc` is the tuple descriptor of the relation from where the data + * is read. + */ + void (*CopyToStart) (CopyToState cstate, TupleDesc tupDesc); + + /* + * Copy one row for COPY TO. + * + * `slot` is the tuple slot where the data is emitted. + */ + void (*CopyToOneRow) (CopyToState cstate, TupleTableSlot *slot); + + /* Called when COPY TO has ended */ + void (*CopyToEnd) (CopyToState cstate); +} CopyToRoutine; + +#endif /* COPYAPI_H */ diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h index cad52fcc78..509b9e92a1 100644 --- a/src/include/commands/copyfrom_internal.h +++ b/src/include/commands/copyfrom_internal.h @@ -15,6 +15,7 @@ #define COPYFROM_INTERNAL_H #include "commands/copy.h" +#include "commands/copyapi.h" #include "commands/trigger.h" #include "nodes/miscnodes.h" @@ -58,6 +59,9 @@ typedef enum CopyInsertMethod */ typedef struct CopyFromStateData { + /* format routine */ + const CopyFromRoutine *routine; + /* low-level state data */ CopySource copy_src; /* type of copy source */ FILE *copy_file; /* used if copy_src == COPY_FILE */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index ee40a341d3..a5ae161ca5 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -475,6 +475,7 @@ ConvertRowtypeExpr CookedConstraint CopyDest CopyFormatOptions +CopyFromRoutine CopyFromState CopyFromStateData CopyHeaderChoice @@ -484,6 +485,7 @@ CopyMultiInsertInfo CopyOnErrorChoice CopySource CopyStmt +CopyToRoutine CopyToState CopyToStateData Cost -- 2.43.0 ----Next_Part(Tue_Mar__5_17_18_08_2024_468)---- ^ permalink raw reply [nested|flat] 29+ messages in thread
* [PATCH v16] Add CopyFromRoutine/CopyToRountine @ 2024-03-04 04:52 Sutou Kouhei <[email protected]> 0 siblings, 0 replies; 29+ messages in thread From: Sutou Kouhei @ 2024-03-04 04:52 UTC (permalink / raw) They are for implementing custom COPY FROM/TO format. But this is not enough to implement custom COPY FROM/TO format yet. We'll export some APIs to receive/send data and add "format" option to COPY FROM/TO later. Existing text/csv/binary format implementations don't use CopyFromRoutine/CopyToRoutine for now. We have a patch for it but we defer it. Because there are some mysterious profile results in spite of we get faster runtimes. See [1] for details. [1] https://www.postgresql.org/message-id/ZdbtQJ-p5H1_EDwE%40paquier.xyz Note that this doesn't change existing text/csv/binary format implementations. There are many diffs for CopyOneRowTo() but they're caused by indentation. They don't change implementations. --- src/backend/commands/copyfrom.c | 24 +++++- src/backend/commands/copyfromparse.c | 5 ++ src/backend/commands/copyto.c | 103 ++++++++++++++--------- src/include/commands/copyapi.h | 100 ++++++++++++++++++++++ src/include/commands/copyfrom_internal.h | 4 + src/tools/pgindent/typedefs.list | 2 + 6 files changed, 193 insertions(+), 45 deletions(-) create mode 100644 src/include/commands/copyapi.h diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c index c3bc897028..9bf2f6497e 100644 --- a/src/backend/commands/copyfrom.c +++ b/src/backend/commands/copyfrom.c @@ -1623,12 +1623,22 @@ BeginCopyFrom(ParseState *pstate, /* Fetch the input function and typioparam info */ if (cstate->opts.binary) + { getTypeBinaryInputInfo(att->atttypid, &in_func_oid, &typioparams[attnum - 1]); + fmgr_info(in_func_oid, &in_functions[attnum - 1]); + } + else if (cstate->routine) + cstate->routine->CopyFromInFunc(cstate, att->atttypid, + &in_functions[attnum - 1], + &typioparams[attnum - 1]); + else + { getTypeInputInfo(att->atttypid, &in_func_oid, &typioparams[attnum - 1]); - fmgr_info(in_func_oid, &in_functions[attnum - 1]); + fmgr_info(in_func_oid, &in_functions[attnum - 1]); + } /* Get default info if available */ defexprs[attnum - 1] = NULL; @@ -1768,10 +1778,13 @@ BeginCopyFrom(ParseState *pstate, /* Read and verify binary header */ ReceiveCopyBinaryHeader(cstate); } - - /* create workspace for CopyReadAttributes results */ - if (!cstate->opts.binary) + else if (cstate->routine) { + cstate->routine->CopyFromStart(cstate, tupDesc); + } + else + { + /* create workspace for CopyReadAttributes results */ AttrNumber attr_count = list_length(cstate->attnumlist); cstate->max_fields = attr_count; @@ -1789,6 +1802,9 @@ BeginCopyFrom(ParseState *pstate, void EndCopyFrom(CopyFromState cstate) { + if (cstate->routine) + cstate->routine->CopyFromEnd(cstate); + /* No COPY FROM related resources except memory. */ if (cstate->is_program) { diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c index 7cacd0b752..8b15080585 100644 --- a/src/backend/commands/copyfromparse.c +++ b/src/backend/commands/copyfromparse.c @@ -978,6 +978,11 @@ NextCopyFrom(CopyFromState cstate, ExprContext *econtext, Assert(fieldno == attr_count); } + else if (cstate->routine) + { + if (!cstate->routine->CopyFromOneRow(cstate, econtext, values, nulls)) + return false; + } else { /* binary */ diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c index 20ffc90363..6080627c83 100644 --- a/src/backend/commands/copyto.c +++ b/src/backend/commands/copyto.c @@ -24,6 +24,7 @@ #include "access/xact.h" #include "access/xlog.h" #include "commands/copy.h" +#include "commands/copyapi.h" #include "commands/progress.h" #include "executor/execdesc.h" #include "executor/executor.h" @@ -71,6 +72,9 @@ typedef enum CopyDest */ typedef struct CopyToStateData { + /* format routine */ + const CopyToRoutine *routine; + /* low-level state data */ CopyDest copy_dest; /* type of copy source/destination */ FILE *copy_file; /* used if copy_dest == COPY_FILE */ @@ -777,14 +781,22 @@ DoCopyTo(CopyToState cstate) Form_pg_attribute attr = TupleDescAttr(tupDesc, attnum - 1); if (cstate->opts.binary) + { getTypeBinaryOutputInfo(attr->atttypid, &out_func_oid, &isvarlena); + fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + } + else if (cstate->routine) + cstate->routine->CopyToOutFunc(cstate, attr->atttypid, + &cstate->out_functions[attnum - 1]); else + { getTypeOutputInfo(attr->atttypid, &out_func_oid, &isvarlena); - fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + fmgr_info(out_func_oid, &cstate->out_functions[attnum - 1]); + } } /* @@ -811,6 +823,8 @@ DoCopyTo(CopyToState cstate) tmp = 0; CopySendInt32(cstate, tmp); } + else if (cstate->routine) + cstate->routine->CopyToStart(cstate, tupDesc); else { /* @@ -892,6 +906,8 @@ DoCopyTo(CopyToState cstate) /* Need to flush out the trailer */ CopySendEndOfRow(cstate); } + else if (cstate->routine) + cstate->routine->CopyToEnd(cstate); MemoryContextDelete(cstate->rowcontext); @@ -916,61 +932,66 @@ CopyOneRowTo(CopyToState cstate, TupleTableSlot *slot) MemoryContextReset(cstate->rowcontext); oldcontext = MemoryContextSwitchTo(cstate->rowcontext); - if (cstate->opts.binary) + if (cstate->routine) + cstate->routine->CopyToOneRow(cstate, slot); + else { - /* Binary per-tuple header */ - CopySendInt16(cstate, list_length(cstate->attnumlist)); - } - - /* Make sure the tuple is fully deconstructed */ - slot_getallattrs(slot); - - foreach(cur, cstate->attnumlist) - { - int attnum = lfirst_int(cur); - Datum value = slot->tts_values[attnum - 1]; - bool isnull = slot->tts_isnull[attnum - 1]; - - if (!cstate->opts.binary) + if (cstate->opts.binary) { - if (need_delim) - CopySendChar(cstate, cstate->opts.delim[0]); - need_delim = true; + /* Binary per-tuple header */ + CopySendInt16(cstate, list_length(cstate->attnumlist)); } - if (isnull) - { - if (!cstate->opts.binary) - CopySendString(cstate, cstate->opts.null_print_client); - else - CopySendInt32(cstate, -1); - } - else + /* Make sure the tuple is fully deconstructed */ + slot_getallattrs(slot); + + foreach(cur, cstate->attnumlist) { + int attnum = lfirst_int(cur); + Datum value = slot->tts_values[attnum - 1]; + bool isnull = slot->tts_isnull[attnum - 1]; + if (!cstate->opts.binary) { - string = OutputFunctionCall(&out_functions[attnum - 1], - value); - if (cstate->opts.csv_mode) - CopyAttributeOutCSV(cstate, string, - cstate->opts.force_quote_flags[attnum - 1]); + if (need_delim) + CopySendChar(cstate, cstate->opts.delim[0]); + need_delim = true; + } + + if (isnull) + { + if (!cstate->opts.binary) + CopySendString(cstate, cstate->opts.null_print_client); else - CopyAttributeOutText(cstate, string); + CopySendInt32(cstate, -1); } else { - bytea *outputbytes; + if (!cstate->opts.binary) + { + string = OutputFunctionCall(&out_functions[attnum - 1], + value); + if (cstate->opts.csv_mode) + CopyAttributeOutCSV(cstate, string, + cstate->opts.force_quote_flags[attnum - 1]); + else + CopyAttributeOutText(cstate, string); + } + else + { + bytea *outputbytes; - outputbytes = SendFunctionCall(&out_functions[attnum - 1], - value); - CopySendInt32(cstate, VARSIZE(outputbytes) - VARHDRSZ); - CopySendData(cstate, VARDATA(outputbytes), - VARSIZE(outputbytes) - VARHDRSZ); + outputbytes = SendFunctionCall(&out_functions[attnum - 1], + value); + CopySendInt32(cstate, VARSIZE(outputbytes) - VARHDRSZ); + CopySendData(cstate, VARDATA(outputbytes), + VARSIZE(outputbytes) - VARHDRSZ); + } } } - } - CopySendEndOfRow(cstate); + CopySendEndOfRow(cstate); + } MemoryContextSwitchTo(oldcontext); } diff --git a/src/include/commands/copyapi.h b/src/include/commands/copyapi.h new file mode 100644 index 0000000000..635c4cbff2 --- /dev/null +++ b/src/include/commands/copyapi.h @@ -0,0 +1,100 @@ +/*------------------------------------------------------------------------- + * + * copyapi.h + * API for COPY TO/FROM handlers + * + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/copyapi.h + * + *------------------------------------------------------------------------- + */ +#ifndef COPYAPI_H +#define COPYAPI_H + +#include "executor/tuptable.h" +#include "nodes/execnodes.h" + +/* These are private in commands/copy[from|to].c */ +typedef struct CopyFromStateData *CopyFromState; +typedef struct CopyToStateData *CopyToState; + +/* + * API structure for a COPY FROM format implementation. Note this must be + * allocated in a server-lifetime manner, typically as a static const struct. + */ +typedef struct CopyFromRoutine +{ + /* + * Called when COPY FROM is started to set up the input functions + * associated to the relation's attributes writing to. `finfo` can be + * optionally filled to provide the catalog information of the input + * function. `typioparam` can be optionally filled to define the OID of + * the type to pass to the input function. `atttypid` is the OID of data + * type used by the relation's attribute. + */ + void (*CopyFromInFunc) (CopyFromState cstate, Oid atttypid, + FmgrInfo *finfo, Oid *typioparam); + + /* + * Called when COPY FROM is started. + * + * `tupDesc` is the tuple descriptor of the relation where the data needs + * to be copied. This can be used for any initialization steps required + * by a format. + */ + void (*CopyFromStart) (CopyFromState cstate, TupleDesc tupDesc); + + /* + * Copy one row to a set of `values` and `nulls` of size tupDesc->natts. + * + * 'econtext' is used to evaluate default expression for each column that + * is either not read from the file or is using the DEFAULT option of COPY + * FROM. It is NULL if no default values are used. + * + * Returns false if there are no more tuples to copy. + */ + bool (*CopyFromOneRow) (CopyFromState cstate, ExprContext *econtext, + Datum *values, bool *nulls); + + /* Called when COPY FROM has ended. */ + void (*CopyFromEnd) (CopyFromState cstate); +} CopyFromRoutine; + +/* + * API structure for a COPY TO format implementation. Note this must be + * allocated in a server-lifetime manner, typically as a static const struct. + */ +typedef struct CopyToRoutine +{ + /* + * Called when COPY TO is started to set up the output functions + * associated to the relation's attributes reading from. `finfo` can be + * optionally filled. `atttypid` is the OID of data type used by the + * relation's attribute. + */ + void (*CopyToOutFunc) (CopyToState cstate, Oid atttypid, + FmgrInfo *finfo); + + /* + * Called when COPY TO is started. + * + * `tupDesc` is the tuple descriptor of the relation from where the data + * is read. + */ + void (*CopyToStart) (CopyToState cstate, TupleDesc tupDesc); + + /* + * Copy one row for COPY TO. + * + * `slot` is the tuple slot where the data is emitted. + */ + void (*CopyToOneRow) (CopyToState cstate, TupleTableSlot *slot); + + /* Called when COPY TO has ended */ + void (*CopyToEnd) (CopyToState cstate); +} CopyToRoutine; + +#endif /* COPYAPI_H */ diff --git a/src/include/commands/copyfrom_internal.h b/src/include/commands/copyfrom_internal.h index cad52fcc78..509b9e92a1 100644 --- a/src/include/commands/copyfrom_internal.h +++ b/src/include/commands/copyfrom_internal.h @@ -15,6 +15,7 @@ #define COPYFROM_INTERNAL_H #include "commands/copy.h" +#include "commands/copyapi.h" #include "commands/trigger.h" #include "nodes/miscnodes.h" @@ -58,6 +59,9 @@ typedef enum CopyInsertMethod */ typedef struct CopyFromStateData { + /* format routine */ + const CopyFromRoutine *routine; + /* low-level state data */ CopySource copy_src; /* type of copy source */ FILE *copy_file; /* used if copy_src == COPY_FILE */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index ee40a341d3..a5ae161ca5 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -475,6 +475,7 @@ ConvertRowtypeExpr CookedConstraint CopyDest CopyFormatOptions +CopyFromRoutine CopyFromState CopyFromStateData CopyHeaderChoice @@ -484,6 +485,7 @@ CopyMultiInsertInfo CopyOnErrorChoice CopySource CopyStmt +CopyToRoutine CopyToState CopyToStateData Cost -- 2.43.0 ----Next_Part(Mon_Mar__4_14_11_08_2024_631)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v16-0001-Add-CopyFromRoutine-CopyToRountine-w.patch" ^ permalink raw reply [nested|flat] 29+ messages in thread
* Re: tablecmds.c/MergeAttributes() cleanup @ 2024-04-20 04:00 Alexander Lakhin <[email protected]> parent: Ashutosh Bapat <[email protected]> 0 siblings, 1 reply; 29+ messages in thread From: Alexander Lakhin @ 2024-04-20 04:00 UTC (permalink / raw) To: Ashutosh Bapat <[email protected]>; Peter Eisentraut <[email protected]>; +Cc: pgsql-hackers; Alvaro Herrera <[email protected]> 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] https://www.postgresql.org/message-id/flat/24656cec-d6ef-4d15-8b5b-e8dfc9c833a7%40eisentraut.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 ^ permalink raw reply [nested|flat] 29+ messages in thread
* Re: tablecmds.c/MergeAttributes() cleanup @ 2024-04-20 04:16 Ashutosh Bapat <[email protected]> parent: Alexander Lakhin <[email protected]> 0 siblings, 1 reply; 29+ messages in thread From: Ashutosh Bapat @ 2024-04-20 04:16 UTC (permalink / raw) To: Alexander Lakhin <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; pgsql-hackers; Alvaro Herrera <[email protected]> On Sat, Apr 20, 2024 at 9:30 AM Alexander Lakhin <[email protected]> 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] > https://www.postgresql.org/message-id/flat/24656cec-d6ef-4d15-8b5b-e8dfc9c833a7%40eisentraut.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 ^ permalink raw reply [nested|flat] 29+ messages in thread
* Re: tablecmds.c/MergeAttributes() cleanup @ 2024-04-29 13:15 Robert Haas <[email protected]> parent: Ashutosh Bapat <[email protected]> 0 siblings, 1 reply; 29+ messages in thread From: Robert Haas @ 2024-04-29 13:15 UTC (permalink / raw) To: Ashutosh Bapat <[email protected]>; +Cc: Alexander Lakhin <[email protected]>; Peter Eisentraut <[email protected]>; pgsql-hackers; Alvaro Herrera <[email protected]> On Sat, Apr 20, 2024 at 12:17 AM Ashutosh Bapat <[email protected]> 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 ^ permalink raw reply [nested|flat] 29+ messages in thread
* Re: tablecmds.c/MergeAttributes() cleanup @ 2024-04-30 06:19 Ashutosh Bapat <[email protected]> parent: Robert Haas <[email protected]> 0 siblings, 1 reply; 29+ messages in thread From: Ashutosh Bapat @ 2024-04-30 06:19 UTC (permalink / raw) To: Robert Haas <[email protected]>; +Cc: Alexander Lakhin <[email protected]>; Peter Eisentraut <[email protected]>; pgsql-hackers; Alvaro Herrera <[email protected]> On Mon, Apr 29, 2024 at 6:46 PM Robert Haas <[email protected]> wrote: > On Sat, Apr 20, 2024 at 12:17 AM Ashutosh Bapat > <[email protected]> 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: [text/x-patch] 0001-Fix-segmentation-fault-in-MergeInheritedAtt-20240430.patch (4.3K, ../../CAExHW5t=N+8DzjDgze_WY0Uf6umJGa0L+yTpc432sZ=5pGyfQw@mail.gmail.com/3-0001-Fix-segmentation-fault-in-MergeInheritedAtt-20240430.patch) download | inline diff: From 7c1ff7b17933eef9523486a2c0a054836db9cf24 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <[email protected]> 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 ^ permalink raw reply [nested|flat] 29+ messages in thread
* Re: tablecmds.c/MergeAttributes() cleanup @ 2024-04-30 19:48 Robert Haas <[email protected]> parent: Ashutosh Bapat <[email protected]> 0 siblings, 1 reply; 29+ messages in thread From: Robert Haas @ 2024-04-30 19:48 UTC (permalink / raw) To: Ashutosh Bapat <[email protected]>; +Cc: Alexander Lakhin <[email protected]>; Peter Eisentraut <[email protected]>; pgsql-hackers; Alvaro Herrera <[email protected]> On Tue, Apr 30, 2024 at 2:19 AM Ashutosh Bapat <[email protected]> wrote: > On Mon, Apr 29, 2024 at 6:46 PM Robert Haas <[email protected]> wrote: >> On Sat, Apr 20, 2024 at 12:17 AM Ashutosh Bapat >> <[email protected]> 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 ^ permalink raw reply [nested|flat] 29+ messages in thread
* Re: tablecmds.c/MergeAttributes() cleanup @ 2024-05-03 09:17 Peter Eisentraut <[email protected]> parent: Robert Haas <[email protected]> 0 siblings, 1 reply; 29+ messages in thread From: Peter Eisentraut @ 2024-05-03 09:17 UTC (permalink / raw) To: Robert Haas <[email protected]>; Ashutosh Bapat <[email protected]>; +Cc: Alexander Lakhin <[email protected]>; pgsql-hackers; Alvaro Herrera <[email protected]> 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. ^ permalink raw reply [nested|flat] 29+ messages in thread
* Re: tablecmds.c/MergeAttributes() cleanup @ 2024-05-03 09:32 Ashutosh Bapat <[email protected]> parent: Peter Eisentraut <[email protected]> 0 siblings, 0 replies; 29+ messages in thread From: Ashutosh Bapat @ 2024-05-03 09:32 UTC (permalink / raw) To: Peter Eisentraut <[email protected]>; +Cc: Robert Haas <[email protected]>; Alexander Lakhin <[email protected]>; pgsql-hackers; Alvaro Herrera <[email protected]> On Fri, May 3, 2024 at 2:47 PM Peter Eisentraut <[email protected]> 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 ^ permalink raw reply [nested|flat] 29+ messages in thread
end of thread, other threads:[~2024-05-03 09:32 UTC | newest] Thread overview: 29+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2018-10-11 01:03 [PATCH 2/7] Write WAL for empty nbtree index build Kyotaro Horiguchi <[email protected]> 2018-10-11 01:03 [PATCH 2/7] Write WAL for empty nbtree index build Kyotaro Horiguchi <[email protected]> 2018-10-11 01:03 [PATCH 2/4] Write WAL for empty nbtree index build Kyotaro Horiguchi <[email protected]> 2018-10-11 01:03 [PATCH 2/7] Write WAL for empty nbtree index build Kyotaro Horiguchi <[email protected]> 2018-10-11 01:03 [PATCH 2/8] Write WAL for empty nbtree index build Kyotaro Horiguchi <[email protected]> 2023-09-19 13:11 Re: tablecmds.c/MergeAttributes() cleanup Peter Eisentraut <[email protected]> 2023-10-05 15:49 ` Re: tablecmds.c/MergeAttributes() cleanup Peter Eisentraut <[email protected]> 2023-12-06 08:23 ` Re: tablecmds.c/MergeAttributes() cleanup Peter Eisentraut <[email protected]> 2024-01-22 12:43 ` Re: tablecmds.c/MergeAttributes() cleanup Peter Eisentraut <[email protected]> 2024-01-24 06:27 ` Re: tablecmds.c/MergeAttributes() cleanup Ashutosh Bapat <[email protected]> 2024-01-26 13:42 ` Re: tablecmds.c/MergeAttributes() cleanup Peter Eisentraut <[email protected]> 2024-01-28 08:00 ` Re: tablecmds.c/MergeAttributes() cleanup Alexander Lakhin <[email protected]> 2024-01-30 06:22 ` Re: tablecmds.c/MergeAttributes() cleanup Ashutosh Bapat <[email protected]> 2024-04-20 04:00 ` Re: tablecmds.c/MergeAttributes() cleanup Alexander Lakhin <[email protected]> 2024-04-20 04:16 ` Re: tablecmds.c/MergeAttributes() cleanup Ashutosh Bapat <[email protected]> 2024-04-29 13:15 ` Re: tablecmds.c/MergeAttributes() cleanup Robert Haas <[email protected]> 2024-04-30 06:19 ` Re: tablecmds.c/MergeAttributes() cleanup Ashutosh Bapat <[email protected]> 2024-04-30 19:48 ` Re: tablecmds.c/MergeAttributes() cleanup Robert Haas <[email protected]> 2024-05-03 09:17 ` Re: tablecmds.c/MergeAttributes() cleanup Peter Eisentraut <[email protected]> 2024-05-03 09:32 ` Re: tablecmds.c/MergeAttributes() cleanup Ashutosh Bapat <[email protected]> 2024-03-04 04:52 [PATCH v16] Add CopyFromRoutine/CopyToRountine Sutou Kouhei <[email protected]> 2024-03-04 04:52 [PATCH v17] Add CopyFromRoutine/CopyToRountine Sutou Kouhei <[email protected]> 2024-03-04 04:52 [PATCH v16] Add CopyFromRoutine/CopyToRountine Sutou Kouhei <[email protected]> 2024-03-04 04:52 [PATCH v17] Add CopyFromRoutine/CopyToRountine Sutou Kouhei <[email protected]> 2024-03-04 04:52 [PATCH v16] Add CopyFromRoutine/CopyToRountine Sutou Kouhei <[email protected]> 2024-03-04 04:52 [PATCH v16] Add CopyFromRoutine/CopyToRountine Sutou Kouhei <[email protected]> 2024-03-04 04:52 [PATCH v16] Add CopyFromRoutine/CopyToRountine Sutou Kouhei <[email protected]> 2024-03-04 04:52 [PATCH v16] Add CopyFromRoutine/CopyToRountine Sutou Kouhei <[email protected]> 2024-03-04 04:52 [PATCH v17] Add CopyFromRoutine/CopyToRountine Sutou Kouhei <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox