Coverage Report

Created: 2025-07-03 06:49

/src/postgres/src/backend/access/rmgrdesc/heapdesc.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * heapdesc.c
4
 *    rmgr descriptor routines for access/heap/heapam.c
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/access/rmgrdesc/heapdesc.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
#include "postgres.h"
16
17
#include "access/heapam_xlog.h"
18
#include "access/rmgrdesc_utils.h"
19
#include "storage/standbydefs.h"
20
21
/*
22
 * NOTE: "keyname" argument cannot have trailing spaces or punctuation
23
 * characters
24
 */
25
static void
26
infobits_desc(StringInfo buf, uint8 infobits, const char *keyname)
27
0
{
28
0
  appendStringInfo(buf, "%s: [", keyname);
29
30
0
  Assert(buf->data[buf->len - 1] != ' ');
31
32
0
  if (infobits & XLHL_XMAX_IS_MULTI)
33
0
    appendStringInfoString(buf, "IS_MULTI, ");
34
0
  if (infobits & XLHL_XMAX_LOCK_ONLY)
35
0
    appendStringInfoString(buf, "LOCK_ONLY, ");
36
0
  if (infobits & XLHL_XMAX_EXCL_LOCK)
37
0
    appendStringInfoString(buf, "EXCL_LOCK, ");
38
0
  if (infobits & XLHL_XMAX_KEYSHR_LOCK)
39
0
    appendStringInfoString(buf, "KEYSHR_LOCK, ");
40
0
  if (infobits & XLHL_KEYS_UPDATED)
41
0
    appendStringInfoString(buf, "KEYS_UPDATED, ");
42
43
0
  if (buf->data[buf->len - 1] == ' ')
44
0
  {
45
    /* Truncate-away final unneeded ", "  */
46
0
    Assert(buf->data[buf->len - 2] == ',');
47
0
    buf->len -= 2;
48
0
    buf->data[buf->len] = '\0';
49
0
  }
50
51
0
  appendStringInfoChar(buf, ']');
52
0
}
53
54
static void
55
truncate_flags_desc(StringInfo buf, uint8 flags)
56
0
{
57
0
  appendStringInfoString(buf, "flags: [");
58
59
0
  if (flags & XLH_TRUNCATE_CASCADE)
60
0
    appendStringInfoString(buf, "CASCADE, ");
61
0
  if (flags & XLH_TRUNCATE_RESTART_SEQS)
62
0
    appendStringInfoString(buf, "RESTART_SEQS, ");
63
64
0
  if (buf->data[buf->len - 1] == ' ')
65
0
  {
66
    /* Truncate-away final unneeded ", "  */
67
0
    Assert(buf->data[buf->len - 2] == ',');
68
0
    buf->len -= 2;
69
0
    buf->data[buf->len] = '\0';
70
0
  }
71
72
0
  appendStringInfoChar(buf, ']');
73
0
}
74
75
static void
76
plan_elem_desc(StringInfo buf, void *plan, void *data)
77
0
{
78
0
  xlhp_freeze_plan *new_plan = (xlhp_freeze_plan *) plan;
79
0
  OffsetNumber **offsets = data;
80
81
0
  appendStringInfo(buf, "{ xmax: %u, infomask: %u, infomask2: %u, ntuples: %u",
82
0
           new_plan->xmax,
83
0
           new_plan->t_infomask, new_plan->t_infomask2,
84
0
           new_plan->ntuples);
85
86
0
  appendStringInfoString(buf, ", offsets:");
87
0
  array_desc(buf, *offsets, sizeof(OffsetNumber), new_plan->ntuples,
88
0
         &offset_elem_desc, NULL);
89
90
0
  *offsets += new_plan->ntuples;
91
92
0
  appendStringInfoString(buf, " }");
93
0
}
94
95
96
/*
97
 * Given a MAXALIGNed buffer returned by XLogRecGetBlockData() and pointed to
98
 * by cursor and any xl_heap_prune flags, deserialize the arrays of
99
 * OffsetNumbers contained in an XLOG_HEAP2_PRUNE_* record.
100
 *
101
 * This is in heapdesc.c so it can be shared between heap2_redo and heap2_desc
102
 * code, the latter of which is used in frontend (pg_waldump) code.
103
 */
104
void
105
heap_xlog_deserialize_prune_and_freeze(char *cursor, uint8 flags,
106
                     int *nplans, xlhp_freeze_plan **plans,
107
                     OffsetNumber **frz_offsets,
108
                     int *nredirected, OffsetNumber **redirected,
109
                     int *ndead, OffsetNumber **nowdead,
110
                     int *nunused, OffsetNumber **nowunused)
111
0
{
112
0
  if (flags & XLHP_HAS_FREEZE_PLANS)
113
0
  {
114
0
    xlhp_freeze_plans *freeze_plans = (xlhp_freeze_plans *) cursor;
115
116
0
    *nplans = freeze_plans->nplans;
117
0
    Assert(*nplans > 0);
118
0
    *plans = freeze_plans->plans;
119
120
0
    cursor += offsetof(xlhp_freeze_plans, plans);
121
0
    cursor += sizeof(xlhp_freeze_plan) * *nplans;
122
0
  }
123
0
  else
124
0
  {
125
0
    *nplans = 0;
126
0
    *plans = NULL;
127
0
  }
128
129
0
  if (flags & XLHP_HAS_REDIRECTIONS)
130
0
  {
131
0
    xlhp_prune_items *subrecord = (xlhp_prune_items *) cursor;
132
133
0
    *nredirected = subrecord->ntargets;
134
0
    Assert(*nredirected > 0);
135
0
    *redirected = &subrecord->data[0];
136
137
0
    cursor += offsetof(xlhp_prune_items, data);
138
0
    cursor += sizeof(OffsetNumber[2]) * *nredirected;
139
0
  }
140
0
  else
141
0
  {
142
0
    *nredirected = 0;
143
0
    *redirected = NULL;
144
0
  }
145
146
0
  if (flags & XLHP_HAS_DEAD_ITEMS)
147
0
  {
148
0
    xlhp_prune_items *subrecord = (xlhp_prune_items *) cursor;
149
150
0
    *ndead = subrecord->ntargets;
151
0
    Assert(*ndead > 0);
152
0
    *nowdead = subrecord->data;
153
154
0
    cursor += offsetof(xlhp_prune_items, data);
155
0
    cursor += sizeof(OffsetNumber) * *ndead;
156
0
  }
157
0
  else
158
0
  {
159
0
    *ndead = 0;
160
0
    *nowdead = NULL;
161
0
  }
162
163
0
  if (flags & XLHP_HAS_NOW_UNUSED_ITEMS)
164
0
  {
165
0
    xlhp_prune_items *subrecord = (xlhp_prune_items *) cursor;
166
167
0
    *nunused = subrecord->ntargets;
168
0
    Assert(*nunused > 0);
169
0
    *nowunused = subrecord->data;
170
171
0
    cursor += offsetof(xlhp_prune_items, data);
172
0
    cursor += sizeof(OffsetNumber) * *nunused;
173
0
  }
174
0
  else
175
0
  {
176
0
    *nunused = 0;
177
0
    *nowunused = NULL;
178
0
  }
179
180
0
  *frz_offsets = (OffsetNumber *) cursor;
181
0
}
182
183
void
184
heap_desc(StringInfo buf, XLogReaderState *record)
185
0
{
186
0
  char     *rec = XLogRecGetData(record);
187
0
  uint8   info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
188
189
0
  info &= XLOG_HEAP_OPMASK;
190
0
  if (info == XLOG_HEAP_INSERT)
191
0
  {
192
0
    xl_heap_insert *xlrec = (xl_heap_insert *) rec;
193
194
0
    appendStringInfo(buf, "off: %u, flags: 0x%02X",
195
0
             xlrec->offnum,
196
0
             xlrec->flags);
197
0
  }
198
0
  else if (info == XLOG_HEAP_DELETE)
199
0
  {
200
0
    xl_heap_delete *xlrec = (xl_heap_delete *) rec;
201
202
0
    appendStringInfo(buf, "xmax: %u, off: %u, ",
203
0
             xlrec->xmax, xlrec->offnum);
204
0
    infobits_desc(buf, xlrec->infobits_set, "infobits");
205
0
    appendStringInfo(buf, ", flags: 0x%02X", xlrec->flags);
206
0
  }
207
0
  else if (info == XLOG_HEAP_UPDATE)
208
0
  {
209
0
    xl_heap_update *xlrec = (xl_heap_update *) rec;
210
211
0
    appendStringInfo(buf, "old_xmax: %u, old_off: %u, ",
212
0
             xlrec->old_xmax, xlrec->old_offnum);
213
0
    infobits_desc(buf, xlrec->old_infobits_set, "old_infobits");
214
0
    appendStringInfo(buf, ", flags: 0x%02X, new_xmax: %u, new_off: %u",
215
0
             xlrec->flags, xlrec->new_xmax, xlrec->new_offnum);
216
0
  }
217
0
  else if (info == XLOG_HEAP_HOT_UPDATE)
218
0
  {
219
0
    xl_heap_update *xlrec = (xl_heap_update *) rec;
220
221
0
    appendStringInfo(buf, "old_xmax: %u, old_off: %u, ",
222
0
             xlrec->old_xmax, xlrec->old_offnum);
223
0
    infobits_desc(buf, xlrec->old_infobits_set, "old_infobits");
224
0
    appendStringInfo(buf, ", flags: 0x%02X, new_xmax: %u, new_off: %u",
225
0
             xlrec->flags, xlrec->new_xmax, xlrec->new_offnum);
226
0
  }
227
0
  else if (info == XLOG_HEAP_TRUNCATE)
228
0
  {
229
0
    xl_heap_truncate *xlrec = (xl_heap_truncate *) rec;
230
231
0
    truncate_flags_desc(buf, xlrec->flags);
232
0
    appendStringInfo(buf, ", nrelids: %u", xlrec->nrelids);
233
0
    appendStringInfoString(buf, ", relids:");
234
0
    array_desc(buf, xlrec->relids, sizeof(Oid), xlrec->nrelids,
235
0
           &oid_elem_desc, NULL);
236
0
  }
237
0
  else if (info == XLOG_HEAP_CONFIRM)
238
0
  {
239
0
    xl_heap_confirm *xlrec = (xl_heap_confirm *) rec;
240
241
0
    appendStringInfo(buf, "off: %u", xlrec->offnum);
242
0
  }
243
0
  else if (info == XLOG_HEAP_LOCK)
244
0
  {
245
0
    xl_heap_lock *xlrec = (xl_heap_lock *) rec;
246
247
0
    appendStringInfo(buf, "xmax: %u, off: %u, ",
248
0
             xlrec->xmax, xlrec->offnum);
249
0
    infobits_desc(buf, xlrec->infobits_set, "infobits");
250
0
    appendStringInfo(buf, ", flags: 0x%02X", xlrec->flags);
251
0
  }
252
0
  else if (info == XLOG_HEAP_INPLACE)
253
0
  {
254
0
    xl_heap_inplace *xlrec = (xl_heap_inplace *) rec;
255
256
0
    appendStringInfo(buf, "off: %u", xlrec->offnum);
257
0
    standby_desc_invalidations(buf, xlrec->nmsgs, xlrec->msgs,
258
0
                   xlrec->dbId, xlrec->tsId,
259
0
                   xlrec->relcacheInitFileInval);
260
0
  }
261
0
}
262
263
void
264
heap2_desc(StringInfo buf, XLogReaderState *record)
265
0
{
266
0
  char     *rec = XLogRecGetData(record);
267
0
  uint8   info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
268
269
0
  info &= XLOG_HEAP_OPMASK;
270
0
  if (info == XLOG_HEAP2_PRUNE_ON_ACCESS ||
271
0
    info == XLOG_HEAP2_PRUNE_VACUUM_SCAN ||
272
0
    info == XLOG_HEAP2_PRUNE_VACUUM_CLEANUP)
273
0
  {
274
0
    xl_heap_prune *xlrec = (xl_heap_prune *) rec;
275
276
0
    if (xlrec->flags & XLHP_HAS_CONFLICT_HORIZON)
277
0
    {
278
0
      TransactionId conflict_xid;
279
280
0
      memcpy(&conflict_xid, rec + SizeOfHeapPrune, sizeof(TransactionId));
281
282
0
      appendStringInfo(buf, "snapshotConflictHorizon: %u",
283
0
               conflict_xid);
284
0
    }
285
286
0
    appendStringInfo(buf, ", isCatalogRel: %c",
287
0
             xlrec->flags & XLHP_IS_CATALOG_REL ? 'T' : 'F');
288
289
0
    if (XLogRecHasBlockData(record, 0))
290
0
    {
291
0
      Size    datalen;
292
0
      OffsetNumber *redirected;
293
0
      OffsetNumber *nowdead;
294
0
      OffsetNumber *nowunused;
295
0
      int     nredirected;
296
0
      int     nunused;
297
0
      int     ndead;
298
0
      int     nplans;
299
0
      xlhp_freeze_plan *plans;
300
0
      OffsetNumber *frz_offsets;
301
302
0
      char     *cursor = XLogRecGetBlockData(record, 0, &datalen);
303
304
0
      heap_xlog_deserialize_prune_and_freeze(cursor, xlrec->flags,
305
0
                           &nplans, &plans, &frz_offsets,
306
0
                           &nredirected, &redirected,
307
0
                           &ndead, &nowdead,
308
0
                           &nunused, &nowunused);
309
310
0
      appendStringInfo(buf, ", nplans: %u, nredirected: %u, ndead: %u, nunused: %u",
311
0
               nplans, nredirected, ndead, nunused);
312
313
0
      if (nplans > 0)
314
0
      {
315
0
        appendStringInfoString(buf, ", plans:");
316
0
        array_desc(buf, plans, sizeof(xlhp_freeze_plan), nplans,
317
0
               &plan_elem_desc, &frz_offsets);
318
0
      }
319
320
0
      if (nredirected > 0)
321
0
      {
322
0
        appendStringInfoString(buf, ", redirected:");
323
0
        array_desc(buf, redirected, sizeof(OffsetNumber) * 2,
324
0
               nredirected, &redirect_elem_desc, NULL);
325
0
      }
326
327
0
      if (ndead > 0)
328
0
      {
329
0
        appendStringInfoString(buf, ", dead:");
330
0
        array_desc(buf, nowdead, sizeof(OffsetNumber), ndead,
331
0
               &offset_elem_desc, NULL);
332
0
      }
333
334
0
      if (nunused > 0)
335
0
      {
336
0
        appendStringInfoString(buf, ", unused:");
337
0
        array_desc(buf, nowunused, sizeof(OffsetNumber), nunused,
338
0
               &offset_elem_desc, NULL);
339
0
      }
340
0
    }
341
0
  }
342
0
  else if (info == XLOG_HEAP2_VISIBLE)
343
0
  {
344
0
    xl_heap_visible *xlrec = (xl_heap_visible *) rec;
345
346
0
    appendStringInfo(buf, "snapshotConflictHorizon: %u, flags: 0x%02X",
347
0
             xlrec->snapshotConflictHorizon, xlrec->flags);
348
0
  }
349
0
  else if (info == XLOG_HEAP2_MULTI_INSERT)
350
0
  {
351
0
    xl_heap_multi_insert *xlrec = (xl_heap_multi_insert *) rec;
352
0
    bool    isinit = (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE) != 0;
353
354
0
    appendStringInfo(buf, "ntuples: %d, flags: 0x%02X", xlrec->ntuples,
355
0
             xlrec->flags);
356
357
0
    if (XLogRecHasBlockData(record, 0) && !isinit)
358
0
    {
359
0
      appendStringInfoString(buf, ", offsets:");
360
0
      array_desc(buf, xlrec->offsets, sizeof(OffsetNumber),
361
0
             xlrec->ntuples, &offset_elem_desc, NULL);
362
0
    }
363
0
  }
364
0
  else if (info == XLOG_HEAP2_LOCK_UPDATED)
365
0
  {
366
0
    xl_heap_lock_updated *xlrec = (xl_heap_lock_updated *) rec;
367
368
0
    appendStringInfo(buf, "xmax: %u, off: %u, ",
369
0
             xlrec->xmax, xlrec->offnum);
370
0
    infobits_desc(buf, xlrec->infobits_set, "infobits");
371
0
    appendStringInfo(buf, ", flags: 0x%02X", xlrec->flags);
372
0
  }
373
0
  else if (info == XLOG_HEAP2_NEW_CID)
374
0
  {
375
0
    xl_heap_new_cid *xlrec = (xl_heap_new_cid *) rec;
376
377
0
    appendStringInfo(buf, "rel: %u/%u/%u, tid: %u/%u",
378
0
             xlrec->target_locator.spcOid,
379
0
             xlrec->target_locator.dbOid,
380
0
             xlrec->target_locator.relNumber,
381
0
             ItemPointerGetBlockNumber(&(xlrec->target_tid)),
382
0
             ItemPointerGetOffsetNumber(&(xlrec->target_tid)));
383
0
    appendStringInfo(buf, ", cmin: %u, cmax: %u, combo: %u",
384
0
             xlrec->cmin, xlrec->cmax, xlrec->combocid);
385
0
  }
386
0
}
387
388
const char *
389
heap_identify(uint8 info)
390
0
{
391
0
  const char *id = NULL;
392
393
0
  switch (info & ~XLR_INFO_MASK)
394
0
  {
395
0
    case XLOG_HEAP_INSERT:
396
0
      id = "INSERT";
397
0
      break;
398
0
    case XLOG_HEAP_INSERT | XLOG_HEAP_INIT_PAGE:
399
0
      id = "INSERT+INIT";
400
0
      break;
401
0
    case XLOG_HEAP_DELETE:
402
0
      id = "DELETE";
403
0
      break;
404
0
    case XLOG_HEAP_UPDATE:
405
0
      id = "UPDATE";
406
0
      break;
407
0
    case XLOG_HEAP_UPDATE | XLOG_HEAP_INIT_PAGE:
408
0
      id = "UPDATE+INIT";
409
0
      break;
410
0
    case XLOG_HEAP_HOT_UPDATE:
411
0
      id = "HOT_UPDATE";
412
0
      break;
413
0
    case XLOG_HEAP_HOT_UPDATE | XLOG_HEAP_INIT_PAGE:
414
0
      id = "HOT_UPDATE+INIT";
415
0
      break;
416
0
    case XLOG_HEAP_TRUNCATE:
417
0
      id = "TRUNCATE";
418
0
      break;
419
0
    case XLOG_HEAP_CONFIRM:
420
0
      id = "HEAP_CONFIRM";
421
0
      break;
422
0
    case XLOG_HEAP_LOCK:
423
0
      id = "LOCK";
424
0
      break;
425
0
    case XLOG_HEAP_INPLACE:
426
0
      id = "INPLACE";
427
0
      break;
428
0
  }
429
430
0
  return id;
431
0
}
432
433
const char *
434
heap2_identify(uint8 info)
435
0
{
436
0
  const char *id = NULL;
437
438
0
  switch (info & ~XLR_INFO_MASK)
439
0
  {
440
0
    case XLOG_HEAP2_PRUNE_ON_ACCESS:
441
0
      id = "PRUNE_ON_ACCESS";
442
0
      break;
443
0
    case XLOG_HEAP2_PRUNE_VACUUM_SCAN:
444
0
      id = "PRUNE_VACUUM_SCAN";
445
0
      break;
446
0
    case XLOG_HEAP2_PRUNE_VACUUM_CLEANUP:
447
0
      id = "PRUNE_VACUUM_CLEANUP";
448
0
      break;
449
0
    case XLOG_HEAP2_VISIBLE:
450
0
      id = "VISIBLE";
451
0
      break;
452
0
    case XLOG_HEAP2_MULTI_INSERT:
453
0
      id = "MULTI_INSERT";
454
0
      break;
455
0
    case XLOG_HEAP2_MULTI_INSERT | XLOG_HEAP_INIT_PAGE:
456
0
      id = "MULTI_INSERT+INIT";
457
0
      break;
458
0
    case XLOG_HEAP2_LOCK_UPDATED:
459
0
      id = "LOCK_UPDATED";
460
0
      break;
461
0
    case XLOG_HEAP2_NEW_CID:
462
0
      id = "NEW_CID";
463
0
      break;
464
0
    case XLOG_HEAP2_REWRITE:
465
0
      id = "REWRITE";
466
0
      break;
467
0
  }
468
469
0
  return id;
470
0
}