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 11072 : tidin(PG_FUNCTION_ARGS)
52 : {
53 11072 : char *str = PG_GETARG_CSTRING(0);
54 11072 : 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 53592 : for (i = 0, p = str; *p && i < NTIDARGS && *p != RDELIM; p++)
65 42520 : if (*p == DELIM || (*p == LDELIM && i == 0))
66 22132 : coord[i++] = p + 1;
67 :
68 11072 : if (i < NTIDARGS)
69 12 : ereturn(escontext, (Datum) 0,
70 : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
71 : errmsg("invalid input syntax for type %s: \"%s\"",
72 : "tid", str)));
73 :
74 11060 : errno = 0;
75 11060 : cvt = strtoul(coord[0], &badp, 10);
76 11060 : 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 11060 : 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 11060 : if (cvt != (unsigned long) blockNumber &&
90 12 : cvt != (unsigned long) ((int32) blockNumber))
91 6 : 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 11054 : cvt = strtoul(coord[1], &badp, 10);
98 11054 : if (errno || *badp != RDELIM ||
99 : cvt > USHRT_MAX)
100 18 : ereturn(escontext, (Datum) 0,
101 : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
102 : errmsg("invalid input syntax for type %s: \"%s\"",
103 : "tid", str)));
104 11036 : offsetNumber = (OffsetNumber) cvt;
105 :
106 11036 : result = (ItemPointer) palloc_object(ItemPointerData);
107 :
108 11036 : ItemPointerSet(result, blockNumber, offsetNumber);
109 :
110 11036 : PG_RETURN_ITEMPOINTER(result);
111 : }
112 :
113 : /* ----------------------------------------------------------------
114 : * tidout
115 : * ----------------------------------------------------------------
116 : */
117 : Datum
118 48214 : tidout(PG_FUNCTION_ARGS)
119 : {
120 48214 : ItemPointer itemPtr = PG_GETARG_ITEMPOINTER(0);
121 : BlockNumber blockNumber;
122 : OffsetNumber offsetNumber;
123 : char buf[32];
124 :
125 48214 : blockNumber = ItemPointerGetBlockNumberNoCheck(itemPtr);
126 48214 : offsetNumber = ItemPointerGetOffsetNumberNoCheck(itemPtr);
127 :
128 : /* Perhaps someday we should output this as a record. */
129 48214 : snprintf(buf, sizeof(buf), "(%u,%u)", blockNumber, offsetNumber);
130 :
131 48214 : 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 18382100 : tideq(PG_FUNCTION_ARGS)
176 : {
177 18382100 : ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
178 18382100 : ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
179 :
180 18382100 : PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) == 0);
181 : }
182 :
183 : Datum
184 228 : tidne(PG_FUNCTION_ARGS)
185 : {
186 228 : ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
187 228 : ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
188 :
189 228 : PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) != 0);
190 : }
191 :
192 : Datum
193 14594 : tidlt(PG_FUNCTION_ARGS)
194 : {
195 14594 : ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
196 14594 : ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
197 :
198 14594 : PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) < 0);
199 : }
200 :
201 : Datum
202 3798 : tidle(PG_FUNCTION_ARGS)
203 : {
204 3798 : ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
205 3798 : ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
206 :
207 3798 : PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) <= 0);
208 : }
209 :
210 : Datum
211 5356 : tidgt(PG_FUNCTION_ARGS)
212 : {
213 5356 : ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
214 5356 : ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
215 :
216 5356 : PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) > 0);
217 : }
218 :
219 : Datum
220 3732 : tidge(PG_FUNCTION_ARGS)
221 : {
222 3732 : ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
223 3732 : ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
224 :
225 3732 : PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) >= 0);
226 : }
227 :
228 : Datum
229 2944344 : bttidcmp(PG_FUNCTION_ARGS)
230 : {
231 2944344 : ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
232 2944344 : ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
233 :
234 2944344 : PG_RETURN_INT32(ItemPointerCompare(arg1, arg2));
235 : }
236 :
237 : Datum
238 1200 : tidlarger(PG_FUNCTION_ARGS)
239 : {
240 1200 : ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
241 1200 : ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
242 :
243 1200 : PG_RETURN_ITEMPOINTER(ItemPointerCompare(arg1, arg2) >= 0 ? arg1 : arg2);
244 : }
245 :
246 : Datum
247 1200 : tidsmaller(PG_FUNCTION_ARGS)
248 : {
249 1200 : ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0);
250 1200 : ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1);
251 :
252 1200 : PG_RETURN_ITEMPOINTER(ItemPointerCompare(arg1, arg2) <= 0 ? arg1 : arg2);
253 : }
254 :
255 : Datum
256 126080 : hashtid(PG_FUNCTION_ARGS)
257 : {
258 126080 : 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 126080 : 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 : /*
284 : * Functions to get latest tid of a specified tuple.
285 : *
286 : * Maybe these implementations should be moved to another place
287 : */
288 :
289 : /*
290 : * Utility wrapper for current CTID functions.
291 : * Returns the latest version of a tuple pointing at "tid" for
292 : * relation "rel".
293 : */
294 : static ItemPointer
295 60 : currtid_internal(Relation rel, const ItemPointerData *tid)
296 : {
297 : ItemPointer result;
298 : AclResult aclresult;
299 : Snapshot snapshot;
300 : TableScanDesc scan;
301 :
302 60 : result = (ItemPointer) palloc_object(ItemPointerData);
303 :
304 60 : aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
305 : ACL_SELECT);
306 60 : if (aclresult != ACLCHECK_OK)
307 0 : aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
308 0 : RelationGetRelationName(rel));
309 :
310 60 : if (rel->rd_rel->relkind == RELKIND_VIEW)
311 24 : return currtid_for_view(rel, tid);
312 :
313 36 : if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
314 6 : ereport(ERROR,
315 : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
316 : errmsg("cannot look at latest visible tid for relation \"%s.%s\"",
317 : get_namespace_name(RelationGetNamespace(rel)),
318 : RelationGetRelationName(rel)));
319 :
320 30 : ItemPointerCopy(tid, result);
321 :
322 30 : snapshot = RegisterSnapshot(GetLatestSnapshot());
323 30 : scan = table_beginscan_tid(rel, snapshot);
324 30 : table_tuple_get_latest_tid(scan, result);
325 18 : table_endscan(scan);
326 18 : UnregisterSnapshot(snapshot);
327 :
328 18 : return result;
329 : }
330 :
331 : /*
332 : * Handle CTIDs of views.
333 : * CTID should be defined in the view and it must
334 : * correspond to the CTID of a base relation.
335 : */
336 : static ItemPointer
337 24 : currtid_for_view(Relation viewrel, const ItemPointerData *tid)
338 : {
339 24 : TupleDesc att = RelationGetDescr(viewrel);
340 : RuleLock *rulelock;
341 : RewriteRule *rewrite;
342 : int i,
343 24 : natts = att->natts,
344 24 : tididx = -1;
345 :
346 30 : for (i = 0; i < natts; i++)
347 : {
348 24 : Form_pg_attribute attr = TupleDescAttr(att, i);
349 :
350 24 : if (strcmp(NameStr(attr->attname), "ctid") == 0)
351 : {
352 18 : if (attr->atttypid != TIDOID)
353 6 : ereport(ERROR,
354 : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
355 : errmsg("ctid isn't of type TID"));
356 12 : tididx = i;
357 12 : break;
358 : }
359 : }
360 18 : if (tididx < 0)
361 6 : ereport(ERROR,
362 : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
363 : errmsg("currtid cannot handle views with no CTID"));
364 12 : rulelock = viewrel->rd_rules;
365 12 : if (!rulelock)
366 0 : ereport(ERROR,
367 : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
368 : errmsg("the view has no rules"));
369 12 : for (i = 0; i < rulelock->numLocks; i++)
370 : {
371 12 : rewrite = rulelock->rules[i];
372 12 : if (rewrite->event == CMD_SELECT)
373 : {
374 : Query *query;
375 : TargetEntry *tle;
376 :
377 12 : if (list_length(rewrite->actions) != 1)
378 0 : ereport(ERROR,
379 : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
380 : errmsg("only one select rule is allowed in views"));
381 12 : query = (Query *) linitial(rewrite->actions);
382 12 : tle = get_tle_by_resno(query->targetList, tididx + 1);
383 12 : if (tle && tle->expr && IsA(tle->expr, Var))
384 : {
385 12 : Var *var = (Var *) tle->expr;
386 : RangeTblEntry *rte;
387 :
388 12 : if (!IS_SPECIAL_VARNO(var->varno) &&
389 12 : var->varattno == SelfItemPointerAttributeNumber)
390 : {
391 12 : rte = rt_fetch(var->varno, query->rtable);
392 12 : if (rte)
393 : {
394 : ItemPointer result;
395 : Relation rel;
396 :
397 12 : rel = table_open(rte->relid, AccessShareLock);
398 12 : result = currtid_internal(rel, tid);
399 6 : table_close(rel, AccessShareLock);
400 6 : return result;
401 : }
402 : }
403 : }
404 0 : break;
405 : }
406 : }
407 0 : elog(ERROR, "currtid cannot handle this view");
408 : return NULL;
409 : }
410 :
411 : /*
412 : * currtid_byrelname
413 : * Get the latest tuple version of the tuple pointing at a CTID, for a
414 : * given relation name.
415 : */
416 : Datum
417 54 : currtid_byrelname(PG_FUNCTION_ARGS)
418 : {
419 54 : text *relname = PG_GETARG_TEXT_PP(0);
420 54 : ItemPointer tid = PG_GETARG_ITEMPOINTER(1);
421 : ItemPointer result;
422 : RangeVar *relrv;
423 : Relation rel;
424 :
425 54 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
426 54 : rel = table_openrv(relrv, AccessShareLock);
427 :
428 : /* grab the latest tuple version associated to this CTID */
429 48 : result = currtid_internal(rel, tid);
430 :
431 18 : table_close(rel, AccessShareLock);
432 :
433 18 : PG_RETURN_ITEMPOINTER(result);
434 : }
|