summaryrefslogtreecommitdiffstats
path: root/contrib/pgrowlocks
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 13:44:03 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 13:44:03 +0000
commit293913568e6a7a86fd1479e1cff8e2ecb58d6568 (patch)
treefc3b469a3ec5ab71b36ea97cc7aaddb838423a0c /contrib/pgrowlocks
parentInitial commit. (diff)
downloadpostgresql-16-293913568e6a7a86fd1479e1cff8e2ecb58d6568.tar.xz
postgresql-16-293913568e6a7a86fd1479e1cff8e2ecb58d6568.zip
Adding upstream version 16.2.upstream/16.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'contrib/pgrowlocks')
-rw-r--r--contrib/pgrowlocks/.gitignore6
-rw-r--r--contrib/pgrowlocks/Makefile24
-rw-r--r--contrib/pgrowlocks/expected/pgrowlocks.out233
-rw-r--r--contrib/pgrowlocks/meson.build37
-rw-r--r--contrib/pgrowlocks/pgrowlocks--1.0--1.1.sql17
-rw-r--r--contrib/pgrowlocks/pgrowlocks--1.1--1.2.sql6
-rw-r--r--contrib/pgrowlocks/pgrowlocks--1.2.sql15
-rw-r--r--contrib/pgrowlocks/pgrowlocks.c276
-rw-r--r--contrib/pgrowlocks/pgrowlocks.control5
-rw-r--r--contrib/pgrowlocks/specs/pgrowlocks.spec39
10 files changed, 658 insertions, 0 deletions
diff --git a/contrib/pgrowlocks/.gitignore b/contrib/pgrowlocks/.gitignore
new file mode 100644
index 0000000..b4903eb
--- /dev/null
+++ b/contrib/pgrowlocks/.gitignore
@@ -0,0 +1,6 @@
+# Generated subdirectories
+/log/
+/results/
+/output_iso/
+/tmp_check/
+/tmp_check_iso/
diff --git a/contrib/pgrowlocks/Makefile b/contrib/pgrowlocks/Makefile
new file mode 100644
index 0000000..e808064
--- /dev/null
+++ b/contrib/pgrowlocks/Makefile
@@ -0,0 +1,24 @@
+# contrib/pgrowlocks/Makefile
+
+MODULE_big = pgrowlocks
+OBJS = \
+ $(WIN32RES) \
+ pgrowlocks.o
+
+EXTENSION = pgrowlocks
+DATA = pgrowlocks--1.2.sql pgrowlocks--1.1--1.2.sql pgrowlocks--1.0--1.1.sql
+PGFILEDESC = "pgrowlocks - display row locking information"
+
+ISOLATION = pgrowlocks
+ISOLATION_OPTS = --load-extension=pgrowlocks
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/pgrowlocks
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/pgrowlocks/expected/pgrowlocks.out b/contrib/pgrowlocks/expected/pgrowlocks.out
new file mode 100644
index 0000000..7254672
--- /dev/null
+++ b/contrib/pgrowlocks/expected/pgrowlocks.out
@@ -0,0 +1,233 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_begin s1_tuplock1 s2_rowlocks s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE;
+a|b
+-+-
+1|2
+3|4
+(2 rows)
+
+step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict');
+locked_row|multi|modes
+----------+-----+-----------------
+(0,1) |f |{"For Key Share"}
+(0,2) |f |{"For Key Share"}
+(2 rows)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_tuplock2 s2_rowlocks s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock2: SELECT * FROM multixact_conflict FOR SHARE;
+a|b
+-+-
+1|2
+3|4
+(2 rows)
+
+step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict');
+locked_row|multi|modes
+----------+-----+-------------
+(0,1) |f |{"For Share"}
+(0,2) |f |{"For Share"}
+(2 rows)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_tuplock3 s2_rowlocks s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE;
+a|b
+-+-
+1|2
+3|4
+(2 rows)
+
+step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict');
+locked_row|multi|modes
+----------+-----+---------------------
+(0,1) |f |{"For No Key Update"}
+(0,2) |f |{"For No Key Update"}
+(2 rows)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_tuplock4 s2_rowlocks s1_commit
+step s1_begin: BEGIN;
+step s1_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE;
+a|b
+-+-
+1|2
+3|4
+(2 rows)
+
+step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict');
+locked_row|multi|modes
+----------+-----+--------------
+(0,1) |f |{"For Update"}
+(0,2) |f |{"For Update"}
+(2 rows)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_updatea s2_rowlocks s1_commit
+step s1_begin: BEGIN;
+step s1_updatea: UPDATE multixact_conflict SET a = 10 WHERE a = 1;
+step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict');
+locked_row|multi|modes
+----------+-----+--------
+(0,1) |f |{Update}
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_updateb s2_rowlocks s1_commit
+step s1_begin: BEGIN;
+step s1_updateb: UPDATE multixact_conflict SET b = 11 WHERE b = 4;
+step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict');
+locked_row|multi|modes
+----------+-----+-----------------
+(0,2) |f |{"No Key Update"}
+(1 row)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock1 s2_rowlocks s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT s;
+a|b
+-+-
+1|2
+3|4
+(2 rows)
+
+step s1_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE;
+a|b
+-+-
+1|2
+3|4
+(2 rows)
+
+step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict');
+locked_row|multi|modes
+----------+-----+-----------------
+(0,1) |f |{"For Key Share"}
+(0,2) |f |{"For Key Share"}
+(2 rows)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock2 s2_rowlocks s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT s;
+a|b
+-+-
+1|2
+3|4
+(2 rows)
+
+step s1_tuplock2: SELECT * FROM multixact_conflict FOR SHARE;
+a|b
+-+-
+1|2
+3|4
+(2 rows)
+
+step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict');
+locked_row|multi|modes
+----------+-----+-------------------
+(0,1) |t |{"Key Share",Share}
+(0,2) |t |{"Key Share",Share}
+(2 rows)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock3 s2_rowlocks s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT s;
+a|b
+-+-
+1|2
+3|4
+(2 rows)
+
+step s1_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE;
+a|b
+-+-
+1|2
+3|4
+(2 rows)
+
+step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict');
+locked_row|multi|modes
+----------+-----+---------------------------------
+(0,1) |t |{"Key Share","For No Key Update"}
+(0,2) |t |{"Key Share","For No Key Update"}
+(2 rows)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_lcksvpt s1_tuplock4 s2_rowlocks s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT s;
+a|b
+-+-
+1|2
+3|4
+(2 rows)
+
+step s1_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE;
+a|b
+-+-
+1|2
+3|4
+(2 rows)
+
+step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict');
+locked_row|multi|modes
+----------+-----+--------------------------
+(0,1) |t |{"Key Share","For Update"}
+(0,2) |t |{"Key Share","For Update"}
+(2 rows)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_lcksvpt s1_updatea s2_rowlocks s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT s;
+a|b
+-+-
+1|2
+3|4
+(2 rows)
+
+step s1_updatea: UPDATE multixact_conflict SET a = 10 WHERE a = 1;
+step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict');
+locked_row|multi|modes
+----------+-----+--------------------
+(0,1) |t |{"Key Share",Update}
+(0,2) |f |{"For Key Share"}
+(2 rows)
+
+step s1_commit: COMMIT;
+
+starting permutation: s1_begin s1_lcksvpt s1_updateb s2_rowlocks s1_commit
+step s1_begin: BEGIN;
+step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT s;
+a|b
+-+-
+1|2
+3|4
+(2 rows)
+
+step s1_updateb: UPDATE multixact_conflict SET b = 11 WHERE b = 4;
+step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict');
+locked_row|multi|modes
+----------+-----+-----------------------------
+(0,1) |f |{"For Key Share"}
+(0,2) |t |{"Key Share","No Key Update"}
+(2 rows)
+
+step s1_commit: COMMIT;
diff --git a/contrib/pgrowlocks/meson.build b/contrib/pgrowlocks/meson.build
new file mode 100644
index 0000000..48bce00
--- /dev/null
+++ b/contrib/pgrowlocks/meson.build
@@ -0,0 +1,37 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+pgrowlocks_sources = files(
+ 'pgrowlocks.c',
+)
+
+if host_system == 'windows'
+ pgrowlocks_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'pgrowlocks',
+ '--FILEDESC', 'pgrowlocks - display row locking information',])
+endif
+
+pgrowlocks = shared_module('pgrowlocks',
+ pgrowlocks_sources,
+ kwargs: contrib_mod_args,
+)
+contrib_targets += pgrowlocks
+
+install_data(
+ 'pgrowlocks--1.0--1.1.sql',
+ 'pgrowlocks--1.1--1.2.sql',
+ 'pgrowlocks--1.2.sql',
+ 'pgrowlocks.control',
+ kwargs: contrib_data_args,
+)
+
+tests += {
+ 'name': 'pgrowlocks',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'isolation': {
+ 'specs': [
+ 'pgrowlocks',
+ ],
+ 'regress_args': ['--load-extension=pgrowlocks'],
+ },
+}
diff --git a/contrib/pgrowlocks/pgrowlocks--1.0--1.1.sql b/contrib/pgrowlocks/pgrowlocks--1.0--1.1.sql
new file mode 100644
index 0000000..3d5ca34
--- /dev/null
+++ b/contrib/pgrowlocks/pgrowlocks--1.0--1.1.sql
@@ -0,0 +1,17 @@
+/* contrib/pgrowlocks/pgrowlocks--1.0--1.1.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pgrowlocks UPDATE TO '1.1'" 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;
diff --git a/contrib/pgrowlocks/pgrowlocks--1.1--1.2.sql b/contrib/pgrowlocks/pgrowlocks--1.1--1.2.sql
new file mode 100644
index 0000000..94ebf54
--- /dev/null
+++ b/contrib/pgrowlocks/pgrowlocks--1.1--1.2.sql
@@ -0,0 +1,6 @@
+/* contrib/pgrowlocks/pgrowlocks--1.1--1.2.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pgrowlocks UPDATE TO '1.2'" to load this file. \quit
+
+ALTER FUNCTION pgrowlocks(text) PARALLEL SAFE;
diff --git a/contrib/pgrowlocks/pgrowlocks--1.2.sql b/contrib/pgrowlocks/pgrowlocks--1.2.sql
new file mode 100644
index 0000000..ff76b8b
--- /dev/null
+++ b/contrib/pgrowlocks/pgrowlocks--1.2.sql
@@ -0,0 +1,15 @@
+/* contrib/pgrowlocks/pgrowlocks--1.2.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 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 PARALLEL SAFE;
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
new file mode 100644
index 0000000..fd104e3
--- /dev/null
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -0,0 +1,276 @@
+/*
+ * contrib/pgrowlocks/pgrowlocks.c
+ *
+ * Copyright (c) 2005-2006 Tatsuo Ishii
+ *
+ * Permission to use, copy, modify, and distribute this software and
+ * its documentation for any purpose, without fee, and without a
+ * written agreement is hereby granted, provided that the above
+ * copyright notice and this paragraph and the following two
+ * paragraphs appear in all copies.
+ *
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT,
+ * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
+ * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
+ * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS
+ * IS" BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE,
+ * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+ */
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/multixact.h"
+#include "access/relscan.h"
+#include "access/tableam.h"
+#include "access/xact.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_am_d.h"
+#include "catalog/pg_authid.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "storage/bufmgr.h"
+#include "storage/procarray.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+#include "utils/snapmgr.h"
+#include "utils/varlena.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(pgrowlocks);
+
+/* ----------
+ * pgrowlocks:
+ * returns tids of rows being locked
+ * ----------
+ */
+
+#define NCHARS 32
+
+#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)
+{
+ text *relname = PG_GETARG_TEXT_PP(0);
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ AttInMetadata *attinmeta;
+ Relation rel;
+ RangeVar *relrv;
+ TableScanDesc scan;
+ HeapScanDesc hscan;
+ HeapTuple tuple;
+ AclResult aclresult;
+ char **values;
+
+ InitMaterializedSRF(fcinfo, 0);
+
+ /* Access the table */
+ relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+ rel = relation_openrv(relrv, AccessShareLock);
+
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partitioned table",
+ RelationGetRelationName(rel)),
+ errdetail("Partitioned tables do not contain rows.")));
+ else if (rel->rd_rel->relkind != RELKIND_RELATION)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a table",
+ RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relam != HEAP_TABLE_AM_OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only heap AM is supported")));
+
+ /*
+ * check permissions: must have SELECT on table or be in
+ * pg_stat_scan_tables
+ */
+ aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
+ ACL_SELECT);
+ if (aclresult != ACLCHECK_OK)
+ aclresult = has_privs_of_role(GetUserId(), ROLE_PG_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV;
+
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
+ RelationGetRelationName(rel));
+
+ /* Scan the relation */
+ scan = table_beginscan(rel, GetActiveSnapshot(), 0, NULL);
+ hscan = (HeapScanDesc) scan;
+
+ attinmeta = TupleDescGetAttInMetadata(rsinfo->setDesc);
+
+ values = (char **) palloc(rsinfo->setDesc->natts * sizeof(char *));
+
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ TM_Result htsu;
+ TransactionId xmax;
+ uint16 infomask;
+
+ /* must hold a buffer lock to call HeapTupleSatisfiesUpdate */
+ LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
+
+ htsu = HeapTupleSatisfiesUpdate(tuple,
+ GetCurrentCommandId(false),
+ hscan->rs_cbuf);
+ xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
+ infomask = tuple->t_data->t_infomask;
+
+ /*
+ * A tuple is locked if HTSU returns BeingModified.
+ */
+ if (htsu == TM_BeingModified)
+ {
+ values[Atnum_tid] = (char *) DirectFunctionCall1(tidout,
+ PointerGetDatum(&tuple->t_self));
+
+ values[Atnum_xmax] = palloc(NCHARS * sizeof(char));
+ snprintf(values[Atnum_xmax], NCHARS, "%u", xmax);
+ if (infomask & HEAP_XMAX_IS_MULTI)
+ {
+ MultiXactMember *members;
+ int nmembers;
+ bool first = true;
+ bool allow_old;
+
+ values[Atnum_ismulti] = pstrdup("true");
+
+ allow_old = HEAP_LOCKED_UPGRADED(infomask);
+ nmembers = GetMultiXactIdMembers(xmax, &members, allow_old,
+ false);
+ if (nmembers == -1)
+ {
+ values[Atnum_xids] = "{0}";
+ values[Atnum_modes] = "{transient upgrade status}";
+ values[Atnum_pids] = "{0}";
+ }
+ else
+ {
+ int 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++)
+ {
+ char buf[NCHARS];
+
+ if (!first)
+ {
+ strcat(values[Atnum_xids], ",");
+ strcat(values[Atnum_modes], ",");
+ strcat(values[Atnum_pids], ",");
+ }
+ snprintf(buf, NCHARS, "%u", 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);
+
+ first = false;
+ }
+
+ strcat(values[Atnum_xids], "}");
+ strcat(values[Atnum_modes], "}");
+ strcat(values[Atnum_pids], "}");
+ }
+ }
+ else
+ {
+ values[Atnum_ismulti] = pstrdup("false");
+
+ values[Atnum_xids] = palloc(NCHARS * sizeof(char));
+ snprintf(values[Atnum_xids], NCHARS, "{%u}", 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))
+ {
+ if (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)
+ snprintf(values[Atnum_modes], NCHARS, "{For Update}");
+ else
+ snprintf(values[Atnum_modes], NCHARS, "{For No Key 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, "{Update}");
+ else
+ snprintf(values[Atnum_modes], NCHARS, "{No Key Update}");
+ }
+
+ values[Atnum_pids] = palloc(NCHARS * sizeof(char));
+ snprintf(values[Atnum_pids], NCHARS, "{%d}",
+ BackendXidGetPid(xmax));
+ }
+
+ LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+
+ /* build a tuple */
+ tuple = BuildTupleFromCStrings(attinmeta, values);
+ tuplestore_puttuple(rsinfo->setResult, tuple);
+ }
+ else
+ {
+ LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
+ }
+ }
+
+ table_endscan(scan);
+ table_close(rel, AccessShareLock);
+ return (Datum) 0;
+}
diff --git a/contrib/pgrowlocks/pgrowlocks.control b/contrib/pgrowlocks/pgrowlocks.control
new file mode 100644
index 0000000..9f92b2f
--- /dev/null
+++ b/contrib/pgrowlocks/pgrowlocks.control
@@ -0,0 +1,5 @@
+# pgrowlocks extension
+comment = 'show row-level locking information'
+default_version = '1.2'
+module_pathname = '$libdir/pgrowlocks'
+relocatable = true
diff --git a/contrib/pgrowlocks/specs/pgrowlocks.spec b/contrib/pgrowlocks/specs/pgrowlocks.spec
new file mode 100644
index 0000000..4f4013c
--- /dev/null
+++ b/contrib/pgrowlocks/specs/pgrowlocks.spec
@@ -0,0 +1,39 @@
+# Tests for contrib/pgrowlocks
+
+setup {
+ CREATE TABLE multixact_conflict (a int PRIMARY KEY, b int);
+ INSERT INTO multixact_conflict VALUES (1, 2), (3, 4);
+}
+
+teardown {
+ DROP TABLE multixact_conflict;
+}
+
+session s1
+step s1_begin { BEGIN; }
+step s1_tuplock1 { SELECT * FROM multixact_conflict FOR KEY SHARE; }
+step s1_tuplock2 { SELECT * FROM multixact_conflict FOR SHARE; }
+step s1_tuplock3 { SELECT * FROM multixact_conflict FOR NO KEY UPDATE; }
+step s1_tuplock4 { SELECT * FROM multixact_conflict FOR UPDATE; }
+step s1_updatea { UPDATE multixact_conflict SET a = 10 WHERE a = 1; }
+step s1_updateb { UPDATE multixact_conflict SET b = 11 WHERE b = 4; }
+step s1_lcksvpt { SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT s; }
+step s1_commit { COMMIT; }
+
+session s2
+step s2_rowlocks { SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict'); }
+
+permutation s1_begin s1_tuplock1 s2_rowlocks s1_commit
+permutation s1_begin s1_tuplock2 s2_rowlocks s1_commit
+permutation s1_begin s1_tuplock3 s2_rowlocks s1_commit
+permutation s1_begin s1_tuplock4 s2_rowlocks s1_commit
+permutation s1_begin s1_updatea s2_rowlocks s1_commit
+permutation s1_begin s1_updateb s2_rowlocks s1_commit
+
+# test multixact cases using savepoints
+permutation s1_begin s1_lcksvpt s1_tuplock1 s2_rowlocks s1_commit
+permutation s1_begin s1_lcksvpt s1_tuplock2 s2_rowlocks s1_commit
+permutation s1_begin s1_lcksvpt s1_tuplock3 s2_rowlocks s1_commit
+permutation s1_begin s1_lcksvpt s1_tuplock4 s2_rowlocks s1_commit
+permutation s1_begin s1_lcksvpt s1_updatea s2_rowlocks s1_commit
+permutation s1_begin s1_lcksvpt s1_updateb s2_rowlocks s1_commit