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