From e436f04287a167697cc11ef036234203e4a99870 Mon Sep 17 00:00:00 2001 From: amitlan Date: Tue, 9 Jun 2020 22:14:20 +0900 Subject: [PATCH v2 1/8] Overhaul UPDATE's targetlist processing Instead of emitting the full tuple matching the target table's tuple descriptor, make the plan emit only the attributes that are assigned values in the SET clause, plus row-identity junk attributes as before. However, a full tuple to pass to the table AM API as the new UPDATE tuple must still be constructed. To do so, a separate targetlist is provided which combining the partial tuple produced by the plan and unchanged values taken from the old tuple, which is refetched after running the plan. This targetlist is passed separately in the ModifyTable node. With this change, FDW APIs for planning and executing updates should no longer assume that the plan's output tuple covers all of the target relation's columns. Especially, it shouldn't assume that it can use the target attribute's attribute number to find the TLE that corresponds to that attribute in the plan's targetlist. TODO: * Remove the hack to use a wholerow Var to construct the full tuple to pass to postgres_fdw, because it hasn't been fixed considering the above node. --- contrib/postgres_fdw/expected/postgres_fdw.out | 94 +++---- contrib/postgres_fdw/postgres_fdw.c | 7 +- src/backend/commands/trigger.c | 5 +- src/backend/executor/execMain.c | 1 - src/backend/executor/nodeModifyTable.c | 358 +++++++++++++++---------- src/backend/nodes/copyfuncs.c | 1 + src/backend/nodes/outfuncs.c | 19 +- src/backend/nodes/readfuncs.c | 1 + src/backend/optimizer/plan/createplan.c | 9 +- src/backend/optimizer/plan/planner.c | 66 +++++ src/backend/optimizer/plan/setrefs.c | 55 ++++ src/backend/optimizer/prep/preptlist.c | 105 +++++++- src/backend/optimizer/util/pathnode.c | 4 +- src/backend/optimizer/util/plancat.c | 57 ++++ src/backend/optimizer/util/tlist.c | 19 ++ src/backend/rewrite/rewriteHandler.c | 30 ++- src/include/executor/executor.h | 9 + src/include/nodes/execnodes.h | 9 +- src/include/nodes/nodes.h | 1 + src/include/nodes/pathnodes.h | 37 +++ src/include/nodes/plannodes.h | 5 +- src/include/optimizer/optimizer.h | 1 + src/include/optimizer/pathnode.h | 2 +- src/include/optimizer/plancat.h | 2 + src/include/optimizer/prep.h | 6 +- src/test/regress/expected/inherit.out | 12 +- src/test/regress/expected/updatable_views.out | 22 +- src/test/regress/expected/update.out | 8 +- 28 files changed, 714 insertions(+), 231 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 82fc129..45efaf7 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -5457,13 +5457,13 @@ UPDATE ft2 AS target SET (c2, c7) = ( FROM ft2 AS src WHERE target.c1 = src.c1 ) WHERE c1 > 1100; - QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------- Update on public.ft2 target Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2, c7 = $3 WHERE ctid = $1 -> Foreign Scan on public.ft2 target - Output: target.c1, $1, NULL::integer, target.c3, target.c4, target.c5, target.c6, $2, target.c8, (SubPlan 1 (returns $1,$2)), target.ctid - Remote SQL: SELECT "C 1", c3, c4, c5, c6, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 1100)) FOR UPDATE + Output: $1, $2, (SubPlan 1 (returns $1,$2)), target.ctid, target.* + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 1100)) FOR UPDATE SubPlan 1 (returns $1,$2) -> Foreign Scan on public.ft2 src Output: (src.c2 * 10), src.c7 @@ -5493,9 +5493,9 @@ UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; Output: c1, c2, c3, c4, c5, c6, c7, c8 Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8 -> Foreign Scan on public.ft2 - Output: c1, c2, NULL::integer, 'bar'::text, c4, c5, c6, c7, c8, ctid + Output: 'bar'::text, ctid, ft2.* Filter: (postgres_fdw_abs(ft2.c1) > 2000) - Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" FOR UPDATE + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" FOR UPDATE (7 rows) UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; @@ -5524,11 +5524,11 @@ UPDATE ft2 SET c3 = 'baz' Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3 Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8 -> Nested Loop - Output: ft2.c1, ft2.c2, NULL::integer, 'baz'::text, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3 + Output: 'baz'::text, ft2.ctid, ft2.*, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3 Join Filter: (ft2.c2 === ft4.c1) -> Foreign Scan on public.ft2 - Output: ft2.c1, ft2.c2, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid - Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE + Output: ft2.ctid, ft2.*, ft2.c2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE -> Foreign Scan Output: ft4.*, ft4.c1, ft4.c2, ft4.c3, ft5.*, ft5.c1, ft5.c2, ft5.c3 Relations: (public.ft4) INNER JOIN (public.ft5) @@ -6218,7 +6218,7 @@ UPDATE rw_view SET b = b + 5; Update on public.foreign_tbl Remote SQL: UPDATE public.base_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b -> Foreign Scan on public.foreign_tbl - Output: foreign_tbl.a, (foreign_tbl.b + 5), foreign_tbl.ctid + Output: (foreign_tbl.b + 5), foreign_tbl.ctid, foreign_tbl.* Remote SQL: SELECT a, b, ctid FROM public.base_tbl WHERE ((a < b)) FOR UPDATE (5 rows) @@ -6232,7 +6232,7 @@ UPDATE rw_view SET b = b + 15; Update on public.foreign_tbl Remote SQL: UPDATE public.base_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b -> Foreign Scan on public.foreign_tbl - Output: foreign_tbl.a, (foreign_tbl.b + 15), foreign_tbl.ctid + Output: (foreign_tbl.b + 15), foreign_tbl.ctid, foreign_tbl.* Remote SQL: SELECT a, b, ctid FROM public.base_tbl WHERE ((a < b)) FOR UPDATE (5 rows) @@ -6306,7 +6306,7 @@ UPDATE rw_view SET b = b + 5; Foreign Update on public.foreign_tbl parent_tbl_1 Remote SQL: UPDATE public.child_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b -> Foreign Scan on public.foreign_tbl parent_tbl_1 - Output: parent_tbl_1.a, (parent_tbl_1.b + 5), parent_tbl_1.ctid + Output: (parent_tbl_1.b + 5), parent_tbl_1.ctid, parent_tbl_1.* Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE (6 rows) @@ -6321,7 +6321,7 @@ UPDATE rw_view SET b = b + 15; Foreign Update on public.foreign_tbl parent_tbl_1 Remote SQL: UPDATE public.child_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b -> Foreign Scan on public.foreign_tbl parent_tbl_1 - Output: parent_tbl_1.a, (parent_tbl_1.b + 15), parent_tbl_1.ctid + Output: (parent_tbl_1.b + 15), parent_tbl_1.ctid, parent_tbl_1.* Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE (6 rows) @@ -6638,7 +6638,7 @@ UPDATE rem1 set f1 = 10; -- all columns should be transmitted Update on public.rem1 Remote SQL: UPDATE public.loc1 SET f1 = $2, f2 = $3 WHERE ctid = $1 -> Foreign Scan on public.rem1 - Output: 10, f2, ctid, rem1.* + Output: 10, ctid, rem1.* Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE (5 rows) @@ -6871,7 +6871,7 @@ UPDATE rem1 set f2 = ''; -- can't be pushed down Update on public.rem1 Remote SQL: UPDATE public.loc1 SET f1 = $2, f2 = $3 WHERE ctid = $1 -> Foreign Scan on public.rem1 - Output: f1, ''::text, ctid, rem1.* + Output: ''::text, ctid, rem1.* Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE (5 rows) @@ -6895,7 +6895,7 @@ UPDATE rem1 set f2 = ''; -- can't be pushed down Update on public.rem1 Remote SQL: UPDATE public.loc1 SET f2 = $2 WHERE ctid = $1 RETURNING f1, f2 -> Foreign Scan on public.rem1 - Output: f1, ''::text, ctid, rem1.* + Output: ''::text, ctid, rem1.* Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE (5 rows) @@ -7205,18 +7205,18 @@ select * from bar where f1 in (select f1 from foo) for share; -- Check UPDATE with inherited target and an inherited source table explain (verbose, costs off) update bar set f2 = f2 + 100 where f1 in (select f1 from foo); - QUERY PLAN -------------------------------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------- Update on public.bar Update on public.bar Foreign Update on public.bar2 bar_1 Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1 -> Hash Join - Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.*, foo.tableoid + Output: (bar.f2 + 100), bar.ctid, foo.ctid, foo.*, foo.tableoid Inner Unique: true Hash Cond: (bar.f1 = foo.f1) -> Seq Scan on public.bar - Output: bar.f1, bar.f2, bar.ctid + Output: bar.f2, bar.ctid, bar.f1 -> Hash Output: foo.ctid, foo.f1, foo.*, foo.tableoid -> HashAggregate @@ -7229,11 +7229,11 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo); Output: foo_2.ctid, foo_2.f1, foo_2.*, foo_2.tableoid Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 -> Hash Join - Output: bar_1.f1, (bar_1.f2 + 100), bar_1.f3, bar_1.ctid, foo.ctid, foo.*, foo.tableoid + Output: (bar_1.f2 + 100), bar_1.ctid, bar_1.*, foo.ctid, foo.*, foo.tableoid Inner Unique: true Hash Cond: (bar_1.f1 = foo.f1) -> Foreign Scan on public.bar2 bar_1 - Output: bar_1.f1, bar_1.f2, bar_1.f3, bar_1.ctid + Output: bar_1.f2, bar_1.ctid, bar_1.*, bar_1.f1 Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Hash Output: foo.ctid, foo.f1, foo.*, foo.tableoid @@ -7273,7 +7273,7 @@ where bar.f1 = ss.f1; Foreign Update on public.bar2 bar_1 Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1 -> Hash Join - Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1)) + Output: (bar.f2 + 100), bar.ctid, (ROW(foo.f1)) Hash Cond: (foo.f1 = bar.f1) -> Append -> Seq Scan on public.foo @@ -7287,17 +7287,17 @@ where bar.f1 = ss.f1; Output: ROW((foo_3.f1 + 3)), (foo_3.f1 + 3) Remote SQL: SELECT f1 FROM public.loct1 -> Hash - Output: bar.f1, bar.f2, bar.ctid + Output: bar.f2, bar.ctid, bar.f1 -> Seq Scan on public.bar - Output: bar.f1, bar.f2, bar.ctid + Output: bar.f2, bar.ctid, bar.f1 -> Merge Join - Output: bar_1.f1, (bar_1.f2 + 100), bar_1.f3, bar_1.ctid, (ROW(foo.f1)) + Output: (bar_1.f2 + 100), bar_1.ctid, bar_1.*, (ROW(foo.f1)) Merge Cond: (bar_1.f1 = foo.f1) -> Sort - Output: bar_1.f1, bar_1.f2, bar_1.f3, bar_1.ctid + Output: bar_1.f2, bar_1.ctid, bar_1.*, bar_1.f1 Sort Key: bar_1.f1 -> Foreign Scan on public.bar2 bar_1 - Output: bar_1.f1, bar_1.f2, bar_1.f3, bar_1.ctid + Output: bar_1.f2, bar_1.ctid, bar_1.*, bar_1.f1 Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE -> Sort Output: (ROW(foo.f1)), foo.f1 @@ -7471,7 +7471,7 @@ update bar set f2 = f2 + 100 returning *; Update on public.bar Foreign Update on public.bar2 bar_1 -> Seq Scan on public.bar - Output: bar.f1, (bar.f2 + 100), bar.ctid + Output: (bar.f2 + 100), bar.ctid -> Foreign Update on public.bar2 bar_1 Remote SQL: UPDATE public.loct2 SET f2 = (f2 + 100) RETURNING f1, f2 (8 rows) @@ -7503,9 +7503,9 @@ update bar set f2 = f2 + 100; Foreign Update on public.bar2 bar_1 Remote SQL: UPDATE public.loct2 SET f1 = $2, f2 = $3, f3 = $4 WHERE ctid = $1 RETURNING f1, f2, f3 -> Seq Scan on public.bar - Output: bar.f1, (bar.f2 + 100), bar.ctid + Output: (bar.f2 + 100), bar.ctid -> Foreign Scan on public.bar2 bar_1 - Output: bar_1.f1, (bar_1.f2 + 100), bar_1.f3, bar_1.ctid, bar_1.* + Output: (bar_1.f2 + 100), bar_1.ctid, bar_1.* Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE (9 rows) @@ -7574,10 +7574,10 @@ update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a re Update on public.parent Foreign Update on public.remt1 parent_1 -> Nested Loop - Output: parent.a, (parent.b || remt2.b), parent.ctid, remt2.*, remt2.a, remt2.b + Output: (parent.b || remt2.b), parent.ctid, remt2.*, remt2.a, remt2.b Join Filter: (parent.a = remt2.a) -> Seq Scan on public.parent - Output: parent.a, parent.b, parent.ctid + Output: parent.b, parent.ctid, parent.a -> Foreign Scan on public.remt2 Output: remt2.b, remt2.*, remt2.a Remote SQL: SELECT a, b FROM public.loct2 @@ -7832,7 +7832,7 @@ update utrtest set a = 1 where a = 1 or a = 2 returning *; -> Foreign Update on public.remp utrtest_1 Remote SQL: UPDATE public.loct SET a = 1 WHERE (((a = 1) OR (a = 2))) RETURNING a, b -> Seq Scan on public.locp utrtest_2 - Output: 1, utrtest_2.b, utrtest_2.ctid + Output: 1, utrtest_2.ctid Filter: ((utrtest_2.a = 1) OR (utrtest_2.a = 2)) (9 rows) @@ -7848,13 +7848,13 @@ insert into utrtest values (2, 'qux'); -- Check case where the foreign partition isn't a subplan target rel explain (verbose, costs off) update utrtest set a = 1 where a = 2 returning *; - QUERY PLAN ------------------------------------------------- + QUERY PLAN +----------------------------------------- Update on public.utrtest Output: utrtest_1.a, utrtest_1.b Update on public.locp utrtest_1 -> Seq Scan on public.locp utrtest_1 - Output: 1, utrtest_1.b, utrtest_1.ctid + Output: 1, utrtest_1.ctid Filter: (utrtest_1.a = 2) (6 rows) @@ -7884,7 +7884,7 @@ update utrtest set a = 1 returning *; -> Foreign Update on public.remp utrtest_1 Remote SQL: UPDATE public.loct SET a = 1 RETURNING a, b -> Seq Scan on public.locp utrtest_2 - Output: 1, utrtest_2.b, utrtest_2.ctid + Output: 1, utrtest_2.ctid (8 rows) update utrtest set a = 1 returning *; @@ -7908,20 +7908,20 @@ update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *; Remote SQL: UPDATE public.loct SET a = $2 WHERE ctid = $1 RETURNING a, b Update on public.locp utrtest_2 -> Hash Join - Output: 1, utrtest_1.b, utrtest_1.ctid, "*VALUES*".*, "*VALUES*".column1 + Output: 1, utrtest_1.ctid, utrtest_1.*, "*VALUES*".*, "*VALUES*".column1 Hash Cond: (utrtest_1.a = "*VALUES*".column1) -> Foreign Scan on public.remp utrtest_1 - Output: utrtest_1.b, utrtest_1.ctid, utrtest_1.a + Output: utrtest_1.ctid, utrtest_1.*, utrtest_1.a Remote SQL: SELECT a, b, ctid FROM public.loct FOR UPDATE -> Hash Output: "*VALUES*".*, "*VALUES*".column1 -> Values Scan on "*VALUES*" Output: "*VALUES*".*, "*VALUES*".column1 -> Hash Join - Output: 1, utrtest_2.b, utrtest_2.ctid, "*VALUES*".*, "*VALUES*".column1 + Output: 1, utrtest_2.ctid, "*VALUES*".*, "*VALUES*".column1 Hash Cond: (utrtest_2.a = "*VALUES*".column1) -> Seq Scan on public.locp utrtest_2 - Output: utrtest_2.b, utrtest_2.ctid, utrtest_2.a + Output: utrtest_2.ctid, utrtest_2.a -> Hash Output: "*VALUES*".*, "*VALUES*".column1 -> Values Scan on "*VALUES*" @@ -7957,7 +7957,7 @@ update utrtest set a = 3 returning *; Update on public.locp utrtest_1 Foreign Update on public.remp utrtest_2 -> Seq Scan on public.locp utrtest_1 - Output: 3, utrtest_1.b, utrtest_1.ctid + Output: 3, utrtest_1.ctid -> Foreign Update on public.remp utrtest_2 Remote SQL: UPDATE public.loct SET a = 3 RETURNING a, b (8 rows) @@ -7975,19 +7975,19 @@ update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *; Foreign Update on public.remp utrtest_2 Remote SQL: UPDATE public.loct SET a = $2 WHERE ctid = $1 RETURNING a, b -> Hash Join - Output: 3, utrtest_1.b, utrtest_1.ctid, "*VALUES*".*, "*VALUES*".column1 + Output: 3, utrtest_1.ctid, "*VALUES*".*, "*VALUES*".column1 Hash Cond: (utrtest_1.a = "*VALUES*".column1) -> Seq Scan on public.locp utrtest_1 - Output: utrtest_1.b, utrtest_1.ctid, utrtest_1.a + Output: utrtest_1.ctid, utrtest_1.a -> Hash Output: "*VALUES*".*, "*VALUES*".column1 -> Values Scan on "*VALUES*" Output: "*VALUES*".*, "*VALUES*".column1 -> Hash Join - Output: 3, utrtest_2.b, utrtest_2.ctid, "*VALUES*".*, "*VALUES*".column1 + Output: 3, utrtest_2.ctid, utrtest_2.*, "*VALUES*".*, "*VALUES*".column1 Hash Cond: (utrtest_2.a = "*VALUES*".column1) -> Foreign Scan on public.remp utrtest_2 - Output: utrtest_2.b, utrtest_2.ctid, utrtest_2.a + Output: utrtest_2.ctid, utrtest_2.*, utrtest_2.a Remote SQL: SELECT a, b, ctid FROM public.loct FOR UPDATE -> Hash Output: "*VALUES*".*, "*VALUES*".column1 diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 9fc53ca..16c5f19 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -2150,6 +2150,7 @@ postgresPlanDirectModify(PlannerInfo *root, List *params_list = NIL; List *returningList = NIL; List *retrieved_attrs = NIL; + ResultRelPlanInfo *resultInfo; /* * Decide whether it is safe to modify a foreign table directly. @@ -2166,6 +2167,8 @@ postgresPlanDirectModify(PlannerInfo *root, * joins needed. */ subplan = (Plan *) list_nth(plan->plans, subplan_index); + resultInfo = linitial(root->result_rel_list); + Assert(resultInfo != NULL); if (!IsA(subplan, ForeignScan)) return false; fscan = (ForeignScan *) subplan; @@ -2211,7 +2214,7 @@ postgresPlanDirectModify(PlannerInfo *root, if (attno <= InvalidAttrNumber) /* shouldn't happen */ elog(ERROR, "system-column update is not supported"); - tle = get_tle_by_resno(subplan->targetlist, attno); + tle = get_tle_by_resno(resultInfo->subplanTargetList, attno); if (!tle) elog(ERROR, "attribute number %d not found in subplan targetlist", @@ -2271,7 +2274,7 @@ postgresPlanDirectModify(PlannerInfo *root, case CMD_UPDATE: deparseDirectUpdateSql(&sql, root, resultRelation, rel, foreignrel, - ((Plan *) fscan)->targetlist, + resultInfo->subplanTargetList, targetAttrs, remote_exprs, ¶ms_list, returningList, &retrieved_attrs); diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 672fccf..58a5111 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -2686,7 +2686,10 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, { TupleTableSlot *epqslot_clean; - epqslot_clean = ExecFilterJunk(relinfo->ri_junkFilter, epqslot_candidate); + Assert(relinfo->ri_projectNew != NULL); + Assert(relinfo->ri_oldTupleSlot != NULL); + epqslot_clean = ExecGetUpdateNewTuple(relinfo, epqslot_candidate, + tupleid, NULL); if (newslot != epqslot_clean) ExecCopySlot(newslot, epqslot_clean); diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 4fdffad..166486d 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -1318,7 +1318,6 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo, resultRelInfo->ri_usesFdwDirectModify = false; resultRelInfo->ri_ConstraintExprs = NULL; resultRelInfo->ri_GeneratedExprs = NULL; - resultRelInfo->ri_junkFilter = NULL; resultRelInfo->ri_projectReturning = NULL; resultRelInfo->ri_onConflictArbiterIndexes = NIL; resultRelInfo->ri_onConflict = NULL; diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 20a4c47..0a99818 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -75,6 +75,8 @@ static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node); static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate); static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node, int whichplan); +static TupleTableSlot *ExecGetNewInsertTuple(ResultRelInfo *relinfo, + TupleTableSlot *planSlot); /* * Verify that the tuples to be produced by INSERT or UPDATE match the @@ -1043,6 +1045,65 @@ ldelete:; return NULL; } +/* + * ExecProjectNewInsertTuple + * This prepares a "new" tuple ready to be put into a result relation + * by removing any junk columns of the tuple produced by a plan. + */ +static TupleTableSlot * +ExecGetNewInsertTuple(ResultRelInfo *relinfo, + TupleTableSlot *planSlot) +{ + ProjectionInfo *newProj = relinfo->ri_projectNew; + ExprContext *econtext; + + if (newProj == NULL) + return planSlot; + + econtext = newProj->pi_exprContext; + econtext->ecxt_outertuple = planSlot; + return ExecProject(newProj); +} + + +/* + * ExecProjectNewUpdateTuple + * This prepares a "new" tuple ready to be put into a result relation + * by combining the "plan" tuple and the "old" tuple. + * + * The "plan" tuple contains values for only those columns that were specified + * in the update statement's SET clause and some junk columns which must be + * filtered out. Values for the rest of the columns are taken from the "old" + * tuple. + * + * The caller may pass either tupleid of the old tuple or its HeapTuple. + */ +TupleTableSlot * +ExecGetUpdateNewTuple(ResultRelInfo *relinfo, + TupleTableSlot *planSlot, + ItemPointerData *tupleid, + HeapTupleData *oldtuple) +{ + ProjectionInfo *newProj = relinfo->ri_projectNew; + ExprContext *econtext; + TupleTableSlot *oldSlot = relinfo->ri_oldTupleSlot; + Relation relation = relinfo->ri_RelationDesc; + + Assert(newProj != NULL); + Assert(oldSlot != NULL); + Assert(tupleid != NULL || oldtuple != NULL); + ExecClearTuple(oldSlot); + if (tupleid != NULL) + table_tuple_fetch_row_version(relation, tupleid, SnapshotAny, + oldSlot); + else + ExecForceStoreHeapTuple(oldtuple, oldSlot, false); + econtext = newProj->pi_exprContext; + econtext->ecxt_scantuple = oldSlot; + econtext->ecxt_outertuple = planSlot; + return ExecProject(newProj); +} + /* ---------------------------------------------------------------- * ExecUpdate * @@ -1269,7 +1330,8 @@ lreplace:; return NULL; else { - slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot); + slot = ExecGetUpdateNewTuple(resultRelInfo, epqslot, + tupleid, oldtuple); goto lreplace; } } @@ -1423,7 +1485,9 @@ lreplace:; /* Tuple not passing quals anymore, exiting... */ return NULL; - slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot); + slot = ExecGetUpdateNewTuple(resultRelInfo, + epqslot, tupleid, + oldtuple); goto lreplace; case TM_Deleted: @@ -2022,7 +2086,6 @@ ExecModifyTable(PlanState *pstate) ResultRelInfo *saved_resultRelInfo; ResultRelInfo *resultRelInfo; PlanState *subplanstate; - JunkFilter *junkfilter; TupleTableSlot *slot; TupleTableSlot *planSlot; ItemPointer tupleid; @@ -2065,7 +2128,6 @@ ExecModifyTable(PlanState *pstate) /* Preload local variables */ resultRelInfo = node->resultRelInfo + node->mt_whichplan; subplanstate = node->mt_plans[node->mt_whichplan]; - junkfilter = resultRelInfo->ri_junkFilter; /* * es_result_relation_info must point to the currently active result @@ -2110,7 +2172,6 @@ ExecModifyTable(PlanState *pstate) { resultRelInfo++; subplanstate = node->mt_plans[node->mt_whichplan]; - junkfilter = resultRelInfo->ri_junkFilter; estate->es_result_relation_info = resultRelInfo; EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan, node->mt_arowmarks[node->mt_whichplan]); @@ -2165,80 +2226,78 @@ ExecModifyTable(PlanState *pstate) tupleid = NULL; oldtuple = NULL; - if (junkfilter != NULL) + /* + * extract the 'ctid' or 'wholerow' junk attribute. + */ + if (operation == CMD_UPDATE || operation == CMD_DELETE) { - /* - * extract the 'ctid' or 'wholerow' junk attribute. - */ - if (operation == CMD_UPDATE || operation == CMD_DELETE) - { - char relkind; - Datum datum; - bool isNull; - - relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind; - if (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW) - { - datum = ExecGetJunkAttribute(slot, - junkfilter->jf_junkAttNo, - &isNull); - /* shouldn't ever get a null result... */ - if (isNull) - elog(ERROR, "ctid is NULL"); - - tupleid = (ItemPointer) DatumGetPointer(datum); - tuple_ctid = *tupleid; /* be sure we don't free ctid!! */ - tupleid = &tuple_ctid; - } + char relkind; + Datum datum; + bool isNull; - /* - * Use the wholerow attribute, when available, to reconstruct - * the old relation tuple. - * - * Foreign table updates have a wholerow attribute when the - * relation has a row-level trigger. Note that the wholerow - * attribute does not carry system columns. Foreign table - * triggers miss seeing those, except that we know enough here - * to set t_tableOid. Quite separately from this, the FDW may - * fetch its own junk attrs to identify the row. - * - * Other relevant relkinds, currently limited to views, always - * have a wholerow attribute. - */ - else if (AttributeNumberIsValid(junkfilter->jf_junkAttNo)) - { - datum = ExecGetJunkAttribute(slot, - junkfilter->jf_junkAttNo, - &isNull); - /* shouldn't ever get a null result... */ - if (isNull) - elog(ERROR, "wholerow is NULL"); - - oldtupdata.t_data = DatumGetHeapTupleHeader(datum); - oldtupdata.t_len = - HeapTupleHeaderGetDatumLength(oldtupdata.t_data); - ItemPointerSetInvalid(&(oldtupdata.t_self)); - /* Historically, view triggers see invalid t_tableOid. */ - oldtupdata.t_tableOid = - (relkind == RELKIND_VIEW) ? InvalidOid : - RelationGetRelid(resultRelInfo->ri_RelationDesc); - - oldtuple = &oldtupdata; - } - else - Assert(relkind == RELKIND_FOREIGN_TABLE); + relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind; + if (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW) + { + Assert(resultRelInfo->ri_junkAttno > 0); + datum = ExecGetJunkAttribute(slot, + resultRelInfo->ri_junkAttno, + &isNull); + /* shouldn't ever get a null result... */ + if (isNull) + elog(ERROR, "ctid is NULL"); + + tupleid = (ItemPointer) DatumGetPointer(datum); + tuple_ctid = *tupleid; /* be sure we don't free ctid!! */ + tupleid = &tuple_ctid; } /* - * apply the junkfilter if needed. + * Use the wholerow attribute, when available, to reconstruct + * the old relation tuple. The old tuple serves one of two + * purposes or both: 1) it serves as the OLD tuple for any row + * triggers on foreign tables, 2) it provides values for any + * missing columns for the NEW tuple of the UPDATEs targeting + * foreign tables, because the plan itself does not produce all + * the columns of the target table; see the "oldtuple" being + * passed to ExecGetUpdateNewTuple() below. + * + * Note that the wholerow attribute does not carry system columns, + * so foreign table triggers miss seeing those, except that we + * know enough here to set t_tableOid. Quite separately from this, + * the FDW may fetch its own junk attrs to identify the row. + * + * Other relevant relkinds, currently limited to views, always + * have a wholerow attribute. */ - if (operation != CMD_DELETE) - slot = ExecFilterJunk(junkfilter, slot); + else if (AttributeNumberIsValid(resultRelInfo->ri_junkAttno)) + { + datum = ExecGetJunkAttribute(slot, + resultRelInfo->ri_junkAttno, + &isNull); + /* shouldn't ever get a null result... */ + if (isNull) + elog(ERROR, "wholerow is NULL"); + + oldtupdata.t_data = DatumGetHeapTupleHeader(datum); + oldtupdata.t_len = + HeapTupleHeaderGetDatumLength(oldtupdata.t_data); + ItemPointerSetInvalid(&(oldtupdata.t_self)); + /* Historically, view triggers see invalid t_tableOid. */ + oldtupdata.t_tableOid = + (relkind == RELKIND_VIEW) ? InvalidOid : + RelationGetRelid(resultRelInfo->ri_RelationDesc); + oldtuple = &oldtupdata; + } + else + Assert(relkind == RELKIND_FOREIGN_TABLE); } + estate->es_result_relation_info = resultRelInfo; + switch (operation) { case CMD_INSERT: + slot = ExecGetNewInsertTuple(resultRelInfo, planSlot); /* Prepare for tuple routing if needed. */ if (proute) slot = ExecPrepareTupleRouting(node, estate, proute, @@ -2250,6 +2309,10 @@ ExecModifyTable(PlanState *pstate) estate->es_result_relation_info = resultRelInfo; break; case CMD_UPDATE: + Assert(resultRelInfo->ri_projectNew); + Assert(resultRelInfo->ri_oldTupleSlot); + slot = ExecGetUpdateNewTuple(resultRelInfo, planSlot, tupleid, + oldtuple); slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot, &node->mt_epqstate, estate, node->canSetTag); break; @@ -2646,90 +2709,111 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) * This section of code is also a convenient place to verify that the * output of an INSERT or UPDATE matches the target table(s). */ + for (i = 0; i < nplans; i++) { - bool junk_filter_needed = false; + List *resultTargetList = NIL; + bool need_projection = false; - switch (operation) + resultRelInfo = &mtstate->resultRelInfo[i]; + subplan = mtstate->mt_plans[i]->plan; + + /* + * Prepare to generate tuples suitable for the target relation. + */ + if (operation == CMD_INSERT || operation == CMD_UPDATE) { - case CMD_INSERT: + if (operation == CMD_INSERT) + { foreach(l, subplan->targetlist) { TargetEntry *tle = (TargetEntry *) lfirst(l); - if (tle->resjunk) - { - junk_filter_needed = true; - break; - } + if (!tle->resjunk) + resultTargetList = lappend(resultTargetList, tle); + else + need_projection = true; } - break; - case CMD_UPDATE: - case CMD_DELETE: - junk_filter_needed = true; - break; - default: - elog(ERROR, "unknown operation"); - break; - } - - if (junk_filter_needed) - { - resultRelInfo = mtstate->resultRelInfo; - for (i = 0; i < nplans; i++) + } + else { - JunkFilter *j; - TupleTableSlot *junkresslot; + resultTargetList = (List *) list_nth(node->updateTargetLists, + i); + need_projection = true; + } - subplan = mtstate->mt_plans[i]->plan; - if (operation == CMD_INSERT || operation == CMD_UPDATE) - ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, - subplan->targetlist); + /* + * The clean list must produce a tuple suitable for the result + * relation. + */ + ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, + resultTargetList); + } - junkresslot = - ExecInitExtraTupleSlot(estate, NULL, - table_slot_callbacks(resultRelInfo->ri_RelationDesc)); - j = ExecInitJunkFilter(subplan->targetlist, - junkresslot); + if (need_projection) + { + TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc); - if (operation == CMD_UPDATE || operation == CMD_DELETE) - { - /* For UPDATE/DELETE, find the appropriate junk attr now */ - char relkind; + /* + * For UPDATE, we use the old tuple to fill up missing values in + * the tuple produced by the plan to get the new tuple. + */ + if (operation == CMD_UPDATE) + resultRelInfo->ri_oldTupleSlot = + table_slot_create(resultRelInfo->ri_RelationDesc, + &mtstate->ps.state->es_tupleTable); + resultRelInfo->ri_newTupleSlot = + table_slot_create(resultRelInfo->ri_RelationDesc, + &mtstate->ps.state->es_tupleTable); + + /* need an expression context to do the projection */ + if (mtstate->ps.ps_ExprContext == NULL) + ExecAssignExprContext(estate, &mtstate->ps); + resultRelInfo->ri_projectNew = + ExecBuildProjectionInfo(resultTargetList, + mtstate->ps.ps_ExprContext, + resultRelInfo->ri_newTupleSlot, + &mtstate->ps, + relDesc); + } - relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind; - if (relkind == RELKIND_RELATION || - relkind == RELKIND_MATVIEW || - relkind == RELKIND_PARTITIONED_TABLE) - { - j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); - if (!AttributeNumberIsValid(j->jf_junkAttNo)) - elog(ERROR, "could not find junk ctid column"); - } - else if (relkind == RELKIND_FOREIGN_TABLE) - { - /* - * When there is a row-level trigger, there should be - * a wholerow attribute. - */ - j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow"); - } - else - { - j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow"); - if (!AttributeNumberIsValid(j->jf_junkAttNo)) - elog(ERROR, "could not find junk wholerow column"); - } - } + /* + * For UPDATE/DELETE, find the appropriate junk attr now. + */ + if (operation == CMD_UPDATE || operation == CMD_DELETE) + { + char relkind; - resultRelInfo->ri_junkFilter = j; - resultRelInfo++; + relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind; + if (relkind == RELKIND_RELATION || + relkind == RELKIND_MATVIEW || + relkind == RELKIND_PARTITIONED_TABLE) + { + resultRelInfo->ri_junkAttno = + ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid"); + if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno)) + elog(ERROR, "could not find junk ctid column"); + } + else if (relkind == RELKIND_FOREIGN_TABLE) + { + /* + * When there is a row-level trigger, there should be + * a wholerow attribute. + */ + resultRelInfo->ri_junkAttno = + ExecFindJunkAttributeInTlist(subplan->targetlist, + "wholerow"); + /* HACK: we require it to be present for updates. */ + if (mtstate->operation == CMD_UPDATE && + !AttributeNumberIsValid(resultRelInfo->ri_junkAttno)) + elog(ERROR, "could not find junk wholerow column"); + } + else + { + resultRelInfo->ri_junkAttno = + ExecFindJunkAttributeInTlist(subplan->targetlist, "wholerow"); + if (!AttributeNumberIsValid(resultRelInfo->ri_junkAttno)) + elog(ERROR, "could not find junk wholerow column"); } - } - else - { - if (operation == CMD_INSERT) - ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc, - subplan->targetlist); } } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index d8cf87e..c988c96 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -212,6 +212,7 @@ _copyModifyTable(const ModifyTable *from) COPY_NODE_FIELD(plans); COPY_NODE_FIELD(withCheckOptionLists); COPY_NODE_FIELD(returningLists); + COPY_NODE_FIELD(updateTargetLists); COPY_NODE_FIELD(fdwPrivLists); COPY_BITMAPSET_FIELD(fdwDirectModifyPlans); COPY_NODE_FIELD(rowMarks); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index e2f1775..cd8dfcf 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -413,6 +413,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node) WRITE_NODE_FIELD(plans); WRITE_NODE_FIELD(withCheckOptionLists); WRITE_NODE_FIELD(returningLists); + WRITE_NODE_FIELD(updateTargetLists); WRITE_NODE_FIELD(fdwPrivLists); WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans); WRITE_NODE_FIELD(rowMarks); @@ -2120,6 +2121,7 @@ _outModifyTablePath(StringInfo str, const ModifyTablePath *node) WRITE_NODE_FIELD(subroots); WRITE_NODE_FIELD(withCheckOptionLists); WRITE_NODE_FIELD(returningLists); + WRITE_NODE_FIELD(updateTargetLists); WRITE_NODE_FIELD(rowMarks); WRITE_NODE_FIELD(onconflict); WRITE_INT_FIELD(epqParam); @@ -2235,6 +2237,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_NODE_FIELD(full_join_clauses); WRITE_NODE_FIELD(join_info_list); WRITE_NODE_FIELD(append_rel_list); + WRITE_NODE_FIELD(result_rel_list); WRITE_NODE_FIELD(rowMarks); WRITE_NODE_FIELD(placeholder_list); WRITE_NODE_FIELD(fkey_list); @@ -2249,7 +2252,6 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_FLOAT_FIELD(tuple_fraction, "%.4f"); WRITE_FLOAT_FIELD(limit_tuples, "%.0f"); WRITE_UINT_FIELD(qual_security_level); - WRITE_ENUM_FIELD(inhTargetKind, InheritanceKind); WRITE_BOOL_FIELD(hasJoinRTEs); WRITE_BOOL_FIELD(hasLateralRTEs); WRITE_BOOL_FIELD(hasHavingQual); @@ -2546,6 +2548,18 @@ _outAppendRelInfo(StringInfo str, const AppendRelInfo *node) } static void +_outResultRelPlanInfo(StringInfo str, const ResultRelPlanInfo *node) +{ + WRITE_NODE_TYPE("RESULTRELPLANINFO"); + + WRITE_UINT_FIELD(resultRelation); + WRITE_NODE_FIELD(subplanTargetList); + WRITE_NODE_FIELD(updateTargetList); + WRITE_NODE_FIELD(withCheckOptions); + WRITE_NODE_FIELD(returningList); +} + +static void _outPlaceHolderInfo(StringInfo str, const PlaceHolderInfo *node) { WRITE_NODE_TYPE("PLACEHOLDERINFO"); @@ -4148,6 +4162,9 @@ outNode(StringInfo str, const void *obj) case T_AppendRelInfo: _outAppendRelInfo(str, obj); break; + case T_ResultRelPlanInfo: + _outResultRelPlanInfo(str, obj); + break; case T_PlaceHolderInfo: _outPlaceHolderInfo(str, obj); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 42050ab..92bb7ad 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1644,6 +1644,7 @@ _readModifyTable(void) READ_NODE_FIELD(plans); READ_NODE_FIELD(withCheckOptionLists); READ_NODE_FIELD(returningLists); + READ_NODE_FIELD(updateTargetLists); READ_NODE_FIELD(fdwPrivLists); READ_BITMAPSET_FIELD(fdwDirectModifyPlans); READ_NODE_FIELD(rowMarks); diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index eb9543f..07fc199 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -297,6 +297,7 @@ static ModifyTable *make_modifytable(PlannerInfo *root, bool partColsUpdated, List *resultRelations, List *subplans, List *subroots, List *withCheckOptionLists, List *returningLists, + List *updateTargetLists, List *rowMarks, OnConflictExpr *onconflict, int epqParam); static GatherMerge *create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path); @@ -2690,6 +2691,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path) best_path->subroots, best_path->withCheckOptionLists, best_path->returningLists, + best_path->updateTargetLists, best_path->rowMarks, best_path->onconflict, best_path->epqParam); @@ -6798,6 +6800,7 @@ make_modifytable(PlannerInfo *root, bool partColsUpdated, List *resultRelations, List *subplans, List *subroots, List *withCheckOptionLists, List *returningLists, + List *updateTargetLists, List *rowMarks, OnConflictExpr *onconflict, int epqParam) { ModifyTable *node = makeNode(ModifyTable); @@ -6807,12 +6810,13 @@ make_modifytable(PlannerInfo *root, ListCell *lc2; int i; - Assert(list_length(resultRelations) == list_length(subplans)); - Assert(list_length(resultRelations) == list_length(subroots)); Assert(withCheckOptionLists == NIL || list_length(resultRelations) == list_length(withCheckOptionLists)); Assert(returningLists == NIL || list_length(resultRelations) == list_length(returningLists)); + Assert(operation != CMD_UPDATE || + (updateTargetLists != NIL && + list_length(resultRelations) == list_length(updateTargetLists))); node->plan.lefttree = NULL; node->plan.righttree = NULL; @@ -6857,6 +6861,7 @@ make_modifytable(PlannerInfo *root, } node->withCheckOptionLists = withCheckOptionLists; node->returningLists = returningLists; + node->updateTargetLists = updateTargetLists; node->rowMarks = rowMarks; node->epqParam = epqParam; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 4131019..afad8c6 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -636,6 +636,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->wt_param_id = -1; root->non_recursive_path = NULL; root->partColsUpdated = false; + root->result_rel_list = NIL; /* * If there is a WITH list, process each WITH query and either convert it @@ -1231,6 +1232,7 @@ inheritance_planner(PlannerInfo *root) List *withCheckOptionLists = NIL; List *returningLists = NIL; List *rowMarks; + List *updateTargetLists = NIL; RelOptInfo *final_rel; ListCell *lc; ListCell *lc2; @@ -1484,6 +1486,8 @@ inheritance_planner(PlannerInfo *root) RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; + ResultRelPlanInfo *resultInfo; + AttrNumber resno; /* * expand_inherited_rtentry() always processes a parent before any of @@ -1581,6 +1585,9 @@ inheritance_planner(PlannerInfo *root) subroot->append_rel_list = copyObject(root->append_rel_list); subroot->rowMarks = copyObject(root->rowMarks); + /* Child result relation's only ResultRelPlanInfo goes here. */ + subroot->result_rel_list = NIL; + /* * If this isn't the first child Query, adjust Vars and jointree * entries to reference the appropriate set of subquery RTEs. @@ -1701,6 +1708,27 @@ inheritance_planner(PlannerInfo *root) returningLists = lappend(returningLists, subroot->parse->returningList); + Assert(list_length(subroot->result_rel_list) == 1); + resultInfo = linitial(subroot->result_rel_list); + if (parse->commandType == CMD_UPDATE) + updateTargetLists = lappend(updateTargetLists, + resultInfo->updateTargetList); + root->result_rel_list = lappend(root->result_rel_list, resultInfo); + + /* + * While we are here, renumber the top-level targetlist so that + * resnos match those in the top-level plan's targetlist. + * XXX - really, this is to prevent apply_tlist_labeling() from + * crashing. + */ + resno = 1; + foreach(lc, subroot->processed_tlist) + { + TargetEntry *tle = lfirst(lc); + + tle->resno = resno++; + } + Assert(!parse->onConflict); } @@ -1726,7 +1754,10 @@ inheritance_planner(PlannerInfo *root) Path *dummy_path; /* tlist processing never got done, either */ + Assert(root->result_rel_list == NIL); root->processed_tlist = preprocess_targetlist(root); + /* that should also have a made a ResultRelPlanInfo for us. */ + Assert(list_length(root->result_rel_list) == 1); final_rel->reltarget = create_pathtarget(root, root->processed_tlist); /* Make a dummy path, cf set_dummy_rel_pathlist() */ @@ -1742,6 +1773,14 @@ inheritance_planner(PlannerInfo *root) withCheckOptionLists = list_make1(parse->withCheckOptions); if (parse->returningList) returningLists = list_make1(parse->returningList); + /* ExecInitModifyTable insists that updateTargetList is present. */ + if (parse->commandType == CMD_UPDATE) + { + ResultRelPlanInfo *resultInfo; + + resultInfo = linitial(root->result_rel_list); + updateTargetLists = list_make1(resultInfo->updateTargetList); + } /* Disable tuple routing, too, just to be safe */ root->partColsUpdated = false; } @@ -1794,6 +1833,7 @@ inheritance_planner(PlannerInfo *root) resultRelations, subpaths, subroots, + updateTargetLists, withCheckOptionLists, returningLists, rowMarks, @@ -2333,6 +2373,8 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, List *withCheckOptionLists; List *returningLists; List *rowMarks; + List *updateTargetLists = NIL; + AttrNumber resno; /* * If target is a partition root table, we need to mark the @@ -2368,6 +2410,29 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, else rowMarks = root->rowMarks; + if (root->result_rel_list) + { + ResultRelPlanInfo *resultInfo = + linitial(root->result_rel_list); + + if (resultInfo->updateTargetList) + updateTargetLists = list_make1(resultInfo->updateTargetList); + } + + /* + * While we are here, renumber the top-level targetlist so that + * resnos match those in the top-level plan's targetlist. + * XXX - really, this is to prevent apply_tlist_labeling() from + * crashing. + */ + resno = 1; + foreach(lc, root->processed_tlist) + { + TargetEntry *tle = lfirst(lc); + + tle->resno = resno++; + } + path = (Path *) create_modifytable_path(root, final_rel, parse->commandType, @@ -2378,6 +2443,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, list_make1_int(parse->resultRelation), list_make1(path), list_make1(root), + updateTargetLists, withCheckOptionLists, returningLists, rowMarks, diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index baefe0e..2c31a7c 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -148,6 +148,9 @@ static List *set_returning_clause_references(PlannerInfo *root, Plan *topplan, Index resultRelation, int rtoffset); +static void set_update_tlist_references(PlannerInfo *root, + ModifyTable *splan, + int rtoffset); /***************************************************************************** @@ -812,6 +815,9 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) Assert(splan->plan.targetlist == NIL); Assert(splan->plan.qual == NIL); + if (splan->operation == CMD_UPDATE) + set_update_tlist_references(root, splan, rtoffset); + splan->withCheckOptionLists = fix_scan_list(root, splan->withCheckOptionLists, rtoffset); @@ -2694,6 +2700,55 @@ set_returning_clause_references(PlannerInfo *root, return rlist; } +/* + * Update splan->updateTargetLists to refer to the subplan's output where + * applicable. + */ +static void +set_update_tlist_references(PlannerInfo *root, + ModifyTable *splan, + int rtoffset) +{ + ListCell *lc1, + *lc2, + *lc3; + + forthree(lc1, splan->resultRelations, + lc2, splan->updateTargetLists, + lc3, root->result_rel_list) + { + Index resultRel = lfirst_int(lc1); + List *updateTargetList = lfirst(lc2); + ResultRelPlanInfo *resultInfo = lfirst(lc3); + AttrNumber resno; + ListCell *lc; + indexed_tlist *itlist; + + Assert(resultInfo); + + /* + * Make resnos of subplan tlist TLEs match their ordinal position in + * the list. It's okay to scribble on them now. + */ + resno = 1; + foreach(lc, resultInfo->subplanTargetList) + { + TargetEntry *tle = lfirst(lc); + + tle->resno = resno++; + } + + itlist = build_tlist_index(resultInfo->subplanTargetList); + lfirst(lc2) = fix_join_expr(root, + updateTargetList, + itlist, + NULL, + resultRel, + rtoffset); + + } +} + /***************************************************************************** * QUERY DEPENDENCY MANAGEMENT diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index d56d8c6..89f7d89 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -45,6 +45,7 @@ #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "optimizer/optimizer.h" +#include "optimizer/plancat.h" #include "optimizer/prep.h" #include "optimizer/tlist.h" #include "parser/parse_coerce.h" @@ -111,12 +112,21 @@ preprocess_targetlist(PlannerInfo *root) * for heap_form_tuple to work, the targetlist must match the exact order * of the attributes. We also need to fill in any missing attributes. -ay * 10/94 + * + * For UPDATE, we don't expand the plan's targetlist. Instead, another + * targetlist to be computed separately from the plan's targetlist is + * built and passed to the executor in the ModifyTable node; see + * make_update_targetlist(). */ tlist = parse->targetList; - if (command_type == CMD_INSERT || command_type == CMD_UPDATE) + if (command_type == CMD_INSERT) tlist = expand_targetlist(tlist, command_type, result_relation, target_relation); + /* Make ResultRelPlanInfo for a UPDATE/DELETE result relation. */ + if (target_relation && command_type != CMD_INSERT) + make_result_relation_info(root, result_relation, target_relation); + /* * Add necessary junk columns for rowmarked rels. These values are needed * for locking of rels selected FOR UPDATE/SHARE, and to do EvalPlanQual @@ -415,6 +425,99 @@ expand_targetlist(List *tlist, int command_type, return new_tlist; } +/* + * make_update_targetlist + * Makes UPDATE targetlist such that "plan" TLEs are used for the + * attributes for which they are present. For the rest, Vars are + * added to fetch the existing values in the "old" tuple of the + * attributes that are not dropped, and NULL constants otherwise. + */ +List * +make_update_targetlist(PlannerInfo *root, + Index rti, Relation relation, + List *plan_tlist) +{ + List *new_tlist = NIL; + ListCell *plan_item; + int attrno, + numattrs; + + plan_item = list_head(plan_tlist); + + numattrs = RelationGetNumberOfAttributes(relation); + for (attrno = 1; attrno <= numattrs; attrno++) + { + Form_pg_attribute att = TupleDescAttr(relation->rd_att, attrno - 1); + TargetEntry *new_tle = NULL; + Node *new_expr; + + if (plan_item != NULL) + { + TargetEntry *plan_tle = (TargetEntry *) lfirst(plan_item); + + if (!plan_tle->resjunk && plan_tle->resno == attrno) + { + /* Don't use the plan TLE as is; it gets changed later. */ + new_tle = copyObject(plan_tle); + plan_item = lnext(plan_tlist, plan_item); + } + } + + + /* + * If not plan TLE present, generate a Var reference to the existing + * value of the attribute, so that it gets copied to the new tuple. But + * generate a NULL for dropped columns (we want to drop any old values). + */ + if (new_tle == NULL) + { + if (!att->attisdropped) + { + new_expr = (Node *) makeVar(rti, + attrno, + att->atttypid, + att->atttypmod, + att->attcollation, + 0); + } + else + { + /* Insert NULL for dropped column */ + new_expr = (Node *) makeConst(INT4OID, + -1, + InvalidOid, + sizeof(int32), + (Datum) 0, + true, /* isnull */ + true /* byval */ ); + } + new_tle = makeTargetEntry((Expr *) new_expr, + attrno, + pstrdup(NameStr(att->attname)), + false); + new_tlist = lappend(new_tlist, new_tle); + } + else + new_tlist = lappend(new_tlist, new_tle); + } + + + /* + * The remaining tlist entries should be resjunk. We don't need them + * for the update target list, but still check that they really are + * junk. + */ + while (plan_item) + { + TargetEntry *plan_tle = (TargetEntry *) lfirst(plan_item); + + if (!plan_tle->resjunk) + elog(ERROR, "targetlist is not sorted correctly"); + plan_item = lnext(plan_tlist, plan_item); + } + + return new_tlist; +} /* * Locate PlanRowMark for given RT index, or return NULL if none diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index e845a4b..64da7f5 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -3490,6 +3490,7 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel, * 'resultRelations' is an integer list of actual RT indexes of target rel(s) * 'subpaths' is a list of Path(s) producing source data (one per rel) * 'subroots' is a list of PlannerInfo structs (one per rel) + * 'updateTargetLists' is a list of UPDATE NEW tuple targetlists. * 'withCheckOptionLists' is a list of WCO lists (one per rel) * 'returningLists' is a list of RETURNING tlists (one per rel) * 'rowMarks' is a list of PlanRowMarks (non-locking only) @@ -3502,7 +3503,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel, Index nominalRelation, Index rootRelation, bool partColsUpdated, List *resultRelations, List *subpaths, - List *subroots, + List *subroots, List *updateTargetLists, List *withCheckOptionLists, List *returningLists, List *rowMarks, OnConflictExpr *onconflict, int epqParam) @@ -3572,6 +3573,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel, pathnode->resultRelations = resultRelations; pathnode->subpaths = subpaths; pathnode->subroots = subroots; + pathnode->updateTargetLists = updateTargetLists; pathnode->withCheckOptionLists = withCheckOptionLists; pathnode->returningLists = returningLists; pathnode->rowMarks = rowMarks; diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 2554502..a9f833a 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -35,6 +35,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/supportnodes.h" +#include "optimizer/appendinfo.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/optimizer.h" @@ -2345,3 +2346,59 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel) rel->partition_qual = partconstr; } } + +/* + * make_result_relation_info + * Adds information to the PlannerInfo about a UPDATE/DELETE result + * relation that will be made part of the ModifyTable node in the + * final plan. + * + * For UPDATE, this adds the targetlist (called 'updateTargetList') that is + * computed separately from the plan's top-level targetlist to get the tuples + * suitable for this result relation. Some entries are copied directly from + * the plan's top-level targetlist while others are simply Vars of the + * relation. In fact, each TLE copied from the top-level targetlist is later + * changed such that its expression is an OUTER Var referring to the + * appropriate column in the plan's output; see setrefs.c: + * set_update_tlist_references(). + */ +void +make_result_relation_info(PlannerInfo *root, + Index rti, Relation relation) +{ + Query *parse = root->parse; + ResultRelPlanInfo *resultInfo = makeNode(ResultRelPlanInfo); + List *subplanTargetList; + List *returningList = NIL; + List *updateTargetList = NIL; + List *withCheckOptions = NIL; + + /* parse->targetList resnos get updated later, so make a copy. */ + subplanTargetList = copyObject(parse->targetList); + withCheckOptions = parse->withCheckOptions; + returningList = parse->returningList; + + if (parse->commandType == CMD_UPDATE) + { + List *planTargetList = subplanTargetList; + + /* Fix to use plan TLEs over the existing ones. */ + updateTargetList = make_update_targetlist(root, rti, relation, + planTargetList); + } + + /* + * Of the following, everything except subplanTargetList goes into the + * ModifyaTable plan. subplanTargetListis is only kept around because + * set_update_tlist_references() must use result relation specific + * version of the original targetlist when matching the entries in + * their updateTargetLists. + */ + resultInfo->resultRelation = rti; + resultInfo->subplanTargetList = subplanTargetList; + resultInfo->updateTargetList = updateTargetList; + resultInfo->withCheckOptions = withCheckOptions; + resultInfo->returningList = returningList; + + root->result_rel_list = lappend(root->result_rel_list, resultInfo); +} diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c index 02a3c6b..4c1677f 100644 --- a/src/backend/optimizer/util/tlist.c +++ b/src/backend/optimizer/util/tlist.c @@ -220,6 +220,25 @@ count_nonjunk_tlist_entries(List *tlist) return len; } +/* + * filter_junk_tlist_entries + * What it says ... + */ +List * +filter_junk_tlist_entries(List *tlist) +{ + List *result = NIL; + ListCell *l; + + foreach(l, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(l); + + if (!tle->resjunk) + result = lappend(result, tle); + } + return result; +} /* * tlist_same_exprs diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index fe777c3..7bef075 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -1480,17 +1480,27 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte, target_relation); /* - * If we have a row-level trigger corresponding to the operation, emit - * a whole-row Var so that executor will have the "old" row to pass to - * the trigger. Alas, this misses system columns. + * We need the "old" tuple to fill up the values for unassigned-to + * attributes in the case of UPDATE. We will need it also if there + * are any DELETE row triggers. + * + * This is a HACK. The previous comment and if condition: + * + * -* If we have a row-level trigger corresponding to the operation, emit + * -* a whole-row Var so that executor will have the "old" row to pass to + * -* the trigger. Alas, this misses system columns. + * -if (target_relation->trigdesc && + * - ((parsetree->commandType == CMD_UPDATE && + * - (target_relation->trigdesc->trig_update_after_row || + * - target_relation->trigdesc->trig_update_before_row)) || + * - (parsetree->commandType == CMD_DELETE && + * - (target_relation->trigdesc->trig_delete_after_row || + * - target_relation->trigdesc->trig_delete_before_row)))) */ - if (target_relation->trigdesc && - ((parsetree->commandType == CMD_UPDATE && - (target_relation->trigdesc->trig_update_after_row || - target_relation->trigdesc->trig_update_before_row)) || - (parsetree->commandType == CMD_DELETE && - (target_relation->trigdesc->trig_delete_after_row || - target_relation->trigdesc->trig_delete_before_row)))) + if (parsetree->commandType == CMD_UPDATE || + (target_relation->trigdesc && + (target_relation->trigdesc->trig_delete_after_row || + target_relation->trigdesc->trig_delete_before_row))) { var = makeWholeRowVar(target_rte, parsetree->resultRelation, diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index c7deeac..903b110 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -603,4 +603,13 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd); extern void CheckSubscriptionRelkind(char relkind, const char *nspname, const char *relname); +/* prototypes from functions in nodeModifyTable.c */ +extern ResultRelInfo *ExecLookupResultRelByOid(ModifyTableState *mtstate, Oid reloid, + int *whichrel); +/* needed by trigger.c */ +extern TupleTableSlot *ExecGetUpdateNewTuple(ResultRelInfo *relinfo, + TupleTableSlot *planSlot, + ItemPointerData *tupleid, + HeapTupleData *oldtuple); + #endif /* EXECUTOR_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index f5dfa32..520ef1b 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -462,9 +462,6 @@ typedef struct ResultRelInfo /* number of stored generated columns we need to compute */ int ri_NumGeneratedNeeded; - /* for removing junk attributes from tuples */ - JunkFilter *ri_junkFilter; - /* list of RETURNING expressions */ List *ri_returningList; @@ -491,6 +488,12 @@ typedef struct ResultRelInfo /* For use by copy.c when performing multi-inserts */ struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer; + + /* Stuff for generating and holding "clean" tuples of this relation */ + int ri_junkAttno; + TupleTableSlot *ri_oldTupleSlot; + TupleTableSlot *ri_newTupleSlot; + ProjectionInfo *ri_projectNew; } ResultRelInfo; /* ---------------- diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 381d84b..173f66d 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -269,6 +269,7 @@ typedef enum NodeTag T_PlaceHolderVar, T_SpecialJoinInfo, T_AppendRelInfo, + T_ResultRelPlanInfo, T_PlaceHolderInfo, T_MinMaxAggInfo, T_PlannerParamItem, diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 485d1b0..3cee3a3 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -218,6 +218,11 @@ struct PlannerInfo */ struct AppendRelInfo **append_rel_array; + /* Valid for UPDATE/DELETE. */ + List *result_rel_list; /* List of ResultRelPlanInfo */ + /* Same length as other "simple" rel arrays. */ + struct ResultRelPlanInfo **result_rel_array; + /* * all_baserels is a Relids set of all base relids (but not "other" * relids) in the query; that is, the Relids identifier of the final join @@ -1818,6 +1823,7 @@ typedef struct ModifyTablePath List *resultRelations; /* integer list of RT indexes */ List *subpaths; /* Path(s) producing source data */ List *subroots; /* per-target-table PlannerInfos */ + List *updateTargetLists; /* per-target-table tlists */ List *withCheckOptionLists; /* per-target-table WCO lists */ List *returningLists; /* per-target-table RETURNING tlists */ List *rowMarks; /* PlanRowMarks (non-locking only) */ @@ -2274,6 +2280,37 @@ typedef struct AppendRelInfo } AppendRelInfo; /* + * ResultRelPlanInfo + * Information about UPDATE/DELETE result relations + * + * For the original result relation, these fields point to the information + * in the original Query, except the target lists are different. + * subplanTargetList is set to a copy of PlannerInfo.processed_tlist after all + * the necessary junk columns have been added and is also what the top-level + * plan produces. updateTargetList is obtained by applying expand_targetlist() + * to subplanTargetList, followed by filtering out junk columns, so that its + * output is a tuple ready to be put into the result relation. + * + * For child result relations, relevant fields are obtained by translating Vars + * and for updateTargetList also adjusting the resnos to match the child + * attributes numbers. + * + * Everything execpt subplanTargetList goes into the ModifyTable node. + * + * See set_result_relation_info(). + */ +typedef struct ResultRelPlanInfo +{ + NodeTag type; + + Index resultRelation; + List *subplanTargetList; + List *updateTargetList; + List *withCheckOptions; + List *returningList; +} ResultRelPlanInfo; + +/* * For each distinct placeholder expression generated during planning, we * store a PlaceHolderInfo node in the PlannerInfo node's placeholder_list. * This stores info that is needed centrally rather than in each copy of the diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 83e0107..909820b 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -226,9 +226,10 @@ typedef struct ModifyTable List *resultRelations; /* integer list of RT indexes */ int resultRelIndex; /* index of first resultRel in plan's list */ int rootResultRelIndex; /* index of the partitioned table root */ - List *plans; /* plan(s) producing source data */ + List *plans; /* Plan(s) producing source data */ List *withCheckOptionLists; /* per-target-table WCO lists */ - List *returningLists; /* per-target-table RETURNING tlists */ + List *returningLists; /* per-target-table RETURNING tlists */ + List *updateTargetLists; /* per-target-table tlists */ List *fdwPrivLists; /* per-target-table FDW private data lists */ Bitmapset *fdwDirectModifyPlans; /* indices of FDW DM plans */ List *rowMarks; /* PlanRowMarks (non-locking only) */ diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h index 3e41710..eb9d4fc 100644 --- a/src/include/optimizer/optimizer.h +++ b/src/include/optimizer/optimizer.h @@ -152,6 +152,7 @@ extern bool predicate_refuted_by(List *predicate_list, List *clause_list, /* in util/tlist.c: */ extern int count_nonjunk_tlist_entries(List *tlist); +extern List *filter_junk_tlist_entries(List *tlist); extern TargetEntry *get_sortgroupref_tle(Index sortref, List *targetList); extern TargetEntry *get_sortgroupclause_tle(SortGroupClause *sgClause, diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 715a24a..21caac3 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -261,7 +261,7 @@ extern ModifyTablePath *create_modifytable_path(PlannerInfo *root, Index nominalRelation, Index rootRelation, bool partColsUpdated, List *resultRelations, List *subpaths, - List *subroots, + List *subroots, List *updateTargetLists, List *withCheckOptionLists, List *returningLists, List *rowMarks, OnConflictExpr *onconflict, int epqParam); diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index c29a709..66dbde2 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -27,6 +27,8 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook; extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, RelOptInfo *rel); +extern void make_result_relation_info(PlannerInfo *root, + Index rti, Relation relation); extern List *infer_arbiter_indexes(PlannerInfo *root); diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index 19c9230..6ee87af 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -16,6 +16,7 @@ #include "nodes/pathnodes.h" #include "nodes/plannodes.h" +#include "utils/relcache.h" /* @@ -35,7 +36,10 @@ extern Relids get_relids_for_join(Query *query, int joinrelid); * prototypes for preptlist.c */ extern List *preprocess_targetlist(PlannerInfo *root); - +/* XXX not really a "prep" function? */ +extern List *make_update_targetlist(PlannerInfo *root, + Index rti, Relation relation, + List *plan_tlist); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); /* diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index 2b68aef..94e43c3 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -545,25 +545,25 @@ 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 ----------------------------------- + QUERY PLAN +-------------------------------- Update on public.some_tab Update on public.some_tab -> Result - Output: (a + 1), b, ctid + Output: (a + 1), 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 ----------------------------------- + QUERY PLAN +-------------------------------- Update on public.some_tab Output: b, a Update on public.some_tab -> Result - Output: (a + 1), b, ctid + Output: (a + 1), ctid One-Time Filter: false (6 rows) diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out index 5de53f2..13d0d2a 100644 --- a/src/test/regress/expected/updatable_views.out +++ b/src/test/regress/expected/updatable_views.out @@ -1283,12 +1283,12 @@ SELECT * FROM rw_view1; (4 rows) EXPLAIN (verbose, costs off) UPDATE rw_view1 SET b = b + 1 RETURNING *; - QUERY PLAN -------------------------------------------------------------- + QUERY PLAN +------------------------------------------------- Update on public.base_tbl Output: base_tbl.a, base_tbl.b -> Seq Scan on public.base_tbl - Output: base_tbl.a, (base_tbl.b + 1), base_tbl.ctid + Output: (base_tbl.b + 1), base_tbl.ctid (4 rows) UPDATE rw_view1 SET b = b + 1 RETURNING *; @@ -2309,7 +2309,7 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; Update on public.t12 t1_2 Update on public.t111 t1_3 -> Index Scan using t1_a_idx on public.t1 - Output: 100, t1.b, t1.c, t1.ctid + Output: 100, t1.ctid Index Cond: ((t1.a > 5) AND (t1.a < 7)) Filter: ((t1.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1.a) AND leakproof(t1.a)) SubPlan 1 @@ -2325,15 +2325,15 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; -> Seq Scan on public.t111 t12_5 Output: t12_5.a -> Index Scan using t11_a_idx on public.t11 t1_1 - Output: 100, t1_1.b, t1_1.c, t1_1.d, t1_1.ctid + Output: 100, t1_1.ctid Index Cond: ((t1_1.a > 5) AND (t1_1.a < 7)) Filter: ((t1_1.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_1.a) AND leakproof(t1_1.a)) -> Index Scan using t12_a_idx on public.t12 t1_2 - Output: 100, t1_2.b, t1_2.c, t1_2.e, t1_2.ctid + Output: 100, t1_2.ctid Index Cond: ((t1_2.a > 5) AND (t1_2.a < 7)) Filter: ((t1_2.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_2.a) AND leakproof(t1_2.a)) -> Index Scan using t111_a_idx on public.t111 t1_3 - Output: 100, t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid + Output: 100, t1_3.ctid Index Cond: ((t1_3.a > 5) AND (t1_3.a < 7)) Filter: ((t1_3.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a)) (33 rows) @@ -2359,7 +2359,7 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; Update on public.t12 t1_2 Update on public.t111 t1_3 -> Index Scan using t1_a_idx on public.t1 - Output: (t1.a + 1), t1.b, t1.c, t1.ctid + Output: (t1.a + 1), t1.ctid Index Cond: ((t1.a > 5) AND (t1.a = 8)) Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1.a) AND leakproof(t1.a)) SubPlan 1 @@ -2375,15 +2375,15 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; -> Seq Scan on public.t111 t12_5 Output: t12_5.a -> Index Scan using t11_a_idx on public.t11 t1_1 - Output: (t1_1.a + 1), t1_1.b, t1_1.c, t1_1.d, t1_1.ctid + Output: (t1_1.a + 1), t1_1.ctid Index Cond: ((t1_1.a > 5) AND (t1_1.a = 8)) Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_1.a) AND leakproof(t1_1.a)) -> Index Scan using t12_a_idx on public.t12 t1_2 - Output: (t1_2.a + 1), t1_2.b, t1_2.c, t1_2.e, t1_2.ctid + Output: (t1_2.a + 1), t1_2.ctid Index Cond: ((t1_2.a > 5) AND (t1_2.a = 8)) Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_2.a) AND leakproof(t1_2.a)) -> Index Scan using t111_a_idx on public.t111 t1_3 - Output: (t1_3.a + 1), t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid + Output: (t1_3.a + 1), t1_3.ctid Index Cond: ((t1_3.a > 5) AND (t1_3.a = 8)) Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a)) (33 rows) diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out index bf939d7..dece036 100644 --- a/src/test/regress/expected/update.out +++ b/src/test/regress/expected/update.out @@ -172,14 +172,14 @@ EXPLAIN (VERBOSE, COSTS OFF) UPDATE update_test t SET (a, b) = (SELECT b, a FROM update_test s WHERE s.a = t.a) WHERE CURRENT_USER = SESSION_USER; - QUERY PLAN ------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------- Update on public.update_test t -> Result - Output: $1, $2, t.c, (SubPlan 1 (returns $1,$2)), t.ctid + Output: $1, $2, (SubPlan 1 (returns $1,$2)), t.ctid One-Time Filter: (CURRENT_USER = SESSION_USER) -> Seq Scan on public.update_test t - Output: t.c, t.a, t.ctid + Output: t.a, t.ctid SubPlan 1 (returns $1,$2) -> Seq Scan on public.update_test s Output: s.b, s.a -- 1.8.3.1