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