/src/postgres/src/backend/utils/adt/tid.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * tid.c |
4 | | * Functions for the built-in type tuple id |
5 | | * |
6 | | * Portions Copyright (c) 1996-2025, 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 | 0 | #define LDELIM '(' |
41 | 0 | #define RDELIM ')' |
42 | 0 | #define DELIM ',' |
43 | 0 | #define NTIDARGS 2 |
44 | | |
45 | | static ItemPointer currtid_for_view(Relation viewrel, ItemPointer tid); |
46 | | |
47 | | /* ---------------------------------------------------------------- |
48 | | * tidin |
49 | | * ---------------------------------------------------------------- |
50 | | */ |
51 | | Datum |
52 | | tidin(PG_FUNCTION_ARGS) |
53 | 0 | { |
54 | 0 | char *str = PG_GETARG_CSTRING(0); |
55 | 0 | Node *escontext = fcinfo->context; |
56 | 0 | char *p, |
57 | 0 | *coord[NTIDARGS]; |
58 | 0 | int i; |
59 | 0 | ItemPointer result; |
60 | 0 | BlockNumber blockNumber; |
61 | 0 | OffsetNumber offsetNumber; |
62 | 0 | char *badp; |
63 | 0 | unsigned long cvt; |
64 | |
|
65 | 0 | for (i = 0, p = str; *p && i < NTIDARGS && *p != RDELIM; p++) |
66 | 0 | if (*p == DELIM || (*p == LDELIM && i == 0)) |
67 | 0 | coord[i++] = p + 1; |
68 | |
|
69 | 0 | if (i < NTIDARGS) |
70 | 0 | ereturn(escontext, (Datum) 0, |
71 | 0 | (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
72 | 0 | errmsg("invalid input syntax for type %s: \"%s\"", |
73 | 0 | "tid", str))); |
74 | | |
75 | 0 | errno = 0; |
76 | 0 | cvt = strtoul(coord[0], &badp, 10); |
77 | 0 | if (errno || *badp != DELIM) |
78 | 0 | ereturn(escontext, (Datum) 0, |
79 | 0 | (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
80 | 0 | errmsg("invalid input syntax for type %s: \"%s\"", |
81 | 0 | "tid", str))); |
82 | 0 | 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 | 0 | #if SIZEOF_LONG > 4 |
90 | 0 | if (cvt != (unsigned long) blockNumber && |
91 | 0 | cvt != (unsigned long) ((int32) blockNumber)) |
92 | 0 | ereturn(escontext, (Datum) 0, |
93 | 0 | (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
94 | 0 | errmsg("invalid input syntax for type %s: \"%s\"", |
95 | 0 | "tid", str))); |
96 | 0 | #endif |
97 | | |
98 | 0 | cvt = strtoul(coord[1], &badp, 10); |
99 | 0 | if (errno || *badp != RDELIM || |
100 | 0 | cvt > USHRT_MAX) |
101 | 0 | ereturn(escontext, (Datum) 0, |
102 | 0 | (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
103 | 0 | errmsg("invalid input syntax for type %s: \"%s\"", |
104 | 0 | "tid", str))); |
105 | 0 | offsetNumber = (OffsetNumber) cvt; |
106 | |
|
107 | 0 | result = (ItemPointer) palloc(sizeof(ItemPointerData)); |
108 | |
|
109 | 0 | ItemPointerSet(result, blockNumber, offsetNumber); |
110 | |
|
111 | 0 | PG_RETURN_ITEMPOINTER(result); |
112 | 0 | } |
113 | | |
114 | | /* ---------------------------------------------------------------- |
115 | | * tidout |
116 | | * ---------------------------------------------------------------- |
117 | | */ |
118 | | Datum |
119 | | tidout(PG_FUNCTION_ARGS) |
120 | 0 | { |
121 | 0 | ItemPointer itemPtr = PG_GETARG_ITEMPOINTER(0); |
122 | 0 | BlockNumber blockNumber; |
123 | 0 | OffsetNumber offsetNumber; |
124 | 0 | char buf[32]; |
125 | |
|
126 | 0 | blockNumber = ItemPointerGetBlockNumberNoCheck(itemPtr); |
127 | 0 | offsetNumber = ItemPointerGetOffsetNumberNoCheck(itemPtr); |
128 | | |
129 | | /* Perhaps someday we should output this as a record. */ |
130 | 0 | snprintf(buf, sizeof(buf), "(%u,%u)", blockNumber, offsetNumber); |
131 | |
|
132 | 0 | PG_RETURN_CSTRING(pstrdup(buf)); |
133 | 0 | } |
134 | | |
135 | | /* |
136 | | * tidrecv - converts external binary format to tid |
137 | | */ |
138 | | Datum |
139 | | tidrecv(PG_FUNCTION_ARGS) |
140 | 0 | { |
141 | 0 | StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); |
142 | 0 | ItemPointer result; |
143 | 0 | BlockNumber blockNumber; |
144 | 0 | 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 | 0 | } |
155 | | |
156 | | /* |
157 | | * tidsend - converts tid to binary format |
158 | | */ |
159 | | Datum |
160 | | tidsend(PG_FUNCTION_ARGS) |
161 | 0 | { |
162 | 0 | ItemPointer itemPtr = PG_GETARG_ITEMPOINTER(0); |
163 | 0 | 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 | 0 | } |
170 | | |
171 | | /***************************************************************************** |
172 | | * PUBLIC ROUTINES * |
173 | | *****************************************************************************/ |
174 | | |
175 | | Datum |
176 | | tideq(PG_FUNCTION_ARGS) |
177 | 0 | { |
178 | 0 | ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0); |
179 | 0 | ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1); |
180 | |
|
181 | 0 | PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) == 0); |
182 | 0 | } |
183 | | |
184 | | Datum |
185 | | tidne(PG_FUNCTION_ARGS) |
186 | 0 | { |
187 | 0 | ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0); |
188 | 0 | ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1); |
189 | |
|
190 | 0 | PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) != 0); |
191 | 0 | } |
192 | | |
193 | | Datum |
194 | | tidlt(PG_FUNCTION_ARGS) |
195 | 0 | { |
196 | 0 | ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0); |
197 | 0 | ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1); |
198 | |
|
199 | 0 | PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) < 0); |
200 | 0 | } |
201 | | |
202 | | Datum |
203 | | tidle(PG_FUNCTION_ARGS) |
204 | 0 | { |
205 | 0 | ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0); |
206 | 0 | ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1); |
207 | |
|
208 | 0 | PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) <= 0); |
209 | 0 | } |
210 | | |
211 | | Datum |
212 | | tidgt(PG_FUNCTION_ARGS) |
213 | 0 | { |
214 | 0 | ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0); |
215 | 0 | ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1); |
216 | |
|
217 | 0 | PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) > 0); |
218 | 0 | } |
219 | | |
220 | | Datum |
221 | | tidge(PG_FUNCTION_ARGS) |
222 | 0 | { |
223 | 0 | ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0); |
224 | 0 | ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1); |
225 | |
|
226 | 0 | PG_RETURN_BOOL(ItemPointerCompare(arg1, arg2) >= 0); |
227 | 0 | } |
228 | | |
229 | | Datum |
230 | | bttidcmp(PG_FUNCTION_ARGS) |
231 | 0 | { |
232 | 0 | ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0); |
233 | 0 | ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1); |
234 | |
|
235 | 0 | PG_RETURN_INT32(ItemPointerCompare(arg1, arg2)); |
236 | 0 | } |
237 | | |
238 | | Datum |
239 | | tidlarger(PG_FUNCTION_ARGS) |
240 | 0 | { |
241 | 0 | ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0); |
242 | 0 | ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1); |
243 | |
|
244 | 0 | PG_RETURN_ITEMPOINTER(ItemPointerCompare(arg1, arg2) >= 0 ? arg1 : arg2); |
245 | 0 | } |
246 | | |
247 | | Datum |
248 | | tidsmaller(PG_FUNCTION_ARGS) |
249 | 0 | { |
250 | 0 | ItemPointer arg1 = PG_GETARG_ITEMPOINTER(0); |
251 | 0 | ItemPointer arg2 = PG_GETARG_ITEMPOINTER(1); |
252 | |
|
253 | 0 | PG_RETURN_ITEMPOINTER(ItemPointerCompare(arg1, arg2) <= 0 ? arg1 : arg2); |
254 | 0 | } |
255 | | |
256 | | Datum |
257 | | hashtid(PG_FUNCTION_ARGS) |
258 | 0 | { |
259 | 0 | 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 | 0 | return hash_any((unsigned char *) key, |
268 | 0 | sizeof(BlockIdData) + sizeof(OffsetNumber)); |
269 | 0 | } |
270 | | |
271 | | Datum |
272 | | hashtidextended(PG_FUNCTION_ARGS) |
273 | 0 | { |
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 | 0 | sizeof(BlockIdData) + sizeof(OffsetNumber), |
280 | 0 | seed); |
281 | 0 | } |
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 | | currtid_internal(Relation rel, ItemPointer tid) |
297 | 0 | { |
298 | 0 | ItemPointer result; |
299 | 0 | AclResult aclresult; |
300 | 0 | Snapshot snapshot; |
301 | 0 | TableScanDesc scan; |
302 | |
|
303 | 0 | result = (ItemPointer) palloc(sizeof(ItemPointerData)); |
304 | |
|
305 | 0 | aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), |
306 | 0 | ACL_SELECT); |
307 | 0 | if (aclresult != ACLCHECK_OK) |
308 | 0 | aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind), |
309 | 0 | RelationGetRelationName(rel)); |
310 | |
|
311 | 0 | if (rel->rd_rel->relkind == RELKIND_VIEW) |
312 | 0 | return currtid_for_view(rel, tid); |
313 | | |
314 | 0 | if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind)) |
315 | 0 | ereport(ERROR, |
316 | 0 | errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
317 | 0 | errmsg("cannot look at latest visible tid for relation \"%s.%s\"", |
318 | 0 | get_namespace_name(RelationGetNamespace(rel)), |
319 | 0 | RelationGetRelationName(rel))); |
320 | | |
321 | 0 | ItemPointerCopy(tid, result); |
322 | |
|
323 | 0 | snapshot = RegisterSnapshot(GetLatestSnapshot()); |
324 | 0 | scan = table_beginscan_tid(rel, snapshot); |
325 | 0 | table_tuple_get_latest_tid(scan, result); |
326 | 0 | table_endscan(scan); |
327 | 0 | UnregisterSnapshot(snapshot); |
328 | |
|
329 | 0 | return result; |
330 | 0 | } |
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 | | currtid_for_view(Relation viewrel, ItemPointer tid) |
339 | 0 | { |
340 | 0 | TupleDesc att = RelationGetDescr(viewrel); |
341 | 0 | RuleLock *rulelock; |
342 | 0 | RewriteRule *rewrite; |
343 | 0 | int i, |
344 | 0 | natts = att->natts, |
345 | 0 | tididx = -1; |
346 | |
|
347 | 0 | for (i = 0; i < natts; i++) |
348 | 0 | { |
349 | 0 | Form_pg_attribute attr = TupleDescAttr(att, i); |
350 | |
|
351 | 0 | if (strcmp(NameStr(attr->attname), "ctid") == 0) |
352 | 0 | { |
353 | 0 | if (attr->atttypid != TIDOID) |
354 | 0 | ereport(ERROR, |
355 | 0 | errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
356 | 0 | errmsg("ctid isn't of type TID")); |
357 | 0 | tididx = i; |
358 | 0 | break; |
359 | 0 | } |
360 | 0 | } |
361 | 0 | if (tididx < 0) |
362 | 0 | ereport(ERROR, |
363 | 0 | errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
364 | 0 | errmsg("currtid cannot handle views with no CTID")); |
365 | 0 | rulelock = viewrel->rd_rules; |
366 | 0 | if (!rulelock) |
367 | 0 | ereport(ERROR, |
368 | 0 | errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
369 | 0 | errmsg("the view has no rules")); |
370 | 0 | for (i = 0; i < rulelock->numLocks; i++) |
371 | 0 | { |
372 | 0 | rewrite = rulelock->rules[i]; |
373 | 0 | if (rewrite->event == CMD_SELECT) |
374 | 0 | { |
375 | 0 | Query *query; |
376 | 0 | TargetEntry *tle; |
377 | |
|
378 | 0 | if (list_length(rewrite->actions) != 1) |
379 | 0 | ereport(ERROR, |
380 | 0 | errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
381 | 0 | errmsg("only one select rule is allowed in views")); |
382 | 0 | query = (Query *) linitial(rewrite->actions); |
383 | 0 | tle = get_tle_by_resno(query->targetList, tididx + 1); |
384 | 0 | if (tle && tle->expr && IsA(tle->expr, Var)) |
385 | 0 | { |
386 | 0 | Var *var = (Var *) tle->expr; |
387 | 0 | RangeTblEntry *rte; |
388 | |
|
389 | 0 | if (!IS_SPECIAL_VARNO(var->varno) && |
390 | 0 | var->varattno == SelfItemPointerAttributeNumber) |
391 | 0 | { |
392 | 0 | rte = rt_fetch(var->varno, query->rtable); |
393 | 0 | if (rte) |
394 | 0 | { |
395 | 0 | ItemPointer result; |
396 | 0 | Relation rel; |
397 | |
|
398 | 0 | rel = table_open(rte->relid, AccessShareLock); |
399 | 0 | result = currtid_internal(rel, tid); |
400 | 0 | table_close(rel, AccessShareLock); |
401 | 0 | return result; |
402 | 0 | } |
403 | 0 | } |
404 | 0 | } |
405 | 0 | break; |
406 | 0 | } |
407 | 0 | } |
408 | 0 | elog(ERROR, "currtid cannot handle this view"); |
409 | 0 | return NULL; |
410 | 0 | } |
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 | | currtid_byrelname(PG_FUNCTION_ARGS) |
419 | 0 | { |
420 | 0 | text *relname = PG_GETARG_TEXT_PP(0); |
421 | 0 | ItemPointer tid = PG_GETARG_ITEMPOINTER(1); |
422 | 0 | ItemPointer result; |
423 | 0 | RangeVar *relrv; |
424 | 0 | Relation rel; |
425 | |
|
426 | 0 | relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); |
427 | 0 | rel = table_openrv(relrv, AccessShareLock); |
428 | | |
429 | | /* grab the latest tuple version associated to this CTID */ |
430 | 0 | result = currtid_internal(rel, tid); |
431 | |
|
432 | 0 | table_close(rel, AccessShareLock); |
433 | |
|
434 | 0 | PG_RETURN_ITEMPOINTER(result); |
435 | 0 | } |