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