Skip to content

Commit deadbf4

Browse files
committed
Fix corner case where SELECT FOR UPDATE could return a row twice.
In READ COMMITTED mode, if a SELECT FOR UPDATE discovers it has to redo WHERE-clause checking on rows that have been updated since the SELECT's snapshot, it invokes EvalPlanQual processing to do that. If this first occurs within a non-first child table of an inheritance tree, the previous coding could accidentally re-return a matching row from an earlier, already-scanned child table. (And, to add insult to injury, I think this could make it miss returning a row that should have been returned, if the updated row that this happens on should still have passed the WHERE qual.) Per report from Kyotaro Horiguchi; the added isolation test is based on his test case. This has been broken for quite awhile, so back-patch to all supported branches.
1 parent 2b53d58 commit deadbf4

File tree

3 files changed

+61
-0
lines changed

3 files changed

+61
-0
lines changed

src/backend/executor/nodeLockRows.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,29 @@ ExecLockRows(LockRowsState *node)
161161
*/
162162
if (!epq_started)
163163
{
164+
ListCell *lc2;
165+
164166
EvalPlanQualBegin(&node->lr_epqstate, estate);
167+
168+
/*
169+
* Ensure that rels with already-visited rowmarks are told
170+
* not to return tuples during the first EPQ test. We can
171+
* exit this loop once it reaches the current rowmark;
172+
* rels appearing later in the list will be set up
173+
* correctly by the EvalPlanQualSetTuple call at the top
174+
* of the loop.
175+
*/
176+
foreach(lc2, node->lr_arowMarks)
177+
{
178+
ExecAuxRowMark *aerm2 = (ExecAuxRowMark *) lfirst(lc2);
179+
180+
if (lc2 == lc)
181+
break;
182+
EvalPlanQualSetTuple(&node->lr_epqstate,
183+
aerm2->rowmark->rti,
184+
NULL);
185+
}
186+
165187
epq_started = true;
166188
}
167189

src/test/isolation/expected/eval-plan-qual.out

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,26 @@ accountid balance
4949

5050
checking 600
5151
savings 2334
52+
53+
starting permutation: readp1 writep1 readp2 c1 c2
54+
step readp1: SELECT tableoid::regclass, ctid, * FROM p WHERE b IN (0, 1) AND c = 0 FOR UPDATE;
55+
tableoid ctid a b c
56+
57+
c1 (0,1) 0 0 0
58+
c1 (0,4) 0 1 0
59+
c2 (0,1) 1 0 0
60+
c2 (0,4) 1 1 0
61+
c3 (0,1) 2 0 0
62+
c3 (0,4) 2 1 0
63+
step writep1: UPDATE p SET b = -1 WHERE a = 1 AND b = 1 AND c = 0;
64+
step readp2: SELECT tableoid::regclass, ctid, * FROM p WHERE b IN (0, 1) AND c = 0 FOR UPDATE; <waiting ...>
65+
step c1: COMMIT;
66+
step readp2: <... completed>
67+
tableoid ctid a b c
68+
69+
c1 (0,1) 0 0 0
70+
c1 (0,4) 0 1 0
71+
c2 (0,1) 1 0 0
72+
c3 (0,1) 2 0 0
73+
c3 (0,4) 2 1 0
74+
step c2: COMMIT;

src/test/isolation/specs/eval-plan-qual.spec

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,20 @@ setup
88
{
99
CREATE TABLE accounts (accountid text PRIMARY KEY, balance numeric not null);
1010
INSERT INTO accounts VALUES ('checking', 600), ('savings', 600);
11+
12+
CREATE TABLE p (a int, b int, c int);
13+
CREATE TABLE c1 () INHERITS (p);
14+
CREATE TABLE c2 () INHERITS (p);
15+
CREATE TABLE c3 () INHERITS (p);
16+
INSERT INTO c1 SELECT 0, a / 3, a % 3 FROM generate_series(0, 9) a;
17+
INSERT INTO c2 SELECT 1, a / 3, a % 3 FROM generate_series(0, 9) a;
18+
INSERT INTO c3 SELECT 2, a / 3, a % 3 FROM generate_series(0, 9) a;
1119
}
1220

1321
teardown
1422
{
1523
DROP TABLE accounts;
24+
DROP TABLE p CASCADE;
1625
}
1726

1827
session "s1"
@@ -30,6 +39,11 @@ step "upsert1" {
3039
INSERT INTO accounts SELECT 'savings', 500
3140
WHERE NOT EXISTS (SELECT 1 FROM upsert);
3241
}
42+
# tests with table p check inheritance cases, specifically a bug where
43+
# nodeLockRows did the wrong thing when the first updated tuple was in
44+
# a non-first child table
45+
step "readp1" { SELECT tableoid::regclass, ctid, * FROM p WHERE b IN (0, 1) AND c = 0 FOR UPDATE; }
46+
step "writep1" { UPDATE p SET b = -1 WHERE a = 1 AND b = 1 AND c = 0; }
3347
step "c1" { COMMIT; }
3448

3549
session "s2"
@@ -44,6 +58,7 @@ step "upsert2" {
4458
INSERT INTO accounts SELECT 'savings', 1234
4559
WHERE NOT EXISTS (SELECT 1 FROM upsert);
4660
}
61+
step "readp2" { SELECT tableoid::regclass, ctid, * FROM p WHERE b IN (0, 1) AND c = 0 FOR UPDATE; }
4762
step "c2" { COMMIT; }
4863

4964
session "s3"
@@ -54,3 +69,4 @@ teardown { COMMIT; }
5469
permutation "wx1" "wx2" "c1" "c2" "read"
5570
permutation "wy1" "wy2" "c1" "c2" "read"
5671
permutation "upsert1" "upsert2" "c1" "c2" "read"
72+
permutation "readp1" "writep1" "readp2" "c1" "c2"

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy