1 /*-------------------------------------------------------------------------
2  *
3  * tid.c
4  *	  Functions for the built-in type tuple id
5  *
6  * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *	  src/backend/utils/adt/tid.c
12  *
13  * NOTES
14  *	  input routine largely stolen from boxin().
15  *
16  *-------------------------------------------------------------------------
17  */
18 #include "postgres.h"
19 
20 #include <math.h>
21 #include <limits.h>
22 
23 #include "access/heapam.h"
24 #include "access/sysattr.h"
25 #include "access/tableam.h"
26 #include "catalog/namespace.h"
27 #include "catalog/pg_type.h"
28 #include "common/hashfn.h"
29 #include "libpq/pqformat.h"
30 #include "miscadmin.h"
31 #include "parser/parsetree.h"
32 #include "utils/acl.h"
33 #include "utils/builtins.h"
34 #include "utils/lsyscache.h"
35 #include "utils/rel.h"
36 #include "utils/snapmgr.h"
37 #include "utils/varlena.h"
38 
39 
40 #define DatumGetItemPointer(X)	 ((ItemPointer) DatumGetPointer(X))
41 #define ItemPointerGetDatum(X)	 PointerGetDatum(X)
42 #define PG_GETARG_ITEMPOINTER(n) DatumGetItemPointer(PG_GETARG_DATUM(n))
43 #define PG_RETURN_ITEMPOINTER(x) return ItemPointerGetDatum(x)
44 
45 #define LDELIM			'('
46 #define RDELIM			')'
47 #define DELIM			','
48 #define NTIDARGS		2
49 
50 static ItemPointer currtid_for_view(Relation viewrel, ItemPointer tid);
51 
52 /* ----------------------------------------------------------------
53  *		tidin
54  * ----------------------------------------------------------------
55  */
56 Datum
tidin(PG_FUNCTION_ARGS)57 tidin(PG_FUNCTION_ARGS)
58 {
59 	char	   *str = PG_GETARG_CSTRING(0);
60 	char	   *p,
61 			   *coord[NTIDARGS];
62 	int			i;
63 	ItemPointer result;
64 	BlockNumber blockNumber;
65 	OffsetNumber offsetNumber;
66 	char	   *badp;
67 	int			hold_offset;
68 
69 	for (i = 0, p = str; *p && i < NTIDARGS && *p != RDELIM; p++)
70 		if (*p == DELIM || (*p == LDELIM && !i))
71 			coord[i++] = p + 1;
72 
73 	if (i < NTIDARGS)
74 		ereport(ERROR,
75 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
76 				 errmsg("invalid input syntax for type %s: \"%s\"",
77 						"tid", str)));
78 
79 	errno = 0;
80 	blockNumber = strtoul(coord[0], &badp, 10);
81 	if (errno || *badp != DELIM)
82 		ereport(ERROR,
83 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
84 				 errmsg("invalid input syntax for type %s: \"%s\"",
85 						"tid", str)));
86 
87 	hold_offset = strtol(coord[1], &badp, 10);
88 	if (errno || *badp != RDELIM ||
89 		hold_offset > USHRT_MAX || hold_offset < 0)
90 		ereport(ERROR,
91 				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
92 				 errmsg("invalid input syntax for type %s: \"%s\"",
93 						"tid", str)));
94 
95 	offsetNumber = hold_offset;
96 
97 	result = (ItemPointer) palloc(sizeof(ItemPointerData));
98 
99 	ItemPointerSet(result, blockNumber, offsetNumber);
100 
101 	PG_RETURN_ITEMPOINTER(result);
102 }
103 
104 /* ----------------------------------------------------------------
105  *		tidout
106  * ----------------------------------------------------------------
107  */
108 Datum
tidout(PG_FUNCTION_ARGS)109 tidout(PG_FUNCTION_ARGS)
110 {
111 	ItemPointer itemPtr = PG_GETARG_ITEMPOINTER(0);
112 	BlockNumber blockNumber;
113 	OffsetNumber offsetNumber;
114 	char		buf[32];
115 
116 	blockNumber = ItemPointerGetBlockNumberNoCheck(itemPtr);
117 	offsetNumber = ItemPointerGetOffsetNumberNoCheck(itemPtr);
118 
119 	/* Perhaps someday we should output this as a record. */
120 	snprintf(buf, sizeof(buf), "(%u,%u)", blockNumber, offsetNumber);
121 
122 	PG_RETURN_CSTRING(pstrdup(buf));
123 }
124 
125 /*
126  *		tidrecv			- converts external binary format to tid
127  */
128 Datum
tidrecv(PG_FUNCTION_ARGS)129 tidrecv(PG_FUNCTION_ARGS)
130 {
131 	StringInfo	buf = (StringInfo) PG_GETARG_POINTER(0);
132 	ItemPointer result;
133 	BlockNumber blockNumber;
134 	OffsetNumber offsetNumber;
135 
136 	blockNumber = pq_getmsgint(buf, sizeof(blockNumber));
137 	offsetNumber = pq_getmsgint(buf, sizeof(offsetNumber));
138 
139 	result = (ItemPointer) palloc(sizeof(ItemPointerData));
140 
141 	ItemPointerSet(result, blockNumber, offsetNumber);
142 
143 	PG_RETURN_ITEMPOINTER(result);
144 }
145 
146 /*
147  *		tidsend			- converts tid to binary format
148  */
149 Datum
tidsend(PG_FUNCTION_ARGS)150 tidsend(PG_FUNCTION_ARGS)
151 {
152 	ItemPointer itemPtr = PG_GETARG_ITEMPOINTER(0);
153 	StringInfoData buf;
154 
155 	pq_begintypsend(&buf);
156 	pq_sendint32(&buf, ItemPointerGetBlockNumberNoCheck(itemPtr));
157 	pq_sendint16(&buf, ItemPointerGetOffsetNumberNoCheck(itemPtr));
158 	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
159 }
160 
161 /*****************************************************************************
162  *	 PUBLIC ROUTINES														 *
163  *****************************************************************************/
164 
165 Datum
tideq(PG_FUNCTION_ARGS)166 tideq(PG_FUNCTION_ARGS)
167 {
168 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
169 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
170 
171 	PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) == 0);
172 }
173 
174 Datum
tidne(PG_FUNCTION_ARGS)175 tidne(PG_FUNCTION_ARGS)
176 {
177 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
178 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
179 
180 	PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) != 0);
181 }
182 
183 Datum
tidlt(PG_FUNCTION_ARGS)184 tidlt(PG_FUNCTION_ARGS)
185 {
186 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
187 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
188 
189 	PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) < 0);
190 }
191 
192 Datum
tidle(PG_FUNCTION_ARGS)193 tidle(PG_FUNCTION_ARGS)
194 {
195 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
196 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
197 
198 	PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) <= 0);
199 }
200 
201 Datum
tidgt(PG_FUNCTION_ARGS)202 tidgt(PG_FUNCTION_ARGS)
203 {
204 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
205 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
206 
207 	PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) > 0);
208 }
209 
210 Datum
tidge(PG_FUNCTION_ARGS)211 tidge(PG_FUNCTION_ARGS)
212 {
213 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
214 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
215 
216 	PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) >= 0);
217 }
218 
219 Datum
bttidcmp(PG_FUNCTION_ARGS)220 bttidcmp(PG_FUNCTION_ARGS)
221 {
222 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
223 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
224 
225 	PG_RETURN_INT32(ItemPointerCompare(arg1, arg2));
226 }
227 
228 Datum
tidlarger(PG_FUNCTION_ARGS)229 tidlarger(PG_FUNCTION_ARGS)
230 {
231 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
232 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
233 
234 	PG_RETURN_ITEMPOINTER(ItemPointerCompare(arg1, arg2) >= 0 ? arg1 : arg2);
235 }
236 
237 Datum
tidsmaller(PG_FUNCTION_ARGS)238 tidsmaller(PG_FUNCTION_ARGS)
239 {
240 	ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
241 	ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
242 
243 	PG_RETURN_ITEMPOINTER(ItemPointerCompare(arg1, arg2) <= 0 ? arg1 : arg2);
244 }
245 
246 Datum
hashtid(PG_FUNCTION_ARGS)247 hashtid(PG_FUNCTION_ARGS)
248 {
249 	ItemPointer key = PG_GETARG_ITEMPOINTER(0);
250 
251 	/*
252 	 * While you'll probably have a lot of trouble with a compiler that
253 	 * insists on appending pad space to struct ItemPointerData, we can at
254 	 * least make this code work, by not using sizeof(ItemPointerData).
255 	 * Instead rely on knowing the sizes of the component fields.
256 	 */
257 	return hash_any((unsigned char *) key,
258 					sizeof(BlockIdData) + sizeof(OffsetNumber));
259 }
260 
261 Datum
hashtidextended(PG_FUNCTION_ARGS)262 hashtidextended(PG_FUNCTION_ARGS)
263 {
264 	ItemPointer key = PG_GETARG_ITEMPOINTER(0);
265 	uint64		seed = PG_GETARG_INT64(1);
266 
267 	/* As above */
268 	return hash_any_extended((unsigned char *) key,
269 							 sizeof(BlockIdData) + sizeof(OffsetNumber),
270 							 seed);
271 }
272 
273 
274 /*
275  *	Functions to get latest tid of a specified tuple.
276  *
277  *	Maybe these implementations should be moved to another place
278  */
279 
280 /*
281  * Utility wrapper for current CTID functions.
282  *		Returns the latest version of a tuple pointing at "tid" for
283  *		relation "rel".
284  */
285 static ItemPointer
currtid_internal(Relation rel,ItemPointer tid)286 currtid_internal(Relation rel, ItemPointer tid)
287 {
288 	ItemPointer result;
289 	AclResult	aclresult;
290 	Snapshot	snapshot;
291 	TableScanDesc scan;
292 
293 	result = (ItemPointer) palloc(sizeof(ItemPointerData));
294 
295 	aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
296 								  ACL_SELECT);
297 	if (aclresult != ACLCHECK_OK)
298 		aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
299 					   RelationGetRelationName(rel));
300 
301 	if (rel->rd_rel->relkind == RELKIND_VIEW)
302 		return currtid_for_view(rel, tid);
303 
304 	if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
305 		elog(ERROR, "cannot look at latest visible tid for relation \"%s.%s\"",
306 			 get_namespace_name(RelationGetNamespace(rel)),
307 			 RelationGetRelationName(rel));
308 
309 	ItemPointerCopy(tid, result);
310 
311 	snapshot = RegisterSnapshot(GetLatestSnapshot());
312 	scan = table_beginscan_tid(rel, snapshot);
313 	table_tuple_get_latest_tid(scan, result);
314 	table_endscan(scan);
315 	UnregisterSnapshot(snapshot);
316 
317 	return result;
318 }
319 
320 /*
321  *	Handle CTIDs of views.
322  *		CTID should be defined in the view and it must
323  *		correspond to the CTID of a base relation.
324  */
325 static ItemPointer
currtid_for_view(Relation viewrel,ItemPointer tid)326 currtid_for_view(Relation viewrel, ItemPointer tid)
327 {
328 	TupleDesc	att = RelationGetDescr(viewrel);
329 	RuleLock   *rulelock;
330 	RewriteRule *rewrite;
331 	int			i,
332 				natts = att->natts,
333 				tididx = -1;
334 
335 	for (i = 0; i < natts; i++)
336 	{
337 		Form_pg_attribute attr = TupleDescAttr(att, i);
338 
339 		if (strcmp(NameStr(attr->attname), "ctid") == 0)
340 		{
341 			if (attr->atttypid != TIDOID)
342 				elog(ERROR, "ctid isn't of type TID");
343 			tididx = i;
344 			break;
345 		}
346 	}
347 	if (tididx < 0)
348 		elog(ERROR, "currtid cannot handle views with no CTID");
349 	rulelock = viewrel->rd_rules;
350 	if (!rulelock)
351 		elog(ERROR, "the view has no rules");
352 	for (i = 0; i < rulelock->numLocks; i++)
353 	{
354 		rewrite = rulelock->rules[i];
355 		if (rewrite->event == CMD_SELECT)
356 		{
357 			Query	   *query;
358 			TargetEntry *tle;
359 
360 			if (list_length(rewrite->actions) != 1)
361 				elog(ERROR, "only one select rule is allowed in views");
362 			query = (Query *) linitial(rewrite->actions);
363 			tle = get_tle_by_resno(query->targetList, tididx + 1);
364 			if (tle && tle->expr && IsA(tle->expr, Var))
365 			{
366 				Var		   *var = (Var *) tle->expr;
367 				RangeTblEntry *rte;
368 
369 				if (!IS_SPECIAL_VARNO(var->varno) &&
370 					var->varattno == SelfItemPointerAttributeNumber)
371 				{
372 					rte = rt_fetch(var->varno, query->rtable);
373 					if (rte)
374 					{
375 						ItemPointer result;
376 						Relation	rel;
377 
378 						rel = table_open(rte->relid, AccessShareLock);
379 						result = currtid_internal(rel, tid);
380 						table_close(rel, AccessShareLock);
381 						return result;
382 					}
383 				}
384 			}
385 			break;
386 		}
387 	}
388 	elog(ERROR, "currtid cannot handle this view");
389 	return NULL;
390 }
391 
392 /*
393  * currtid_byrelname
394  *		Get the latest tuple version of the tuple pointing at a CTID, for a
395  *		given relation name.
396  */
397 Datum
currtid_byrelname(PG_FUNCTION_ARGS)398 currtid_byrelname(PG_FUNCTION_ARGS)
399 {
400 	text	   *relname = PG_GETARG_TEXT_PP(0);
401 	ItemPointer tid = PG_GETARG_ITEMPOINTER(1);
402 	ItemPointer result;
403 	RangeVar   *relrv;
404 	Relation	rel;
405 
406 	relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
407 	rel = table_openrv(relrv, AccessShareLock);
408 
409 	/* grab the latest tuple version associated to this CTID */
410 	result = currtid_internal(rel, tid);
411 
412 	table_close(rel, AccessShareLock);
413 
414 	PG_RETURN_ITEMPOINTER(result);
415 }
416