diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 7f1f962f60..bbf355dbf2 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -1570,34 +1570,58 @@ inheritance_planner(PlannerInfo *root) * to get control here. */ - /* - * If we managed to exclude every child rel, return a dummy plan; it - * doesn't even need a ModifyTable node. - */ if (subpaths == NIL) { - set_dummy_rel_pathlist(final_rel); - return; + /* + * We managed to exclude every child rel, so generate a dummy path + * representing the empty set. Although it's clear that no data will + * be updated or deleted, we will still need to have a ModifyTable + * node so that any statement triggers are executed. (This could be + * cleaner if we fixed nodeModifyTable.c to support zero child nodes, + * but that probably wouldn't be a net win.) + */ + List *tlist; + Path *dummy_path; + + /* tlist processing never got done, either */ + tlist = root->processed_tlist = preprocess_targetlist(root); + final_rel->reltarget = create_pathtarget(root, tlist); + + /* Make a dummy path, cf set_dummy_rel_pathlist() */ + dummy_path = (Path *) create_append_path(NULL, final_rel, NIL, NIL, + NULL, 0, false, NIL, -1); + + /* These lists must be nonempty to make a valid ModifyTable node */ + subpaths = list_make1(dummy_path); + subroots = list_make1(root); + resultRelations = list_make1_int(parse->resultRelation); + if (parse->withCheckOptions) + withCheckOptionLists = list_make1(parse->withCheckOptions); + if (parse->returningList) + returningLists = list_make1(parse->returningList); } - - /* - * Put back the final adjusted rtable into the master copy of the Query. - * (We mustn't do this if we found no non-excluded children.) - */ - parse->rtable = final_rtable; - root->simple_rel_array_size = save_rel_array_size; - root->simple_rel_array = save_rel_array; - root->append_rel_array = save_append_rel_array; - - /* Must reconstruct master's simple_rte_array, too */ - root->simple_rte_array = (RangeTblEntry **) - palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); - rti = 1; - foreach(lc, final_rtable) + else { - RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); + /* + * Put back the final adjusted rtable into the master copy of the + * Query. (We mustn't do this if we found no non-excluded children, + * since we never saved an adjusted rtable at all.) + */ + parse->rtable = final_rtable; + root->simple_rel_array_size = save_rel_array_size; + root->simple_rel_array = save_rel_array; + root->append_rel_array = save_append_rel_array; - root->simple_rte_array[rti++] = rte; + /* Must reconstruct master's simple_rte_array, too */ + root->simple_rte_array = (RangeTblEntry **) + palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *)); + rti = 1; + foreach(lc, final_rtable) + { + RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc); + + root->simple_rte_array[rti++] = rte; + } } /* diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index b2b912ed5c..f6d70e9f7a 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -539,6 +539,47 @@ 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" violates not-null constraint DETAIL: Failing row contains (null, text). +-- Check inherited UPDATE with all children excluded +create table some_tab (a int, b int); +create table some_tab_child () inherits (some_tab); +insert into some_tab_child values(1,2); +explain (verbose, costs off) +update some_tab set a = a + 1 where false; + QUERY PLAN +---------------------------------- + Update on public.some_tab + Update on public.some_tab + -> Result + Output: (a + 1), b, ctid + One-Time Filter: false +(5 rows) + +update some_tab set a = a + 1 where false; +explain (verbose, costs off) +update some_tab set a = a + 1 where false returning b, a; + QUERY PLAN +---------------------------------- + Update on public.some_tab + Output: b, a + Update on public.some_tab + -> Result + Output: (a + 1), b, ctid + One-Time Filter: false +(6 rows) + +update some_tab set a = a + 1 where false returning b, a; + b | a +---+--- +(0 rows) + +table some_tab; + a | b +---+--- + 1 | 2 +(1 row) + +drop table some_tab cascade; +NOTICE: drop cascades to table some_tab_child -- Check UPDATE with inherited target and an inherited source table create temp table foo(f1 int, f2 int); create temp table foo2(f3 int) inherits (foo); diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out index 843c94094b..31dbc9bcfc 100644 --- a/src/test/regress/expected/triggers.out +++ b/src/test/regress/expected/triggers.out @@ -1714,6 +1714,40 @@ drop table self_ref_trigger; drop function self_ref_trigger_ins_func(); drop function self_ref_trigger_del_func(); -- +-- Check that statement triggers work correctly even with all children excluded +-- +create table stmt_trig_on_empty_upd (a int); +create table stmt_trig_on_empty_upd1 () inherits (stmt_trig_on_empty_upd); +create function update_stmt_notice() returns trigger as $$ +begin + raise notice 'updating %', TG_TABLE_NAME; + return null; +end; +$$ language plpgsql; +create trigger before_stmt_trigger + before update on stmt_trig_on_empty_upd + execute procedure update_stmt_notice(); +create trigger before_stmt_trigger + before update on stmt_trig_on_empty_upd1 + execute procedure update_stmt_notice(); +-- inherited no-op update +update stmt_trig_on_empty_upd set a = a where false returning a+1 as aa; +NOTICE: updating stmt_trig_on_empty_upd + aa +---- +(0 rows) + +-- simple no-op update +update stmt_trig_on_empty_upd1 set a = a where false returning a+1 as aa; +NOTICE: updating stmt_trig_on_empty_upd1 + aa +---- +(0 rows) + +drop table stmt_trig_on_empty_upd cascade; +NOTICE: drop cascades to table stmt_trig_on_empty_upd1 +drop function update_stmt_notice(); +-- -- Check that index creation (or DDL in general) is prohibited in a trigger -- create table trigger_ddl_table ( diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql index 5a48376fc0..30a45a20ae 100644 --- a/src/test/regress/sql/inherit.sql +++ b/src/test/regress/sql/inherit.sql @@ -97,6 +97,21 @@ 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 all children excluded +create table some_tab (a int, b int); +create table some_tab_child () inherits (some_tab); +insert into some_tab_child values(1,2); + +explain (verbose, costs off) +update some_tab set a = a + 1 where false; +update some_tab set a = a + 1 where false; +explain (verbose, costs off) +update some_tab set a = a + 1 where false returning b, a; +update some_tab set a = a + 1 where false returning b, a; +table some_tab; + +drop table some_tab cascade; + -- Check UPDATE with inherited target and an inherited source table create temp table foo(f1 int, f2 int); create temp table foo2(f3 int) inherits (foo); diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql index 451527dbd5..60b3e22c5c 100644 --- a/src/test/regress/sql/triggers.sql +++ b/src/test/regress/sql/triggers.sql @@ -1182,6 +1182,33 @@ drop table self_ref_trigger; drop function self_ref_trigger_ins_func(); drop function self_ref_trigger_del_func(); +-- +-- Check that statement triggers work correctly even with all children excluded +-- + +create table stmt_trig_on_empty_upd (a int); +create table stmt_trig_on_empty_upd1 () inherits (stmt_trig_on_empty_upd); +create function update_stmt_notice() returns trigger as $$ +begin + raise notice 'updating %', TG_TABLE_NAME; + return null; +end; +$$ language plpgsql; +create trigger before_stmt_trigger + before update on stmt_trig_on_empty_upd + execute procedure update_stmt_notice(); +create trigger before_stmt_trigger + before update on stmt_trig_on_empty_upd1 + execute procedure update_stmt_notice(); + +-- inherited no-op update +update stmt_trig_on_empty_upd set a = a where false returning a+1 as aa; +-- simple no-op update +update stmt_trig_on_empty_upd1 set a = a where false returning a+1 as aa; + +drop table stmt_trig_on_empty_upd cascade; +drop function update_stmt_notice(); + -- -- Check that index creation (or DDL in general) is prohibited in a trigger --