Improve concurrency of foreign key locking

This patch introduces two additional lock modes for tuples: "SELECT FOR
KEY SHARE" and "SELECT FOR NO KEY UPDATE".  These don't block each
other, in contrast with already existing "SELECT FOR SHARE" and "SELECT
FOR UPDATE".  UPDATE commands that do not modify the values stored in
the columns that are part of the key of the tuple now grab a SELECT FOR
NO KEY UPDATE lock on the tuple, allowing them to proceed concurrently
with tuple locks of the FOR KEY SHARE variety.

Foreign key triggers now use FOR KEY SHARE instead of FOR SHARE; this
means the concurrency improvement applies to them, which is the whole
point of this patch.

The added tuple lock semantics require some rejiggering of the multixact
module, so that the locking level that each transaction is holding can
be stored alongside its Xid.  Also, multixacts now need to persist
across server restarts and crashes, because they can now represent not
only tuple locks, but also tuple updates.  This means we need more
careful tracking of lifetime of pg_multixact SLRU files; since they now
persist longer, we require more infrastructure to figure out when they
can be removed.  pg_upgrade also needs to be careful to copy
pg_multixact files over from the old server to the new, or at least part
of multixact.c state, depending on the versions of the old and new
servers.

Tuple time qualification rules (HeapTupleSatisfies routines) need to be
careful not to consider tuples with the "is multi" infomask bit set as
being only locked; they might need to look up MultiXact values (i.e.
possibly do pg_multixact I/O) to find out the Xid that updated a tuple,
whereas they previously were assured to only use information readily
available from the tuple header.  This is considered acceptable, because
the extra I/O would involve cases that would previously cause some
commands to block waiting for concurrent transactions to finish.

Another important change is the fact that locking tuples that have
previously been updated causes the future versions to be marked as
locked, too; this is essential for correctness of foreign key checks.
This causes additional WAL-logging, also (there was previously a single
WAL record for a locked tuple; now there are as many as updated copies
of the tuple there exist.)

With all this in place, contention related to tuples being checked by
foreign key rules should be much reduced.

As a bonus, the old behavior that a subtransaction grabbing a stronger
tuple lock than the parent (sub)transaction held on a given tuple and
later aborting caused the weaker lock to be lost, has been fixed.

Many new spec files were added for isolation tester framework, to ensure
overall behavior is sane.  There's probably room for several more tests.

There were several reviewers of this patch; in particular, Noah Misch
and Andres Freund spent considerable time in it.  Original idea for the
patch came from Simon Riggs, after a problem report by Joel Jacobson.
Most code is from me, with contributions from Marti Raudsepp, Alexander
Shulgin, Noah Misch and Andres Freund.

This patch was discussed in several pgsql-hackers threads; the most
important start at the following message-ids:
	AANLkTimo9XVcEzfiBR-ut3KVNDkjm2Vxh+t8kAmWjPuv@mail.gmail.com
	1290721684-sup-3951@alvh.no-ip.org
	1294953201-sup-2099@alvh.no-ip.org
	1320343602-sup-2290@alvh.no-ip.org
	1339690386-sup-8927@alvh.no-ip.org
	4FE5FF020200002500048A3D@gw.wicourts.gov
	4FEAB90A0200002500048B7D@gw.wicourts.gov
This commit is contained in:
Alvaro Herrera 2013-01-23 12:04:59 -03:00
parent f925c79b9f
commit 0ac5ad5134
106 changed files with 6019 additions and 1483 deletions

View File

@ -191,7 +191,7 @@ ERROR: cannot change foreign table "agg_csv"
DELETE FROM agg_csv WHERE a = 100;
ERROR: cannot change foreign table "agg_csv"
SELECT * FROM agg_csv FOR UPDATE OF agg_csv;
ERROR: SELECT FOR UPDATE/SHARE cannot be used with foreign table "agg_csv"
ERROR: SELECT FOR UPDATE/SHARE/KEY UPDATE/KEY SHARE cannot be used with foreign table "agg_csv"
LINE 1: SELECT * FROM agg_csv FOR UPDATE OF agg_csv;
^
-- but this should be ignored

View File

@ -163,7 +163,7 @@ heap_page_items(PG_FUNCTION_ARGS)
tuphdr = (HeapTupleHeader) PageGetItem(page, id);
values[4] = UInt32GetDatum(HeapTupleHeaderGetXmin(tuphdr));
values[5] = UInt32GetDatum(HeapTupleHeaderGetXmax(tuphdr));
values[5] = UInt32GetDatum(HeapTupleHeaderGetRawXmax(tuphdr));
values[6] = UInt32GetDatum(HeapTupleHeaderGetRawCommandId(tuphdr)); /* shared with xvac */
values[7] = PointerGetDatum(&tuphdr->t_ctid);
values[8] = UInt32GetDatum(tuphdr->t_infomask2);

View File

@ -40,6 +40,9 @@ get_control_data(ClusterInfo *cluster, bool live_check)
bool got_xid = false;
bool got_oid = false;
bool got_nextxlogfile = false;
bool got_multi = false;
bool got_mxoff = false;
bool got_oldestmulti = false;
bool got_log_id = false;
bool got_log_seg = false;
bool got_tli = false;
@ -246,6 +249,39 @@ get_control_data(ClusterInfo *cluster, bool live_check)
cluster->controldata.chkpnt_nxtoid = str2uint(p);
got_oid = true;
}
else if ((p = strstr(bufin, "Latest checkpoint's NextMultiXactId:")) != NULL)
{
p = strchr(p, ':');
if (p == NULL || strlen(p) <= 1)
pg_log(PG_FATAL, "%d: controldata retrieval problem\n", __LINE__);
p++; /* removing ':' char */
cluster->controldata.chkpnt_nxtmulti = str2uint(p);
got_multi = true;
}
else if ((p = strstr(bufin, "Latest checkpoint's oldestMultiXid:")) != NULL)
{
p = strchr(p, ':');
if (p == NULL || strlen(p) <= 1)
pg_log(PG_FATAL, "%d: controldata retrieval problem\n", __LINE__);
p++; /* removing ':' char */
cluster->controldata.chkpnt_oldstMulti = str2uint(p);
got_oldestmulti = true;
}
else if ((p = strstr(bufin, "Latest checkpoint's NextMultiOffset:")) != NULL)
{
p = strchr(p, ':');
if (p == NULL || strlen(p) <= 1)
pg_log(PG_FATAL, "%d: controldata retrieval problem\n", __LINE__);
p++; /* removing ':' char */
cluster->controldata.chkpnt_nxtmxoff = str2uint(p);
got_mxoff = true;
}
else if ((p = strstr(bufin, "Maximum data alignment:")) != NULL)
{
p = strchr(p, ':');
@ -433,6 +469,7 @@ get_control_data(ClusterInfo *cluster, bool live_check)
/* verify that we got all the mandatory pg_control data */
if (!got_xid || !got_oid ||
!got_multi || !got_mxoff || !got_oldestmulti ||
(!live_check && !got_nextxlogfile) ||
!got_tli ||
!got_align || !got_blocksz || !got_largesz || !got_walsz ||
@ -448,6 +485,15 @@ get_control_data(ClusterInfo *cluster, bool live_check)
if (!got_oid)
pg_log(PG_REPORT, " latest checkpoint next OID\n");
if (!got_multi)
pg_log(PG_REPORT, " latest checkpoint next MultiXactId\n");
if (!got_mxoff)
pg_log(PG_REPORT, " latest checkpoint next MultiXactOffset\n");
if (!got_oldestmulti)
pg_log(PG_REPORT, " latest checkpoint oldest MultiXactId\n");
if (!live_check && !got_nextxlogfile)
pg_log(PG_REPORT, " first WAL segment after reset\n");

View File

@ -382,6 +382,52 @@ copy_clog_xlog_xid(void)
new_cluster.pgdata);
check_ok();
/*
* If both new and old are after the pg_multixact change commit, copy those
* files too. If the old server is before that change and the new server
* is after, then we don't copy anything but we need to reset pg_control so
* that the new server doesn't attempt to read multis older than the cutoff
* value.
*/
if (old_cluster.controldata.cat_ver >= MULTIXACT_FORMATCHANGE_CAT_VER &&
new_cluster.controldata.cat_ver >= MULTIXACT_FORMATCHANGE_CAT_VER)
{
copy_subdir_files("pg_multixact/offsets");
copy_subdir_files("pg_multixact/members");
prep_status("Setting next multixact ID and offset for new cluster");
/*
* we preserve all files and contents, so we must preserve both "next"
* counters here and the oldest multi present on system.
*/
exec_prog(UTILITY_LOG_FILE, NULL, true,
"\"%s/pg_resetxlog\" -O %u -m %u,%u \"%s\"",
new_cluster.bindir,
old_cluster.controldata.chkpnt_nxtmxoff,
old_cluster.controldata.chkpnt_nxtmulti,
old_cluster.controldata.chkpnt_oldstMulti,
new_cluster.pgdata);
check_ok();
}
else if (new_cluster.controldata.cat_ver >= MULTIXACT_FORMATCHANGE_CAT_VER)
{
prep_status("Setting oldest multixact ID on new cluster");
/*
* We don't preserve files in this case, but it's important that the
* oldest multi is set to the latest value used by the old system, so
* that multixact.c returns the empty set for multis that might be
* present on disk. We set next multi to the value following that; it
* might end up wrapped around (i.e. 0) if the old cluster had
* next=MaxMultiXactId, but multixact.c can cope with that just fine.
*/
exec_prog(UTILITY_LOG_FILE, NULL, true,
"\"%s/pg_resetxlog\" -m %u,%u \"%s\"",
new_cluster.bindir,
old_cluster.controldata.chkpnt_nxtmulti + 1,
old_cluster.controldata.chkpnt_nxtmulti,
new_cluster.pgdata);
check_ok();
}
/* now reset the wal archives in the new cluster */
prep_status("Resetting WAL archives");
exec_prog(UTILITY_LOG_FILE, NULL, true,

View File

@ -108,6 +108,10 @@ extern char *output_files[];
*/
#define VISIBILITY_MAP_CRASHSAFE_CAT_VER 201107031
/*
* pg_multixact format changed in this catversion:
*/
#define MULTIXACT_FORMATCHANGE_CAT_VER 201301231
/*
* Each relation is represented by a relinfo structure.
@ -182,6 +186,9 @@ typedef struct
uint32 chkpnt_tli;
uint32 chkpnt_nxtxid;
uint32 chkpnt_nxtoid;
uint32 chkpnt_nxtmulti;
uint32 chkpnt_nxtmxoff;
uint32 chkpnt_oldstMulti;
uint32 align;
uint32 blocksz;
uint32 largesz;

View File

@ -4,7 +4,7 @@ MODULE_big = pgrowlocks
OBJS = pgrowlocks.o
EXTENSION = pgrowlocks
DATA = pgrowlocks--1.0.sql pgrowlocks--unpackaged--1.0.sql
DATA = pgrowlocks--1.1.sql pgrowlocks--1.0--1.1.sql pgrowlocks--unpackaged--1.0.sql
ifdef USE_PGXS
PG_CONFIG = pg_config

View File

@ -0,0 +1,17 @@
/* contrib/pgrowlocks/pgrowlocks--1.0--1.1.sql */
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION pgrowlocks" to load this file. \quit
ALTER EXTENSION pgrowlocks DROP FUNCTION pgrowlocks(text);
DROP FUNCTION pgrowlocks(text);
CREATE FUNCTION pgrowlocks(IN relname text,
OUT locked_row TID, -- row TID
OUT locker XID, -- locking XID
OUT multi bool, -- multi XID?
OUT xids xid[], -- multi XIDs
OUT modes text[], -- multi XID statuses
OUT pids INTEGER[]) -- locker's process id
RETURNS SETOF record
AS 'MODULE_PATHNAME', 'pgrowlocks'
LANGUAGE C STRICT;

View File

@ -1,14 +1,14 @@
/* contrib/pgrowlocks/pgrowlocks--1.0.sql */
/* contrib/pgrowlocks/pgrowlocks--1.1.sql */
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION pgrowlocks" to load this file. \quit
CREATE FUNCTION pgrowlocks(IN relname text,
OUT locked_row TID, -- row TID
OUT lock_type TEXT, -- lock type
OUT locker XID, -- locking XID
OUT multi bool, -- multi XID?
OUT xids xid[], -- multi XIDs
OUT modes text[], -- multi XID statuses
OUT pids INTEGER[]) -- locker's process id
RETURNS SETOF record
AS 'MODULE_PATHNAME', 'pgrowlocks'

View File

@ -59,6 +59,13 @@ typedef struct
int ncolumns;
} MyData;
#define Atnum_tid 0
#define Atnum_xmax 1
#define Atnum_ismulti 2
#define Atnum_xids 3
#define Atnum_modes 4
#define Atnum_pids 5
Datum
pgrowlocks(PG_FUNCTION_ARGS)
{
@ -117,79 +124,146 @@ pgrowlocks(PG_FUNCTION_ARGS)
/* scan the relation */
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
HTSU_Result htsu;
TransactionId xmax;
uint16 infomask;
/* must hold a buffer lock to call HeapTupleSatisfiesUpdate */
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
if (HeapTupleSatisfiesUpdate(tuple->t_data,
GetCurrentCommandId(false),
scan->rs_cbuf) == HeapTupleBeingUpdated)
{
htsu = HeapTupleSatisfiesUpdate(tuple->t_data,
GetCurrentCommandId(false),
scan->rs_cbuf);
xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
infomask = tuple->t_data->t_infomask;
/*
* a tuple is locked if HTSU returns BeingUpdated, and if it returns
* MayBeUpdated but the Xmax is valid and pointing at us.
*/
if (htsu == HeapTupleBeingUpdated ||
(htsu == HeapTupleMayBeUpdated &&
!(infomask & HEAP_XMAX_INVALID) &&
!(infomask & HEAP_XMAX_IS_MULTI) &&
(xmax == GetCurrentTransactionIdIfAny())))
{
char **values;
int i;
values = (char **) palloc(mydata->ncolumns * sizeof(char *));
i = 0;
values[i++] = (char *) DirectFunctionCall1(tidout, PointerGetDatum(&tuple->t_self));
values[Atnum_tid] = (char *) DirectFunctionCall1(tidout,
PointerGetDatum(&tuple->t_self));
if (tuple->t_data->t_infomask & HEAP_XMAX_SHARED_LOCK)
values[i++] = pstrdup("Shared");
else
values[i++] = pstrdup("Exclusive");
values[i] = palloc(NCHARS * sizeof(char));
snprintf(values[i++], NCHARS, "%d", HeapTupleHeaderGetXmax(tuple->t_data));
if (tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)
values[Atnum_xmax] = palloc(NCHARS * sizeof(char));
snprintf(values[Atnum_xmax], NCHARS, "%d", xmax);
if (infomask & HEAP_XMAX_IS_MULTI)
{
TransactionId *xids;
int nxids;
int j;
int isValidXid = 0; /* any valid xid ever exists? */
MultiXactMember *members;
int nmembers;
bool first = true;
bool allow_old;
values[i++] = pstrdup("true");
nxids = GetMultiXactIdMembers(HeapTupleHeaderGetXmax(tuple->t_data), &xids);
if (nxids == -1)
values[Atnum_ismulti] = pstrdup("true");
allow_old = !(infomask & HEAP_LOCK_MASK) &&
(infomask & HEAP_XMAX_LOCK_ONLY);
nmembers = GetMultiXactIdMembers(xmax, &members, allow_old);
if (nmembers == -1)
{
elog(ERROR, "GetMultiXactIdMembers returns error");
values[Atnum_xids] = "{0}";
values[Atnum_modes] = "{transient upgrade status}";
values[Atnum_pids] = "{0}";
}
values[i] = palloc(NCHARS * nxids);
values[i + 1] = palloc(NCHARS * nxids);
strcpy(values[i], "{");
strcpy(values[i + 1], "{");
for (j = 0; j < nxids; j++)
else
{
char buf[NCHARS];
int j;
if (TransactionIdIsInProgress(xids[j]))
values[Atnum_xids] = palloc(NCHARS * nmembers);
values[Atnum_modes] = palloc(NCHARS * nmembers);
values[Atnum_pids] = palloc(NCHARS * nmembers);
strcpy(values[Atnum_xids], "{");
strcpy(values[Atnum_modes], "{");
strcpy(values[Atnum_pids], "{");
for (j = 0; j < nmembers; j++)
{
if (isValidXid)
char buf[NCHARS];
if (!first)
{
strcat(values[i], ",");
strcat(values[i + 1], ",");
strcat(values[Atnum_xids], ",");
strcat(values[Atnum_modes], ",");
strcat(values[Atnum_pids], ",");
}
snprintf(buf, NCHARS, "%d", xids[j]);
strcat(values[i], buf);
snprintf(buf, NCHARS, "%d", BackendXidGetPid(xids[j]));
strcat(values[i + 1], buf);
snprintf(buf, NCHARS, "%d", members[j].xid);
strcat(values[Atnum_xids], buf);
switch (members[j].status)
{
case MultiXactStatusUpdate:
snprintf(buf, NCHARS, "Update");
break;
case MultiXactStatusNoKeyUpdate:
snprintf(buf, NCHARS, "No Key Update");
break;
case MultiXactStatusForUpdate:
snprintf(buf, NCHARS, "For Update");
break;
case MultiXactStatusForNoKeyUpdate:
snprintf(buf, NCHARS, "For No Key Update");
break;
case MultiXactStatusForShare:
snprintf(buf, NCHARS, "Share");
break;
case MultiXactStatusForKeyShare:
snprintf(buf, NCHARS, "Key Share");
break;
}
strcat(values[Atnum_modes], buf);
snprintf(buf, NCHARS, "%d",
BackendXidGetPid(members[j].xid));
strcat(values[Atnum_pids], buf);
isValidXid = 1;
first = false;
}
}
strcat(values[i], "}");
strcat(values[i + 1], "}");
i++;
strcat(values[Atnum_xids], "}");
strcat(values[Atnum_modes], "}");
strcat(values[Atnum_pids], "}");
}
}
else
{
values[i++] = pstrdup("false");
values[i] = palloc(NCHARS * sizeof(char));
snprintf(values[i++], NCHARS, "{%d}", HeapTupleHeaderGetXmax(tuple->t_data));
values[Atnum_ismulti] = pstrdup("false");
values[i] = palloc(NCHARS * sizeof(char));
snprintf(values[i++], NCHARS, "{%d}", BackendXidGetPid(HeapTupleHeaderGetXmax(tuple->t_data)));
values[Atnum_xids] = palloc(NCHARS * sizeof(char));
snprintf(values[Atnum_xids], NCHARS, "{%d}", xmax);
values[Atnum_modes] = palloc(NCHARS);
if (infomask & HEAP_XMAX_LOCK_ONLY)
{
if (HEAP_XMAX_IS_SHR_LOCKED(infomask))
snprintf(values[Atnum_modes], NCHARS, "{For Share}");
else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask))
snprintf(values[Atnum_modes], NCHARS, "{For Key Share}");
else if (HEAP_XMAX_IS_EXCL_LOCKED(infomask))
snprintf(values[Atnum_modes], NCHARS, "{For Update}");
else
/* neither keyshare nor exclusive bit it set */
snprintf(values[Atnum_modes], NCHARS,
"{transient upgrade status}");
}
else
{
if (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)
snprintf(values[Atnum_modes], NCHARS, "{Key Update}");
else
snprintf(values[Atnum_modes], NCHARS, "{Update}");
}
values[Atnum_pids] = palloc(NCHARS * sizeof(char));
snprintf(values[Atnum_pids], NCHARS, "{%d}",
BackendXidGetPid(xmax));
}
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
@ -200,10 +274,10 @@ pgrowlocks(PG_FUNCTION_ARGS)
/* make the tuple into a datum */
result = HeapTupleGetDatum(tuple);
/* Clean up */
for (i = 0; i < mydata->ncolumns; i++)
pfree(values[i]);
pfree(values);
/*
* no need to pfree what we allocated; it's on a short-lived memory
* context anyway
*/
SRF_RETURN_NEXT(funcctx, result);
}

View File

@ -1,5 +1,5 @@
# pgrowlocks extension
comment = 'show row-level locking information'
default_version = '1.0'
default_version = '1.1'
module_pathname = '$libdir/pgrowlocks'
relocatable = true

View File

@ -43,12 +43,6 @@ pgrowlocks(text) returns setof record
<entry><type>tid</type></entry>
<entry>Tuple ID (TID) of locked row</entry>
</row>
<row>
<entry><structfield>lock_type</structfield></entry>
<entry><type>text</type></entry>
<entry><literal>Shared</> for shared lock, or
<literal>Exclusive</> for exclusive lock</entry>
</row>
<row>
<entry><structfield>locker</structfield></entry>
<entry><type>xid</type></entry>
@ -64,6 +58,15 @@ pgrowlocks(text) returns setof record
<entry><type>xid[]</type></entry>
<entry>Transaction IDs of lockers (more than one if multitransaction)</entry>
</row>
<row>
<entry><structfield>lock_type</structfield></entry>
<entry><type>text[]</type></entry>
<entry>Lock mode of lockers (more than one if multitransaction),
an array of <literal>Key Share</>, <literal>Share</>,
<literal>For No Key Update</>, <literal>No Key Update</>,
<literal>For Update</>, <literal>Update</>.</entry>
</row>
<row>
<entry><structfield>pids</structfield></entry>
<entry><type>integer[]</type></entry>

View File

@ -45,7 +45,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
[ LIMIT { <replaceable class="parameter">count</replaceable> | ALL } ]
[ OFFSET <replaceable class="parameter">start</replaceable> [ ROW | ROWS ] ]
[ FETCH { FIRST | NEXT } [ <replaceable class="parameter">count</replaceable> ] { ROW | ROWS } ONLY ]
[ FOR { UPDATE | SHARE } [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ] [ NOWAIT ] [...] ]
[ FOR { UPDATE | NO KEY UPDATE | SHARE | KEY SHARE } [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ] [ NOWAIT ] [...] ]
<phrase>where <replaceable class="parameter">from_item</replaceable> can be one of:</phrase>
@ -178,7 +178,8 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
<listitem>
<para>
If <literal>FOR UPDATE</literal> or <literal>FOR SHARE</literal>
If <literal>FOR UPDATE</>, <literal>FOR NO KEY UPDATE</literal>, <literal>FOR SHARE</literal>
or <literal>FOR KEY SHARE</literal>
is specified, the
<command>SELECT</command> statement locks the selected rows
against concurrent updates. (See <xref linkend="sql-for-update-share"
@ -190,8 +191,9 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
<para>
You must have <literal>SELECT</literal> privilege on each column used
in a <command>SELECT</> command. The use of <literal>FOR UPDATE</literal>
or <literal>FOR SHARE</literal> requires
in a <command>SELECT</> command. The use of <literal>FOR NO KEY UPDATE</>,
<literal>FOR UPDATE</literal>,
<literal>FOR SHARE</literal> or <literal>FOR KEY SHARE</literal> requires
<literal>UPDATE</literal> privilege as well (for at least one column
of each table so selected).
</para>
@ -873,8 +875,8 @@ SELECT DISTINCT ON (location) location, time, report
<replaceable class="parameter">select_statement</replaceable> UNION [ ALL | DISTINCT ] <replaceable class="parameter">select_statement</replaceable>
</synopsis><replaceable class="parameter">select_statement</replaceable> is
any <command>SELECT</command> statement without an <literal>ORDER
BY</>, <literal>LIMIT</>, <literal>FOR UPDATE</literal>, or
<literal>FOR SHARE</literal> clause.
BY</>, <literal>LIMIT</>, <literal>FOR NO KEY UPDATE</>, <literal>FOR UPDATE</literal>,
<literal>FOR SHARE</literal>, or <literal>FOR KEY SHARE</literal> clause.
(<literal>ORDER BY</> and <literal>LIMIT</> can be attached to a
subexpression if it is enclosed in parentheses. Without
parentheses, these clauses will be taken to apply to the result of
@ -910,7 +912,8 @@ SELECT DISTINCT ON (location) location, time, report
</para>
<para>
Currently, <literal>FOR UPDATE</> and <literal>FOR SHARE</> cannot be
Currently, <literal>FOR NO KEY UPDATE</>, <literal>FOR UPDATE</>, <literal>FOR SHARE</> and
<literal>FOR KEY SHARE</> cannot be
specified either for a <literal>UNION</> result or for any input of a
<literal>UNION</>.
</para>
@ -925,8 +928,8 @@ SELECT DISTINCT ON (location) location, time, report
<replaceable class="parameter">select_statement</replaceable> INTERSECT [ ALL | DISTINCT ] <replaceable class="parameter">select_statement</replaceable>
</synopsis><replaceable class="parameter">select_statement</replaceable> is
any <command>SELECT</command> statement without an <literal>ORDER
BY</>, <literal>LIMIT</>, <literal>FOR UPDATE</literal>, or
<literal>FOR SHARE</literal> clause.
BY</>, <literal>LIMIT</>, <literal>FOR NO KEY UPDATE</>, <literal>FOR UPDATE</literal>,
<literal>FOR SHARE</literal>, or <literal>FOR KEY SHARE</> clause.
</para>
<para>
@ -957,7 +960,8 @@ SELECT DISTINCT ON (location) location, time, report
</para>
<para>
Currently, <literal>FOR UPDATE</> and <literal>FOR SHARE</> cannot be
Currently, <literal>FOR NO KEY UPDATE</>, <literal>FOR UPDATE</>, <literal>FOR SHARE</> and
<literal>FOR KEY SHARE</> cannot be
specified either for an <literal>INTERSECT</> result or for any input of
an <literal>INTERSECT</>.
</para>
@ -972,8 +976,8 @@ SELECT DISTINCT ON (location) location, time, report
<replaceable class="parameter">select_statement</replaceable> EXCEPT [ ALL | DISTINCT ] <replaceable class="parameter">select_statement</replaceable>
</synopsis><replaceable class="parameter">select_statement</replaceable> is
any <command>SELECT</command> statement without an <literal>ORDER
BY</>, <literal>LIMIT</>, <literal>FOR UPDATE</literal>, or
<literal>FOR SHARE</literal> clause.
BY</>, <literal>LIMIT</>, <literal>FOR NO KEY UPDATE</>, <literal>FOR UPDATE</literal>,
<literal>FOR SHARE</literal>, or <literal>FOR KEY SHARE</> clause.
</para>
<para>
@ -1000,7 +1004,8 @@ SELECT DISTINCT ON (location) location, time, report
</para>
<para>
Currently, <literal>FOR UPDATE</> and <literal>FOR SHARE</> cannot be
Currently, <literal>FOR NO KEY UPDATE</>, <literal>FOR UPDATE</>, <literal>FOR SHARE</> and
<literal>FOR KEY SHARE</> cannot be
specified either for an <literal>EXCEPT</> result or for any input of
an <literal>EXCEPT</>.
</para>
@ -1185,7 +1190,14 @@ FETCH { FIRST | NEXT } [ <replaceable class="parameter">count</replaceable> ] {
</refsect2>
<refsect2 id="SQL-FOR-UPDATE-SHARE">
<title id="sql-for-update-share-title"><literal>FOR UPDATE</literal>/<literal>FOR SHARE</literal> Clause</title>
<title id="sql-for-update-share-title"><literal>FOR UPDATE</>, <literal>FOR NO KEY UPDATE</>/<literal>FOR SHARE</>/<literal>FOR KEY SHARE</> Clauses</title>
<para>
<literal>FOR UPDATE</>, <literal>FOR NO KEY UPDATE</>, <literal>FOR SHARE</>
and <literal>FOR KEY SHARE</>
are <firstterm>locking clauses</>; they affect how <literal>SELECT</>
locks rows as they are obtained from the table.
</para>
<para>
The <literal>FOR UPDATE</literal> clause has this form:
@ -1194,6 +1206,13 @@ FOR UPDATE [ OF <replaceable class="parameter">table_name</replaceable> [, ...]
</synopsis>
</para>
<para>
The <literal>FOR NO KEY UPDATE</literal> clause has this form:
<synopsis>
FOR NO KEY UPDATE [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ] [ NOWAIT ]
</synopsis>
</para>
<para>
The closely related <literal>FOR SHARE</literal> clause has this form:
<synopsis>
@ -1201,14 +1220,31 @@ FOR SHARE [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ]
</synopsis>
</para>
<para>
Similarly, the <literal>FOR KEY SHARE</> clause has this form:
<synopsis>
FOR KEY SHARE [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ] [ NOWAIT ]
</synopsis>
</para>
<para>
<literal>FOR UPDATE</literal> causes the rows retrieved by the
<command>SELECT</command> statement to be locked as though for
update. This prevents them from being modified or deleted by
other transactions until the current transaction ends. That is,
other transactions that attempt <command>UPDATE</command>,
<command>DELETE</command>, or <command>SELECT FOR UPDATE</command>
<command>DELETE</command>,
<command>SELECT FOR UPDATE</command>,
<command>SELECT FOR SHARE</command> or
<command>SELECT FOR KEY SHARE</command>
of these rows will be blocked until the current transaction ends.
The <literal>FOR UPDATE</> lock mode
is also acquired by any <command>DELETE</> on a row, and also by an
<command>UPDATE</> that modifies the values on certain columns. Currently,
the set of columns considered for the <command>UPDATE</> case are those that
have an unique index on them that can be used in a foreign key (so partial
indexes and expressional indexes are not considered), but this may change
in the future.
Also, if an <command>UPDATE</command>, <command>DELETE</command>,
or <command>SELECT FOR UPDATE</command> from another transaction
has already locked a selected row or rows, <command>SELECT FOR
@ -1220,13 +1256,33 @@ FOR SHARE [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ]
linkend="mvcc">.
</para>
<para>
<literal>FOR NO KEY UPDATE</> behaves similarly, except that the lock
acquired is weaker: this lock will not block
<literal>SELECT FOR KEY SHARE</> commands that attempt to acquire
a lock on the same rows.
</para>
<para>
<literal>FOR SHARE</literal> behaves similarly, except that it
acquires a shared rather than exclusive lock on each retrieved
row. A shared lock blocks other transactions from performing
<command>UPDATE</command>, <command>DELETE</command>, or <command>SELECT
FOR UPDATE</command> on these rows, but it does not prevent them
from performing <command>SELECT FOR SHARE</command>.
from performing <command>SELECT FOR SHARE</command> or
<command>SELECT FOR KEY SHARE</command>.
</para>
<para>
<literal>FOR KEY SHARE</> behaves similarly to <literal>FOR SHARE</literal>,
except that the lock
is weaker: <literal>SELECT FOR UPDATE</> is blocked, but
not <literal>SELECT FOR NO KEY UPDATE</>. A key-shared
lock blocks other transactions from performing <command>DELETE</command>
or any <command>UPDATE</command> that changes the key values, but not
other <command>UPDATE</>, and neither it does prevent
<command>SELECT FOR UPDATE</>, <command>SELECT FOR SHARE</>, or
<command>SELECT FOR KEY SHARE</>.
</para>
<para>
@ -1243,41 +1299,39 @@ FOR SHARE [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ]
</para>
<para>
If specific tables are named in <literal>FOR UPDATE</literal>
or <literal>FOR SHARE</literal>,
If specific tables are named in a locking clause,
then only rows coming from those tables are locked; any other
tables used in the <command>SELECT</command> are simply read as
usual. A <literal>FOR UPDATE</literal> or <literal>FOR SHARE</literal>
usual. A locking
clause without a table list affects all tables used in the statement.
If <literal>FOR UPDATE</literal> or <literal>FOR SHARE</literal> is
If a locking clause is
applied to a view or sub-query, it affects all tables used in
the view or sub-query.
However, <literal>FOR UPDATE</literal>/<literal>FOR SHARE</literal>
However, these clauses
do not apply to <literal>WITH</> queries referenced by the primary query.
If you want row locking to occur within a <literal>WITH</> query, specify
<literal>FOR UPDATE</literal> or <literal>FOR SHARE</literal> within the
<literal>WITH</> query.
a locking clause within the <literal>WITH</> query.
</para>
<para>
Multiple <literal>FOR UPDATE</literal> and <literal>FOR SHARE</literal>
Multiple locking
clauses can be written if it is necessary to specify different locking
behavior for different tables. If the same table is mentioned (or
implicitly affected) by both <literal>FOR UPDATE</literal> and
<literal>FOR SHARE</literal> clauses, then it is processed as
<literal>FOR UPDATE</literal>. Similarly, a table is processed
implicitly affected) by more than one locking clause,
then it is processed as if it was only specified by the strongest one.
Similarly, a table is processed
as <literal>NOWAIT</> if that is specified in any of the clauses
affecting it.
</para>
<para>
<literal>FOR UPDATE</literal> and <literal>FOR SHARE</literal> cannot be
The locking clauses cannot be
used in contexts where returned rows cannot be clearly identified with
individual table rows; for example they cannot be used with aggregation.
</para>
<para>
When <literal>FOR UPDATE</literal> or <literal>FOR SHARE</literal>
When a locking clause
appears at the top level of a <command>SELECT</> query, the rows that
are locked are exactly those that are returned by the query; in the
case of a join query, the rows locked are those that contribute to
@ -1288,13 +1342,13 @@ FOR SHARE [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ]
<literal>LIMIT</> is used, locking stops
once enough rows have been returned to satisfy the limit (but note that
rows skipped over by <literal>OFFSET</> will get locked). Similarly,
if <literal>FOR UPDATE</literal> or <literal>FOR SHARE</literal>
if a locking clause
is used in a cursor's query, only rows actually fetched or stepped past
by the cursor will be locked.
</para>
<para>
When <literal>FOR UPDATE</literal> or <literal>FOR SHARE</literal>
When a locking clause
appears in a sub-<command>SELECT</>, the rows locked are those
returned to the outer query by the sub-query. This might involve
fewer rows than inspection of the sub-query alone would suggest,
@ -1307,11 +1361,9 @@ SELECT * FROM (SELECT * FROM mytable FOR UPDATE) ss WHERE col1 = 5;
condition is not textually within the sub-query.
</para>
<caution>
<para>
Avoid locking a row and then modifying it within a later savepoint or
<application>PL/pgSQL</application> exception block. A subsequent
rollback would cause the lock to be lost. For example:
<para>
Previous releases failed to preserve a lock which is upgraded by a later
savepoint. For example, this code:
<programlisting>
BEGIN;
SELECT * FROM mytable WHERE key = 1 FOR UPDATE;
@ -1319,23 +1371,15 @@ SAVEPOINT s;
UPDATE mytable SET ... WHERE key = 1;
ROLLBACK TO s;
</programlisting>
After the <command>ROLLBACK</>, the row is effectively unlocked, rather
than returned to its pre-savepoint state of being locked but not modified.
This hazard occurs if a row locked in the current transaction is updated
or deleted, or if a shared lock is upgraded to exclusive: in all these
cases, the former lock state is forgotten. If the transaction is then
rolled back to a state between the original locking command and the
subsequent change, the row will appear not to be locked at all. This is
an implementation deficiency which will be addressed in a future release
of <productname>PostgreSQL</productname>.
</para>
</caution>
would fail to preserve the <literal>FOR UPDATE</> lock after the
<command>ROLLBACK</>. This has been fixed in release 9.2.
</para>
<caution>
<para>
It is possible for a <command>SELECT</> command running at the <literal>READ
COMMITTED</literal> transaction isolation level and using <literal>ORDER
BY</literal> and <literal>FOR UPDATE/SHARE</literal> to return rows out of
BY</literal> and a locking clause to return rows out of
order. This is because <literal>ORDER BY</> is applied first.
The command sorts the result, but might then block trying to obtain a lock
on one or more of the rows. Once the <literal>SELECT</> unblocks, some
@ -1765,14 +1809,16 @@ SELECT distributors.* WHERE distributors.name = 'Westward';
</refsect2>
<refsect2>
<title><literal>FOR UPDATE</> and <literal>FOR SHARE</></title>
<title><literal>FOR NO KEY UPDATE</>, <literal>FOR UPDATE</>, <literal>FOR SHARE</>, <literal>FOR KEY SHARE</></title>
<para>
Although <literal>FOR UPDATE</> appears in the SQL standard, the
standard allows it only as an option of <command>DECLARE CURSOR</>.
<productname>PostgreSQL</productname> allows it in any <command>SELECT</>
query as well as in sub-<command>SELECT</>s, but this is an extension.
The <literal>FOR SHARE</> variant, and the <literal>NOWAIT</> option,
The <literal>FOR NO KEY UPDATE</>, <literal>FOR SHARE</> and
<literal>FOR KEY SHARE</> variants,
as well as the <literal>NOWAIT</> option,
do not appear in the standard.
</para>
</refsect2>

View File

@ -542,7 +542,7 @@ heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull)
result = TransactionIdGetDatum(HeapTupleHeaderGetXmin(tup->t_data));
break;
case MaxTransactionIdAttributeNumber:
result = TransactionIdGetDatum(HeapTupleHeaderGetXmax(tup->t_data));
result = TransactionIdGetDatum(HeapTupleHeaderGetRawXmax(tup->t_data));
break;
case MinCommandIdAttributeNumber:
case MaxCommandIdAttributeNumber:

View File

@ -0,0 +1,139 @@
Locking tuples
--------------
Locking tuples is not as easy as locking tables or other database objects.
The problem is that transactions might want to lock large numbers of tuples at
any one time, so it's not possible to keep the locks objects in shared memory.
To work around this limitation, we use a two-level mechanism. The first level
is implemented by storing locking information in the tuple header: a tuple is
marked as locked by setting the current transaction's XID as its XMAX, and
setting additional infomask bits to distinguish this case from the more normal
case of having deleted the tuple. When multiple transactions concurrently
lock a tuple, a MultiXact is used; see below. This mechanism can accomodate
arbitrarily large numbers of tuples being locked simultaneously.
When it is necessary to wait for a tuple-level lock to be released, the basic
delay is provided by XactLockTableWait or MultiXactIdWait on the contents of
the tuple's XMAX. However, that mechanism will release all waiters
concurrently, so there would be a race condition as to which waiter gets the
tuple, potentially leading to indefinite starvation of some waiters. The
possibility of share-locking makes the problem much worse --- a steady stream
of share-lockers can easily block an exclusive locker forever. To provide
more reliable semantics about who gets a tuple-level lock first, we use the
standard lock manager, which implements the second level mentioned above. The
protocol for waiting for a tuple-level lock is really
LockTuple()
XactLockTableWait()
mark tuple as locked by me
UnlockTuple()
When there are multiple waiters, arbitration of who is to get the lock next
is provided by LockTuple(). However, at most one tuple-level lock will
be held or awaited per backend at any time, so we don't risk overflow
of the lock table. Note that incoming share-lockers are required to
do LockTuple as well, if there is any conflict, to ensure that they don't
starve out waiting exclusive-lockers. However, if there is not any active
conflict for a tuple, we don't incur any extra overhead.
We provide four levels of tuple locking strength: SELECT FOR KEY UPDATE is
super-exclusive locking (used to delete tuples and more generally to update
tuples modifying the values of the columns that make up the key of the tuple);
SELECT FOR UPDATE is a standards-compliant exclusive lock; SELECT FOR SHARE
implements shared locks; and finally SELECT FOR KEY SHARE is a super-weak mode
that does not conflict with exclusive mode, but conflicts with SELECT FOR KEY
UPDATE. This last mode implements a mode just strong enough to implement RI
checks, i.e. it ensures that tuples do not go away from under a check, without
blocking when some other transaction that want to update the tuple without
changing its key.
The conflict table is:
KEY UPDATE UPDATE SHARE KEY SHARE
KEY UPDATE conflict conflict conflict conflict
UPDATE conflict conflict conflict
SHARE conflict conflict
KEY SHARE conflict
When there is a single locker in a tuple, we can just store the locking info
in the tuple itself. We do this by storing the locker's Xid in XMAX, and
setting infomask bits specifying the locking strength. There is one exception
here: since infomask space is limited, we do not provide a separate bit
for SELECT FOR SHARE, so we have to use the extended info in a MultiXact in
that case. (The other cases, SELECT FOR UPDATE and SELECT FOR KEY SHARE, are
presumably more commonly used due to being the standards-mandated locking
mechanism, or heavily used by the RI code, so we want to provide fast paths
for those.)
MultiXacts
----------
A tuple header provides very limited space for storing information about tuple
locking and updates: there is room only for a single Xid and a small number of
infomask bits. Whenever we need to store more than one lock, we replace the
first locker's Xid with a new MultiXactId. Each MultiXact provides extended
locking data; it comprises an array of Xids plus some flags bits for each one.
The flags are currently used to store the locking strength of each member
transaction. (The flags also distinguish a pure locker from an updater.)
In earlier PostgreSQL releases, a MultiXact always meant that the tuple was
locked in shared mode by multiple transactions. This is no longer the case; a
MultiXact may contain an update or delete Xid. (Keep in mind that tuple locks
in a transaction do not conflict with other tuple locks in the same
transaction, so it's possible to have otherwise conflicting locks in a
MultiXact if they belong to the same transaction).
Note that each lock is attributed to the subtransaction that acquires it.
This means that a subtransaction that aborts is seen as though it releases the
locks it acquired; concurrent transactions can then proceed without having to
wait for the main transaction to finish. It also means that a subtransaction
can upgrade to a stronger lock level than an earlier transaction had, and if
the subxact aborts, the earlier, weaker lock is kept.
The possibility of having an update within a MultiXact means that they must
persist across crashes and restarts: a future reader of the tuple needs to
figure out whether the update committed or aborted. So we have a requirement
that pg_multixact needs to retain pages of its data until we're certain that
the MultiXacts in them are no longer of interest.
VACUUM is in charge of removing old MultiXacts at the time of tuple freezing.
This works in the same way that pg_clog segments are removed: we have a
pg_class column that stores the earliest multixact that could possibly be
stored in the table; the minimum of all such values is stored in a pg_database
column. VACUUM computes the minimum across all pg_database values, and
removes pg_multixact segments older than the minimum.
Infomask Bits
-------------
The following infomask bits are applicable:
- HEAP_XMAX_INVALID
Any tuple with this bit set does not have a valid value stored in XMAX.
- HEAP_XMAX_IS_MULTI
This bit is set if the tuple's Xmax is a MultiXactId (as opposed to a
regular TransactionId).
- HEAP_XMAX_LOCK_ONLY
This bit is set when the XMAX is a locker only; that is, if it's a
multixact, it does not contain an update among its members. It's set when
the XMAX is a plain Xid that locked the tuple, as well.
- HEAP_XMAX_KEYSHR_LOCK
- HEAP_XMAX_EXCL_LOCK
These bits indicate the strength of the lock acquired; they are useful when
the XMAX is not a MultiXactId. If it's a multi, the info is to be found in
the member flags. If HEAP_XMAX_IS_MULTI is not set and HEAP_XMAX_LOCK_ONLY
is set, then one of these *must* be set as well.
Note there is no infomask bit for a SELECT FOR SHARE lock. Also there is no
separate bit for a SELECT FOR KEY UPDATE lock; this is implemented by the
HEAP_KEYS_UPDATED bit.
- HEAP_KEYS_UPDATED
This bit lives in t_infomask2. If set, indicates that the XMAX updated
this tuple and changed the key values, or it deleted the tuple.
It's set regardless of whether the XMAX is a TransactionId or a MultiXactId.
We currently never set the HEAP_XMAX_COMMITTED when the HEAP_XMAX_IS_MULTI bit
is set.

File diff suppressed because it is too large Load Diff

View File

@ -463,7 +463,7 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
* that the page is reconsidered for pruning in future.
*/
heap_prune_record_prunable(prstate,
HeapTupleHeaderGetXmax(htup));
HeapTupleHeaderGetUpdateXid(htup));
break;
case HEAPTUPLE_DELETE_IN_PROGRESS:
@ -473,7 +473,7 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
* that the page is reconsidered for pruning in future.
*/
heap_prune_record_prunable(prstate,
HeapTupleHeaderGetXmax(htup));
HeapTupleHeaderGetUpdateXid(htup));
break;
case HEAPTUPLE_LIVE:
@ -521,7 +521,7 @@ heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum,
Assert(ItemPointerGetBlockNumber(&htup->t_ctid) ==
BufferGetBlockNumber(buffer));
offnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
priorXmax = HeapTupleHeaderGetXmax(htup);
priorXmax = HeapTupleHeaderGetUpdateXid(htup);
}
/*
@ -746,7 +746,7 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
/* Set up to scan the HOT-chain */
nextoffnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
priorXmax = HeapTupleHeaderGetXmax(htup);
priorXmax = HeapTupleHeaderGetUpdateXid(htup);
}
else
{
@ -787,7 +787,7 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets)
break;
nextoffnum = ItemPointerGetOffsetNumber(&htup->t_ctid);
priorXmax = HeapTupleHeaderGetXmax(htup);
priorXmax = HeapTupleHeaderGetUpdateXid(htup);
}
}
}

View File

@ -111,6 +111,7 @@
#include "storage/smgr.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/tqual.h"
/*
@ -128,6 +129,8 @@ typedef struct RewriteStateData
* determine tuple visibility */
TransactionId rs_freeze_xid;/* Xid that will be used as freeze cutoff
* point */
MultiXactId rs_freeze_multi;/* MultiXactId that will be used as freeze
* cutoff point for multixacts */
MemoryContext rs_cxt; /* for hash tables and entries and tuples in
* them */
HTAB *rs_unresolved_tups; /* unmatched A tuples */
@ -177,6 +180,7 @@ static void raw_heap_insert(RewriteState state, HeapTuple tup);
* new_heap new, locked heap relation to insert tuples to
* oldest_xmin xid used by the caller to determine which tuples are dead
* freeze_xid xid before which tuples will be frozen
* freeze_multi multixact before which multis will be frozen
* use_wal should the inserts to the new heap be WAL-logged?
*
* Returns an opaque RewriteState, allocated in current memory context,
@ -184,7 +188,8 @@ static void raw_heap_insert(RewriteState state, HeapTuple tup);
*/
RewriteState
begin_heap_rewrite(Relation new_heap, TransactionId oldest_xmin,
TransactionId freeze_xid, bool use_wal)
TransactionId freeze_xid, MultiXactId freeze_multi,
bool use_wal)
{
RewriteState state;
MemoryContext rw_cxt;
@ -213,6 +218,7 @@ begin_heap_rewrite(Relation new_heap, TransactionId oldest_xmin,
state->rs_use_wal = use_wal;
state->rs_oldest_xmin = oldest_xmin;
state->rs_freeze_xid = freeze_xid;
state->rs_freeze_multi = freeze_multi;
state->rs_cxt = rw_cxt;
/* Initialize hash tables used to track update chains */
@ -337,7 +343,8 @@ rewrite_heap_tuple(RewriteState state,
* While we have our hands on the tuple, we may as well freeze any
* very-old xmin or xmax, so that future VACUUM effort can be saved.
*/
heap_freeze_tuple(new_tuple->t_data, state->rs_freeze_xid);
heap_freeze_tuple(new_tuple->t_data, state->rs_freeze_xid,
state->rs_freeze_multi);
/*
* Invalid ctid means that ctid should point to the tuple itself. We'll
@ -348,15 +355,15 @@ rewrite_heap_tuple(RewriteState state,
/*
* If the tuple has been updated, check the old-to-new mapping hash table.
*/
if (!(old_tuple->t_data->t_infomask & (HEAP_XMAX_INVALID |
HEAP_IS_LOCKED)) &&
if (!((old_tuple->t_data->t_infomask & HEAP_XMAX_INVALID) ||
HeapTupleHeaderIsOnlyLocked(old_tuple->t_data)) &&
!(ItemPointerEquals(&(old_tuple->t_self),
&(old_tuple->t_data->t_ctid))))
{
OldToNewMapping mapping;
memset(&hashkey, 0, sizeof(hashkey));
hashkey.xmin = HeapTupleHeaderGetXmax(old_tuple->t_data);
hashkey.xmin = HeapTupleHeaderGetUpdateXid(old_tuple->t_data);
hashkey.tid = old_tuple->t_data->t_ctid;
mapping = (OldToNewMapping)

View File

@ -25,6 +25,21 @@ out_target(StringInfo buf, xl_heaptid *target)
ItemPointerGetOffsetNumber(&(target->tid)));
}
static void
out_infobits(StringInfo buf, uint8 infobits)
{
if (infobits & XLHL_XMAX_IS_MULTI)
appendStringInfo(buf, "IS_MULTI ");
if (infobits & XLHL_XMAX_LOCK_ONLY)
appendStringInfo(buf, "LOCK_ONLY ");
if (infobits & XLHL_XMAX_EXCL_LOCK)
appendStringInfo(buf, "EXCL_LOCK ");
if (infobits & XLHL_XMAX_KEYSHR_LOCK)
appendStringInfo(buf, "KEYSHR_LOCK ");
if (infobits & XLHL_KEYS_UPDATED)
appendStringInfo(buf, "KEYS_UPDATED ");
}
void
heap_desc(StringInfo buf, uint8 xl_info, char *rec)
{
@ -47,6 +62,8 @@ heap_desc(StringInfo buf, uint8 xl_info, char *rec)
appendStringInfo(buf, "delete: ");
out_target(buf, &(xlrec->target));
appendStringInfoChar(buf, ' ');
out_infobits(buf, xlrec->infobits_set);
}
else if (info == XLOG_HEAP_UPDATE)
{
@ -57,9 +74,12 @@ heap_desc(StringInfo buf, uint8 xl_info, char *rec)
else
appendStringInfo(buf, "update: ");
out_target(buf, &(xlrec->target));
appendStringInfo(buf, "; new %u/%u",
appendStringInfo(buf, " xmax %u ", xlrec->old_xmax);
out_infobits(buf, xlrec->old_infobits_set);
appendStringInfo(buf, "; new tid %u/%u xmax %u",
ItemPointerGetBlockNumber(&(xlrec->newtid)),
ItemPointerGetOffsetNumber(&(xlrec->newtid)));
ItemPointerGetOffsetNumber(&(xlrec->newtid)),
xlrec->new_xmax);
}
else if (info == XLOG_HEAP_HOT_UPDATE)
{
@ -70,9 +90,12 @@ heap_desc(StringInfo buf, uint8 xl_info, char *rec)
else
appendStringInfo(buf, "hot_update: ");
out_target(buf, &(xlrec->target));
appendStringInfo(buf, "; new %u/%u",
appendStringInfo(buf, " xmax %u ", xlrec->old_xmax);
out_infobits(buf, xlrec->old_infobits_set);
appendStringInfo(buf, "; new tid %u/%u xmax %u",
ItemPointerGetBlockNumber(&(xlrec->newtid)),
ItemPointerGetOffsetNumber(&(xlrec->newtid)));
ItemPointerGetOffsetNumber(&(xlrec->newtid)),
xlrec->new_xmax);
}
else if (info == XLOG_HEAP_NEWPAGE)
{
@ -87,16 +110,10 @@ heap_desc(StringInfo buf, uint8 xl_info, char *rec)
{
xl_heap_lock *xlrec = (xl_heap_lock *) rec;
if (xlrec->shared_lock)
appendStringInfo(buf, "shared_lock: ");
else
appendStringInfo(buf, "exclusive_lock: ");
if (xlrec->xid_is_mxact)
appendStringInfo(buf, "mxid ");
else
appendStringInfo(buf, "xid ");
appendStringInfo(buf, "%u ", xlrec->locking_xid);
appendStringInfo(buf, "lock %u: ", xlrec->locking_xid);
out_target(buf, &(xlrec->target));
appendStringInfoChar(buf, ' ');
out_infobits(buf, xlrec->infobits_set);
}
else if (info == XLOG_HEAP_INPLACE)
{
@ -108,7 +125,6 @@ heap_desc(StringInfo buf, uint8 xl_info, char *rec)
else
appendStringInfo(buf, "UNKNOWN");
}
void
heap2_desc(StringInfo buf, uint8 xl_info, char *rec)
{
@ -119,10 +135,10 @@ heap2_desc(StringInfo buf, uint8 xl_info, char *rec)
{
xl_heap_freeze *xlrec = (xl_heap_freeze *) rec;
appendStringInfo(buf, "freeze: rel %u/%u/%u; blk %u; cutoff %u",
appendStringInfo(buf, "freeze: rel %u/%u/%u; blk %u; cutoff xid %u multi %u",
xlrec->node.spcNode, xlrec->node.dbNode,
xlrec->node.relNode, xlrec->block,
xlrec->cutoff_xid);
xlrec->cutoff_xid, xlrec->cutoff_multi);
}
else if (info == XLOG_HEAP2_CLEAN)
{
@ -160,6 +176,14 @@ heap2_desc(StringInfo buf, uint8 xl_info, char *rec)
xlrec->node.spcNode, xlrec->node.dbNode, xlrec->node.relNode,
xlrec->blkno, xlrec->ntuples);
}
else if (info == XLOG_HEAP2_LOCK_UPDATED)
{
xl_heap_lock_updated *xlrec = (xl_heap_lock_updated *) rec;
appendStringInfo(buf, "lock updated: xmax %u msk %04x; ", xlrec->xmax,
xlrec->infobits_set);
out_target(buf, &(xlrec->target));
}
else
appendStringInfo(buf, "UNKNOWN");
}

View File

@ -16,6 +16,35 @@
#include "access/multixact.h"
static void
out_member(StringInfo buf, MultiXactMember *member)
{
appendStringInfo(buf, "%u ", member->xid);
switch (member->status)
{
case MultiXactStatusForKeyShare:
appendStringInfoString(buf, "(keysh) ");
break;
case MultiXactStatusForShare:
appendStringInfoString(buf, "(sh) ");
break;
case MultiXactStatusForNoKeyUpdate:
appendStringInfoString(buf, "(fornokeyupd) ");
break;
case MultiXactStatusForUpdate:
appendStringInfoString(buf, "(forupd) ");
break;
case MultiXactStatusNoKeyUpdate:
appendStringInfoString(buf, "(nokeyupd) ");
break;
case MultiXactStatusUpdate:
appendStringInfoString(buf, "(upd) ");
break;
default:
appendStringInfoString(buf, "(unk) ");
break;
}
}
void
multixact_desc(StringInfo buf, uint8 xl_info, char *rec)
@ -41,10 +70,10 @@ multixact_desc(StringInfo buf, uint8 xl_info, char *rec)
xl_multixact_create *xlrec = (xl_multixact_create *) rec;
int i;
appendStringInfo(buf, "create multixact %u offset %u:",
xlrec->mid, xlrec->moff);
for (i = 0; i < xlrec->nxids; i++)
appendStringInfo(buf, " %u", xlrec->xids[i]);
appendStringInfo(buf, "create mxid %u offset %u nmembers %d: ", xlrec->mid,
xlrec->moff, xlrec->nmembers);
for (i = 0; i < xlrec->nmembers; i++)
out_member(buf, &xlrec->members[i]);
}
else
appendStringInfo(buf, "UNKNOWN");

View File

@ -41,7 +41,8 @@ xlog_desc(StringInfo buf, uint8 xl_info, char *rec)
appendStringInfo(buf, "checkpoint: redo %X/%X; "
"tli %u; fpw %s; xid %u/%u; oid %u; multi %u; offset %u; "
"oldest xid %u in DB %u; oldest running xid %u; %s",
"oldest xid %u in DB %u; oldest multi %u in DB %u; "
"oldest running xid %u; %s",
(uint32) (checkpoint->redo >> 32), (uint32) checkpoint->redo,
checkpoint->ThisTimeLineID,
checkpoint->fullPageWrites ? "true" : "false",
@ -51,6 +52,8 @@ xlog_desc(StringInfo buf, uint8 xl_info, char *rec)
checkpoint->nextMultiOffset,
checkpoint->oldestXid,
checkpoint->oldestXidDB,
checkpoint->oldestMulti,
checkpoint->oldestMultiDB,
checkpoint->oldestActiveXid,
(info == XLOG_CHECKPOINT_SHUTDOWN) ? "shutdown" : "online");
}

View File

@ -791,10 +791,10 @@ parent transaction to complete.
Not all transactional behaviour is emulated, for example we do not insert
a transaction entry into the lock table, nor do we maintain the transaction
stack in memory. Clog entries are made normally. Multitrans is not maintained
stack in memory. Clog entries are made normally. Multixact is not maintained
because its purpose is to record tuple level locks that an application has
requested to prevent write locks. Since write locks cannot be obtained at all,
there is never any conflict and so there is no reason to update multitrans.
requested to prevent other tuple locks. Since tuple locks cannot be obtained at
all, there is never any conflict and so there is no reason to update multixact.
Subtrans is maintained during recovery but the details of the transaction
tree are ignored and all subtransactions reference the top-level TransactionId
directly. Since commit is atomic this provides correct lock wait behaviour

File diff suppressed because it is too large Load Diff

View File

@ -75,6 +75,8 @@ GetNewTransactionId(bool isSubXact)
* If we're past xidStopLimit, refuse to execute transactions, unless
* we are running in a standalone backend (which gives an escape hatch
* to the DBA who somehow got past the earlier defenses).
*
* Note that this coding also appears in GetNewMultiXactId.
*----------
*/
if (TransactionIdFollowsOrEquals(xid, ShmemVariableCache->xidVacLimit))

View File

@ -3899,6 +3899,8 @@ BootStrapXLOG(void)
checkPoint.nextMultiOffset = 0;
checkPoint.oldestXid = FirstNormalTransactionId;
checkPoint.oldestXidDB = TemplateDbOid;
checkPoint.oldestMulti = FirstMultiXactId;
checkPoint.oldestMultiDB = TemplateDbOid;
checkPoint.time = (pg_time_t) time(NULL);
checkPoint.oldestActiveXid = InvalidTransactionId;
@ -3907,6 +3909,7 @@ BootStrapXLOG(void)
ShmemVariableCache->oidCount = 0;
MultiXactSetNextMXact(checkPoint.nextMulti, checkPoint.nextMultiOffset);
SetTransactionIdLimit(checkPoint.oldestXid, checkPoint.oldestXidDB);
SetMultiXactIdLimit(checkPoint.oldestMulti, checkPoint.oldestMultiDB);
/* Set up the XLOG page header */
page->xlp_magic = XLOG_PAGE_MAGIC;
@ -4979,6 +4982,9 @@ StartupXLOG(void)
ereport(DEBUG1,
(errmsg("oldest unfrozen transaction ID: %u, in database %u",
checkPoint.oldestXid, checkPoint.oldestXidDB)));
ereport(DEBUG1,
(errmsg("oldest MultiXactId: %u, in database %u",
checkPoint.oldestMulti, checkPoint.oldestMultiDB)));
if (!TransactionIdIsNormal(checkPoint.nextXid))
ereport(PANIC,
(errmsg("invalid next transaction ID")));
@ -4989,6 +4995,7 @@ StartupXLOG(void)
ShmemVariableCache->oidCount = 0;
MultiXactSetNextMXact(checkPoint.nextMulti, checkPoint.nextMultiOffset);
SetTransactionIdLimit(checkPoint.oldestXid, checkPoint.oldestXidDB);
SetMultiXactIdLimit(checkPoint.oldestMulti, checkPoint.oldestMultiDB);
XLogCtl->ckptXidEpoch = checkPoint.nextXidEpoch;
XLogCtl->ckptXid = checkPoint.nextXid;
@ -6724,7 +6731,9 @@ CreateCheckPoint(int flags)
MultiXactGetCheckptMulti(shutdown,
&checkPoint.nextMulti,
&checkPoint.nextMultiOffset);
&checkPoint.nextMultiOffset,
&checkPoint.oldestMulti,
&checkPoint.oldestMultiDB);
/*
* Having constructed the checkpoint record, ensure all shmem disk buffers
@ -7479,6 +7488,7 @@ xlog_redo(XLogRecPtr lsn, XLogRecord *record)
MultiXactSetNextMXact(checkPoint.nextMulti,
checkPoint.nextMultiOffset);
SetTransactionIdLimit(checkPoint.oldestXid, checkPoint.oldestXidDB);
SetMultiXactIdLimit(checkPoint.oldestMulti, checkPoint.oldestMultiDB);
/*
* If we see a shutdown checkpoint while waiting for an end-of-backup
@ -7577,6 +7587,8 @@ xlog_redo(XLogRecPtr lsn, XLogRecord *record)
checkPoint.oldestXid))
SetTransactionIdLimit(checkPoint.oldestXid,
checkPoint.oldestXidDB);
MultiXactAdvanceOldest(checkPoint.oldestMulti,
checkPoint.oldestMultiDB);
/* ControlFile->checkPointCopy always tracks the latest ckpt XID */
ControlFile->checkPointCopy.nextXidEpoch = checkPoint.nextXidEpoch;

View File

@ -30,6 +30,7 @@
#include "postgres.h"
#include "access/htup_details.h"
#include "access/multixact.h"
#include "access/sysattr.h"
#include "access/transam.h"
#include "access/xact.h"
@ -779,6 +780,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
if (relacl != (Datum) 0)
values[Anum_pg_class_relacl - 1] = relacl;
else
@ -854,7 +856,7 @@ AddNewRelationTuple(Relation pg_class_desc,
break;
}
/* Initialize relfrozenxid */
/* Initialize relfrozenxid and relminmxid */
if (relkind == RELKIND_RELATION ||
relkind == RELKIND_TOASTVALUE)
{
@ -864,6 +866,15 @@ AddNewRelationTuple(Relation pg_class_desc,
* that will do.
*/
new_rel_reltup->relfrozenxid = RecentXmin;
/*
* Similarly, initialize the minimum Multixact to the first value that
* could possibly be stored in tuples in the table. Running
* transactions could reuse values from their local cache, so we are
* careful to consider all currently running multis.
*
* XXX this could be refined further, but is it worth the hassle?
*/
new_rel_reltup->relminmxid = GetOldestMultiXactId();
}
else
{
@ -874,6 +885,7 @@ AddNewRelationTuple(Relation pg_class_desc,
* commands/sequence.c.)
*/
new_rel_reltup->relfrozenxid = InvalidTransactionId;
new_rel_reltup->relfrozenxid = InvalidMultiXactId;
}
new_rel_reltup->relowner = relowner;

View File

@ -23,6 +23,7 @@
#include <unistd.h>
#include "access/multixact.h"
#include "access/relscan.h"
#include "access/sysattr.h"
#include "access/transam.h"
@ -2353,8 +2354,7 @@ IndexBuildHeapScan(Relation heapRelation,
* As with INSERT_IN_PROGRESS case, this is unexpected
* unless it's our own deletion or a system catalog.
*/
Assert(!(heapTuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI));
xwait = HeapTupleHeaderGetXmax(heapTuple->t_data);
xwait = HeapTupleHeaderGetUpdateXid(heapTuple->t_data);
if (!TransactionIdIsCurrentTransactionId(xwait))
{
if (!is_system_catalog)
@ -3184,7 +3184,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks)
}
/* We'll build a new physical relation for the index */
RelationSetNewRelfilenode(iRel, InvalidTransactionId);
RelationSetNewRelfilenode(iRel, InvalidTransactionId,
InvalidMultiXactId);
/* Initialize the index and rebuild */
/* Note: we do not need to re-establish pkey setting */
@ -3364,7 +3365,7 @@ reindex_relation(Oid relid, int flags)
/* Ensure rd_indexattr is valid; see comments for RelationSetIndexList */
if (is_pg_class)
(void) RelationGetIndexAttrBitmap(rel);
(void) RelationGetIndexAttrBitmap(rel, false);
PG_TRY();
{

View File

@ -16,6 +16,7 @@
#include <math.h>
#include "access/multixact.h"
#include "access/transam.h"
#include "access/tupconvert.h"
#include "access/tuptoaster.h"
@ -580,7 +581,8 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
totalrows,
visibilitymap_count(onerel),
hasindex,
InvalidTransactionId);
InvalidTransactionId,
InvalidMultiXactId);
/*
* Same for indexes. Vacuum always scans all indexes, so if we're part of
@ -600,7 +602,8 @@ do_analyze_rel(Relation onerel, VacuumStmt *vacstmt,
totalindexrows,
0,
false,
InvalidTransactionId);
InvalidTransactionId,
InvalidMultiXactId);
}
}
@ -1193,7 +1196,7 @@ acquire_sample_rows(Relation onerel, int elevel,
* right. (Note: this works out properly when the row was
* both inserted and deleted in our xact.)
*/
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(targtuple.t_data)))
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(targtuple.t_data)))
deadrows += 1;
else
liverows += 1;

View File

@ -17,6 +17,7 @@
*/
#include "postgres.h"
#include "access/multixact.h"
#include "access/relscan.h"
#include "access/rewriteheap.h"
#include "access/transam.h"
@ -65,7 +66,8 @@ static void rebuild_relation(Relation OldHeap, Oid indexOid,
int freeze_min_age, int freeze_table_age, bool verbose);
static void copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
int freeze_min_age, int freeze_table_age, bool verbose,
bool *pSwapToastByContent, TransactionId *pFreezeXid);
bool *pSwapToastByContent, TransactionId *pFreezeXid,
MultiXactId *pFreezeMulti);
static List *get_tables_to_cluster(MemoryContext cluster_context);
static void reform_and_rewrite_tuple(HeapTuple tuple,
TupleDesc oldTupDesc, TupleDesc newTupDesc,
@ -549,6 +551,7 @@ rebuild_relation(Relation OldHeap, Oid indexOid,
bool is_system_catalog;
bool swap_toast_by_content;
TransactionId frozenXid;
MultiXactId frozenMulti;
/* Mark the correct index as clustered */
if (OidIsValid(indexOid))
@ -566,14 +569,14 @@ rebuild_relation(Relation OldHeap, Oid indexOid,
/* Copy the heap data into the new table in the desired order */
copy_heap_data(OIDNewHeap, tableOid, indexOid,
freeze_min_age, freeze_table_age, verbose,
&swap_toast_by_content, &frozenXid);
&swap_toast_by_content, &frozenXid, &frozenMulti);
/*
* Swap the physical files of the target and transient tables, then
* rebuild the target's indexes and throw away the transient table.
*/
finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog,
swap_toast_by_content, false, frozenXid);
swap_toast_by_content, false, frozenXid, frozenMulti);
}
@ -706,7 +709,8 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace)
static void
copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
int freeze_min_age, int freeze_table_age, bool verbose,
bool *pSwapToastByContent, TransactionId *pFreezeXid)
bool *pSwapToastByContent, TransactionId *pFreezeXid,
MultiXactId *pFreezeMulti)
{
Relation NewHeap,
OldHeap,
@ -722,6 +726,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool is_system_catalog;
TransactionId OldestXmin;
TransactionId FreezeXid;
MultiXactId MultiXactFrzLimit;
RewriteState rwstate;
bool use_sort;
Tuplesortstate *tuplesort;
@ -822,7 +827,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
*/
vacuum_set_xid_limits(freeze_min_age, freeze_table_age,
OldHeap->rd_rel->relisshared,
&OldestXmin, &FreezeXid, NULL);
&OldestXmin, &FreezeXid, NULL, &MultiXactFrzLimit);
/*
* FreezeXid will become the table's new relfrozenxid, and that mustn't go
@ -831,14 +836,16 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
if (TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
FreezeXid = OldHeap->rd_rel->relfrozenxid;
/* return selected value to caller */
/* return selected values to caller */
*pFreezeXid = FreezeXid;
*pFreezeMulti = MultiXactFrzLimit;
/* Remember if it's a system catalog */
is_system_catalog = IsSystemRelation(OldHeap);
/* Initialize the rewrite operation */
rwstate = begin_heap_rewrite(NewHeap, OldestXmin, FreezeXid, use_wal);
rwstate = begin_heap_rewrite(NewHeap, OldestXmin, FreezeXid,
MultiXactFrzLimit, use_wal);
/*
* Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@ -966,9 +973,8 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
/*
* Similar situation to INSERT_IN_PROGRESS case.
*/
Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI));
if (!is_system_catalog &&
!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple->t_data)))
!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple->t_data)))
elog(WARNING, "concurrent delete in progress within table \"%s\"",
RelationGetRelationName(OldHeap));
/* treat as recently dead */
@ -1097,6 +1103,7 @@ static void
swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
bool swap_toast_by_content,
TransactionId frozenXid,
MultiXactId frozenMulti,
Oid *mapped_tables)
{
Relation relRelation;
@ -1204,11 +1211,13 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
* and then fail to commit the pg_class update.
*/
/* set rel1's frozen Xid */
/* set rel1's frozen Xid and minimum MultiXid */
if (relform1->relkind != RELKIND_INDEX)
{
Assert(TransactionIdIsNormal(frozenXid));
relform1->relfrozenxid = frozenXid;
Assert(MultiXactIdIsValid(frozenMulti));
relform1->relminmxid = frozenMulti;
}
/* swap size statistics too, since new rel has freshly-updated stats */
@ -1272,6 +1281,7 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
target_is_pg_class,
swap_toast_by_content,
frozenXid,
frozenMulti,
mapped_tables);
}
else
@ -1361,6 +1371,7 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
target_is_pg_class,
swap_toast_by_content,
InvalidTransactionId,
InvalidMultiXactId,
mapped_tables);
/* Clean up. */
@ -1398,7 +1409,8 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
bool is_system_catalog,
bool swap_toast_by_content,
bool check_constraints,
TransactionId frozenXid)
TransactionId frozenXid,
MultiXactId frozenMulti)
{
ObjectAddress object;
Oid mapped_tables[4];
@ -1414,7 +1426,8 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
*/
swap_relation_files(OIDOldHeap, OIDNewHeap,
(OIDOldHeap == RelationRelationId),
swap_toast_by_content, frozenXid, mapped_tables);
swap_toast_by_content, frozenXid, frozenMulti,
mapped_tables);
/*
* If it's a system catalog, queue an sinval message to flush all

View File

@ -80,6 +80,7 @@ static bool get_db_info(const char *name, LOCKMODE lockmode,
Oid *dbIdP, Oid *ownerIdP,
int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
Oid *dbLastSysOidP, TransactionId *dbFrozenXidP,
MultiXactId *dbMinMultiP,
Oid *dbTablespace, char **dbCollate, char **dbCtype);
static bool have_createdb_privilege(void);
static void remove_dbtablespaces(Oid db_id);
@ -104,6 +105,7 @@ createdb(const CreatedbStmt *stmt)
bool src_allowconn;
Oid src_lastsysoid;
TransactionId src_frozenxid;
MultiXactId src_minmxid;
Oid src_deftablespace;
volatile Oid dst_deftablespace;
Relation pg_database_rel;
@ -288,7 +290,7 @@ createdb(const CreatedbStmt *stmt)
if (!get_db_info(dbtemplate, ShareLock,
&src_dboid, &src_owner, &src_encoding,
&src_istemplate, &src_allowconn, &src_lastsysoid,
&src_frozenxid, &src_deftablespace,
&src_frozenxid, &src_minmxid, &src_deftablespace,
&src_collate, &src_ctype))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_DATABASE),
@ -491,6 +493,7 @@ createdb(const CreatedbStmt *stmt)
new_record[Anum_pg_database_datconnlimit - 1] = Int32GetDatum(dbconnlimit);
new_record[Anum_pg_database_datlastsysoid - 1] = ObjectIdGetDatum(src_lastsysoid);
new_record[Anum_pg_database_datfrozenxid - 1] = TransactionIdGetDatum(src_frozenxid);
new_record[Anum_pg_database_datminmxid - 1] = TransactionIdGetDatum(src_minmxid);
new_record[Anum_pg_database_dattablespace - 1] = ObjectIdGetDatum(dst_deftablespace);
/*
@ -786,7 +789,7 @@ dropdb(const char *dbname, bool missing_ok)
pgdbrel = heap_open(DatabaseRelationId, RowExclusiveLock);
if (!get_db_info(dbname, AccessExclusiveLock, &db_id, NULL, NULL,
&db_istemplate, NULL, NULL, NULL, NULL, NULL, NULL))
&db_istemplate, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
{
if (!missing_ok)
{
@ -945,7 +948,7 @@ RenameDatabase(const char *oldname, const char *newname)
rel = heap_open(DatabaseRelationId, RowExclusiveLock);
if (!get_db_info(oldname, AccessExclusiveLock, &db_id, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL))
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_DATABASE),
errmsg("database \"%s\" does not exist", oldname)));
@ -1046,7 +1049,7 @@ movedb(const char *dbname, const char *tblspcname)
pgdbrel = heap_open(DatabaseRelationId, RowExclusiveLock);
if (!get_db_info(dbname, AccessExclusiveLock, &db_id, NULL, NULL,
NULL, NULL, NULL, NULL, &src_tblspcoid, NULL, NULL))
NULL, NULL, NULL, NULL, NULL, &src_tblspcoid, NULL, NULL))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_DATABASE),
errmsg("database \"%s\" does not exist", dbname)));
@ -1599,6 +1602,7 @@ get_db_info(const char *name, LOCKMODE lockmode,
Oid *dbIdP, Oid *ownerIdP,
int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
Oid *dbLastSysOidP, TransactionId *dbFrozenXidP,
MultiXactId *dbMinMultiP,
Oid *dbTablespace, char **dbCollate, char **dbCtype)
{
bool result = false;
@ -1685,6 +1689,9 @@ get_db_info(const char *name, LOCKMODE lockmode,
/* limit of frozen XIDs */
if (dbFrozenXidP)
*dbFrozenXidP = dbform->datfrozenxid;
/* limit of frozen Multixacts */
if (dbMinMultiP)
*dbMinMultiP = dbform->datminmxid;
/* default tablespace for this database */
if (dbTablespace)
*dbTablespace = dbform->dattablespace;

View File

@ -14,8 +14,9 @@
*/
#include "postgres.h"
#include "access/transam.h"
#include "access/htup_details.h"
#include "access/multixact.h"
#include "access/transam.h"
#include "access/xlogutils.h"
#include "catalog/dependency.h"
#include "catalog/namespace.h"
@ -282,8 +283,10 @@ ResetSequence(Oid seq_relid)
/*
* Create a new storage file for the sequence. We want to keep the
* sequence's relfrozenxid at 0, since it won't contain any unfrozen XIDs.
* Same with relminmxid, since a sequence will never contain multixacts.
*/
RelationSetNewRelfilenode(seq_rel, InvalidTransactionId);
RelationSetNewRelfilenode(seq_rel, InvalidTransactionId,
InvalidMultiXactId);
/*
* Insert the modified tuple into the new storage file.
@ -1110,7 +1113,8 @@ read_seq_tuple(SeqTable elm, Relation rel, Buffer *buf, HeapTuple seqtuple)
* bit update, ie, don't bother to WAL-log it, since we can certainly do
* this again if the update gets lost.
*/
if (HeapTupleHeaderGetXmax(seqtuple->t_data) != InvalidTransactionId)
Assert(!(seqtuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI));
if (HeapTupleHeaderGetRawXmax(seqtuple->t_data) != InvalidTransactionId)
{
HeapTupleHeaderSetXmax(seqtuple->t_data, InvalidTransactionId);
seqtuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED;

View File

@ -15,7 +15,9 @@
#include "postgres.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "access/heapam_xlog.h"
#include "access/multixact.h"
#include "access/reloptions.h"
#include "access/relscan.h"
#include "access/sysattr.h"
@ -1130,6 +1132,7 @@ ExecuteTruncate(TruncateStmt *stmt)
{
Oid heap_relid;
Oid toast_relid;
MultiXactId minmulti;
/*
* This effectively deletes all rows in the table, and may be done
@ -1139,6 +1142,8 @@ ExecuteTruncate(TruncateStmt *stmt)
*/
CheckTableForSerializableConflictIn(rel);
minmulti = GetOldestMultiXactId();
/*
* Need the full transaction-safe pushups.
*
@ -1146,7 +1151,7 @@ ExecuteTruncate(TruncateStmt *stmt)
* as the relfilenode value. The old storage file is scheduled for
* deletion at commit.
*/
RelationSetNewRelfilenode(rel, RecentXmin);
RelationSetNewRelfilenode(rel, RecentXmin, minmulti);
if (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
heap_create_init_fork(rel);
@ -1159,7 +1164,7 @@ ExecuteTruncate(TruncateStmt *stmt)
if (OidIsValid(toast_relid))
{
rel = relation_open(toast_relid, AccessExclusiveLock);
RelationSetNewRelfilenode(rel, RecentXmin);
RelationSetNewRelfilenode(rel, RecentXmin, minmulti);
if (rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
heap_create_init_fork(rel);
heap_close(rel, NoLock);
@ -3516,7 +3521,8 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
* interest in letting this code work on system catalogs.
*/
finish_heap_swap(tab->relid, OIDNewHeap,
false, false, true, RecentXmin);
false, false, true, RecentXmin,
ReadNextMultiXactId());
}
else
{

View File

@ -73,6 +73,7 @@ static HeapTuple GetTupleForTrigger(EState *estate,
EPQState *epqstate,
ResultRelInfo *relinfo,
ItemPointer tid,
LockTupleMode lockmode,
TupleTableSlot **newSlot);
static bool TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
Trigger *trigger, TriggerEvent event,
@ -2147,7 +2148,7 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
int i;
trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
&newSlot);
LockTupleExclusive, &newSlot);
if (trigtuple == NULL)
return false;
@ -2201,7 +2202,8 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
if (trigdesc && trigdesc->trig_delete_after_row)
{
HeapTuple trigtuple = GetTupleForTrigger(estate, NULL, relinfo,
tupleid, NULL);
tupleid, LockTupleExclusive,
NULL);
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE,
true, trigtuple, NULL, NIL, NULL);
@ -2332,10 +2334,24 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
TupleTableSlot *newSlot;
int i;
Bitmapset *modifiedCols;
Bitmapset *keyCols;
LockTupleMode lockmode;
/*
* Compute lock mode to use. If columns that are part of the key have not
* been modified, then we can use a weaker lock, allowing for better
* concurrency.
*/
modifiedCols = GetModifiedColumns(relinfo, estate);
keyCols = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc, true);
if (bms_overlap(keyCols, modifiedCols))
lockmode = LockTupleExclusive;
else
lockmode = LockTupleNoKeyExclusive;
/* get a copy of the on-disk tuple we are planning to update */
trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
&newSlot);
lockmode, &newSlot);
if (trigtuple == NULL)
return NULL; /* cancel the update action */
@ -2357,7 +2373,6 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
newtuple = slottuple;
}
modifiedCols = GetModifiedColumns(relinfo, estate);
LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
@ -2426,7 +2441,8 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
if (trigdesc && trigdesc->trig_update_after_row)
{
HeapTuple trigtuple = GetTupleForTrigger(estate, NULL, relinfo,
tupleid, NULL);
tupleid, LockTupleExclusive,
NULL);
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE,
true, trigtuple, newtuple, recheckIndexes,
@ -2565,6 +2581,7 @@ GetTupleForTrigger(EState *estate,
EPQState *epqstate,
ResultRelInfo *relinfo,
ItemPointer tid,
LockTupleMode lockmode,
TupleTableSlot **newSlot)
{
Relation relation = relinfo->ri_RelationDesc;
@ -2589,8 +2606,8 @@ ltrmark:;
tuple.t_self = *tid;
test = heap_lock_tuple(relation, &tuple,
estate->es_output_cid,
LockTupleExclusive, false /* wait */,
&buffer, &hufd);
lockmode, false /* wait */,
false, &buffer, &hufd);
switch (test)
{
case HeapTupleSelfUpdated:
@ -2630,6 +2647,7 @@ ltrmark:;
epqstate,
relation,
relinfo->ri_RangeTableIndex,
lockmode,
&hufd.ctid,
hufd.xmax);
if (!TupIsNull(epqslot))

View File

@ -26,6 +26,7 @@
#include "access/genam.h"
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/multixact.h"
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/namespace.h"
@ -63,7 +64,7 @@ static BufferAccessStrategy vac_strategy;
/* non-export function prototypes */
static List *get_rel_oids(Oid relid, const RangeVar *vacrel);
static void vac_truncate_clog(TransactionId frozenXID);
static void vac_truncate_clog(TransactionId frozenXID, MultiXactId frozenMulti);
static bool vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast,
bool for_wraparound);
@ -379,7 +380,8 @@ vacuum_set_xid_limits(int freeze_min_age,
bool sharedRel,
TransactionId *oldestXmin,
TransactionId *freezeLimit,
TransactionId *freezeTableLimit)
TransactionId *freezeTableLimit,
MultiXactId *multiXactFrzLimit)
{
int freezemin;
TransactionId limit;
@ -463,8 +465,22 @@ vacuum_set_xid_limits(int freeze_min_age,
*freezeTableLimit = limit;
}
}
if (multiXactFrzLimit != NULL)
{
MultiXactId mxLimit;
/*
* simplistic multixactid freezing: use the same freezing policy as
* for Xids
*/
mxLimit = GetOldestMultiXactId() - freezemin;
if (mxLimit < FirstMultiXactId)
mxLimit = FirstMultiXactId;
*multiXactFrzLimit = mxLimit;
}
}
/*
* vac_estimate_reltuples() -- estimate the new value for pg_class.reltuples
@ -574,7 +590,8 @@ void
vac_update_relstats(Relation relation,
BlockNumber num_pages, double num_tuples,
BlockNumber num_all_visible_pages,
bool hasindex, TransactionId frozenxid)
bool hasindex, TransactionId frozenxid,
MultiXactId minmulti)
{
Oid relid = RelationGetRelid(relation);
Relation rd;
@ -648,6 +665,14 @@ vac_update_relstats(Relation relation,
dirty = true;
}
/* relminmxid must never go backward, either */
if (MultiXactIdIsValid(minmulti) &&
MultiXactIdPrecedes(pgcform->relminmxid, minmulti))
{
pgcform->relminmxid = minmulti;
dirty = true;
}
/* If anything changed, write out the tuple. */
if (dirty)
heap_inplace_update(rd, ctup);
@ -660,8 +685,13 @@ vac_update_relstats(Relation relation,
* vac_update_datfrozenxid() -- update pg_database.datfrozenxid for our DB
*
* Update pg_database's datfrozenxid entry for our database to be the
* minimum of the pg_class.relfrozenxid values. If we are able to
* advance pg_database.datfrozenxid, also try to truncate pg_clog.
* minimum of the pg_class.relfrozenxid values.
*
* Similarly, update our datfrozenmulti to be the minimum of the
* pg_class.relfrozenmulti values.
*
* If we are able to advance either pg_database value, also try to
* truncate pg_clog and pg_multixact.
*
* We violate transaction semantics here by overwriting the database's
* existing pg_database tuple with the new value. This is reasonably
@ -678,16 +708,23 @@ vac_update_datfrozenxid(void)
SysScanDesc scan;
HeapTuple classTup;
TransactionId newFrozenXid;
MultiXactId newFrozenMulti;
bool dirty = false;
/*
* Initialize the "min" calculation with GetOldestXmin, which is a
* reasonable approximation to the minimum relfrozenxid for not-yet-
* committed pg_class entries for new tables; see AddNewRelationTuple().
* Se we cannot produce a wrong minimum by starting with this.
* So we cannot produce a wrong minimum by starting with this.
*/
newFrozenXid = GetOldestXmin(true, true);
/*
* Similarly, initialize the MultiXact "min" with the value that would
* be used on pg_class for new tables. See AddNewRelationTuple().
*/
newFrozenMulti = GetOldestMultiXactId();
/*
* We must seqscan pg_class to find the minimum Xid, because there is no
* index that can help us here.
@ -710,9 +747,13 @@ vac_update_datfrozenxid(void)
continue;
Assert(TransactionIdIsNormal(classForm->relfrozenxid));
Assert(MultiXactIdIsValid(classForm->relminmxid));
if (TransactionIdPrecedes(classForm->relfrozenxid, newFrozenXid))
newFrozenXid = classForm->relfrozenxid;
if (MultiXactIdPrecedes(classForm->relminmxid, newFrozenMulti))
newFrozenMulti = classForm->relminmxid;
}
/* we're done with pg_class */
@ -720,6 +761,7 @@ vac_update_datfrozenxid(void)
heap_close(relation, AccessShareLock);
Assert(TransactionIdIsNormal(newFrozenXid));
Assert(MultiXactIdIsValid(newFrozenMulti));
/* Now fetch the pg_database tuple we need to update. */
relation = heap_open(DatabaseRelationId, RowExclusiveLock);
@ -740,6 +782,13 @@ vac_update_datfrozenxid(void)
dirty = true;
}
/* ditto */
if (MultiXactIdPrecedes(dbform->datminmxid, newFrozenMulti))
{
dbform->datminmxid = newFrozenMulti;
dirty = true;
}
if (dirty)
heap_inplace_update(relation, tuple);
@ -752,7 +801,7 @@ vac_update_datfrozenxid(void)
* this action will update that too.
*/
if (dirty || ForceTransactionIdLimitUpdate())
vac_truncate_clog(newFrozenXid);
vac_truncate_clog(newFrozenXid, newFrozenMulti);
}
@ -771,17 +820,19 @@ vac_update_datfrozenxid(void)
* info is stale.
*/
static void
vac_truncate_clog(TransactionId frozenXID)
vac_truncate_clog(TransactionId frozenXID, MultiXactId frozenMulti)
{
TransactionId myXID = GetCurrentTransactionId();
Relation relation;
HeapScanDesc scan;
HeapTuple tuple;
Oid oldest_datoid;
Oid oldestxid_datoid;
Oid oldestmulti_datoid;
bool frozenAlreadyWrapped = false;
/* init oldest_datoid to sync with my frozenXID */
oldest_datoid = MyDatabaseId;
/* init oldest datoids to sync with my frozen values */
oldestxid_datoid = MyDatabaseId;
oldestmulti_datoid = MyDatabaseId;
/*
* Scan pg_database to compute the minimum datfrozenxid
@ -804,13 +855,20 @@ vac_truncate_clog(TransactionId frozenXID)
Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple);
Assert(TransactionIdIsNormal(dbform->datfrozenxid));
Assert(MultiXactIdIsValid(dbform->datminmxid));
if (TransactionIdPrecedes(myXID, dbform->datfrozenxid))
frozenAlreadyWrapped = true;
else if (TransactionIdPrecedes(dbform->datfrozenxid, frozenXID))
{
frozenXID = dbform->datfrozenxid;
oldest_datoid = HeapTupleGetOid(tuple);
oldestxid_datoid = HeapTupleGetOid(tuple);
}
if (MultiXactIdPrecedes(dbform->datminmxid, frozenMulti))
{
frozenMulti = dbform->datminmxid;
oldestmulti_datoid = HeapTupleGetOid(tuple);
}
}
@ -832,14 +890,18 @@ vac_truncate_clog(TransactionId frozenXID)
return;
}
/* Truncate CLOG to the oldest frozenxid */
/* Truncate CLOG and Multi to the oldest computed value */
TruncateCLOG(frozenXID);
TruncateMultiXact(frozenMulti);
/*
* Update the wrap limit for GetNewTransactionId. Note: this function
* will also signal the postmaster for an(other) autovac cycle if needed.
* Update the wrap limit for GetNewTransactionId and creation of new
* MultiXactIds. Note: these functions will also signal the postmaster for
* an(other) autovac cycle if needed. XXX should we avoid possibly
* signalling twice?
*/
SetTransactionIdLimit(frozenXID, oldest_datoid);
SetTransactionIdLimit(frozenXID, oldestxid_datoid);
MultiXactAdvanceOldest(frozenMulti, oldestmulti_datoid);
}

View File

@ -41,6 +41,7 @@
#include "access/heapam.h"
#include "access/heapam_xlog.h"
#include "access/htup_details.h"
#include "access/multixact.h"
#include "access/transam.h"
#include "access/visibilitymap.h"
#include "catalog/storage.h"
@ -124,6 +125,7 @@ static int elevel = -1;
static TransactionId OldestXmin;
static TransactionId FreezeLimit;
static MultiXactId MultiXactFrzLimit;
static BufferAccessStrategy vac_strategy;
@ -180,6 +182,7 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt,
double new_rel_tuples;
BlockNumber new_rel_allvisible;
TransactionId new_frozen_xid;
MultiXactId new_min_multi;
/* measure elapsed time iff autovacuum logging requires it */
if (IsAutoVacuumWorkerProcess() && Log_autovacuum_min_duration >= 0)
@ -197,7 +200,8 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt,
vacuum_set_xid_limits(vacstmt->freeze_min_age, vacstmt->freeze_table_age,
onerel->rd_rel->relisshared,
&OldestXmin, &FreezeLimit, &freezeTableLimit);
&OldestXmin, &FreezeLimit, &freezeTableLimit,
&MultiXactFrzLimit);
scan_all = TransactionIdPrecedesOrEquals(onerel->rd_rel->relfrozenxid,
freezeTableLimit);
@ -267,12 +271,17 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt,
if (vacrelstats->scanned_pages < vacrelstats->rel_pages)
new_frozen_xid = InvalidTransactionId;
new_min_multi = MultiXactFrzLimit;
if (vacrelstats->scanned_pages < vacrelstats->rel_pages)
new_min_multi = InvalidMultiXactId;
vac_update_relstats(onerel,
new_rel_pages,
new_rel_tuples,
new_rel_allvisible,
vacrelstats->hasindex,
new_frozen_xid);
new_frozen_xid,
new_min_multi);
/*
* Report results to the stats collector, too. An early terminated
@ -839,7 +848,8 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
* Each non-removable tuple must be checked to see if it needs
* freezing. Note we already have exclusive buffer lock.
*/
if (heap_freeze_tuple(tuple.t_data, FreezeLimit))
if (heap_freeze_tuple(tuple.t_data, FreezeLimit,
MultiXactFrzLimit))
frozen[nfrozen++] = offnum;
}
} /* scan along page */
@ -857,7 +867,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
XLogRecPtr recptr;
recptr = log_heap_freeze(onerel, buf, FreezeLimit,
frozen, nfrozen);
MultiXactFrzLimit, frozen, nfrozen);
PageSetLSN(page, recptr);
PageSetTLI(page, ThisTimeLineID);
}
@ -1176,7 +1186,8 @@ lazy_check_needs_freeze(Buffer buf)
tupleheader = (HeapTupleHeader) PageGetItem(page, itemid);
if (heap_tuple_needs_freeze(tupleheader, FreezeLimit, buf))
if (heap_tuple_needs_freeze(tupleheader, FreezeLimit,
MultiXactFrzLimit, buf))
return true;
} /* scan along page */
@ -1253,7 +1264,8 @@ lazy_cleanup_index(Relation indrel,
stats->num_index_tuples,
0,
false,
InvalidTransactionId);
InvalidTransactionId,
InvalidMultiXactId);
ereport(elevel,
(errmsg("index \"%s\" now contains %.0f row versions in %u pages",

View File

@ -162,7 +162,8 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
case CMD_SELECT:
/*
* SELECT FOR UPDATE/SHARE and modifying CTEs need to mark tuples
* SELECT FOR [KEY] UPDATE/SHARE and modifying CTEs need to mark
* tuples
*/
if (queryDesc->plannedstmt->rowMarks != NIL ||
queryDesc->plannedstmt->hasModifyingCTE)
@ -775,7 +776,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
}
/*
* Similarly, we have to lock relations selected FOR UPDATE/FOR SHARE
* Similarly, we have to lock relations selected FOR [KEY] UPDATE/SHARE
* before we initialize the plan tree, else we'd be risking lock upgrades.
* While we are at it, build the ExecRowMark list.
*/
@ -794,7 +795,9 @@ InitPlan(QueryDesc *queryDesc, int eflags)
switch (rc->markType)
{
case ROW_MARK_EXCLUSIVE:
case ROW_MARK_NOKEYEXCLUSIVE:
case ROW_MARK_SHARE:
case ROW_MARK_KEYSHARE:
relid = getrelid(rc->rti, rangeTable);
relation = heap_open(relid, RowShareLock);
break;
@ -1341,7 +1344,7 @@ ExecEndPlan(PlanState *planstate, EState *estate)
}
/*
* close any relations selected FOR UPDATE/FOR SHARE, again keeping locks
* close any relations selected FOR [KEY] UPDATE/SHARE, again keeping locks
*/
foreach(l, estate->es_rowMarks)
{
@ -1694,6 +1697,7 @@ ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist)
* epqstate - state for EvalPlanQual rechecking
* relation - table containing tuple
* rti - rangetable index of table containing tuple
* lockmode - requested tuple lock mode
* *tid - t_ctid from the outdated tuple (ie, next updated version)
* priorXmax - t_xmax from the outdated tuple
*
@ -1702,10 +1706,13 @@ ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist)
*
* Returns a slot containing the new candidate update/delete tuple, or
* NULL if we determine we shouldn't process the row.
*
* Note: properly, lockmode should be declared as enum LockTupleMode,
* but we use "int" to avoid having to include heapam.h in executor.h.
*/
TupleTableSlot *
EvalPlanQual(EState *estate, EPQState *epqstate,
Relation relation, Index rti,
Relation relation, Index rti, int lockmode,
ItemPointer tid, TransactionId priorXmax)
{
TupleTableSlot *slot;
@ -1716,7 +1723,7 @@ EvalPlanQual(EState *estate, EPQState *epqstate,
/*
* Get and lock the updated version of the row; if fail, return NULL.
*/
copyTuple = EvalPlanQualFetch(estate, relation, LockTupleExclusive,
copyTuple = EvalPlanQualFetch(estate, relation, lockmode,
tid, priorXmax);
if (copyTuple == NULL)
@ -1864,7 +1871,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
test = heap_lock_tuple(relation, &tuple,
estate->es_output_cid,
lockmode, false /* wait */,
&buffer, &hufd);
false, &buffer, &hufd);
/* We now have two pins on the buffer, get rid of one */
ReleaseBuffer(buffer);
@ -1965,7 +1972,7 @@ EvalPlanQualFetch(EState *estate, Relation relation, int lockmode,
/* updated, so look at the updated row */
tuple.t_self = tuple.t_data->t_ctid;
/* updated row should have xmin matching this xmax */
priorXmax = HeapTupleHeaderGetXmax(tuple.t_data);
priorXmax = HeapTupleHeaderGetUpdateXid(tuple.t_data);
ReleaseBuffer(buffer);
/* loop back to fetch next in chain */
}

View File

@ -111,14 +111,29 @@ lnext:
tuple.t_self = *((ItemPointer) DatumGetPointer(datum));
/* okay, try to lock the tuple */
if (erm->markType == ROW_MARK_EXCLUSIVE)
lockmode = LockTupleExclusive;
else
lockmode = LockTupleShared;
switch (erm->markType)
{
case ROW_MARK_EXCLUSIVE:
lockmode = LockTupleExclusive;
break;
case ROW_MARK_NOKEYEXCLUSIVE:
lockmode = LockTupleNoKeyExclusive;
break;
case ROW_MARK_SHARE:
lockmode = LockTupleShare;
break;
case ROW_MARK_KEYSHARE:
lockmode = LockTupleKeyShare;
break;
default:
elog(ERROR, "unsupported rowmark type");
lockmode = LockTupleNoKeyExclusive; /* keep compiler quiet */
break;
}
test = heap_lock_tuple(erm->relation, &tuple,
estate->es_output_cid,
lockmode, erm->noWait,
lockmode, erm->noWait, true,
&buffer, &hufd);
ReleaseBuffer(buffer);
switch (test)

View File

@ -403,6 +403,7 @@ ldelete:;
epqstate,
resultRelationDesc,
resultRelInfo->ri_RangeTableIndex,
LockTupleExclusive,
&hufd.ctid,
hufd.xmax);
if (!TupIsNull(epqslot))
@ -569,6 +570,8 @@ ExecUpdate(ItemPointer tupleid,
}
else
{
LockTupleMode lockmode;
/*
* Check the constraints of the tuple
*
@ -595,7 +598,7 @@ lreplace:;
estate->es_output_cid,
estate->es_crosscheck_snapshot,
true /* wait for commit */,
&hufd);
&hufd, &lockmode);
switch (result)
{
case HeapTupleSelfUpdated:
@ -647,6 +650,7 @@ lreplace:;
epqstate,
resultRelationDesc,
resultRelInfo->ri_RangeTableIndex,
lockmode,
&hufd.ctid,
hufd.xmax);
if (!TupIsNull(epqslot))

View File

@ -2037,7 +2037,7 @@ _copyRowMarkClause(const RowMarkClause *from)
RowMarkClause *newnode = makeNode(RowMarkClause);
COPY_SCALAR_FIELD(rti);
COPY_SCALAR_FIELD(forUpdate);
COPY_SCALAR_FIELD(strength);
COPY_SCALAR_FIELD(noWait);
COPY_SCALAR_FIELD(pushedDown);
@ -2400,7 +2400,7 @@ _copyLockingClause(const LockingClause *from)
LockingClause *newnode = makeNode(LockingClause);
COPY_NODE_FIELD(lockedRels);
COPY_SCALAR_FIELD(forUpdate);
COPY_SCALAR_FIELD(strength);
COPY_SCALAR_FIELD(noWait);
return newnode;

View File

@ -2210,7 +2210,7 @@ static bool
_equalLockingClause(const LockingClause *a, const LockingClause *b)
{
COMPARE_NODE_FIELD(lockedRels);
COMPARE_SCALAR_FIELD(forUpdate);
COMPARE_SCALAR_FIELD(strength);
COMPARE_SCALAR_FIELD(noWait);
return true;
@ -2283,7 +2283,7 @@ static bool
_equalRowMarkClause(const RowMarkClause *a, const RowMarkClause *b)
{
COMPARE_SCALAR_FIELD(rti);
COMPARE_SCALAR_FIELD(forUpdate);
COMPARE_SCALAR_FIELD(strength);
COMPARE_SCALAR_FIELD(noWait);
COMPARE_SCALAR_FIELD(pushedDown);

View File

@ -2111,7 +2111,7 @@ _outLockingClause(StringInfo str, const LockingClause *node)
WRITE_NODE_TYPE("LOCKINGCLAUSE");
WRITE_NODE_FIELD(lockedRels);
WRITE_BOOL_FIELD(forUpdate);
WRITE_ENUM_FIELD(strength, LockClauseStrength);
WRITE_BOOL_FIELD(noWait);
}
@ -2289,7 +2289,7 @@ _outRowMarkClause(StringInfo str, const RowMarkClause *node)
WRITE_NODE_TYPE("ROWMARKCLAUSE");
WRITE_UINT_FIELD(rti);
WRITE_BOOL_FIELD(forUpdate);
WRITE_ENUM_FIELD(strength, LockClauseStrength);
WRITE_BOOL_FIELD(noWait);
WRITE_BOOL_FIELD(pushedDown);
}

View File

@ -301,7 +301,7 @@ _readRowMarkClause(void)
READ_LOCALS(RowMarkClause);
READ_UINT_FIELD(rti);
READ_BOOL_FIELD(forUpdate);
READ_ENUM_FIELD(strength, LockClauseStrength);
READ_BOOL_FIELD(noWait);
READ_BOOL_FIELD(pushedDown);

View File

@ -861,11 +861,11 @@ make_outerjoininfo(PlannerInfo *root,
Assert(jointype != JOIN_RIGHT);
/*
* Presently the executor cannot support FOR UPDATE/SHARE marking of rels
* Presently the executor cannot support FOR [KEY] UPDATE/SHARE marking of rels
* appearing on the nullable side of an outer join. (It's somewhat unclear
* what that would mean, anyway: what should we mark when a result row is
* generated from no element of the nullable relation?) So, complain if
* any nullable rel is FOR UPDATE/SHARE.
* any nullable rel is FOR [KEY] UPDATE/SHARE.
*
* You might be wondering why this test isn't made far upstream in the
* parser. It's because the parser hasn't got enough info --- consider
@ -883,7 +883,7 @@ make_outerjoininfo(PlannerInfo *root,
(jointype == JOIN_FULL && bms_is_member(rc->rti, left_rels)))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to the nullable side of an outer join")));
errmsg("SELECT FOR UPDATE/SHARE/KEY UPDATE/KEY SHARE cannot be applied to the nullable side of an outer join")));
}
sjinfo->syn_lefthand = left_rels;

View File

@ -562,7 +562,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
returningLists = NIL;
/*
* If there was a FOR UPDATE/SHARE clause, the LockRows node will
* 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.
*/
@ -954,7 +954,7 @@ inheritance_planner(PlannerInfo *root)
root->simple_rel_array = save_rel_array;
/*
* If there was a FOR UPDATE/SHARE clause, the LockRows node will have
* 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.
*/
@ -1065,13 +1065,13 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
tlist);
/*
* Can't handle FOR UPDATE/SHARE here (parser should have checked
* Can't handle FOR [KEY] UPDATE/SHARE here (parser should have checked
* already, but let's make sure).
*/
if (parse->rowMarks)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
errmsg("SELECT FOR UPDATE/SHARE/KEY UPDATE/KEY SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
/*
* Calculate pathkeys that represent result ordering requirements
@ -1797,7 +1797,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
}
/*
* If there is a FOR UPDATE/SHARE clause, add the LockRows node. (Note: we
* If there is a FOR [KEY] UPDATE/SHARE clause, add the LockRows node. (Note: we
* intentionally test parse->rowMarks not root->rowMarks here. If there
* are only non-locking rowmarks, they should be handled by the
* ModifyTable node instead.)
@ -1983,7 +1983,7 @@ preprocess_rowmarks(PlannerInfo *root)
if (parse->rowMarks)
{
/*
* We've got trouble if FOR UPDATE/SHARE appears inside grouping,
* We've got trouble if FOR [KEY] UPDATE/SHARE appears inside grouping,
* since grouping renders a reference to individual tuple CTIDs
* invalid. This is also checked at parse time, but that's
* insufficient because of rule substitution, query pullup, etc.
@ -1993,7 +1993,7 @@ preprocess_rowmarks(PlannerInfo *root)
else
{
/*
* We only need rowmarks for UPDATE, DELETE, or FOR UPDATE/SHARE.
* We only need rowmarks for UPDATE, DELETE, or FOR [KEY] UPDATE/SHARE.
*/
if (parse->commandType != CMD_UPDATE &&
parse->commandType != CMD_DELETE)
@ -2003,7 +2003,7 @@ preprocess_rowmarks(PlannerInfo *root)
/*
* We need to have rowmarks for all base relations except the target. We
* make a bitmapset of all base rels and then remove the items we don't
* need or have FOR UPDATE/SHARE marks for.
* need or have FOR [KEY] UPDATE/SHARE marks for.
*/
rels = get_base_rel_indexes((Node *) parse->jointree);
if (parse->resultRelation)
@ -2020,7 +2020,7 @@ preprocess_rowmarks(PlannerInfo *root)
PlanRowMark *newrc;
/*
* Currently, it is syntactically impossible to have FOR UPDATE
* Currently, it is syntactically impossible to have FOR UPDATE et al
* applied to an update/delete target rel. If that ever becomes
* possible, we should drop the target from the PlanRowMark list.
*/
@ -2040,10 +2040,21 @@ preprocess_rowmarks(PlannerInfo *root)
newrc = makeNode(PlanRowMark);
newrc->rti = newrc->prti = rc->rti;
newrc->rowmarkId = ++(root->glob->lastRowMarkId);
if (rc->forUpdate)
newrc->markType = ROW_MARK_EXCLUSIVE;
else
newrc->markType = ROW_MARK_SHARE;
switch (rc->strength)
{
case LCS_FORUPDATE:
newrc->markType = ROW_MARK_EXCLUSIVE;
break;
case LCS_FORNOKEYUPDATE:
newrc->markType = ROW_MARK_NOKEYEXCLUSIVE;
break;
case LCS_FORSHARE:
newrc->markType = ROW_MARK_SHARE;
break;
case LCS_FORKEYSHARE:
newrc->markType = ROW_MARK_KEYSHARE;
break;
}
newrc->noWait = rc->noWait;
newrc->isParent = false;

View File

@ -2139,7 +2139,7 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
/*
* Check for features that are not supported together with FOR UPDATE/SHARE.
* Check for features that are not supported together with FOR [KEY] UPDATE/SHARE.
*
* exported so planner can check again after rewriting, query pullup, etc
*/
@ -2149,35 +2149,35 @@ CheckSelectLocking(Query *qry)
if (qry->setOperations)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
errmsg("SELECT FOR UPDATE/SHARE/FOR KEY UPDATE/FOR KEY SHARE is not allowed with UNION/INTERSECT/EXCEPT")));
if (qry->distinctClause != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE is not allowed with DISTINCT clause")));
errmsg("SELECT FOR UPDATE/SHARE/FOR KEY UPDATE/FOR KEY SHARE is not allowed with DISTINCT clause")));
if (qry->groupClause != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE is not allowed with GROUP BY clause")));
errmsg("SELECT FOR UPDATE/SHARE/FOR KEY UPDATE/FOR KEY SHARE is not allowed with GROUP BY clause")));
if (qry->havingQual != NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE is not allowed with HAVING clause")));
errmsg("SELECT FOR UPDATE/SHARE/FOR KEY UPDATE/FOR KEY SHARE is not allowed with HAVING clause")));
if (qry->hasAggs)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE is not allowed with aggregate functions")));
errmsg("SELECT FOR UPDATE/SHARE/FOR KEY UPDATE/FOR KEY SHARE is not allowed with aggregate functions")));
if (qry->hasWindowFuncs)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE is not allowed with window functions")));
errmsg("SELECT FOR UPDATE/SHARE/FOR KEY UPDATE/FOR KEY SHARE is not allowed with window functions")));
if (expression_returns_set((Node *) qry->targetList))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE is not allowed with set-returning functions in the target list")));
errmsg("SELECT FOR UPDATE/SHARE/FOR KEY UPDATE/FOR KEY SHARE is not allowed with set-returning functions in the target list")));
}
/*
* Transform a FOR UPDATE/SHARE clause
* Transform a FOR [KEY] UPDATE/SHARE clause
*
* This basically involves replacing names by integer relids.
*
@ -2199,7 +2199,7 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
/* make a clause we can pass down to subqueries to select all rels */
allrels = makeNode(LockingClause);
allrels->lockedRels = NIL; /* indicates all rels */
allrels->forUpdate = lc->forUpdate;
allrels->strength = lc->strength;
allrels->noWait = lc->noWait;
if (lockedRels == NIL)
@ -2218,15 +2218,15 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
if (rte->relkind == RELKIND_FOREIGN_TABLE)
break;
applyLockingClause(qry, i,
lc->forUpdate, lc->noWait, pushedDown);
lc->strength, lc->noWait, pushedDown);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
break;
case RTE_SUBQUERY:
applyLockingClause(qry, i,
lc->forUpdate, lc->noWait, pushedDown);
lc->strength, lc->noWait, pushedDown);
/*
* FOR UPDATE/SHARE of subquery is propagated to all of
* FOR [KEY] UPDATE/SHARE of subquery is propagated to all of
* subquery's rels, too. We could do this later (based on
* the marking of the subquery RTE) but it is convenient
* to have local knowledge in each query level about which
@ -2252,7 +2252,7 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
if (thisrel->catalogname || thisrel->schemaname)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("SELECT FOR UPDATE/SHARE must specify unqualified relation names"),
errmsg("SELECT FOR UPDATE/SHARE/KEY UPDATE/KEY SHARE must specify unqualified relation names"),
parser_errposition(pstate, thisrel->location)));
i = 0;
@ -2269,17 +2269,17 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
if (rte->relkind == RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE cannot be used with foreign table \"%s\"",
errmsg("SELECT FOR UPDATE/SHARE/KEY UPDATE/KEY SHARE cannot be used with foreign table \"%s\"",
rte->eref->aliasname),
parser_errposition(pstate, thisrel->location)));
applyLockingClause(qry, i,
lc->forUpdate, lc->noWait,
lc->strength, lc->noWait,
pushedDown);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
break;
case RTE_SUBQUERY:
applyLockingClause(qry, i,
lc->forUpdate, lc->noWait,
lc->strength, lc->noWait,
pushedDown);
/* see comment above */
transformLockingClause(pstate, rte->subquery,
@ -2288,25 +2288,25 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
case RTE_JOIN:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a join"),
errmsg("SELECT FOR UPDATE/SHARE/KEY UPDATE/KEY SHARE cannot be applied to a join"),
parser_errposition(pstate, thisrel->location)));
break;
case RTE_FUNCTION:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a function"),
errmsg("SELECT FOR UPDATE/SHARE/KEY UPDATE/KEY SHARE cannot be applied to a function"),
parser_errposition(pstate, thisrel->location)));
break;
case RTE_VALUES:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES"),
errmsg("SELECT FOR UPDATE/SHARE/KEY UPDATE/KEY SHARE cannot be applied to VALUES"),
parser_errposition(pstate, thisrel->location)));
break;
case RTE_CTE:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a WITH query"),
errmsg("SELECT FOR UPDATE/SHARE/KEY UPDATE/KEY SHARE cannot be applied to a WITH query"),
parser_errposition(pstate, thisrel->location)));
break;
default:
@ -2320,7 +2320,7 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
if (rt == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation \"%s\" in FOR UPDATE/SHARE clause not found in FROM clause",
errmsg("relation \"%s\" in FOR UPDATE/SHARE/KEY UPDATE/KEY SHARE clause not found in FROM clause",
thisrel->relname),
parser_errposition(pstate, thisrel->location)));
}
@ -2332,7 +2332,7 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
*/
void
applyLockingClause(Query *qry, Index rtindex,
bool forUpdate, bool noWait, bool pushedDown)
LockClauseStrength strength, bool noWait, bool pushedDown)
{
RowMarkClause *rc;
@ -2344,10 +2344,10 @@ applyLockingClause(Query *qry, Index rtindex,
if ((rc = get_parse_rowmark(qry, rtindex)) != NULL)
{
/*
* If the same RTE is specified both FOR UPDATE and FOR SHARE, treat
* it as FOR UPDATE. (Reasonable, since you can't take both a shared
* and exclusive lock at the same time; it'll end up being exclusive
* anyway.)
* If the same RTE is specified for more than one locking strength,
* treat is as the strongest. (Reasonable, since you can't take both a
* shared and exclusive lock at the same time; it'll end up being
* exclusive anyway.)
*
* We also consider that NOWAIT wins if it's specified both ways. This
* is a bit more debatable but raising an error doesn't seem helpful.
@ -2356,7 +2356,7 @@ applyLockingClause(Query *qry, Index rtindex,
*
* And of course pushedDown becomes false if any clause is explicit.
*/
rc->forUpdate |= forUpdate;
rc->strength = Max(rc->strength, strength);
rc->noWait |= noWait;
rc->pushedDown &= pushedDown;
return;
@ -2365,7 +2365,7 @@ applyLockingClause(Query *qry, Index rtindex,
/* Make a new RowMarkClause */
rc = makeNode(RowMarkClause);
rc->rti = rtindex;
rc->forUpdate = forUpdate;
rc->strength = strength;
rc->noWait = noWait;
rc->pushedDown = pushedDown;
qry->rowMarks = lappend(qry->rowMarks, rc);

View File

@ -361,6 +361,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
%type <ival> OptTemp
%type <oncommit> OnCommitOption
%type <ival> for_locking_strength
%type <node> for_locking_item
%type <list> for_locking_clause opt_for_locking_clause for_locking_items
%type <list> locked_rels_list
@ -8900,9 +8901,10 @@ select_with_parens:
* The duplicative productions are annoying, but hard to get rid of without
* creating shift/reduce conflicts.
*
* FOR UPDATE/SHARE may be before or after LIMIT/OFFSET.
* The locking clause (FOR UPDATE etc) may be before or after LIMIT/OFFSET.
* In <=7.2.X, LIMIT/OFFSET had to be after FOR UPDATE
* We now support both orderings, but prefer LIMIT/OFFSET before FOR UPDATE/SHARE
* We now support both orderings, but prefer LIMIT/OFFSET before the locking
* clause.
* 2002-08-28 bjm
*/
select_no_parens:
@ -9321,24 +9323,23 @@ for_locking_items:
;
for_locking_item:
FOR UPDATE locked_rels_list opt_nowait
for_locking_strength locked_rels_list opt_nowait
{
LockingClause *n = makeNode(LockingClause);
n->lockedRels = $3;
n->forUpdate = TRUE;
n->noWait = $4;
$$ = (Node *) n;
}
| FOR SHARE locked_rels_list opt_nowait
{
LockingClause *n = makeNode(LockingClause);
n->lockedRels = $3;
n->forUpdate = FALSE;
n->noWait = $4;
n->lockedRels = $2;
n->strength = $1;
n->noWait = $3;
$$ = (Node *) n;
}
;
for_locking_strength:
FOR UPDATE { $$ = LCS_FORUPDATE; }
| FOR NO KEY UPDATE { $$ = LCS_FORNOKEYUPDATE; }
| FOR SHARE { $$ = LCS_FORSHARE; }
| FOR KEY SHARE { $$ = LCS_FORKEYSHARE; }
;
locked_rels_list:
OF qualified_name_list { $$ = $2; }
| /* EMPTY */ { $$ = NIL; }

View File

@ -69,6 +69,7 @@
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/multixact.h"
#include "access/reloptions.h"
#include "access/transam.h"
#include "access/xact.h"
@ -136,8 +137,9 @@ static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t got_SIGUSR2 = false;
static volatile sig_atomic_t got_SIGTERM = false;
/* Comparison point for determining whether freeze_max_age is exceeded */
/* Comparison points for determining whether freeze_max_age is exceeded */
static TransactionId recentXid;
static MultiXactId recentMulti;
/* Default freeze ages to use for autovacuum (varies by database) */
static int default_freeze_min_age;
@ -161,6 +163,7 @@ typedef struct avw_dbase
Oid adw_datid;
char *adw_name;
TransactionId adw_frozenxid;
MultiXactId adw_frozenmulti;
PgStat_StatDBEntry *adw_entry;
} avw_dbase;
@ -1076,7 +1079,9 @@ do_start_worker(void)
List *dblist;
ListCell *cell;
TransactionId xidForceLimit;
MultiXactId multiForceLimit;
bool for_xid_wrap;
bool for_multi_wrap;
avw_dbase *avdb;
TimestampTz current_time;
bool skipit = false;
@ -1122,12 +1127,20 @@ do_start_worker(void)
if (xidForceLimit < FirstNormalTransactionId)
xidForceLimit -= FirstNormalTransactionId;
/* Also determine the oldest datminmxid we will consider. */
recentMulti = ReadNextMultiXactId();
multiForceLimit = recentMulti - autovacuum_freeze_max_age;
if (multiForceLimit < FirstMultiXactId)
multiForceLimit -= FirstMultiXactId;
/*
* Choose a database to connect to. We pick the database that was least
* recently auto-vacuumed, or one that needs vacuuming to prevent Xid
* wraparound-related data loss. If any db at risk of wraparound is
* wraparound-related data loss. If any db at risk of Xid wraparound is
* found, we pick the one with oldest datfrozenxid, independently of
* autovacuum times.
* autovacuum times; similarly we pick the one with the oldest datminmxid
* if any is in MultiXactId wraparound. Note that those in Xid wraparound
* danger are given more priority than those in multi wraparound danger.
*
* Note that a database with no stats entry is not considered, except for
* Xid wraparound purposes. The theory is that if no one has ever
@ -1143,6 +1156,7 @@ do_start_worker(void)
*/
avdb = NULL;
for_xid_wrap = false;
for_multi_wrap = false;
current_time = GetCurrentTimestamp();
foreach(cell, dblist)
{
@ -1153,13 +1167,25 @@ do_start_worker(void)
if (TransactionIdPrecedes(tmp->adw_frozenxid, xidForceLimit))
{
if (avdb == NULL ||
TransactionIdPrecedes(tmp->adw_frozenxid, avdb->adw_frozenxid))
TransactionIdPrecedes(tmp->adw_frozenxid,
avdb->adw_frozenxid))
avdb = tmp;
for_xid_wrap = true;
continue;
}
else if (for_xid_wrap)
continue; /* ignore not-at-risk DBs */
else if (MultiXactIdPrecedes(tmp->adw_frozenmulti, multiForceLimit))
{
if (avdb == NULL ||
MultiXactIdPrecedes(tmp->adw_frozenmulti,
avdb->adw_frozenmulti))
avdb = tmp;
for_multi_wrap = true;
continue;
}
else if (for_multi_wrap)
continue; /* ignore not-at-risk DBs */
/* Find pgstat entry if any */
tmp->adw_entry = pgstat_fetch_stat_dbentry(tmp->adw_datid);
@ -1642,6 +1668,7 @@ AutoVacWorkerMain(int argc, char *argv[])
/* And do an appropriate amount of work */
recentXid = ReadNewTransactionId();
recentMulti = ReadNextMultiXactId();
do_autovacuum();
}
@ -1847,6 +1874,7 @@ get_database_list(void)
avdb->adw_datid = HeapTupleGetOid(tup);
avdb->adw_name = pstrdup(NameStr(pgdatabase->datname));
avdb->adw_frozenxid = pgdatabase->datfrozenxid;
avdb->adw_frozenmulti = pgdatabase->datminmxid;
/* this gets set later: */
avdb->adw_entry = NULL;
@ -2601,6 +2629,7 @@ relation_needs_vacanalyze(Oid relid,
/* freeze parameters */
int freeze_max_age;
TransactionId xidForceLimit;
MultiXactId multiForceLimit;
AssertArg(classForm != NULL);
AssertArg(OidIsValid(relid));
@ -2641,6 +2670,14 @@ relation_needs_vacanalyze(Oid relid,
force_vacuum = (TransactionIdIsNormal(classForm->relfrozenxid) &&
TransactionIdPrecedes(classForm->relfrozenxid,
xidForceLimit));
if (!force_vacuum)
{
multiForceLimit = recentMulti - autovacuum_freeze_max_age;
if (multiForceLimit < FirstMultiXactId)
multiForceLimit -= FirstMultiXactId;
force_vacuum = MultiXactIdPrecedes(classForm->relminmxid,
multiForceLimit);
}
*wraparound = force_vacuum;
/* User disabled it in pg_class.reloptions? (But ignore if at risk) */

View File

@ -55,7 +55,7 @@ static void rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation,
static void rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
Relation target_relation);
static void markQueryForLocking(Query *qry, Node *jtnode,
bool forUpdate, bool noWait, bool pushedDown);
LockClauseStrength strength, bool noWait, bool pushedDown);
static List *matchLocks(CmdType event, RuleLock *rulelocks,
int varno, Query *parsetree);
static Query *fireRIRrules(Query *parsetree, List *activeRIRs,
@ -68,7 +68,7 @@ static Query *fireRIRrules(Query *parsetree, List *activeRIRs,
* These locks will ensure that the relation schemas don't change under us
* while we are rewriting and planning the query.
*
* forUpdatePushedDown indicates that a pushed-down FOR UPDATE/SHARE applies
* forUpdatePushedDown indicates that a pushed-down FOR [KEY] UPDATE/SHARE applies
* to the current subquery, requiring all rels to be opened with RowShareLock.
* This should always be false at the start of the recursion.
*
@ -130,7 +130,7 @@ AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown)
*
* If the relation is the query's result relation, then we
* need RowExclusiveLock. Otherwise, check to see if the
* relation is accessed FOR UPDATE/SHARE or not. We can't
* relation is accessed FOR [KEY] UPDATE/SHARE or not. We can't
* just grab AccessShareLock because then the executor would
* be trying to upgrade the lock, leading to possible
* deadlocks.
@ -1357,7 +1357,7 @@ ApplyRetrieveRule(Query *parsetree,
}
/*
* If FOR UPDATE/SHARE of view, be sure we get right initial lock on the
* If FOR [KEY] UPDATE/SHARE of view, be sure we get right initial lock on the
* relations it references.
*/
rc = get_parse_rowmark(parsetree, rt_index);
@ -1405,8 +1405,8 @@ ApplyRetrieveRule(Query *parsetree,
rte->modifiedCols = NULL;
/*
* If FOR UPDATE/SHARE of view, mark all the contained tables as implicit
* FOR UPDATE/SHARE, the same as the parser would have done if the view's
* If FOR [KEY] UPDATE/SHARE of view, mark all the contained tables as implicit
* FOR [KEY] UPDATE/SHARE, the same as the parser would have done if the view's
* subquery had been written out explicitly.
*
* Note: we don't consider forUpdatePushedDown here; such marks will be
@ -1414,13 +1414,13 @@ ApplyRetrieveRule(Query *parsetree,
*/
if (rc != NULL)
markQueryForLocking(rule_action, (Node *) rule_action->jointree,
rc->forUpdate, rc->noWait, true);
rc->strength, rc->noWait, true);
return parsetree;
}
/*
* Recursively mark all relations used by a view as FOR UPDATE/SHARE.
* Recursively mark all relations used by a view as FOR [KEY] UPDATE/SHARE.
*
* This may generate an invalid query, eg if some sub-query uses an
* aggregate. We leave it to the planner to detect that.
@ -1432,7 +1432,7 @@ ApplyRetrieveRule(Query *parsetree,
*/
static void
markQueryForLocking(Query *qry, Node *jtnode,
bool forUpdate, bool noWait, bool pushedDown)
LockClauseStrength strength, bool noWait, bool pushedDown)
{
if (jtnode == NULL)
return;
@ -1446,16 +1446,16 @@ markQueryForLocking(Query *qry, Node *jtnode,
/* ignore foreign tables */
if (rte->relkind != RELKIND_FOREIGN_TABLE)
{
applyLockingClause(qry, rti, forUpdate, noWait, pushedDown);
applyLockingClause(qry, rti, strength, noWait, pushedDown);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
}
}
else if (rte->rtekind == RTE_SUBQUERY)
{
applyLockingClause(qry, rti, forUpdate, noWait, pushedDown);
/* FOR UPDATE/SHARE of subquery is propagated to subquery's rels */
applyLockingClause(qry, rti, strength, noWait, pushedDown);
/* FOR [KEY] UPDATE/SHARE of subquery is propagated to subquery's rels */
markQueryForLocking(rte->subquery, (Node *) rte->subquery->jointree,
forUpdate, noWait, true);
strength, noWait, true);
}
/* other RTE types are unaffected by FOR UPDATE */
}
@ -1465,14 +1465,14 @@ markQueryForLocking(Query *qry, Node *jtnode,
ListCell *l;
foreach(l, f->fromlist)
markQueryForLocking(qry, lfirst(l), forUpdate, noWait, pushedDown);
markQueryForLocking(qry, lfirst(l), strength, noWait, pushedDown);
}
else if (IsA(jtnode, JoinExpr))
{
JoinExpr *j = (JoinExpr *) jtnode;
markQueryForLocking(qry, j->larg, forUpdate, noWait, pushedDown);
markQueryForLocking(qry, j->rarg, forUpdate, noWait, pushedDown);
markQueryForLocking(qry, j->larg, strength, noWait, pushedDown);
markQueryForLocking(qry, j->rarg, strength, noWait, pushedDown);
}
else
elog(ERROR, "unrecognized node type: %d",

View File

@ -538,6 +538,20 @@ ProcLockHashCode(const PROCLOCKTAG *proclocktag, uint32 hashcode)
return lockhash;
}
/*
* Given two lock modes, return whether they would conflict.
*/
bool
DoLockModesConflict(LOCKMODE mode1, LOCKMODE mode2)
{
LockMethod lockMethodTable = LockMethods[DEFAULT_LOCKMETHOD];
if (lockMethodTable->conflictTab[mode1] & LOCKBIT_ON(mode2))
return true;
return false;
}
/*
* LockHasWaiters -- look up 'locktag' and check if releasing this
* lock would wake up other processes waiting for it.
@ -630,7 +644,6 @@ LockHasWaiters(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock)
return hasWaiters;
}
/*
* LockAcquire -- Check for lock conflicts, sleep if conflict found,
* set lock if/when no conflicts.

View File

@ -3905,10 +3905,10 @@ CheckForSerializableConflictOut(bool visible, Relation relation,
case HEAPTUPLE_RECENTLY_DEAD:
if (!visible)
return;
xid = HeapTupleHeaderGetXmax(tuple->t_data);
xid = HeapTupleHeaderGetUpdateXid(tuple->t_data);
break;
case HEAPTUPLE_DELETE_IN_PROGRESS:
xid = HeapTupleHeaderGetXmax(tuple->t_data);
xid = HeapTupleHeaderGetUpdateXid(tuple->t_data);
break;
case HEAPTUPLE_INSERT_IN_PROGRESS:
xid = HeapTupleHeaderGetXmin(tuple->t_data);

View File

@ -131,7 +131,7 @@ CommandIsReadOnly(Node *parsetree)
{
case CMD_SELECT:
if (stmt->rowMarks != NIL)
return false; /* SELECT FOR UPDATE/SHARE */
return false; /* SELECT FOR [KEY] UPDATE/SHARE */
else if (stmt->hasModifyingCTE)
return false; /* data-modifying CTE */
else
@ -2283,10 +2283,28 @@ CreateCommandTag(Node *parsetree)
else if (stmt->rowMarks != NIL)
{
/* not 100% but probably close enough */
if (((PlanRowMark *) linitial(stmt->rowMarks))->markType == ROW_MARK_EXCLUSIVE)
tag = "SELECT FOR UPDATE";
else
tag = "SELECT FOR SHARE";
switch (((PlanRowMark *) linitial(stmt->rowMarks))->markType)
{
case ROW_MARK_EXCLUSIVE:
tag = "SELECT FOR UPDATE";
break;
case ROW_MARK_NOKEYEXCLUSIVE:
tag = "SELECT FOR NO KEY UPDATE";
break;
case ROW_MARK_SHARE:
tag = "SELECT FOR SHARE";
break;
case ROW_MARK_KEYSHARE:
tag = "SELECT FOR KEY SHARE";
break;
case ROW_MARK_REFERENCE:
case ROW_MARK_COPY:
tag = "SELECT";
break;
default:
tag = "???";
break;
}
}
else
tag = "SELECT";
@ -2331,10 +2349,24 @@ CreateCommandTag(Node *parsetree)
else if (stmt->rowMarks != NIL)
{
/* not 100% but probably close enough */
if (((RowMarkClause *) linitial(stmt->rowMarks))->forUpdate)
tag = "SELECT FOR UPDATE";
else
tag = "SELECT FOR SHARE";
switch (((RowMarkClause *) linitial(stmt->rowMarks))->strength)
{
case LCS_FORKEYSHARE:
tag = "SELECT FOR KEY SHARE";
break;
case LCS_FORSHARE:
tag = "SELECT FOR SHARE";
break;
case LCS_FORNOKEYUPDATE:
tag = "SELECT FOR NO KEY UPDATE";
break;
case LCS_FORUPDATE:
tag = "SELECT FOR UPDATE";
break;
default:
tag = "???";
break;
}
}
else
tag = "SELECT";

View File

@ -299,7 +299,7 @@ RI_FKey_check(TriggerData *trigdata)
* Get the relation descriptors of the FK and PK tables.
*
* pk_rel is opened in RowShareLock mode since that's what our eventual
* SELECT FOR SHARE will get on it.
* SELECT FOR KEY SHARE will get on it.
*/
fk_rel = trigdata->tg_relation;
pk_rel = heap_open(riinfo->pk_relid, RowShareLock);
@ -400,7 +400,8 @@ RI_FKey_check(TriggerData *trigdata)
/* ----------
* The query string built is
* SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...] FOR SHARE
* SELECT 1 FROM ONLY <pktable> x WHERE pkatt1 = $1 [AND ...]
* FOR KEY SHARE OF x
* The type id's for the $ parameters are those of the
* corresponding FK attributes.
* ----------
@ -424,7 +425,7 @@ RI_FKey_check(TriggerData *trigdata)
querysep = "AND";
queryoids[i] = fk_type;
}
appendStringInfo(&querybuf, " FOR SHARE OF x");
appendStringInfo(&querybuf, " FOR KEY SHARE OF x");
/* Prepare and save the plan */
qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
@ -535,7 +536,8 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
/* ----------
* The query string built is
* SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...] FOR SHARE
* SELECT 1 FROM ONLY <pktable> x WHERE pkatt1 = $1 [AND ...]
* FOR KEY SHARE OF x
* The type id's for the $ parameters are those of the
* PK attributes themselves.
* ----------
@ -558,7 +560,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
querysep = "AND";
queryoids[i] = pk_type;
}
appendStringInfo(&querybuf, " FOR SHARE OF x");
appendStringInfo(&querybuf, " FOR KEY SHARE OF x");
/* Prepare and save the plan */
qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
@ -655,7 +657,7 @@ ri_restrict_del(TriggerData *trigdata, bool is_no_action)
* Get the relation descriptors of the FK and PK tables and the old tuple.
*
* fk_rel is opened in RowShareLock mode since that's what our eventual
* SELECT FOR SHARE will get on it.
* SELECT FOR KEY SHARE will get on it.
*/
fk_rel = heap_open(riinfo->fk_relid, RowShareLock);
pk_rel = trigdata->tg_relation;
@ -724,7 +726,8 @@ ri_restrict_del(TriggerData *trigdata, bool is_no_action)
/* ----------
* The query string built is
* SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
* SELECT 1 FROM ONLY <fktable> x WHERE $1 = fkatt1 [AND ...]
* FOR KEY SHARE OF x
* The type id's for the $ parameters are those of the
* corresponding PK attributes.
* ----------
@ -749,7 +752,7 @@ ri_restrict_del(TriggerData *trigdata, bool is_no_action)
querysep = "AND";
queryoids[i] = pk_type;
}
appendStringInfo(&querybuf, " FOR SHARE OF x");
appendStringInfo(&querybuf, " FOR KEY SHARE OF x");
/* Prepare and save the plan */
qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
@ -868,7 +871,7 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
* old tuple.
*
* fk_rel is opened in RowShareLock mode since that's what our eventual
* SELECT FOR SHARE will get on it.
* SELECT FOR KEY SHARE will get on it.
*/
fk_rel = heap_open(riinfo->fk_relid, RowShareLock);
pk_rel = trigdata->tg_relation;
@ -972,7 +975,7 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
querysep = "AND";
queryoids[i] = pk_type;
}
appendStringInfo(&querybuf, " FOR SHARE OF x");
appendStringInfo(&querybuf, " FOR KEY SHARE OF x");
/* Prepare and save the plan */
qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,

View File

@ -4194,7 +4194,7 @@ get_select_query_def(Query *query, deparse_context *context,
get_rule_expr(query->limitCount, context, false);
}
/* Add FOR UPDATE/SHARE clauses if present */
/* Add FOR [KEY] UPDATE/SHARE clauses if present */
if (query->hasForUpdate)
{
foreach(l, query->rowMarks)
@ -4205,12 +4205,26 @@ get_select_query_def(Query *query, deparse_context *context,
if (rc->pushedDown)
continue;
if (rc->forUpdate)
appendContextKeyword(context, " FOR UPDATE",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
else
appendContextKeyword(context, " FOR SHARE",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
switch (rc->strength)
{
case LCS_FORKEYSHARE:
appendContextKeyword(context, " FOR KEY SHARE",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
break;
case LCS_FORSHARE:
appendContextKeyword(context, " FOR SHARE",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
break;
case LCS_FORNOKEYUPDATE:
appendContextKeyword(context, " FOR NO KEY UPDATE",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
break;
case LCS_FORUPDATE:
appendContextKeyword(context, " FOR UPDATE",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
break;
}
appendStringInfo(buf, " OF %s",
quote_identifier(get_rtable_name(rc->rti,
context)));

View File

@ -30,10 +30,11 @@
#include <fcntl.h>
#include <unistd.h>
#include "access/htup_details.h"
#include "access/multixact.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
#include "access/transam.h"
#include "access/htup_details.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/index.h"
@ -2725,7 +2726,8 @@ RelationBuildLocalRelation(const char *relname,
* the XIDs that will be put into the new relation contents.
*/
void
RelationSetNewRelfilenode(Relation relation, TransactionId freezeXid)
RelationSetNewRelfilenode(Relation relation, TransactionId freezeXid,
MultiXactId minmulti)
{
Oid newrelfilenode;
RelFileNodeBackend newrnode;
@ -2738,6 +2740,7 @@ RelationSetNewRelfilenode(Relation relation, TransactionId freezeXid)
relation->rd_rel->relkind == RELKIND_SEQUENCE) ?
freezeXid == InvalidTransactionId :
TransactionIdIsNormal(freezeXid));
Assert(TransactionIdIsNormal(freezeXid) == MultiXactIdIsValid(minmulti));
/* Allocate a new relfilenode */
newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
@ -2793,6 +2796,7 @@ RelationSetNewRelfilenode(Relation relation, TransactionId freezeXid)
classform->relallvisible = 0;
}
classform->relfrozenxid = freezeXid;
classform->relminmxid = minmulti;
simple_heap_update(pg_class, &tuple->t_self, tuple);
CatalogUpdateIndexes(pg_class, tuple);
@ -3764,6 +3768,9 @@ RelationGetIndexPredicate(Relation relation)
* simple index keys, but attributes used in expressions and partial-index
* predicates.)
*
* If "keyAttrs" is true, only attributes that can be referenced by foreign
* keys are considered.
*
* Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
* we can include system attributes (e.g., OID) in the bitmap representation.
*
@ -3775,16 +3782,17 @@ RelationGetIndexPredicate(Relation relation)
* be bms_free'd when not needed anymore.
*/
Bitmapset *
RelationGetIndexAttrBitmap(Relation relation)
RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs)
{
Bitmapset *indexattrs;
Bitmapset *uindexattrs;
List *indexoidlist;
ListCell *l;
MemoryContext oldcxt;
/* Quick exit if we already computed the result. */
if (relation->rd_indexattr != NULL)
return bms_copy(relation->rd_indexattr);
return bms_copy(keyAttrs ? relation->rd_keyattr : relation->rd_indexattr);
/* Fast path if definitely no indexes */
if (!RelationGetForm(relation)->relhasindex)
@ -3810,26 +3818,38 @@ RelationGetIndexAttrBitmap(Relation relation)
* won't be returned at all by RelationGetIndexList.
*/
indexattrs = NULL;
uindexattrs = NULL;
foreach(l, indexoidlist)
{
Oid indexOid = lfirst_oid(l);
Relation indexDesc;
IndexInfo *indexInfo;
int i;
bool isKey;
indexDesc = index_open(indexOid, AccessShareLock);
/* Extract index key information from the index's pg_index row */
indexInfo = BuildIndexInfo(indexDesc);
/* Can this index be referenced by a foreign key? */
isKey = indexInfo->ii_Unique &&
indexInfo->ii_Expressions == NIL &&
indexInfo->ii_Predicate == NIL;
/* Collect simple attribute references */
for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
{
int attrnum = indexInfo->ii_KeyAttrNumbers[i];
if (attrnum != 0)
{
indexattrs = bms_add_member(indexattrs,
attrnum - FirstLowInvalidHeapAttributeNumber);
if (isKey)
uindexattrs = bms_add_member(uindexattrs,
attrnum - FirstLowInvalidHeapAttributeNumber);
}
}
/* Collect all attributes used in expressions, too */
@ -3846,10 +3866,11 @@ RelationGetIndexAttrBitmap(Relation relation)
/* Now save a copy of the bitmap in the relcache entry. */
oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
relation->rd_indexattr = bms_copy(indexattrs);
relation->rd_keyattr = bms_copy(uindexattrs);
MemoryContextSwitchTo(oldcxt);
/* We return our original working copy for caller to play with */
return indexattrs;
return keyAttrs ? uindexattrs : indexattrs;
}
/*

View File

@ -118,9 +118,8 @@ HeapTupleHeaderGetCmax(HeapTupleHeader tup)
{
CommandId cid = HeapTupleHeaderGetRawCommandId(tup);
/* We do not store cmax when locking a tuple */
Assert(!(tup->t_infomask & (HEAP_MOVED | HEAP_IS_LOCKED)));
Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tup)));
Assert(!(tup->t_infomask & HEAP_MOVED));
Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tup)));
if (tup->t_infomask & HEAP_COMBOCID)
return GetRealCmax(cid);

View File

@ -214,12 +214,25 @@ HeapTupleSatisfiesSelf(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer)
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return true;
if (tuple->t_infomask & HEAP_IS_LOCKED) /* not deleter */
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) /* not deleter */
return true;
Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
TransactionId xmax;
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)))
xmax = HeapTupleGetUpdateXid(tuple);
if (!TransactionIdIsValid(xmax))
return true;
/* updating subtransaction must have aborted */
if (!TransactionIdIsCurrentTransactionId(xmax))
return true;
else
return false;
}
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
{
/* deleting subtransaction must have aborted */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@ -250,29 +263,41 @@ HeapTupleSatisfiesSelf(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer)
if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
{
if (tuple->t_infomask & HEAP_IS_LOCKED)
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
return false; /* updated by other */
}
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
/* MultiXacts are currently only allowed to lock tuples */
Assert(tuple->t_infomask & HEAP_IS_LOCKED);
TransactionId xmax;
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
xmax = HeapTupleGetUpdateXid(tuple);
if (!TransactionIdIsValid(xmax))
return true;
if (TransactionIdIsCurrentTransactionId(xmax))
return false;
if (TransactionIdIsInProgress(xmax))
return true;
if (TransactionIdDidCommit(xmax))
return false;
return true;
}
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)))
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
{
if (tuple->t_infomask & HEAP_IS_LOCKED)
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
return false;
}
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmax(tuple)))
if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
return true;
if (!TransactionIdDidCommit(HeapTupleHeaderGetXmax(tuple)))
if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
{
/* it must have aborted or crashed */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@ -282,7 +307,7 @@ HeapTupleSatisfiesSelf(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer)
/* xmax transaction committed */
if (tuple->t_infomask & HEAP_IS_LOCKED)
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
{
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
InvalidTransactionId);
@ -290,7 +315,7 @@ HeapTupleSatisfiesSelf(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer)
}
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
HeapTupleHeaderGetXmax(tuple));
HeapTupleHeaderGetRawXmax(tuple));
return false;
}
@ -380,12 +405,25 @@ HeapTupleSatisfiesNow(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer)
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return true;
if (tuple->t_infomask & HEAP_IS_LOCKED) /* not deleter */
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) /* not deleter */
return true;
Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
TransactionId xmax;
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)))
xmax = HeapTupleGetUpdateXid(tuple);
if (!TransactionIdIsValid(xmax))
return true;
/* updating subtransaction must have aborted */
if (!TransactionIdIsCurrentTransactionId(xmax))
return true;
else
return false;
}
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
{
/* deleting subtransaction must have aborted */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@ -419,21 +457,38 @@ HeapTupleSatisfiesNow(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer)
if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
{
if (tuple->t_infomask & HEAP_IS_LOCKED)
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
return false;
}
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
/* MultiXacts are currently only allowed to lock tuples */
Assert(tuple->t_infomask & HEAP_IS_LOCKED);
TransactionId xmax;
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
xmax = HeapTupleGetUpdateXid(tuple);
if (!TransactionIdIsValid(xmax))
return true;
if (TransactionIdIsCurrentTransactionId(xmax))
{
if (HeapTupleHeaderGetCmax(tuple) >= GetCurrentCommandId(false))
return true; /* deleted after scan started */
else
return false; /* deleted before scan started */
}
if (TransactionIdIsInProgress(xmax))
return true;
if (TransactionIdDidCommit(xmax))
return false;
return true;
}
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)))
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
{
if (tuple->t_infomask & HEAP_IS_LOCKED)
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
if (HeapTupleHeaderGetCmax(tuple) >= GetCurrentCommandId(false))
return true; /* deleted after scan started */
@ -441,10 +496,10 @@ HeapTupleSatisfiesNow(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer)
return false; /* deleted before scan started */
}
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmax(tuple)))
if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
return true;
if (!TransactionIdDidCommit(HeapTupleHeaderGetXmax(tuple)))
if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
{
/* it must have aborted or crashed */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@ -454,7 +509,7 @@ HeapTupleSatisfiesNow(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer)
/* xmax transaction committed */
if (tuple->t_infomask & HEAP_IS_LOCKED)
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
{
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
InvalidTransactionId);
@ -462,7 +517,7 @@ HeapTupleSatisfiesNow(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer)
}
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
HeapTupleHeaderGetXmax(tuple));
HeapTupleHeaderGetRawXmax(tuple));
return false;
}
@ -627,12 +682,30 @@ HeapTupleSatisfiesUpdate(HeapTupleHeader tuple, CommandId curcid,
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return HeapTupleMayBeUpdated;
if (tuple->t_infomask & HEAP_IS_LOCKED) /* not deleter */
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) /* not deleter */
return HeapTupleMayBeUpdated;
Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
TransactionId xmax;
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)))
xmax = HeapTupleGetUpdateXid(tuple);
if (!TransactionIdIsValid(xmax))
return HeapTupleMayBeUpdated;
/* updating subtransaction must have aborted */
if (!TransactionIdIsCurrentTransactionId(xmax))
return HeapTupleMayBeUpdated;
else
{
if (HeapTupleHeaderGetCmax(tuple) >= curcid)
return HeapTupleSelfUpdated; /* updated after scan started */
else
return HeapTupleInvisible; /* updated before scan started */
}
}
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
{
/* deleting subtransaction must have aborted */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@ -666,26 +739,62 @@ HeapTupleSatisfiesUpdate(HeapTupleHeader tuple, CommandId curcid,
if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
{
if (tuple->t_infomask & HEAP_IS_LOCKED)
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return HeapTupleMayBeUpdated;
return HeapTupleUpdated; /* updated by other */
}
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
/* MultiXacts are currently only allowed to lock tuples */
Assert(tuple->t_infomask & HEAP_IS_LOCKED);
TransactionId xmax;
if (MultiXactIdIsRunning(HeapTupleHeaderGetXmax(tuple)))
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
{
/*
* If it's only locked but neither EXCL_LOCK nor KEYSHR_LOCK
* is set, it cannot possibly be running. Otherwise need to
* check.
*/
if ((tuple->t_infomask & (HEAP_XMAX_EXCL_LOCK |
HEAP_XMAX_KEYSHR_LOCK)) &&
MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple)))
return HeapTupleBeingUpdated;
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
return HeapTupleMayBeUpdated;
}
xmax = HeapTupleGetUpdateXid(tuple);
if (!TransactionIdIsValid(xmax))
{
if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple)))
return HeapTupleBeingUpdated;
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
return HeapTupleMayBeUpdated;
}
if (TransactionIdIsCurrentTransactionId(xmax))
{
if (HeapTupleHeaderGetCmax(tuple) >= curcid)
return HeapTupleSelfUpdated; /* updated after scan started */
else
return HeapTupleInvisible; /* updated before scan started */
}
if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple)))
return HeapTupleBeingUpdated;
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
InvalidTransactionId);
if (TransactionIdDidCommit(xmax))
return HeapTupleUpdated;
/* it must have aborted or crashed */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
return HeapTupleMayBeUpdated;
}
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)))
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
{
if (tuple->t_infomask & HEAP_IS_LOCKED)
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return HeapTupleMayBeUpdated;
if (HeapTupleHeaderGetCmax(tuple) >= curcid)
return HeapTupleSelfUpdated; /* updated after scan started */
@ -693,10 +802,10 @@ HeapTupleSatisfiesUpdate(HeapTupleHeader tuple, CommandId curcid,
return HeapTupleInvisible; /* updated before scan started */
}
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmax(tuple)))
if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
return HeapTupleBeingUpdated;
if (!TransactionIdDidCommit(HeapTupleHeaderGetXmax(tuple)))
if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
{
/* it must have aborted or crashed */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@ -706,7 +815,7 @@ HeapTupleSatisfiesUpdate(HeapTupleHeader tuple, CommandId curcid,
/* xmax transaction committed */
if (tuple->t_infomask & HEAP_IS_LOCKED)
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
{
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
InvalidTransactionId);
@ -714,7 +823,7 @@ HeapTupleSatisfiesUpdate(HeapTupleHeader tuple, CommandId curcid,
}
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
HeapTupleHeaderGetXmax(tuple));
HeapTupleHeaderGetRawXmax(tuple));
return HeapTupleUpdated; /* updated by other */
}
@ -793,12 +902,25 @@ HeapTupleSatisfiesDirty(HeapTupleHeader tuple, Snapshot snapshot,
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return true;
if (tuple->t_infomask & HEAP_IS_LOCKED) /* not deleter */
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) /* not deleter */
return true;
Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
TransactionId xmax;
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)))
xmax = HeapTupleGetUpdateXid(tuple);
if (!TransactionIdIsValid(xmax))
return true;
/* updating subtransaction must have aborted */
if (!TransactionIdIsCurrentTransactionId(xmax))
return true;
else
return false;
}
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
{
/* deleting subtransaction must have aborted */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@ -833,32 +955,47 @@ HeapTupleSatisfiesDirty(HeapTupleHeader tuple, Snapshot snapshot,
if (tuple->t_infomask & HEAP_XMAX_COMMITTED)
{
if (tuple->t_infomask & HEAP_IS_LOCKED)
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
return false; /* updated by other */
}
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
/* MultiXacts are currently only allowed to lock tuples */
Assert(tuple->t_infomask & HEAP_IS_LOCKED);
TransactionId xmax;
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
xmax = HeapTupleGetUpdateXid(tuple);
if (!TransactionIdIsValid(xmax))
return true;
if (TransactionIdIsCurrentTransactionId(xmax))
return false;
if (TransactionIdIsInProgress(xmax))
{
snapshot->xmax = xmax;
return true;
}
if (TransactionIdDidCommit(xmax))
return false;
return true;
}
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)))
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
{
if (tuple->t_infomask & HEAP_IS_LOCKED)
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
return false;
}
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmax(tuple)))
if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
{
snapshot->xmax = HeapTupleHeaderGetXmax(tuple);
snapshot->xmax = HeapTupleHeaderGetRawXmax(tuple);
return true;
}
if (!TransactionIdDidCommit(HeapTupleHeaderGetXmax(tuple)))
if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
{
/* it must have aborted or crashed */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@ -868,7 +1005,7 @@ HeapTupleSatisfiesDirty(HeapTupleHeader tuple, Snapshot snapshot,
/* xmax transaction committed */
if (tuple->t_infomask & HEAP_IS_LOCKED)
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
{
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
InvalidTransactionId);
@ -876,7 +1013,7 @@ HeapTupleSatisfiesDirty(HeapTupleHeader tuple, Snapshot snapshot,
}
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
HeapTupleHeaderGetXmax(tuple));
HeapTupleHeaderGetRawXmax(tuple));
return false; /* updated by other */
}
@ -957,12 +1094,27 @@ HeapTupleSatisfiesMVCC(HeapTupleHeader tuple, Snapshot snapshot,
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return true;
if (tuple->t_infomask & HEAP_IS_LOCKED) /* not deleter */
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) /* not deleter */
return true;
Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
TransactionId xmax;
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)))
xmax = HeapTupleGetUpdateXid(tuple);
if (!TransactionIdIsValid(xmax))
return true;
/* updating subtransaction must have aborted */
if (!TransactionIdIsCurrentTransactionId(xmax))
return true;
else if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
return true; /* updated after scan started */
else
return false; /* updated before scan started */
}
if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
{
/* deleting subtransaction must have aborted */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@ -999,19 +1151,41 @@ HeapTupleSatisfiesMVCC(HeapTupleHeader tuple, Snapshot snapshot,
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */
return true;
if (tuple->t_infomask & HEAP_IS_LOCKED)
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return true;
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
/* MultiXacts are currently only allowed to lock tuples */
Assert(tuple->t_infomask & HEAP_IS_LOCKED);
TransactionId xmax;
/* already checked above */
Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
xmax = HeapTupleGetUpdateXid(tuple);
if (!TransactionIdIsValid(xmax))
return true;
if (TransactionIdIsCurrentTransactionId(xmax))
{
if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
return true; /* deleted after scan started */
else
return false; /* deleted before scan started */
}
if (TransactionIdIsInProgress(xmax))
return true;
if (TransactionIdDidCommit(xmax))
{
/* updating transaction committed, but when? */
if (XidInMVCCSnapshot(xmax, snapshot))
return true; /* treat as still in progress */
return false;
}
return true;
}
if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
{
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)))
if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple)))
{
if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
return true; /* deleted after scan started */
@ -1019,10 +1193,10 @@ HeapTupleSatisfiesMVCC(HeapTupleHeader tuple, Snapshot snapshot,
return false; /* deleted before scan started */
}
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmax(tuple)))
if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
return true;
if (!TransactionIdDidCommit(HeapTupleHeaderGetXmax(tuple)))
if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
{
/* it must have aborted or crashed */
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
@ -1032,13 +1206,13 @@ HeapTupleSatisfiesMVCC(HeapTupleHeader tuple, Snapshot snapshot,
/* xmax transaction committed */
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
HeapTupleHeaderGetXmax(tuple));
HeapTupleHeaderGetRawXmax(tuple));
}
/*
* OK, the deleting transaction committed too ... but when?
*/
if (XidInMVCCSnapshot(HeapTupleHeaderGetXmax(tuple), snapshot))
if (XidInMVCCSnapshot(HeapTupleHeaderGetRawXmax(tuple), snapshot))
return true; /* treat as still in progress */
return false;
@ -1112,7 +1286,7 @@ HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId OldestXmin,
{
if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
return HEAPTUPLE_INSERT_IN_PROGRESS;
if (tuple->t_infomask & HEAP_IS_LOCKED)
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return HEAPTUPLE_INSERT_IN_PROGRESS;
/* inserted and then deleted by same xact */
return HEAPTUPLE_DELETE_IN_PROGRESS;
@ -1144,7 +1318,7 @@ HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId OldestXmin,
if (tuple->t_infomask & HEAP_XMAX_INVALID)
return HEAPTUPLE_LIVE;
if (tuple->t_infomask & HEAP_IS_LOCKED)
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
{
/*
* "Deleting" xact really only locked it, so the tuple is live in any
@ -1158,40 +1332,96 @@ HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId OldestXmin,
{
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
if (MultiXactIdIsRunning(HeapTupleHeaderGetXmax(tuple)))
/*
* If it's only locked but neither EXCL_LOCK nor KEYSHR_LOCK
* are set, it cannot possibly be running; otherwise have to
* check.
*/
if ((tuple->t_infomask & (HEAP_XMAX_EXCL_LOCK |
HEAP_XMAX_KEYSHR_LOCK)) &&
MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple)))
return HEAPTUPLE_LIVE;
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
}
else
{
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmax(tuple)))
if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
return HEAPTUPLE_LIVE;
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
InvalidTransactionId);
}
/*
* We don't really care whether xmax did commit, abort or crash.
* We know that xmax did lock the tuple, but it did not and will
* never actually update it.
*/
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
InvalidTransactionId);
}
/*
* We don't really care whether xmax did commit, abort or crash.
* We know that xmax did lock the tuple, but it did not and will
* never actually update it.
*/
return HEAPTUPLE_LIVE;
}
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
{
/* MultiXacts are currently only allowed to lock tuples */
Assert(tuple->t_infomask & HEAP_IS_LOCKED);
return HEAPTUPLE_LIVE;
TransactionId xmax;
if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple)))
{
/* already checked above */
Assert(!HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask));
xmax = HeapTupleGetUpdateXid(tuple);
if (!TransactionIdIsValid(xmax))
return HEAPTUPLE_LIVE;
if (TransactionIdIsInProgress(xmax))
return HEAPTUPLE_DELETE_IN_PROGRESS;
else if (TransactionIdDidCommit(xmax))
/* there are still lockers around -- can't return DEAD here */
return HEAPTUPLE_RECENTLY_DEAD;
/* updating transaction aborted */
return HEAPTUPLE_LIVE;
}
Assert(!(tuple->t_infomask & HEAP_XMAX_COMMITTED));
xmax = HeapTupleGetUpdateXid(tuple);
if (!TransactionIdIsValid(xmax))
return HEAPTUPLE_LIVE;
/* multi is not running -- updating xact cannot be */
Assert(!TransactionIdIsInProgress(xmax));
if (TransactionIdDidCommit(xmax))
{
if (!TransactionIdPrecedes(xmax, OldestXmin))
return HEAPTUPLE_RECENTLY_DEAD;
else
return HEAPTUPLE_DEAD;
}
else
{
/*
* Not in Progress, Not Committed, so either Aborted or crashed.
*/
SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
return HEAPTUPLE_LIVE;
}
/*
* Deleter committed, but perhaps it was recent enough that some open
* transactions could still see the tuple.
*/
/* Otherwise, it's dead and removable */
return HEAPTUPLE_DEAD;
}
if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED))
{
if (TransactionIdIsInProgress(HeapTupleHeaderGetXmax(tuple)))
if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple)))
return HEAPTUPLE_DELETE_IN_PROGRESS;
else if (TransactionIdDidCommit(HeapTupleHeaderGetXmax(tuple)))
else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple)))
SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED,
HeapTupleHeaderGetXmax(tuple));
HeapTupleHeaderGetRawXmax(tuple));
else
{
/*
@ -1213,7 +1443,7 @@ HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId OldestXmin,
* Deleter committed, but perhaps it was recent enough that some open
* transactions could still see the tuple.
*/
if (!TransactionIdPrecedes(HeapTupleHeaderGetXmax(tuple), OldestXmin))
if (!TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin))
return HEAPTUPLE_RECENTLY_DEAD;
/* Otherwise, it's dead and removable */
@ -1246,11 +1476,22 @@ HeapTupleIsSurelyDead(HeapTupleHeader tuple, TransactionId OldestXmin)
/*
* If the inserting transaction committed, but any deleting transaction
* aborted, the tuple is still alive. Likewise, if XMAX is a lock rather
* than a delete, the tuple is still alive.
* aborted, the tuple is still alive.
*/
if (tuple->t_infomask &
(HEAP_XMAX_INVALID | HEAP_IS_LOCKED | HEAP_XMAX_IS_MULTI))
if (tuple->t_infomask & HEAP_XMAX_INVALID)
return false;
/*
* If the XMAX is just a lock, the tuple is still alive.
*/
if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask))
return false;
/*
* If the Xmax is a MultiXact, it might be dead or alive, but we cannot
* know without checking pg_multixact.
*/
if (tuple->t_infomask & HEAP_XMAX_IS_MULTI)
return false;
/* If deleter isn't known to have committed, assume it's still running. */
@ -1258,7 +1499,7 @@ HeapTupleIsSurelyDead(HeapTupleHeader tuple, TransactionId OldestXmin)
return false;
/* Deleter committed, so tuple is dead if the XID is old enough. */
return TransactionIdPrecedes(HeapTupleHeaderGetXmax(tuple), OldestXmin);
return TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin);
}
/*
@ -1375,3 +1616,54 @@ XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
return false;
}
/*
* Is the tuple really only locked? That is, is it not updated?
*
* It's easy to check just infomask bits if the locker is not a multi; but
* otherwise we need to verify that the updating transaction has not aborted.
*
* This function is here because it follows the same time qualification rules
* laid out at the top of this file.
*/
bool
HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple)
{
TransactionId xmax;
/* if there's no valid Xmax, then there's obviously no update either */
if (tuple->t_infomask & HEAP_XMAX_INVALID)
return true;
if (tuple->t_infomask & HEAP_XMAX_LOCK_ONLY)
return true;
/* invalid xmax means no update */
if (!TransactionIdIsValid(HeapTupleHeaderGetRawXmax(tuple)))
return true;
/*
* if HEAP_XMAX_LOCK_ONLY is not set and not a multi, then this
* must necessarily have been updated
*/
if (!(tuple->t_infomask & HEAP_XMAX_IS_MULTI))
return false;
/* ... but if it's a multi, then perhaps the updating Xid aborted. */
xmax = HeapTupleGetUpdateXid(tuple);
if (!TransactionIdIsValid(xmax)) /* shouldn't happen .. */
return true;
if (TransactionIdIsCurrentTransactionId(xmax))
return false;
if (TransactionIdIsInProgress(xmax))
return false;
if (TransactionIdDidCommit(xmax))
return false;
/*
* not current, not in progress, not committed -- must have aborted or
* crashed
*/
return true;
}

View File

@ -232,6 +232,10 @@ main(int argc, char *argv[])
ControlFile.checkPointCopy.oldestXidDB);
printf(_("Latest checkpoint's oldestActiveXID: %u\n"),
ControlFile.checkPointCopy.oldestActiveXid);
printf(_("Latest checkpoint's oldestMultiXact: %u\n"),
ControlFile.checkPointCopy.oldestMulti);
printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
ControlFile.checkPointCopy.oldestMultiDB);
printf(_("Time of latest checkpoint: %s\n"),
ckpttime_str);
printf(_("Min recovery ending location: %X/%X\n"),

View File

@ -85,10 +85,12 @@ main(int argc, char *argv[])
TransactionId set_xid = 0;
Oid set_oid = 0;
MultiXactId set_mxid = 0;
MultiXactId set_oldestmxid = 0;
MultiXactOffset set_mxoff = (MultiXactOffset) -1;
uint32 minXlogTli = 0;
XLogSegNo minXlogSegNo = 0;
char *endptr;
char *endptr2;
char *DataDir;
int fd;
@ -170,7 +172,15 @@ main(int argc, char *argv[])
case 'm':
set_mxid = strtoul(optarg, &endptr, 0);
if (endptr == optarg || *endptr != '\0')
if (endptr == optarg || *endptr != ',')
{
fprintf(stderr, _("%s: invalid argument for option -m\n"), progname);
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
exit(1);
}
set_oldestmxid = strtoul(endptr + 1, &endptr2, 0);
if (endptr2 == endptr + 1 || *endptr2 != '\0')
{
fprintf(stderr, _("%s: invalid argument for option -m\n"), progname);
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
@ -181,6 +191,16 @@ main(int argc, char *argv[])
fprintf(stderr, _("%s: multitransaction ID (-m) must not be 0\n"), progname);
exit(1);
}
/*
* XXX It'd be nice to have more sanity checks here, e.g. so
* that oldest is not wrapped around w.r.t. nextMulti.
*/
if (set_oldestmxid == 0)
{
fprintf(stderr, _("%s: oldest multitransaction ID (-m) must not be 0\n"),
progname);
exit(1);
}
break;
case 'O':
@ -307,8 +327,15 @@ main(int argc, char *argv[])
ControlFile.checkPointCopy.nextOid = set_oid;
if (set_mxid != 0)
{
ControlFile.checkPointCopy.nextMulti = set_mxid;
ControlFile.checkPointCopy.oldestMulti = set_oldestmxid;
if (ControlFile.checkPointCopy.oldestMulti < FirstMultiXactId)
ControlFile.checkPointCopy.oldestMulti += FirstMultiXactId;
ControlFile.checkPointCopy.oldestMultiDB = InvalidOid;
}
if (set_mxoff != -1)
ControlFile.checkPointCopy.nextMultiOffset = set_mxoff;
@ -471,6 +498,8 @@ GuessControlValues(void)
ControlFile.checkPointCopy.nextMultiOffset = 0;
ControlFile.checkPointCopy.oldestXid = FirstNormalTransactionId;
ControlFile.checkPointCopy.oldestXidDB = InvalidOid;
ControlFile.checkPointCopy.oldestMulti = FirstMultiXactId;
ControlFile.checkPointCopy.oldestMultiDB = InvalidOid;
ControlFile.checkPointCopy.time = (pg_time_t) time(NULL);
ControlFile.checkPointCopy.oldestActiveXid = InvalidTransactionId;
@ -562,6 +591,10 @@ PrintControlValues(bool guessed)
ControlFile.checkPointCopy.oldestXidDB);
printf(_("Latest checkpoint's oldestActiveXID: %u\n"),
ControlFile.checkPointCopy.oldestActiveXid);
printf(_("Latest checkpoint's oldestMultiXid: %u\n"),
ControlFile.checkPointCopy.oldestMulti);
printf(_("Latest checkpoint's oldestMulti's DB: %u\n"),
ControlFile.checkPointCopy.oldestMultiDB);
printf(_("Maximum data alignment: %u\n"),
ControlFile.maxAlign);
/* we don't print floatFormat since can't say much useful about it */
@ -994,7 +1027,7 @@ usage(void)
printf(_(" -e XIDEPOCH set next transaction ID epoch\n"));
printf(_(" -f force update to be done\n"));
printf(_(" -l xlogfile force minimum WAL starting location for new transaction log\n"));
printf(_(" -m XID set next multitransaction ID\n"));
printf(_(" -m XID,OLDEST set next multitransaction ID and oldest value\n"));
printf(_(" -n no update, just show extracted control values (for testing)\n"));
printf(_(" -o OID set next OID\n"));
printf(_(" -O OFFSET set next multitransaction offset\n"));

View File

@ -30,12 +30,23 @@
typedef struct BulkInsertStateData *BulkInsertState;
typedef enum
/*
* Possible lock modes for a tuple.
*/
typedef enum LockTupleMode
{
LockTupleShared,
/* SELECT FOR KEY SHARE */
LockTupleKeyShare,
/* SELECT FOR SHARE */
LockTupleShare,
/* SELECT FOR NO KEY UPDATE, and UPDATEs that don't modify key columns */
LockTupleNoKeyExclusive,
/* SELECT FOR UPDATE, UPDATEs that modify key columns, and DELETE */
LockTupleExclusive
} LockTupleMode;
#define MaxLockTupleMode LockTupleExclusive
/*
* When heap_update, heap_delete, or heap_lock_tuple fail because the target
* tuple is already outdated, they fill in this struct to provide information
@ -129,14 +140,16 @@ extern HTSU_Result heap_delete(Relation relation, ItemPointer tid,
extern HTSU_Result heap_update(Relation relation, ItemPointer otid,
HeapTuple newtup,
CommandId cid, Snapshot crosscheck, bool wait,
HeapUpdateFailureData *hufd);
HeapUpdateFailureData *hufd, LockTupleMode *lockmode);
extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple,
CommandId cid, LockTupleMode mode, bool nowait,
bool follow_update,
Buffer *buffer, HeapUpdateFailureData *hufd);
extern void heap_inplace_update(Relation relation, HeapTuple tuple);
extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid);
extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
TransactionId cutoff_multi);
extern bool heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
Buffer buf);
MultiXactId cutoff_multi, Buffer buf);
extern Oid simple_heap_insert(Relation relation, HeapTuple tup);
extern void simple_heap_delete(Relation relation, ItemPointer tid);

View File

@ -54,6 +54,7 @@
#define XLOG_HEAP2_CLEANUP_INFO 0x30
#define XLOG_HEAP2_VISIBLE 0x40
#define XLOG_HEAP2_MULTI_INSERT 0x50
#define XLOG_HEAP2_LOCK_UPDATED 0x60
/*
* All what we need to find changed tuple
@ -75,6 +76,8 @@ typedef struct xl_heaptid
typedef struct xl_heap_delete
{
xl_heaptid target; /* deleted tuple id */
TransactionId xmax; /* xmax of the deleted tuple */
uint8 infobits_set; /* infomask bits */
bool all_visible_cleared; /* PD_ALL_VISIBLE was cleared */
} xl_heap_delete;
@ -141,7 +144,10 @@ typedef struct xl_multi_insert_tuple
typedef struct xl_heap_update
{
xl_heaptid target; /* deleted tuple id */
TransactionId old_xmax; /* xmax of the old tuple */
TransactionId new_xmax; /* xmax of the new tuple */
ItemPointerData newtid; /* new inserted tuple id */
uint8 old_infobits_set; /* infomask bits to set on old tuple */
bool all_visible_cleared; /* PD_ALL_VISIBLE was cleared */
bool new_all_visible_cleared; /* same for the page of newtid */
/* NEW TUPLE xl_heap_header AND TUPLE DATA FOLLOWS AT END OF STRUCT */
@ -197,16 +203,32 @@ typedef struct xl_heap_newpage
#define SizeOfHeapNewpage (offsetof(xl_heap_newpage, blkno) + sizeof(BlockNumber))
/* flags for infobits_set */
#define XLHL_XMAX_IS_MULTI 0x01
#define XLHL_XMAX_LOCK_ONLY 0x02
#define XLHL_XMAX_EXCL_LOCK 0x04
#define XLHL_XMAX_KEYSHR_LOCK 0x08
#define XLHL_KEYS_UPDATED 0x10
/* This is what we need to know about lock */
typedef struct xl_heap_lock
{
xl_heaptid target; /* locked tuple id */
TransactionId locking_xid; /* might be a MultiXactId not xid */
bool xid_is_mxact; /* is it? */
bool shared_lock; /* shared or exclusive row lock? */
int8 infobits_set; /* infomask and infomask2 bits to set */
} xl_heap_lock;
#define SizeOfHeapLock (offsetof(xl_heap_lock, shared_lock) + sizeof(bool))
#define SizeOfHeapLock (offsetof(xl_heap_lock, infobits_set) + sizeof(int8))
/* This is what we need to know about locking an updated version of a row */
typedef struct xl_heap_lock_updated
{
xl_heaptid target;
TransactionId xmax;
uint8 infobits_set;
} xl_heap_lock_updated;
#define SizeOfHeapLockUpdated (offsetof(xl_heap_lock_updated, infobits_set) + sizeof(uint8))
/* This is what we need to know about in-place update */
typedef struct xl_heap_inplace
@ -223,10 +245,11 @@ typedef struct xl_heap_freeze
RelFileNode node;
BlockNumber block;
TransactionId cutoff_xid;
MultiXactId cutoff_multi;
/* TUPLE OFFSET NUMBERS FOLLOW AT THE END */
} xl_heap_freeze;
#define SizeOfHeapFreeze (offsetof(xl_heap_freeze, cutoff_xid) + sizeof(TransactionId))
#define SizeOfHeapFreeze (offsetof(xl_heap_freeze, cutoff_multi) + sizeof(MultiXactId))
/* This is what we need to know about setting a visibility map bit */
typedef struct xl_heap_visible
@ -254,7 +277,7 @@ extern XLogRecPtr log_heap_clean(Relation reln, Buffer buffer,
OffsetNumber *nowunused, int nunused,
TransactionId latestRemovedXid);
extern XLogRecPtr log_heap_freeze(Relation reln, Buffer buffer,
TransactionId cutoff_xid,
TransactionId cutoff_xid, MultiXactId cutoff_multi,
OffsetNumber *offsets, int offcnt);
extern XLogRecPtr log_heap_visible(RelFileNode rnode, BlockNumber block,
Buffer vm_buffer, TransactionId cutoff_xid);

View File

@ -80,7 +80,9 @@ typedef HeapTupleData *HeapTuple;
extern CommandId HeapTupleHeaderGetCmin(HeapTupleHeader tup);
extern CommandId HeapTupleHeaderGetCmax(HeapTupleHeader tup);
extern void HeapTupleHeaderAdjustCmax(HeapTupleHeader tup,
CommandId *cmax,
bool *iscombo);
CommandId *cmax, bool *iscombo);
/* Prototype for HeapTupleHeader accessors in heapam.c */
extern TransactionId HeapTupleGetUpdateXid(HeapTupleHeader tuple);
#endif /* HTUP_H */

View File

@ -162,12 +162,16 @@ struct HeapTupleHeaderData
#define HEAP_HASVARWIDTH 0x0002 /* has variable-width attribute(s) */
#define HEAP_HASEXTERNAL 0x0004 /* has external stored attribute(s) */
#define HEAP_HASOID 0x0008 /* has an object-id field */
/* bit 0x0010 is available */
#define HEAP_XMAX_KEYSHR_LOCK 0x0010 /* xmax is a key-shared locker */
#define HEAP_COMBOCID 0x0020 /* t_cid is a combo cid */
#define HEAP_XMAX_EXCL_LOCK 0x0040 /* xmax is exclusive locker */
#define HEAP_XMAX_SHARED_LOCK 0x0080 /* xmax is shared locker */
/* if either LOCK bit is set, xmax hasn't deleted the tuple, only locked it */
#define HEAP_IS_LOCKED (HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_SHARED_LOCK)
#define HEAP_XMAX_LOCK_ONLY 0x0080 /* xmax, if valid, is only a locker */
/* xmax is a shared locker */
#define HEAP_XMAX_SHR_LOCK (HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_KEYSHR_LOCK)
#define HEAP_LOCK_MASK (HEAP_XMAX_SHR_LOCK | HEAP_XMAX_EXCL_LOCK | \
HEAP_XMAX_KEYSHR_LOCK)
#define HEAP_XMIN_COMMITTED 0x0100 /* t_xmin committed */
#define HEAP_XMIN_INVALID 0x0200 /* t_xmin invalid/aborted */
#define HEAP_XMAX_COMMITTED 0x0400 /* t_xmax committed */
@ -182,17 +186,42 @@ struct HeapTupleHeaderData
* upgrade support */
#define HEAP_MOVED (HEAP_MOVED_OFF | HEAP_MOVED_IN)
#define HEAP_XACT_MASK 0xFFE0 /* visibility-related bits */
#define HEAP_XACT_MASK 0xFFF0 /* visibility-related bits */
/*
* A tuple is only locked (i.e. not updated by its Xmax) if it the
* HEAP_XMAX_LOCK_ONLY bit is set.
*
* See also HeapTupleHeaderIsOnlyLocked, which also checks for a possible
* aborted updater transaction.
*/
#define HEAP_XMAX_IS_LOCKED_ONLY(infomask) \
((infomask) & HEAP_XMAX_LOCK_ONLY)
/*
* Use these to test whether a particular lock is applied to a tuple
*/
#define HEAP_XMAX_IS_SHR_LOCKED(infomask) \
(((infomask) & HEAP_LOCK_MASK) == HEAP_XMAX_SHR_LOCK)
#define HEAP_XMAX_IS_EXCL_LOCKED(infomask) \
(((infomask) & HEAP_LOCK_MASK) == HEAP_XMAX_EXCL_LOCK)
#define HEAP_XMAX_IS_KEYSHR_LOCKED(infomask) \
(((infomask) & HEAP_LOCK_MASK) == HEAP_XMAX_KEYSHR_LOCK)
/* turn these all off when Xmax is to change */
#define HEAP_XMAX_BITS (HEAP_XMAX_COMMITTED | HEAP_XMAX_INVALID | \
HEAP_XMAX_IS_MULTI | HEAP_LOCK_MASK | HEAP_XMAX_LOCK_ONLY)
/*
* information stored in t_infomask2:
*/
#define HEAP_NATTS_MASK 0x07FF /* 11 bits for number of attributes */
/* bits 0x3800 are available */
/* bits 0x1800 are available */
#define HEAP_KEYS_UPDATED 0x2000 /* tuple was updated and key cols
* modified, or tuple deleted */
#define HEAP_HOT_UPDATED 0x4000 /* tuple was HOT-updated */
#define HEAP_ONLY_TUPLE 0x8000 /* this is heap-only tuple */
#define HEAP2_XACT_MASK 0xC000 /* visibility-related bits */
#define HEAP2_XACT_MASK 0xE000 /* visibility-related bits */
/*
* HEAP_TUPLE_HAS_MATCH is a temporary flag used during hash joins. It is
@ -219,7 +248,24 @@ struct HeapTupleHeaderData
(tup)->t_choice.t_heap.t_xmin = (xid) \
)
#define HeapTupleHeaderGetXmax(tup) \
/*
* HeapTupleHeaderGetRawXmax gets you the raw Xmax field. To find out the Xid
* that updated a tuple, you might need to resolve the MultiXactId if certain
* bits are set. HeapTupleHeaderGetUpdateXid checks those bits and takes care
* to resolve the MultiXactId if necessary. This might involve multixact I/O,
* so it should only be used if absolutely necessary.
*/
#define HeapTupleHeaderGetUpdateXid(tup) \
( \
(!((tup)->t_infomask & HEAP_XMAX_INVALID) && \
((tup)->t_infomask & HEAP_XMAX_IS_MULTI) && \
!((tup)->t_infomask & HEAP_XMAX_LOCK_ONLY)) ? \
HeapTupleGetUpdateXid(tup) \
: \
HeapTupleHeaderGetRawXmax(tup) \
)
#define HeapTupleHeaderGetRawXmax(tup) \
( \
(tup)->t_choice.t_heap.t_xmax \
)

View File

@ -13,8 +13,15 @@
#include "access/xlog.h"
/*
* The first two MultiXactId values are reserved to store the truncation Xid
* and epoch of the first segment, so we start assigning multixact values from
* 2.
*/
#define InvalidMultiXactId ((MultiXactId) 0)
#define FirstMultiXactId ((MultiXactId) 1)
#define MaxMultiXactId ((MultiXactId) 0xFFFFFFFF)
#define MultiXactIdIsValid(multi) ((multi) != InvalidMultiXactId)
@ -22,6 +29,33 @@
#define NUM_MXACTOFFSET_BUFFERS 8
#define NUM_MXACTMEMBER_BUFFERS 16
/*
* Possible multixact lock modes ("status"). The first four modes are for
* tuple locks (FOR KEY SHARE, FOR SHARE, FOR NO KEY UPDATE, FOR UPDATE); the
* next two are used for update and delete modes.
*/
typedef enum
{
MultiXactStatusForKeyShare = 0x00,
MultiXactStatusForShare = 0x01,
MultiXactStatusForNoKeyUpdate = 0x02,
MultiXactStatusForUpdate = 0x03,
/* an update that doesn't touch "key" columns */
MultiXactStatusNoKeyUpdate = 0x04,
/* other updates, and delete */
MultiXactStatusUpdate = 0x05
} MultiXactStatus;
#define MaxMultiXactStatus MultiXactStatusUpdate
typedef struct MultiXactMember
{
TransactionId xid;
MultiXactStatus status;
} MultiXactMember;
/* ----------------
* multixact-related XLOG entries
* ----------------
@ -35,21 +69,24 @@ typedef struct xl_multixact_create
{
MultiXactId mid; /* new MultiXact's ID */
MultiXactOffset moff; /* its starting offset in members file */
int32 nxids; /* number of member XIDs */
TransactionId xids[1]; /* VARIABLE LENGTH ARRAY */
int32 nmembers; /* number of member XIDs */
MultiXactMember members[FLEXIBLE_ARRAY_MEMBER];
} xl_multixact_create;
#define MinSizeOfMultiXactCreate offsetof(xl_multixact_create, xids)
#define SizeOfMultiXactCreate (offsetof(xl_multixact_create, members))
extern MultiXactId MultiXactIdCreate(TransactionId xid1, TransactionId xid2);
extern MultiXactId MultiXactIdExpand(MultiXactId multi, TransactionId xid);
extern MultiXactId MultiXactIdCreate(TransactionId xid1,
MultiXactStatus status1, TransactionId xid2,
MultiXactStatus status2);
extern MultiXactId MultiXactIdExpand(MultiXactId multi, TransactionId xid,
MultiXactStatus status);
extern MultiXactId ReadNextMultiXactId(void);
extern bool MultiXactIdIsRunning(MultiXactId multi);
extern bool MultiXactIdIsCurrent(MultiXactId multi);
extern void MultiXactIdWait(MultiXactId multi);
extern bool ConditionalMultiXactIdWait(MultiXactId multi);
extern void MultiXactIdSetOldestMember(void);
extern int GetMultiXactIdMembers(MultiXactId multi, TransactionId **xids);
extern int GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **xids,
bool allow_old);
extern bool MultiXactIdPrecedes(MultiXactId multi1, MultiXactId multi2);
extern void AtEOXact_MultiXact(void);
extern void AtPrepare_MultiXact(void);
@ -60,14 +97,21 @@ extern void MultiXactShmemInit(void);
extern void BootStrapMultiXact(void);
extern void StartupMultiXact(void);
extern void ShutdownMultiXact(void);
extern void SetMultiXactIdLimit(MultiXactId oldest_datminmxid,
Oid oldest_datoid);
extern void MultiXactGetCheckptMulti(bool is_shutdown,
MultiXactId *nextMulti,
MultiXactOffset *nextMultiOffset);
MultiXactOffset *nextMultiOffset,
MultiXactId *oldestMulti,
Oid *oldestMultiDB);
extern void CheckPointMultiXact(void);
extern MultiXactId GetOldestMultiXactId(void);
extern void TruncateMultiXact(MultiXactId cutoff_multi);
extern void MultiXactSetNextMXact(MultiXactId nextMulti,
MultiXactOffset nextMultiOffset);
extern void MultiXactAdvanceNextMXact(MultiXactId minMulti,
MultiXactOffset minMultiOffset);
extern void MultiXactAdvanceOldest(MultiXactId oldestMulti, Oid oldestMultiDB);
extern void multixact_twophase_recover(TransactionId xid, uint16 info,
void *recdata, uint32 len);
@ -78,5 +122,7 @@ extern void multixact_twophase_postabort(TransactionId xid, uint16 info,
extern void multixact_redo(XLogRecPtr lsn, XLogRecord *record);
extern void multixact_desc(StringInfo buf, uint8 xl_info, char *rec);
extern char *mxid_to_string(MultiXactId multi, int nmembers,
MultiXactMember *members);
#endif /* MULTIXACT_H */

View File

@ -21,7 +21,7 @@ typedef struct RewriteStateData *RewriteState;
extern RewriteState begin_heap_rewrite(Relation NewHeap,
TransactionId OldestXmin, TransactionId FreezeXid,
bool use_wal);
MultiXactId MultiXactFrzLimit, bool use_wal);
extern void end_heap_rewrite(RewriteState state);
extern void rewrite_heap_tuple(RewriteState state, HeapTuple oldTuple,
HeapTuple newTuple);

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201301211
#define CATALOG_VERSION_NO 201301231
#endif

View File

@ -67,6 +67,8 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
bool relhastriggers; /* has (or has had) any TRIGGERs */
bool relhassubclass; /* has (or has had) derived classes */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
TransactionId relminmxid; /* all multixacts in this rel are >= this.
* this is really a MultiXactId */
#ifdef CATALOG_VARLEN /* variable-length fields start here */
/* NOTE: These fields are not present in a relcache entry's rd_rel field. */
@ -77,7 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
/* Size of fixed part of pg_class tuples, not counting var-length fields */
#define CLASS_TUPLE_SIZE \
(offsetof(FormData_pg_class,relfrozenxid) + sizeof(TransactionId))
(offsetof(FormData_pg_class,relminmxid) + sizeof(TransactionId))
/* ----------------
* Form_pg_class corresponds to a pointer to a tuple with
@ -91,7 +93,7 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
#define Natts_pg_class 27
#define Natts_pg_class 28
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
@ -117,8 +119,9 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relhastriggers 23
#define Anum_pg_class_relhassubclass 24
#define Anum_pg_class_relfrozenxid 25
#define Anum_pg_class_relacl 26
#define Anum_pg_class_reloptions 27
#define Anum_pg_class_relminmxid 26
#define Anum_pg_class_relacl 27
#define Anum_pg_class_reloptions 28
/* ----------------
* initial contents of pg_class
@ -129,14 +132,17 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
/* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f 3 _null_ _null_ ));
/*
* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
* similarly, "1" in relminmxid stands for FirstMultiXactId
*/
DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f 3 1 _null_ _null_ ));
DESCR("");
DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f 3 _null_ _null_ ));
DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f 3 1 _null_ _null_ ));
DESCR("");
DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 1 _null_ _null_ ));
DESCR("");
DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 28 0 t f f f f 3 1 _null_ _null_ ));
DESCR("");

View File

@ -21,7 +21,7 @@
/* Version identifier for this pg_control format */
#define PG_CONTROL_VERSION 932
#define PG_CONTROL_VERSION 933
/*
* Body of CheckPoint XLOG records. This is declared here because we keep
@ -41,6 +41,8 @@ typedef struct CheckPoint
MultiXactOffset nextMultiOffset; /* next free MultiXact offset */
TransactionId oldestXid; /* cluster-wide minimum datfrozenxid */
Oid oldestXidDB; /* database with minimum datfrozenxid */
MultiXactId oldestMulti; /* cluster-wide minimum datminmxid */
Oid oldestMultiDB; /* database with minimum datminmxid */
pg_time_t time; /* time stamp of checkpoint */
/*

View File

@ -41,6 +41,7 @@ CATALOG(pg_database,1262) BKI_SHARED_RELATION BKI_ROWTYPE_OID(1248) BKI_SCHEMA_M
int32 datconnlimit; /* max connections allowed (-1=no limit) */
Oid datlastsysoid; /* highest OID to consider a system OID */
TransactionId datfrozenxid; /* all Xids < this are frozen in this DB */
TransactionId datminmxid; /* all multixacts in the DB are >= this */
Oid dattablespace; /* default table space for this DB */
#ifdef CATALOG_VARLEN /* variable-length fields start here */
@ -59,7 +60,7 @@ typedef FormData_pg_database *Form_pg_database;
* compiler constants for pg_database
* ----------------
*/
#define Natts_pg_database 12
#define Natts_pg_database 13
#define Anum_pg_database_datname 1
#define Anum_pg_database_datdba 2
#define Anum_pg_database_encoding 3
@ -70,10 +71,11 @@ typedef FormData_pg_database *Form_pg_database;
#define Anum_pg_database_datconnlimit 8
#define Anum_pg_database_datlastsysoid 9
#define Anum_pg_database_datfrozenxid 10
#define Anum_pg_database_dattablespace 11
#define Anum_pg_database_datacl 12
#define Anum_pg_database_datminmxid 11
#define Anum_pg_database_dattablespace 12
#define Anum_pg_database_datacl 13
DATA(insert OID = 1 ( template1 PGUID ENCODING "LC_COLLATE" "LC_CTYPE" t t -1 0 0 1663 _null_));
DATA(insert OID = 1 ( template1 PGUID ENCODING "LC_COLLATE" "LC_CTYPE" t t -1 0 0 1 1663 _null_));
SHDESCR("default template for new databases");
#define TemplateDbOid 1

View File

@ -2909,6 +2909,8 @@ DATA(insert OID = 1371 ( pg_lock_status PGNSP PGUID 12 1 1000 0 0 f f f f t t
DESCR("view system lock information");
DATA(insert OID = 1065 ( pg_prepared_xact PGNSP PGUID 12 1 1000 0 0 f f f f t t v 0 0 2249 "" "{28,25,1184,26,26}" "{o,o,o,o,o}" "{transaction,gid,prepared,ownerid,dbid}" _null_ pg_prepared_xact _null_ _null_ _null_ ));
DESCR("view two-phase transactions");
DATA(insert OID = 3819 ( pg_get_multixact_members PGNSP PGUID 12 1 1000 0 0 f f f f t t v 1 0 2249 "28" "{28,28,25}" "{i,o,o}" "{multixid,xid,mode}" _null_ pg_get_multixact_members _null_ _null_ _null_ ));
DESCR("view members of a multixactid");
DATA(insert OID = 3537 ( pg_describe_object PGNSP PGUID 12 1 0 0 0 f f f f t f s 3 0 25 "26 26 23" _null_ _null_ _null_ _null_ pg_describe_object _null_ _null_ _null_ ));
DESCR("get identification of SQL object");

View File

@ -30,6 +30,7 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
bool is_system_catalog,
bool swap_toast_by_content,
bool check_constraints,
TransactionId frozenXid);
TransactionId frozenXid,
MultiXactId frozenMulti);
#endif /* CLUSTER_H */

View File

@ -153,12 +153,14 @@ extern void vac_update_relstats(Relation relation,
double num_tuples,
BlockNumber num_all_visible_pages,
bool hasindex,
TransactionId frozenxid);
TransactionId frozenxid,
MultiXactId minmulti);
extern void vacuum_set_xid_limits(int freeze_min_age, int freeze_table_age,
bool sharedRel,
TransactionId *oldestXmin,
TransactionId *freezeLimit,
TransactionId *freezeTableLimit);
TransactionId *freezeTableLimit,
MultiXactId *multiXactFrzLimit);
extern void vac_update_datfrozenxid(void);
extern void vacuum_delay_point(void);

View File

@ -193,7 +193,7 @@ extern void ExecConstraints(ResultRelInfo *resultRelInfo,
extern ExecRowMark *ExecFindRowMark(EState *estate, Index rti);
extern ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist);
extern TupleTableSlot *EvalPlanQual(EState *estate, EPQState *epqstate,
Relation relation, Index rti,
Relation relation, Index rti, int lockmode,
ItemPointer tid, TransactionId priorXmax);
extern HeapTuple EvalPlanQualFetch(EState *estate, Relation relation,
int lockmode, ItemPointer tid, TransactionId priorXmax);

View File

@ -403,9 +403,9 @@ typedef struct EState
/*
* ExecRowMark -
* runtime representation of FOR UPDATE/SHARE clauses
* runtime representation of FOR [KEY] UPDATE/SHARE clauses
*
* When doing UPDATE, DELETE, or SELECT FOR UPDATE/SHARE, we should have an
* When doing UPDATE, DELETE, or SELECT FOR [KEY] UPDATE/SHARE, we should have an
* ExecRowMark for each non-target relation in the query (except inheritance
* parent RTEs, which can be ignored at runtime). See PlanRowMark for details
* about most of the fields. In addition to fields directly derived from
@ -426,7 +426,7 @@ typedef struct ExecRowMark
/*
* ExecAuxRowMark -
* additional runtime representation of FOR UPDATE/SHARE clauses
* additional runtime representation of FOR [KEY] UPDATE/SHARE clauses
*
* 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,
@ -1824,7 +1824,7 @@ typedef struct SetOpState
/* ----------------
* LockRowsState information
*
* LockRows nodes are used to enforce FOR UPDATE/FOR SHARE locking.
* LockRows nodes are used to enforce FOR [KEY] UPDATE/SHARE locking.
* ----------------
*/
typedef struct LockRowsState

View File

@ -74,7 +74,7 @@ typedef uint32 AclMode; /* a bitmask of privilege bits */
#define ACL_CONNECT (1<<11) /* for databases */
#define N_ACL_RIGHTS 12 /* 1 plus the last 1<<x */
#define ACL_NO_RIGHTS 0
/* Currently, SELECT ... FOR UPDATE/FOR SHARE requires UPDATE privileges */
/* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
#define ACL_SELECT_FOR_UPDATE ACL_UPDATE
@ -119,7 +119,7 @@ typedef struct Query
bool hasDistinctOn; /* distinctClause is from DISTINCT ON */
bool hasRecursive; /* WITH RECURSIVE was specified */
bool hasModifyingCTE; /* has INSERT/UPDATE/DELETE in WITH */
bool hasForUpdate; /* FOR UPDATE or FOR SHARE was specified */
bool hasForUpdate; /* FOR [KEY] UPDATE/SHARE was specified */
List *cteList; /* WITH list (of CommonTableExpr's) */
@ -572,18 +572,28 @@ typedef struct DefElem
} DefElem;
/*
* LockingClause - raw representation of FOR UPDATE/SHARE options
* LockingClause - raw representation of FOR [NO KEY] UPDATE/[KEY] SHARE
* options
*
* Note: lockedRels == NIL means "all relations in query". Otherwise it
* is a list of RangeVar nodes. (We use RangeVar mainly because it carries
* a location field --- currently, parse analysis insists on unqualified
* names in LockingClause.)
*/
typedef enum LockClauseStrength
{
/* order is important -- see applyLockingClause */
LCS_FORKEYSHARE,
LCS_FORSHARE,
LCS_FORNOKEYUPDATE,
LCS_FORUPDATE
} LockClauseStrength;
typedef struct LockingClause
{
NodeTag type;
List *lockedRels; /* FOR UPDATE or FOR SHARE relations */
bool forUpdate; /* true = FOR UPDATE, false = FOR SHARE */
List *lockedRels; /* FOR [KEY] UPDATE/SHARE relations */
LockClauseStrength strength;
bool noWait; /* NOWAIT option */
} LockingClause;
@ -865,21 +875,21 @@ typedef struct WindowClause
/*
* RowMarkClause -
* parser output representation of FOR UPDATE/SHARE clauses
* parser output representation of FOR [KEY] UPDATE/SHARE clauses
*
* Query.rowMarks contains a separate RowMarkClause node for each relation
* identified as a FOR UPDATE/SHARE target. If FOR UPDATE/SHARE is applied
* to a subquery, we generate RowMarkClauses for all normal and subquery rels
* in the subquery, but they are marked pushedDown = true to distinguish them
* from clauses that were explicitly written at this query level. Also,
* Query.hasForUpdate tells whether there were explicit FOR UPDATE/SHARE
* clauses in the current query level.
* identified as a FOR [KEY] UPDATE/SHARE target. If one of these clauses
* is applied to a subquery, we generate RowMarkClauses for all normal and
* subquery rels in the subquery, but they are marked pushedDown = true to
* distinguish them from clauses that were explicitly written at this query
* level. Also, Query.hasForUpdate tells whether there were explicit FOR
* UPDATE/SHARE/KEY SHARE clauses in the current query level.
*/
typedef struct RowMarkClause
{
NodeTag type;
Index rti; /* range table index of target relation */
bool forUpdate; /* true = FOR UPDATE, false = FOR SHARE */
LockClauseStrength strength;
bool noWait; /* NOWAIT option */
bool pushedDown; /* pushed down from higher query level? */
} RowMarkClause;

View File

@ -752,7 +752,7 @@ typedef struct Limit
* RowMarkType -
* enums for types of row-marking operations
*
* When doing UPDATE, DELETE, or SELECT FOR UPDATE/SHARE, we have to uniquely
* When doing UPDATE, DELETE, or SELECT FOR [KEY] UPDATE/SHARE, we have to uniquely
* identify all the source rows, not only those from the target relations, so
* that we can perform EvalPlanQual rechecking at need. For plain tables we
* can just fetch the TID, the same as for a target relation. Otherwise (for
@ -763,20 +763,22 @@ typedef struct Limit
typedef enum RowMarkType
{
ROW_MARK_EXCLUSIVE, /* obtain exclusive tuple lock */
ROW_MARK_NOKEYEXCLUSIVE, /* obtain no-key exclusive tuple lock */
ROW_MARK_SHARE, /* obtain shared tuple lock */
ROW_MARK_KEYSHARE, /* obtain keyshare tuple lock */
ROW_MARK_REFERENCE, /* just fetch the TID */
ROW_MARK_COPY /* physically copy the row value */
} RowMarkType;
#define RowMarkRequiresRowShareLock(marktype) ((marktype) <= ROW_MARK_SHARE)
#define RowMarkRequiresRowShareLock(marktype) ((marktype) <= ROW_MARK_KEYSHARE)
/*
* PlanRowMark -
* plan-time representation of FOR UPDATE/SHARE clauses
* plan-time representation of FOR [KEY] UPDATE/SHARE clauses
*
* When doing UPDATE, DELETE, or SELECT FOR UPDATE/SHARE, we create a separate
* When doing UPDATE, DELETE, or SELECT FOR [KEY] UPDATE/SHARE, we create a separate
* PlanRowMark node for each non-target relation in the query. Relations that
* are not specified as FOR UPDATE/SHARE are marked ROW_MARK_REFERENCE (if
* are not specified as FOR [KEY] UPDATE/SHARE are marked ROW_MARK_REFERENCE (if
* real tables) or ROW_MARK_COPY (if not).
*
* Initially all PlanRowMarks have rti == prti and isParent == false.

View File

@ -38,6 +38,6 @@ extern bool analyze_requires_snapshot(Node *parseTree);
extern void CheckSelectLocking(Query *qry);
extern void applyLockingClause(Query *qry, Index rtindex,
bool forUpdate, bool noWait, bool pushedDown);
LockClauseStrength strength, bool noWait, bool pushedDown);
#endif /* ANALYZE_H */

View File

@ -456,6 +456,13 @@ typedef Datum *DatumPtr;
#define TransactionIdGetDatum(X) ((Datum) SET_4_BYTES((X)))
/*
* MultiXactIdGetDatum
* Returns datum representation for a multixact identifier.
*/
#define MultiXactIdGetDatum(X) ((Datum) SET_4_BYTES((X)))
/*
* DatumGetCommandId
* Returns command identifier value of a datum.

View File

@ -478,6 +478,7 @@ typedef enum
extern void InitLocks(void);
extern LockMethod GetLocksMethodTable(const LOCK *lock);
extern uint32 LockTagHashCode(const LOCKTAG *locktag);
extern bool DoLockModesConflict(LOCKMODE mode1, LOCKMODE mode2);
extern LockAcquireResult LockAcquire(const LOCKTAG *locktag,
LOCKMODE lockmode,
bool sessionLock,

View File

@ -1134,6 +1134,9 @@ extern Datum ginarrayconsistent(PG_FUNCTION_ARGS);
/* access/transam/twophase.c */
extern Datum pg_prepared_xact(PG_FUNCTION_ARGS);
/* access/transam/multixact.c */
extern Datum pg_get_multixact_members(PG_FUNCTION_ARGS);
/* catalogs/dependency.c */
extern Datum pg_describe_object(PG_FUNCTION_ARGS);

View File

@ -114,6 +114,7 @@ typedef struct RelationData
Oid rd_id; /* relation's object id */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Bitmapset *rd_indexattr; /* identifies columns used in indexes */
Bitmapset *rd_keyattr; /* cols that can be ref'd by foreign keys */
Oid rd_oidindex; /* OID of unique index on OID, if any */
LockInfoData rd_lockInfo; /* lock mgr's info for locking relation */
RuleLock *rd_rules; /* rewrite rules */

View File

@ -41,7 +41,7 @@ extern List *RelationGetIndexList(Relation relation);
extern Oid RelationGetOidIndex(Relation relation);
extern List *RelationGetIndexExpressions(Relation relation);
extern List *RelationGetIndexPredicate(Relation relation);
extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation);
extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation, bool keyAttrs);
extern void RelationGetExclusionInfo(Relation indexRelation,
Oid **operators,
Oid **procs,
@ -77,7 +77,7 @@ extern Relation RelationBuildLocalRelation(const char *relname,
* Routine to manage assignment of new relfilenode to a relation
*/
extern void RelationSetNewRelfilenode(Relation relation,
TransactionId freezeXid);
TransactionId freezeXid, MultiXactId minmulti);
/*
* Routines for flushing/rebuilding relcache entries in various scenarios

View File

@ -88,5 +88,6 @@ extern bool HeapTupleIsSurelyDead(HeapTupleHeader tuple,
extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
uint16 infomask, TransactionId xid);
extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple);
#endif /* TQUAL_H */

View File

@ -0,0 +1,276 @@
Parsed test spec with 2 sessions
starting permutation: s1s s1u s1r s1l s1c s2l s2c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s1r: ROLLBACK TO f;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2c: COMMIT;
starting permutation: s1s s1u s1r s1l s2l s1c s2c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s1r: ROLLBACK TO f;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
step s2c: COMMIT;
starting permutation: s1s s1u s1r s1l s2l s2c s1c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s1r: ROLLBACK TO f;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2c: COMMIT;
step s1c: COMMIT;
starting permutation: s1s s1u s1r s2l s1l s1c s2c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s1r: ROLLBACK TO f;
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
step s2c: COMMIT;
starting permutation: s1s s1u s1r s2l s1l s2c s1c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s1r: ROLLBACK TO f;
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2c: COMMIT;
step s1c: COMMIT;
starting permutation: s1s s1u s1r s2l s2c s1l s1c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s1r: ROLLBACK TO f;
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2c: COMMIT;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
starting permutation: s1s s1u s2l s1r s1l s1c s2c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s2l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
step s1r: ROLLBACK TO f;
step s2l: <... completed>
key value
1 1
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
step s2c: COMMIT;
starting permutation: s1s s1u s2l s1r s1l s2c s1c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s2l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
step s1r: ROLLBACK TO f;
step s2l: <... completed>
key value
1 1
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2c: COMMIT;
step s1c: COMMIT;
starting permutation: s1s s1u s2l s1r s2c s1l s1c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s2l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
step s1r: ROLLBACK TO f;
step s2l: <... completed>
key value
1 1
step s2c: COMMIT;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
starting permutation: s1s s1u s2l s2c s1r s1l s1c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s2l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
invalid permutation detected
starting permutation: s1s s2l s1u s1r s1l s1c s2c
step s1s: SAVEPOINT f;
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1u: UPDATE foo SET key = 2; <waiting ...>
invalid permutation detected
starting permutation: s1s s2l s1u s1r s1l s2c s1c
step s1s: SAVEPOINT f;
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1u: UPDATE foo SET key = 2; <waiting ...>
invalid permutation detected
starting permutation: s1s s2l s1u s1r s2c s1l s1c
step s1s: SAVEPOINT f;
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1u: UPDATE foo SET key = 2; <waiting ...>
invalid permutation detected
starting permutation: s1s s2l s1u s2c s1r s1l s1c
step s1s: SAVEPOINT f;
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1u: UPDATE foo SET key = 2; <waiting ...>
step s2c: COMMIT;
step s1u: <... completed>
step s1r: ROLLBACK TO f;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
starting permutation: s1s s2l s2c s1u s1r s1l s1c
step s1s: SAVEPOINT f;
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2c: COMMIT;
step s1u: UPDATE foo SET key = 2;
step s1r: ROLLBACK TO f;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
starting permutation: s2l s1s s1u s1r s1l s1c s2c
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2; <waiting ...>
invalid permutation detected
starting permutation: s2l s1s s1u s1r s1l s2c s1c
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2; <waiting ...>
invalid permutation detected
starting permutation: s2l s1s s1u s1r s2c s1l s1c
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2; <waiting ...>
invalid permutation detected
starting permutation: s2l s1s s1u s2c s1r s1l s1c
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2; <waiting ...>
step s2c: COMMIT;
step s1u: <... completed>
step s1r: ROLLBACK TO f;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
starting permutation: s2l s1s s2c s1u s1r s1l s1c
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1s: SAVEPOINT f;
step s2c: COMMIT;
step s1u: UPDATE foo SET key = 2;
step s1r: ROLLBACK TO f;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
starting permutation: s2l s2c s1s s1u s1r s1l s1c
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2c: COMMIT;
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s1r: ROLLBACK TO f;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;

View File

@ -0,0 +1,278 @@
Parsed test spec with 2 sessions
starting permutation: s1s s1u s1r s1l s1c s2l s2c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s1r: ROLLBACK TO f;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2c: COMMIT;
starting permutation: s1s s1u s1r s1l s2l s1c s2c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s1r: ROLLBACK TO f;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
step s2c: COMMIT;
starting permutation: s1s s1u s1r s1l s2l s2c s1c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s1r: ROLLBACK TO f;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2c: COMMIT;
step s1c: COMMIT;
starting permutation: s1s s1u s1r s2l s1l s1c s2c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s1r: ROLLBACK TO f;
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
step s2c: COMMIT;
starting permutation: s1s s1u s1r s2l s1l s2c s1c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s1r: ROLLBACK TO f;
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2c: COMMIT;
step s1c: COMMIT;
starting permutation: s1s s1u s1r s2l s2c s1l s1c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s1r: ROLLBACK TO f;
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2c: COMMIT;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
starting permutation: s1s s1u s2l s1r s1l s1c s2c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s2l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
step s1r: ROLLBACK TO f;
step s2l: <... completed>
key value
1 1
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
step s2c: COMMIT;
starting permutation: s1s s1u s2l s1r s1l s2c s1c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s2l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
step s1r: ROLLBACK TO f;
step s2l: <... completed>
key value
1 1
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2c: COMMIT;
step s1c: COMMIT;
starting permutation: s1s s1u s2l s1r s2c s1l s1c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s2l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
step s1r: ROLLBACK TO f;
step s2l: <... completed>
key value
1 1
step s2c: COMMIT;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
starting permutation: s1s s1u s2l s2c s1r s1l s1c
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s2l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
invalid permutation detected
starting permutation: s1s s2l s1u s1r s1l s1c s2c
step s1s: SAVEPOINT f;
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1u: UPDATE foo SET key = 2; <waiting ...>
invalid permutation detected
starting permutation: s1s s2l s1u s1r s1l s2c s1c
step s1s: SAVEPOINT f;
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1u: UPDATE foo SET key = 2; <waiting ...>
invalid permutation detected
starting permutation: s1s s2l s1u s1r s2c s1l s1c
step s1s: SAVEPOINT f;
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1u: UPDATE foo SET key = 2; <waiting ...>
invalid permutation detected
starting permutation: s1s s2l s1u s2c s1r s1l s1c
step s1s: SAVEPOINT f;
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1u: UPDATE foo SET key = 2; <waiting ...>
step s2c: COMMIT;
step s1u: <... completed>
error in steps s2c s1u: ERROR: could not serialize access due to concurrent update
step s1r: ROLLBACK TO f;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
starting permutation: s1s s2l s2c s1u s1r s1l s1c
step s1s: SAVEPOINT f;
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2c: COMMIT;
step s1u: UPDATE foo SET key = 2;
step s1r: ROLLBACK TO f;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
starting permutation: s2l s1s s1u s1r s1l s1c s2c
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2; <waiting ...>
invalid permutation detected
starting permutation: s2l s1s s1u s1r s1l s2c s1c
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2; <waiting ...>
invalid permutation detected
starting permutation: s2l s1s s1u s1r s2c s1l s1c
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2; <waiting ...>
invalid permutation detected
starting permutation: s2l s1s s1u s2c s1r s1l s1c
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2; <waiting ...>
step s2c: COMMIT;
step s1u: <... completed>
error in steps s2c s1u: ERROR: could not serialize access due to concurrent update
step s1r: ROLLBACK TO f;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
starting permutation: s2l s1s s2c s1u s1r s1l s1c
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1s: SAVEPOINT f;
step s2c: COMMIT;
step s1u: UPDATE foo SET key = 2;
step s1r: ROLLBACK TO f;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;
starting permutation: s2l s2c s1s s1u s1r s1l s1c
step s2l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2c: COMMIT;
step s1s: SAVEPOINT f;
step s1u: UPDATE foo SET key = 2;
step s1r: ROLLBACK TO f;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1c: COMMIT;

View File

@ -0,0 +1,76 @@
Parsed test spec with 2 sessions
starting permutation: s1l s1svp s1d s1r s2l s1c s2c
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1svp: SAVEPOINT f;
step s1d: SELECT * FROM foo FOR NO KEY UPDATE;
key value
1 1
step s1r: ROLLBACK TO f;
step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
step s1c: COMMIT;
step s2l: <... completed>
key value
1 1
step s2c: COMMIT;
starting permutation: s1l s1svp s1d s2l s1r s1c s2c
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1svp: SAVEPOINT f;
step s1d: SELECT * FROM foo FOR NO KEY UPDATE;
key value
1 1
step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
step s1r: ROLLBACK TO f;
step s1c: COMMIT;
step s2l: <... completed>
key value
1 1
step s2c: COMMIT;
starting permutation: s1l s1svp s1d s1r s2l2 s1c s2c
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1svp: SAVEPOINT f;
step s1d: SELECT * FROM foo FOR NO KEY UPDATE;
key value
1 1
step s1r: ROLLBACK TO f;
step s2l2: SELECT * FROM foo FOR NO KEY UPDATE;
key value
1 1
step s1c: COMMIT;
step s2c: COMMIT;
starting permutation: s1l s1svp s1d s2l2 s1r s1c s2c
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1svp: SAVEPOINT f;
step s1d: SELECT * FROM foo FOR NO KEY UPDATE;
key value
1 1
step s2l2: SELECT * FROM foo FOR NO KEY UPDATE; <waiting ...>
step s1r: ROLLBACK TO f;
step s2l2: <... completed>
key value
1 1
step s1c: COMMIT;
step s2c: COMMIT;

View File

@ -0,0 +1,243 @@
Parsed test spec with 2 sessions
starting permutation: s1l s1svp s1d s1r s1c s2l s2c
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1svp: SAVEPOINT f;
step s1d: DELETE FROM foo;
step s1r: ROLLBACK TO f;
step s1c: COMMIT;
step s2l: SELECT * FROM foo FOR UPDATE;
key value
1 1
step s2c: COMMIT;
starting permutation: s1l s1svp s1d s1r s2l s1c s2c
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1svp: SAVEPOINT f;
step s1d: DELETE FROM foo;
step s1r: ROLLBACK TO f;
step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
step s1c: COMMIT;
step s2l: <... completed>
key value
1 1
step s2c: COMMIT;
starting permutation: s1l s1svp s1d s1r s2l s2c s1c
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1svp: SAVEPOINT f;
step s1d: DELETE FROM foo;
step s1r: ROLLBACK TO f;
step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
invalid permutation detected
starting permutation: s1l s1svp s1d s2l s1r s1c s2c
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1svp: SAVEPOINT f;
step s1d: DELETE FROM foo;
step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
step s1r: ROLLBACK TO f;
step s1c: COMMIT;
step s2l: <... completed>
key value
1 1
step s2c: COMMIT;
starting permutation: s1l s1svp s1d s2l s1r s2c s1c
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1svp: SAVEPOINT f;
step s1d: DELETE FROM foo;
step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
step s1r: ROLLBACK TO f;
invalid permutation detected
starting permutation: s1l s1svp s1d s2l s2c s1r s1c
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1svp: SAVEPOINT f;
step s1d: DELETE FROM foo;
step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
invalid permutation detected
starting permutation: s1l s1svp s2l s1d s1r s1c s2c
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1svp: SAVEPOINT f;
step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
step s1d: DELETE FROM foo;
step s1r: ROLLBACK TO f;
step s1c: COMMIT;
step s2l: <... completed>
key value
1 1
step s2c: COMMIT;
starting permutation: s1l s1svp s2l s1d s1r s2c s1c
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1svp: SAVEPOINT f;
step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
step s1d: DELETE FROM foo;
step s1r: ROLLBACK TO f;
invalid permutation detected
starting permutation: s1l s1svp s2l s1d s2c s1r s1c
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1svp: SAVEPOINT f;
step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
step s1d: DELETE FROM foo;
invalid permutation detected
starting permutation: s1l s1svp s2l s2c s1d s1r s1c
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1svp: SAVEPOINT f;
step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
invalid permutation detected
starting permutation: s1l s2l s1svp s1d s1r s1c s2c
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
step s1svp: SAVEPOINT f;
step s1d: DELETE FROM foo;
step s1r: ROLLBACK TO f;
step s1c: COMMIT;
step s2l: <... completed>
key value
1 1
step s2c: COMMIT;
starting permutation: s1l s2l s1svp s1d s1r s2c s1c
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
step s1svp: SAVEPOINT f;
step s1d: DELETE FROM foo;
step s1r: ROLLBACK TO f;
invalid permutation detected
starting permutation: s1l s2l s1svp s1d s2c s1r s1c
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
step s1svp: SAVEPOINT f;
step s1d: DELETE FROM foo;
invalid permutation detected
starting permutation: s1l s2l s1svp s2c s1d s1r s1c
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
step s1svp: SAVEPOINT f;
invalid permutation detected
starting permutation: s1l s2l s2c s1svp s1d s1r s1c
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2l: SELECT * FROM foo FOR UPDATE; <waiting ...>
invalid permutation detected
starting permutation: s2l s1l s1svp s1d s1r s1c s2c
step s2l: SELECT * FROM foo FOR UPDATE;
key value
1 1
step s1l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
invalid permutation detected
starting permutation: s2l s1l s1svp s1d s1r s2c s1c
step s2l: SELECT * FROM foo FOR UPDATE;
key value
1 1
step s1l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
invalid permutation detected
starting permutation: s2l s1l s1svp s1d s2c s1r s1c
step s2l: SELECT * FROM foo FOR UPDATE;
key value
1 1
step s1l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
invalid permutation detected
starting permutation: s2l s1l s1svp s2c s1d s1r s1c
step s2l: SELECT * FROM foo FOR UPDATE;
key value
1 1
step s1l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
invalid permutation detected
starting permutation: s2l s1l s2c s1svp s1d s1r s1c
step s2l: SELECT * FROM foo FOR UPDATE;
key value
1 1
step s1l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
step s2c: COMMIT;
step s1l: <... completed>
key value
1 1
step s1svp: SAVEPOINT f;
step s1d: DELETE FROM foo;
step s1r: ROLLBACK TO f;
step s1c: COMMIT;
starting permutation: s2l s2c s1l s1svp s1d s1r s1c
step s2l: SELECT * FROM foo FOR UPDATE;
key value
1 1
step s2c: COMMIT;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s1svp: SAVEPOINT f;
step s1d: DELETE FROM foo;
step s1r: ROLLBACK TO f;
step s1c: COMMIT;

View File

@ -7,9 +7,8 @@ step upd: UPDATE foo SET b = 'Hello World';
starting permutation: ins upd com
step ins: INSERT INTO bar VALUES (42);
step upd: UPDATE foo SET b = 'Hello World'; <waiting ...>
step upd: UPDATE foo SET b = 'Hello World';
step com: COMMIT;
step upd: <... completed>
starting permutation: upd ins com
step upd: UPDATE foo SET b = 'Hello World';

View File

@ -11,57 +11,151 @@ step s2c: COMMIT;
starting permutation: s1i s1u s2i s1c s2u s2c
step s1i: INSERT INTO child VALUES (1, 1);
step s1u: UPDATE parent SET aux = 'bar';
step s2i: INSERT INTO child VALUES (2, 1); <waiting ...>
step s2i: INSERT INTO child VALUES (2, 1);
step s1c: COMMIT;
step s2u: UPDATE parent SET aux = 'baz';
step s2c: COMMIT;
starting permutation: s1i s1u s2i s2u s1c s2c
step s1i: INSERT INTO child VALUES (1, 1);
step s1u: UPDATE parent SET aux = 'bar';
step s2i: INSERT INTO child VALUES (2, 1);
step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
step s1c: COMMIT;
step s2u: <... completed>
step s2c: COMMIT;
starting permutation: s1i s1u s2i s2u s2c s1c
step s1i: INSERT INTO child VALUES (1, 1);
step s1u: UPDATE parent SET aux = 'bar';
step s2i: INSERT INTO child VALUES (2, 1);
step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
invalid permutation detected
starting permutation: s1i s2i s1u s1c s2u s2c
step s1i: INSERT INTO child VALUES (1, 1);
step s2i: INSERT INTO child VALUES (2, 1);
step s1u: UPDATE parent SET aux = 'bar';
step s1c: COMMIT;
step s2i: <... completed>
step s2u: UPDATE parent SET aux = 'baz';
step s2c: COMMIT;
starting permutation: s1i s2i s1u s2u s1c s2c
step s1i: INSERT INTO child VALUES (1, 1);
step s2i: INSERT INTO child VALUES (2, 1);
step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
step s2u: UPDATE parent SET aux = 'baz';
step s1u: <... completed>
error in steps s2u s1u: ERROR: deadlock detected
step s1u: UPDATE parent SET aux = 'bar';
step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
step s1c: COMMIT;
step s2u: <... completed>
step s2c: COMMIT;
starting permutation: s1i s2i s1u s2u s2c s1c
step s1i: INSERT INTO child VALUES (1, 1);
step s2i: INSERT INTO child VALUES (2, 1);
step s1u: UPDATE parent SET aux = 'bar';
step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
invalid permutation detected
starting permutation: s1i s2i s2u s1u s1c s2c
step s1i: INSERT INTO child VALUES (1, 1);
step s2i: INSERT INTO child VALUES (2, 1);
step s2u: UPDATE parent SET aux = 'baz';
step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
invalid permutation detected
starting permutation: s1i s2i s2u s1u s2c s1c
step s1i: INSERT INTO child VALUES (1, 1);
step s2i: INSERT INTO child VALUES (2, 1);
step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
step s1u: UPDATE parent SET aux = 'bar';
step s2u: <... completed>
error in steps s1u s2u: ERROR: deadlock detected
step s2u: UPDATE parent SET aux = 'baz';
step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
step s2c: COMMIT;
step s1u: <... completed>
step s1c: COMMIT;
starting permutation: s1i s2i s2u s2c s1u s1c
step s1i: INSERT INTO child VALUES (1, 1);
step s2i: INSERT INTO child VALUES (2, 1);
step s2u: UPDATE parent SET aux = 'baz';
step s2c: COMMIT;
step s1u: UPDATE parent SET aux = 'bar';
step s1c: COMMIT;
starting permutation: s2i s1i s1u s1c s2u s2c
step s2i: INSERT INTO child VALUES (2, 1);
step s1i: INSERT INTO child VALUES (1, 1);
step s1u: UPDATE parent SET aux = 'bar';
step s1c: COMMIT;
step s2u: UPDATE parent SET aux = 'baz';
step s2c: COMMIT;
starting permutation: s2i s1i s1u s2u s1c s2c
step s2i: INSERT INTO child VALUES (2, 1);
step s1i: INSERT INTO child VALUES (1, 1);
step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
step s2u: UPDATE parent SET aux = 'baz';
step s1u: <... completed>
error in steps s2u s1u: ERROR: deadlock detected
step s1u: UPDATE parent SET aux = 'bar';
step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
step s1c: COMMIT;
step s2u: <... completed>
step s2c: COMMIT;
starting permutation: s2i s1i s1u s2u s2c s1c
step s2i: INSERT INTO child VALUES (2, 1);
step s1i: INSERT INTO child VALUES (1, 1);
step s1u: UPDATE parent SET aux = 'bar';
step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
invalid permutation detected
starting permutation: s2i s1i s2u s1u s1c s2c
step s2i: INSERT INTO child VALUES (2, 1);
step s1i: INSERT INTO child VALUES (1, 1);
step s2u: UPDATE parent SET aux = 'baz';
step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
invalid permutation detected
starting permutation: s2i s1i s2u s1u s2c s1c
step s2i: INSERT INTO child VALUES (2, 1);
step s1i: INSERT INTO child VALUES (1, 1);
step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
step s1u: UPDATE parent SET aux = 'bar';
step s2u: <... completed>
error in steps s1u s2u: ERROR: deadlock detected
step s2u: UPDATE parent SET aux = 'baz';
step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
step s2c: COMMIT;
step s1u: <... completed>
step s1c: COMMIT;
starting permutation: s2i s1i s2u s2c s1u s1c
step s2i: INSERT INTO child VALUES (2, 1);
step s1i: INSERT INTO child VALUES (1, 1);
step s2u: UPDATE parent SET aux = 'baz';
step s2c: COMMIT;
step s1u: UPDATE parent SET aux = 'bar';
step s1c: COMMIT;
starting permutation: s2i s2u s1i s1u s1c s2c
step s2i: INSERT INTO child VALUES (2, 1);
step s2u: UPDATE parent SET aux = 'baz';
step s1i: INSERT INTO child VALUES (1, 1);
step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
invalid permutation detected
starting permutation: s2i s2u s1i s1u s2c s1c
step s2i: INSERT INTO child VALUES (2, 1);
step s2u: UPDATE parent SET aux = 'baz';
step s1i: INSERT INTO child VALUES (1, 1);
step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
step s2c: COMMIT;
step s1u: <... completed>
step s1c: COMMIT;
starting permutation: s2i s2u s1i s2c s1u s1c
step s2i: INSERT INTO child VALUES (2, 1);
step s2u: UPDATE parent SET aux = 'baz';
step s1i: INSERT INTO child VALUES (1, 1); <waiting ...>
step s1i: INSERT INTO child VALUES (1, 1);
step s2c: COMMIT;
step s1i: <... completed>
step s1u: UPDATE parent SET aux = 'bar';
step s1c: COMMIT;
starting permutation: s2i s2u s2c s1i s1u s1c
step s2i: INSERT INTO child VALUES (2, 1);
step s2u: UPDATE parent SET aux = 'baz';
step s2c: COMMIT;
step s1i: INSERT INTO child VALUES (1, 1);
step s1u: UPDATE parent SET aux = 'bar';
step s1c: COMMIT;

View File

@ -17,91 +17,138 @@ step s2u1: <... completed>
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2c: COMMIT;
starting permutation: s1u1 s1u2 s2u1 s2u2 s1c s2c
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
invalid permutation detected
starting permutation: s1u1 s1u2 s2u1 s2u2 s2c s1c
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
invalid permutation detected
starting permutation: s1u1 s2u1 s1u2 s1c s2u2 s2c
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
invalid permutation detected
starting permutation: s1u1 s2u1 s1u2 s2u2 s1c s2c
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: <... completed>
error in steps s2u2 s1u2: ERROR: deadlock detected
step s1c: COMMIT;
step s2c: COMMIT;
invalid permutation detected
starting permutation: s1u1 s2u1 s1u2 s2u2 s2c s1c
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: <... completed>
error in steps s2u2 s1u2: ERROR: deadlock detected
step s2c: COMMIT;
step s1u2: <... completed>
step s1c: COMMIT;
starting permutation: s1u1 s2u1 s2u2 s1u2 s1c s2c
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: <... completed>
error in steps s1u2 s2u2: ERROR: deadlock detected
step s1c: COMMIT;
step s2c: COMMIT;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
invalid permutation detected
starting permutation: s1u1 s2u1 s2u2 s1u2 s2c s1c
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: <... completed>
error in steps s1u2 s2u2: ERROR: deadlock detected
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s2c: COMMIT;
step s1u2: <... completed>
step s1c: COMMIT;
starting permutation: s1u1 s2u1 s2u2 s2c s1u2 s1c
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2c: COMMIT;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1c: COMMIT;
starting permutation: s2u1 s1u1 s1u2 s1c s2u2 s2c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
invalid permutation detected
starting permutation: s2u1 s1u1 s1u2 s2u2 s1c s2c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: <... completed>
error in steps s2u2 s1u2: ERROR: deadlock detected
step s1c: COMMIT;
step s2c: COMMIT;
invalid permutation detected
starting permutation: s2u1 s1u1 s1u2 s2u2 s2c s1c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: <... completed>
error in steps s2u2 s1u2: ERROR: deadlock detected
step s2c: COMMIT;
step s1u2: <... completed>
step s1c: COMMIT;
starting permutation: s2u1 s1u1 s2u2 s1u2 s1c s2c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: <... completed>
error in steps s1u2 s2u2: ERROR: deadlock detected
step s1c: COMMIT;
step s2c: COMMIT;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
invalid permutation detected
starting permutation: s2u1 s1u1 s2u2 s1u2 s2c s1c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: <... completed>
error in steps s1u2 s2u2: ERROR: deadlock detected
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s2c: COMMIT;
step s1u2: <... completed>
step s1c: COMMIT;
starting permutation: s2u1 s1u1 s2u2 s2c s1u2 s1c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2c: COMMIT;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1c: COMMIT;
starting permutation: s2u1 s2u2 s1u1 s1u2 s1c s2c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
invalid permutation detected
starting permutation: s2u1 s2u2 s1u1 s1u2 s2c s1c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s2c: COMMIT;
step s1u2: <... completed>
step s1c: COMMIT;
starting permutation: s2u1 s2u2 s1u1 s2c s1u2 s1c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1; <waiting ...>
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2c: COMMIT;
step s1u1: <... completed>
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1c: COMMIT;
starting permutation: s2u1 s2u2 s2c s1u1 s1u2 s1c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2c: COMMIT;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1c: COMMIT;

View File

@ -19,92 +19,87 @@ step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
ERROR: current transaction is aborted, commands ignored until end of transaction block
step s2c: COMMIT;
starting permutation: s1u1 s2u1 s1u2 s2u2 s1c s2c
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: <... completed>
error in steps s2u2 s1u2: ERROR: deadlock detected
step s1c: COMMIT;
step s2c: COMMIT;
starting permutation: s1u1 s2u1 s1u2 s2u2 s2c s1c
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2c: COMMIT;
step s1u2: <... completed>
error in steps s2u2 s1u2: ERROR: deadlock detected
step s2c: COMMIT;
error in steps s2c s1u2: ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;
starting permutation: s1u1 s2u1 s2u2 s1u2 s1c s2c
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: <... completed>
error in steps s1u2 s2u2: ERROR: deadlock detected
step s1c: COMMIT;
step s2c: COMMIT;
starting permutation: s1u1 s2u1 s2u2 s1u2 s2c s1c
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: <... completed>
error in steps s1u2 s2u2: ERROR: deadlock detected
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s2c: COMMIT;
step s1u2: <... completed>
error in steps s2c s1u2: ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;
starting permutation: s2u1 s1u1 s1u2 s2u2 s1c s2c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
starting permutation: s1u1 s2u1 s2u2 s2c s1u2 s1c
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: <... completed>
error in steps s2u2 s1u2: ERROR: deadlock detected
step s1c: COMMIT;
step s2c: COMMIT;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
ERROR: could not serialize access due to read/write dependencies among transactions
step s1c: COMMIT;
starting permutation: s2u1 s1u1 s1u2 s2u2 s2c s1c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2c: COMMIT;
step s1u2: <... completed>
error in steps s2u2 s1u2: ERROR: deadlock detected
step s2c: COMMIT;
error in steps s2c s1u2: ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;
starting permutation: s2u1 s1u1 s2u2 s1u2 s1c s2c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: <... completed>
error in steps s1u2 s2u2: ERROR: deadlock detected
step s1c: COMMIT;
step s2c: COMMIT;
starting permutation: s2u1 s1u1 s2u2 s1u2 s2c s1c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: <... completed>
error in steps s1u2 s2u2: ERROR: deadlock detected
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s2c: COMMIT;
step s1u2: <... completed>
error in steps s2c s1u2: ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;
starting permutation: s2u1 s1u1 s2u2 s2c s1u2 s1c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2c: COMMIT;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
ERROR: could not serialize access due to read/write dependencies among transactions
step s1c: COMMIT;
starting permutation: s2u1 s2u2 s1u1 s1u2 s2c s1c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s2c: COMMIT;
step s1u2: <... completed>
error in steps s2c s1u2: ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;
starting permutation: s2u1 s2u2 s1u1 s2c s1u2 s1c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1; <waiting ...>
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2c: COMMIT;
step s1u1: <... completed>
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
ERROR: could not serialize access due to read/write dependencies among transactions
step s1c: COMMIT;
starting permutation: s2u1 s2u2 s2c s1u1 s1u2 s1c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2c: COMMIT;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1c: COMMIT;

View File

@ -0,0 +1,105 @@
Parsed test spec with 2 sessions
starting permutation: s1u1 s1u2 s1c s2u1 s2u2 s2c
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1c: COMMIT;
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2c: COMMIT;
starting permutation: s1u1 s1u2 s2u1 s1c s2u2 s2c
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s1c: COMMIT;
step s2u1: <... completed>
error in steps s1c s2u1: ERROR: could not serialize access due to concurrent update
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
ERROR: current transaction is aborted, commands ignored until end of transaction block
step s2c: COMMIT;
starting permutation: s1u1 s2u1 s1u2 s2u2 s2c s1c
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2c: COMMIT;
step s1u2: <... completed>
error in steps s2c s1u2: ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;
starting permutation: s1u1 s2u1 s2u2 s1u2 s2c s1c
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s2c: COMMIT;
step s1u2: <... completed>
error in steps s2c s1u2: ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;
starting permutation: s1u1 s2u1 s2u2 s2c s1u2 s1c
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2c: COMMIT;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;
starting permutation: s2u1 s1u1 s1u2 s2u2 s2c s1c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2c: COMMIT;
step s1u2: <... completed>
error in steps s2c s1u2: ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;
starting permutation: s2u1 s1u1 s2u2 s1u2 s2c s1c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s2c: COMMIT;
step s1u2: <... completed>
error in steps s2c s1u2: ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;
starting permutation: s2u1 s1u1 s2u2 s2c s1u2 s1c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2c: COMMIT;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;
starting permutation: s2u1 s2u2 s1u1 s1u2 s2c s1c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2; <waiting ...>
step s2c: COMMIT;
step s1u2: <... completed>
error in steps s2c s1u2: ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;
starting permutation: s2u1 s2u2 s1u1 s2c s1u2 s1c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s2c: COMMIT;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;
starting permutation: s2u1 s2u2 s2c s1u1 s1u2 s1c
step s2u1: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s2c: COMMIT;
step s1u1: UPDATE A SET Col1 = 1 WHERE AID = 1;
step s1u2: UPDATE B SET Col2 = 1 WHERE BID = 2;
step s1c: COMMIT;

View File

@ -11,61 +11,57 @@ step s2c: COMMIT;
starting permutation: s1i s1u s2i s1c s2u s2c
step s1i: INSERT INTO child VALUES (1, 1);
step s1u: UPDATE parent SET aux = 'bar';
step s2i: INSERT INTO child VALUES (2, 1); <waiting ...>
step s2i: INSERT INTO child VALUES (2, 1);
step s1c: COMMIT;
step s2i: <... completed>
error in steps s1c s2i: ERROR: could not serialize access due to concurrent update
step s2u: UPDATE parent SET aux = 'baz';
ERROR: current transaction is aborted, commands ignored until end of transaction block
ERROR: could not serialize access due to read/write dependencies among transactions
step s2c: COMMIT;
starting permutation: s1i s2i s1u s2u s1c s2c
step s1i: INSERT INTO child VALUES (1, 1);
step s2i: INSERT INTO child VALUES (2, 1);
step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
step s2u: UPDATE parent SET aux = 'baz';
step s1u: <... completed>
error in steps s2u s1u: ERROR: deadlock detected
step s1u: UPDATE parent SET aux = 'bar';
step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
step s1c: COMMIT;
step s2u: <... completed>
error in steps s1c s2u: ERROR: could not serialize access due to concurrent update
step s2c: COMMIT;
starting permutation: s1i s2i s2u s1u s2c s1c
step s1i: INSERT INTO child VALUES (1, 1);
step s2i: INSERT INTO child VALUES (2, 1);
step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
step s1u: UPDATE parent SET aux = 'bar';
step s2u: <... completed>
error in steps s1u s2u: ERROR: deadlock detected
step s2u: UPDATE parent SET aux = 'baz';
step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
step s2c: COMMIT;
step s1u: <... completed>
error in steps s2c s1u: ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;
starting permutation: s2i s1i s1u s2u s1c s2c
step s2i: INSERT INTO child VALUES (2, 1);
step s1i: INSERT INTO child VALUES (1, 1);
step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
step s2u: UPDATE parent SET aux = 'baz';
step s1u: <... completed>
error in steps s2u s1u: ERROR: deadlock detected
step s1u: UPDATE parent SET aux = 'bar';
step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
step s1c: COMMIT;
step s2u: <... completed>
error in steps s1c s2u: ERROR: could not serialize access due to concurrent update
step s2c: COMMIT;
starting permutation: s2i s1i s2u s1u s2c s1c
step s2i: INSERT INTO child VALUES (2, 1);
step s1i: INSERT INTO child VALUES (1, 1);
step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
step s1u: UPDATE parent SET aux = 'bar';
step s2u: <... completed>
error in steps s1u s2u: ERROR: deadlock detected
step s2u: UPDATE parent SET aux = 'baz';
step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
step s2c: COMMIT;
step s1u: <... completed>
error in steps s2c s1u: ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;
starting permutation: s2i s2u s1i s2c s1u s1c
step s2i: INSERT INTO child VALUES (2, 1);
step s2u: UPDATE parent SET aux = 'baz';
step s1i: INSERT INTO child VALUES (1, 1); <waiting ...>
step s1i: INSERT INTO child VALUES (1, 1);
step s2c: COMMIT;
step s1i: <... completed>
error in steps s2c s1i: ERROR: could not serialize access due to concurrent update
step s1u: UPDATE parent SET aux = 'bar';
ERROR: current transaction is aborted, commands ignored until end of transaction block
ERROR: could not serialize access due to read/write dependencies among transactions
step s1c: COMMIT;

View File

@ -0,0 +1,67 @@
Parsed test spec with 2 sessions
starting permutation: s1i s1u s1c s2i s2u s2c
step s1i: INSERT INTO child VALUES (1, 1);
step s1u: UPDATE parent SET aux = 'bar';
step s1c: COMMIT;
step s2i: INSERT INTO child VALUES (2, 1);
step s2u: UPDATE parent SET aux = 'baz';
step s2c: COMMIT;
starting permutation: s1i s1u s2i s1c s2u s2c
step s1i: INSERT INTO child VALUES (1, 1);
step s1u: UPDATE parent SET aux = 'bar';
step s2i: INSERT INTO child VALUES (2, 1);
step s1c: COMMIT;
step s2u: UPDATE parent SET aux = 'baz';
ERROR: could not serialize access due to concurrent update
step s2c: COMMIT;
starting permutation: s1i s2i s1u s2u s1c s2c
step s1i: INSERT INTO child VALUES (1, 1);
step s2i: INSERT INTO child VALUES (2, 1);
step s1u: UPDATE parent SET aux = 'bar';
step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
step s1c: COMMIT;
step s2u: <... completed>
error in steps s1c s2u: ERROR: could not serialize access due to concurrent update
step s2c: COMMIT;
starting permutation: s1i s2i s2u s1u s2c s1c
step s1i: INSERT INTO child VALUES (1, 1);
step s2i: INSERT INTO child VALUES (2, 1);
step s2u: UPDATE parent SET aux = 'baz';
step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
step s2c: COMMIT;
step s1u: <... completed>
error in steps s2c s1u: ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;
starting permutation: s2i s1i s1u s2u s1c s2c
step s2i: INSERT INTO child VALUES (2, 1);
step s1i: INSERT INTO child VALUES (1, 1);
step s1u: UPDATE parent SET aux = 'bar';
step s2u: UPDATE parent SET aux = 'baz'; <waiting ...>
step s1c: COMMIT;
step s2u: <... completed>
error in steps s1c s2u: ERROR: could not serialize access due to concurrent update
step s2c: COMMIT;
starting permutation: s2i s1i s2u s1u s2c s1c
step s2i: INSERT INTO child VALUES (2, 1);
step s1i: INSERT INTO child VALUES (1, 1);
step s2u: UPDATE parent SET aux = 'baz';
step s1u: UPDATE parent SET aux = 'bar'; <waiting ...>
step s2c: COMMIT;
step s1u: <... completed>
error in steps s2c s1u: ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;
starting permutation: s2i s2u s1i s2c s1u s1c
step s2i: INSERT INTO child VALUES (2, 1);
step s2u: UPDATE parent SET aux = 'baz';
step s1i: INSERT INTO child VALUES (1, 1);
step s2c: COMMIT;
step s1u: UPDATE parent SET aux = 'bar';
ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;

View File

@ -0,0 +1,41 @@
Parsed test spec with 2 sessions
starting permutation: s1d s1c s2i s2c
step s1d: DELETE FROM A WHERE AID = 1;
step s1c: COMMIT;
step s2i: INSERT INTO B (BID,AID,Col2) VALUES (2,1,0);
ERROR: insert or update on table "b" violates foreign key constraint "b_aid_fkey"
step s2c: COMMIT;
starting permutation: s1d s2i s1c s2c
step s1d: DELETE FROM A WHERE AID = 1;
step s2i: INSERT INTO B (BID,AID,Col2) VALUES (2,1,0); <waiting ...>
step s1c: COMMIT;
step s2i: <... completed>
error in steps s1c s2i: ERROR: insert or update on table "b" violates foreign key constraint "b_aid_fkey"
step s2c: COMMIT;
starting permutation: s1d s2i s2c s1c
step s1d: DELETE FROM A WHERE AID = 1;
step s2i: INSERT INTO B (BID,AID,Col2) VALUES (2,1,0); <waiting ...>
invalid permutation detected
starting permutation: s2i s1d s1c s2c
step s2i: INSERT INTO B (BID,AID,Col2) VALUES (2,1,0);
step s1d: DELETE FROM A WHERE AID = 1; <waiting ...>
invalid permutation detected
starting permutation: s2i s1d s2c s1c
step s2i: INSERT INTO B (BID,AID,Col2) VALUES (2,1,0);
step s1d: DELETE FROM A WHERE AID = 1; <waiting ...>
step s2c: COMMIT;
step s1d: <... completed>
error in steps s2c s1d: ERROR: update or delete on table "a" violates foreign key constraint "b_aid_fkey" on table "b"
step s1c: COMMIT;
starting permutation: s2i s2c s1d s1c
step s2i: INSERT INTO B (BID,AID,Col2) VALUES (2,1,0);
step s2c: COMMIT;
step s1d: DELETE FROM A WHERE AID = 1;
ERROR: update or delete on table "a" violates foreign key constraint "b_aid_fkey" on table "b"
step s1c: COMMIT;

View File

@ -0,0 +1,65 @@
Parsed test spec with 2 sessions
starting permutation: s1b s2b s1s s2u s2d s1l s2c s1c
step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s2b: BEGIN;
step s1s: SELECT * FROM foo;
key value
1 1
step s2u: UPDATE foo SET value = 2 WHERE key = 1;
step s2d: DELETE FROM foo;
step s1l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
step s2c: COMMIT;
step s1l: <... completed>
error in steps s2c s1l: ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;
starting permutation: s1b s2b s1s s2u s2d s1l s2r s1c
step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s2b: BEGIN;
step s1s: SELECT * FROM foo;
key value
1 1
step s2u: UPDATE foo SET value = 2 WHERE key = 1;
step s2d: DELETE FROM foo;
step s1l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
step s2r: ROLLBACK;
step s1l: <... completed>
key value
1 1
step s1c: COMMIT;
starting permutation: s1b s2b s1s s2u s2u2 s1l s2c s1c
step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s2b: BEGIN;
step s1s: SELECT * FROM foo;
key value
1 1
step s2u: UPDATE foo SET value = 2 WHERE key = 1;
step s2u2: UPDATE foo SET key = 2 WHERE key = 1;
step s1l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
step s2c: COMMIT;
step s1l: <... completed>
error in steps s2c s1l: ERROR: could not serialize access due to concurrent update
step s1c: COMMIT;
starting permutation: s1b s2b s1s s2u s2u2 s1l s2r s1c
step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s2b: BEGIN;
step s1s: SELECT * FROM foo;
key value
1 1
step s2u: UPDATE foo SET value = 2 WHERE key = 1;
step s2u2: UPDATE foo SET key = 2 WHERE key = 1;
step s1l: SELECT * FROM foo FOR KEY SHARE; <waiting ...>
step s2r: ROLLBACK;
step s1l: <... completed>
key value
1 1
step s1c: COMMIT;

View File

@ -0,0 +1,18 @@
Parsed test spec with 2 sessions
starting permutation: s1b s2b s1s s2u s1l s2c s2d s1c
step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ;
step s2b: BEGIN;
step s1s: SELECT * FROM foo;
key value
1 1
step s2u: UPDATE foo SET value = 2 WHERE key = 1;
step s1l: SELECT * FROM foo FOR KEY SHARE;
key value
1 1
step s2c: COMMIT;
step s2d: DELETE FROM foo WHERE key = 1; <waiting ...>
step s1c: COMMIT;
step s2d: <... completed>

View File

@ -0,0 +1,24 @@
Parsed test spec with 3 sessions
starting permutation: s1lock s2lock s1svpt s3lock s1lock2 s2c s1c s3c
step s1lock: SELECT * FROM justthis FOR SHARE;
value
1
step s2lock: SELECT * FROM justthis FOR SHARE;
value
1
step s1svpt: SAVEPOINT foo;
step s3lock: SELECT * FROM justthis FOR UPDATE; <waiting ...>
step s1lock2: SELECT * FROM justthis FOR SHARE;
value
1
step s2c: COMMIT;
step s1c: COMMIT;
step s3lock: <... completed>
value
1
step s3c: COMMIT;

View File

@ -14,4 +14,9 @@ test: fk-contention
test: fk-deadlock
test: fk-deadlock2
test: eval-plan-qual
test: lock-update-delete
test: lock-update-traversal
test: delete-abort-savept
test: delete-abort-savept-2
test: aborted-keyrevoke
test: drop-index-concurrently-1

View File

@ -564,6 +564,7 @@ run_permutation(TestSpec * testspec, int nsteps, Step ** steps)
* but it can only be unblocked by running steps from other
* sessions.
*/
fflush(stdout);
fprintf(stderr, "invalid permutation detected\n");
/* Cancel the waiting statement from this session. */

View File

@ -0,0 +1,31 @@
# When a tuple that has been updated is locked, the locking command
# should traverse the update chain; thus, a DELETE should not be able
# to proceed until the lock has been released.
setup
{
CREATE TABLE foo (
key int PRIMARY KEY,
value int
);
INSERT INTO foo VALUES (1, 1);
}
teardown
{
DROP TABLE foo;
}
session "s1"
setup { BEGIN; }
step "s1s" { SAVEPOINT f; }
step "s1u" { UPDATE foo SET key = 2; } # obtain KEY REVOKE
step "s1r" { ROLLBACK TO f; } # lose KEY REVOKE
step "s1l" { SELECT * FROM foo FOR KEY SHARE; }
step "s1c" { COMMIT; }
session "s2"
setup { BEGIN; }
step "s2l" { SELECT * FROM foo FOR KEY SHARE; }
step "s2c" { COMMIT; }

View File

@ -0,0 +1,34 @@
# A funkier version of delete-abort-savept
setup
{
CREATE TABLE foo (
key INT PRIMARY KEY,
value INT
);
INSERT INTO foo VALUES (1, 1);
}
teardown
{
DROP TABLE foo;
}
session "s1"
setup { BEGIN; }
step "s1l" { SELECT * FROM foo FOR KEY SHARE; }
step "s1svp" { SAVEPOINT f; }
step "s1d" { SELECT * FROM foo FOR NO KEY UPDATE; }
step "s1r" { ROLLBACK TO f; }
step "s1c" { COMMIT; }
session "s2"
setup { BEGIN; }
step "s2l" { SELECT * FROM foo FOR UPDATE; }
step "s2l2" { SELECT * FROM foo FOR NO KEY UPDATE; }
step "s2c" { COMMIT; }
permutation "s1l" "s1svp" "s1d" "s1r" "s2l" "s1c" "s2c"
permutation "s1l" "s1svp" "s1d" "s2l" "s1r" "s1c" "s2c"
permutation "s1l" "s1svp" "s1d" "s1r" "s2l2" "s1c" "s2c"
permutation "s1l" "s1svp" "s1d" "s2l2" "s1r" "s1c" "s2c"

Some files were not shown because too many files have changed in this diff Show More