Rework planning and execution of UPDATE and DELETE.

This patch makes two closely related sets of changes:

1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns.  The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree.  The disadvantage of course
is an extra fetch of each tuple to be updated.  However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost.  At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.

2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that.  To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information.  This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.

Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy.  Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD.  (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)

There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.

FDW authors should note several API changes:

* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query.  Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.

* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable.  See postgres_fdw for sample code.

* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.

Amit Langote and Tom Lane

Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
This commit is contained in:
Tom Lane 2021-03-31 11:52:34 -04:00
parent 055fee7eb4
commit 86dc90056d
55 changed files with 2353 additions and 2199 deletions

View File

@ -1275,7 +1275,7 @@ deparseLockingClause(deparse_expr_cxt *context)
* that DECLARE CURSOR ... FOR UPDATE is supported, which it isn't
* before 8.3.
*/
if (relid == root->parse->resultRelation &&
if (bms_is_member(relid, root->all_result_relids) &&
(root->parse->commandType == CMD_UPDATE ||
root->parse->commandType == CMD_DELETE))
{
@ -1867,6 +1867,7 @@ deparseUpdateSql(StringInfo buf, RangeTblEntry *rte,
* '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
* (note that this only contains new-value expressions and junk attrs)
* '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
@ -1888,8 +1889,9 @@ deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root,
deparse_expr_cxt context;
int nestlevel;
bool first;
ListCell *lc;
RangeTblEntry *rte = planner_rt_fetch(rtindex, root);
ListCell *lc,
*lc2;
/* Set up context struct for recursion */
context.root = root;
@ -1908,14 +1910,13 @@ deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root,
nestlevel = set_transmission_modes();
first = true;
foreach(lc, targetAttrs)
forboth(lc, targetlist, lc2, targetAttrs)
{
int attnum = lfirst_int(lc);
TargetEntry *tle = get_tle_by_resno(targetlist, attnum);
TargetEntry *tle = lfirst_node(TargetEntry, lc);
int attnum = lfirst_int(lc2);
if (!tle)
elog(ERROR, "attribute number %d not found in UPDATE targetlist",
attnum);
/* update's new-value expressions shouldn't be resjunk */
Assert(!tle->resjunk);
if (!first)
appendStringInfoString(buf, ", ");

View File

@ -5503,13 +5503,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
@ -5539,9 +5539,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 *;
@ -5570,11 +5570,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)
@ -6266,7 +6266,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)
@ -6280,7 +6280,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)
@ -6348,13 +6348,13 @@ SELECT * FROM foreign_tbl;
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE rw_view SET b = b + 5;
QUERY PLAN
----------------------------------------------------------------------------------------
QUERY PLAN
------------------------------------------------------------------------------------------------
Update on public.parent_tbl
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.tableoid, parent_tbl_1.ctid, parent_tbl_1.*
Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
(6 rows)
@ -6363,13 +6363,13 @@ ERROR: new row violates check option for view "rw_view"
DETAIL: Failing row contains (20, 20).
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE rw_view SET b = b + 15;
QUERY PLAN
----------------------------------------------------------------------------------------
QUERY PLAN
-------------------------------------------------------------------------------------------------
Update on public.parent_tbl
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.tableoid, parent_tbl_1.ctid, parent_tbl_1.*
Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
(6 rows)
@ -6686,7 +6686,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)
@ -6919,7 +6919,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)
@ -6943,7 +6943,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)
@ -7253,18 +7253,22 @@ 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
Update on public.bar bar_1
Foreign Update on public.bar2 bar_2
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), foo.ctid, bar.tableoid, bar.ctid, (NULL::record), foo.*, foo.tableoid
Inner Unique: true
Hash Cond: (bar.f1 = foo.f1)
-> Seq Scan on public.bar
Output: bar.f1, bar.f2, bar.ctid
-> Append
-> Seq Scan on public.bar bar_1
Output: bar_1.f2, bar_1.f1, bar_1.tableoid, bar_1.ctid, NULL::record
-> Foreign Scan on public.bar2 bar_2
Output: bar_2.f2, bar_2.f1, bar_2.tableoid, bar_2.ctid, bar_2.*
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
-> Hash
Output: foo.ctid, foo.f1, foo.*, foo.tableoid
-> HashAggregate
@ -7276,25 +7280,7 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
-> Foreign Scan on public.foo2 foo_2
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
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
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
-> Hash
Output: foo.ctid, foo.f1, foo.*, foo.tableoid
-> HashAggregate
Output: foo.ctid, foo.f1, foo.*, foo.tableoid
Group Key: foo.f1
-> Append
-> Seq Scan on public.foo foo_1
Output: foo_1.ctid, foo_1.f1, foo_1.*, foo_1.tableoid
-> Foreign Scan on public.foo2 foo_2
Output: foo_2.ctid, foo_2.f1, foo_2.*, foo_2.tableoid
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
(39 rows)
(25 rows)
update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
select tableoid::regclass, * from bar order by 1,2;
@ -7314,39 +7300,24 @@ update bar set f2 = f2 + 100
from
( select f1 from foo union all select f1+3 from foo ) ss
where bar.f1 = ss.f1;
QUERY PLAN
--------------------------------------------------------------------------------------
QUERY PLAN
------------------------------------------------------------------------------------------------
Update on public.bar
Update on public.bar
Foreign Update on public.bar2 bar_1
Update on public.bar bar_1
Foreign Update on public.bar2 bar_2
Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
-> Hash Join
Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
Hash Cond: (foo.f1 = bar.f1)
-> Append
-> Seq Scan on public.foo
Output: ROW(foo.f1), foo.f1
-> Foreign Scan on public.foo2 foo_1
Output: ROW(foo_1.f1), foo_1.f1
Remote SQL: SELECT f1 FROM public.loct1
-> Seq Scan on public.foo foo_2
Output: ROW((foo_2.f1 + 3)), (foo_2.f1 + 3)
-> Foreign Scan on public.foo2 foo_3
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
-> Seq Scan on public.bar
Output: bar.f1, bar.f2, bar.ctid
-> Merge Join
Output: bar_1.f1, (bar_1.f2 + 100), bar_1.f3, bar_1.ctid, (ROW(foo.f1))
Merge Cond: (bar_1.f1 = foo.f1)
Output: (bar.f2 + 100), (ROW(foo.f1)), bar.tableoid, bar.ctid, (NULL::record)
Merge Cond: (bar.f1 = foo.f1)
-> Sort
Output: bar_1.f1, bar_1.f2, bar_1.f3, bar_1.ctid
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
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
Output: bar.f2, bar.f1, bar.tableoid, bar.ctid, (NULL::record)
Sort Key: bar.f1
-> Append
-> Seq Scan on public.bar bar_1
Output: bar_1.f2, bar_1.f1, bar_1.tableoid, bar_1.ctid, NULL::record
-> Foreign Scan on public.bar2 bar_2
Output: bar_2.f2, bar_2.f1, bar_2.tableoid, bar_2.ctid, bar_2.*
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
-> Sort
Output: (ROW(foo.f1)), foo.f1
Sort Key: foo.f1
@ -7361,7 +7332,7 @@ where bar.f1 = ss.f1;
-> Foreign Scan on public.foo2 foo_3
Output: ROW((foo_3.f1 + 3)), (foo_3.f1 + 3)
Remote SQL: SELECT f1 FROM public.loct1
(45 rows)
(30 rows)
update bar set f2 = f2 + 100
from
@ -7487,18 +7458,19 @@ ERROR: WHERE CURRENT OF is not supported for this table type
rollback;
explain (verbose, costs off)
delete from foo where f1 < 5 returning *;
QUERY PLAN
--------------------------------------------------------------------------------
QUERY PLAN
--------------------------------------------------------------------------------------
Delete on public.foo
Output: foo.f1, foo.f2
Delete on public.foo
Foreign Delete on public.foo2 foo_1
-> Index Scan using i_foo_f1 on public.foo
Output: foo.ctid
Index Cond: (foo.f1 < 5)
-> Foreign Delete on public.foo2 foo_1
Remote SQL: DELETE FROM public.loct1 WHERE ((f1 < 5)) RETURNING f1, f2
(9 rows)
Output: foo_1.f1, foo_1.f2
Delete on public.foo foo_1
Foreign Delete on public.foo2 foo_2
-> Append
-> Index Scan using i_foo_f1 on public.foo foo_1
Output: foo_1.tableoid, foo_1.ctid
Index Cond: (foo_1.f1 < 5)
-> Foreign Delete on public.foo2 foo_2
Remote SQL: DELETE FROM public.loct1 WHERE ((f1 < 5)) RETURNING f1, f2
(10 rows)
delete from foo where f1 < 5 returning *;
f1 | f2
@ -7512,17 +7484,20 @@ delete from foo where f1 < 5 returning *;
explain (verbose, costs off)
update bar set f2 = f2 + 100 returning *;
QUERY PLAN
------------------------------------------------------------------------------
QUERY PLAN
------------------------------------------------------------------------------------------
Update on public.bar
Output: bar.f1, bar.f2
Update on public.bar
Foreign Update on public.bar2 bar_1
-> Seq Scan on public.bar
Output: bar.f1, (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)
Output: bar_1.f1, bar_1.f2
Update on public.bar bar_1
Foreign Update on public.bar2 bar_2
-> Result
Output: (bar.f2 + 100), bar.tableoid, bar.ctid, (NULL::record)
-> Append
-> Seq Scan on public.bar bar_1
Output: bar_1.f2, bar_1.tableoid, bar_1.ctid, NULL::record
-> Foreign Update on public.bar2 bar_2
Remote SQL: UPDATE public.loct2 SET f2 = (f2 + 100) RETURNING f1, f2
(11 rows)
update bar set f2 = f2 + 100 returning *;
f1 | f2
@ -7547,15 +7522,18 @@ update bar set f2 = f2 + 100;
QUERY PLAN
--------------------------------------------------------------------------------------------------------
Update on public.bar
Update on public.bar
Foreign Update on public.bar2 bar_1
Update on public.bar bar_1
Foreign Update on public.bar2 bar_2
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
-> Foreign Scan on public.bar2 bar_1
Output: bar_1.f1, (bar_1.f2 + 100), bar_1.f3, bar_1.ctid, bar_1.*
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
(9 rows)
-> Result
Output: (bar.f2 + 100), bar.tableoid, bar.ctid, (NULL::record)
-> Append
-> Seq Scan on public.bar bar_1
Output: bar_1.f2, bar_1.tableoid, bar_1.ctid, NULL::record
-> Foreign Scan on public.bar2 bar_2
Output: bar_2.f2, bar_2.tableoid, bar_2.ctid, bar_2.*
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
(12 rows)
update bar set f2 = f2 + 100;
NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON bar2
@ -7572,19 +7550,20 @@ NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON bar2
NOTICE: OLD: (7,277,77),NEW: (7,377,77)
explain (verbose, costs off)
delete from bar where f2 < 400;
QUERY PLAN
---------------------------------------------------------------------------------------------
QUERY PLAN
---------------------------------------------------------------------------------------------------
Delete on public.bar
Delete on public.bar
Foreign Delete on public.bar2 bar_1
Delete on public.bar bar_1
Foreign Delete on public.bar2 bar_2
Remote SQL: DELETE FROM public.loct2 WHERE ctid = $1 RETURNING f1, f2, f3
-> Seq Scan on public.bar
Output: bar.ctid
Filter: (bar.f2 < 400)
-> Foreign Scan on public.bar2 bar_1
Output: bar_1.ctid, bar_1.*
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 WHERE ((f2 < 400)) FOR UPDATE
(10 rows)
-> Append
-> Seq Scan on public.bar bar_1
Output: bar_1.tableoid, bar_1.ctid, NULL::record
Filter: (bar_1.f2 < 400)
-> Foreign Scan on public.bar2 bar_2
Output: bar_2.tableoid, bar_2.ctid, bar_2.*
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 WHERE ((f2 < 400)) FOR UPDATE
(11 rows)
delete from bar where f2 < 400;
NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON bar2
@ -7615,23 +7594,28 @@ analyze remt1;
analyze remt2;
explain (verbose, costs off)
update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------
QUERY PLAN
----------------------------------------------------------------------------------------------------------------
Update on public.parent
Output: parent.a, parent.b, remt2.a, remt2.b
Update on public.parent
Foreign Update on public.remt1 parent_1
Output: parent_1.a, parent_1.b, remt2.a, remt2.b
Update on public.parent parent_1
Foreign Update on public.remt1 parent_2
Remote SQL: UPDATE public.loct1 SET b = $2 WHERE ctid = $1 RETURNING a, b
-> Nested Loop
Output: parent.a, (parent.b || remt2.b), parent.ctid, remt2.*, remt2.a, remt2.b
Output: (parent.b || remt2.b), remt2.*, remt2.a, remt2.b, parent.tableoid, parent.ctid, (NULL::record)
Join Filter: (parent.a = remt2.a)
-> Seq Scan on public.parent
Output: parent.a, parent.b, parent.ctid
-> Foreign Scan on public.remt2
-> Append
-> Seq Scan on public.parent parent_1
Output: parent_1.b, parent_1.a, parent_1.tableoid, parent_1.ctid, NULL::record
-> Foreign Scan on public.remt1 parent_2
Output: parent_2.b, parent_2.a, parent_2.tableoid, parent_2.ctid, parent_2.*
Remote SQL: SELECT a, b, ctid FROM public.loct1 FOR UPDATE
-> Materialize
Output: remt2.b, remt2.*, remt2.a
Remote SQL: SELECT a, b FROM public.loct2
-> Foreign Update
Remote SQL: UPDATE public.loct1 r4 SET b = (r4.b || r2.b) FROM public.loct2 r2 WHERE ((r4.a = r2.a)) RETURNING r4.a, r4.b, r2.a, r2.b
(14 rows)
-> Foreign Scan on public.remt2
Output: remt2.b, remt2.*, remt2.a
Remote SQL: SELECT a, b FROM public.loct2
(19 rows)
update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *;
a | b | a | b
@ -7642,23 +7626,28 @@ update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a re
explain (verbose, costs off)
delete from parent using remt2 where parent.a = remt2.a returning parent;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------
QUERY PLAN
-----------------------------------------------------------------------------
Delete on public.parent
Output: parent.*
Delete on public.parent
Foreign Delete on public.remt1 parent_1
Output: parent_1.*
Delete on public.parent parent_1
Foreign Delete on public.remt1 parent_2
Remote SQL: DELETE FROM public.loct1 WHERE ctid = $1 RETURNING a, b
-> Nested Loop
Output: parent.ctid, remt2.*
Output: remt2.*, parent.tableoid, parent.ctid
Join Filter: (parent.a = remt2.a)
-> Seq Scan on public.parent
Output: parent.ctid, parent.a
-> Foreign Scan on public.remt2
-> Append
-> Seq Scan on public.parent parent_1
Output: parent_1.a, parent_1.tableoid, parent_1.ctid
-> Foreign Scan on public.remt1 parent_2
Output: parent_2.a, parent_2.tableoid, parent_2.ctid
Remote SQL: SELECT a, ctid FROM public.loct1 FOR UPDATE
-> Materialize
Output: remt2.*, remt2.a
Remote SQL: SELECT a, b FROM public.loct2
-> Foreign Delete
Remote SQL: DELETE FROM public.loct1 r4 USING public.loct2 r2 WHERE ((r4.a = r2.a)) RETURNING r4.a, r4.b
(14 rows)
-> Foreign Scan on public.remt2
Output: remt2.*, remt2.a
Remote SQL: SELECT a, b FROM public.loct2
(19 rows)
delete from parent using remt2 where parent.a = remt2.a returning parent;
parent
@ -7837,29 +7826,25 @@ DETAIL: Failing row contains (2, foo).
CONTEXT: remote SQL command: UPDATE public.loct SET a = 2 WHERE ((b = 'foo'::text)) RETURNING a, b
-- But the reverse is allowed
update utrtest set a = 1 where b = 'qux' returning *;
a | b
---+-----
1 | qux
(1 row)
ERROR: cannot route tuples into foreign table to be updated "remp"
select tableoid::regclass, * FROM utrtest;
tableoid | a | b
----------+---+-----
remp | 1 | foo
remp | 1 | qux
locp | 2 | qux
(2 rows)
select tableoid::regclass, * FROM remp;
tableoid | a | b
----------+---+-----
remp | 1 | foo
remp | 1 | qux
(2 rows)
(1 row)
select tableoid::regclass, * FROM locp;
tableoid | a | b
----------+---+---
(0 rows)
tableoid | a | b
----------+---+-----
locp | 2 | qux
(1 row)
-- The executor should not let unexercised FDWs shut down
update utrtest set a = 1 where b = 'foo';
@ -7871,38 +7856,35 @@ insert into utrtest values (2, 'qux');
-- Check case where the foreign partition is a subplan target rel
explain (verbose, costs off)
update utrtest set a = 1 where a = 1 or a = 2 returning *;
QUERY PLAN
----------------------------------------------------------------------------------------------
QUERY PLAN
----------------------------------------------------------------------------------------------------
Update on public.utrtest
Output: utrtest_1.a, utrtest_1.b
Foreign Update on public.remp utrtest_1
Update on public.locp utrtest_2
-> 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
Filter: ((utrtest_2.a = 1) OR (utrtest_2.a = 2))
(9 rows)
-> Append
-> 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.tableoid, utrtest_2.ctid, NULL::record
Filter: ((utrtest_2.a = 1) OR (utrtest_2.a = 2))
(10 rows)
-- The new values are concatenated with ' triggered !'
update utrtest set a = 1 where a = 1 or a = 2 returning *;
a | b
---+-----------------
1 | qux triggered !
(1 row)
ERROR: cannot route tuples into foreign table to be updated "remp"
delete from utrtest;
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.tableoid, utrtest_1.ctid
Filter: (utrtest_1.a = 2)
(6 rows)
@ -7923,66 +7905,51 @@ insert into utrtest values (2, 'qux');
-- with a direct modification plan
explain (verbose, costs off)
update utrtest set a = 1 returning *;
QUERY PLAN
-----------------------------------------------------------------
QUERY PLAN
---------------------------------------------------------------------------
Update on public.utrtest
Output: utrtest_1.a, utrtest_1.b
Foreign Update on public.remp utrtest_1
Update on public.locp utrtest_2
-> 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
(8 rows)
-> Append
-> 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.tableoid, utrtest_2.ctid, NULL::record
(9 rows)
update utrtest set a = 1 returning *;
a | b
---+-----
1 | foo
1 | qux
(2 rows)
ERROR: cannot route tuples into foreign table to be updated "remp"
delete from utrtest;
insert into utrtest values (1, 'foo');
insert into utrtest values (2, 'qux');
-- with a non-direct modification plan
explain (verbose, costs off)
update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *;
QUERY PLAN
----------------------------------------------------------------------------------
QUERY PLAN
------------------------------------------------------------------------------------------------
Update on public.utrtest
Output: utrtest_1.a, utrtest_1.b, "*VALUES*".column1
Foreign Update on public.remp utrtest_1
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
Hash Cond: (utrtest_1.a = "*VALUES*".column1)
-> Foreign Scan on public.remp utrtest_1
Output: utrtest_1.b, utrtest_1.ctid, utrtest_1.a
Remote SQL: SELECT a, b, ctid FROM public.loct FOR UPDATE
Output: 1, "*VALUES*".*, "*VALUES*".column1, utrtest.tableoid, utrtest.ctid, utrtest.*
Hash Cond: (utrtest.a = "*VALUES*".column1)
-> Append
-> Foreign Scan on public.remp utrtest_1
Output: utrtest_1.a, utrtest_1.tableoid, utrtest_1.ctid, utrtest_1.*
Remote SQL: SELECT a, b, ctid FROM public.loct FOR UPDATE
-> Seq Scan on public.locp utrtest_2
Output: utrtest_2.a, utrtest_2.tableoid, utrtest_2.ctid, NULL::record
-> 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
Hash Cond: (utrtest_2.a = "*VALUES*".column1)
-> Seq Scan on public.locp utrtest_2
Output: utrtest_2.b, utrtest_2.ctid, utrtest_2.a
-> Hash
Output: "*VALUES*".*, "*VALUES*".column1
-> Values Scan on "*VALUES*"
Output: "*VALUES*".*, "*VALUES*".column1
(24 rows)
(18 rows)
update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *;
a | b | x
---+-----+---
1 | foo | 1
1 | qux | 2
(2 rows)
ERROR: cannot route tuples into foreign table to be updated "remp"
-- Change the definition of utrtest so that the foreign partition get updated
-- after the local partition
delete from utrtest;
@ -7998,50 +7965,45 @@ insert into utrtest values (3, 'xyzzy');
-- with a direct modification plan
explain (verbose, costs off)
update utrtest set a = 3 returning *;
QUERY PLAN
-----------------------------------------------------------------
QUERY PLAN
---------------------------------------------------------------------------
Update on public.utrtest
Output: utrtest_1.a, utrtest_1.b
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
-> Foreign Update on public.remp utrtest_2
Remote SQL: UPDATE public.loct SET a = 3 RETURNING a, b
(8 rows)
-> Append
-> Seq Scan on public.locp utrtest_1
Output: 3, utrtest_1.tableoid, utrtest_1.ctid, NULL::record
-> Foreign Update on public.remp utrtest_2
Remote SQL: UPDATE public.loct SET a = 3 RETURNING a, b
(9 rows)
update utrtest set a = 3 returning *; -- ERROR
ERROR: cannot route tuples into foreign table to be updated "remp"
-- with a non-direct modification plan
explain (verbose, costs off)
update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *;
QUERY PLAN
----------------------------------------------------------------------------------
QUERY PLAN
-----------------------------------------------------------------------------------------------------
Update on public.utrtest
Output: utrtest_1.a, utrtest_1.b, "*VALUES*".column1
Update on public.locp utrtest_1
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
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: 3, "*VALUES*".*, "*VALUES*".column1, utrtest.tableoid, utrtest.ctid, (NULL::record)
Hash Cond: (utrtest.a = "*VALUES*".column1)
-> Append
-> Seq Scan on public.locp utrtest_1
Output: utrtest_1.a, utrtest_1.tableoid, utrtest_1.ctid, NULL::record
-> Foreign Scan on public.remp utrtest_2
Output: utrtest_2.a, utrtest_2.tableoid, utrtest_2.ctid, utrtest_2.*
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: 3, utrtest_2.b, utrtest_2.ctid, "*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
Remote SQL: SELECT a, b, ctid FROM public.loct FOR UPDATE
-> Hash
Output: "*VALUES*".*, "*VALUES*".column1
-> Values Scan on "*VALUES*"
Output: "*VALUES*".*, "*VALUES*".column1
(24 rows)
(18 rows)
update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *; -- ERROR
ERROR: cannot route tuples into foreign table to be updated "remp"
@ -9428,11 +9390,12 @@ CREATE TABLE batch_cp_up_test1 PARTITION OF batch_cp_upd_test
INSERT INTO batch_cp_upd_test VALUES (1), (2);
-- The following moves a row from the local partition to the foreign one
UPDATE batch_cp_upd_test t SET a = 1 FROM (VALUES (1), (2)) s(a) WHERE t.a = s.a;
ERROR: cannot route tuples into foreign table to be updated "batch_cp_upd_test1_f"
SELECT tableoid::regclass, * FROM batch_cp_upd_test;
tableoid | a
----------------------+---
batch_cp_upd_test1_f | 1
batch_cp_upd_test1_f | 1
batch_cp_up_test1 | 2
(2 rows)
-- Clean up

View File

@ -27,6 +27,7 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/appendinfo.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/optimizer.h"
@ -345,7 +346,8 @@ static void postgresBeginForeignScan(ForeignScanState *node, int eflags);
static TupleTableSlot *postgresIterateForeignScan(ForeignScanState *node);
static void postgresReScanForeignScan(ForeignScanState *node);
static void postgresEndForeignScan(ForeignScanState *node);
static void postgresAddForeignUpdateTargets(Query *parsetree,
static void postgresAddForeignUpdateTargets(PlannerInfo *root,
Index rtindex,
RangeTblEntry *target_rte,
Relation target_relation);
static List *postgresPlanForeignModify(PlannerInfo *root,
@ -1669,36 +1671,27 @@ postgresEndForeignScan(ForeignScanState *node)
* Add resjunk column(s) needed for update/delete on a foreign table
*/
static void
postgresAddForeignUpdateTargets(Query *parsetree,
postgresAddForeignUpdateTargets(PlannerInfo *root,
Index rtindex,
RangeTblEntry *target_rte,
Relation target_relation)
{
Var *var;
const char *attrname;
TargetEntry *tle;
/*
* In postgres_fdw, what we need is the ctid, same as for a regular table.
*/
/* Make a Var representing the desired value */
var = makeVar(parsetree->resultRelation,
var = makeVar(rtindex,
SelfItemPointerAttributeNumber,
TIDOID,
-1,
InvalidOid,
0);
/* Wrap it in a resjunk TLE with the right name ... */
attrname = "ctid";
tle = makeTargetEntry((Expr *) var,
list_length(parsetree->targetList) + 1,
pstrdup(attrname),
true);
/* ... and add it to the query's targetlist */
parsetree->targetList = lappend(parsetree->targetList, tle);
/* Register it as a row-identity column needed by this target rel */
add_row_identity_var(root, var, rtindex, "ctid");
}
/*
@ -1886,7 +1879,7 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
rte,
resultRelInfo,
mtstate->operation,
mtstate->mt_plans[subplan_index]->plan,
outerPlanState(mtstate)->plan,
query,
target_attrs,
values_end_len,
@ -2086,8 +2079,7 @@ postgresBeginForeignInsert(ModifyTableState *mtstate,
*/
if (plan && plan->operation == CMD_UPDATE &&
(resultRelInfo->ri_usesFdwDirectModify ||
resultRelInfo->ri_FdwState) &&
resultRelInfo > mtstate->resultRelInfo + mtstate->mt_whichplan)
resultRelInfo->ri_FdwState))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot route tuples into foreign table to be updated \"%s\"",
@ -2283,6 +2275,65 @@ postgresRecheckForeignScan(ForeignScanState *node, TupleTableSlot *slot)
return true;
}
/*
* find_modifytable_subplan
* Helper routine for postgresPlanDirectModify to find the
* ModifyTable subplan node that scans the specified RTI.
*
* Returns NULL if the subplan couldn't be identified. That's not a fatal
* error condition, we just abandon trying to do the update directly.
*/
static ForeignScan *
find_modifytable_subplan(PlannerInfo *root,
ModifyTable *plan,
Index rtindex,
int subplan_index)
{
Plan *subplan = outerPlan(plan);
/*
* The cases we support are (1) the desired ForeignScan is the immediate
* child of ModifyTable, or (2) it is the subplan_index'th child of an
* Append node that is the immediate child of ModifyTable. There is no
* point in looking further down, as that would mean that local joins are
* involved, so we can't do the update directly.
*
* There could be a Result atop the Append too, acting to compute the
* UPDATE targetlist values. We ignore that here; the tlist will be
* checked by our caller.
*
* In principle we could examine all the children of the Append, but it's
* currently unlikely that the core planner would generate such a plan
* with the children out-of-order. Moreover, such a search risks costing
* O(N^2) time when there are a lot of children.
*/
if (IsA(subplan, Append))
{
Append *appendplan = (Append *) subplan;
if (subplan_index < list_length(appendplan->appendplans))
subplan = (Plan *) list_nth(appendplan->appendplans, subplan_index);
}
else if (IsA(subplan, Result) && IsA(outerPlan(subplan), Append))
{
Append *appendplan = (Append *) outerPlan(subplan);
if (subplan_index < list_length(appendplan->appendplans))
subplan = (Plan *) list_nth(appendplan->appendplans, subplan_index);
}
/* Now, have we got a ForeignScan on the desired rel? */
if (IsA(subplan, ForeignScan))
{
ForeignScan *fscan = (ForeignScan *) subplan;
if (bms_is_member(rtindex, fscan->fs_relids))
return fscan;
}
return NULL;
}
/*
* postgresPlanDirectModify
* Consider a direct foreign table modification
@ -2297,13 +2348,13 @@ postgresPlanDirectModify(PlannerInfo *root,
int subplan_index)
{
CmdType operation = plan->operation;
Plan *subplan;
RelOptInfo *foreignrel;
RangeTblEntry *rte;
PgFdwRelationInfo *fpinfo;
Relation rel;
StringInfoData sql;
ForeignScan *fscan;
List *processed_tlist = NIL;
List *targetAttrs = NIL;
List *remote_exprs;
List *params_list = NIL;
@ -2321,19 +2372,17 @@ postgresPlanDirectModify(PlannerInfo *root,
return false;
/*
* It's unsafe to modify a foreign table directly if there are any local
* joins needed.
* Try to locate the ForeignScan subplan that's scanning resultRelation.
*/
subplan = (Plan *) list_nth(plan->plans, subplan_index);
if (!IsA(subplan, ForeignScan))
fscan = find_modifytable_subplan(root, plan, resultRelation, subplan_index);
if (!fscan)
return false;
fscan = (ForeignScan *) subplan;
/*
* It's unsafe to modify a foreign table directly if there are any quals
* that should be evaluated locally.
*/
if (subplan->qual != NIL)
if (fscan->scan.plan.qual != NIL)
return false;
/* Safe to fetch data about the target foreign rel */
@ -2354,32 +2403,28 @@ postgresPlanDirectModify(PlannerInfo *root,
*/
if (operation == CMD_UPDATE)
{
int col;
ListCell *lc,
*lc2;
/*
* We transmit only columns that were explicitly targets of the
* UPDATE, so as to avoid unnecessary data transmission.
* The expressions of concern are the first N columns of the processed
* targetlist, where N is the length of the rel's update_colnos.
*/
col = -1;
while ((col = bms_next_member(rte->updatedCols, col)) >= 0)
get_translated_update_targetlist(root, resultRelation,
&processed_tlist, &targetAttrs);
forboth(lc, processed_tlist, lc2, targetAttrs)
{
/* bit numbers are offset by FirstLowInvalidHeapAttributeNumber */
AttrNumber attno = col + FirstLowInvalidHeapAttributeNumber;
TargetEntry *tle;
TargetEntry *tle = lfirst_node(TargetEntry, lc);
AttrNumber attno = lfirst_int(lc2);
/* update's new-value expressions shouldn't be resjunk */
Assert(!tle->resjunk);
if (attno <= InvalidAttrNumber) /* shouldn't happen */
elog(ERROR, "system-column update is not supported");
tle = get_tle_by_resno(subplan->targetlist, attno);
if (!tle)
elog(ERROR, "attribute number %d not found in subplan targetlist",
attno);
if (!is_foreign_expr(root, foreignrel, (Expr *) tle->expr))
return false;
targetAttrs = lappend_int(targetAttrs, attno);
}
}
@ -2430,7 +2475,7 @@ postgresPlanDirectModify(PlannerInfo *root,
case CMD_UPDATE:
deparseDirectUpdateSql(&sql, root, resultRelation, rel,
foreignrel,
((Plan *) fscan)->targetlist,
processed_tlist,
targetAttrs,
remote_exprs, &params_list,
returningList, &retrieved_attrs);

View File

@ -4823,8 +4823,7 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
well, provided that typical queries allow the query planner to prune all
but a small number of partitions. Planning times become longer and memory
consumption becomes higher when more partitions remain after the planner
performs partition pruning. This is particularly true for the
<command>UPDATE</command> and <command>DELETE</command> commands. Another
performs partition pruning. Another
reason to be concerned about having a large number of partitions is that
the server's memory consumption may grow significantly over
time, especially if many sessions touch large numbers of partitions.

View File

@ -424,7 +424,8 @@ GetForeignUpperPaths(PlannerInfo *root,
<para>
<programlisting>
void
AddForeignUpdateTargets(Query *parsetree,
AddForeignUpdateTargets(PlannerInfo *root,
Index rtindex,
RangeTblEntry *target_rte,
Relation target_relation);
</programlisting>
@ -440,27 +441,31 @@ AddForeignUpdateTargets(Query *parsetree,
</para>
<para>
To do that, add <structname>TargetEntry</structname> items to
<literal>parsetree-&gt;targetList</literal>, containing expressions for the
extra values to be fetched. Each such entry must be marked
<structfield>resjunk</structfield> = <literal>true</literal>, and must have a distinct
<structfield>resname</structfield> that will identify it at execution time.
Avoid using names matching <literal>ctid<replaceable>N</replaceable></literal>,
<literal>wholerow</literal>, or
<literal>wholerow<replaceable>N</replaceable></literal>, as the core system can
generate junk columns of these names.
If the extra expressions are more complex than simple Vars, they
must be run through <function>eval_const_expressions</function>
before adding them to the target list.
</para>
<para>
Although this function is called during planning, the
information provided is a bit different from that available to other
planning routines.
<literal>parsetree</literal> is the parse tree for the <command>UPDATE</command> or
<command>DELETE</command> command, while <literal>target_rte</literal> and
<literal>target_relation</literal> describe the target foreign table.
To do that, construct a <structname>Var</structname> representing
an extra value you need, and pass it
to <function>add_row_identity_var</function>, along with a name for
the junk column. (You can do this more than once if several columns
are needed.) You must choose a distinct junk column name for each
different <structname>Var</structname> you need, except
that <structname>Var</structname>s that are identical except for
the <structfield>varno</structfield> field can and should share a
column name.
The core system uses the junk column names
<literal>tableoid</literal> for a
table's <structfield>tableoid</structfield> column,
<literal>ctid</literal>
or <literal>ctid<replaceable>N</replaceable></literal>
for <structfield>ctid</structfield>,
<literal>wholerow</literal>
for a whole-row <structname>Var</structname> marked with
<structfield>vartype</structfield> = <type>RECORD</type>,
and <literal>wholerow<replaceable>N</replaceable></literal>
for a whole-row <structname>Var</structname> with
<structfield>vartype</structfield> equal to the table's declared rowtype.
Re-use these names when you can (the planner will combine duplicate
requests for identical junk columns). If you need another kind of
junk column besides these, it might be wise to choose a name prefixed
with your extension name, to avoid conflicts against other FDWs.
</para>
<para>
@ -495,8 +500,8 @@ PlanForeignModify(PlannerInfo *root,
<literal>resultRelation</literal> identifies the target foreign table by its
range table index. <literal>subplan_index</literal> identifies which target of
the <structname>ModifyTable</structname> plan node this is, counting from zero;
use this if you want to index into <literal>plan-&gt;plans</literal> or other
substructure of the <literal>plan</literal> node.
use this if you want to index into per-target-relation substructures of the
<literal>plan</literal> node.
</para>
<para>
@ -703,10 +708,14 @@ ExecForeignUpdate(EState *estate,
<literal>slot</literal> contains the new data for the tuple; it will match the
row-type definition of the foreign table.
<literal>planSlot</literal> contains the tuple that was generated by the
<structname>ModifyTable</structname> plan node's subplan; it differs from
<literal>slot</literal> in possibly containing additional <quote>junk</quote>
columns. In particular, any junk columns that were requested by
<function>AddForeignUpdateTargets</function> will be available from this slot.
<structname>ModifyTable</structname> plan node's subplan. Unlike
<literal>slot</literal>, this tuple contains only the new values for
columns changed by the query, so do not rely on attribute numbers of the
foreign table to index into <literal>planSlot</literal>.
Also, <literal>planSlot</literal> typically contains
additional <quote>junk</quote> columns. In particular, any junk columns
that were requested by <function>AddForeignUpdateTargets</function> will
be available from this slot.
</para>
<para>

View File

@ -773,13 +773,14 @@ EXPLAIN ANALYZE UPDATE tenk1 SET hundred = hundred + 1 WHERE unique1 &lt; 100;
QUERY PLAN
-------------------------------------------------------------------&zwsp;-------------------------------------------------------------
Update on tenk1 (cost=5.07..229.46 rows=0 width=0) (actual time=14.628..14.628 rows=0 loops=1)
-&gt; Bitmap Heap Scan on tenk1 (cost=5.07..229.46 rows=101 width=250) (actual time=0.101..0.439 rows=100 loops=1)
Update on tenk1 (cost=5.08..230.08 rows=0 width=0) (actual time=3.791..3.792 rows=0 loops=1)
-&gt; Bitmap Heap Scan on tenk1 (cost=5.08..230.08 rows=102 width=10) (actual time=0.069..0.513 rows=100 loops=1)
Recheck Cond: (unique1 &lt; 100)
-&gt; Bitmap Index Scan on tenk1_unique1 (cost=0.00..5.04 rows=101 width=0) (actual time=0.043..0.043 rows=100 loops=1)
Heap Blocks: exact=90
-&gt; Bitmap Index Scan on tenk1_unique1 (cost=0.00..5.05 rows=102 width=0) (actual time=0.036..0.037 rows=300 loops=1)
Index Cond: (unique1 &lt; 100)
Planning time: 0.079 ms
Execution time: 14.727 ms
Planning Time: 0.113 ms
Execution Time: 3.850 ms
ROLLBACK;
</screen>
@ -807,30 +808,30 @@ ROLLBACK;
<screen>
EXPLAIN UPDATE parent SET f2 = f2 + 1 WHERE f1 = 101;
QUERY PLAN
-------------------------------------------------------------------&zwsp;----------------
Update on parent (cost=0.00..24.53 rows=0 width=0)
Update on parent
Update on child1
Update on child2
Update on child3
-&gt; Seq Scan on parent (cost=0.00..0.00 rows=1 width=14)
Filter: (f1 = 101)
-&gt; Index Scan using child1_f1_key on child1 (cost=0.15..8.17 rows=1 width=14)
Index Cond: (f1 = 101)
-&gt; Index Scan using child2_f1_key on child2 (cost=0.15..8.17 rows=1 width=14)
Index Cond: (f1 = 101)
-&gt; Index Scan using child3_f1_key on child3 (cost=0.15..8.17 rows=1 width=14)
Index Cond: (f1 = 101)
QUERY PLAN
-------------------------------------------------------------------&zwsp;-----------------------------------
Update on parent (cost=0.00..24.59 rows=0 width=0)
Update on parent parent_1
Update on child1 parent_2
Update on child2 parent_3
Update on child3 parent_4
-&gt; Result (cost=0.00..24.59 rows=4 width=14)
-&gt; Append (cost=0.00..24.54 rows=4 width=14)
-&gt; Seq Scan on parent parent_1 (cost=0.00..0.00 rows=1 width=14)
Filter: (f1 = 101)
-&gt; Index Scan using child1_pkey on child1 parent_2 (cost=0.15..8.17 rows=1 width=14)
Index Cond: (f1 = 101)
-&gt; Index Scan using child2_pkey on child2 parent_3 (cost=0.15..8.17 rows=1 width=14)
Index Cond: (f1 = 101)
-&gt; Index Scan using child3_pkey on child3 parent_4 (cost=0.15..8.17 rows=1 width=14)
Index Cond: (f1 = 101)
</screen>
In this example the Update node needs to consider three child tables as
well as the originally-mentioned parent table. So there are four input
scanning subplans, one per table. For clarity, the Update node is
annotated to show the specific target tables that will be updated, in the
same order as the corresponding subplans. (These annotations are new as
of <productname>PostgreSQL</productname> 9.5; in prior versions the reader had to
intuit the target tables by inspecting the subplans.)
same order as the corresponding subplans.
</para>
<para>

View File

@ -78,7 +78,7 @@
invoked by <command>UPDATE</command> statements executed on partitioned
tables, but it currently does not handle the case where a remote partition
chosen to insert a moved row into is also an <command>UPDATE</command>
target partition that will be updated later.
target partition that will be updated elsewhere in the same command.
</para>
<para>

View File

@ -666,6 +666,7 @@ CopyFrom(CopyFromState cstate)
mtstate->ps.plan = NULL;
mtstate->ps.state = estate;
mtstate->operation = CMD_INSERT;
mtstate->mt_nrels = 1;
mtstate->resultRelInfo = resultRelInfo;
mtstate->rootResultRelInfo = resultRelInfo;

View File

@ -2081,7 +2081,6 @@ ExplainNode(PlanState *planstate, List *ancestors,
haschildren = planstate->initPlan ||
outerPlanState(planstate) ||
innerPlanState(planstate) ||
IsA(plan, ModifyTable) ||
IsA(plan, Append) ||
IsA(plan, MergeAppend) ||
IsA(plan, BitmapAnd) ||
@ -2114,11 +2113,6 @@ ExplainNode(PlanState *planstate, List *ancestors,
/* special child plans */
switch (nodeTag(plan))
{
case T_ModifyTable:
ExplainMemberNodes(((ModifyTableState *) planstate)->mt_plans,
((ModifyTableState *) planstate)->mt_nplans,
ancestors, es);
break;
case T_Append:
ExplainMemberNodes(((AppendState *) planstate)->appendplans,
((AppendState *) planstate)->as_nplans,
@ -3718,14 +3712,14 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
}
/* Should we explicitly label target relations? */
labeltargets = (mtstate->mt_nplans > 1 ||
(mtstate->mt_nplans == 1 &&
labeltargets = (mtstate->mt_nrels > 1 ||
(mtstate->mt_nrels == 1 &&
mtstate->resultRelInfo[0].ri_RangeTableIndex != node->nominalRelation));
if (labeltargets)
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
for (j = 0; j < mtstate->mt_nplans; j++)
for (j = 0; j < mtstate->mt_nrels; j++)
{
ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
@ -3820,10 +3814,10 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
double insert_path;
double other_path;
InstrEndLoop(mtstate->mt_plans[0]->instrument);
InstrEndLoop(outerPlanState(mtstate)->instrument);
/* count the number of source rows */
total = mtstate->mt_plans[0]->instrument->ntuples;
total = outerPlanState(mtstate)->instrument->ntuples;
other_path = mtstate->ps.instrument->ntuples2;
insert_path = total - other_path;
@ -3839,7 +3833,7 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
}
/*
* Explain the constituent plans of a ModifyTable, Append, MergeAppend,
* Explain the constituent plans of an Append, MergeAppend,
* BitmapAnd, or BitmapOr node.
*
* The ancestors list should already contain the immediate parent of these

View File

@ -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 newslot. Neither we nor our callers have any further
* interest in the passed-in tuple, so it's okay to overwrite newslot
* with the newer data.
*
* 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.
* (Typically, newslot was also generated by ExecGetUpdateNewTuple, so
* that epqslot_clean will be that same slot and the copy step below
* is not needed.)
*/
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);

View File

@ -32,10 +32,14 @@ includes a RETURNING clause, the ModifyTable node delivers the computed
RETURNING rows as output, otherwise it returns nothing. Handling INSERT
is pretty straightforward: the tuples returned from the plan tree below
ModifyTable are inserted into the correct result relation. For UPDATE,
the plan tree returns the computed tuples to be updated, plus a "junk"
(hidden) CTID column identifying which table row is to be replaced by each
one. For DELETE, the plan tree need only deliver a CTID column, and the
ModifyTable node visits each of those rows and marks the row deleted.
the plan tree returns the new values of the updated columns, plus "junk"
(hidden) column(s) identifying which table row is to be updated. The
ModifyTable node must fetch that row to extract values for the unchanged
columns, combine the values into a new row, and apply the update. (For a
heap table, the row-identity junk column is a CTID, but other things may
be used for other table types.) For DELETE, the plan tree need only deliver
junk row-identity column(s), and the ModifyTable node visits each of those
rows and marks the row deleted.
XXX a great deal more documentation needs to be written here...

View File

@ -477,6 +477,206 @@ ExecBuildProjectionInfo(List *targetList,
return projInfo;
}
/*
* ExecBuildUpdateProjection
*
* Build a ProjectionInfo node for constructing a new tuple during UPDATE.
* The projection will be executed in the given econtext and the result will
* be stored into the given tuple slot. (Caller must have ensured that tuple
* slot has a descriptor matching the target rel!)
*
* subTargetList is the tlist of the subplan node feeding ModifyTable.
* We use this mainly to cross-check that the expressions being assigned
* are of the correct types. The values from this tlist are assumed to be
* available from the "outer" tuple slot. They are assigned to target columns
* listed in the corresponding targetColnos elements. (Only non-resjunk tlist
* entries are assigned.) Columns not listed in targetColnos are filled from
* the UPDATE's old tuple, which is assumed to be available in the "scan"
* tuple slot.
*
* relDesc must describe the relation we intend to update.
*
* This is basically a specialized variant of ExecBuildProjectionInfo.
* However, it also performs sanity checks equivalent to ExecCheckPlanOutput.
* Since we never make a normal tlist equivalent to the whole
* tuple-to-be-assigned, there is no convenient way to apply
* ExecCheckPlanOutput, so we must do our safety checks here.
*/
ProjectionInfo *
ExecBuildUpdateProjection(List *subTargetList,
List *targetColnos,
TupleDesc relDesc,
ExprContext *econtext,
TupleTableSlot *slot,
PlanState *parent)
{
ProjectionInfo *projInfo = makeNode(ProjectionInfo);
ExprState *state;
int nAssignableCols;
bool sawJunk;
Bitmapset *assignedCols;
LastAttnumInfo deform = {0, 0, 0};
ExprEvalStep scratch = {0};
int outerattnum;
ListCell *lc,
*lc2;
projInfo->pi_exprContext = econtext;
/* We embed ExprState into ProjectionInfo instead of doing extra palloc */
projInfo->pi_state.tag = T_ExprState;
state = &projInfo->pi_state;
state->expr = NULL; /* not used */
state->parent = parent;
state->ext_params = NULL;
state->resultslot = slot;
/*
* Examine the subplan tlist to see how many non-junk columns there are,
* and to verify that the non-junk columns come before the junk ones.
*/
nAssignableCols = 0;
sawJunk = false;
foreach(lc, subTargetList)
{
TargetEntry *tle = lfirst_node(TargetEntry, lc);
if (tle->resjunk)
sawJunk = true;
else
{
if (sawJunk)
elog(ERROR, "subplan target list is out of order");
nAssignableCols++;
}
}
/* We should have one targetColnos entry per non-junk column */
if (nAssignableCols != list_length(targetColnos))
elog(ERROR, "targetColnos does not match subplan target list");
/*
* Build a bitmapset of the columns in targetColnos. (We could just use
* list_member_int() tests, but that risks O(N^2) behavior with many
* columns.)
*/
assignedCols = NULL;
foreach(lc, targetColnos)
{
AttrNumber targetattnum = lfirst_int(lc);
assignedCols = bms_add_member(assignedCols, targetattnum);
}
/*
* We want to insert EEOP_*_FETCHSOME steps to ensure the outer and scan
* tuples are sufficiently deconstructed. Outer tuple is easy, but for
* scan tuple we must find out the last old column we need.
*/
deform.last_outer = nAssignableCols;
for (int attnum = relDesc->natts; attnum > 0; attnum--)
{
Form_pg_attribute attr = TupleDescAttr(relDesc, attnum - 1);
if (attr->attisdropped)
continue;
if (bms_is_member(attnum, assignedCols))
continue;
deform.last_scan = attnum;
break;
}
ExecPushExprSlots(state, &deform);
/*
* Now generate code to fetch data from the outer tuple, incidentally
* validating that it'll be of the right type. The checks above ensure
* that the forboth() will iterate over exactly the non-junk columns.
*/
outerattnum = 0;
forboth(lc, subTargetList, lc2, targetColnos)
{
TargetEntry *tle = lfirst_node(TargetEntry, lc);
AttrNumber targetattnum = lfirst_int(lc2);
Form_pg_attribute attr;
Assert(!tle->resjunk);
/*
* Apply sanity checks comparable to ExecCheckPlanOutput().
*/
if (targetattnum <= 0 || targetattnum > relDesc->natts)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("table row type and query-specified row type do not match"),
errdetail("Query has too many columns.")));
attr = TupleDescAttr(relDesc, targetattnum - 1);
if (attr->attisdropped)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("table row type and query-specified row type do not match"),
errdetail("Query provides a value for a dropped column at ordinal position %d.",
targetattnum)));
if (exprType((Node *) tle->expr) != attr->atttypid)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("table row type and query-specified row type do not match"),
errdetail("Table has type %s at ordinal position %d, but query expects %s.",
format_type_be(attr->atttypid),
targetattnum,
format_type_be(exprType((Node *) tle->expr)))));
/*
* OK, build an outer-tuple reference.
*/
scratch.opcode = EEOP_ASSIGN_OUTER_VAR;
scratch.d.assign_var.attnum = outerattnum++;
scratch.d.assign_var.resultnum = targetattnum - 1;
ExprEvalPushStep(state, &scratch);
}
/*
* Now generate code to copy over any old columns that were not assigned
* to, and to ensure that dropped columns are set to NULL.
*/
for (int attnum = 1; attnum <= relDesc->natts; attnum++)
{
Form_pg_attribute attr = TupleDescAttr(relDesc, attnum - 1);
if (attr->attisdropped)
{
/* Put a null into the ExprState's resvalue/resnull ... */
scratch.opcode = EEOP_CONST;
scratch.resvalue = &state->resvalue;
scratch.resnull = &state->resnull;
scratch.d.constval.value = (Datum) 0;
scratch.d.constval.isnull = true;
ExprEvalPushStep(state, &scratch);
/* ... then assign it to the result slot */
scratch.opcode = EEOP_ASSIGN_TMP;
scratch.d.assign_tmp.resultnum = attnum - 1;
ExprEvalPushStep(state, &scratch);
}
else if (!bms_is_member(attnum, assignedCols))
{
/* Certainly the right type, so needn't check */
scratch.opcode = EEOP_ASSIGN_SCAN_VAR;
scratch.d.assign_var.attnum = attnum - 1;
scratch.d.assign_var.resultnum = attnum - 1;
ExprEvalPushStep(state, &scratch);
}
}
scratch.opcode = EEOP_DONE;
ExprEvalPushStep(state, &scratch);
ExecReadyExpr(state);
return projInfo;
}
/*
* ExecPrepareExpr --- initialize for expression execution outside a normal
* Plan tree context.

View File

@ -59,43 +59,16 @@
JunkFilter *
ExecInitJunkFilter(List *targetList, TupleTableSlot *slot)
{
JunkFilter *junkfilter;
TupleDesc cleanTupType;
int cleanLength;
AttrNumber *cleanMap;
/*
* Compute the tuple descriptor for the cleaned tuple.
*/
cleanTupType = ExecCleanTypeFromTL(targetList);
/*
* The rest is the same as ExecInitJunkFilterInsertion, ie, we want to map
* every non-junk targetlist column into the output tuple.
*/
return ExecInitJunkFilterInsertion(targetList, cleanTupType, slot);
}
/*
* ExecInitJunkFilterInsertion
*
* Initialize a JunkFilter for insertions into a table.
*
* Here, we are given the target "clean" tuple descriptor rather than
* inferring it from the targetlist. Although the target descriptor can
* contain deleted columns, that is not of concern here, since the targetlist
* should contain corresponding NULL constants (cf. ExecCheckPlanOutput).
* It is assumed that the caller has checked that the table's columns match up
* with the non-junk columns of the targetlist.
*/
JunkFilter *
ExecInitJunkFilterInsertion(List *targetList,
TupleDesc cleanTupType,
TupleTableSlot *slot)
{
JunkFilter *junkfilter;
int cleanLength;
AttrNumber *cleanMap;
ListCell *t;
AttrNumber cleanResno;
/*
* Use the given slot, or make a new slot if we weren't given one.
*/
@ -117,6 +90,9 @@ ExecInitJunkFilterInsertion(List *targetList,
cleanLength = cleanTupType->natts;
if (cleanLength > 0)
{
AttrNumber cleanResno;
ListCell *t;
cleanMap = (AttrNumber *) palloc(cleanLength * sizeof(AttrNumber));
cleanResno = 0;
foreach(t, targetList)
@ -262,22 +238,6 @@ ExecFindJunkAttributeInTlist(List *targetlist, const char *attrName)
return InvalidAttrNumber;
}
/*
* ExecGetJunkAttribute
*
* Given a junk filter's input tuple (slot) and a junk attribute's number
* previously found by ExecFindJunkAttribute, extract & return the value and
* isNull flag of the attribute.
*/
Datum
ExecGetJunkAttribute(TupleTableSlot *slot, AttrNumber attno,
bool *isNull)
{
Assert(attno > 0);
return slot_getattr(slot, attno, isNull);
}
/*
* ExecFilterJunk
*

View File

@ -1217,11 +1217,14 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_FdwRoutine = NULL;
/* The following fields are set later if needed */
resultRelInfo->ri_RowIdAttNo = 0;
resultRelInfo->ri_projectNew = NULL;
resultRelInfo->ri_newTupleSlot = NULL;
resultRelInfo->ri_oldTupleSlot = NULL;
resultRelInfo->ri_FdwState = NULL;
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;
@ -2413,7 +2416,8 @@ EvalPlanQualInit(EPQState *epqstate, EState *parentestate,
/*
* EvalPlanQualSetPlan -- set or change subplan of an EPQState.
*
* We need this so that ModifyTable can deal with multiple subplans.
* We used to need this so that ModifyTable could deal with multiple subplans.
* It could now be refactored out of existence.
*/
void
EvalPlanQualSetPlan(EPQState *epqstate, Plan *subplan, List *auxrowmarks)

View File

@ -82,7 +82,7 @@
*
* subplan_resultrel_htab
* Hash table to store subplan ResultRelInfos by Oid. This is used to
* cache ResultRelInfos from subplans of an UPDATE ModifyTable node;
* cache ResultRelInfos from targets of an UPDATE ModifyTable node;
* NULL in other cases. Some of these may be useful for tuple routing
* to save having to build duplicates.
*
@ -527,12 +527,12 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
ctl.entrysize = sizeof(SubplanResultRelHashElem);
ctl.hcxt = CurrentMemoryContext;
htab = hash_create("PartitionTupleRouting table", mtstate->mt_nplans,
htab = hash_create("PartitionTupleRouting table", mtstate->mt_nrels,
&ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
proute->subplan_resultrel_htab = htab;
/* Hash all subplans by their Oid */
for (i = 0; i < mtstate->mt_nplans; i++)
for (i = 0; i < mtstate->mt_nrels; i++)
{
ResultRelInfo *rri = &mtstate->resultRelInfo[i];
bool found;
@ -628,10 +628,10 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
*/
Assert((node->operation == CMD_INSERT &&
list_length(node->withCheckOptionLists) == 1 &&
list_length(node->plans) == 1) ||
list_length(node->resultRelations) == 1) ||
(node->operation == CMD_UPDATE &&
list_length(node->withCheckOptionLists) ==
list_length(node->plans)));
list_length(node->resultRelations)));
/*
* Use the WCO list of the first plan as a reference to calculate
@ -687,10 +687,10 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
/* See the comment above for WCO lists. */
Assert((node->operation == CMD_INSERT &&
list_length(node->returningLists) == 1 &&
list_length(node->plans) == 1) ||
list_length(node->resultRelations) == 1) ||
(node->operation == CMD_UPDATE &&
list_length(node->returningLists) ==
list_length(node->plans)));
list_length(node->resultRelations)));
/*
* Use the RETURNING list of the first plan as a reference to

View File

@ -19,14 +19,10 @@
* ExecReScanModifyTable - rescan the ModifyTable node
*
* NOTES
* Each ModifyTable node contains a list of one or more subplans,
* much like an Append node. There is one subplan per result relation.
* The key reason for this is that in an inherited UPDATE command, each
* result relation could have a different schema (more or different
* columns) requiring a different plan tree to produce it. In an
* inherited DELETE, all the subplans should produce the same output
* rowtype, but we might still find that different plans are appropriate
* for different child relations.
* The ModifyTable node receives input from its outerPlan, which is
* the data to insert for INSERT cases, or the changed columns' new
* values plus row-locating info for UPDATE cases, or just the
* row-locating info for DELETE cases.
*
* If the query specifies RETURNING, then the ModifyTable returns a
* RETURNING tuple after completing each row insert, update, or delete.
@ -58,6 +54,12 @@
#include "utils/rel.h"
typedef struct MTTargetRelLookup
{
Oid relationOid; /* hash key, must be first */
int relationIndex; /* rel's index in resultRelInfo[] array */
} MTTargetRelLookup;
static void ExecBatchInsert(ModifyTableState *mtstate,
ResultRelInfo *resultRelInfo,
TupleTableSlot **slots,
@ -81,7 +83,7 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
ResultRelInfo **partRelInfo);
/*
* Verify that the tuples to be produced by INSERT or UPDATE match the
* Verify that the tuples to be produced by INSERT match the
* target relation's rowtype
*
* We do this to guard against stale plans. If plan invalidation is
@ -91,6 +93,9 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
*
* The plan output is represented by its targetlist, because that makes
* handling the dropped-column case easier.
*
* We used to use this for UPDATE as well, but now the equivalent checks
* are done in ExecBuildUpdateProjection.
*/
static void
ExecCheckPlanOutput(Relation resultRel, List *targetList)
@ -104,8 +109,7 @@ ExecCheckPlanOutput(Relation resultRel, List *targetList)
TargetEntry *tle = (TargetEntry *) lfirst(lc);
Form_pg_attribute attr;
if (tle->resjunk)
continue; /* ignore junk tlist items */
Assert(!tle->resjunk); /* caller removed junk items already */
if (attno >= resultDesc->natts)
ereport(ERROR,
@ -367,6 +371,74 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
MemoryContextSwitchTo(oldContext);
}
/*
* 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
* and (if necessary) coercing the tuple to the right tuple format.
*/
static TupleTableSlot *
ExecGetInsertNewTuple(ResultRelInfo *relinfo,
TupleTableSlot *planSlot)
{
ProjectionInfo *newProj = relinfo->ri_projectNew;
ExprContext *econtext;
/*
* If there's no projection to be done, just make sure the slot is of the
* right type for the target rel. If the planSlot is the right type we
* can use it as-is, else copy the data into ri_newTupleSlot.
*/
if (newProj == NULL)
{
if (relinfo->ri_newTupleSlot->tts_ops != planSlot->tts_ops)
{
ExecCopySlot(relinfo->ri_newTupleSlot, planSlot);
return relinfo->ri_newTupleSlot;
}
else
return planSlot;
}
/*
* Else project; since the projection output slot is ri_newTupleSlot, this
* will also fix any slot-type problem.
*
* Note: currently, this is dead code, because INSERT cases don't receive
* any junk columns so there's never a projection to be done.
*/
econtext = newProj->pi_exprContext;
econtext->ecxt_outertuple = planSlot;
return ExecProject(newProj);
}
/*
* ExecGetUpdateNewTuple
* This prepares a "new" tuple by combining an UPDATE subplan's output
* tuple (which contains values of changed columns) with unchanged
* columns taken from the old tuple.
*
* The subplan tuple might also contain junk columns, which are ignored.
* Note that the projection also ensures we have a slot of the right type.
*/
TupleTableSlot *
ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
TupleTableSlot *planSlot,
TupleTableSlot *oldSlot)
{
ProjectionInfo *newProj = relinfo->ri_projectNew;
ExprContext *econtext;
Assert(planSlot != NULL && !TTS_EMPTY(planSlot));
Assert(oldSlot != NULL && !TTS_EMPTY(oldSlot));
econtext = newProj->pi_exprContext;
econtext->ecxt_outertuple = planSlot;
econtext->ecxt_scantuple = oldSlot;
return ExecProject(newProj);
}
/* ----------------------------------------------------------------
* ExecInsert
*
@ -374,6 +446,10 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
* (or partition thereof) and insert appropriate tuples into the index
* relations.
*
* slot contains the new tuple value to be stored.
* planSlot is the output of the ModifyTable's subplan; we use it
* to access "junk" columns that are not going to be stored.
*
* Returns RETURNING result if any, otherwise NULL.
*
* This may change the currently active tuple conversion map in
@ -1269,13 +1345,22 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
return true;
else
{
*retry_slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
/* Fetch the most recent version of old tuple. */
TupleTableSlot *oldSlot = resultRelInfo->ri_oldTupleSlot;
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;
}
}
/*
* resultRelInfo is one of the per-subplan resultRelInfos. So we should
* resultRelInfo is one of the per-relation resultRelInfos. So we should
* convert the tuple into root's tuple descriptor if needed, since
* ExecInsert() starts the search from root.
*/
@ -1319,6 +1404,11 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
* foreign table triggers; it is NULL when the foreign table has
* no relevant triggers.
*
* slot contains the new tuple value to be stored.
* planSlot is the output of the ModifyTable's subplan; we use it
* to access values from other input tables (for RETURNING),
* row-ID junk columns, etc.
*
* Returns RETURNING result if any, otherwise NULL.
* ----------------------------------------------------------------
*/
@ -1545,6 +1635,7 @@ lreplace:;
{
TupleTableSlot *inputslot;
TupleTableSlot *epqslot;
TupleTableSlot *oldSlot;
if (IsolationUsesXactSnapshot())
ereport(ERROR,
@ -1578,7 +1669,15 @@ lreplace:;
/* Tuple not passing quals anymore, exiting... */
return NULL;
slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot);
/* Fetch the most recent version of old tuple. */
oldSlot = resultRelInfo->ri_oldTupleSlot;
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:
@ -2051,16 +2150,16 @@ ExecModifyTable(PlanState *pstate)
CmdType operation = node->operation;
ResultRelInfo *resultRelInfo;
PlanState *subplanstate;
JunkFilter *junkfilter;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
TupleTableSlot *oldSlot;
ItemPointer tupleid;
ItemPointerData tuple_ctid;
HeapTupleData oldtupdata;
HeapTuple oldtuple;
PartitionTupleRouting *proute = node->mt_partition_tuple_routing;
List *relinfos = NIL;
ListCell *lc;
List *relinfos = NIL;
ListCell *lc;
CHECK_FOR_INTERRUPTS();
@ -2095,12 +2194,11 @@ ExecModifyTable(PlanState *pstate)
}
/* Preload local variables */
resultRelInfo = node->resultRelInfo + node->mt_whichplan;
subplanstate = node->mt_plans[node->mt_whichplan];
junkfilter = resultRelInfo->ri_junkFilter;
resultRelInfo = node->resultRelInfo + node->mt_lastResultIndex;
subplanstate = outerPlanState(node);
/*
* Fetch rows from subplan(s), and execute the required table modification
* Fetch rows from subplan, and execute the required table modification
* for each row.
*/
for (;;)
@ -2123,30 +2221,61 @@ ExecModifyTable(PlanState *pstate)
planSlot = ExecProcNode(subplanstate);
/* No more tuples to process? */
if (TupIsNull(planSlot))
{
/* advance to next subplan if any */
node->mt_whichplan++;
if (node->mt_whichplan < node->mt_nplans)
{
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;
}
else
break;
}
break;
/*
* Ensure input tuple is the right format for the target relation.
* When there are multiple result relations, each tuple contains a
* junk column that gives the OID of the rel from which it came.
* Extract it and select the correct result relation.
*/
if (node->mt_scans[node->mt_whichplan]->tts_ops != planSlot->tts_ops)
if (AttributeNumberIsValid(node->mt_resultOidAttno))
{
ExecCopySlot(node->mt_scans[node->mt_whichplan], planSlot);
planSlot = node->mt_scans[node->mt_whichplan];
Datum datum;
bool isNull;
Oid resultoid;
datum = ExecGetJunkAttribute(planSlot, node->mt_resultOidAttno,
&isNull);
if (isNull)
elog(ERROR, "tableoid is NULL");
resultoid = DatumGetObjectId(datum);
/* If it's not the same as last time, we need to locate the rel */
if (resultoid != node->mt_lastResultOid)
{
if (node->mt_resultOidHash)
{
/* Use the pre-built hash table to locate the rel */
MTTargetRelLookup *mtlookup;
mtlookup = (MTTargetRelLookup *)
hash_search(node->mt_resultOidHash, &resultoid,
HASH_FIND, NULL);
if (!mtlookup)
elog(ERROR, "incorrect result rel OID %u", resultoid);
node->mt_lastResultOid = resultoid;
node->mt_lastResultIndex = mtlookup->relationIndex;
resultRelInfo = node->resultRelInfo + mtlookup->relationIndex;
}
else
{
/* With few target rels, just do a simple search */
int ndx;
for (ndx = 0; ndx < node->mt_nrels; ndx++)
{
resultRelInfo = node->resultRelInfo + ndx;
if (RelationGetRelid(resultRelInfo->ri_RelationDesc) == resultoid)
break;
}
if (ndx >= node->mt_nrels)
elog(ERROR, "incorrect result rel OID %u", resultoid);
node->mt_lastResultOid = resultoid;
node->mt_lastResultIndex = ndx;
}
}
}
/*
@ -2173,84 +2302,116 @@ ExecModifyTable(PlanState *pstate)
tupleid = NULL;
oldtuple = NULL;
if (junkfilter != NULL)
/*
* For UPDATE/DELETE, fetch the row identity info for the tuple to be
* updated/deleted. For a heap relation, that's a TID; otherwise we
* may have a wholerow junk attr that carries the old tuple in toto.
* Keep this in step with the part of ExecInitModifyTable that sets up
* ri_RowIdAttNo.
*/
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 ||
relkind == RELKIND_PARTITIONED_TABLE)
{
char relkind;
Datum datum;
bool isNull;
/* ri_RowIdAttNo refers to a ctid attribute */
Assert(AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo));
datum = ExecGetJunkAttribute(slot,
resultRelInfo->ri_RowIdAttNo,
&isNull);
/* shouldn't ever get a null result... */
if (isNull)
elog(ERROR, "ctid is NULL");
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);
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 or both of two
* purposes: 1) it serves as the OLD tuple for row triggers, 2) it
* provides values for any unchanged columns for the NEW tuple of
* an UPDATE, because the subplan does not produce all the columns
* of the target table.
*
* 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_RowIdAttNo))
{
datum = ExecGetJunkAttribute(slot,
resultRelInfo->ri_RowIdAttNo,
&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
{
/* Only foreign tables are allowed to omit a row-ID attr */
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:
/*
* Make the new tuple by combining plan's output tuple with
* the old tuple being updated.
*/
oldSlot = resultRelInfo->ri_oldTupleSlot;
if (oldtuple != NULL)
{
/* Use the wholerow junk attr as the old tuple. */
ExecForceStoreHeapTuple(oldtuple, oldSlot, false);
}
else
{
/* Fetch the most recent version of old tuple. */
Relation relation = resultRelInfo->ri_RelationDesc;
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);
/* Now apply the update. */
slot = ExecUpdate(node, resultRelInfo, tupleid, oldtuple, slot,
planSlot, &node->mt_epqstate, estate,
node->canSetTag);
@ -2313,12 +2474,12 @@ ModifyTableState *
ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
{
ModifyTableState *mtstate;
Plan *subplan = outerPlan(node);
CmdType operation = node->operation;
int nplans = list_length(node->plans);
int nrels = list_length(node->resultRelations);
ResultRelInfo *resultRelInfo;
Plan *subplan;
ListCell *l,
*l1;
List *arowmarks;
ListCell *l;
int i;
Relation rel;
bool update_tuple_routing_needed = node->partColsUpdated;
@ -2338,10 +2499,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->canSetTag = node->canSetTag;
mtstate->mt_done = false;
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
mtstate->mt_nrels = nrels;
mtstate->resultRelInfo = (ResultRelInfo *)
palloc(nplans * sizeof(ResultRelInfo));
mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans);
palloc(nrels * sizeof(ResultRelInfo));
/*----------
* Resolve the target relation. This is the same as:
@ -2370,9 +2530,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
linitial_int(node->resultRelations));
}
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
mtstate->mt_nplans = nplans;
/* set up epqstate with dummy subplan data for the moment */
EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
mtstate->fireBSTriggers = true;
@ -2385,23 +2542,17 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecSetupTransitionCaptureState(mtstate, estate);
/*
* call ExecInitNode on each of the plans to be executed and save the
* results into the array "mt_plans". This is also a convenient place to
* verify that the proposed target relations are valid and open their
* indexes for insertion of new index entries.
* Open all the result relations and initialize the ResultRelInfo structs.
* (But root relation was initialized above, if it's part of the array.)
* We must do this before initializing the subplan, because direct-modify
* FDWs expect their ResultRelInfos to be available.
*/
resultRelInfo = mtstate->resultRelInfo;
i = 0;
forboth(l, node->resultRelations, l1, node->plans)
foreach(l, node->resultRelations)
{
Index resultRelation = lfirst_int(l);
subplan = (Plan *) lfirst(l1);
/*
* This opens result relation and fills ResultRelInfo. (root relation
* was initialized already.)
*/
if (resultRelInfo != mtstate->rootResultRelInfo)
ExecInitResultRelation(estate, resultRelInfo, resultRelation);
@ -2414,6 +2565,22 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
*/
CheckValidResultRel(resultRelInfo, operation);
resultRelInfo++;
i++;
}
/*
* Now we may initialize the subplan.
*/
outerPlanState(mtstate) = ExecInitNode(subplan, estate, eflags);
/*
* Do additional per-result-relation initialization.
*/
for (i = 0; i < nrels; i++)
{
resultRelInfo = &mtstate->resultRelInfo[i];
/*
* If there are indices on the result relation, open them and save
* descriptors in the result relation info, so that we can add new
@ -2439,12 +2606,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
operation == CMD_UPDATE)
update_tuple_routing_needed = true;
/* Now init the plan for this result rel */
mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
mtstate->mt_scans[i] =
ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
table_slot_callbacks(resultRelInfo->ri_RelationDesc));
/* Also let FDWs init themselves for foreign-table result rels */
if (!resultRelInfo->ri_usesFdwDirectModify &&
resultRelInfo->ri_FdwRoutine != NULL &&
@ -2476,11 +2637,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
resultRelInfo->ri_ChildToRootMap =
convert_tuples_by_name(RelationGetDescr(resultRelInfo->ri_RelationDesc),
RelationGetDescr(mtstate->rootResultRelInfo->ri_RelationDesc));
resultRelInfo++;
i++;
}
/* Get the target relation */
/* Get the root target relation */
rel = mtstate->rootResultRelInfo->ri_RelationDesc;
/*
@ -2596,8 +2755,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
TupleDesc relationDesc;
TupleDesc tupDesc;
/* insert may only have one plan, inheritance is not expanded */
Assert(nplans == 1);
/* insert may only have one relation, inheritance is not expanded */
Assert(nrels == 1);
/* already exists if created by RETURNING processing above */
if (mtstate->ps.ps_ExprContext == NULL)
@ -2649,150 +2808,224 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
* EvalPlanQual mechanism needs to be told about them. Locate the
* relevant ExecRowMarks.
*/
arowmarks = NIL;
foreach(l, node->rowMarks)
{
PlanRowMark *rc = lfirst_node(PlanRowMark, l);
ExecRowMark *erm;
ExecAuxRowMark *aerm;
/* ignore "parent" rowmarks; they are irrelevant at runtime */
if (rc->isParent)
continue;
/* find ExecRowMark (same for all subplans) */
/* Find ExecRowMark and build ExecAuxRowMark */
erm = ExecFindRowMark(estate, rc->rti, false);
/* build ExecAuxRowMark for each subplan */
for (i = 0; i < nplans; i++)
{
ExecAuxRowMark *aerm;
subplan = mtstate->mt_plans[i]->plan;
aerm = ExecBuildAuxRowMark(erm, subplan->targetlist);
mtstate->mt_arowmarks[i] = lappend(mtstate->mt_arowmarks[i], aerm);
}
aerm = ExecBuildAuxRowMark(erm, subplan->targetlist);
arowmarks = lappend(arowmarks, aerm);
}
/* select first subplan */
mtstate->mt_whichplan = 0;
subplan = (Plan *) linitial(node->plans);
EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan,
mtstate->mt_arowmarks[0]);
EvalPlanQualSetPlan(&mtstate->mt_epqstate, subplan, arowmarks);
/*
* 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(s) to create tuples suitable for result rel(s).
* INSERT queries may need a projection to filter out junk attrs in the
* tlist. UPDATE always needs a projection, because (1) there's always
* some junk attrs, and (2) we may need to merge values of not-updated
* columns from the old tuple into the final tuple. In UPDATE, the tuple
* arriving from the subplan contains only new values for the changed
* columns, plus row identity info in the junk attrs.
*
* 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 projection 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 < nrels; i++)
{
bool junk_filter_needed = false;
resultRelInfo = &mtstate->resultRelInfo[i];
switch (operation)
/*
* Prepare to generate tuples suitable for the target relation.
*/
if (operation == CMD_INSERT)
{
case CMD_INSERT:
foreach(l, subplan->targetlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
List *insertTargetList = NIL;
bool need_projection = false;
if (tle->resjunk)
{
junk_filter_needed = true;
break;
}
}
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++)
foreach(l, subplan->targetlist)
{
JunkFilter *j;
TupleTableSlot *junkresslot;
TargetEntry *tle = (TargetEntry *) lfirst(l);
subplan = mtstate->mt_plans[i]->plan;
junkresslot =
ExecInitExtraTupleSlot(estate, NULL,
table_slot_callbacks(resultRelInfo->ri_RelationDesc));
/*
* 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).
*/
if (operation == CMD_INSERT || operation == CMD_UPDATE)
{
ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
subplan->targetlist);
j = ExecInitJunkFilterInsertion(subplan->targetlist,
RelationGetDescr(resultRelInfo->ri_RelationDesc),
junkresslot);
}
if (!tle->resjunk)
insertTargetList = lappend(insertTargetList, tle);
else
j = ExecInitJunkFilter(subplan->targetlist,
junkresslot);
need_projection = true;
}
if (operation == CMD_UPDATE || operation == CMD_DELETE)
{
/* For UPDATE/DELETE, find the appropriate junk attr now */
char relkind;
/*
* The junk-free list must produce a tuple suitable for the result
* relation.
*/
ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
insertTargetList);
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");
}
}
/* We'll need a slot matching the table's format. */
resultRelInfo->ri_newTupleSlot =
table_slot_create(resultRelInfo->ri_RelationDesc,
&mtstate->ps.state->es_tupleTable);
resultRelInfo->ri_junkFilter = j;
resultRelInfo++;
/* Build ProjectionInfo if needed (it probably isn't). */
if (need_projection)
{
TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
/* need an expression context to do the projection */
if (mtstate->ps.ps_ExprContext == NULL)
ExecAssignExprContext(estate, &mtstate->ps);
resultRelInfo->ri_projectNew =
ExecBuildProjectionInfo(insertTargetList,
mtstate->ps.ps_ExprContext,
resultRelInfo->ri_newTupleSlot,
&mtstate->ps,
relDesc);
}
}
else
else if (operation == CMD_UPDATE)
{
if (operation == CMD_INSERT)
ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
subplan->targetlist);
List *updateColnos;
TupleDesc relDesc = RelationGetDescr(resultRelInfo->ri_RelationDesc);
updateColnos = (List *) list_nth(node->updateColnosLists, i);
/*
* For UPDATE, we use the old tuple to fill up missing values in
* the tuple produced by the plan to get the new tuple. We need
* two slots, both matching the table's desired format.
*/
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 =
ExecBuildUpdateProjection(subplan->targetlist,
updateColnos,
relDesc,
mtstate->ps.ps_ExprContext,
resultRelInfo->ri_newTupleSlot,
&mtstate->ps);
}
/*
* For UPDATE/DELETE, find the appropriate junk attr now, either a
* 'ctid' or 'wholerow' attribute depending on relkind. For foreign
* tables, the FDW might have created additional junk attr(s), but
* those are no concern of ours.
*/
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_RowIdAttNo =
ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
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. We also require it to be present in
* UPDATE, so we can get the values of unchanged columns.
*/
resultRelInfo->ri_RowIdAttNo =
ExecFindJunkAttributeInTlist(subplan->targetlist,
"wholerow");
if (mtstate->operation == CMD_UPDATE &&
!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
elog(ERROR, "could not find junk wholerow column");
}
else
{
/* Other valid target relkinds must provide wholerow */
resultRelInfo->ri_RowIdAttNo =
ExecFindJunkAttributeInTlist(subplan->targetlist,
"wholerow");
if (!AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo))
elog(ERROR, "could not find junk wholerow column");
}
}
}
/*
* If this is an inherited update/delete, there will be a junk attribute
* named "tableoid" present in the subplan's targetlist. It will be used
* to identify the result relation for a given tuple to be
* updated/deleted.
*/
mtstate->mt_resultOidAttno =
ExecFindJunkAttributeInTlist(subplan->targetlist, "tableoid");
Assert(AttributeNumberIsValid(mtstate->mt_resultOidAttno) || nrels == 1);
mtstate->mt_lastResultOid = InvalidOid; /* force lookup at first tuple */
mtstate->mt_lastResultIndex = 0; /* must be zero if no such attr */
/*
* If there are a lot of result relations, use a hash table to speed the
* lookups. If there are not a lot, a simple linear search is faster.
*
* It's not clear where the threshold is, but try 64 for starters. In a
* debugging build, use a small threshold so that we get some test
* coverage of both code paths.
*/
#ifdef USE_ASSERT_CHECKING
#define MT_NRELS_HASH 4
#else
#define MT_NRELS_HASH 64
#endif
if (nrels >= MT_NRELS_HASH)
{
HASHCTL hash_ctl;
hash_ctl.keysize = sizeof(Oid);
hash_ctl.entrysize = sizeof(MTTargetRelLookup);
hash_ctl.hcxt = CurrentMemoryContext;
mtstate->mt_resultOidHash =
hash_create("ModifyTable target hash",
nrels, &hash_ctl,
HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
for (i = 0; i < nrels; i++)
{
Oid hashkey;
MTTargetRelLookup *mtlookup;
bool found;
resultRelInfo = &mtstate->resultRelInfo[i];
hashkey = RelationGetRelid(resultRelInfo->ri_RelationDesc);
mtlookup = (MTTargetRelLookup *)
hash_search(mtstate->mt_resultOidHash, &hashkey,
HASH_ENTER, &found);
Assert(!found);
mtlookup->relationIndex = i;
}
}
else
mtstate->mt_resultOidHash = NULL;
/*
* Determine if the FDW supports batch insert and determine the batch
* size (a FDW may support batching, but it may be disabled for the
@ -2804,7 +3037,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (operation == CMD_INSERT)
{
resultRelInfo = mtstate->resultRelInfo;
for (i = 0; i < nplans; i++)
for (i = 0; i < nrels; i++)
{
if (!resultRelInfo->ri_usesFdwDirectModify &&
resultRelInfo->ri_FdwRoutine != NULL &&
@ -2853,7 +3086,7 @@ ExecEndModifyTable(ModifyTableState *node)
/*
* Allow any FDWs to shut down
*/
for (i = 0; i < node->mt_nplans; i++)
for (i = 0; i < node->mt_nrels; i++)
{
ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
@ -2893,10 +3126,9 @@ ExecEndModifyTable(ModifyTableState *node)
EvalPlanQualEnd(&node->mt_epqstate);
/*
* shut down subplans
* shut down subplan
*/
for (i = 0; i < node->mt_nplans; i++)
ExecEndNode(node->mt_plans[i]);
ExecEndNode(outerPlanState(node));
}
void

View File

@ -207,7 +207,7 @@ _copyModifyTable(const ModifyTable *from)
COPY_SCALAR_FIELD(rootRelation);
COPY_SCALAR_FIELD(partColsUpdated);
COPY_NODE_FIELD(resultRelations);
COPY_NODE_FIELD(plans);
COPY_NODE_FIELD(updateColnosLists);
COPY_NODE_FIELD(withCheckOptionLists);
COPY_NODE_FIELD(returningLists);
COPY_NODE_FIELD(fdwPrivLists);

View File

@ -4002,12 +4002,6 @@ planstate_tree_walker(PlanState *planstate,
/* special child plans */
switch (nodeTag(plan))
{
case T_ModifyTable:
if (planstate_walk_members(((ModifyTableState *) planstate)->mt_plans,
((ModifyTableState *) planstate)->mt_nplans,
walker, context))
return true;
break;
case T_Append:
if (planstate_walk_members(((AppendState *) planstate)->appendplans,
((AppendState *) planstate)->as_nplans,

View File

@ -408,7 +408,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
WRITE_UINT_FIELD(rootRelation);
WRITE_BOOL_FIELD(partColsUpdated);
WRITE_NODE_FIELD(resultRelations);
WRITE_NODE_FIELD(plans);
WRITE_NODE_FIELD(updateColnosLists);
WRITE_NODE_FIELD(withCheckOptionLists);
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(fdwPrivLists);
@ -2138,14 +2138,14 @@ _outModifyTablePath(StringInfo str, const ModifyTablePath *node)
_outPathInfo(str, (const Path *) node);
WRITE_NODE_FIELD(subpath);
WRITE_ENUM_FIELD(operation, CmdType);
WRITE_BOOL_FIELD(canSetTag);
WRITE_UINT_FIELD(nominalRelation);
WRITE_UINT_FIELD(rootRelation);
WRITE_BOOL_FIELD(partColsUpdated);
WRITE_NODE_FIELD(resultRelations);
WRITE_NODE_FIELD(subpaths);
WRITE_NODE_FIELD(subroots);
WRITE_NODE_FIELD(updateColnosLists);
WRITE_NODE_FIELD(withCheckOptionLists);
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(rowMarks);
@ -2261,7 +2261,10 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node)
WRITE_NODE_FIELD(right_join_clauses);
WRITE_NODE_FIELD(full_join_clauses);
WRITE_NODE_FIELD(join_info_list);
WRITE_BITMAPSET_FIELD(all_result_relids);
WRITE_BITMAPSET_FIELD(leaf_result_relids);
WRITE_NODE_FIELD(append_rel_list);
WRITE_NODE_FIELD(row_identity_vars);
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(placeholder_list);
WRITE_NODE_FIELD(fkey_list);
@ -2271,12 +2274,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_colnos);
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);
@ -2576,6 +2579,17 @@ _outAppendRelInfo(StringInfo str, const AppendRelInfo *node)
WRITE_OID_FIELD(parent_reloid);
}
static void
_outRowIdentityVarInfo(StringInfo str, const RowIdentityVarInfo *node)
{
WRITE_NODE_TYPE("ROWIDENTITYVARINFO");
WRITE_NODE_FIELD(rowidvar);
WRITE_INT_FIELD(rowidwidth);
WRITE_STRING_FIELD(rowidname);
WRITE_BITMAPSET_FIELD(rowidrels);
}
static void
_outPlaceHolderInfo(StringInfo str, const PlaceHolderInfo *node)
{
@ -4235,6 +4249,9 @@ outNode(StringInfo str, const void *obj)
case T_AppendRelInfo:
_outAppendRelInfo(str, obj);
break;
case T_RowIdentityVarInfo:
_outRowIdentityVarInfo(str, obj);
break;
case T_PlaceHolderInfo:
_outPlaceHolderInfo(str, obj);
break;

View File

@ -1685,7 +1685,7 @@ _readModifyTable(void)
READ_UINT_FIELD(rootRelation);
READ_BOOL_FIELD(partColsUpdated);
READ_NODE_FIELD(resultRelations);
READ_NODE_FIELD(plans);
READ_NODE_FIELD(updateColnosLists);
READ_NODE_FIELD(withCheckOptionLists);
READ_NODE_FIELD(returningLists);
READ_NODE_FIELD(fdwPrivLists);

View File

@ -1149,7 +1149,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
Var *parentvar = (Var *) lfirst(parentvars);
Node *childvar = (Node *) lfirst(childvars);
if (IsA(parentvar, Var))
if (IsA(parentvar, Var) && parentvar->varno == parentRTindex)
{
int pndx = parentvar->varattno - rel->min_attr;
int32 child_width = 0;

View File

@ -3398,7 +3398,7 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel)
* and pass them through to EvalPlanQual via a side channel; but for now,
* we just don't remove implied quals at all for target relations.
*/
is_target_rel = (rel->relid == root->parse->resultRelation ||
is_target_rel = (bms_is_member(rel->relid, root->all_result_relids) ||
get_plan_rowmark(root->rowMarks, rel->relid) != NULL);
/*

View File

@ -298,11 +298,12 @@ static SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
static LockRows *make_lockrows(Plan *lefttree, List *rowMarks, int epqParam);
static Result *make_result(List *tlist, Node *resconstantqual, Plan *subplan);
static ProjectSet *make_project_set(List *tlist, Plan *subplan);
static ModifyTable *make_modifytable(PlannerInfo *root,
static ModifyTable *make_modifytable(PlannerInfo *root, Plan *subplan,
CmdType operation, bool canSetTag,
Index nominalRelation, Index rootRelation,
bool partColsUpdated,
List *resultRelations, List *subplans, List *subroots,
List *resultRelations,
List *updateColnosLists,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict, int epqParam);
static GatherMerge *create_gather_merge_plan(PlannerInfo *root,
@ -2292,12 +2293,7 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
/*
* During setrefs.c, we'll need the grouping_map to fix up the cols lists
* in GroupingFunc nodes. Save it for setrefs.c to use.
*
* This doesn't work if we're in an inheritance subtree (see notes in
* create_modifytable_plan). Fortunately we can't be because there would
* never be grouping in an UPDATE/DELETE; but let's Assert that.
*/
Assert(root->inhTargetKind == INHKIND_NONE);
Assert(root->grouping_map == NULL);
root->grouping_map = grouping_map;
@ -2459,12 +2455,7 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path)
* with InitPlan output params. (We can't just do that locally in the
* MinMaxAgg node, because path nodes above here may have Agg references
* as well.) Save the mmaggregates list to tell setrefs.c to do that.
*
* This doesn't work if we're in an inheritance subtree (see notes in
* create_modifytable_plan). Fortunately we can't be because there would
* never be aggregates in an UPDATE/DELETE; but let's Assert that.
*/
Assert(root->inhTargetKind == INHKIND_NONE);
Assert(root->minmax_aggs == NIL);
root->minmax_aggs = best_path->mmaggregates;
@ -2681,46 +2672,24 @@ static ModifyTable *
create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
{
ModifyTable *plan;
List *subplans = NIL;
ListCell *subpaths,
*subroots;
Path *subpath = best_path->subpath;
Plan *subplan;
/* Build the plan for each input path */
forboth(subpaths, best_path->subpaths,
subroots, best_path->subroots)
{
Path *subpath = (Path *) lfirst(subpaths);
PlannerInfo *subroot = (PlannerInfo *) lfirst(subroots);
Plan *subplan;
/* Subplan must produce exactly the specified tlist */
subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
/*
* In an inherited UPDATE/DELETE, reference the per-child modified
* subroot while creating Plans from Paths for the child rel. This is
* a kluge, but otherwise it's too hard to ensure that Plan creation
* functions (particularly in FDWs) don't depend on the contents of
* "root" matching what they saw at Path creation time. The main
* downside is that creation functions for Plans that might appear
* below a ModifyTable cannot expect to modify the contents of "root"
* and have it "stick" for subsequent processing such as setrefs.c.
* That's not great, but it seems better than the alternative.
*/
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);
}
/* Transfer resname/resjunk labeling, too, to keep executor happy */
apply_tlist_labeling(subplan->targetlist, root->processed_tlist);
plan = make_modifytable(root,
subplan,
best_path->operation,
best_path->canSetTag,
best_path->nominalRelation,
best_path->rootRelation,
best_path->partColsUpdated,
best_path->resultRelations,
subplans,
best_path->subroots,
best_path->updateColnosLists,
best_path->withCheckOptionLists,
best_path->returningLists,
best_path->rowMarks,
@ -6916,11 +6885,12 @@ make_project_set(List *tlist,
* Build a ModifyTable plan node
*/
static ModifyTable *
make_modifytable(PlannerInfo *root,
make_modifytable(PlannerInfo *root, Plan *subplan,
CmdType operation, bool canSetTag,
Index nominalRelation, Index rootRelation,
bool partColsUpdated,
List *resultRelations, List *subplans, List *subroots,
List *resultRelations,
List *updateColnosLists,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict, int epqParam)
{
@ -6928,17 +6898,17 @@ make_modifytable(PlannerInfo *root,
List *fdw_private_list;
Bitmapset *direct_modify_plans;
ListCell *lc;
ListCell *lc2;
int i;
Assert(list_length(resultRelations) == list_length(subplans));
Assert(list_length(resultRelations) == list_length(subroots));
Assert(operation == CMD_UPDATE ?
list_length(resultRelations) == list_length(updateColnosLists) :
updateColnosLists == NIL);
Assert(withCheckOptionLists == NIL ||
list_length(resultRelations) == list_length(withCheckOptionLists));
Assert(returningLists == NIL ||
list_length(resultRelations) == list_length(returningLists));
node->plan.lefttree = NULL;
node->plan.lefttree = subplan;
node->plan.righttree = NULL;
node->plan.qual = NIL;
/* setrefs.c will fill in the targetlist, if needed */
@ -6950,7 +6920,6 @@ make_modifytable(PlannerInfo *root,
node->rootRelation = rootRelation;
node->partColsUpdated = partColsUpdated;
node->resultRelations = resultRelations;
node->plans = subplans;
if (!onconflict)
{
node->onConflictAction = ONCONFLICT_NONE;
@ -6977,6 +6946,7 @@ make_modifytable(PlannerInfo *root,
node->exclRelRTI = onconflict->exclRelIndex;
node->exclRelTlist = onconflict->exclRelTlist;
}
node->updateColnosLists = updateColnosLists;
node->withCheckOptionLists = withCheckOptionLists;
node->returningLists = returningLists;
node->rowMarks = rowMarks;
@ -6989,10 +6959,9 @@ make_modifytable(PlannerInfo *root,
fdw_private_list = NIL;
direct_modify_plans = NULL;
i = 0;
forboth(lc, resultRelations, lc2, subroots)
foreach(lc, resultRelations)
{
Index rti = lfirst_int(lc);
PlannerInfo *subroot = lfirst_node(PlannerInfo, lc2);
FdwRoutine *fdwroutine;
List *fdw_private;
bool direct_modify;
@ -7004,16 +6973,16 @@ make_modifytable(PlannerInfo *root,
* so it's not a baserel; and there are also corner cases for
* updatable views where the target rel isn't a baserel.)
*/
if (rti < subroot->simple_rel_array_size &&
subroot->simple_rel_array[rti] != NULL)
if (rti < root->simple_rel_array_size &&
root->simple_rel_array[rti] != NULL)
{
RelOptInfo *resultRel = subroot->simple_rel_array[rti];
RelOptInfo *resultRel = root->simple_rel_array[rti];
fdwroutine = resultRel->fdwroutine;
}
else
{
RangeTblEntry *rte = planner_rt_fetch(rti, subroot);
RangeTblEntry *rte = planner_rt_fetch(rti, root);
Assert(rte->rtekind == RTE_RELATION);
if (rte->relkind == RELKIND_FOREIGN_TABLE)
@ -7036,16 +7005,16 @@ make_modifytable(PlannerInfo *root,
fdwroutine->IterateDirectModify != NULL &&
fdwroutine->EndDirectModify != NULL &&
withCheckOptionLists == NIL &&
!has_row_triggers(subroot, rti, operation) &&
!has_stored_generated_columns(subroot, rti))
direct_modify = fdwroutine->PlanDirectModify(subroot, node, rti, i);
!has_row_triggers(root, rti, operation) &&
!has_stored_generated_columns(root, rti))
direct_modify = fdwroutine->PlanDirectModify(root, node, rti, i);
if (direct_modify)
direct_modify_plans = bms_add_member(direct_modify_plans, i);
if (!direct_modify &&
fdwroutine != NULL &&
fdwroutine->PlanForeignModify != NULL)
fdw_private = fdwroutine->PlanForeignModify(subroot, node, rti, i);
fdw_private = fdwroutine->PlanForeignModify(root, node, rti, i);
else
fdw_private = NIL;
fdw_private_list = lappend(fdw_private_list, fdw_private);

View File

@ -263,6 +263,13 @@ query_planner(PlannerInfo *root,
*/
add_other_rels_to_query(root);
/*
* Distribute any UPDATE/DELETE row identity variables to the target
* relations. This can't be done till we've finished expansion of
* appendrels.
*/
distribute_row_identity_vars(root);
/*
* Ready to do the primary planning.
*/

View File

@ -129,9 +129,7 @@ typedef struct
/* Local functions */
static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind);
static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode);
static void inheritance_planner(PlannerInfo *root);
static void grouping_planner(PlannerInfo *root, bool inheritance_update,
double tuple_fraction);
static void grouping_planner(PlannerInfo *root, double tuple_fraction);
static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root);
static List *remap_to_groupclause_idx(List *groupClause, List *gsets,
int *tleref_to_colnum_map);
@ -615,15 +613,19 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
root->multiexpr_params = NIL;
root->eq_classes = NIL;
root->ec_merging_done = false;
root->all_result_relids =
parse->resultRelation ? bms_make_singleton(parse->resultRelation) : NULL;
root->leaf_result_relids = NULL; /* we'll find out leaf-ness later */
root->append_rel_list = NIL;
root->row_identity_vars = NIL;
root->rowMarks = NIL;
memset(root->upper_rels, 0, sizeof(root->upper_rels));
memset(root->upper_targets, 0, sizeof(root->upper_targets));
root->processed_tlist = NIL;
root->update_colnos = NIL;
root->grouping_map = NULL;
root->minmax_aggs = NIL;
root->qual_security_level = 0;
root->inhTargetKind = INHKIND_NONE;
root->hasPseudoConstantQuals = false;
root->hasAlternativeSubPlans = false;
root->hasRecursion = hasRecursion;
@ -743,6 +745,19 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
list_length(rte->securityQuals));
}
/*
* If we have now verified that the query target relation is
* non-inheriting, mark it as a leaf target.
*/
if (parse->resultRelation)
{
RangeTblEntry *rte = rt_fetch(parse->resultRelation, parse->rtable);
if (!rte->inh)
root->leaf_result_relids =
bms_make_singleton(parse->resultRelation);
}
/*
* Preprocess RowMark information. We need to do this after subquery
* pullup, so that all base relations are present.
@ -999,14 +1014,9 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
remove_useless_result_rtes(root);
/*
* Do the main planning. If we have an inherited target relation, that
* needs special processing, else go straight to grouping_planner.
* Do the main planning.
*/
if (parse->resultRelation &&
rt_fetch(parse->resultRelation, parse->rtable)->inh)
inheritance_planner(root);
else
grouping_planner(root, false, tuple_fraction);
grouping_planner(root, tuple_fraction);
/*
* Capture the set of outer-level param IDs we have access to, for use in
@ -1180,621 +1190,6 @@ preprocess_phv_expression(PlannerInfo *root, Expr *expr)
return (Expr *) preprocess_expression(root, (Node *) expr, EXPRKIND_PHV);
}
/*
* inheritance_planner
* Generate Paths in the case where the result relation is an
* inheritance set.
*
* We have to handle this case differently from cases where a source relation
* is an inheritance set. Source inheritance is expanded at the bottom of the
* plan tree (see allpaths.c), but target inheritance has to be expanded at
* the top. The reason is that for UPDATE, each target relation needs a
* different targetlist matching its own column set. Fortunately,
* the UPDATE/DELETE target can never be the nullable side of an outer join,
* so it's OK to generate the plan this way.
*
* Returns nothing; the useful output is in the Paths we attach to
* the (UPPERREL_FINAL, NULL) upperrel stored in *root.
*
* Note that we have not done set_cheapest() on the final rel; it's convenient
* to leave this to the caller.
*/
static void
inheritance_planner(PlannerInfo *root)
{
Query *parse = root->parse;
int top_parentRTindex = parse->resultRelation;
List *select_rtable;
List *select_appinfos;
List *child_appinfos;
List *old_child_rtis;
List *new_child_rtis;
Bitmapset *subqueryRTindexes;
Index next_subquery_rti;
int nominalRelation = -1;
Index rootRelation = 0;
List *final_rtable = NIL;
List *final_rowmarks = NIL;
List *final_appendrels = NIL;
int save_rel_array_size = 0;
RelOptInfo **save_rel_array = NULL;
AppendRelInfo **save_append_rel_array = NULL;
List *subpaths = NIL;
List *subroots = NIL;
List *resultRelations = NIL;
List *withCheckOptionLists = NIL;
List *returningLists = NIL;
List *rowMarks;
RelOptInfo *final_rel;
ListCell *lc;
ListCell *lc2;
Index rti;
RangeTblEntry *parent_rte;
Bitmapset *parent_relids;
Query **parent_parses;
/* Should only get here for UPDATE or DELETE */
Assert(parse->commandType == CMD_UPDATE ||
parse->commandType == CMD_DELETE);
/*
* We generate a modified instance of the original Query for each target
* relation, plan that, and put all the plans into a list that will be
* controlled by a single ModifyTable node. All the instances share the
* same rangetable, but each instance must have its own set of subquery
* RTEs within the finished rangetable because (1) they are likely to get
* scribbled on during planning, and (2) it's not inconceivable that
* subqueries could get planned differently in different cases. We need
* not create duplicate copies of other RTE kinds, in particular not the
* target relations, because they don't have either of those issues. Not
* having to duplicate the target relations is important because doing so
* (1) would result in a rangetable of length O(N^2) for N targets, with
* at least O(N^3) work expended here; and (2) would greatly complicate
* management of the rowMarks list.
*
* To begin with, generate a bitmapset of the relids of the subquery RTEs.
*/
subqueryRTindexes = NULL;
rti = 1;
foreach(lc, parse->rtable)
{
RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
if (rte->rtekind == RTE_SUBQUERY)
subqueryRTindexes = bms_add_member(subqueryRTindexes, rti);
rti++;
}
/*
* If the parent RTE is a partitioned table, we should use that as the
* nominal target relation, because the RTEs added for partitioned tables
* (including the root parent) as child members of the inheritance set do
* not appear anywhere else in the plan, so the confusion explained below
* for non-partitioning inheritance cases is not possible.
*/
parent_rte = rt_fetch(top_parentRTindex, parse->rtable);
Assert(parent_rte->inh);
if (parent_rte->relkind == RELKIND_PARTITIONED_TABLE)
{
nominalRelation = top_parentRTindex;
rootRelation = top_parentRTindex;
}
/*
* Before generating the real per-child-relation plans, do a cycle of
* planning as though the query were a SELECT. The objective here is to
* find out which child relations need to be processed, using the same
* expansion and pruning logic as for a SELECT. We'll then pull out the
* RangeTblEntry-s generated for the child rels, and make use of the
* AppendRelInfo entries for them to guide the real planning. (This is
* rather inefficient; we could perhaps stop short of making a full Path
* tree. But this whole function is inefficient and slated for
* destruction, so let's not contort query_planner for that.)
*/
{
PlannerInfo *subroot;
/*
* Flat-copy the PlannerInfo to prevent modification of the original.
*/
subroot = makeNode(PlannerInfo);
memcpy(subroot, root, sizeof(PlannerInfo));
/*
* Make a deep copy of the parsetree for this planning cycle to mess
* around with, and change it to look like a SELECT. (Hack alert: the
* target RTE still has updatedCols set if this is an UPDATE, so that
* expand_partitioned_rtentry will correctly update
* subroot->partColsUpdated.)
*/
subroot->parse = copyObject(root->parse);
subroot->parse->commandType = CMD_SELECT;
subroot->parse->resultRelation = 0;
/*
* Ensure the subroot has its own copy of the original
* append_rel_list, since it'll be scribbled on. (Note that at this
* point, the list only contains AppendRelInfos for flattened UNION
* ALL subqueries.)
*/
subroot->append_rel_list = copyObject(root->append_rel_list);
/*
* Better make a private copy of the rowMarks, too.
*/
subroot->rowMarks = copyObject(root->rowMarks);
/* There shouldn't be any OJ info to translate, as yet */
Assert(subroot->join_info_list == NIL);
/* and we haven't created PlaceHolderInfos, either */
Assert(subroot->placeholder_list == NIL);
/* Generate Path(s) for accessing this result relation */
grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ );
/* Extract the info we need. */
select_rtable = subroot->parse->rtable;
select_appinfos = subroot->append_rel_list;
/*
* We need to propagate partColsUpdated back, too. (The later
* planning cycles will not set this because they won't run
* expand_partitioned_rtentry for the UPDATE target.)
*/
root->partColsUpdated = subroot->partColsUpdated;
}
/*----------
* Since only one rangetable can exist in the final plan, we need to make
* sure that it contains all the RTEs needed for any child plan. This is
* complicated by the need to use separate subquery RTEs for each child.
* We arrange the final rtable as follows:
* 1. All original rtable entries (with their original RT indexes).
* 2. All the relation RTEs generated for children of the target table.
* 3. Subquery RTEs for children after the first. We need N * (K - 1)
* RT slots for this, if there are N subqueries and K child tables.
* 4. Additional RTEs generated during the child planning runs, such as
* children of inheritable RTEs other than the target table.
* We assume that each child planning run will create an identical set
* of type-4 RTEs.
*
* So the next thing to do is append the type-2 RTEs (the target table's
* children) to the original rtable. We look through select_appinfos
* to find them.
*
* To identify which AppendRelInfos are relevant as we thumb through
* select_appinfos, we need to look for both direct and indirect children
* of top_parentRTindex, so we use a bitmap of known parent relids.
* expand_inherited_rtentry() always processes a parent before any of that
* parent's children, so we should see an intermediate parent before its
* children.
*----------
*/
child_appinfos = NIL;
old_child_rtis = NIL;
new_child_rtis = NIL;
parent_relids = bms_make_singleton(top_parentRTindex);
foreach(lc, select_appinfos)
{
AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
RangeTblEntry *child_rte;
/* append_rel_list contains all append rels; ignore others */
if (!bms_is_member(appinfo->parent_relid, parent_relids))
continue;
/* remember relevant AppendRelInfos for use below */
child_appinfos = lappend(child_appinfos, appinfo);
/* extract RTE for this child rel */
child_rte = rt_fetch(appinfo->child_relid, select_rtable);
/* and append it to the original rtable */
parse->rtable = lappend(parse->rtable, child_rte);
/* remember child's index in the SELECT rtable */
old_child_rtis = lappend_int(old_child_rtis, appinfo->child_relid);
/* and its new index in the final rtable */
new_child_rtis = lappend_int(new_child_rtis, list_length(parse->rtable));
/* if child is itself partitioned, update parent_relids */
if (child_rte->inh)
{
Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE);
parent_relids = bms_add_member(parent_relids, appinfo->child_relid);
}
}
/*
* It's possible that the RTIs we just assigned for the child rels in the
* final rtable are different from what they were in the SELECT query.
* Adjust the AppendRelInfos so that they will correctly map RT indexes to
* the final indexes. We can do this left-to-right since no child rel's
* final RT index could be greater than what it had in the SELECT query.
*/
forboth(lc, old_child_rtis, lc2, new_child_rtis)
{
int old_child_rti = lfirst_int(lc);
int new_child_rti = lfirst_int(lc2);
if (old_child_rti == new_child_rti)
continue; /* nothing to do */
Assert(old_child_rti > new_child_rti);
ChangeVarNodes((Node *) child_appinfos,
old_child_rti, new_child_rti, 0);
}
/*
* Now set up rangetable entries for subqueries for additional children
* (the first child will just use the original ones). These all have to
* look more or less real, or EXPLAIN will get unhappy; so we just make
* them all clones of the original subqueries.
*/
next_subquery_rti = list_length(parse->rtable) + 1;
if (subqueryRTindexes != NULL)
{
int n_children = list_length(child_appinfos);
while (n_children-- > 1)
{
int oldrti = -1;
while ((oldrti = bms_next_member(subqueryRTindexes, oldrti)) >= 0)
{
RangeTblEntry *subqrte;
subqrte = rt_fetch(oldrti, parse->rtable);
parse->rtable = lappend(parse->rtable, copyObject(subqrte));
}
}
}
/*
* The query for each child is obtained by translating the query for its
* immediate parent, since the AppendRelInfo data we have shows deltas
* between parents and children. We use the parent_parses array to
* remember the appropriate query trees. This is indexed by parent relid.
* Since the maximum number of parents is limited by the number of RTEs in
* the SELECT query, we use that number to allocate the array. An extra
* entry is needed since relids start from 1.
*/
parent_parses = (Query **) palloc0((list_length(select_rtable) + 1) *
sizeof(Query *));
parent_parses[top_parentRTindex] = parse;
/*
* And now we can get on with generating a plan for each child table.
*/
foreach(lc, child_appinfos)
{
AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
Index this_subquery_rti = next_subquery_rti;
Query *parent_parse;
PlannerInfo *subroot;
RangeTblEntry *child_rte;
RelOptInfo *sub_final_rel;
Path *subpath;
/*
* expand_inherited_rtentry() always processes a parent before any of
* that parent's children, so the parent query for this relation
* should already be available.
*/
parent_parse = parent_parses[appinfo->parent_relid];
Assert(parent_parse != NULL);
/*
* We need a working copy of the PlannerInfo so that we can control
* propagation of information back to the main copy.
*/
subroot = makeNode(PlannerInfo);
memcpy(subroot, root, sizeof(PlannerInfo));
/*
* Generate modified query with this rel as target. We first apply
* adjust_appendrel_attrs, which copies the Query and changes
* references to the parent RTE to refer to the current child RTE,
* then fool around with subquery RTEs.
*/
subroot->parse = (Query *)
adjust_appendrel_attrs(subroot,
(Node *) parent_parse,
1, &appinfo);
/*
* If there are securityQuals attached to the parent, move them to the
* child rel (they've already been transformed properly for that).
*/
parent_rte = rt_fetch(appinfo->parent_relid, subroot->parse->rtable);
child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable);
child_rte->securityQuals = parent_rte->securityQuals;
parent_rte->securityQuals = NIL;
/*
* HACK: setting this to a value other than INHKIND_NONE signals to
* relation_excluded_by_constraints() to treat the result relation as
* being an appendrel member.
*/
subroot->inhTargetKind =
(rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED;
/*
* If this child is further partitioned, remember it as a parent.
* Since a partitioned table does not have any data, we don't need to
* create a plan for it, and we can stop processing it here. We do,
* however, need to remember its modified PlannerInfo for use when
* processing its children, since we'll update their varnos based on
* the delta from immediate parent to child, not from top to child.
*
* Note: a very non-obvious point is that we have not yet added
* duplicate subquery RTEs to the subroot's rtable. We mustn't,
* because then its children would have two sets of duplicates,
* confusing matters.
*/
if (child_rte->inh)
{
Assert(child_rte->relkind == RELKIND_PARTITIONED_TABLE);
parent_parses[appinfo->child_relid] = subroot->parse;
continue;
}
/*
* Set the nominal target relation of the ModifyTable node if not
* already done. If the target is a partitioned table, we already set
* nominalRelation to refer to the partition root, above. For
* non-partitioned inheritance cases, we'll use the first child
* relation (even if it's excluded) as the nominal target relation.
* Because of the way expand_inherited_rtentry works, that should be
* the RTE representing the parent table in its role as a simple
* member of the inheritance set.
*
* It would be logically cleaner to *always* use the inheritance
* parent RTE as the nominal relation; but that RTE is not otherwise
* referenced in the plan in the non-partitioned inheritance case.
* Instead the duplicate child RTE created by expand_inherited_rtentry
* is used elsewhere in the plan, so using the original parent RTE
* would give rise to confusing use of multiple aliases in EXPLAIN
* output for what the user will think is the "same" table. OTOH,
* it's not a problem in the partitioned inheritance case, because
* there is no duplicate RTE for the parent.
*/
if (nominalRelation < 0)
nominalRelation = appinfo->child_relid;
/*
* As above, each child plan run needs its own append_rel_list and
* rowmarks, which should start out as pristine copies of the
* originals. There can't be any references to UPDATE/DELETE target
* rels in them; but there could be subquery references, which we'll
* fix up in a moment.
*/
subroot->append_rel_list = copyObject(root->append_rel_list);
subroot->rowMarks = copyObject(root->rowMarks);
/*
* If this isn't the first child Query, adjust Vars and jointree
* entries to reference the appropriate set of subquery RTEs.
*/
if (final_rtable != NIL && subqueryRTindexes != NULL)
{
int oldrti = -1;
while ((oldrti = bms_next_member(subqueryRTindexes, oldrti)) >= 0)
{
Index newrti = next_subquery_rti++;
ChangeVarNodes((Node *) subroot->parse, oldrti, newrti, 0);
ChangeVarNodes((Node *) subroot->append_rel_list,
oldrti, newrti, 0);
ChangeVarNodes((Node *) subroot->rowMarks, oldrti, newrti, 0);
}
}
/* There shouldn't be any OJ info to translate, as yet */
Assert(subroot->join_info_list == NIL);
/* and we haven't created PlaceHolderInfos, either */
Assert(subroot->placeholder_list == NIL);
/* Generate Path(s) for accessing this result relation */
grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ );
/*
* Select cheapest path in case there's more than one. We always run
* modification queries to conclusion, so we care only for the
* cheapest-total path.
*/
sub_final_rel = fetch_upper_rel(subroot, UPPERREL_FINAL, NULL);
set_cheapest(sub_final_rel);
subpath = sub_final_rel->cheapest_total_path;
/*
* If this child rel was excluded by constraint exclusion, exclude it
* from the result plan.
*/
if (IS_DUMMY_REL(sub_final_rel))
continue;
/*
* If this is the first non-excluded child, its post-planning rtable
* becomes the initial contents of final_rtable; otherwise, copy its
* modified subquery RTEs into final_rtable, to ensure we have sane
* copies of those. Also save the first non-excluded child's version
* of the rowmarks list; we assume all children will end up with
* equivalent versions of that. Likewise for append_rel_list.
*/
if (final_rtable == NIL)
{
final_rtable = subroot->parse->rtable;
final_rowmarks = subroot->rowMarks;
final_appendrels = subroot->append_rel_list;
}
else
{
Assert(list_length(final_rtable) ==
list_length(subroot->parse->rtable));
if (subqueryRTindexes != NULL)
{
int oldrti = -1;
while ((oldrti = bms_next_member(subqueryRTindexes, oldrti)) >= 0)
{
Index newrti = this_subquery_rti++;
RangeTblEntry *subqrte;
ListCell *newrticell;
subqrte = rt_fetch(newrti, subroot->parse->rtable);
newrticell = list_nth_cell(final_rtable, newrti - 1);
lfirst(newrticell) = subqrte;
}
}
}
/*
* We need to collect all the RelOptInfos from all child plans into
* the main PlannerInfo, since setrefs.c will need them. We use the
* last child's simple_rel_array, so we have to propagate forward the
* RelOptInfos that were already built in previous children.
*/
Assert(subroot->simple_rel_array_size >= save_rel_array_size);
for (rti = 1; rti < save_rel_array_size; rti++)
{
RelOptInfo *brel = save_rel_array[rti];
if (brel)
subroot->simple_rel_array[rti] = brel;
}
save_rel_array_size = subroot->simple_rel_array_size;
save_rel_array = subroot->simple_rel_array;
save_append_rel_array = subroot->append_rel_array;
/*
* Make sure any initplans from this rel get into the outer list. Note
* we're effectively assuming all children generate the same
* init_plans.
*/
root->init_plans = subroot->init_plans;
/* Build list of sub-paths */
subpaths = lappend(subpaths, subpath);
/* Build list of modified subroots, too */
subroots = lappend(subroots, subroot);
/* Build list of target-relation RT indexes */
resultRelations = lappend_int(resultRelations, appinfo->child_relid);
/* Build lists of per-relation WCO and RETURNING targetlists */
if (parse->withCheckOptions)
withCheckOptionLists = lappend(withCheckOptionLists,
subroot->parse->withCheckOptions);
if (parse->returningList)
returningLists = lappend(returningLists,
subroot->parse->returningList);
Assert(!parse->onConflict);
}
/* Result path must go into outer query's FINAL upperrel */
final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
/*
* We don't currently worry about setting final_rel's consider_parallel
* flag in this case, nor about allowing FDWs or create_upper_paths_hook
* to get control here.
*/
if (subpaths == NIL)
{
/*
* We managed to exclude every child rel, so generate a dummy path
* representing the empty set. Although it's clear that no data will
* be updated or deleted, we will still need to have a ModifyTable
* node so that any statement triggers are executed. (This could be
* cleaner if we fixed nodeModifyTable.c to support zero child nodes,
* but that probably wouldn't be a net win.)
*/
Path *dummy_path;
/* tlist processing never got done, either */
root->processed_tlist = preprocess_targetlist(root);
final_rel->reltarget = create_pathtarget(root, root->processed_tlist);
/* Make a dummy path, cf set_dummy_rel_pathlist() */
dummy_path = (Path *) create_append_path(NULL, final_rel, NIL, NIL,
NIL, NULL, 0, false,
-1);
/* These lists must be nonempty to make a valid ModifyTable node */
subpaths = list_make1(dummy_path);
subroots = list_make1(root);
resultRelations = list_make1_int(parse->resultRelation);
if (parse->withCheckOptions)
withCheckOptionLists = list_make1(parse->withCheckOptions);
if (parse->returningList)
returningLists = list_make1(parse->returningList);
/* Disable tuple routing, too, just to be safe */
root->partColsUpdated = false;
}
else
{
/*
* Put back the final adjusted rtable into the original copy of the
* Query. (We mustn't do this if we found no non-excluded children,
* since we never saved an adjusted rtable at all.)
*/
parse->rtable = final_rtable;
root->simple_rel_array_size = save_rel_array_size;
root->simple_rel_array = save_rel_array;
root->append_rel_array = save_append_rel_array;
/* Must reconstruct original's simple_rte_array, too */
root->simple_rte_array = (RangeTblEntry **)
palloc0((list_length(final_rtable) + 1) * sizeof(RangeTblEntry *));
rti = 1;
foreach(lc, final_rtable)
{
RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
root->simple_rte_array[rti++] = rte;
}
/* Put back adjusted rowmarks and appendrels, too */
root->rowMarks = final_rowmarks;
root->append_rel_list = final_appendrels;
}
/*
* If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will
* have dealt with fetching non-locked marked rows, else we need to have
* ModifyTable do that.
*/
if (parse->rowMarks)
rowMarks = NIL;
else
rowMarks = root->rowMarks;
/* Create Path representing a ModifyTable to do the UPDATE/DELETE work */
add_path(final_rel, (Path *)
create_modifytable_path(root, final_rel,
parse->commandType,
parse->canSetTag,
nominalRelation,
rootRelation,
root->partColsUpdated,
resultRelations,
subpaths,
subroots,
withCheckOptionLists,
returningLists,
rowMarks,
NULL,
assign_special_exec_param(root)));
}
/*--------------------
* grouping_planner
* Perform planning steps related to grouping, aggregation, etc.
@ -1802,11 +1197,6 @@ inheritance_planner(PlannerInfo *root)
* This function adds all required top-level processing to the scan/join
* Path(s) produced by query_planner.
*
* If inheritance_update is true, we're being called from inheritance_planner
* and should not include a ModifyTable step in the resulting Path(s).
* (inheritance_planner will create a single ModifyTable node covering all the
* target tables.)
*
* tuple_fraction is the fraction of tuples we expect will be retrieved.
* tuple_fraction is interpreted as follows:
* 0: expect all tuples to be retrieved (normal case)
@ -1824,8 +1214,7 @@ inheritance_planner(PlannerInfo *root)
*--------------------
*/
static void
grouping_planner(PlannerInfo *root, bool inheritance_update,
double tuple_fraction)
grouping_planner(PlannerInfo *root, double tuple_fraction)
{
Query *parse = root->parse;
int64 offset_est = 0;
@ -1969,7 +1358,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
* that we can transfer its decoration (resnames etc) to the topmost
* tlist of the finished Plan. This is kept in processed_tlist.
*/
root->processed_tlist = preprocess_targetlist(root);
preprocess_targetlist(root);
/*
* Mark all the aggregates with resolved aggtranstypes, and detect
@ -2307,16 +1696,117 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
}
/*
* If this is an INSERT/UPDATE/DELETE, and we're not being called from
* inheritance_planner, add the ModifyTable node.
* If this is an INSERT/UPDATE/DELETE, add the ModifyTable node.
*/
if (parse->commandType != CMD_SELECT && !inheritance_update)
if (parse->commandType != CMD_SELECT)
{
Index rootRelation;
List *withCheckOptionLists;
List *returningLists;
List *resultRelations = NIL;
List *updateColnosLists = NIL;
List *withCheckOptionLists = NIL;
List *returningLists = NIL;
List *rowMarks;
if (bms_membership(root->all_result_relids) == BMS_MULTIPLE)
{
/* Inherited UPDATE/DELETE */
RelOptInfo *top_result_rel = find_base_rel(root,
parse->resultRelation);
int resultRelation = -1;
/* Add only leaf children to ModifyTable. */
while ((resultRelation = bms_next_member(root->leaf_result_relids,
resultRelation)) >= 0)
{
RelOptInfo *this_result_rel = find_base_rel(root,
resultRelation);
/*
* Also exclude any leaf rels that have turned dummy since
* being added to the list, for example, by being excluded
* by constraint exclusion.
*/
if (IS_DUMMY_REL(this_result_rel))
continue;
/* Build per-target-rel lists needed by ModifyTable */
resultRelations = lappend_int(resultRelations,
resultRelation);
if (parse->commandType == CMD_UPDATE)
{
List *update_colnos = root->update_colnos;
if (this_result_rel != top_result_rel)
update_colnos =
adjust_inherited_attnums_multilevel(root,
update_colnos,
this_result_rel->relid,
top_result_rel->relid);
updateColnosLists = lappend(updateColnosLists,
update_colnos);
}
if (parse->withCheckOptions)
{
List *withCheckOptions = parse->withCheckOptions;
if (this_result_rel != top_result_rel)
withCheckOptions = (List *)
adjust_appendrel_attrs_multilevel(root,
(Node *) withCheckOptions,
this_result_rel->relids,
top_result_rel->relids);
withCheckOptionLists = lappend(withCheckOptionLists,
withCheckOptions);
}
if (parse->returningList)
{
List *returningList = parse->returningList;
if (this_result_rel != top_result_rel)
returningList = (List *)
adjust_appendrel_attrs_multilevel(root,
(Node *) returningList,
this_result_rel->relids,
top_result_rel->relids);
returningLists = lappend(returningLists,
returningList);
}
}
if (resultRelations == NIL)
{
/*
* We managed to exclude every child rel, so generate a
* dummy one-relation plan using info for the top target
* rel (even though that may not be a leaf target).
* Although it's clear that no data will be updated or
* deleted, we still need to have a ModifyTable node so
* that any statement triggers will be executed. (This
* could be cleaner if we fixed nodeModifyTable.c to allow
* zero target relations, but that probably wouldn't be a
* net win.)
*/
resultRelations = list_make1_int(parse->resultRelation);
if (parse->commandType == CMD_UPDATE)
updateColnosLists = list_make1(root->update_colnos);
if (parse->withCheckOptions)
withCheckOptionLists = list_make1(parse->withCheckOptions);
if (parse->returningList)
returningLists = list_make1(parse->returningList);
}
}
else
{
/* Single-relation INSERT/UPDATE/DELETE. */
resultRelations = list_make1_int(parse->resultRelation);
if (parse->commandType == CMD_UPDATE)
updateColnosLists = list_make1(root->update_colnos);
if (parse->withCheckOptions)
withCheckOptionLists = list_make1(parse->withCheckOptions);
if (parse->returningList)
returningLists = list_make1(parse->returningList);
}
/*
* If target is a partition root table, we need to mark the
* ModifyTable node appropriately for that.
@ -2327,20 +1817,6 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
else
rootRelation = 0;
/*
* Set up the WITH CHECK OPTION and RETURNING lists-of-lists, if
* needed.
*/
if (parse->withCheckOptions)
withCheckOptionLists = list_make1(parse->withCheckOptions);
else
withCheckOptionLists = NIL;
if (parse->returningList)
returningLists = list_make1(parse->returningList);
else
returningLists = NIL;
/*
* If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node
* will have dealt with fetching non-locked marked rows, else we
@ -2353,14 +1829,14 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
path = (Path *)
create_modifytable_path(root, final_rel,
path,
parse->commandType,
parse->canSetTag,
parse->resultRelation,
rootRelation,
false,
list_make1_int(parse->resultRelation),
list_make1(path),
list_make1(root),
root->partColsUpdated,
resultRelations,
updateColnosLists,
withCheckOptionLists,
returningLists,
rowMarks,

View File

@ -868,6 +868,29 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
set_upper_references(root, plan, rtoffset);
else
{
/*
* The tlist of a childless Result could contain
* unresolved ROWID_VAR Vars, in case it's representing a
* target relation which is completely empty because of
* constraint exclusion. Replace any such Vars by null
* constants, as though they'd been resolved for a leaf
* scan node that doesn't support them. We could have
* fix_scan_expr do this, but since the case is only
* expected to occur here, it seems safer to special-case
* it here and keep the assertions that ROWID_VARs
* shouldn't be seen by fix_scan_expr.
*/
foreach(l, splan->plan.targetlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
Var *var = (Var *) tle->expr;
if (var && IsA(var, Var) && var->varno == ROWID_VAR)
tle->expr = (Expr *) makeNullConst(var->vartype,
var->vartypmod,
var->varcollid);
}
splan->plan.targetlist =
fix_scan_list(root, splan->plan.targetlist,
rtoffset, NUM_EXEC_TLIST(plan));
@ -897,23 +920,20 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
if (splan->returningLists)
{
List *newRL = NIL;
Plan *subplan = outerPlan(splan);
ListCell *lcrl,
*lcrr,
*lcp;
*lcrr;
/*
* Pass each per-subplan returningList through
* Pass each per-resultrel returningList through
* set_returning_clause_references().
*/
Assert(list_length(splan->returningLists) == list_length(splan->resultRelations));
Assert(list_length(splan->returningLists) == list_length(splan->plans));
forthree(lcrl, splan->returningLists,
lcrr, splan->resultRelations,
lcp, splan->plans)
forboth(lcrl, splan->returningLists,
lcrr, splan->resultRelations)
{
List *rlist = (List *) lfirst(lcrl);
Index resultrel = lfirst_int(lcrr);
Plan *subplan = (Plan *) lfirst(lcp);
rlist = set_returning_clause_references(root,
rlist,
@ -983,12 +1003,6 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
rc->rti += rtoffset;
rc->prti += rtoffset;
}
foreach(l, splan->plans)
{
lfirst(l) = set_plan_refs(root,
(Plan *) lfirst(l),
rtoffset);
}
/*
* Append this ModifyTable node's final result relation RT
@ -1792,6 +1806,13 @@ fix_alternative_subplan(PlannerInfo *root, AlternativeSubPlan *asplan,
* choosing the best implementation for AlternativeSubPlans,
* looking up operator opcode info for OpExpr and related nodes,
* and adding OIDs from regclass Const nodes into root->glob->relationOids.
*
* 'node': the expression to be modified
* 'rtoffset': how much to increment varnos by
* 'num_exec': estimated number of executions of expression
*
* The expression tree is either copied-and-modified, or modified in-place
* if that seems safe.
*/
static Node *
fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset, double num_exec)
@ -1840,11 +1861,12 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
Assert(var->varlevelsup == 0);
/*
* We should not see any Vars marked INNER_VAR or OUTER_VAR. But an
* indexqual expression could contain INDEX_VAR Vars.
* We should not see Vars marked INNER_VAR, OUTER_VAR, or ROWID_VAR.
* But an indexqual expression could contain INDEX_VAR Vars.
*/
Assert(var->varno != INNER_VAR);
Assert(var->varno != OUTER_VAR);
Assert(var->varno != ROWID_VAR);
if (!IS_SPECIAL_VARNO(var->varno))
var->varno += context->rtoffset;
if (var->varnosyn > 0)
@ -1907,6 +1929,7 @@ fix_scan_expr_walker(Node *node, fix_scan_expr_context *context)
{
if (node == NULL)
return false;
Assert(!(IsA(node, Var) && ((Var *) node)->varno == ROWID_VAR));
Assert(!IsA(node, PlaceHolderVar));
Assert(!IsA(node, AlternativeSubPlan));
fix_expr_common(context->root, node);

View File

@ -2535,7 +2535,6 @@ finalize_plan(PlannerInfo *root, Plan *plan,
case T_ModifyTable:
{
ModifyTable *mtplan = (ModifyTable *) plan;
ListCell *l;
/* Force descendant scan nodes to reference epqParam */
locally_added_param = mtplan->epqParam;
@ -2550,16 +2549,6 @@ finalize_plan(PlannerInfo *root, Plan *plan,
finalize_primnode((Node *) mtplan->onConflictWhere,
&context);
/* exclRelTlist contains only Vars, doesn't need examination */
foreach(l, mtplan->plans)
{
context.paramids =
bms_add_members(context.paramids,
finalize_plan(root,
(Plan *) lfirst(l),
gather_param,
valid_params,
scan_params));
}
}
break;

View File

@ -920,15 +920,18 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
subroot->multiexpr_params = NIL;
subroot->eq_classes = NIL;
subroot->ec_merging_done = false;
subroot->all_result_relids = NULL;
subroot->leaf_result_relids = NULL;
subroot->append_rel_list = NIL;
subroot->row_identity_vars = NIL;
subroot->rowMarks = NIL;
memset(subroot->upper_rels, 0, sizeof(subroot->upper_rels));
memset(subroot->upper_targets, 0, sizeof(subroot->upper_targets));
subroot->processed_tlist = NIL;
subroot->update_colnos = NIL;
subroot->grouping_map = NULL;
subroot->minmax_aggs = NIL;
subroot->qual_security_level = 0;
subroot->inhTargetKind = INHKIND_NONE;
subroot->hasRecursion = false;
subroot->wt_param_id = -1;
subroot->non_recursive_path = NULL;

View File

@ -3,30 +3,26 @@
* 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 an INSERT, the targetlist must contain an entry for each attribute of
* the target relation in the correct order.
*
* For an UPDATE, the targetlist just contains the expressions for the new
* column values.
*
* For UPDATE and DELETE queries, the targetlist must also contain "junk"
* tlist entries needed to allow the executor to identify the rows to be
* updated or deleted; for example, the ctid of a heap row. (The planner
* adds these; they're not in what we receive from the planner/rewriter.)
*
* For all query types, there can be additional junk tlist entries, such as
* sort keys, Vars needed for a 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
* partially historical, but it's not entirely arbitrary. In particular,
* consider an UPDATE across an inheritance tree. What rewriteTargetListIU
* does need be done only once (because it depends only on the properties of
* the parent relation). What's done here has to be done over again for each
* child relation, because it depends on the properties of the child, which
* might be of a different relation type, or have more columns and/or a
* different column order than the parent.
*
* The fact that rewriteTargetListIU sorts non-resjunk tlist entries by column
* position, which expand_targetlist depends on, violates the above comment
* because the sorting is only valid for the parent relation. In inherited
* UPDATE cases, adjust_inherited_tlist runs in between to take care of fixing
* the tlists for child tables to keep expand_targetlist happy. We do it like
* that because it's faster in typical non-inherited cases.
* partially historical, but it's not entirely arbitrary. The stuff done
* here is closely connected to physical access to tables, whereas the
* rewriter's work is more concerned with SQL semantics.
*
*
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
@ -40,18 +36,17 @@
#include "postgres.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "optimizer/appendinfo.h"
#include "optimizer/optimizer.h"
#include "optimizer/prep.h"
#include "optimizer/tlist.h"
#include "parser/parse_coerce.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteHandler.h"
#include "utils/rel.h"
static List *extract_update_colnos(List *tlist);
static List *expand_targetlist(List *tlist, int command_type,
Index result_relation, Relation rel);
@ -60,12 +55,15 @@ static List *expand_targetlist(List *tlist, int command_type,
* preprocess_targetlist
* Driver for preprocessing the parse tree targetlist.
*
* Returns the new targetlist.
* The preprocessed targetlist is returned in root->processed_tlist.
* Also, if this is an UPDATE, we return a list of target column numbers
* in root->update_colnos. (Resnos in processed_tlist will be consecutive,
* so do not look at that to find out which columns are targets!)
*
* As a side effect, if there's an ON CONFLICT UPDATE clause, its targetlist
* is also preprocessed (and updated in-place).
*/
List *
void
preprocess_targetlist(PlannerInfo *root)
{
Query *parse = root->parse;
@ -99,23 +97,38 @@ preprocess_targetlist(PlannerInfo *root)
Assert(command_type == CMD_SELECT);
/*
* For UPDATE/DELETE, add any junk column(s) needed to allow the executor
* to identify the rows to be updated or deleted. Note that this step
* scribbles on parse->targetList, which is not very desirable, but we
* keep it that way to avoid changing APIs used by FDWs.
*/
if (command_type == CMD_UPDATE || command_type == CMD_DELETE)
rewriteTargetListUD(parse, target_rte, target_relation);
/*
* 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
* In an INSERT, the executor expects the targetlist to match the exact
* order of the target table's attributes, including entries for
* attributes not mentioned in the source query.
*
* In an UPDATE, we don't rearrange the tlist order, but we need to make a
* separate list of the target attribute numbers, in tlist order, and then
* renumber the processed_tlist entries to be consecutive.
*/
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_colnos = extract_update_colnos(tlist);
/*
* For non-inherited UPDATE/DELETE, register any junk column(s) needed to
* allow the executor to identify the rows to be updated or deleted. In
* the inheritance case, we do nothing now, leaving this to be dealt with
* when expand_inherited_rtentry() makes the leaf target relations. (But
* there might not be any leaf target relations, in which case we must do
* this in distribute_row_identity_vars().)
*/
if ((command_type == CMD_UPDATE || command_type == CMD_DELETE) &&
!target_rte->inh)
{
/* row-identity logic expects to add stuff to processed_tlist */
root->processed_tlist = tlist;
add_row_identity_columns(root, result_relation,
target_rte, target_relation);
tlist = root->processed_tlist;
}
/*
* Add necessary junk columns for rowmarked rels. These values are needed
@ -123,6 +136,14 @@ preprocess_targetlist(PlannerInfo *root)
* rechecking. See comments for PlanRowMark in plannodes.h. If you
* change this stanza, see also expand_inherited_rtentry(), which has to
* be able to add on junk columns equivalent to these.
*
* (Someday it might be useful to fold these resjunk columns into the
* row-identity-column management used for UPDATE/DELETE. Today is not
* that day, however. One notable issue is that it seems important that
* the whole-row Vars made here use the real table rowtype, not RECORD, so
* that conversion to/from child relations' rowtypes will happen. Also,
* since these entries don't potentially bloat with more and more child
* relations, there's not really much need for column sharing.)
*/
foreach(lc, root->rowMarks)
{
@ -222,6 +243,8 @@ preprocess_targetlist(PlannerInfo *root)
list_free(vars);
}
root->processed_tlist = tlist;
/*
* If there's an ON CONFLICT UPDATE clause, preprocess its targetlist too
* while we have the relation open.
@ -235,8 +258,35 @@ preprocess_targetlist(PlannerInfo *root)
if (target_relation)
table_close(target_relation, NoLock);
}
return tlist;
/*
* extract_update_colnos
* Extract a list of the target-table column numbers that
* an UPDATE's targetlist wants to assign to, then renumber.
*
* The convention in the parser and rewriter is that the resnos in an
* UPDATE's non-resjunk TLE entries are the target column numbers
* to assign to. Here, we extract that info into a separate list, and
* then convert the tlist to the sequential-numbering convention that's
* used by all other query types.
*/
static List *
extract_update_colnos(List *tlist)
{
List *update_colnos = NIL;
AttrNumber nextresno = 1;
ListCell *lc;
foreach(lc, tlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(lc);
if (!tle->resjunk)
update_colnos = lappend_int(update_colnos, tle->resno);
tle->resno = nextresno++;
}
return update_colnos;
}
@ -251,6 +301,10 @@ preprocess_targetlist(PlannerInfo *root)
* Given a target list as generated by the parser and a result relation,
* add targetlist entries for any missing attributes, and ensure the
* non-junk attributes appear in proper field order.
*
* command_type is a bit of an archaism now: it's CMD_INSERT when we're
* processing an INSERT, all right, but the only other use of this function
* is for ON CONFLICT UPDATE tlists, for which command_type is CMD_UPDATE.
*/
static List *
expand_targetlist(List *tlist, int command_type,

View File

@ -15,9 +15,12 @@
#include "postgres.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "foreign/fdwapi.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/appendinfo.h"
#include "optimizer/pathnode.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
@ -37,8 +40,6 @@ static void make_inh_translation_list(Relation oldrelation,
AppendRelInfo *appinfo);
static Node *adjust_appendrel_attrs_mutator(Node *node,
adjust_appendrel_attrs_context *context);
static List *adjust_inherited_tlist(List *tlist,
AppendRelInfo *context);
/*
@ -194,7 +195,6 @@ Node *
adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos,
AppendRelInfo **appinfos)
{
Node *result;
adjust_appendrel_attrs_context context;
context.root = root;
@ -204,40 +204,10 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos,
/* If there's nothing to adjust, don't call this function. */
Assert(nappinfos >= 1 && appinfos != NULL);
/*
* Must be prepared to start with a Query or a bare expression tree.
*/
if (node && IsA(node, Query))
{
Query *newnode;
int cnt;
/* Should never be translating a Query tree. */
Assert(node == NULL || !IsA(node, Query));
newnode = query_tree_mutator((Query *) node,
adjust_appendrel_attrs_mutator,
(void *) &context,
QTW_IGNORE_RC_SUBQUERIES);
for (cnt = 0; cnt < nappinfos; cnt++)
{
AppendRelInfo *appinfo = appinfos[cnt];
if (newnode->resultRelation == appinfo->parent_relid)
{
newnode->resultRelation = appinfo->child_relid;
/* Fix tlist resnos too, if it's inherited UPDATE */
if (newnode->commandType == CMD_UPDATE)
newnode->targetList =
adjust_inherited_tlist(newnode->targetList,
appinfo);
break;
}
}
result = (Node *) newnode;
}
else
result = adjust_appendrel_attrs_mutator(node, &context);
return result;
return adjust_appendrel_attrs_mutator(node, &context);
}
static Node *
@ -343,6 +313,57 @@ adjust_appendrel_attrs_mutator(Node *node,
}
/* system attributes don't need any other translation */
}
else if (var->varno == ROWID_VAR)
{
/*
* If it's a ROWID_VAR placeholder, see if we've reached a leaf
* target rel, for which we can translate the Var to a specific
* instantiation. We should never be asked to translate to a set
* of relids containing more than one leaf target rel, so the
* answer will be unique. If we're still considering non-leaf
* inheritance levels, return the ROWID_VAR Var as-is.
*/
Relids leaf_result_relids = context->root->leaf_result_relids;
Index leaf_relid = 0;
for (cnt = 0; cnt < nappinfos; cnt++)
{
if (bms_is_member(appinfos[cnt]->child_relid,
leaf_result_relids))
{
if (leaf_relid)
elog(ERROR, "cannot translate to multiple leaf relids");
leaf_relid = appinfos[cnt]->child_relid;
}
}
if (leaf_relid)
{
RowIdentityVarInfo *ridinfo = (RowIdentityVarInfo *)
list_nth(context->root->row_identity_vars, var->varattno - 1);
if (bms_is_member(leaf_relid, ridinfo->rowidrels))
{
/* Substitute the Var given in the RowIdentityVarInfo */
var = copyObject(ridinfo->rowidvar);
/* ... but use the correct relid */
var->varno = leaf_relid;
/* varnosyn in the RowIdentityVarInfo is probably wrong */
var->varnosyn = 0;
var->varattnosyn = 0;
}
else
{
/*
* This leaf rel can't return the desired value, so
* substitute a NULL of the correct type.
*/
return (Node *) makeNullConst(var->vartype,
var->vartypmod,
var->varcollid);
}
}
}
return (Node *) var;
}
if (IsA(node, CurrentOfExpr))
@ -361,44 +382,6 @@ adjust_appendrel_attrs_mutator(Node *node,
}
return (Node *) cexpr;
}
if (IsA(node, RangeTblRef))
{
RangeTblRef *rtr = (RangeTblRef *) copyObject(node);
for (cnt = 0; cnt < nappinfos; cnt++)
{
AppendRelInfo *appinfo = appinfos[cnt];
if (rtr->rtindex == appinfo->parent_relid)
{
rtr->rtindex = appinfo->child_relid;
break;
}
}
return (Node *) rtr;
}
if (IsA(node, JoinExpr))
{
/* Copy the JoinExpr node with correct mutation of subnodes */
JoinExpr *j;
AppendRelInfo *appinfo;
j = (JoinExpr *) expression_tree_mutator(node,
adjust_appendrel_attrs_mutator,
(void *) context);
/* now fix JoinExpr's rtindex (probably never happens) */
for (cnt = 0; cnt < nappinfos; cnt++)
{
appinfo = appinfos[cnt];
if (j->rtindex == appinfo->parent_relid)
{
j->rtindex = appinfo->child_relid;
break;
}
}
return (Node *) j;
}
if (IsA(node, PlaceHolderVar))
{
/* Copy the PlaceHolderVar node with correct mutation of subnodes */
@ -486,6 +469,9 @@ adjust_appendrel_attrs_mutator(Node *node,
*/
Assert(!IsA(node, SubLink));
Assert(!IsA(node, Query));
/* We should never see these Query substructures, either. */
Assert(!IsA(node, RangeTblRef));
Assert(!IsA(node, JoinExpr));
return expression_tree_mutator(node, adjust_appendrel_attrs_mutator,
(void *) context);
@ -621,100 +607,101 @@ adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
}
/*
* Adjust the targetlist entries of an inherited UPDATE operation
*
* The expressions have already been fixed, but we have to make sure that
* the target resnos match the child table (they may not, in the case of
* a column that was added after-the-fact by ALTER TABLE). In some cases
* this can force us to re-order the tlist to preserve resno ordering.
* (We do all this work in special cases so that preptlist.c is fast for
* the typical case.)
*
* The given tlist has already been through expression_tree_mutator;
* therefore the TargetEntry nodes are fresh copies that it's okay to
* scribble on.
*
* Note that this is not needed for INSERT because INSERT isn't inheritable.
* adjust_inherited_attnums
* Translate an integer list of attribute numbers from parent to child.
*/
static List *
adjust_inherited_tlist(List *tlist, AppendRelInfo *context)
List *
adjust_inherited_attnums(List *attnums, AppendRelInfo *context)
{
bool changed_it = false;
ListCell *tl;
List *new_tlist;
bool more;
int attrno;
List *result = NIL;
ListCell *lc;
/* This should only happen for an inheritance case, not UNION ALL */
Assert(OidIsValid(context->parent_reloid));
/* Scan tlist and update resnos to match attnums of child rel */
foreach(tl, tlist)
/* Look up each attribute in the AppendRelInfo's translated_vars list */
foreach(lc, attnums)
{
TargetEntry *tle = (TargetEntry *) lfirst(tl);
AttrNumber parentattno = lfirst_int(lc);
Var *childvar;
if (tle->resjunk)
continue; /* ignore junk items */
/* Look up the translation of this column: it must be a Var */
if (tle->resno <= 0 ||
tle->resno > list_length(context->translated_vars))
if (parentattno <= 0 ||
parentattno > list_length(context->translated_vars))
elog(ERROR, "attribute %d of relation \"%s\" does not exist",
tle->resno, get_rel_name(context->parent_reloid));
childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1);
parentattno, get_rel_name(context->parent_reloid));
childvar = (Var *) list_nth(context->translated_vars, parentattno - 1);
if (childvar == NULL || !IsA(childvar, Var))
elog(ERROR, "attribute %d of relation \"%s\" does not exist",
tle->resno, get_rel_name(context->parent_reloid));
parentattno, get_rel_name(context->parent_reloid));
if (tle->resno != childvar->varattno)
{
tle->resno = childvar->varattno;
changed_it = true;
}
result = lappend_int(result, childvar->varattno);
}
return result;
}
/*
* If we changed anything, re-sort the tlist by resno, and make sure
* resjunk entries have resnos above the last real resno. The sort
* algorithm is a bit stupid, but for such a seldom-taken path, small is
* probably better than fast.
*/
if (!changed_it)
return tlist;
/*
* adjust_inherited_attnums_multilevel
* As above, but traverse multiple inheritance levels as needed.
*/
List *
adjust_inherited_attnums_multilevel(PlannerInfo *root, List *attnums,
Index child_relid, Index top_parent_relid)
{
AppendRelInfo *appinfo = root->append_rel_array[child_relid];
new_tlist = NIL;
more = true;
for (attrno = 1; more; attrno++)
if (!appinfo)
elog(ERROR, "child rel %d not found in append_rel_array", child_relid);
/* Recurse if immediate parent is not the top parent. */
if (appinfo->parent_relid != top_parent_relid)
attnums = adjust_inherited_attnums_multilevel(root, attnums,
appinfo->parent_relid,
top_parent_relid);
/* Now translate for this child */
return adjust_inherited_attnums(attnums, appinfo);
}
/*
* get_translated_update_targetlist
* Get the processed_tlist of an UPDATE query, translated as needed to
* match a child target relation.
*
* Optionally also return the list of target column numbers translated
* to this target relation. (The resnos in processed_tlist MUST NOT be
* relied on for this purpose.)
*/
void
get_translated_update_targetlist(PlannerInfo *root, Index relid,
List **processed_tlist, List **update_colnos)
{
/* This is pretty meaningless for commands other than UPDATE. */
Assert(root->parse->commandType == CMD_UPDATE);
if (relid == root->parse->resultRelation)
{
more = false;
foreach(tl, tlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(tl);
if (tle->resjunk)
continue; /* ignore junk items */
if (tle->resno == attrno)
new_tlist = lappend(new_tlist, tle);
else if (tle->resno > attrno)
more = true;
}
/*
* Non-inheritance case, so it's easy. The caller might be expecting
* a tree it can scribble on, though, so copy.
*/
*processed_tlist = copyObject(root->processed_tlist);
if (update_colnos)
*update_colnos = copyObject(root->update_colnos);
}
foreach(tl, tlist)
else
{
TargetEntry *tle = (TargetEntry *) lfirst(tl);
if (!tle->resjunk)
continue; /* here, ignore non-junk items */
tle->resno = attrno;
new_tlist = lappend(new_tlist, tle);
attrno++;
Assert(bms_is_member(relid, root->all_result_relids));
*processed_tlist = (List *)
adjust_appendrel_attrs_multilevel(root,
(Node *) root->processed_tlist,
bms_make_singleton(relid),
bms_make_singleton(root->parse->resultRelation));
if (update_colnos)
*update_colnos =
adjust_inherited_attnums_multilevel(root, root->update_colnos,
relid,
root->parse->resultRelation);
}
return new_tlist;
}
/*
@ -746,3 +733,270 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
}
return appinfos;
}
/*****************************************************************************
*
* ROW-IDENTITY VARIABLE MANAGEMENT
*
* This code lacks a good home, perhaps. We choose to keep it here because
* adjust_appendrel_attrs_mutator() is its principal co-conspirator. That
* function does most of what is needed to expand ROWID_VAR Vars into the
* right things.
*
*****************************************************************************/
/*
* add_row_identity_var
* Register a row-identity column to be used in UPDATE/DELETE.
*
* The Var must be equal(), aside from varno, to any other row-identity
* column with the same rowid_name. Thus, for example, "wholerow"
* row identities had better use vartype == RECORDOID.
*
* rtindex is currently redundant with rowid_var->varno, but we specify
* it as a separate parameter in case this is ever generalized to support
* non-Var expressions. (We could reasonably handle expressions over
* Vars of the specified rtindex, but for now that seems unnecessary.)
*/
void
add_row_identity_var(PlannerInfo *root, Var *orig_var,
Index rtindex, const char *rowid_name)
{
TargetEntry *tle;
Var *rowid_var;
RowIdentityVarInfo *ridinfo;
ListCell *lc;
/* For now, the argument must be just a Var of the given rtindex */
Assert(IsA(orig_var, Var));
Assert(orig_var->varno == rtindex);
Assert(orig_var->varlevelsup == 0);
/*
* If we're doing non-inherited UPDATE/DELETE, there's little need for
* ROWID_VAR shenanigans. Just shove the presented Var into the
* processed_tlist, and we're done.
*/
if (rtindex == root->parse->resultRelation)
{
tle = makeTargetEntry((Expr *) orig_var,
list_length(root->processed_tlist) + 1,
pstrdup(rowid_name),
true);
root->processed_tlist = lappend(root->processed_tlist, tle);
return;
}
/*
* Otherwise, rtindex should reference a leaf target relation that's being
* added to the query during expand_inherited_rtentry().
*/
Assert(bms_is_member(rtindex, root->leaf_result_relids));
Assert(root->append_rel_array[rtindex] != NULL);
/*
* We have to find a matching RowIdentityVarInfo, or make one if there is
* none. To allow using equal() to match the vars, change the varno to
* ROWID_VAR, leaving all else alone.
*/
rowid_var = copyObject(orig_var);
/* This could eventually become ChangeVarNodes() */
rowid_var->varno = ROWID_VAR;
/* Look for an existing row-id column of the same name */
foreach(lc, root->row_identity_vars)
{
ridinfo = (RowIdentityVarInfo *) lfirst(lc);
if (strcmp(rowid_name, ridinfo->rowidname) != 0)
continue;
if (equal(rowid_var, ridinfo->rowidvar))
{
/* Found a match; we need only record that rtindex needs it too */
ridinfo->rowidrels = bms_add_member(ridinfo->rowidrels, rtindex);
return;
}
else
{
/* Ooops, can't handle this */
elog(ERROR, "conflicting uses of row-identity name \"%s\"",
rowid_name);
}
}
/* No request yet, so add a new RowIdentityVarInfo */
ridinfo = makeNode(RowIdentityVarInfo);
ridinfo->rowidvar = copyObject(rowid_var);
/* for the moment, estimate width using just the datatype info */
ridinfo->rowidwidth = get_typavgwidth(exprType((Node *) rowid_var),
exprTypmod((Node *) rowid_var));
ridinfo->rowidname = pstrdup(rowid_name);
ridinfo->rowidrels = bms_make_singleton(rtindex);
root->row_identity_vars = lappend(root->row_identity_vars, ridinfo);
/* Change rowid_var into a reference to this row_identity_vars entry */
rowid_var->varattno = list_length(root->row_identity_vars);
/* Push the ROWID_VAR reference variable into processed_tlist */
tle = makeTargetEntry((Expr *) rowid_var,
list_length(root->processed_tlist) + 1,
pstrdup(rowid_name),
true);
root->processed_tlist = lappend(root->processed_tlist, tle);
}
/*
* add_row_identity_columns
*
* This function adds the row identity columns needed by the core code.
* FDWs might call add_row_identity_var() for themselves to add nonstandard
* columns. (Duplicate requests are fine.)
*/
void
add_row_identity_columns(PlannerInfo *root, Index rtindex,
RangeTblEntry *target_rte,
Relation target_relation)
{
CmdType commandType = root->parse->commandType;
char relkind = target_relation->rd_rel->relkind;
Var *var;
Assert(commandType == CMD_UPDATE || commandType == CMD_DELETE);
if (relkind == RELKIND_RELATION ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Emit CTID so that executor can find the row to update or delete.
*/
var = makeVar(rtindex,
SelfItemPointerAttributeNumber,
TIDOID,
-1,
InvalidOid,
0);
add_row_identity_var(root, var, rtindex, "ctid");
}
else if (relkind == RELKIND_FOREIGN_TABLE)
{
/*
* Let the foreign table's FDW add whatever junk TLEs it wants.
*/
FdwRoutine *fdwroutine;
fdwroutine = GetFdwRoutineForRelation(target_relation, false);
if (fdwroutine->AddForeignUpdateTargets != NULL)
fdwroutine->AddForeignUpdateTargets(root, rtindex,
target_rte, target_relation);
/*
* For UPDATE, we need to make the FDW fetch unchanged columns by
* asking it to fetch a whole-row Var. That's because the top-level
* targetlist only contains entries for changed columns, but
* ExecUpdate will need to build the complete new tuple. (Actually,
* we only really need this in UPDATEs that are not pushed to the
* remote side, but it's hard to tell if that will be the case at the
* point when this function is called.)
*
* We will also need the whole row if there are any row triggers, so
* that the executor will have the "old" row to pass to the trigger.
* Alas, this misses system columns.
*/
if (commandType == CMD_UPDATE ||
(target_relation->trigdesc &&
(target_relation->trigdesc->trig_delete_after_row ||
target_relation->trigdesc->trig_delete_before_row)))
{
var = makeVar(rtindex,
InvalidAttrNumber,
RECORDOID,
-1,
InvalidOid,
0);
add_row_identity_var(root, var, rtindex, "wholerow");
}
}
}
/*
* distribute_row_identity_vars
*
* After we have finished identifying all the row identity columns
* needed by an inherited UPDATE/DELETE query, make sure that these
* columns will be generated by all the target relations.
*
* This is more or less like what build_base_rel_tlists() does,
* except that it would not understand what to do with ROWID_VAR Vars.
* Since that function runs before inheritance relations are expanded,
* it will never see any such Vars anyway.
*/
void
distribute_row_identity_vars(PlannerInfo *root)
{
Query *parse = root->parse;
int result_relation = parse->resultRelation;
RangeTblEntry *target_rte;
RelOptInfo *target_rel;
ListCell *lc;
/* There's nothing to do if this isn't an inherited UPDATE/DELETE. */
if (parse->commandType != CMD_UPDATE && parse->commandType != CMD_DELETE)
{
Assert(root->row_identity_vars == NIL);
return;
}
target_rte = rt_fetch(result_relation, parse->rtable);
if (!target_rte->inh)
{
Assert(root->row_identity_vars == NIL);
return;
}
/*
* Ordinarily, we expect that leaf result relation(s) will have added some
* ROWID_VAR Vars to the query. However, it's possible that constraint
* exclusion suppressed every leaf relation. The executor will get upset
* if the plan has no row identity columns at all, even though it will
* certainly process no rows. Handle this edge case by re-opening the top
* result relation and adding the row identity columns it would have used,
* as preprocess_targetlist() would have done if it weren't marked "inh".
* (This is a bit ugly, but it seems better to confine the ugliness and
* extra cycles to this unusual corner case.) We needn't worry about
* fixing the rel's reltarget, as that won't affect the finished plan.
*/
if (root->row_identity_vars == NIL)
{
Relation target_relation;
target_relation = table_open(target_rte->relid, NoLock);
add_row_identity_columns(root, result_relation,
target_rte, target_relation);
table_close(target_relation, NoLock);
return;
}
/*
* Dig through the processed_tlist to find the ROWID_VAR reference Vars,
* and forcibly copy them into the reltarget list of the topmost target
* relation. That's sufficient because they'll be copied to the
* individual leaf target rels (with appropriate translation) later,
* during appendrel expansion --- see set_append_rel_size().
*/
target_rel = find_base_rel(root, result_relation);
foreach(lc, root->processed_tlist)
{
TargetEntry *tle = lfirst(lc);
Var *var = (Var *) tle->expr;
if (var && IsA(var, Var) && var->varno == ROWID_VAR)
{
target_rel->reltarget->exprs =
lappend(target_rel->reltarget->exprs, copyObject(var));
/* reltarget cost and width will be computed later */
}
}
}

View File

@ -219,6 +219,10 @@ expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
* targetlist and update parent rel's reltarget. This should match what
* preprocess_targetlist() would have added if the mark types had been
* requested originally.
*
* (Someday it might be useful to fold these resjunk columns into the
* row-identity-column management used for UPDATE/DELETE. Today is not
* that day, however.)
*/
if (oldrc)
{
@ -585,6 +589,46 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
root->rowMarks = lappend(root->rowMarks, childrc);
}
/*
* If we are creating a child of the query target relation (only possible
* in UPDATE/DELETE), add it to all_result_relids, as well as
* leaf_result_relids if appropriate, and make sure that we generate
* required row-identity data.
*/
if (bms_is_member(parentRTindex, root->all_result_relids))
{
/* OK, record the child as a result rel too. */
root->all_result_relids = bms_add_member(root->all_result_relids,
childRTindex);
/* Non-leaf partitions don't need any row identity info. */
if (childrte->relkind != RELKIND_PARTITIONED_TABLE)
{
Var *rrvar;
root->leaf_result_relids = bms_add_member(root->leaf_result_relids,
childRTindex);
/*
* If we have any child target relations, assume they all need to
* generate a junk "tableoid" column. (If only one child survives
* pruning, we wouldn't really need this, but it's not worth
* thrashing about to avoid it.)
*/
rrvar = makeVar(childRTindex,
TableOidAttributeNumber,
OIDOID,
-1,
InvalidOid,
0);
add_row_identity_var(root, rrvar, childRTindex, "tableoid");
/* Register any row-identity columns needed by this child. */
add_row_identity_columns(root, childRTindex,
childrte, childrel);
}
}
}
/*

View File

@ -3540,6 +3540,7 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
* Creates a pathnode that represents performing INSERT/UPDATE/DELETE mods
*
* 'rel' is the parent relation associated with the result
* 'subpath' is a Path producing source data
* 'operation' is the operation type
* 'canSetTag' is true if we set the command tag/es_processed
* 'nominalRelation' is the parent RT index for use of EXPLAIN
@ -3547,8 +3548,8 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
* 'partColsUpdated' is true if any partitioning columns are being updated,
* either from the target relation or a descendent partitioned table.
* 'resultRelations' is an integer list of actual RT indexes of target rel(s)
* 'subpaths' is a list of Path(s) producing source data (one per rel)
* 'subroots' is a list of PlannerInfo structs (one per rel)
* 'updateColnosLists' is a list of UPDATE target column number lists
* (one sublist per rel); or NIL if not an UPDATE
* '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)
@ -3557,21 +3558,21 @@ create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
*/
ModifyTablePath *
create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath,
CmdType operation, bool canSetTag,
Index nominalRelation, Index rootRelation,
bool partColsUpdated,
List *resultRelations, List *subpaths,
List *subroots,
List *resultRelations,
List *updateColnosLists,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict,
int epqParam)
{
ModifyTablePath *pathnode = makeNode(ModifyTablePath);
double total_size;
ListCell *lc;
Assert(list_length(resultRelations) == list_length(subpaths));
Assert(list_length(resultRelations) == list_length(subroots));
Assert(operation == CMD_UPDATE ?
list_length(resultRelations) == list_length(updateColnosLists) :
updateColnosLists == NIL);
Assert(withCheckOptionLists == NIL ||
list_length(resultRelations) == list_length(withCheckOptionLists));
Assert(returningLists == NIL ||
@ -3589,7 +3590,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
pathnode->path.pathkeys = NIL;
/*
* Compute cost & rowcount as sum of subpath costs & rowcounts.
* Compute cost & rowcount as subpath cost & rowcount (if RETURNING)
*
* Currently, we don't charge anything extra for the actual table
* modification work, nor for the WITH CHECK OPTIONS or RETURNING
@ -3598,42 +3599,34 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
* costs to change any higher-level planning choices. But we might want
* to make it look better sometime.
*/
pathnode->path.startup_cost = 0;
pathnode->path.total_cost = 0;
pathnode->path.rows = 0;
total_size = 0;
foreach(lc, subpaths)
pathnode->path.startup_cost = subpath->startup_cost;
pathnode->path.total_cost = subpath->total_cost;
if (returningLists != NIL)
{
Path *subpath = (Path *) lfirst(lc);
pathnode->path.rows = subpath->rows;
if (lc == list_head(subpaths)) /* first node? */
pathnode->path.startup_cost = subpath->startup_cost;
pathnode->path.total_cost += subpath->total_cost;
if (returningLists != NIL)
{
pathnode->path.rows += subpath->rows;
total_size += subpath->pathtarget->width * subpath->rows;
}
/*
* Set width to match the subpath output. XXX this is totally wrong:
* we should return an average of the RETURNING tlist widths. But
* it's what happened historically, and improving it is a task for
* another day. (Again, it's mostly window dressing.)
*/
pathnode->path.pathtarget->width = subpath->pathtarget->width;
}
else
{
pathnode->path.rows = 0;
pathnode->path.pathtarget->width = 0;
}
/*
* Set width to the average width of the subpath outputs. XXX this is
* totally wrong: we should return an average of the RETURNING tlist
* widths. But it's what happened historically, and improving it is a task
* for another day.
*/
if (pathnode->path.rows > 0)
total_size /= pathnode->path.rows;
pathnode->path.pathtarget->width = rint(total_size);
pathnode->subpath = subpath;
pathnode->operation = operation;
pathnode->canSetTag = canSetTag;
pathnode->nominalRelation = nominalRelation;
pathnode->rootRelation = rootRelation;
pathnode->partColsUpdated = partColsUpdated;
pathnode->resultRelations = resultRelations;
pathnode->subpaths = subpaths;
pathnode->subroots = subroots;
pathnode->updateColnosLists = updateColnosLists;
pathnode->withCheckOptionLists = withCheckOptionLists;
pathnode->returningLists = returningLists;
pathnode->rowMarks = rowMarks;

View File

@ -1515,18 +1515,11 @@ relation_excluded_by_constraints(PlannerInfo *root,
/*
* When constraint_exclusion is set to 'partition' we only handle
* appendrel members. Normally, they are RELOPT_OTHER_MEMBER_REL
* relations, but we also consider inherited target relations as
* appendrel members for the purposes of constraint exclusion
* (since, indeed, they were appendrel members earlier in
* inheritance_planner).
*
* In both cases, partition pruning was already applied, so there
* is no need to consider the rel's partition constraints here.
* appendrel members. Partition pruning has already been applied,
* so there is no need to consider the rel's partition constraints
* here.
*/
if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL ||
(rel->relid == root->parse->resultRelation &&
root->inhTargetKind != INHKIND_NONE))
if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
break; /* appendrel member, so process it */
return false;
@ -1539,9 +1532,7 @@ relation_excluded_by_constraints(PlannerInfo *root,
* its partition constraints haven't been considered yet, so
* include them in the processing here.
*/
if (rel->reloptkind == RELOPT_BASEREL &&
!(rel->relid == root->parse->resultRelation &&
root->inhTargetKind != INHKIND_NONE))
if (rel->reloptkind == RELOPT_BASEREL)
include_partition = true;
break; /* always try to exclude */
}

View File

@ -977,8 +977,6 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
foreach(vars, input_rel->reltarget->exprs)
{
Var *var = (Var *) lfirst(vars);
RelOptInfo *baserel;
int ndx;
/*
* Ignore PlaceHolderVars in the input tlists; we'll make our own
@ -996,17 +994,35 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
elog(ERROR, "unexpected node type in rel targetlist: %d",
(int) nodeTag(var));
/* Get the Var's original base rel */
baserel = find_base_rel(root, var->varno);
/* Is it still needed above this joinrel? */
ndx = var->varattno - baserel->min_attr;
if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
if (var->varno == ROWID_VAR)
{
/* Yup, add it to the output */
joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs, var);
/* UPDATE/DELETE row identity vars are always needed */
RowIdentityVarInfo *ridinfo = (RowIdentityVarInfo *)
list_nth(root->row_identity_vars, var->varattno - 1);
joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs,
var);
/* Vars have cost zero, so no need to adjust reltarget->cost */
joinrel->reltarget->width += baserel->attr_widths[ndx];
joinrel->reltarget->width += ridinfo->rowidwidth;
}
else
{
RelOptInfo *baserel;
int ndx;
/* Get the Var's original base rel */
baserel = find_base_rel(root, var->varno);
/* Is it still needed above this joinrel? */
ndx = var->varattno - baserel->min_attr;
if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
{
/* Yup, add it to the output */
joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs,
var);
/* Vars have cost zero, so no need to adjust reltarget->cost */
joinrel->reltarget->width += baserel->attr_widths[ndx];
}
}
}
}

View File

@ -705,16 +705,9 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index)
*
* We must do items 1,2,3 before firing rewrite rules, else rewritten
* references to NEW.foo will produce wrong or incomplete results. Item 4
* is not needed for rewriting, but will be needed by the planner, and we
* is not needed for rewriting, but it is helpful for the planner, and we
* can do it essentially for free while handling the other items.
*
* Note that for an inheritable UPDATE, this processing is only done once,
* using the parent relation as reference. It must not do anything that
* will not be correct when transposed to the child relation(s). (Step 4
* is incorrect by this light, since child relations might have different
* column ordering, but the planner will fix things by re-sorting the tlist
* for each child.)
*
* If values_rte is non-NULL (i.e., we are doing a multi-row INSERT using
* values from a VALUES RTE), we populate *unused_values_attrnos with the
* attribute numbers of any unused columns from the VALUES RTE. This can
@ -1607,90 +1600,6 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti,
}
/*
* rewriteTargetListUD - rewrite UPDATE/DELETE targetlist as needed
*
* This function adds a "junk" TLE that is needed to allow the executor to
* find the original row for the update or delete. When the target relation
* is a regular table, the junk TLE emits the ctid attribute of the original
* row. When the target relation is a foreign table, we let the FDW decide
* what to add.
*
* We used to do this during RewriteQuery(), but now that inheritance trees
* can contain a mix of regular and foreign tables, we must postpone it till
* planning, after the inheritance tree has been expanded. In that way we
* can do the right thing for each child table.
*/
void
rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
Relation target_relation)
{
Var *var = NULL;
const char *attrname;
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW ||
target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* Emit CTID so that executor can find the row to update or delete.
*/
var = makeVar(parsetree->resultRelation,
SelfItemPointerAttributeNumber,
TIDOID,
-1,
InvalidOid,
0);
attrname = "ctid";
}
else if (target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
{
/*
* Let the foreign table's FDW add whatever junk TLEs it wants.
*/
FdwRoutine *fdwroutine;
fdwroutine = GetFdwRoutineForRelation(target_relation, false);
if (fdwroutine->AddForeignUpdateTargets != NULL)
fdwroutine->AddForeignUpdateTargets(parsetree, 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.
*/
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))))
{
var = makeWholeRowVar(target_rte,
parsetree->resultRelation,
0,
false);
attrname = "wholerow";
}
}
if (var != NULL)
{
tle = makeTargetEntry((Expr *) var,
list_length(parsetree->targetList) + 1,
pstrdup(attrname),
true);
parsetree->targetList = lappend(parsetree->targetList, tle);
}
}
/*
* Record in target_rte->extraUpdatedCols the indexes of any generated columns
* that depend on any columns mentioned in target_rte->updatedCols.

View File

@ -4741,16 +4741,12 @@ set_deparse_plan(deparse_namespace *dpns, Plan *plan)
* We special-case Append and MergeAppend to pretend that the first child
* plan is the OUTER referent; we have to interpret OUTER Vars in their
* tlists according to one of the children, and the first one is the most
* natural choice. Likewise special-case ModifyTable to pretend that the
* first child plan is the OUTER referent; this is to support RETURNING
* lists containing references to non-target relations.
* natural choice.
*/
if (IsA(plan, Append))
dpns->outer_plan = linitial(((Append *) plan)->appendplans);
else if (IsA(plan, MergeAppend))
dpns->outer_plan = linitial(((MergeAppend *) plan)->mergeplans);
else if (IsA(plan, ModifyTable))
dpns->outer_plan = linitial(((ModifyTable *) plan)->plans);
else
dpns->outer_plan = outerPlan(plan);

View File

@ -156,9 +156,6 @@ extern void ResetTupleHashTable(TupleHashTable hashtable);
*/
extern JunkFilter *ExecInitJunkFilter(List *targetList,
TupleTableSlot *slot);
extern JunkFilter *ExecInitJunkFilterInsertion(List *targetList,
TupleDesc cleanTupType,
TupleTableSlot *slot);
extern JunkFilter *ExecInitJunkFilterConversion(List *targetList,
TupleDesc cleanTupType,
TupleTableSlot *slot);
@ -166,11 +163,24 @@ extern AttrNumber ExecFindJunkAttribute(JunkFilter *junkfilter,
const char *attrName);
extern AttrNumber ExecFindJunkAttributeInTlist(List *targetlist,
const char *attrName);
extern Datum ExecGetJunkAttribute(TupleTableSlot *slot, AttrNumber attno,
bool *isNull);
extern TupleTableSlot *ExecFilterJunk(JunkFilter *junkfilter,
TupleTableSlot *slot);
/*
* ExecGetJunkAttribute
*
* Given a junk filter's input tuple (slot) and a junk attribute's number
* previously found by ExecFindJunkAttribute, extract & return the value and
* isNull flag of the attribute.
*/
#ifndef FRONTEND
static inline Datum
ExecGetJunkAttribute(TupleTableSlot *slot, AttrNumber attno, bool *isNull)
{
Assert(attno > 0);
return slot_getattr(slot, attno, isNull);
}
#endif
/*
* prototypes from functions in execMain.c
@ -270,6 +280,12 @@ extern ProjectionInfo *ExecBuildProjectionInfo(List *targetList,
TupleTableSlot *slot,
PlanState *parent,
TupleDesc inputDesc);
extern ProjectionInfo *ExecBuildUpdateProjection(List *subTargetList,
List *targetColnos,
TupleDesc relDesc,
ExprContext *econtext,
TupleTableSlot *slot,
PlanState *parent);
extern ExprState *ExecPrepareExpr(Expr *node, EState *estate);
extern ExprState *ExecPrepareQual(List *qual, EState *estate);
extern ExprState *ExecPrepareCheck(List *qual, EState *estate);
@ -622,4 +638,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 */

View File

@ -65,7 +65,8 @@ typedef void (*GetForeignUpperPaths_function) (PlannerInfo *root,
RelOptInfo *output_rel,
void *extra);
typedef void (*AddForeignUpdateTargets_function) (Query *parsetree,
typedef void (*AddForeignUpdateTargets_function) (PlannerInfo *root,
Index rtindex,
RangeTblEntry *target_rte,
Relation target_relation);

View File

@ -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;
/*
@ -423,6 +418,19 @@ typedef struct ResultRelInfo
/* array of key/attr info for indices */
IndexInfo **ri_IndexRelationInfo;
/*
* For UPDATE/DELETE result relations, the attribute number of the row
* identity junk attribute in the source plan's output tuples
*/
AttrNumber ri_RowIdAttNo;
/* Projection to generate new tuple in an INSERT/UPDATE */
ProjectionInfo *ri_projectNew;
/* Slot to hold that tuple */
TupleTableSlot *ri_newTupleSlot;
/* Slot to hold the old tuple being updated */
TupleTableSlot *ri_oldTupleSlot;
/* triggers to be fired, if any */
TriggerDesc *ri_TrigDesc;
@ -470,9 +478,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;
@ -677,10 +682,7 @@ typedef struct ExecRowMark
* Each LockRows and ModifyTable node keeps a list of the rowmarks it needs to
* deal with. In addition to a pointer to the related entry in es_rowmarks,
* this struct carries the column number(s) of the resjunk columns associated
* with the rowmark (see comments for PlanRowMark for more detail). In the
* case of ModifyTable, there has to be a separate ExecAuxRowMark list for
* each child plan, because the resjunk columns could be at different physical
* column positions in different subplans.
* with the rowmark (see comments for PlanRowMark for more detail).
*/
typedef struct ExecAuxRowMark
{
@ -1082,9 +1084,8 @@ typedef struct PlanState
* EvalPlanQualSlot), and/or found using the rowmark mechanism (non-locking
* rowmarks by the EPQ machinery itself, locking ones by the caller).
*
* While the plan to be checked may be changed using EvalPlanQualSetPlan() -
* e.g. so all source plans for a ModifyTable node can be processed - all such
* plans need to share the same EState.
* While the plan to be checked may be changed using EvalPlanQualSetPlan(),
* all such plans need to share the same EState.
*/
typedef struct EPQState
{
@ -1178,23 +1179,31 @@ typedef struct ModifyTableState
CmdType operation; /* INSERT, UPDATE, or DELETE */
bool canSetTag; /* do we set the command tag/es_processed? */
bool mt_done; /* are we done? */
PlanState **mt_plans; /* subplans (one per target rel) */
int mt_nplans; /* number of plans in the array */
int mt_whichplan; /* which one is being executed (0..n-1) */
TupleTableSlot **mt_scans; /* input tuple corresponding to underlying
* plans */
ResultRelInfo *resultRelInfo; /* per-subplan target relations */
int mt_nrels; /* number of entries in resultRelInfo[] */
ResultRelInfo *resultRelInfo; /* info about target relation(s) */
/*
* Target relation mentioned in the original statement, used to fire
* statement-level triggers and as the root for tuple routing.
* statement-level triggers and as the root for tuple routing. (This
* might point to one of the resultRelInfo[] entries, but it can also be a
* distinct struct.)
*/
ResultRelInfo *rootResultRelInfo;
List **mt_arowmarks; /* per-subplan ExecAuxRowMark lists */
EPQState mt_epqstate; /* for evaluating EvalPlanQual rechecks */
bool fireBSTriggers; /* do we need to fire stmt triggers? */
/*
* These fields are used for inherited UPDATE and DELETE, to track which
* target relation a given tuple is from. If there are a lot of target
* relations, we use a hash table to translate table OIDs to
* resultRelInfo[] indexes; otherwise mt_resultOidHash is NULL.
*/
int mt_resultOidAttno; /* resno of "tableoid" junk attr */
Oid mt_lastResultOid; /* last-seen value of tableoid */
int mt_lastResultIndex; /* corresponding index in resultRelInfo[] */
HTAB *mt_resultOidHash; /* optional hash table to speed lookups */
/*
* Slot for storing tuples in the root partitioned table's rowtype during
* an UPDATE of a partitioned table.

View File

@ -270,6 +270,7 @@ typedef enum NodeTag
T_PlaceHolderVar,
T_SpecialJoinInfo,
T_AppendRelInfo,
T_RowIdentityVarInfo,
T_PlaceHolderInfo,
T_MinMaxAggInfo,
T_PlannerParamItem,

View File

@ -77,18 +77,6 @@ typedef enum UpperRelationKind
/* NB: UPPERREL_FINAL must be last enum entry; it's used to size arrays */
} UpperRelationKind;
/*
* This enum identifies which type of relation is being planned through the
* inheritance planner. INHKIND_NONE indicates the inheritance planner
* was not used.
*/
typedef enum InheritanceKind
{
INHKIND_NONE,
INHKIND_INHERITED,
INHKIND_PARTITIONED
} InheritanceKind;
/*----------
* PlannerGlobal
* Global information for planning/optimization
@ -276,6 +264,17 @@ struct PlannerInfo
List *join_info_list; /* list of SpecialJoinInfos */
/*
* all_result_relids is empty for SELECT, otherwise it contains at least
* parse->resultRelation. For UPDATE/DELETE across an inheritance or
* partitioning tree, the result rel's child relids are added. When using
* multi-level partitioning, intermediate partitioned rels are included.
* leaf_result_relids is similar except that only actual result tables,
* not partitioned tables, are included in it.
*/
Relids all_result_relids; /* set of all result relids */
Relids leaf_result_relids; /* set of all leaf relids */
/*
* Note: for AppendRelInfos describing partitions of a partitioned table,
* we guarantee that partitions that come earlier in the partitioned
@ -283,6 +282,8 @@ struct PlannerInfo
*/
List *append_rel_list; /* list of AppendRelInfos */
List *row_identity_vars; /* list of RowIdentityVarInfos */
List *rowMarks; /* list of PlanRowMarks */
List *placeholder_list; /* list of PlaceHolderInfos */
@ -309,15 +310,23 @@ 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()
* 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.)
* 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
* that 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 list contains the target table's attribute numbers to
* which the first N entries of processed_tlist are to be assigned. (Any
* additional entries in processed_tlist must be resjunk.) DO NOT use the
* resnos in processed_tlist to identify the UPDATE target columns.
*/
List *update_colnos;
/* Fields filled during create_plan() for use in setrefs.c */
AttrNumber *grouping_map; /* for GroupingFunc fixup */
List *minmax_aggs; /* List of MinMaxAggInfos */
@ -333,9 +342,6 @@ struct PlannerInfo
Index qual_security_level; /* minimum security_level for quals */
/* Note: qual_security_level is zero if there are no securityQuals */
InheritanceKind inhTargetKind; /* indicates if the target relation is an
* inheritance child or partition or a
* partitioned table */
bool hasJoinRTEs; /* true if any RTEs are RTE_JOIN kind */
bool hasLateralRTEs; /* true if any RTEs are marked LATERAL */
bool hasHavingQual; /* true if havingQual was non-null */
@ -1839,20 +1845,20 @@ typedef struct LockRowsPath
* ModifyTablePath represents performing INSERT/UPDATE/DELETE modifications
*
* We represent most things that will be in the ModifyTable plan node
* literally, except we have child Path(s) not Plan(s). But analysis of the
* literally, except we have a child Path not Plan. But analysis of the
* OnConflictExpr is deferred to createplan.c, as is collection of FDW data.
*/
typedef struct ModifyTablePath
{
Path path;
Path *subpath; /* Path producing source data */
CmdType operation; /* INSERT, UPDATE, or DELETE */
bool canSetTag; /* do we set the command tag/es_processed? */
Index nominalRelation; /* Parent RT index for use of EXPLAIN */
Index rootRelation; /* Root RT index, if target is partitioned */
bool partColsUpdated; /* some part key in hierarchy updated */
bool partColsUpdated; /* some part key in hierarchy updated? */
List *resultRelations; /* integer list of RT indexes */
List *subpaths; /* Path(s) producing source data */
List *subroots; /* per-target-table PlannerInfos */
List *updateColnosLists; /* per-target-table update_colnos lists */
List *withCheckOptionLists; /* per-target-table WCO lists */
List *returningLists; /* per-target-table RETURNING tlists */
List *rowMarks; /* PlanRowMarks (non-locking only) */
@ -2311,6 +2317,34 @@ typedef struct AppendRelInfo
Oid parent_reloid; /* OID of parent relation */
} AppendRelInfo;
/*
* Information about a row-identity "resjunk" column in UPDATE/DELETE.
*
* In partitioned UPDATE/DELETE it's important for child partitions to share
* row-identity columns whenever possible, so as not to chew up too many
* targetlist columns. We use these structs to track which identity columns
* have been requested. In the finished plan, each of these will give rise
* to one resjunk entry in the targetlist of the ModifyTable's subplan node.
*
* All the Vars stored in RowIdentityVarInfos must have varno ROWID_VAR, for
* convenience of detecting duplicate requests. We'll replace that, in the
* final plan, with the varno of the generating rel.
*
* Outside this list, a Var with varno ROWID_VAR and varattno k is a reference
* to the k-th element of the row_identity_vars list (k counting from 1).
* We add such a reference to root->processed_tlist when creating the entry,
* and it propagates into the plan tree from there.
*/
typedef struct RowIdentityVarInfo
{
NodeTag type;
Var *rowidvar; /* Var to be evaluated (but varno=ROWID_VAR) */
int32 rowidwidth; /* estimated average width */
char *rowidname; /* name of the resjunk column */
Relids rowidrels; /* RTE indexes of target rels using this */
} RowIdentityVarInfo;
/*
* For each distinct placeholder expression generated during planning, we
* store a PlaceHolderInfo node in the PlannerInfo node's placeholder_list.

View File

@ -201,7 +201,7 @@ typedef struct ProjectSet
/* ----------------
* ModifyTable node -
* Apply rows produced by subplan(s) to result table(s),
* Apply rows produced by outer plan to result table(s),
* by inserting, updating, or deleting.
*
* If the originally named target table is a partitioned table, both
@ -211,7 +211,7 @@ typedef struct ProjectSet
* EXPLAIN should claim is the INSERT/UPDATE/DELETE target.
*
* Note that rowMarks and epqParam are presumed to be valid for all the
* subplan(s); they can't contain any info that varies across subplans.
* table(s); they can't contain any info that varies across tables.
* ----------------
*/
typedef struct ModifyTable
@ -221,9 +221,9 @@ typedef struct ModifyTable
bool canSetTag; /* do we set the command tag/es_processed? */
Index nominalRelation; /* Parent RT index for use of EXPLAIN */
Index rootRelation; /* Root RT index, if target is partitioned */
bool partColsUpdated; /* some part key in hierarchy updated */
bool partColsUpdated; /* some part key in hierarchy updated? */
List *resultRelations; /* integer list of RT indexes */
List *plans; /* plan(s) producing source data */
List *updateColnosLists; /* per-target-table update_colnos lists */
List *withCheckOptionLists; /* per-target-table WCO lists */
List *returningLists; /* per-target-table RETURNING tlists */
List *fdwPrivLists; /* per-target-table FDW private data lists */

View File

@ -158,6 +158,10 @@ typedef struct Expr
* than a heap column. (In ForeignScan and CustomScan plan nodes, INDEX_VAR
* is abused to signify references to columns of a custom scan tuple type.)
*
* ROWID_VAR is used in the planner to identify nonce variables that carry
* row identity information during UPDATE/DELETE. This value should never
* be seen outside the planner.
*
* In the parser, varnosyn and varattnosyn are either identical to
* varno/varattno, or they specify the column's position in an aliased JOIN
* RTE that hides the semantic referent RTE's refname. This is a syntactic
@ -171,6 +175,7 @@ typedef struct Expr
#define INNER_VAR 65000 /* reference to inner subplan */
#define OUTER_VAR 65001 /* reference to outer subplan */
#define INDEX_VAR 65002 /* reference to index column */
#define ROWID_VAR 65003 /* row identity column during planning */
#define IS_SPECIAL_VARNO(varno) ((varno) >= INNER_VAR)
@ -1386,13 +1391,14 @@ typedef struct InferenceElem
* column for the item; so there may be missing or out-of-order resnos.
* It is even legal to have duplicated resnos; consider
* UPDATE table SET arraycol[1] = ..., arraycol[2] = ..., ...
* The two meanings come together in the executor, because the planner
* transforms INSERT/UPDATE tlists into a normalized form with exactly
* one entry for each column of the destination table. Before that's
* happened, however, it is risky to assume that resno == position.
* Generally get_tle_by_resno() should be used rather than list_nth()
* to fetch tlist entries by resno, and only in SELECT should you assume
* that resno is a unique identifier.
* In an INSERT, the rewriter and planner will normalize the tlist by
* reordering it into physical column order and filling in default values
* for any columns not assigned values by the original query. In an UPDATE,
* after the rewriter merges multiple assignments for the same column, the
* planner extracts the target-column numbers into a separate "update_colnos"
* list, and then renumbers the tlist elements serially. Thus, tlist resnos
* match ordinal position in all tlists seen by the executor; but it is wrong
* to assume that before planning has happened.
*
* resname is required to represent the correct column name in non-resjunk
* entries of top-level SELECT targetlists, since it will be used as the

View File

@ -28,8 +28,23 @@ extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
extern Relids adjust_child_relids(Relids relids, int nappinfos,
AppendRelInfo **appinfos);
extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
Relids child_relids, Relids top_parent_relids);
Relids child_relids,
Relids top_parent_relids);
extern List *adjust_inherited_attnums(List *attnums, AppendRelInfo *context);
extern List *adjust_inherited_attnums_multilevel(PlannerInfo *root,
List *attnums,
Index child_relid,
Index top_parent_relid);
extern void get_translated_update_targetlist(PlannerInfo *root, Index relid,
List **processed_tlist,
List **update_colnos);
extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
Relids relids, int *nappinfos);
extern void add_row_identity_var(PlannerInfo *root, Var *rowid_var,
Index rtindex, const char *rowid_name);
extern void add_row_identity_columns(PlannerInfo *root, Index rtindex,
RangeTblEntry *target_rte,
Relation target_relation);
extern void distribute_row_identity_vars(PlannerInfo *root);
#endif /* APPENDINFO_H */

View File

@ -260,11 +260,12 @@ extern LockRowsPath *create_lockrows_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath, List *rowMarks, int epqParam);
extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
RelOptInfo *rel,
Path *subpath,
CmdType operation, bool canSetTag,
Index nominalRelation, Index rootRelation,
bool partColsUpdated,
List *resultRelations, List *subpaths,
List *subroots,
List *resultRelations,
List *updateColnosLists,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, OnConflictExpr *onconflict,
int epqParam);

View File

@ -34,7 +34,7 @@ extern Relids get_relids_for_join(Query *query, int joinrelid);
/*
* prototypes for preptlist.c
*/
extern List *preprocess_targetlist(PlannerInfo *root);
extern void preprocess_targetlist(PlannerInfo *root);
extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex);

View File

@ -23,8 +23,6 @@ extern void AcquireRewriteLocks(Query *parsetree,
bool forUpdatePushedDown);
extern Node *build_column_default(Relation rel, int attrno);
extern void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
Relation target_relation);
extern void fill_extraUpdatedCols(RangeTblEntry *target_rte,
Relation target_relation);

View File

@ -545,27 +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: (some_tab.a + 1), NULL::oid, NULL::tid
One-Time Filter: false
(5 rows)
(4 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
Output: some_tab.b, some_tab.a
-> Result
Output: (a + 1), b, ctid
Output: (some_tab.a + 1), NULL::oid, NULL::tid
One-Time Filter: false
(6 rows)
(5 rows)
update some_tab set a = a + 1 where false returning b, a;
b | a
@ -670,7 +668,7 @@ explain update parted_tab set a = 2 where false;
QUERY PLAN
--------------------------------------------------------
Update on parted_tab (cost=0.00..0.00 rows=0 width=0)
-> Result (cost=0.00..0.00 rows=0 width=0)
-> Result (cost=0.00..0.00 rows=0 width=10)
One-Time Filter: false
(3 rows)

View File

@ -213,7 +213,7 @@ explain (costs off, format json) insert into insertconflicttest values (0, 'Bilb
"Plans": [ +
{ +
"Node Type": "Result", +
"Parent Relationship": "Member", +
"Parent Relationship": "Outer", +
"Parallel Aware": false, +
"Async Capable": false +
} +

View File

@ -1926,37 +1926,27 @@ WHERE EXISTS (
FROM int4_tbl,
LATERAL (SELECT int4_tbl.f1 FROM int8_tbl LIMIT 2) ss
WHERE prt1_l.c IS NULL);
QUERY PLAN
---------------------------------------------------------------
QUERY PLAN
----------------------------------------------------------
Delete on prt1_l
Delete on prt1_l_p1 prt1_l_1
Delete on prt1_l_p3_p1 prt1_l_2
Delete on prt1_l_p3_p2 prt1_l_3
-> Nested Loop Semi Join
-> Seq Scan on prt1_l_p1 prt1_l_1
Filter: (c IS NULL)
-> Nested Loop
-> Seq Scan on int4_tbl
-> Subquery Scan on ss
-> Limit
-> Seq Scan on int8_tbl
-> Nested Loop Semi Join
-> Seq Scan on prt1_l_p3_p1 prt1_l_2
Filter: (c IS NULL)
-> Nested Loop
-> Seq Scan on int4_tbl
-> Subquery Scan on ss_1
-> Limit
-> Seq Scan on int8_tbl int8_tbl_1
-> Nested Loop Semi Join
-> Seq Scan on prt1_l_p3_p2 prt1_l_3
Filter: (c IS NULL)
-> Nested Loop
-> Seq Scan on int4_tbl
-> Subquery Scan on ss_2
-> Limit
-> Seq Scan on int8_tbl int8_tbl_2
(28 rows)
-> Append
-> Seq Scan on prt1_l_p1 prt1_l_1
Filter: (c IS NULL)
-> Seq Scan on prt1_l_p3_p1 prt1_l_2
Filter: (c IS NULL)
-> Seq Scan on prt1_l_p3_p2 prt1_l_3
Filter: (c IS NULL)
-> Materialize
-> Nested Loop
-> Seq Scan on int4_tbl
-> Subquery Scan on ss
-> Limit
-> Seq Scan on int8_tbl
(18 rows)
--
-- negative testcases

View File

@ -2463,74 +2463,43 @@ deallocate ab_q6;
insert into ab values (1,2);
explain (analyze, costs off, summary off, timing off)
update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a;
QUERY PLAN
-------------------------------------------------------------------------------------
QUERY PLAN
-------------------------------------------------------------------------------------------
Update on ab_a1 (actual rows=0 loops=1)
Update on ab_a1_b1 ab_a1_1
Update on ab_a1_b2 ab_a1_2
Update on ab_a1_b3 ab_a1_3
-> Nested Loop (actual rows=0 loops=1)
-> Nested Loop (actual rows=1 loops=1)
-> Append (actual rows=1 loops=1)
-> Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
Recheck Cond: (a = 1)
-> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
Index Cond: (a = 1)
-> Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
Recheck Cond: (a = 1)
Heap Blocks: exact=1
-> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
Index Cond: (a = 1)
-> Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
Recheck Cond: (a = 1)
-> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=0 loops=1)
Index Cond: (a = 1)
-> Materialize (actual rows=0 loops=1)
-> Bitmap Heap Scan on ab_a1_b1 ab_a1_1 (actual rows=0 loops=1)
Recheck Cond: (a = 1)
-> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
Index Cond: (a = 1)
-> Nested Loop (actual rows=1 loops=1)
-> Append (actual rows=1 loops=1)
-> Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
Recheck Cond: (a = 1)
-> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
Index Cond: (a = 1)
-> Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
Recheck Cond: (a = 1)
Heap Blocks: exact=1
-> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
Index Cond: (a = 1)
-> Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
Recheck Cond: (a = 1)
-> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
Index Cond: (a = 1)
-> Materialize (actual rows=1 loops=1)
-> Bitmap Heap Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1)
Recheck Cond: (a = 1)
Heap Blocks: exact=1
-> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
Index Cond: (a = 1)
-> Nested Loop (actual rows=0 loops=1)
-> Append (actual rows=1 loops=1)
-> Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
Recheck Cond: (a = 1)
-> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
Index Cond: (a = 1)
-> Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
Recheck Cond: (a = 1)
Heap Blocks: exact=1
-> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
Index Cond: (a = 1)
-> Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
Recheck Cond: (a = 1)
-> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
Index Cond: (a = 1)
-> Materialize (actual rows=0 loops=1)
-> Bitmap Heap Scan on ab_a1_b3 ab_a1_3 (actual rows=0 loops=1)
Recheck Cond: (a = 1)
-> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
Index Cond: (a = 1)
(65 rows)
-> Materialize (actual rows=1 loops=1)
-> Append (actual rows=1 loops=1)
-> Bitmap Heap Scan on ab_a1_b1 ab_1 (actual rows=0 loops=1)
Recheck Cond: (a = 1)
-> Bitmap Index Scan on ab_a1_b1_a_idx (actual rows=0 loops=1)
Index Cond: (a = 1)
-> Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
Recheck Cond: (a = 1)
Heap Blocks: exact=1
-> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
Index Cond: (a = 1)
-> Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
Recheck Cond: (a = 1)
-> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
Index Cond: (a = 1)
(34 rows)
table ab;
a | b
@ -2551,9 +2520,12 @@ update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1);
Update on ab_a1_b3 ab_a1_3
InitPlan 1 (returns $0)
-> Result (actual rows=1 loops=1)
-> Nested Loop (actual rows=1 loops=1)
-> Seq Scan on ab_a1_b1 ab_a1_1 (actual rows=1 loops=1)
-> Materialize (actual rows=1 loops=1)
-> Nested Loop (actual rows=3 loops=1)
-> Append (actual rows=3 loops=1)
-> Seq Scan on ab_a1_b1 ab_a1_1 (actual rows=1 loops=1)
-> Seq Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1)
-> Seq Scan on ab_a1_b3 ab_a1_3 (actual rows=1 loops=1)
-> Materialize (actual rows=1 loops=3)
-> Append (actual rows=1 loops=1)
-> Seq Scan on ab_a2_b1 ab_a2_1 (actual rows=1 loops=1)
Filter: (b = $0)
@ -2561,27 +2533,7 @@ update ab_a1 set b = 3 from ab_a2 where ab_a2.b = (select 1);
Filter: (b = $0)
-> Seq Scan on ab_a2_b3 ab_a2_3 (never executed)
Filter: (b = $0)
-> Nested Loop (actual rows=1 loops=1)
-> Seq Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1)
-> Materialize (actual rows=1 loops=1)
-> Append (actual rows=1 loops=1)
-> Seq Scan on ab_a2_b1 ab_a2_1 (actual rows=1 loops=1)
Filter: (b = $0)
-> Seq Scan on ab_a2_b2 ab_a2_2 (never executed)
Filter: (b = $0)
-> Seq Scan on ab_a2_b3 ab_a2_3 (never executed)
Filter: (b = $0)
-> Nested Loop (actual rows=1 loops=1)
-> Seq Scan on ab_a1_b3 ab_a1_3 (actual rows=1 loops=1)
-> Materialize (actual rows=1 loops=1)
-> Append (actual rows=1 loops=1)
-> Seq Scan on ab_a2_b1 ab_a2_1 (actual rows=1 loops=1)
Filter: (b = $0)
-> Seq Scan on ab_a2_b2 ab_a2_2 (never executed)
Filter: (b = $0)
-> Seq Scan on ab_a2_b3 ab_a2_3 (never executed)
Filter: (b = $0)
(36 rows)
(19 rows)
select tableoid::regclass, * from ab;
tableoid | a | b
@ -3420,28 +3372,30 @@ explain (costs off) select * from pp_lp where a = 1;
(5 rows)
explain (costs off) update pp_lp set value = 10 where a = 1;
QUERY PLAN
----------------------------------
QUERY PLAN
----------------------------------------
Update on pp_lp
Update on pp_lp1 pp_lp_1
Update on pp_lp2 pp_lp_2
-> Seq Scan on pp_lp1 pp_lp_1
Filter: (a = 1)
-> Seq Scan on pp_lp2 pp_lp_2
Filter: (a = 1)
(7 rows)
-> Append
-> Seq Scan on pp_lp1 pp_lp_1
Filter: (a = 1)
-> Seq Scan on pp_lp2 pp_lp_2
Filter: (a = 1)
(8 rows)
explain (costs off) delete from pp_lp where a = 1;
QUERY PLAN
----------------------------------
QUERY PLAN
----------------------------------------
Delete on pp_lp
Delete on pp_lp1 pp_lp_1
Delete on pp_lp2 pp_lp_2
-> Seq Scan on pp_lp1 pp_lp_1
Filter: (a = 1)
-> Seq Scan on pp_lp2 pp_lp_2
Filter: (a = 1)
(7 rows)
-> Append
-> Seq Scan on pp_lp1 pp_lp_1
Filter: (a = 1)
-> Seq Scan on pp_lp2 pp_lp_2
Filter: (a = 1)
(8 rows)
set constraint_exclusion = 'off'; -- this should not affect the result.
explain (costs off) select * from pp_lp where a = 1;
@ -3455,28 +3409,30 @@ explain (costs off) select * from pp_lp where a = 1;
(5 rows)
explain (costs off) update pp_lp set value = 10 where a = 1;
QUERY PLAN
----------------------------------
QUERY PLAN
----------------------------------------
Update on pp_lp
Update on pp_lp1 pp_lp_1
Update on pp_lp2 pp_lp_2
-> Seq Scan on pp_lp1 pp_lp_1
Filter: (a = 1)
-> Seq Scan on pp_lp2 pp_lp_2
Filter: (a = 1)
(7 rows)
-> Append
-> Seq Scan on pp_lp1 pp_lp_1
Filter: (a = 1)
-> Seq Scan on pp_lp2 pp_lp_2
Filter: (a = 1)
(8 rows)
explain (costs off) delete from pp_lp where a = 1;
QUERY PLAN
----------------------------------
QUERY PLAN
----------------------------------------
Delete on pp_lp
Delete on pp_lp1 pp_lp_1
Delete on pp_lp2 pp_lp_2
-> Seq Scan on pp_lp1 pp_lp_1
Filter: (a = 1)
-> Seq Scan on pp_lp2 pp_lp_2
Filter: (a = 1)
(7 rows)
-> Append
-> Seq Scan on pp_lp1 pp_lp_1
Filter: (a = 1)
-> Seq Scan on pp_lp2 pp_lp_2
Filter: (a = 1)
(8 rows)
drop table pp_lp;
-- Ensure enable_partition_prune does not affect non-partitioned tables.
@ -3500,28 +3456,31 @@ explain (costs off) select * from inh_lp where a = 1;
(5 rows)
explain (costs off) update inh_lp set value = 10 where a = 1;
QUERY PLAN
------------------------------------
QUERY PLAN
------------------------------------------------
Update on inh_lp
Update on inh_lp
Update on inh_lp1 inh_lp_1
-> Seq Scan on inh_lp
Filter: (a = 1)
-> Seq Scan on inh_lp1 inh_lp_1
Filter: (a = 1)
(7 rows)
Update on inh_lp inh_lp_1
Update on inh_lp1 inh_lp_2
-> Result
-> Append
-> Seq Scan on inh_lp inh_lp_1
Filter: (a = 1)
-> Seq Scan on inh_lp1 inh_lp_2
Filter: (a = 1)
(9 rows)
explain (costs off) delete from inh_lp where a = 1;
QUERY PLAN
------------------------------------
QUERY PLAN
------------------------------------------
Delete on inh_lp
Delete on inh_lp
Delete on inh_lp1 inh_lp_1
-> Seq Scan on inh_lp
Filter: (a = 1)
-> Seq Scan on inh_lp1 inh_lp_1
Filter: (a = 1)
(7 rows)
Delete on inh_lp inh_lp_1
Delete on inh_lp1 inh_lp_2
-> Append
-> Seq Scan on inh_lp inh_lp_1
Filter: (a = 1)
-> Seq Scan on inh_lp1 inh_lp_2
Filter: (a = 1)
(8 rows)
-- Ensure we don't exclude normal relations when we only expect to exclude
-- inheritance children

View File

@ -1632,19 +1632,21 @@ EXPLAIN (COSTS OFF) EXECUTE p2(2);
--
SET SESSION AUTHORIZATION regress_rls_bob;
EXPLAIN (COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b);
QUERY PLAN
-----------------------------------------------
QUERY PLAN
-----------------------------------------------------------
Update on t1
Update on t1
Update on t2 t1_1
Update on t3 t1_2
-> Seq Scan on t1
Filter: (((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t2 t1_1
Filter: (((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t3 t1_2
Filter: (((a % 2) = 0) AND f_leak(b))
(10 rows)
Update on t1 t1_1
Update on t2 t1_2
Update on t3 t1_3
-> Result
-> Append
-> Seq Scan on t1 t1_1
Filter: (((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t2 t1_2
Filter: (((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t3 t1_3
Filter: (((a % 2) = 0) AND f_leak(b))
(12 rows)
UPDATE t1 SET b = b || b WHERE f_leak(b);
NOTICE: f_leak => bbb
@ -1722,31 +1724,27 @@ NOTICE: f_leak => cde
NOTICE: f_leak => yyyyyy
EXPLAIN (COSTS OFF) UPDATE t1 SET b=t1.b FROM t2
WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
QUERY PLAN
-----------------------------------------------------------------
QUERY PLAN
-----------------------------------------------------------------------
Update on t1
Update on t1
Update on t2 t1_1
Update on t3 t1_2
Update on t1 t1_1
Update on t2 t1_2
Update on t3 t1_3
-> Nested Loop
-> Seq Scan on t1
Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t2
Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
-> Nested Loop
-> Seq Scan on t2 t1_1
Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t2
Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
-> Nested Loop
-> Seq Scan on t3 t1_2
Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t2
Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
(19 rows)
-> Append
-> Seq Scan on t1 t1_1
Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t2 t1_2
Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t3 t1_3
Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
(14 rows)
UPDATE t1 SET b=t1.b FROM t2
WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
NOTICE: f_leak => cde
EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t1
WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
QUERY PLAN
@ -1795,46 +1793,30 @@ NOTICE: f_leak => cde
EXPLAIN (COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
QUERY PLAN
-----------------------------------------------------------------------
QUERY PLAN
-----------------------------------------------------------------------------
Update on t1 t1_1
Update on t1 t1_1
Update on t2 t1_1_1
Update on t3 t1_1_2
Update on t1 t1_1_1
Update on t2 t1_1_2
Update on t3 t1_1_3
-> Nested Loop
Join Filter: (t1_1.b = t1_2.b)
-> Seq Scan on t1 t1_1
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-> Append
-> Seq Scan on t1 t1_2_1
-> Seq Scan on t1 t1_1_1
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t2 t1_2_2
-> Seq Scan on t2 t1_1_2
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t3 t1_2_3
-> Seq Scan on t3 t1_1_3
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-> Nested Loop
Join Filter: (t1_1_1.b = t1_2.b)
-> Seq Scan on t2 t1_1_1
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-> Append
-> Seq Scan on t1 t1_2_1
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t2 t1_2_2
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t3 t1_2_3
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-> Nested Loop
Join Filter: (t1_1_2.b = t1_2.b)
-> Seq Scan on t3 t1_1_2
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-> Append
-> Seq Scan on t1 t1_2_1
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t2 t1_2_2
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t3 t1_2_3
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
(37 rows)
-> Materialize
-> Append
-> Seq Scan on t1 t1_2_1
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t2 t1_2_2
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t3 t1_2_3
Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
(21 rows)
UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
@ -1842,8 +1824,6 @@ AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
NOTICE: f_leak => daddad_updt
NOTICE: f_leak => daddad_updt
NOTICE: f_leak => defdef
NOTICE: f_leak => defdef
NOTICE: f_leak => daddad_updt
NOTICE: f_leak => defdef
id | a | b | id | a | b | t1_1 | t1_2
-----+---+-------------+-----+---+-------------+---------------------+---------------------
@ -1880,19 +1860,20 @@ EXPLAIN (COSTS OFF) DELETE FROM only t1 WHERE f_leak(b);
(3 rows)
EXPLAIN (COSTS OFF) DELETE FROM t1 WHERE f_leak(b);
QUERY PLAN
-----------------------------------------------
QUERY PLAN
-----------------------------------------------------
Delete on t1
Delete on t1
Delete on t2 t1_1
Delete on t3 t1_2
-> Seq Scan on t1
Filter: (((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t2 t1_1
Filter: (((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t3 t1_2
Filter: (((a % 2) = 0) AND f_leak(b))
(10 rows)
Delete on t1 t1_1
Delete on t2 t1_2
Delete on t3 t1_3
-> Append
-> Seq Scan on t1 t1_1
Filter: (((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t2 t1_2
Filter: (((a % 2) = 0) AND f_leak(b))
-> Seq Scan on t3 t1_3
Filter: (((a % 2) = 0) AND f_leak(b))
(11 rows)
DELETE FROM only t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1;
NOTICE: f_leak => bbbbbb_updt

View File

@ -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 *;
@ -1607,26 +1607,21 @@ UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id;
QUERY PLAN
-------------------------------------------------------------------------
Update on base_tbl_parent
Update on base_tbl_parent
Update on base_tbl_child base_tbl_parent_1
-> Hash Join
Hash Cond: (other_tbl_parent.id = base_tbl_parent.a)
-> Append
-> Seq Scan on other_tbl_parent other_tbl_parent_1
-> Seq Scan on other_tbl_child other_tbl_parent_2
-> Hash
-> Seq Scan on base_tbl_parent
Update on base_tbl_parent base_tbl_parent_1
Update on base_tbl_child base_tbl_parent_2
-> Merge Join
Merge Cond: (base_tbl_parent_1.a = other_tbl_parent.id)
Merge Cond: (base_tbl_parent.a = other_tbl_parent.id)
-> Sort
Sort Key: base_tbl_parent_1.a
-> Seq Scan on base_tbl_child base_tbl_parent_1
Sort Key: base_tbl_parent.a
-> Append
-> Seq Scan on base_tbl_parent base_tbl_parent_1
-> Seq Scan on base_tbl_child base_tbl_parent_2
-> Sort
Sort Key: other_tbl_parent.id
-> Append
-> Seq Scan on other_tbl_parent other_tbl_parent_1
-> Seq Scan on other_tbl_child other_tbl_parent_2
(20 rows)
(15 rows)
UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id;
SELECT * FROM ONLY base_tbl_parent ORDER BY a;
@ -2332,36 +2327,39 @@ SELECT * FROM v1 WHERE a=8;
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
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: 100, t1.b, t1.c, 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
-> Append
-> Seq Scan on public.t12 t12_1
Filter: (t12_1.a = t1.a)
-> 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
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
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
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)
Update on public.t1 t1_1
Update on public.t11 t1_2
Update on public.t12 t1_3
Update on public.t111 t1_4
-> Result
Output: 100, t1.tableoid, t1.ctid
-> Append
-> Index Scan using t1_a_idx on public.t1 t1_1
Output: t1_1.tableoid, 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))
SubPlan 1
-> Append
-> Seq Scan on public.t12 t12_1
Filter: (t12_1.a = t1_1.a)
-> Seq Scan on public.t111 t12_2
Filter: (t12_2.a = t1_1.a)
-> Index Scan using t11_a_idx on public.t11 t1_2
Output: t1_2.tableoid, 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 t12_a_idx on public.t12 t1_3
Output: t1_3.tableoid, 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))
-> Index Scan using t111_a_idx on public.t111 t1_4
Output: t1_4.tableoid, t1_4.ctid
Index Cond: ((t1_4.a > 5) AND (t1_4.a < 7))
Filter: ((t1_4.a <> 6) AND (SubPlan 1) AND snoop(t1_4.a) AND leakproof(t1_4.a))
(30 rows)
UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
SELECT * FROM v1 WHERE a=100; -- Nothing should have been changed to 100
@ -2376,36 +2374,39 @@ 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
Index Cond: ((t1.a > 5) AND (t1.a = 8))
Filter: ((SubPlan 1) AND snoop(t1.a) AND leakproof(t1.a))
SubPlan 1
-> Append
-> Seq Scan on public.t12 t12_1
Filter: (t12_1.a = t1.a)
-> 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
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
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
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)
Update on public.t1 t1_1
Update on public.t11 t1_2
Update on public.t12 t1_3
Update on public.t111 t1_4
-> Result
Output: (t1.a + 1), t1.tableoid, t1.ctid
-> Append
-> Index Scan using t1_a_idx on public.t1 t1_1
Output: t1_1.a, t1_1.tableoid, 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))
SubPlan 1
-> Append
-> Seq Scan on public.t12 t12_1
Filter: (t12_1.a = t1_1.a)
-> Seq Scan on public.t111 t12_2
Filter: (t12_2.a = t1_1.a)
-> Index Scan using t11_a_idx on public.t11 t1_2
Output: t1_2.a, t1_2.tableoid, 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 t12_a_idx on public.t12 t1_3
Output: t1_3.a, t1_3.tableoid, 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))
-> Index Scan using t111_a_idx on public.t111 t1_4
Output: t1_4.a, t1_4.tableoid, t1_4.ctid
Index Cond: ((t1_4.a > 5) AND (t1_4.a = 8))
Filter: ((SubPlan 1) AND snoop(t1_4.a) AND leakproof(t1_4.a))
(30 rows)
UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
NOTICE: snooped value: 8

View File

@ -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
@ -308,8 +308,8 @@ ALTER TABLE part_b_10_b_20 ATTACH PARTITION part_c_1_100 FOR VALUES FROM (1) TO
-- The order of subplans should be in bound order
EXPLAIN (costs off) UPDATE range_parted set c = c - 50 WHERE c > 97;
QUERY PLAN
-------------------------------------------------
QUERY PLAN
-------------------------------------------------------
Update on range_parted
Update on part_a_1_a_10 range_parted_1
Update on part_a_10_a_20 range_parted_2
@ -318,21 +318,22 @@ EXPLAIN (costs off) UPDATE range_parted set c = c - 50 WHERE c > 97;
Update on part_d_1_15 range_parted_5
Update on part_d_15_20 range_parted_6
Update on part_b_20_b_30 range_parted_7
-> Seq Scan on part_a_1_a_10 range_parted_1
Filter: (c > '97'::numeric)
-> Seq Scan on part_a_10_a_20 range_parted_2
Filter: (c > '97'::numeric)
-> Seq Scan on part_b_1_b_10 range_parted_3
Filter: (c > '97'::numeric)
-> Seq Scan on part_c_1_100 range_parted_4
Filter: (c > '97'::numeric)
-> Seq Scan on part_d_1_15 range_parted_5
Filter: (c > '97'::numeric)
-> Seq Scan on part_d_15_20 range_parted_6
Filter: (c > '97'::numeric)
-> Seq Scan on part_b_20_b_30 range_parted_7
Filter: (c > '97'::numeric)
(22 rows)
-> Append
-> Seq Scan on part_a_1_a_10 range_parted_1
Filter: (c > '97'::numeric)
-> Seq Scan on part_a_10_a_20 range_parted_2
Filter: (c > '97'::numeric)
-> Seq Scan on part_b_1_b_10 range_parted_3
Filter: (c > '97'::numeric)
-> Seq Scan on part_c_1_100 range_parted_4
Filter: (c > '97'::numeric)
-> Seq Scan on part_d_1_15 range_parted_5
Filter: (c > '97'::numeric)
-> Seq Scan on part_d_15_20 range_parted_6
Filter: (c > '97'::numeric)
-> Seq Scan on part_b_20_b_30 range_parted_7
Filter: (c > '97'::numeric)
(23 rows)
-- fail, row movement happens only within the partition subtree.
UPDATE part_c_100_200 set c = c - 20, d = c WHERE c = 105;

View File

@ -2906,47 +2906,35 @@ SELECT * FROM parent;
EXPLAIN (VERBOSE, COSTS OFF)
WITH wcte AS ( INSERT INTO int8_tbl VALUES ( 42, 47 ) RETURNING q2 )
DELETE FROM a USING wcte WHERE aa = q2;
QUERY PLAN
----------------------------------------------------
QUERY PLAN
------------------------------------------------------------
Delete on public.a
Delete on public.a
Delete on public.b a_1
Delete on public.c a_2
Delete on public.d a_3
Delete on public.a a_1
Delete on public.b a_2
Delete on public.c a_3
Delete on public.d a_4
CTE wcte
-> Insert on public.int8_tbl
Output: int8_tbl.q2
-> Result
Output: '42'::bigint, '47'::bigint
-> Nested Loop
Output: a.ctid, wcte.*
Join Filter: (a.aa = wcte.q2)
-> Seq Scan on public.a
Output: a.ctid, a.aa
-> CTE Scan on wcte
-> Hash Join
Output: wcte.*, a.tableoid, a.ctid
Hash Cond: (a.aa = wcte.q2)
-> Append
-> Seq Scan on public.a a_1
Output: a_1.aa, a_1.tableoid, a_1.ctid
-> Seq Scan on public.b a_2
Output: a_2.aa, a_2.tableoid, a_2.ctid
-> Seq Scan on public.c a_3
Output: a_3.aa, a_3.tableoid, a_3.ctid
-> Seq Scan on public.d a_4
Output: a_4.aa, a_4.tableoid, a_4.ctid
-> Hash
Output: wcte.*, wcte.q2
-> Nested Loop
Output: a_1.ctid, wcte.*
Join Filter: (a_1.aa = wcte.q2)
-> Seq Scan on public.b a_1
Output: a_1.ctid, a_1.aa
-> CTE Scan on wcte
Output: wcte.*, wcte.q2
-> Nested Loop
Output: a_2.ctid, wcte.*
Join Filter: (a_2.aa = wcte.q2)
-> Seq Scan on public.c a_2
Output: a_2.ctid, a_2.aa
-> CTE Scan on wcte
Output: wcte.*, wcte.q2
-> Nested Loop
Output: a_3.ctid, wcte.*
Join Filter: (a_3.aa = wcte.q2)
-> Seq Scan on public.d a_3
Output: a_3.ctid, a_3.aa
-> CTE Scan on wcte
Output: wcte.*, wcte.q2
(38 rows)
-> CTE Scan on wcte
Output: wcte.*, wcte.q2
(26 rows)
-- error cases
-- data-modifying WITH tries to use its own output