Add isolation tests for UPDATE/DELETE FOR PORTION OF

Add documentation about concurrency issues related to UPDATE/DELETE
FOR PORTION OF as well as supporting isolation tests.

Author: Paul A. Jungwirth <pj@illuminatedcomputing.com>
Reviewed-by: Peter Eisentraut <peter@eisentraut.org>
Discussion: https://www.postgresql.org/message-id/flat/ec498c3d-5f2b-48ec-b989-5561c8aa2024%40illuminatedcomputing.com
This commit is contained in:
Peter Eisentraut 2026-04-07 11:07:59 +02:00
parent 5bcc3fbd19
commit b6ccd30d8f
9 changed files with 4823 additions and 1 deletions

View file

@ -394,6 +394,44 @@ DELETE FROM products
triggers are fired, but permission checks for inserting rows are
skipped.
</para>
<para>
In <literal>READ COMMITTED</literal> mode, temporal updates and deletes can
yield unexpected results when they concurrently touch the same row. It is
possible to lose all or part of the second update or delete. The scenario
is illustrated in <xref linkend="temporal-isolation-figure"/>. Session 2
searches for rows to change, and it finds one that Session 1 has already
modified. It waits for Session 1 to commit. Then it re-checks whether the
row still matches its search criteria (including the start/end times
targeted by <literal>FOR PORTION OF</literal>). Session 1 may have changed
those times so that they no longer qualify.
</para>
<para>
In addition, the temporal leftovers inserted by Session 1 are not visible
within Session 2's transaction, because they are not yet committed.
Therefore there is nothing for Session 2 to update/delete: neither the
modified row nor the leftovers. The portion of history that Session 2
intended to change is not affected.
</para>
<figure id="temporal-isolation-figure">
<title>Temporal Isolation Example</title>
<mediaobject>
<imageobject>
<imagedata fileref="images/temporal-isolation.svg" format="SVG" width="35%"/>
</imageobject>
</mediaobject>
</figure>
<para>
To solve these problems, precede every temporal update/delete with a
<literal>SELECT FOR UPDATE</literal> matching the same criteria (including
the targeted portion of application time). That way the actual
update/delete doesn't begin until the lock is held, and all concurrent
leftovers will be visible. In higher transaction isolation levels, this
lock is not required.
</para>
</sect1>
<sect1 id="dml-returning">

View file

@ -10,7 +10,8 @@ ALL_IMAGES = \
temporal-entities.svg \
temporal-references.svg \
temporal-update.svg \
temporal-delete.svg
temporal-delete.svg \
temporal-isolation.svg
DITAA = ditaa
DOT = dot

View file

@ -18,6 +18,7 @@ all_files = [
'temporal-references.txt',
'temporal-update.txt',
'temporal-delete.txt',
'temporal-isolation.txt',
]
foreach file : all_files

View file

@ -0,0 +1,47 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 350 672" width="350" height="672" shape-rendering="geometricPrecision" version="1.0">
<defs>
<filter id="f2" x="0" y="0" width="200%" height="200%">
<feOffset result="offOut" in="SourceGraphic" dx="5" dy="5"/>
<feGaussianBlur result="blurOut" in="offOut" stdDeviation="3"/>
<feBlend in="SourceGraphic" in2="blurOut" mode="normal"/>
</filter>
</defs>
<g stroke-width="1" stroke-linecap="square" stroke-linejoin="round">
<rect x="0" y="0" width="350" height="672" style="fill: #ffffff"/>
<path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#ffff33" d="M205.0 301.0 L205.0 371.0 L325.0 371.0 L325.0 301.0 z"/>
<path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#ffff33" d="M325.0 469.0 L325.0 539.0 L205.0 539.0 L205.0 469.0 z"/>
<path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#99dd99" d="M25.0 35.0 L25.0 91.0 L145.0 91.0 L145.0 35.0 z"/>
<path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#99dd99" d="M25.0 133.0 L25.0 189.0 L145.0 189.0 L145.0 133.0 z"/>
<path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#99dd99" d="M25.0 287.0 L145.0 287.0 L145.0 231.0 L25.0 231.0 z"/>
<path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#ffff33" d="M205.0 133.0 L205.0 189.0 L325.0 189.0 L325.0 133.0 z"/>
<path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#ffff33" d="M325.0 581.0 L205.0 581.0 L205.0 637.0 L325.0 637.0 z"/>
<path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#ffff33" d="M205.0 91.0 L325.0 91.0 L325.0 35.0 L205.0 35.0 z"/>
<path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#99dd99" d="M145.0 399.0 L145.0 455.0 L25.0 455.0 L25.0 399.0 z"/>
<path stroke="none" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#000000" d="M80.0 112.0 L85.0 126.0 L90.0 112.0 z"/>
<path stroke="none" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#000000" d="M260.0 112.0 L265.0 126.0 L270.0 112.0 z"/>
<path stroke="none" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#000000" d="M80.0 210.0 L85.0 224.0 L90.0 210.0 z"/>
<path stroke="none" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#000000" d="M260.0 280.0 L265.0 294.0 L270.0 280.0 z"/>
<path stroke="none" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#000000" d="M80.0 378.0 L85.0 392.0 L90.0 378.0 z"/>
<path stroke="none" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#000000" d="M260.0 448.0 L265.0 462.0 L270.0 448.0 z"/>
<path stroke="none" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="#000000" d="M260.0 560.0 L265.0 574.0 L270.0 560.0 z"/>
<path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M85.0 91.0 L85.0 119.0 "/>
<path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M85.0 189.0 L85.0 217.0 "/>
<path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M265.0 539.0 L265.0 567.0 "/>
<path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M265.0 91.0 L265.0 119.0 "/>
<path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M265.0 371.0 L265.0 455.0 "/>
<path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M85.0 385.0 L85.0 287.0 "/>
<path stroke="#000000" stroke-width="1.000000" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M265.0 189.0 L265.0 287.0 "/>
<text x="46" y="68" font-family="Courier" font-size="13" stroke="none" fill="#000000">Session 1</text>
<text x="220" y="334" font-family="Courier" font-size="13" stroke="none" fill="#000000">UPDATE;</text>
<text x="220" y="348" font-family="Courier" font-size="13" stroke="none" fill="#000000">waits...</text>
<text x="48" y="166" font-family="Courier" font-size="13" stroke="none" fill="#000000">BEGIN;</text>
<text x="228" y="166" font-family="Courier" font-size="13" stroke="none" fill="#000000">BEGIN;</text>
<text x="226" y="68" font-family="Courier" font-size="13" stroke="none" fill="#000000">Session 2</text>
<text x="47" y="264" font-family="Courier" font-size="13" stroke="none" fill="#000000">UPDATE;</text>
<text x="46" y="432" font-family="Courier" font-size="13" stroke="none" fill="#000000">COMMIT;</text>
<text x="226" y="614" font-family="Courier" font-size="13" stroke="none" fill="#000000">COMMIT;</text>
<text x="220" y="502" font-family="Courier" font-size="13" stroke="none" fill="#000000">...resumes</text>
<text x="220" y="516" font-family="Courier" font-size="13" stroke="none" fill="#000000">rechecks</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -0,0 +1,44 @@
+-----------+ +-----------+
| cGRE | | cYEL |
|Session 1 | |Session 2 |
| | | |
+-----+-----+ +-----+-----+
| |
v v
+-----+-----+ +-----+-----+
| cGRE | | cYEL |
| BEGIN; | | BEGIN; |
| | | |
+-----+-----+ +-----+-----+
| |
v |
+-----+-----+ |
| cGRE | |
| UPDATE; | |
| | |
+-----+-----+ v
| +-----+-----+
| | cYEL |
| | UPDATE; |
| | waits... |
| | |
| +-----+-----+
v |
+-----+-----+ |
| cGRE | |
| COMMIT; | |
| | |
+-----------+ v
+-----+-----+
| cYEL |
| ...resumes|
| rechecks |
| |
+-----+-----+
|
v
+-----+-----+
| cYEL |
| COMMIT; |
| |
+-----------+

View file

@ -1465,6 +1465,10 @@ ExecForPortionOfLeftovers(ModifyTableContext *context,
* We have already locked the tuple in ExecUpdate/ExecDelete, and it has
* passed EvalPlanQual. This ensures that concurrent updates in READ
* COMMITTED can't insert conflicting temporal leftovers.
*
* It does *not* protect against concurrent update/deletes overlooking
* each others' leftovers though. See our isolation tests for details
* about that and a viable workaround.
*/
if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc, tupleid, SnapshotAny, oldtupleSlot))
elog(ERROR, "failed to fetch tuple for FOR PORTION OF");

File diff suppressed because it is too large Load diff

View file

@ -125,3 +125,4 @@ test: serializable-parallel-2
test: serializable-parallel-3
test: matview-write-skew
test: lock-nowait
test: for-portion-of

View file

@ -0,0 +1,597 @@
# UPDATE/DELETE FOR PORTION OF test
#
# Test inserting temporal leftovers from a FOR PORTION OF update/delete.
#
# In READ COMMITTED mode, concurrent updates/deletes to the same records cause
# weird results. Portions of history that should have been updated/deleted don't
# get changed. That's because the leftovers from one operation are added too
# late to be seen by the other. EvalPlanQual will reload the changed-in-common
# row, but it won't re-scan to find new leftovers.
#
# MariaDB similarly gives undesirable results in READ COMMITTED mode (although
# not the same results). DB2 doesn't have READ COMMITTED, but it gives correct
# results at all levels, in particular READ STABILITY (which seems closest).
#
# A workaround is to lock the part of history you want before changing it (using
# SELECT FOR UPDATE). That way the search for rows is late enough to see
# leftovers from the other session(s). This shouldn't impose any new deadlock
# risks, since the locks are the same as before. Adding a third/fourth/etc.
# connection also doesn't change the semantics. The READ COMMITTED tests here
# demonstrate the problem and also show that solving it with manual locks is
# viable and not vitiated by any bugs. Incidentally, this approach also works in
# MariaDB.
#
# We run the same tests under REPEATABLE READ to show the problem goes away.
# In general they do what you'd want with no explicit locking required, but some
# orderings raise a concurrent update/delete failure (as expected). If there is
# a prior read by s1, concurrent update/delete failures are more common.
#
# To save on test time, we only run a couple SERIALIZABLE tests (for the more
# problematic permutations).
#
# We test updates where s2 updates history that is:
#
# - non-overlapping with s1,
# - contained entirely in s1,
# - partly contained in s1.
#
# We don't need to test where s2 entirely contains s1 because of symmetry:
# we test both when s1 precedes s2 and when s2 precedes s1, so that scenario is
# covered.
#
# We test various orderings of the update/delete/commit from s1 and s2.
# Note that `s1lock s2lock s1change` is boring because it's the same as
# `s1lock s1change s2lock`. In other words it doesn't matter if something
# interposes between the lock and its change (as long as everyone is following
# the same policy).
setup
{
CREATE TABLE products (
id int4range NOT NULL,
valid_at daterange NOT NULL,
price decimal NOT NULL,
PRIMARY KEY (id, valid_at WITHOUT OVERLAPS));
INSERT INTO products VALUES
('[1,2)', '[2020-01-01,2030-01-01)', 5.00);
}
teardown { DROP TABLE products; }
session s1
setup { SET datestyle TO ISO, YMD; }
step s1rc { BEGIN ISOLATION LEVEL READ COMMITTED; }
step s1rr { BEGIN ISOLATION LEVEL REPEATABLE READ; }
step s1ser { BEGIN ISOLATION LEVEL SERIALIZABLE; }
step s1lock2025 {
SELECT * FROM products
WHERE id = '[1,2)' AND valid_at && '[2025-01-01,2026-01-01)'
ORDER BY valid_at FOR UPDATE;
}
step s1upd2025 {
UPDATE products
FOR PORTION OF valid_at FROM '2025-01-01' TO '2026-01-01'
SET price = 8.00
WHERE id = '[1,2)';
}
step s1del2025 {
DELETE FROM products
FOR PORTION OF valid_at FROM '2025-01-01' TO '2026-01-01'
WHERE id = '[1,2)';
}
step s1q { SELECT * FROM products ORDER BY id, valid_at; }
step s1c { COMMIT; }
session s2
setup { SET datestyle TO ISO, YMD; }
step s2rc { BEGIN ISOLATION LEVEL READ COMMITTED; }
step s2rr { BEGIN ISOLATION LEVEL REPEATABLE READ; }
step s2ser { BEGIN ISOLATION LEVEL SERIALIZABLE; }
step s2lock202503 {
SELECT * FROM products
WHERE id = '[1,2)' AND valid_at && '[2025-03-01,2025-04-01)'
ORDER BY valid_at FOR UPDATE;
}
step s2lock20252026 {
SELECT * FROM products
WHERE id = '[1,2)' AND valid_at && '[2025-06-01,2026-06-01)'
ORDER BY valid_at FOR UPDATE;
}
step s2lock2027 {
SELECT * FROM products
WHERE id = '[1,2)' AND valid_at && '[2027-01-01,2028-01-01)'
ORDER BY valid_at FOR UPDATE;
}
step s2upd202503 {
UPDATE products
FOR PORTION OF valid_at FROM '2025-03-01' TO '2025-04-01'
SET price = 10.00
WHERE id = '[1,2)';
}
step s2upd20252026 {
UPDATE products
FOR PORTION OF valid_at FROM '2025-06-01' TO '2026-06-01'
SET price = 10.00
WHERE id = '[1,2)';
}
step s2upd2027 {
UPDATE products
FOR PORTION OF valid_at FROM '2027-01-01' TO '2028-01-01'
SET price = 10.00
WHERE id = '[1,2)';
}
step s2del202503 {
DELETE FROM products
FOR PORTION OF valid_at FROM '2025-03-01' TO '2025-04-01'
WHERE id = '[1,2)';
}
step s2del20252026 {
DELETE FROM products
FOR PORTION OF valid_at FROM '2025-06-01' TO '2026-06-01'
WHERE id = '[1,2)';
}
step s2del2027 {
DELETE FROM products
FOR PORTION OF valid_at FROM '2027-01-01' TO '2028-01-01'
WHERE id = '[1,2)';
}
step s2c { COMMIT; }
# ########################################
# READ COMMITTED tests, UPDATE+UPDATE:
# ########################################
# s1 sees the leftovers
permutation s1rc s2rc s2lock2027 s2upd2027 s2c s1lock2025 s1upd2025 s1c s1q
# s1 reloads the updated row and sees its leftovers
permutation s1rc s2rc s2lock202503 s2upd202503 s2c s1lock2025 s1upd2025 s1c s1q
# s1 reloads the updated row and sees its leftovers
permutation s1rc s2rc s2lock20252026 s2upd20252026 s2c s1lock2025 s1upd2025 s1c s1q
# s2 sees the leftovers
permutation s1rc s2rc s1lock2025 s1upd2025 s1c s2lock2027 s2upd2027 s2c s1q
# s2 loads the updated row
permutation s1rc s2rc s1lock2025 s1upd2025 s1c s2lock202503 s2upd202503 s2c s1q
# s2 loads the updated row and sees its leftovers
permutation s1rc s2rc s1lock2025 s1upd2025 s1c s2lock20252026 s2upd20252026 s2c s1q
# Problem:
# s1 (without locking) overlooks the leftovers from s2
# and EvalPlanQual no longer matches the row to be updated either.
permutation s1rc s2rc s2upd2027 s1upd2025 s2c s1c s1q
# Workaround:
# s1 updates the leftovers from s2
# Locking is required or s1 won't see the leftovers.
permutation s1rc s2rc s2lock2027 s2upd2027 s1lock2025 s2c s1upd2025 s1c s1q
# Problem:
# s1 (without locking) overlooks the leftovers from s2
# but EvalPlanQual still matches the row to be updated.
permutation s1rc s2rc s2upd202503 s1upd2025 s2c s1c s1q
# Workaround:
# s1 overwrites the row from s2 and sees its leftovers
permutation s1rc s2rc s2lock202503 s2upd202503 s1lock2025 s2c s1upd2025 s1c s1q
# Problem:
# s1 (without locking) overlooks the leftovers from s2
# but EvalPlanQual still matches the row to be updated,
# and s1's leftovers don't conflict with s2's.
permutation s1rc s2rc s2upd20252026 s1upd2025 s2c s1c s1q
# Workaround:
# s1 overwrites the row from s2 and sees its leftovers
# Locking is required or s1 won't see the leftovers.
permutation s1rc s2rc s2lock20252026 s2upd20252026 s1lock2025 s2c s1upd2025 s1c s1q
# ########################################
# READ COMMITTED tests, UPDATE+DELETE:
# ########################################
# s1 sees the leftovers
permutation s1rc s2rc s2lock2027 s2del2027 s2c s1lock2025 s1upd2025 s1c s1q
# s1 ignores the deleted row and sees its leftovers
permutation s1rc s2rc s2lock202503 s2del202503 s2c s1lock2025 s1upd2025 s1c s1q
# s1 ignores the deleted row and sees its leftovers
permutation s1rc s2rc s2lock20252026 s2del20252026 s2c s1lock2025 s1upd2025 s1c s1q
# s2 sees the leftovers
permutation s1rc s2rc s1lock2025 s1upd2025 s1c s2lock2027 s2del2027 s2c s1q
# s2 loads the updated row
permutation s1rc s2rc s1lock2025 s1upd2025 s1c s2lock202503 s2del202503 s2c s1q
# s2 loads the updated row and sees its leftovers
permutation s1rc s2rc s1lock2025 s1upd2025 s1c s2lock20252026 s2del20252026 s2c s1q
# Problem:
# s1 (without locking) overlooks the leftovers from s2
# and EvalPlanQual no longer matches the row to be updated either.
permutation s1rc s2rc s2del2027 s1upd2025 s2c s1c s1q
# Workaround:
# s1 updates the leftovers from s2
# Locking is required or s1 won't see the leftovers.
permutation s1rc s2rc s2lock2027 s2del2027 s1lock2025 s2c s1upd2025 s1c s1q
# Problem:
# s1 (without locking) overlooks the leftovers from s2
# and EvalPlanQual no longer matches the row to be updated either.
permutation s1rc s2rc s2del202503 s1upd2025 s2c s1c s1q
# Workaround:
# s1 sees the leftovers from s2
# Locking is required or s1 won't see the leftovers.
permutation s1rc s2rc s2lock202503 s2del202503 s1lock2025 s2c s1upd2025 s1c s1q
# Problem:
# s1 (without locking) overlooks the leftovers from s2
# and EvalPlanQual no longer matches the row to be updated either.
permutation s1rc s2rc s2del20252026 s1upd2025 s2c s1c s1q
# Workaround:
# s1 sees the leftovers from s2
# Locking is required or s1 won't see the leftovers.
permutation s1rc s2rc s2lock20252026 s2del20252026 s1lock2025 s2c s1upd2025 s1c s1q
# ########################################
# READ COMMITTED tests, DELETE+UPDATE:
# ########################################
# s1 sees the leftovers
permutation s1rc s2rc s2lock2027 s2upd2027 s2c s1lock2025 s1del2025 s1c s1q
# s1 reloads the updated row and sees its leftovers
permutation s1rc s2rc s2lock202503 s2upd202503 s2c s1lock2025 s1del2025 s1c s1q
# s1 reloads the updated row and sees its leftovers
permutation s1rc s2rc s2lock20252026 s2upd20252026 s2c s1lock2025 s1del2025 s1c s1q
# s2 sees the leftovers
permutation s1rc s2rc s1lock2025 s1del2025 s1c s2lock2027 s2upd2027 s2c s1q
# s2 ignores the deleted row
permutation s1rc s2rc s1lock2025 s1del2025 s1c s2lock202503 s2upd202503 s2c s1q
# s2 ignores the deleted row and sees its leftovers
permutation s1rc s2rc s1lock2025 s1del2025 s1c s2lock20252026 s2upd20252026 s2c s1q
# Problem:
# s1 (without locking) overlooks the leftovers from s2
# and EvalPlanQual no longer matches the row to be deleted either.
permutation s1rc s2rc s2upd2027 s1del2025 s2c s1c s1q
# Workaround:
# s1 deletes the leftovers from s2
# Locking is required or s1 won't see the leftovers.
permutation s1rc s2rc s2lock2027 s2upd2027 s1lock2025 s2c s1del2025 s1c s1q
# Problem:
# s1 (without locking) overlooks the leftovers from s2
# but EvalPlanQual still matches the row to be deleted.
permutation s1rc s2rc s2upd202503 s1del2025 s2c s1c s1q
# Workaround:
# s1 deletes the new row from s2 and its leftovers
# Locking is required or s1 won't see the leftovers.
permutation s1rc s2rc s2lock202503 s2upd202503 s1lock2025 s2c s1del2025 s1c s1q
# Problem:
# s1 (without locking) overlooks the leftovers from s2
# but EvalPlanQual still matches the row to be deleted,
# and s1 leaves leftovers from the row created by s2.
permutation s1rc s2rc s2upd20252026 s1del2025 s2c s1c s1q
# Workaround:
# s1 deletes the new row from s2 and its leftovers
# Locking is required or s1 won't see the leftovers.
permutation s1rc s2rc s2lock20252026 s2upd20252026 s1lock2025 s2c s1del2025 s1c s1q
# ########################################
# READ COMMITTED tests, DELETE+DELETE:
# ########################################
# s1 sees the leftovers
permutation s1rc s2rc s2lock2027 s2del2027 s2c s1lock2025 s1del2025 s1c s1q
# s1 ignores the deleted row and sees its leftovers
permutation s1rc s2rc s2lock202503 s2del202503 s2c s1lock2025 s1del2025 s1c s1q
# s1 ignores the deleted row and sees its leftovers
permutation s1rc s2rc s2lock20252026 s2del20252026 s2c s1lock2025 s1del2025 s1c s1q
# s2 sees the leftovers
permutation s1rc s2rc s1lock2025 s1del2025 s1c s2lock2027 s2del2027 s2c s1q
# s2 ignores the deleted row
permutation s1rc s2rc s1lock2025 s1del2025 s1c s2lock202503 s2del202503 s2c s1q
# s2 ignores the deleted row and sees its leftovers
permutation s1rc s2rc s1lock2025 s1del2025 s1c s2lock20252026 s2del20252026 s2c s1q
# Problem:
# s1 (without locking) overlooks the leftovers from s2
# and EvalPlanQual no longer matches the row to be deleted either.
permutation s1rc s2rc s2del2027 s1del2025 s2c s1c s1q
# Workaround:
# s1 deletes the leftovers from s2
# Locking is required or s1 won't see the leftovers.
permutation s1rc s2rc s2lock2027 s2del2027 s1lock2025 s2c s1del2025 s1c s1q
# Problem:
# s1 (without locking) overlooks the leftovers from s2
# and EvalPlanQual no longer matches the row to be deleted either.
permutation s1rc s2rc s2del202503 s1del2025 s2c s1c s1q
# Workaround:
# s1 deletes the leftovers from s2
# Locking is required or s1 won't see the leftovers.
permutation s1rc s2rc s2lock202503 s2del202503 s1lock2025 s2c s1del2025 s1c s1q
# Problem:
# s1 (without locking) overlooks the leftovers from s2
# and EvalPlanQual no longer matches the row to be deleted either.
permutation s1rc s2rc s2del20252026 s1del2025 s2c s1c s1q
# Workaround:
# s1 deletes the leftovers from s2
# Locking is required or s1 won't see the leftovers.
permutation s1rc s2rc s2lock20252026 s2del20252026 s1lock2025 s2c s1del2025 s1c s1q
# ########################################
# REPEATABLE READ tests, UPDATE+UPDATE:
# ########################################
# s1 sees the leftovers
permutation s1rr s2rr s2upd2027 s2c s1upd2025 s1c s1q
# s1 reloads the updated row and sees its leftovers
permutation s1rr s2rr s2upd202503 s2c s1upd2025 s1c s1q
# s1 reloads the updated row and sees its leftovers
permutation s1rr s2rr s2upd20252026 s2c s1upd2025 s1c s1q
# s2 sees the leftovers
permutation s1rr s2rr s1upd2025 s1c s2upd2027 s2c s1q
# s2 loads the updated row and sees its leftovers
permutation s1rr s2rr s1upd2025 s1c s2upd202503 s2c s1q
# s2 loads the updated row and sees its leftovers
permutation s1rr s2rr s1upd2025 s1c s2upd20252026 s2c s1q
# s1 fails from concurrent update
permutation s1rr s2rr s2upd2027 s1upd2025 s2c s1c s1q
# s1 fails from concurrent update
permutation s1rr s2rr s2upd202503 s1upd2025 s2c s1c s1q
# s1 fails from concurrent update
permutation s1rr s2rr s2upd20252026 s1upd2025 s2c s1c s1q
## with prior read by s1:
# s1 fails from concurrent update
permutation s1rr s2rr s1q s2upd2027 s2c s1upd2025 s1c s1q
# s1 fails from concurrent update
permutation s1rr s2rr s1q s2upd202503 s2c s1upd2025 s1c s1q
# s1 fails from concurrent update
permutation s1rr s2rr s1q s2upd20252026 s2c s1upd2025 s1c s1q
# s2 sees the leftovers
permutation s1rr s2rr s1q s1upd2025 s1c s2upd2027 s2c s1q
# s2 loads the updated row
permutation s1rr s2rr s1q s1upd2025 s1c s2upd202503 s2c s1q
# s2 loads the updated row and sees its leftovers
permutation s1rr s2rr s1q s1upd2025 s1c s2upd20252026 s2c s1q
# s1 fails from concurrent update
permutation s1rr s2rr s1q s2upd2027 s1upd2025 s2c s1c s1q
# s1 fails from concurrent update
permutation s1rr s2rr s1q s2upd202503 s1upd2025 s2c s1c s1q
# s1 fails from concurrent update
permutation s1rr s2rr s1q s2upd20252026 s1upd2025 s2c s1c s1q
# ########################################
# REPEATABLE READ tests, UPDATE+DELETE:
# ########################################
# s1 sees the leftovers
permutation s1rr s2rr s2del2027 s2c s1upd2025 s1c s1q
# s1 ignores the deleted row and sees its leftovers
permutation s1rr s2rr s2del202503 s2c s1upd2025 s1c s1q
# s1 ignores the deleted row and sees its leftovers
permutation s1rr s2rr s2del20252026 s2c s1upd2025 s1c s1q
# s2 sees the leftovers
permutation s1rr s2rr s1upd2025 s1c s2del2027 s2c s1q
# s2 loads the updated row
permutation s1rr s2rr s1upd2025 s1c s2del202503 s2c s1q
# s2 loads the updated row and sees its leftovers
permutation s1rr s2rr s1upd2025 s1c s2del20252026 s2c s1q
# s1 fails from concurrent delete
permutation s1rr s2rr s2del2027 s1upd2025 s2c s1c s1q
# s1 fails from concurrent delete
permutation s1rr s2rr s2del202503 s1upd2025 s2c s1c s1q
# s1 fails from concurrent delete
permutation s1rr s2rr s2del20252026 s1upd2025 s2c s1c s1q
## with prior read by s1:
# s1 fails from concurrent delete
permutation s1rr s2rr s1q s2del2027 s2c s1upd2025 s1c s1q
# s1 fails from concurrent delete
permutation s1rr s2rr s1q s2del202503 s2c s1upd2025 s1c s1q
# s1 fails from concurrent delete
permutation s1rr s2rr s1q s2del20252026 s2c s1upd2025 s1c s1q
# s2 sees the leftovers
permutation s1rr s2rr s1q s1upd2025 s1c s2del2027 s2c s1q
# s2 loads the updated row
permutation s1rr s2rr s1q s1upd2025 s1c s2del202503 s2c s1q
# s2 loads the updated row and sees its leftovers
permutation s1rr s2rr s1q s1upd2025 s1c s2del20252026 s2c s1q
# s1 fails from concurrent delete
permutation s1rr s2rr s1q s2del2027 s1upd2025 s2c s1c s1q
# s1 fails from concurrent delete
permutation s1rr s2rr s1q s2del202503 s1upd2025 s2c s1c s1q
# s1 fails from concurrent delete
permutation s1rr s2rr s1q s2del20252026 s1upd2025 s2c s1c s1q
# ########################################
# REPEATABLE READ tests, DELETE+UPDATE:
# ########################################
# s1 sees the leftovers
permutation s1rr s2rr s2upd2027 s2c s1del2025 s1c s1q
# s1 reloads the updated row and sees its leftovers
permutation s1rr s2rr s2upd202503 s2c s1del2025 s1c s1q
# s1 reloads the updated row and sees its leftovers
permutation s1rr s2rr s2upd20252026 s2c s1del2025 s1c s1q
# s2 sees the leftovers
permutation s1rr s2rr s1del2025 s1c s2upd2027 s2c s1q
# s2 ignores the deleted row
permutation s1rr s2rr s1del2025 s1c s2upd202503 s2c s1q
# s2 ignores the deleted row and sees its leftovers
permutation s1rr s2rr s1del2025 s1c s2upd20252026 s2c s1q
# s1 fails from concurrent update
permutation s1rr s2rr s2upd2027 s1del2025 s2c s1c s1q
# s1 fails from concurrent update
permutation s1rr s2rr s2upd202503 s1del2025 s2c s1c s1q
# s1 fails from concurrent update
permutation s1rr s2rr s2upd20252026 s1del2025 s2c s1c s1q
## with prior read by s1:
# s1 fails from concurrent update
permutation s1rr s2rr s1q s2upd2027 s2c s1del2025 s1c s1q
# s1 fails from concurrent update
permutation s1rr s2rr s1q s2upd202503 s2c s1del2025 s1c s1q
# s1 fails from concurrent update
permutation s1rr s2rr s1q s2upd20252026 s2c s1del2025 s1c s1q
# s2 sees the leftovers
permutation s1rr s2rr s1q s1del2025 s1c s2upd2027 s2c s1q
# s2 ignores the deleted row
permutation s1rr s2rr s1q s1del2025 s1c s2upd202503 s2c s1q
# s2 ignores the deleted row and sees its leftovers
permutation s1rr s2rr s1q s1del2025 s1c s2upd20252026 s2c s1q
# s1 fails from concurrent update
permutation s1rr s2rr s1q s2upd2027 s1del2025 s2c s1c s1q
# s1 fails from concurrent update
permutation s1rr s2rr s1q s2upd202503 s1del2025 s2c s1c s1q
# s1 fails from concurrent update
permutation s1rr s2rr s1q s2upd20252026 s1del2025 s2c s1c s1q
# ########################################
# REPEATABLE READ tests, DELETE+DELETE:
# ########################################
# s1 sees the leftovers
permutation s1rr s2rr s2del2027 s2c s1del2025 s1c s1q
# s1 reloads the updated row and sees its leftovers
permutation s1rr s2rr s2del202503 s2c s1del2025 s1c s1q
# s1 reloads the updated row and sees its leftovers
permutation s1rr s2rr s2del20252026 s2c s1del2025 s1c s1q
# s2 sees the leftovers
permutation s1rr s2rr s1del2025 s1c s2del2027 s2c s1q
# s2 ignores the deleted row
permutation s1rr s2rr s1del2025 s1c s2del202503 s2c s1q
# s2 ignores the deleted row and sees its leftovers
permutation s1rr s2rr s1del2025 s1c s2del20252026 s2c s1q
# s1 fails from concurrent delete
permutation s1rr s2rr s2del2027 s1del2025 s2c s1c s1q
# s1 fails from concurrent delete
permutation s1rr s2rr s2del202503 s1del2025 s2c s1c s1q
# s1 fails from concurrent delete
permutation s1rr s2rr s2del20252026 s1del2025 s2c s1c s1q
## with prior read by s1:
# s1 fails from concurrent delete
permutation s1rr s2rr s1q s2del2027 s2c s1del2025 s1c s1q
# s1 fails from concurrent delete
permutation s1rr s2rr s1q s2del202503 s2c s1del2025 s1c s1q
# s1 fails from concurrent delete
permutation s1rr s2rr s1q s2del20252026 s2c s1del2025 s1c s1q
# s2 sees the leftovers
permutation s1rr s2rr s1q s1del2025 s1c s2del2027 s2c s1q
# s2 ignores the deleted row
permutation s1rr s2rr s1q s1del2025 s1c s2del202503 s2c s1q
# s2 ignores the deleted row and sees its leftovers
permutation s1rr s2rr s1q s1del2025 s1c s2del20252026 s2c s1q
# s1 fails from concurrent delete
permutation s1rr s2rr s1q s2del2027 s1del2025 s2c s1c s1q
# s1 fails from concurrent delete
permutation s1rr s2rr s1q s2del202503 s1del2025 s2c s1c s1q
# s1 fails from concurrent delete
permutation s1rr s2rr s1q s2del20252026 s1del2025 s2c s1c s1q
# ########################################
# SERIALIZABLE tests, UPDATE+UPDATE:
# ########################################
# s1 sees the leftovers
permutation s1ser s2ser s2upd2027 s2c s1upd2025 s1c s1q
# s1 reloads the updated row and sees its leftovers
permutation s1ser s2ser s2upd20252026 s2c s1upd2025 s1c s1q