Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 759d9d6

Browse files
committed
Add simple tests of EvalPlanQual using the isolationtester infrastructure.
Much more could be done here, but at least now we have *some* automated test coverage of that mechanism. In particular this tests the writable-CTE case reported by Phil Sorber. In passing, remove isolationtester's arbitrary restriction on the number of steps in a permutation list. I used this so that a single spec file could be used to run several related test scenarios, but there are other possible reasons to want a step series that's not exactly a permutation. Improve documentation and fix a couple other nits as well.
1 parent 7c1719b commit 759d9d6

File tree

5 files changed

+176
-38
lines changed

5 files changed

+176
-38
lines changed

src/test/isolation/README

Lines changed: 60 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,42 @@ src/test/isolation/README
33
Isolation tests
44
===============
55

6-
This directory contains a set of tests for the serializable isolation level.
7-
Testing isolation requires running multiple overlapping transactions,
8-
which requires multiple concurrent connections, and therefore can't be
9-
tested using the normal pg_regress program.
6+
This directory contains a set of tests for concurrent behaviors in
7+
PostgreSQL. These tests require running multiple interacting transactions,
8+
which requires management of multiple concurrent connections, and therefore
9+
can't be tested using the normal pg_regress program. The name "isolation"
10+
comes from the fact that the original motivation was to test the
11+
serializable isolation level; but tests for other sorts of concurrent
12+
behaviors have been added as well.
1013

1114
To run the tests, you need to have a server running at the default port
1215
expected by libpq. (You can set PGPORT and so forth in your environment
1316
to control this.) Then run
1417
gmake installcheck
15-
Note that the prepared-transactions test will not pass unless you have
16-
the server's max_prepared_transactions parameter set to at least 3.
17-
18-
To represent a test with overlapping transactions, we use a test specification
19-
file with a custom syntax, which is described in the next section.
18+
To run just specific test(s), you can do something like
19+
./pg_isolation_regress fk-contention fk-deadlock
20+
(look into the specs/ subdirectory to see the available tests).
21+
22+
Note that the prepared-transactions test requires the server's
23+
max_prepared_transactions parameter to be set to at least 3. We have
24+
provided a variant expected-output file that allows the test to "pass"
25+
when max_prepared_transactions has its default value of zero, but of
26+
course that is not really exercising the feature.
27+
28+
To define tests with overlapping transactions, we use test specification
29+
files with a custom syntax, which is described in the next section. To add
30+
a new test, place a spec file in the specs/ subdirectory, add the expected
31+
output in the expected/ subdirectory, and add the test's name to the
32+
isolation_schedule file.
2033

2134
isolationtester is a program that uses libpq to open multiple connections,
2235
and executes a test specified by a spec file. A libpq connection string
2336
specifies the server and database to connect to; defaults derived from
2437
environment variables are used otherwise.
2538

2639
pg_isolation_regress is a tool similar to pg_regress, but instead of using
27-
psql to execute a test, it uses isolationtester.
40+
psql to execute a test, it uses isolationtester. It accepts all the same
41+
command-line arguments as pg_regress.
2842

2943

3044
Test specification
@@ -36,48 +50,65 @@ subdirectory. A test specification consists of four parts, in this order:
3650
setup { <SQL> }
3751

3852
The given SQL block is executed once, in one session only, before running
39-
the test. Create any test tables or such objects here. This part is
40-
optional.
53+
the test. Create any test tables or other required objects here. This
54+
part is optional.
4155

4256
teardown { <SQL> }
4357

4458
The teardown SQL block is executed once after the test is finished. Use
45-
this to clean up, e.g dropping any test tables. This part is optional.
59+
this to clean up in preparation for the next permutation, e.g dropping
60+
any test tables created by setup. This part is optional.
4661

4762
session "<name>"
4863

49-
Each session is executed in a separate connection. A session consists
50-
of four parts: setup, teardown and one or more steps. The per-session
64+
There are normally several "session" parts in a spec file. Each
65+
session is executed in its own connection. A session part consists
66+
of three parts: setup, teardown and one or more "steps". The per-session
5167
setup and teardown parts have the same syntax as the per-test setup and
52-
teardown described above, but they are executed in every session,
53-
before and after each permutation. The setup part typically contains a
54-
"BEGIN" command to begin a transaction.
68+
teardown described above, but they are executed in each session. The
69+
setup part typically contains a "BEGIN" command to begin a transaction.
5570

56-
Each step has a syntax of
71+
Each step has the syntax
5772

5873
step "<name>" { <SQL> }
5974

60-
where <name> is a unique name identifying this step, and SQL is a SQL
61-
statement (or statements, separated by semicolons) that is executed in the
62-
step.
75+
where <name> is a name identifying this step, and SQL is a SQL statement
76+
(or statements, separated by semicolons) that is executed in the step.
77+
Step names must be unique across the whole spec file.
6378

6479
permutation "<step name>" ...
6580

6681
A permutation line specifies a list of steps that are run in that order.
67-
If no permutation lines are given, the test program automatically generates
68-
all possible overlapping orderings of the given sessions.
82+
Any number of permutation lines can appear. If no permutation lines are
83+
given, the test program automatically generates all possible orderings
84+
of the steps from each session (running the steps of any one session in
85+
order). Note that the list of steps in a manually specified
86+
"permutation" line doesn't actually have to be a permutation of the
87+
available steps; it could for instance repeat some steps more than once,
88+
or leave others out.
6989

7090
Lines beginning with a # are considered comments.
7191

92+
For each permutation of the session steps (whether these are manually
93+
specified in the spec file, or automatically generated), the isolation
94+
tester runs the main setup part, then per-session setup parts, then
95+
the selected session steps, then per-session teardown, then the main
96+
teardown script. Each selected step is sent to the connection associated
97+
with its session.
98+
7299

73100
Support for blocking commands
74101
=============================
75102

76-
Each spec may contain commands that block until further action has been taken
103+
Each step may contain commands that block until further action has been taken
77104
(most likely, some other session runs a step that unblocks it or causes a
78-
deadlock). Such a spec needs to be careful to manually specify valid
105+
deadlock). A test that uses this ability must manually specify valid
79106
permutations, i.e. those that would not expect a blocked session to execute a
80-
command. If the spec fails to follow that rule, the spec is aborted.
107+
command. If the test fails to follow that rule, the test is aborted.
108+
109+
Currently, at most one step can be waiting at a time. As long as one
110+
step is waiting, subsequent steps are run to completion synchronously.
81111

82-
Only one command can be waiting at a time. As long as one command is waiting,
83-
other commands are run to completion synchronously.
112+
Note that isolationtester recognizes that a command has blocked by looking
113+
to see if it is shown as waiting in the pg_locks view; therefore, only
114+
blocks on heavyweight locks will be detected.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
Parsed test spec with 3 sessions
2+
3+
starting permutation: wx1 wx2 c1 c2 read
4+
step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking';
5+
step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking'; <waiting ...>
6+
step c1: COMMIT;
7+
step wx2: <... completed>
8+
step c2: COMMIT;
9+
step read: SELECT * FROM accounts ORDER BY accountid;
10+
accountid balance
11+
12+
checking 850
13+
savings 600
14+
15+
starting permutation: wy1 wy2 c1 c2 read
16+
step wy1: UPDATE accounts SET balance = balance + 500 WHERE accountid = 'checking';
17+
step wy2: UPDATE accounts SET balance = balance + 1000 WHERE accountid = 'checking' AND balance < 1000; <waiting ...>
18+
step c1: COMMIT;
19+
step wy2: <... completed>
20+
step c2: COMMIT;
21+
step read: SELECT * FROM accounts ORDER BY accountid;
22+
accountid balance
23+
24+
checking 1100
25+
savings 600
26+
27+
starting permutation: upsert1 upsert2 c1 c2 read
28+
step upsert1:
29+
WITH upsert AS
30+
(UPDATE accounts SET balance = balance + 500
31+
WHERE accountid = 'savings'
32+
RETURNING accountid)
33+
INSERT INTO accounts SELECT 'savings', 500
34+
WHERE NOT EXISTS (SELECT 1 FROM upsert);
35+
36+
step upsert2:
37+
WITH upsert AS
38+
(UPDATE accounts SET balance = balance + 1234
39+
WHERE accountid = 'savings'
40+
RETURNING accountid)
41+
INSERT INTO accounts SELECT 'savings', 1234
42+
WHERE NOT EXISTS (SELECT 1 FROM upsert);
43+
<waiting ...>
44+
step c1: COMMIT;
45+
step upsert2: <... completed>
46+
step c2: COMMIT;
47+
step read: SELECT * FROM accounts ORDER BY accountid;
48+
accountid balance
49+
50+
checking 600
51+
savings 2334

src/test/isolation/isolation_schedule

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ test: prepared-transactions
1313
test: fk-contention
1414
test: fk-deadlock
1515
test: fk-deadlock2
16+
test: eval-plan-qual

src/test/isolation/isolationtester.c

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -395,15 +395,9 @@ run_named_permutations(TestSpec * testspec)
395395
Permutation *p = testspec->permutations[i];
396396
Step **steps;
397397

398-
if (p->nsteps != nallsteps)
399-
{
400-
fprintf(stderr, "invalid number of steps in permutation %d\n", i + 1);
401-
exit_nicely();
402-
}
403-
404398
steps = malloc(p->nsteps * sizeof(Step *));
405399

406-
/* Find all the named steps from the lookup table */
400+
/* Find all the named steps using the lookup table */
407401
for (j = 0; j < p->nsteps; j++)
408402
{
409403
Step **this = (Step **) bsearch(p->stepnames[j], allsteps,
@@ -418,7 +412,9 @@ run_named_permutations(TestSpec * testspec)
418412
steps[j] = *this;
419413
}
420414

415+
/* And run them */
421416
run_permutation(testspec, p->nsteps, steps);
417+
422418
free(steps);
423419
}
424420
}
@@ -483,6 +479,8 @@ report_two_error_messages(Step *step1, Step *step2)
483479
free(step2->errormsg);
484480
step2->errormsg = NULL;
485481
}
482+
483+
free(prefix);
486484
}
487485

488486
/*
@@ -700,7 +698,7 @@ try_complete_step(Step *step, int flags)
700698

701699
FD_ZERO(&read_set);
702700

703-
while (flags & STEP_NONBLOCK && PQisBusy(conn))
701+
while ((flags & STEP_NONBLOCK) && PQisBusy(conn))
704702
{
705703
FD_SET(sock, &read_set);
706704
timeout.tv_sec = 0;
@@ -739,7 +737,8 @@ try_complete_step(Step *step, int flags)
739737
}
740738
else if (!PQconsumeInput(conn)) /* select(): data available */
741739
{
742-
fprintf(stderr, "PQconsumeInput failed: %s", PQerrorMessage(conn));
740+
fprintf(stderr, "PQconsumeInput failed: %s\n",
741+
PQerrorMessage(conn));
743742
exit_nicely();
744743
}
745744
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Tests for the EvalPlanQual mechanism
2+
#
3+
# EvalPlanQual is used in READ COMMITTED isolation level to attempt to
4+
# re-execute UPDATE and DELETE operations against rows that were updated
5+
# by some concurrent transaction.
6+
7+
setup
8+
{
9+
CREATE TABLE accounts (accountid text PRIMARY KEY, balance numeric not null);
10+
INSERT INTO accounts VALUES ('checking', 600), ('savings', 600);
11+
}
12+
13+
teardown
14+
{
15+
DROP TABLE accounts;
16+
}
17+
18+
session "s1"
19+
setup { BEGIN ISOLATION LEVEL READ COMMITTED; }
20+
# wx1 then wx2 checks the basic case of re-fetching up-to-date values
21+
step "wx1" { UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; }
22+
# wy1 then wy2 checks the case where quals pass then fail
23+
step "wy1" { UPDATE accounts SET balance = balance + 500 WHERE accountid = 'checking'; }
24+
# upsert tests are to check writable-CTE cases
25+
step "upsert1" {
26+
WITH upsert AS
27+
(UPDATE accounts SET balance = balance + 500
28+
WHERE accountid = 'savings'
29+
RETURNING accountid)
30+
INSERT INTO accounts SELECT 'savings', 500
31+
WHERE NOT EXISTS (SELECT 1 FROM upsert);
32+
}
33+
step "c1" { COMMIT; }
34+
35+
session "s2"
36+
setup { BEGIN ISOLATION LEVEL READ COMMITTED; }
37+
step "wx2" { UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking'; }
38+
step "wy2" { UPDATE accounts SET balance = balance + 1000 WHERE accountid = 'checking' AND balance < 1000; }
39+
step "upsert2" {
40+
WITH upsert AS
41+
(UPDATE accounts SET balance = balance + 1234
42+
WHERE accountid = 'savings'
43+
RETURNING accountid)
44+
INSERT INTO accounts SELECT 'savings', 1234
45+
WHERE NOT EXISTS (SELECT 1 FROM upsert);
46+
}
47+
step "c2" { COMMIT; }
48+
49+
session "s3"
50+
setup { BEGIN ISOLATION LEVEL READ COMMITTED; }
51+
step "read" { SELECT * FROM accounts ORDER BY accountid; }
52+
teardown { COMMIT; }
53+
54+
permutation "wx1" "wx2" "c1" "c2" "read"
55+
permutation "wy1" "wy2" "c1" "c2" "read"
56+
permutation "upsert1" "upsert2" "c1" "c2" "read"

0 commit comments

Comments
 (0)