On 2018/10/05 16:05, Michael Paquier wrote:
>> As of commit 2fbdf1b38bc [1], which has been applied in 11 and HEAD
>> branches, RelationBuildPartitionDesc emits an error if we don't find
>> relpartbound set for a child found by scanning pg_inherits, instead of
>> skipping such children. While that commit switched the order of creating
>> pg_inherits entry and checking a new bound against existing bounds in
>> DefineRelation in light of aforementioned change, it didn't in
>> ATExecAttachPartition, hence this error.
>>
>> Attached patch fixes that.
>
> Could you please add a minimal regression test in your patch? That's
> the second bug related to ATTACH PARTITION I am looking at today..
OK, done, although I hope that's not bloating the tests.
>> I thought we'd need to apply this to 10, 11, HEAD, but I couldn't
>> reproduce this in 10. That's because the above commit wasn't applied to
>> 10, so the child that causes this error is being skipped in 10's case.
>
> Hmm. Indeed, v10 does not complain but HEAD does. (I ran the attached
> SQL file, which is the complete test case both of you have compiled).
Did you forget to attach some file?
Thanks,
Amit
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 8219d05d83..75e41ed21c 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -195,23 +195,12 @@ RelationBuildPartitionDesc(Relation rel)
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u",
inhrelid);
- /*
- * It is possible that the pg_class tuple of a partition has
not been
- * updated yet to set its relpartbound field. The only case
where
- * this happens is when we open the parent relation to check
using its
- * partition descriptor that a new partition's bound does not
overlap
- * some existing partition.
- */
- if (!((Form_pg_class) GETSTRUCT(tuple))->relispartition)
- {
- ReleaseSysCache(tuple);
- continue;
- }
-
datum = SysCacheGetAttr(RELOID, tuple,
Anum_pg_class_relpartbound,
&isnull);
- Assert(!isnull);
+ if(isnull)
+ elog(ERROR, "null relpartbound for relation %u",
inhrelid);
+
boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
boundspecs = lappend(boundspecs, boundspec);
partoids = lappend_oid(partoids, inhrelid);
@@ -1856,8 +1845,7 @@ generate_partition_qual(Relation rel)
Anum_pg_class_relpartbound,
&isnull);
if (isnull) /* should not happen */
- elog(ERROR, "relation \"%s\" has relpartbound = null",
- RelationGetRelationName(rel));
+ elog(ERROR, "null relpartbound for relation %u",
RelationGetRelid(rel));
bound = castNode(PartitionBoundSpec,
stringToNode(TextDatumGetCString(boundDatum)));
ReleaseSysCache(tuple);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f143101b5d..43b76812d2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -745,9 +745,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
false,
typaddress);
- /* Store inheritance information for new rel. */
- StoreCatalogInheritance(relationId, inheritOids, stmt->partbound !=
NULL);
-
/*
* We must bump the command counter to make the newly-created relation
* tuple visible for opening.
@@ -809,6 +806,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
CommandCounterIncrement();
}
+ /* Store inheritance information for new rel. */
+ StoreCatalogInheritance(relationId, inheritOids, stmt->partbound !=
NULL);
+
/*
* Process the partitioning specification (if any) and store the
partition
* key information into the catalog.
@@ -13611,9 +13611,6 @@ ATExecAttachPartition(List **wqueue, Relation rel,
PartitionCmd *cmd)
trigger_name,
RelationGetRelationName(attachrel)),
errdetail("ROW triggers with transition tables
are not supported on partitions")));
- /* OK to create inheritance. Rest of the checks performed there */
- CreateInheritance(attachrel, rel);
-
/*
* Check that the new partition's bound is valid and does not overlap
any
* of existing partitions of the parent - note that it does not return
on
@@ -13622,6 +13619,9 @@ ATExecAttachPartition(List **wqueue, Relation rel,
PartitionCmd *cmd)
check_new_partition_bound(RelationGetRelationName(attachrel), rel,
cmd->bound);
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachrel, rel);
+
/* Update the pg_class entry. */
StorePartitionBound(attachrel, rel, cmd->bound);
@@ -13837,10 +13837,6 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
RelationGetRelid(partRel));
Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition);
- (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
- &isnull);
- Assert(!isnull);
-
/* Clear relpartbound and reset relispartition */
memset(new_val, 0, sizeof(new_val));
memset(new_null, false, sizeof(new_null));
diff --git a/src/test/regress/expected/alter_table.out
b/src/test/regress/expected/alter_table.out
index 86127ce0df..bd3f918e48 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3669,3 +3669,18 @@ alter table temp_part_parent attach partition
temp_part_child
for values in (1, 2); -- ok
drop table perm_part_parent cascade;
drop table temp_part_parent cascade;
+-- test case where the partitioning operator is a SQL function whose
+-- evaluation results in the table's relcache being rebuilt partway through
+-- the execution of an ATTACH PARTITION command
+create function at_test_sql_partop (int4, int4) returns int language sql
+as $$ select case when $1 = $2 then 0 when $1 > $2 then 1 else -1 end; $$;
+create operator class at_test_sql_partop for type int4 using btree as
+ operator 1 < (int4, int4), operator 2 <= (int4, int4),
+ operator 3 = (int4, int4), operator 4 >= (int4, int4),
+ operator 5 > (int4, int4), function 1 at_test_sql_partop(int4, int4);
+create table at_test_sql_partop (a int) partition by range (a
at_test_sql_partop);
+create table at_test_sql_partop_1 (a int);
+alter table at_test_sql_partop attach partition at_test_sql_partop_1 for
values from (0) to (10);
+drop table at_test_sql_partop;
+drop operator class at_test_sql_partop using btree;
+drop function at_test_sql_partop;
diff --git a/src/test/regress/sql/alter_table.sql
b/src/test/regress/sql/alter_table.sql
index 519d984fd9..24b60f9b6e 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2388,3 +2388,19 @@ alter table temp_part_parent attach partition
temp_part_child
for values in (1, 2); -- ok
drop table perm_part_parent cascade;
drop table temp_part_parent cascade;
+
+-- test case where the partitioning operator is a SQL function whose
+-- evaluation results in the table's relcache being rebuilt partway through
+-- the execution of an ATTACH PARTITION command
+create function at_test_sql_partop (int4, int4) returns int language sql
+as $$ select case when $1 = $2 then 0 when $1 > $2 then 1 else -1 end; $$;
+create operator class at_test_sql_partop for type int4 using btree as
+ operator 1 < (int4, int4), operator 2 <= (int4, int4),
+ operator 3 = (int4, int4), operator 4 >= (int4, int4),
+ operator 5 > (int4, int4), function 1 at_test_sql_partop(int4, int4);
+create table at_test_sql_partop (a int) partition by range (a
at_test_sql_partop);
+create table at_test_sql_partop_1 (a int);
+alter table at_test_sql_partop attach partition at_test_sql_partop_1 for
values from (0) to (10);
+drop table at_test_sql_partop;
+drop operator class at_test_sql_partop using btree;
+drop function at_test_sql_partop;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c145385f84..2d0043d1db 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -14247,9 +14247,6 @@ ATExecAttachPartition(List **wqueue, Relation rel,
PartitionCmd *cmd)
trigger_name,
RelationGetRelationName(attachrel)),
errdetail("ROW triggers with transition tables
are not supported on partitions")));
- /* OK to create inheritance. Rest of the checks performed there */
- CreateInheritance(attachrel, rel);
-
/*
* Check that the new partition's bound is valid and does not overlap
any
* of existing partitions of the parent - note that it does not return
on
@@ -14258,6 +14255,9 @@ ATExecAttachPartition(List **wqueue, Relation rel,
PartitionCmd *cmd)
check_new_partition_bound(RelationGetRelationName(attachrel), rel,
cmd->bound);
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachrel, rel);
+
/* Update the pg_class entry. */
StorePartitionBound(attachrel, rel, cmd->bound);
diff --git a/src/test/regress/expected/alter_table.out
b/src/test/regress/expected/alter_table.out
index dccc9b27c5..be0b54cc52 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3960,3 +3960,18 @@ ERROR: cannot attach a temporary relation as partition
of permanent relation "p
alter table temp_part_parent attach partition temp_part_child default; -- ok
drop table perm_part_parent cascade;
drop table temp_part_parent cascade;
+-- test case where the partitioning operator is a SQL function whose
+-- evaluation results in the table's relcache being rebuilt partway through
+-- the execution of an ATTACH PARTITION command
+create function at_test_sql_partop (int4, int4) returns int language sql
+as $$ select case when $1 = $2 then 0 when $1 > $2 then 1 else -1 end; $$;
+create operator class at_test_sql_partop for type int4 using btree as
+ operator 1 < (int4, int4), operator 2 <= (int4, int4),
+ operator 3 = (int4, int4), operator 4 >= (int4, int4),
+ operator 5 > (int4, int4), function 1 at_test_sql_partop(int4, int4);
+create table at_test_sql_partop (a int) partition by range (a
at_test_sql_partop);
+create table at_test_sql_partop_1 (a int);
+alter table at_test_sql_partop attach partition at_test_sql_partop_1 for
values from (0) to (10);
+drop table at_test_sql_partop;
+drop operator class at_test_sql_partop using btree;
+drop function at_test_sql_partop;
diff --git a/src/test/regress/sql/alter_table.sql
b/src/test/regress/sql/alter_table.sql
index b90497804b..179bbfb9a1 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2618,3 +2618,19 @@ alter table perm_part_parent attach partition
temp_part_child default; -- error
alter table temp_part_parent attach partition temp_part_child default; -- ok
drop table perm_part_parent cascade;
drop table temp_part_parent cascade;
+
+-- test case where the partitioning operator is a SQL function whose
+-- evaluation results in the table's relcache being rebuilt partway through
+-- the execution of an ATTACH PARTITION command
+create function at_test_sql_partop (int4, int4) returns int language sql
+as $$ select case when $1 = $2 then 0 when $1 > $2 then 1 else -1 end; $$;
+create operator class at_test_sql_partop for type int4 using btree as
+ operator 1 < (int4, int4), operator 2 <= (int4, int4),
+ operator 3 = (int4, int4), operator 4 >= (int4, int4),
+ operator 5 > (int4, int4), function 1 at_test_sql_partop(int4, int4);
+create table at_test_sql_partop (a int) partition by range (a
at_test_sql_partop);
+create table at_test_sql_partop_1 (a int);
+alter table at_test_sql_partop attach partition at_test_sql_partop_1 for
values from (0) to (10);
+drop table at_test_sql_partop;
+drop operator class at_test_sql_partop using btree;
+drop function at_test_sql_partop;