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