1 /*
2  * contrib/pgrowlocks/pgrowlocks.c
3  *
4  * Copyright (c) 2005-2006	Tatsuo Ishii
5  *
6  * Permission to use, copy, modify, and distribute this software and
7  * its documentation for any purpose, without fee, and without a
8  * written agreement is hereby granted, provided that the above
9  * copyright notice and this paragraph and the following two
10  * paragraphs appear in all copies.
11  *
12  * IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT,
13  * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
14  * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
15  * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED
16  * OF THE POSSIBILITY OF SUCH DAMAGE.
17  *
18  * THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
19  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20  * A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS
21  * IS" BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE,
22  * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
23  */
24 
25 #include "postgres.h"
26 
27 #include "access/multixact.h"
28 #include "access/relscan.h"
29 #include "access/xact.h"
30 #include "catalog/namespace.h"
31 #include "catalog/pg_authid.h"
32 #include "funcapi.h"
33 #include "miscadmin.h"
34 #include "storage/bufmgr.h"
35 #include "storage/procarray.h"
36 #include "utils/acl.h"
37 #include "utils/builtins.h"
38 #include "utils/rel.h"
39 #include "utils/snapmgr.h"
40 #include "utils/tqual.h"
41 #include "utils/varlena.h"
42 
43 PG_MODULE_MAGIC;
44 
45 PG_FUNCTION_INFO_V1(pgrowlocks);
46 
47 /* ----------
48  * pgrowlocks:
49  * returns tids of rows being locked
50  * ----------
51  */
52 
53 #define NCHARS 32
54 
55 #define		Atnum_tid		0
56 #define		Atnum_xmax		1
57 #define		Atnum_ismulti	2
58 #define		Atnum_xids		3
59 #define		Atnum_modes		4
60 #define		Atnum_pids		5
61 
62 Datum
pgrowlocks(PG_FUNCTION_ARGS)63 pgrowlocks(PG_FUNCTION_ARGS)
64 {
65 	text	   *relname = PG_GETARG_TEXT_PP(0);
66 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
67 	bool		randomAccess;
68 	TupleDesc	tupdesc;
69 	Tuplestorestate *tupstore;
70 	AttInMetadata *attinmeta;
71 	Relation	rel;
72 	RangeVar   *relrv;
73 	HeapScanDesc scan;
74 	HeapTuple	tuple;
75 	MemoryContext oldcontext;
76 	AclResult	aclresult;
77 	char	  **values;
78 
79 	/* check to see if caller supports us returning a tuplestore */
80 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
81 		ereport(ERROR,
82 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
83 				 errmsg("set-valued function called in context that cannot accept a set")));
84 	if (!(rsinfo->allowedModes & SFRM_Materialize))
85 		ereport(ERROR,
86 				(errcode(ERRCODE_SYNTAX_ERROR),
87 				 errmsg("materialize mode required, but it is not allowed in this context")));
88 
89 	/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
90 	oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
91 
92 	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
93 		elog(ERROR, "return type must be a row type");
94 
95 	randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
96 	tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
97 	rsinfo->returnMode = SFRM_Materialize;
98 	rsinfo->setResult = tupstore;
99 	rsinfo->setDesc = tupdesc;
100 
101 	MemoryContextSwitchTo(oldcontext);
102 
103 	/* Access the table */
104 	relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
105 	rel = relation_openrv(relrv, AccessShareLock);
106 
107 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
108 		ereport(ERROR,
109 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
110 				 errmsg("\"%s\" is a partitioned table",
111 						RelationGetRelationName(rel)),
112 				 errdetail("Partitioned tables do not contain rows.")));
113 	else if (rel->rd_rel->relkind != RELKIND_RELATION)
114 		ereport(ERROR,
115 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
116 				 errmsg("\"%s\" is not a table",
117 						RelationGetRelationName(rel))));
118 
119 	/*
120 	 * check permissions: must have SELECT on table or be in
121 	 * pg_stat_scan_tables
122 	 */
123 	aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
124 								  ACL_SELECT);
125 	if (aclresult != ACLCHECK_OK)
126 		aclresult = is_member_of_role(GetUserId(), DEFAULT_ROLE_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV;
127 
128 	if (aclresult != ACLCHECK_OK)
129 		aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
130 					   RelationGetRelationName(rel));
131 
132 	/* Scan the relation */
133 	scan = heap_beginscan(rel, GetActiveSnapshot(), 0, NULL);
134 
135 	attinmeta = TupleDescGetAttInMetadata(tupdesc);
136 
137 	values = (char **) palloc(tupdesc->natts * sizeof(char *));
138 
139 	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
140 	{
141 		HTSU_Result htsu;
142 		TransactionId xmax;
143 		uint16		infomask;
144 
145 		/* must hold a buffer lock to call HeapTupleSatisfiesUpdate */
146 		LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
147 
148 		htsu = HeapTupleSatisfiesUpdate(tuple,
149 										GetCurrentCommandId(false),
150 										scan->rs_cbuf);
151 		xmax = HeapTupleHeaderGetRawXmax(tuple->t_data);
152 		infomask = tuple->t_data->t_infomask;
153 
154 		/*
155 		 * A tuple is locked if HTSU returns BeingUpdated.
156 		 */
157 		if (htsu == HeapTupleBeingUpdated)
158 		{
159 			values[Atnum_tid] = (char *) DirectFunctionCall1(tidout,
160 															 PointerGetDatum(&tuple->t_self));
161 
162 			values[Atnum_xmax] = palloc(NCHARS * sizeof(char));
163 			snprintf(values[Atnum_xmax], NCHARS, "%d", xmax);
164 			if (infomask & HEAP_XMAX_IS_MULTI)
165 			{
166 				MultiXactMember *members;
167 				int			nmembers;
168 				bool		first = true;
169 				bool		allow_old;
170 
171 				values[Atnum_ismulti] = pstrdup("true");
172 
173 				allow_old = HEAP_LOCKED_UPGRADED(infomask);
174 				nmembers = GetMultiXactIdMembers(xmax, &members, allow_old,
175 												 false);
176 				if (nmembers == -1)
177 				{
178 					values[Atnum_xids] = "{0}";
179 					values[Atnum_modes] = "{transient upgrade status}";
180 					values[Atnum_pids] = "{0}";
181 				}
182 				else
183 				{
184 					int			j;
185 
186 					values[Atnum_xids] = palloc(NCHARS * nmembers);
187 					values[Atnum_modes] = palloc(NCHARS * nmembers);
188 					values[Atnum_pids] = palloc(NCHARS * nmembers);
189 
190 					strcpy(values[Atnum_xids], "{");
191 					strcpy(values[Atnum_modes], "{");
192 					strcpy(values[Atnum_pids], "{");
193 
194 					for (j = 0; j < nmembers; j++)
195 					{
196 						char		buf[NCHARS];
197 
198 						if (!first)
199 						{
200 							strcat(values[Atnum_xids], ",");
201 							strcat(values[Atnum_modes], ",");
202 							strcat(values[Atnum_pids], ",");
203 						}
204 						snprintf(buf, NCHARS, "%d", members[j].xid);
205 						strcat(values[Atnum_xids], buf);
206 						switch (members[j].status)
207 						{
208 							case MultiXactStatusUpdate:
209 								snprintf(buf, NCHARS, "Update");
210 								break;
211 							case MultiXactStatusNoKeyUpdate:
212 								snprintf(buf, NCHARS, "No Key Update");
213 								break;
214 							case MultiXactStatusForUpdate:
215 								snprintf(buf, NCHARS, "For Update");
216 								break;
217 							case MultiXactStatusForNoKeyUpdate:
218 								snprintf(buf, NCHARS, "For No Key Update");
219 								break;
220 							case MultiXactStatusForShare:
221 								snprintf(buf, NCHARS, "Share");
222 								break;
223 							case MultiXactStatusForKeyShare:
224 								snprintf(buf, NCHARS, "Key Share");
225 								break;
226 						}
227 						strcat(values[Atnum_modes], buf);
228 						snprintf(buf, NCHARS, "%d",
229 								 BackendXidGetPid(members[j].xid));
230 						strcat(values[Atnum_pids], buf);
231 
232 						first = false;
233 					}
234 
235 					strcat(values[Atnum_xids], "}");
236 					strcat(values[Atnum_modes], "}");
237 					strcat(values[Atnum_pids], "}");
238 				}
239 			}
240 			else
241 			{
242 				values[Atnum_ismulti] = pstrdup("false");
243 
244 				values[Atnum_xids] = palloc(NCHARS * sizeof(char));
245 				snprintf(values[Atnum_xids], NCHARS, "{%d}", xmax);
246 
247 				values[Atnum_modes] = palloc(NCHARS);
248 				if (infomask & HEAP_XMAX_LOCK_ONLY)
249 				{
250 					if (HEAP_XMAX_IS_SHR_LOCKED(infomask))
251 						snprintf(values[Atnum_modes], NCHARS, "{For Share}");
252 					else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask))
253 						snprintf(values[Atnum_modes], NCHARS, "{For Key Share}");
254 					else if (HEAP_XMAX_IS_EXCL_LOCKED(infomask))
255 					{
256 						if (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)
257 							snprintf(values[Atnum_modes], NCHARS, "{For Update}");
258 						else
259 							snprintf(values[Atnum_modes], NCHARS, "{For No Key Update}");
260 					}
261 					else
262 						/* neither keyshare nor exclusive bit it set */
263 						snprintf(values[Atnum_modes], NCHARS,
264 								 "{transient upgrade status}");
265 				}
266 				else
267 				{
268 					if (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED)
269 						snprintf(values[Atnum_modes], NCHARS, "{Update}");
270 					else
271 						snprintf(values[Atnum_modes], NCHARS, "{No Key Update}");
272 				}
273 
274 				values[Atnum_pids] = palloc(NCHARS * sizeof(char));
275 				snprintf(values[Atnum_pids], NCHARS, "{%d}",
276 						 BackendXidGetPid(xmax));
277 			}
278 
279 			LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
280 
281 			/* build a tuple */
282 			tuple = BuildTupleFromCStrings(attinmeta, values);
283 			tuplestore_puttuple(tupstore, tuple);
284 		}
285 		else
286 		{
287 			LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
288 		}
289 	}
290 
291 	heap_endscan(scan);
292 	heap_close(rel, AccessShareLock);
293 	return (Datum) 0;
294 }
295