From c198957075f797be9a1d186744cb024d0fb6db2e Mon Sep 17 00:00:00 2001 From: amitlan Date: Tue, 9 Jun 2020 22:14:20 +0900 Subject: [PATCH v8 1/2] Overhaul how updates compute new tuple Currently, the planner rewrites the top-level targetlist of an update statement's parsetree so that it contains entries for all attributes of the target relation, including for those columns that have not been changed. This arrangement means that the executor can take a tuple that the plan produces, remove any junk attributes in it and pass it down to the table AM or FDW update API as the new tuple. It also means that in an inherited update, where there are multiple target relations, the planner must produce that many plans, because the targetlists for different target relations may not all look the same considering that child relations may have different sets of columns with varying attribute numbers. This commit revises things so that the planner no longer expands the parsetree targetlist to include unchanged columns so that the plan only produces values of the changed columns. To make the new tuple to pass to table AM and FDW update API, executor now evaluates another targetlist matching the target table's TupleDesc which refers to the plan's output tuple to gets values of the changed columns and to the old tuple that is refetched for values of unchanged columns. To get values for unchanged columns to use when forming the new tuple to pass to ExecForeignUpdate(), we now require foreign scans to always include the wholerow Var corresponding to the old tuple being updated, because the unchanged columns are not present in the plan's targetlist. As a note to FDW authors, any FDW update planning APIs that look at the plan's targetlist for checking if it is pushable to remote side (e.g. PlanDirectModify) should now instead look at "update targetlist" that is set by the planner in PlannerInfo.update_tlist, because resnos in the plan's targetlist is no longer indexable by target column's attribute numbers. Note that even though the main goal of doing this is to avoid having to make multiple plans in the inherited update case, this commit does not touch that subject. A subsequent commit will change things that are necessary to make inherited updates work with a single plan. --- contrib/postgres_fdw/deparse.c | 6 +- .../postgres_fdw/expected/postgres_fdw.out | 94 ++-- contrib/postgres_fdw/postgres_fdw.c | 16 +- doc/src/sgml/fdwhandler.sgml | 9 +- src/backend/commands/trigger.c | 16 +- src/backend/executor/execMain.c | 1 - src/backend/executor/nodeModifyTable.c | 430 +++++++++++------- src/backend/nodes/copyfuncs.c | 1 + src/backend/nodes/outfuncs.c | 4 +- src/backend/nodes/readfuncs.c | 1 + src/backend/optimizer/plan/createplan.c | 50 +- src/backend/optimizer/plan/planner.c | 17 + src/backend/optimizer/plan/setrefs.c | 46 ++ src/backend/optimizer/prep/preptlist.c | 47 +- src/backend/optimizer/util/pathnode.c | 4 +- src/backend/optimizer/util/tlist.c | 19 + src/backend/rewrite/rewriteHandler.c | 22 +- src/include/executor/executor.h | 5 + src/include/nodes/execnodes.h | 24 +- src/include/nodes/pathnodes.h | 14 +- src/include/nodes/plannodes.h | 1 + src/include/optimizer/optimizer.h | 1 + src/include/optimizer/pathnode.h | 2 +- src/test/regress/expected/inherit.out | 12 +- src/test/regress/expected/updatable_views.out | 26 +- src/test/regress/expected/update.out | 8 +- 26 files changed, 587 insertions(+), 289 deletions(-) diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index 6faf499f9a..9ddc318af4 100644 --- a/contrib/postgres_fdw/deparse.c +++ b/contrib/postgres_fdw/deparse.c @@ -1866,7 +1866,7 @@ deparseUpdateSql(StringInfo buf, RangeTblEntry *rte, * 'rel' is the relation descriptor for the target relation * 'foreignrel' is the RelOptInfo for the target relation or the join relation * containing all base relations in the query - * 'targetlist' is the tlist of the underlying foreign-scan plan node + * 'update_tlist' is UPDATE targetlist * 'targetAttrs' is the target columns of the UPDATE * 'remote_conds' is the qual clauses that must be evaluated remotely * '*params_list' is an output list of exprs that will become remote Params @@ -1878,7 +1878,7 @@ void deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, RelOptInfo *foreignrel, - List *targetlist, + List *update_tlist, List *targetAttrs, List *remote_conds, List **params_list, @@ -1911,7 +1911,7 @@ deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root, foreach(lc, targetAttrs) { int attnum = lfirst_int(lc); - TargetEntry *tle = get_tle_by_resno(targetlist, attnum); + TargetEntry *tle = get_tle_by_resno(update_tlist, attnum); if (!tle) elog(ERROR, "attribute number %d not found in UPDATE targetlist", diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index b09dce63f5..ba0dabb7c2 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -5485,13 +5485,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 @@ -5521,9 +5521,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 *; @@ -5552,11 +5552,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) @@ -6248,7 +6248,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) @@ -6262,7 +6262,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) @@ -6336,7 +6336,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) @@ -6351,7 +6351,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) @@ -6668,7 +6668,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) @@ -6901,7 +6901,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) @@ -6925,7 +6925,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) @@ -7235,18 +7235,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 @@ -7259,11 +7259,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 @@ -7303,7 +7303,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 @@ -7317,17 +7317,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 @@ -7501,7 +7501,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) @@ -7533,9 +7533,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) @@ -7604,10 +7604,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 @@ -7862,7 +7862,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) @@ -7878,13 +7878,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) @@ -7914,7 +7914,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 *; @@ -7938,20 +7938,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*" @@ -7987,7 +7987,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) @@ -8005,19 +8005,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 2ce42ce3f1..17079ba27f 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -2253,6 +2253,7 @@ postgresPlanDirectModify(PlannerInfo *root, Relation rel; StringInfoData sql; ForeignScan *fscan; + List *update_tlist; List *targetAttrs = NIL; List *remote_exprs; List *params_list = NIL; @@ -2299,12 +2300,19 @@ postgresPlanDirectModify(PlannerInfo *root, /* * It's unsafe to update a foreign table directly, if any expressions to - * assign to the target columns are unsafe to evaluate remotely. + * assign to the target columns are unsafe to evaluate remotely. Note + * that we look those expressions up in the result relation's update_tlist, + * not the subplan's targetlist, because the latter cannot be indexed with + * target column attribute numbers, as the resnos of the TLEs contained in + * it don't match with the target columns' attribute numbers. */ + update_tlist = root->update_tlist; if (operation == CMD_UPDATE) { int col; + Assert(update_tlist != NIL); + /* * We transmit only columns that were explicitly targets of the * UPDATE, so as to avoid unnecessary data transmission. @@ -2319,10 +2327,10 @@ 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(update_tlist, attno); if (!tle) - elog(ERROR, "attribute number %d not found in subplan targetlist", + elog(ERROR, "attribute number %d not found in UPDATE targetlist", attno); if (!is_foreign_expr(root, foreignrel, (Expr *) tle->expr)) @@ -2379,7 +2387,7 @@ postgresPlanDirectModify(PlannerInfo *root, case CMD_UPDATE: deparseDirectUpdateSql(&sql, root, resultRelation, rel, foreignrel, - ((Plan *) fscan)->targetlist, + update_tlist, targetAttrs, remote_exprs, ¶ms_list, returningList, &retrieved_attrs); diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml index 854913ae5f..cba48750e6 100644 --- a/doc/src/sgml/fdwhandler.sgml +++ b/doc/src/sgml/fdwhandler.sgml @@ -705,9 +705,12 @@ ExecForeignUpdate(EState *estate, row-type definition of the foreign table. planSlot contains the tuple that was generated by the ModifyTable plan node's subplan; it differs from - slot in possibly containing additional junk - columns. In particular, any junk columns that were requested by - AddForeignUpdateTargets will be available from this slot. + slot in that it contains only the columns changed by + the query, so the individual columns in it are not addressable using + attribute numbers. Also, planSlot possibly contains + additional junk columns. In particular, any junk columns + that were requested by AddForeignUpdateTargets will + be available from this slot. diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 2d687f6dfb..0f006cbaa1 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -2725,20 +2725,22 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, /* * In READ COMMITTED isolation level it's possible that target tuple * was changed due to concurrent update. In that case we have a raw - * subplan output tuple in epqslot_candidate, and need to run it - * through the junk filter to produce an insertable tuple. + * subplan output tuple in epqslot_candidate, and need to form a new + * insertable tuple using ExecGetUpdateNewTuple to replace the one + * we received in passed-in slot ('newslot'). * * Caution: more than likely, the passed-in slot is the same as the - * junkfilter's output slot, so we are clobbering the original value - * of slottuple by doing the filtering. This is OK since neither we - * nor our caller have any more interest in the prior contents of that - * slot. + * slot previously returned by ExecGetUpdateNewTuple(), so we are + * clobbering the original value of that slot. This is OK since + * neither we nor our caller have any more interest in the prior + * contents of that slot. */ if (epqslot_candidate != NULL) { TupleTableSlot *epqslot_clean; - epqslot_clean = ExecFilterJunk(relinfo->ri_junkFilter, epqslot_candidate); + epqslot_clean = ExecGetUpdateNewTuple(relinfo, epqslot_candidate, + oldslot); if (newslot != epqslot_clean) ExecCopySlot(newslot, epqslot_clean); diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index f4dd47acc7..66ffd1adeb 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -1235,7 +1235,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 5d90337498..28fd1bc201 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -79,6 +79,8 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate, ResultRelInfo *targetRelInfo, TupleTableSlot *slot, ResultRelInfo **partRelInfo); +static TupleTableSlot *ExecGetInsertNewTuple(ResultRelInfo *relinfo, + TupleTableSlot *planSlot); /* * Verify that the tuples to be produced by INSERT or UPDATE match the @@ -1195,6 +1197,7 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo, ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot, TupleTableSlot *planSlot, + TupleTableSlot *oldSlot, EPQState *epqstate, bool canSetTag, TupleTableSlot **retry_slot, TupleTableSlot **inserted_tuple) @@ -1269,7 +1272,15 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate, return true; else { - *retry_slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot); + /* Fetch the most recent version of old tuple. */ + ExecClearTuple(oldSlot); + if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc, + tupleid, + SnapshotAny, + oldSlot)) + elog(ERROR, "failed to fetch tuple being updated"); + *retry_slot = ExecGetUpdateNewTuple(resultRelInfo, epqslot, + oldSlot); return false; } } @@ -1300,6 +1311,53 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate, return true; } +/* + * ExecGetInsertNewTuple + * This prepares a "new" tuple ready to be inserted into given result + * relation by removing any junk columns of the plan's output tuple + */ +static TupleTableSlot * +ExecGetInsertNewTuple(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); +} + + +/* + * ExecGetUpdateNewTuple + * This prepares a "new" tuple by combining an UPDATE plan's output + * tuple which contains values of changed columns and old tuple being + * updated from where values of unchanged columns are taken + * + * Also, this effectively filters out junk columns in the plan's output + * tuple. + */ +TupleTableSlot * +ExecGetUpdateNewTuple(ResultRelInfo *relinfo, + TupleTableSlot *planSlot, + TupleTableSlot *oldSlot) +{ + ProjectionInfo *newProj = relinfo->ri_projectNew; + ExprContext *econtext; + + Assert(newProj != NULL); + Assert(oldSlot != NULL && !TTS_EMPTY(oldSlot)); + + econtext = newProj->pi_exprContext; + econtext->ecxt_scantuple = oldSlot; + econtext->ecxt_outertuple = planSlot; + return ExecProject(newProj); +} + /* ---------------------------------------------------------------- * ExecUpdate * @@ -1319,6 +1377,8 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate, * foreign table triggers; it is NULL when the foreign table has * no relevant triggers. * + * oldSlot contains the old tuple being updated. + * * Returns RETURNING result if any, otherwise NULL. * ---------------------------------------------------------------- */ @@ -1329,6 +1389,7 @@ ExecUpdate(ModifyTableState *mtstate, HeapTuple oldtuple, TupleTableSlot *slot, TupleTableSlot *planSlot, + TupleTableSlot *oldSlot, EPQState *epqstate, EState *estate, bool canSetTag) @@ -1466,7 +1527,7 @@ lreplace:; */ retry = !ExecCrossPartitionUpdate(mtstate, resultRelInfo, tupleid, oldtuple, slot, planSlot, - epqstate, canSetTag, + oldSlot, epqstate, canSetTag, &retry_slot, &inserted_tuple); if (retry) { @@ -1578,7 +1639,15 @@ lreplace:; /* Tuple not passing quals anymore, exiting... */ return NULL; - slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot); + /* Fetch the most recent version of old tuple. */ + ExecClearTuple(oldSlot); + if (!table_tuple_fetch_row_version(resultRelationDesc, + tupleid, + SnapshotAny, + oldSlot)) + elog(ERROR, "failed to fetch tuple being updated"); + slot = ExecGetUpdateNewTuple(resultRelInfo, + epqslot, oldSlot); goto lreplace; case TM_Deleted: @@ -1874,7 +1943,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, /* Execute UPDATE with projection */ *returning = ExecUpdate(mtstate, resultRelInfo, conflictTid, NULL, resultRelInfo->ri_onConflict->oc_ProjSlot, - planSlot, + planSlot, existing, &mtstate->mt_epqstate, mtstate->ps.state, canSetTag); @@ -2051,7 +2120,6 @@ ExecModifyTable(PlanState *pstate) CmdType operation = node->operation; ResultRelInfo *resultRelInfo; PlanState *subplanstate; - JunkFilter *junkfilter; TupleTableSlot *slot; TupleTableSlot *planSlot; ItemPointer tupleid; @@ -2097,7 +2165,6 @@ ExecModifyTable(PlanState *pstate) /* Preload local variables */ resultRelInfo = node->resultRelInfo + node->mt_whichplan; subplanstate = node->mt_plans[node->mt_whichplan]; - junkfilter = resultRelInfo->ri_junkFilter; /* * Fetch rows from subplan(s), and execute the required table modification @@ -2131,7 +2198,6 @@ ExecModifyTable(PlanState *pstate) { resultRelInfo++; subplanstate = node->mt_plans[node->mt_whichplan]; - junkfilter = resultRelInfo->ri_junkFilter; EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan, node->mt_arowmarks[node->mt_whichplan]); continue; @@ -2173,87 +2239,111 @@ 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; + 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; - } - - /* - * 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); } switch (operation) { case CMD_INSERT: + slot = ExecGetInsertNewTuple(resultRelInfo, planSlot); slot = ExecInsert(node, resultRelInfo, slot, planSlot, estate, node->canSetTag); break; case CMD_UPDATE: - slot = ExecUpdate(node, resultRelInfo, tupleid, oldtuple, slot, - planSlot, &node->mt_epqstate, estate, - node->canSetTag); + { + TupleTableSlot *oldSlot = resultRelInfo->ri_oldTupleSlot; + + /* + * Make the new tuple by combining plan's output tuple + * with the old tuple being updated. + */ + ExecClearTuple(oldSlot); + if (oldtuple != NULL) + { + /* Foreign table update. */ + ExecForceStoreHeapTuple(oldtuple, oldSlot, false); + } + else + { + Relation relation = resultRelInfo->ri_RelationDesc; + + /* Fetch the most recent version of old tuple. */ + Assert(tupleid != NULL); + if (!table_tuple_fetch_row_version(relation, tupleid, + SnapshotAny, + oldSlot)) + elog(ERROR, "failed to fetch tuple being updated"); + } + slot = ExecGetUpdateNewTuple(resultRelInfo, planSlot, + oldSlot); + slot = ExecUpdate(node, resultRelInfo, tupleid, oldtuple, + slot, planSlot, oldSlot, + &node->mt_epqstate, estate, + node->canSetTag); + } break; case CMD_DELETE: slot = ExecDelete(node, resultRelInfo, tupleid, oldtuple, @@ -2679,117 +2769,133 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) mtstate->mt_arowmarks[0]); /* - * Initialize the junk filter(s) if needed. INSERT queries need a filter - * if there are any junk attrs in the tlist. UPDATE and DELETE always - * need a filter, since there's always at least one junk attribute present - * --- no need to look first. Typically, this will be a 'ctid' or - * 'wholerow' attribute, but in the case of a foreign data wrapper it - * might be a set of junk attributes sufficient to identify the remote - * row. + * Initialize projection to project tuples suitable for result relations. + * INSERT queries may need the projection to filter out any junk attrs in + * the tlist. UPDATE always needs the projection, because it produces + * tuples for a given result relation by combining the subplan tuple, + * which contains values for only the changed columns, with the old tuple + * fetched from the relation from which values for unchanged columns will + * be taken. * - * If there are multiple result relations, each one needs its own junk - * filter. Note multiple rels are only possible for UPDATE/DELETE, so we - * can't be fooled by some needing a filter and some not. + * If there are multiple result relations, each one needs its own + * projection. Note multiple rels are only possible for UPDATE/DELETE, so + * we can't be fooled by some needing a filter and some not. * * 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; + } + else + { + resultTargetList = (List *) list_nth(node->updateTargetLists, + i); + need_projection = true; + } + + /* + * The clean list must produce a tuple suitable for the result + * relation. + */ + ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, + resultTargetList); } - if (junk_filter_needed) + if (need_projection) { - resultRelInfo = mtstate->resultRelInfo; - for (i = 0; i < nplans; i++) - { - JunkFilter *j; - TupleTableSlot *junkresslot; + TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc); - subplan = mtstate->mt_plans[i]->plan; + /* + * 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); + } - junkresslot = - ExecInitExtraTupleSlot(estate, NULL, - table_slot_callbacks(resultRelInfo->ri_RelationDesc)); + /* + * For UPDATE/DELETE, find the appropriate junk attr now. Typically, + * this will be a 'ctid' or 'wholerow' attribute, but in the case of a + * foreign data wrapper it might be a set of junk attributes sufficient + * to identify the remote row. + */ + if (operation == CMD_UPDATE || operation == CMD_DELETE) + { + char relkind; + 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) + { /* - * For an INSERT or UPDATE, the result tuple must always match - * the target table's descriptor. For a DELETE, it won't - * (indeed, there's probably no non-junk output columns). + * When there is a row-level trigger, there should be + * a wholerow attribute. */ - if (operation == CMD_INSERT || operation == CMD_UPDATE) - { - ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc, - subplan->targetlist); - j = ExecInitJunkFilterInsertion(subplan->targetlist, - RelationGetDescr(resultRelInfo->ri_RelationDesc), - junkresslot); - } - else - j = ExecInitJunkFilter(subplan->targetlist, - junkresslot); - - if (operation == CMD_UPDATE || operation == CMD_DELETE) - { - /* For UPDATE/DELETE, find the appropriate junk attr now */ - char relkind; - - 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"); - } - } - - resultRelInfo->ri_junkFilter = j; - resultRelInfo++; + resultRelInfo->ri_junkAttNo = + ExecFindJunkAttributeInTlist(subplan->targetlist, + "wholerow"); + /* + * We require it to be present for updates to get the values + * of unchanged columns. + */ + 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 65bbc18ecb..268703ac3c 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -209,6 +209,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 f5dcedf6e8..cb8df1879a 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -410,6 +410,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); @@ -2135,6 +2136,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); @@ -2258,12 +2260,12 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_NODE_FIELD(distinct_pathkeys); WRITE_NODE_FIELD(sort_pathkeys); WRITE_NODE_FIELD(processed_tlist); + WRITE_NODE_FIELD(update_tlist); WRITE_NODE_FIELD(minmax_aggs); WRITE_FLOAT_FIELD(total_table_pages, "%.0f"); 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); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 4388aae71d..815878586b 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1684,6 +1684,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 6c8305c977..417b4b303d 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); @@ -2628,7 +2629,8 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path) ModifyTable *plan; List *subplans = NIL; ListCell *subpaths, - *subroots; + *subroots, + *lc; /* Build the plan for each input path */ forboth(subpaths, best_path->subpaths, @@ -2651,9 +2653,6 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path) */ subplan = create_plan_recurse(subroot, subpath, CP_EXACT_TLIST); - /* Transfer resname/resjunk labeling, too, to keep executor happy */ - apply_tlist_labeling(subplan->targetlist, subroot->processed_tlist); - subplans = lappend(subplans, subplan); } @@ -2668,12 +2667,48 @@ 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); copy_generic_path_info(&plan->plan, &best_path->path); + forboth(lc, subplans, + subroots, best_path->subroots) + { + Plan *subplan = (Plan *) lfirst(lc); + PlannerInfo *subroot = (PlannerInfo *) lfirst(subroots); + + /* + * Fix up the resnos of query's TLEs to make them match their ordinal + * position in the list, which they may not in the case of an UPDATE. + * It's safe to revise that targetlist now, because nothing after this + * point needs those resnos to match target relation's attribute + * numbers. + * XXX - we do this simply because apply_tlist_labeling() asserts that + * resnos in processed_tlist and resnos in subplan targetlist are + * exactly same, but maybe we can just remove the assert? + */ + if (plan->operation == CMD_UPDATE) + { + ListCell *l; + AttrNumber resno = 1; + + foreach(l, subroot->processed_tlist) + { + TargetEntry *tle = lfirst(l); + + tle = flatCopyTargetEntry(tle); + tle->resno = resno++; + lfirst(l) = tle; + } + } + + /* Transfer resname/resjunk labeling, too, to keep executor happy */ + apply_tlist_labeling(subplan->targetlist, subroot->processed_tlist); + } + return plan; } @@ -6783,6 +6818,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); @@ -6792,12 +6828,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; @@ -6840,6 +6877,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 adf68d8790..771b1f78d9 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -620,6 +620,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, memset(root->upper_rels, 0, sizeof(root->upper_rels)); memset(root->upper_targets, 0, sizeof(root->upper_targets)); root->processed_tlist = NIL; + root->update_tlist = NIL; root->grouping_map = NULL; root->minmax_aggs = NIL; root->qual_security_level = 0; @@ -1225,6 +1226,7 @@ inheritance_planner(PlannerInfo *root) List *withCheckOptionLists = NIL; List *returningLists = NIL; List *rowMarks; + List *updateTargetLists = NIL; RelOptInfo *final_rel; ListCell *lc; ListCell *lc2; @@ -1695,6 +1697,12 @@ inheritance_planner(PlannerInfo *root) returningLists = lappend(returningLists, subroot->parse->returningList); + if (parse->commandType == CMD_UPDATE) + { + Assert(subroot->update_tlist != NIL); + updateTargetLists = lappend(updateTargetLists, + subroot->update_tlist); + } Assert(!parse->onConflict); } @@ -1736,6 +1744,13 @@ 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) + { + Assert(root->update_tlist != NIL); + updateTargetLists = lappend(updateTargetLists, + root->update_tlist); + } /* Disable tuple routing, too, just to be safe */ root->partColsUpdated = false; } @@ -1788,6 +1803,7 @@ inheritance_planner(PlannerInfo *root) resultRelations, subpaths, subroots, + updateTargetLists, withCheckOptionLists, returningLists, rowMarks, @@ -2361,6 +2377,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, list_make1_int(parse->resultRelation), list_make1(path), list_make1(root), + list_make1(root->update_tlist), withCheckOptionLists, returningLists, rowMarks, diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index c3c36be13e..68fc46b7f5 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -172,6 +172,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); /***************************************************************************** @@ -873,6 +876,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, 1); @@ -2873,6 +2879,46 @@ 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, splan->plans) + { + Index resultRelation = lfirst_int(lc1); + List *update_tlist = lfirst(lc2); + Plan *subplan = lfirst(lc3); + indexed_tlist *itlist; + + /* + * Abusing the fix_join_expr machinery here too. We search + * update_tlist to find expressions appearing in subplan's targetlist + * and convert them into references to the matching expressions in the + * latter. Any result relation Vars found are left as-is and are + * computed by referring to the old tuple. + */ + itlist = build_tlist_index(subplan->targetlist); + lfirst(lc2) = fix_join_expr(root, + update_tlist, + itlist, + NULL, + resultRelation, + rtoffset, + NUM_EXEC_TLIST(subplan)); + } +} + /***************************************************************************** * QUERY DEPENDENCY MANAGEMENT diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 23f9f861f4..1b3dd37b5d 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -3,13 +3,18 @@ * preptlist.c * Routines to preprocess the parse tree target list * - * For INSERT and UPDATE queries, the targetlist must contain an entry for - * each attribute of the target relation in the correct order. For UPDATE and - * DELETE queries, it must also contain junk tlist entries needed to allow the - * executor to identify the rows to be updated or deleted. For all query - * types, we may need to add junk tlist entries for Vars used in the RETURNING - * list and row ID information needed for SELECT FOR UPDATE locking and/or - * EvalPlanQual checking. + * For INSERT and UPDATE, the targetlist must contain an entry for each + * attribute of the target relation in the correct order. For INSERT, we + * modify the query's main targetlist to satisfy that requirement, whereas + * in UPDATE's case, we maintain another targetlist that will be computed + * separately from the query's main targetlist, leaving the latter to produce + * only values for the updated columns as before. + * + * For UPDATE and DELETE queries, the main targetlist must also contain junk + * tlist entries needed to allow the executor to identify the rows to be + * updated or deleted. For all query types, we may need to add junk tlist + * entries for Vars used in the RETURNING list and row ID information needed + * for SELECT FOR UPDATE locking and/or EvalPlanQual checking. * * The query rewrite phase also does preprocessing of the targetlist (see * rewriteTargetListIU). The division of labor between here and there is @@ -52,10 +57,10 @@ #include "rewrite/rewriteHandler.h" #include "utils/rel.h" +static List *make_update_tlist(List *tlist, Index result_relation, Relation rel); static List *expand_targetlist(List *tlist, int command_type, Index result_relation, Relation rel); - /* * preprocess_targetlist * Driver for preprocessing the parse tree targetlist. @@ -111,11 +116,20 @@ 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 INSERT, we fix the the main targetlist itself to produce the full + * tuple. For UPDATE, we maintain another targetlist that is computed + * separately from the main targetlist to get the full tuple, where any + * missing values are taken from the old tuple. */ 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); + else if (command_type == CMD_UPDATE) + root->update_tlist = make_update_tlist(tlist, + result_relation, + target_relation); /* * Add necessary junk columns for rowmarked rels. These values are needed @@ -240,6 +254,21 @@ preprocess_targetlist(PlannerInfo *root) } +/* + * make_update_tlist + * Wrapper over expand_targetlist for generating the expanded tlist + * for an update target relation + * + * The returned list does not contain any junk entries, only those for + * target relation attributes. + */ +static List * +make_update_tlist(List *tlist, Index result_relation, Relation rel) +{ + return filter_junk_tlist_entries(expand_targetlist(tlist, CMD_UPDATE, + result_relation, rel)); +} + /***************************************************************************** * * TARGETLIST EXPANSION diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 9be0c4a6af..bb9b0243bf 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -3519,6 +3519,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 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) @@ -3531,7 +3532,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) @@ -3604,6 +3605,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/tlist.c b/src/backend/optimizer/util/tlist.c index 89853a0630..2bfe8684b6 100644 --- a/src/backend/optimizer/util/tlist.c +++ b/src/backend/optimizer/util/tlist.c @@ -215,6 +215,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 0672f497c6..3da9f6f72d 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -1659,17 +1659,19 @@ 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. + * For UPDATE, we need to let an FDW fetch unchanged columns by + * asking it to fetch the wholerow. That's because the top-level + * targetlist only contains entries for changed columns. Actually, + * we only really need this for UPDATEs that are not pushed to the + * remote side, it's hard to tell if that's the case at the point + * when this function is called. + * + * We will need it also if there are any row triggers. */ - 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 758c3ca097..26af37eb28 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -617,4 +617,9 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd); extern void CheckSubscriptionRelkind(char relkind, const char *nspname, const char *relname); +/* needed by trigger.c */ +extern TupleTableSlot *ExecGetUpdateNewTuple(ResultRelInfo *relinfo, + TupleTableSlot *planSlot, + TupleTableSlot *oldSlot); + #endif /* EXECUTOR_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index d65099c94a..ba72c882aa 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -356,10 +356,6 @@ typedef struct ProjectionInfo * attribute numbers of the "original" tuple and the * attribute numbers of the "clean" tuple. * resultSlot: tuple slot used to hold cleaned tuple. - * junkAttNo: not used by junkfilter code. Can be used by caller - * to remember the attno of a specific junk attribute - * (nodeModifyTable.c keeps the "ctid" or "wholerow" - * attno here). * ---------------- */ typedef struct JunkFilter @@ -369,7 +365,6 @@ typedef struct JunkFilter TupleDesc jf_cleanTupType; AttrNumber *jf_cleanMap; TupleTableSlot *jf_resultSlot; - AttrNumber jf_junkAttNo; } JunkFilter; /* @@ -467,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; @@ -506,6 +498,22 @@ typedef struct ResultRelInfo /* for use by copyfrom.c when performing multi-inserts */ struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer; + + /* + * For UPDATE and DELETE result relations, this stores the attribute + * number of the junk attribute in the source plan's output tuple. + */ + AttrNumber ri_junkAttNo; + + /* Slot to hold the old tuple being updated */ + TupleTableSlot *ri_oldTupleSlot; + + /* + * Projection to generate new tuple of an UPDATE/INSERT and slot to hold + * that tuple + */ + TupleTableSlot *ri_newTupleSlot; + ProjectionInfo *ri_projectNew; } ResultRelInfo; /* ---------------- diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 0ec93e648c..9bc1959332 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -309,15 +309,22 @@ struct PlannerInfo /* * The fully-processed targetlist is kept here. It differs from - * parse->targetList in that (for INSERT and UPDATE) it's been reordered - * to match the target table, and defaults have been filled in. Also, - * additional resjunk targets may be present. preprocess_targetlist() + * parse->targetList in that (for INSERT) it's been reordered to match + * the target table, and defaults have been filled in. Also, additional + * resjunk targets may be present. preprocess_targetlist() does most of * does most of this work, but note that more resjunk targets can get * added during appendrel expansion. (Hence, upper_targets mustn't get * set up till after that.) */ List *processed_tlist; + /* + * For UPDATE, this contains the targetlist to compute the updated tuple, + * wherein the updated columns refer to the plan's output tuple (given by + * by 'processed_tlist') and the rest to the old tuple being updated. + */ + List *update_tlist; + /* Fields filled during create_plan() for use in setrefs.c */ AttrNumber *grouping_map; /* for GroupingFunc fixup */ List *minmax_aggs; /* List of MinMaxAggInfos */ @@ -1821,6 +1828,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 UPDATE tlists */ List *withCheckOptionLists; /* per-target-table WCO lists */ List *returningLists; /* per-target-table RETURNING tlists */ List *rowMarks; /* PlanRowMarks (non-locking only) */ diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 43160439f0..583294afb7 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -221,6 +221,7 @@ typedef struct ModifyTable List *plans; /* plan(s) producing source data */ List *withCheckOptionLists; /* per-target-table WCO lists */ List *returningLists; /* per-target-table RETURNING tlists */ + List *updateTargetLists; /* per-target-table UPDATE 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 d587952b7d..3b6fc90313 100644 --- a/src/include/optimizer/optimizer.h +++ b/src/include/optimizer/optimizer.h @@ -164,6 +164,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 8dfc36a4e1..0f2a74cb46 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -260,7 +260,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/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index 2b68aef654..94e43c3410 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 24905332b1..770eab38b5 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 *; @@ -2340,7 +2340,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 (SubPlan 1) AND snoop(t1.a) AND leakproof(t1.a)) SubPlan 1 @@ -2350,15 +2350,15 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; -> Seq Scan on public.t111 t12_2 Filter: (t12_2.a = t1.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 (SubPlan 1) 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 (SubPlan 1) 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 (SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a)) (27 rows) @@ -2376,15 +2376,15 @@ SELECT * FROM t1 WHERE a=100; -- Nothing should have been changed to 100 EXPLAIN (VERBOSE, COSTS OFF) UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; - QUERY PLAN -------------------------------------------------------------------------- + QUERY PLAN +----------------------------------------------------------------------- Update on public.t1 Update on public.t1 Update on public.t11 t1_1 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: ((SubPlan 1) AND snoop(t1.a) AND leakproof(t1.a)) SubPlan 1 @@ -2394,15 +2394,15 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; -> Seq Scan on public.t111 t12_2 Filter: (t12_2.a = t1.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: ((SubPlan 1) 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: ((SubPlan 1) 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: ((SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a)) (27 rows) diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out index bf939d79f6..dece036069 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 -- 2.24.1