Coverage Report

Created: 2026-08-13 07:12

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