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