diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index c1f289a0fc..fd0789e9ba 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -2860,10 +2860,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) * must be converted, and * - the root partitioned table used for tuple routing. * - * If it's a partitioned table, the root partition doesn't appear - * elsewhere in the plan and its RT index is given explicitly in - * node->rootRelation. Otherwise (i.e. table inheritance) the target - * relation is the first relation in the node->resultRelations list. + * If it's a partitioned or inherited table, the root partition or + * appendrel RTE doesn't appear elsewhere in the plan and its RT index is + * given explicitly in node->rootRelation. Otherwise, the target relation + * is the sole relation in the node->resultRelations list. *---------- */ if (node->rootRelation > 0) @@ -2874,6 +2874,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) } else { + Assert(list_length(node->resultRelations) == 1); mtstate->rootResultRelInfo = mtstate->resultRelInfo; ExecInitResultRelation(estate, mtstate->resultRelInfo, linitial_int(node->resultRelations)); diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index fe2e029908..5da863d85d 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -1725,6 +1725,9 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) parse->resultRelation); int resultRelation = -1; + /* Pass the root result rel forward to the executor. */ + rootRelation = parse->resultRelation; + /* Add only leaf children to ModifyTable. */ while ((resultRelation = bms_next_member(root->leaf_result_relids, resultRelation)) >= 0) @@ -1809,6 +1812,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) else { /* Single-relation INSERT/UPDATE/DELETE. */ + rootRelation = 0; /* there's no separate root rel */ resultRelations = list_make1_int(parse->resultRelation); if (parse->commandType == CMD_UPDATE) updateColnosLists = list_make1(root->update_colnos); @@ -1818,16 +1822,6 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) returningLists = list_make1(parse->returningList); } - /* - * If target is a partition root table, we need to mark the - * ModifyTable node appropriately for that. - */ - if (rt_fetch(parse->resultRelation, parse->rtable)->relkind == - RELKIND_PARTITIONED_TABLE) - rootRelation = parse->resultRelation; - else - rootRelation = 0; - /* * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node * will have dealt with fetching non-locked marked rows, else we diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 3afc081345..f8642d5662 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -3609,7 +3609,7 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel, * 'operation' is the operation type * 'canSetTag' is true if we set the command tag/es_processed * 'nominalRelation' is the parent RT index for use of EXPLAIN - * 'rootRelation' is the partitioned table root RT index, or 0 if none + * 'rootRelation' is the partitioned/inherited table root RTI, or 0 if none * 'partColsUpdated' is true if any partitioning columns are being updated, * either from the target relation or a descendent partitioned table. * 'resultRelations' is an integer list of actual RT indexes of target rel(s) diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index a24ba2fdcd..3c034fa3c5 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -1884,7 +1884,7 @@ typedef struct ModifyTablePath CmdType operation; /* INSERT, UPDATE, or DELETE */ bool canSetTag; /* do we set the command tag/es_processed? */ Index nominalRelation; /* Parent RT index for use of EXPLAIN */ - Index rootRelation; /* Root RT index, if target is partitioned */ + Index rootRelation; /* Root RT index, if partitioned/inherited */ bool partColsUpdated; /* some part key in hierarchy updated? */ List *resultRelations; /* integer list of RT indexes */ List *updateColnosLists; /* per-target-table update_colnos lists */ diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 2308c80dde..1c9357f6a7 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -204,11 +204,12 @@ typedef struct ProjectSet * Apply rows produced by outer plan to result table(s), * by inserting, updating, or deleting. * - * If the originally named target table is a partitioned table, both - * nominalRelation and rootRelation contain the RT index of the partition - * root, which is not otherwise mentioned in the plan. Otherwise rootRelation - * is zero. However, nominalRelation will always be set, as it's the rel that - * EXPLAIN should claim is the INSERT/UPDATE/DELETE target. + * If the originally named target table is a partitioned table or inheritance + * tree, both nominalRelation and rootRelation contain the RT index of the + * partition root or appendrel RTE, which is not otherwise mentioned in the + * plan. Otherwise rootRelation is zero. However, nominalRelation will + * always be set, as it's the rel that EXPLAIN should claim is the + * INSERT/UPDATE/DELETE target. * * Note that rowMarks and epqParam are presumed to be valid for all the * table(s); they can't contain any info that varies across tables. @@ -220,7 +221,7 @@ typedef struct ModifyTable CmdType operation; /* INSERT, UPDATE, or DELETE */ bool canSetTag; /* do we set the command tag/es_processed? */ Index nominalRelation; /* Parent RT index for use of EXPLAIN */ - Index rootRelation; /* Root RT index, if target is partitioned */ + Index rootRelation; /* Root RT index, if partitioned/inherited */ bool partColsUpdated; /* some part key in hierarchy updated? */ List *resultRelations; /* integer list of RT indexes */ List *updateColnosLists; /* per-target-table update_colnos lists */ diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index 445b9e2467..2162542735 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -539,6 +539,33 @@ CREATE TEMP TABLE z (b TEXT, PRIMARY KEY(aa, b)) inherits (a); INSERT INTO z VALUES (NULL, 'text'); -- should fail ERROR: null value in column "aa" of relation "z" violates not-null constraint DETAIL: Failing row contains (null, text). +-- Check inherited UPDATE with first child excluded +create table some_tab (f1 int, f2 int, f3 int, check (f1 < 10) no inherit); +create table some_tab_child () inherits(some_tab); +insert into some_tab_child select i, i+1, 0 from generate_series(1,1000) i; +create index on some_tab_child(f1, f2); +-- while at it, also check that statement-level triggers fire +create function some_tab_stmt_trig_func() returns trigger as +$$begin raise notice 'updating some_tab'; return NULL; end;$$ +language plpgsql; +create trigger some_tab_stmt_trig + before update on some_tab execute function some_tab_stmt_trig_func(); +explain (costs off) +update some_tab set f3 = 11 where f1 = 12 and f2 = 13; + QUERY PLAN +------------------------------------------------------------------------------------ + Update on some_tab + Update on some_tab_child some_tab_1 + -> Result + -> Index Scan using some_tab_child_f1_f2_idx on some_tab_child some_tab_1 + Index Cond: ((f1 = 12) AND (f2 = 13)) +(5 rows) + +update some_tab set f3 = 11 where f1 = 12 and f2 = 13; +NOTICE: updating some_tab +drop table some_tab cascade; +NOTICE: drop cascades to table some_tab_child +drop function some_tab_stmt_trig_func(); -- Check inherited UPDATE with all children excluded create table some_tab (a int, b int); create table some_tab_child () inherits (some_tab); diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql index 1aa59e8355..d3674fa29d 100644 --- a/src/test/regress/sql/inherit.sql +++ b/src/test/regress/sql/inherit.sql @@ -97,6 +97,25 @@ SELECT relname, d.* FROM ONLY d, pg_class where d.tableoid = pg_class.oid; CREATE TEMP TABLE z (b TEXT, PRIMARY KEY(aa, b)) inherits (a); INSERT INTO z VALUES (NULL, 'text'); -- should fail +-- Check inherited UPDATE with first child excluded +create table some_tab (f1 int, f2 int, f3 int, check (f1 < 10) no inherit); +create table some_tab_child () inherits(some_tab); +insert into some_tab_child select i, i+1, 0 from generate_series(1,1000) i; +create index on some_tab_child(f1, f2); +-- while at it, also check that statement-level triggers fire +create function some_tab_stmt_trig_func() returns trigger as +$$begin raise notice 'updating some_tab'; return NULL; end;$$ +language plpgsql; +create trigger some_tab_stmt_trig + before update on some_tab execute function some_tab_stmt_trig_func(); + +explain (costs off) +update some_tab set f3 = 11 where f1 = 12 and f2 = 13; +update some_tab set f3 = 11 where f1 = 12 and f2 = 13; + +drop table some_tab cascade; +drop function some_tab_stmt_trig_func(); + -- Check inherited UPDATE with all children excluded create table some_tab (a int, b int); create table some_tab_child () inherits (some_tab);