Coverage Report

Created: 2025-06-15 06:31

/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
}