Fix EvalPlanQualStart to handle partitioned result rels correctly.

The es_root_result_relations array needs to be shallow-copied in the
same way as the main es_result_relations array, else EPQ rechecks on
partitioned result relations fail, as seen in bug #15677 from
Norbert Benkocs.

Amit Langote, isolation test case added by me

Discussion: https://postgr.es/m/15677-0bf089579b4cd02d@postgresql.org
Discussion: https://postgr.es/m/19321.1554567786@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2019-04-08 12:20:22 -04:00
parent 348f57ce5b
commit a8cb8f1246
3 changed files with 42 additions and 1 deletions

View File

@ -2779,7 +2779,7 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
* es_param_exec_vals, etc. * es_param_exec_vals, etc.
* *
* The ResultRelInfo array management is trickier than it looks. We * The ResultRelInfo array management is trickier than it looks. We
* create a fresh array for the child but copy all the content from the * create fresh arrays for the child but copy all the content from the
* parent. This is because it's okay for the child to share any * parent. This is because it's okay for the child to share any
* per-relation state the parent has already created --- but if the child * per-relation state the parent has already created --- but if the child
* sets up any ResultRelInfo fields, such as its own junkfilter, that * sets up any ResultRelInfo fields, such as its own junkfilter, that
@ -2800,6 +2800,7 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
if (parentestate->es_num_result_relations > 0) if (parentestate->es_num_result_relations > 0)
{ {
int numResultRelations = parentestate->es_num_result_relations; int numResultRelations = parentestate->es_num_result_relations;
int numRootResultRels = parentestate->es_num_root_result_relations;
ResultRelInfo *resultRelInfos; ResultRelInfo *resultRelInfos;
resultRelInfos = (ResultRelInfo *) resultRelInfos = (ResultRelInfo *)
@ -2808,6 +2809,17 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
numResultRelations * sizeof(ResultRelInfo)); numResultRelations * sizeof(ResultRelInfo));
estate->es_result_relations = resultRelInfos; estate->es_result_relations = resultRelInfos;
estate->es_num_result_relations = numResultRelations; estate->es_num_result_relations = numResultRelations;
/* Also transfer partitioned root result relations. */
if (numRootResultRels > 0)
{
resultRelInfos = (ResultRelInfo *)
palloc(numRootResultRels * sizeof(ResultRelInfo));
memcpy(resultRelInfos, parentestate->es_root_result_relations,
numRootResultRels * sizeof(ResultRelInfo));
estate->es_root_result_relations = resultRelInfos;
estate->es_num_root_result_relations = numRootResultRels;
}
} }
/* es_result_relation_info must NOT be copied */ /* es_result_relation_info must NOT be copied */
/* es_trig_target_relations must NOT be copied */ /* es_trig_target_relations must NOT be copied */

View File

@ -629,3 +629,15 @@ step multireadwcte: <... completed>
subid id subid id
1 1 1 1
starting permutation: simplepartupdate complexpartupdate c1 c2
step simplepartupdate:
update parttbl set a = a;
step complexpartupdate:
with u as (update parttbl set a = a returning parttbl.*)
update parttbl set a = u.a from u;
<waiting ...>
step c1: COMMIT;
step complexpartupdate: <... completed>
step c2: COMMIT;

View File

@ -32,6 +32,10 @@ setup
CREATE TABLE jointest AS SELECT generate_series(1,10) AS id, 0 AS data; CREATE TABLE jointest AS SELECT generate_series(1,10) AS id, 0 AS data;
CREATE INDEX ON jointest(id); CREATE INDEX ON jointest(id);
CREATE TABLE parttbl (a int) PARTITION BY LIST (a);
CREATE TABLE parttbl1 PARTITION OF parttbl FOR VALUES IN (1);
INSERT INTO parttbl VALUES (1);
} }
teardown teardown
@ -41,6 +45,7 @@ teardown
DROP TABLE accounts_ext; DROP TABLE accounts_ext;
DROP TABLE p CASCADE; DROP TABLE p CASCADE;
DROP TABLE table_a, table_b, jointest; DROP TABLE table_a, table_b, jointest;
DROP TABLE parttbl;
} }
session "s1" session "s1"
@ -137,6 +142,12 @@ step "selectresultforupdate" {
where jt.id = y for update of jt, ss1, ss2; where jt.id = y for update of jt, ss1, ss2;
} }
# test for EPQ on a partitioned result table
step "simplepartupdate" {
update parttbl set a = a;
}
session "s2" session "s2"
setup { BEGIN ISOLATION LEVEL READ COMMITTED; } setup { BEGIN ISOLATION LEVEL READ COMMITTED; }
@ -174,6 +185,10 @@ step "updateforcip3" {
} }
step "wrtwcte" { UPDATE table_a SET value = 'tableAValue2' WHERE id = 1; } step "wrtwcte" { UPDATE table_a SET value = 'tableAValue2' WHERE id = 1; }
step "wrjt" { UPDATE jointest SET data = 42 WHERE id = 7; } step "wrjt" { UPDATE jointest SET data = 42 WHERE id = 7; }
step "complexpartupdate" {
with u as (update parttbl set a = a returning parttbl.*)
update parttbl set a = u.a from u;
}
# Use writable CTEs to create self-updated rows, that then are # Use writable CTEs to create self-updated rows, that then are
# (updated|deleted). The *fail versions of the tests additionally # (updated|deleted). The *fail versions of the tests additionally
@ -261,3 +276,5 @@ permutation "wrtwcte" "readwcte" "c1" "c2"
permutation "wrjt" "selectjoinforupdate" "c2" "c1" permutation "wrjt" "selectjoinforupdate" "c2" "c1"
permutation "wrjt" "selectresultforupdate" "c2" "c1" permutation "wrjt" "selectresultforupdate" "c2" "c1"
permutation "wrtwcte" "multireadwcte" "c1" "c2" permutation "wrtwcte" "multireadwcte" "c1" "c2"
permutation "simplepartupdate" "complexpartupdate" "c1" "c2"