Coverage Report

Created: 2026-09-28 06:55

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/access/rmgrdesc/xactdesc.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * xactdesc.c
4
 *    rmgr descriptor routines for access/transam/xact.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/xactdesc.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
#include "postgres.h"
16
17
#include "access/transam.h"
18
#include "access/xact.h"
19
#include "replication/origin.h"
20
#include "storage/sinval.h"
21
#include "storage/standbydefs.h"
22
#include "utils/timestamp.h"
23
24
/*
25
 * Parse the WAL format of an xact commit and abort records into an easier to
26
 * understand format.
27
 *
28
 * These routines are in xactdesc.c because they're accessed in backend (when
29
 * replaying WAL) and frontend (pg_waldump) code. This file is the only xact
30
 * specific one shared between both. They're complicated enough that
31
 * duplication would be bothersome.
32
 */
33
34
void
35
ParseCommitRecord(uint8 info, xl_xact_commit *xlrec, xl_xact_parsed_commit *parsed)
36
0
{
37
0
  char     *data = ((char *) xlrec) + MinSizeOfXactCommit;
38
39
0
  memset(parsed, 0, sizeof(*parsed));
40
41
0
  parsed->xinfo = 0;      /* default, if no XLOG_XACT_HAS_INFO is
42
                 * present */
43
44
0
  parsed->xact_time = xlrec->xact_time;
45
46
0
  if (info & XLOG_XACT_HAS_INFO)
47
0
  {
48
0
    xl_xact_xinfo *xl_xinfo = (xl_xact_xinfo *) data;
49
50
0
    parsed->xinfo = xl_xinfo->xinfo;
51
52
0
    data += sizeof(xl_xact_xinfo);
53
0
  }
54
55
0
  if (parsed->xinfo & XACT_XINFO_HAS_DBINFO)
56
0
  {
57
0
    xl_xact_dbinfo *xl_dbinfo = (xl_xact_dbinfo *) data;
58
59
0
    parsed->dbId = xl_dbinfo->dbId;
60
0
    parsed->tsId = xl_dbinfo->tsId;
61
62
0
    data += sizeof(xl_xact_dbinfo);
63
0
  }
64
65
0
  if (parsed->xinfo & XACT_XINFO_HAS_SUBXACTS)
66
0
  {
67
0
    xl_xact_subxacts *xl_subxacts = (xl_xact_subxacts *) data;
68
69
0
    parsed->nsubxacts = xl_subxacts->nsubxacts;
70
0
    parsed->subxacts = xl_subxacts->subxacts;
71
72
0
    data += MinSizeOfXactSubxacts;
73
0
    data += parsed->nsubxacts * sizeof(TransactionId);
74
0
  }
75
76
0
  if (parsed->xinfo & XACT_XINFO_HAS_RELFILELOCATORS)
77
0
  {
78
0
    xl_xact_relfilelocators *xl_rellocators = (xl_xact_relfilelocators *) data;
79
80
0
    parsed->nrels = xl_rellocators->nrels;
81
0
    parsed->xlocators = xl_rellocators->xlocators;
82
83
0
    data += MinSizeOfXactRelfileLocators;
84
0
    data += xl_rellocators->nrels * sizeof(RelFileLocator);
85
0
  }
86
87
0
  if (parsed->xinfo & XACT_XINFO_HAS_DROPPED_STATS)
88
0
  {
89
0
    xl_xact_stats_items *xl_drops = (xl_xact_stats_items *) data;
90
91
0
    parsed->nstats = xl_drops->nitems;
92
0
    parsed->stats = xl_drops->items;
93
94
0
    data += MinSizeOfXactStatsItems;
95
0
    data += xl_drops->nitems * sizeof(xl_xact_stats_item);
96
0
  }
97
98
0
  if (parsed->xinfo & XACT_XINFO_HAS_INVALS)
99
0
  {
100
0
    xl_xact_invals *xl_invals = (xl_xact_invals *) data;
101
102
0
    parsed->nmsgs = xl_invals->nmsgs;
103
0
    parsed->msgs = xl_invals->msgs;
104
105
0
    data += MinSizeOfXactInvals;
106
0
    data += xl_invals->nmsgs * sizeof(SharedInvalidationMessage);
107
0
  }
108
109
0
  if (parsed->xinfo & XACT_XINFO_HAS_TWOPHASE)
110
0
  {
111
0
    xl_xact_twophase *xl_twophase = (xl_xact_twophase *) data;
112
113
0
    parsed->twophase_xid = xl_twophase->xid;
114
115
0
    data += sizeof(xl_xact_twophase);
116
117
0
    if (parsed->xinfo & XACT_XINFO_HAS_GID)
118
0
    {
119
0
      strlcpy(parsed->twophase_gid, data, sizeof(parsed->twophase_gid));
120
0
      data += strlen(data) + 1;
121
0
    }
122
0
  }
123
124
  /* Note: no alignment is guaranteed after this point */
125
126
0
  if (parsed->xinfo & XACT_XINFO_HAS_ORIGIN)
127
0
  {
128
0
    xl_xact_origin xl_origin;
129
130
    /* no alignment is guaranteed, so copy onto stack */
131
0
    memcpy(&xl_origin, data, sizeof(xl_origin));
132
133
0
    parsed->origin_lsn = xl_origin.origin_lsn;
134
0
    parsed->origin_timestamp = xl_origin.origin_timestamp;
135
136
0
    data += sizeof(xl_xact_origin);
137
0
  }
138
0
}
139
140
void
141
ParseAbortRecord(uint8 info, xl_xact_abort *xlrec, xl_xact_parsed_abort *parsed)
142
0
{
143
0
  char     *data = ((char *) xlrec) + MinSizeOfXactAbort;
144
145
0
  memset(parsed, 0, sizeof(*parsed));
146
147
0
  parsed->xinfo = 0;      /* default, if no XLOG_XACT_HAS_INFO is
148
                 * present */
149
150
0
  parsed->xact_time = xlrec->xact_time;
151
152
0
  if (info & XLOG_XACT_HAS_INFO)
153
0
  {
154
0
    xl_xact_xinfo *xl_xinfo = (xl_xact_xinfo *) data;
155
156
0
    parsed->xinfo = xl_xinfo->xinfo;
157
158
0
    data += sizeof(xl_xact_xinfo);
159
0
  }
160
161
0
  if (parsed->xinfo & XACT_XINFO_HAS_DBINFO)
162
0
  {
163
0
    xl_xact_dbinfo *xl_dbinfo = (xl_xact_dbinfo *) data;
164
165
0
    parsed->dbId = xl_dbinfo->dbId;
166
0
    parsed->tsId = xl_dbinfo->tsId;
167
168
0
    data += sizeof(xl_xact_dbinfo);
169
0
  }
170
171
0
  if (parsed->xinfo & XACT_XINFO_HAS_SUBXACTS)
172
0
  {
173
0
    xl_xact_subxacts *xl_subxacts = (xl_xact_subxacts *) data;
174
175
0
    parsed->nsubxacts = xl_subxacts->nsubxacts;
176
0
    parsed->subxacts = xl_subxacts->subxacts;
177
178
0
    data += MinSizeOfXactSubxacts;
179
0
    data += parsed->nsubxacts * sizeof(TransactionId);
180
0
  }
181
182
0
  if (parsed->xinfo & XACT_XINFO_HAS_RELFILELOCATORS)
183
0
  {
184
0
    xl_xact_relfilelocators *xl_rellocator = (xl_xact_relfilelocators *) data;
185
186
0
    parsed->nrels = xl_rellocator->nrels;
187
0
    parsed->xlocators = xl_rellocator->xlocators;
188
189
0
    data += MinSizeOfXactRelfileLocators;
190
0
    data += xl_rellocator->nrels * sizeof(RelFileLocator);
191
0
  }
192
193
0
  if (parsed->xinfo & XACT_XINFO_HAS_DROPPED_STATS)
194
0
  {
195
0
    xl_xact_stats_items *xl_drops = (xl_xact_stats_items *) data;
196
197
0
    parsed->nstats = xl_drops->nitems;
198
0
    parsed->stats = xl_drops->items;
199
200
0
    data += MinSizeOfXactStatsItems;
201
0
    data += xl_drops->nitems * sizeof(xl_xact_stats_item);
202
0
  }
203
204
0
  if (parsed->xinfo & XACT_XINFO_HAS_TWOPHASE)
205
0
  {
206
0
    xl_xact_twophase *xl_twophase = (xl_xact_twophase *) data;
207
208
0
    parsed->twophase_xid = xl_twophase->xid;
209
210
0
    data += sizeof(xl_xact_twophase);
211
212
0
    if (parsed->xinfo & XACT_XINFO_HAS_GID)
213
0
    {
214
0
      strlcpy(parsed->twophase_gid, data, sizeof(parsed->twophase_gid));
215
0
      data += strlen(data) + 1;
216
0
    }
217
0
  }
218
219
  /* Note: no alignment is guaranteed after this point */
220
221
0
  if (parsed->xinfo & XACT_XINFO_HAS_ORIGIN)
222
0
  {
223
0
    xl_xact_origin xl_origin;
224
225
    /* no alignment is guaranteed, so copy onto stack */
226
0
    memcpy(&xl_origin, data, sizeof(xl_origin));
227
228
0
    parsed->origin_lsn = xl_origin.origin_lsn;
229
0
    parsed->origin_timestamp = xl_origin.origin_timestamp;
230
231
0
    data += sizeof(xl_xact_origin);
232
0
  }
233
0
}
234
235
/*
236
 * ParsePrepareRecord
237
 */
238
void
239
ParsePrepareRecord(uint8 info, xl_xact_prepare *xlrec, xl_xact_parsed_prepare *parsed)
240
0
{
241
0
  char     *bufptr;
242
243
0
  bufptr = ((char *) xlrec) + MAXALIGN(sizeof(xl_xact_prepare));
244
245
0
  memset(parsed, 0, sizeof(*parsed));
246
247
0
  parsed->xact_time = xlrec->prepared_at;
248
0
  parsed->origin_lsn = xlrec->origin_lsn;
249
0
  parsed->origin_timestamp = xlrec->origin_timestamp;
250
0
  parsed->twophase_xid = xlrec->xid;
251
0
  parsed->dbId = xlrec->database;
252
0
  parsed->nsubxacts = xlrec->nsubxacts;
253
0
  parsed->nrels = xlrec->ncommitrels;
254
0
  parsed->nabortrels = xlrec->nabortrels;
255
0
  parsed->nstats = xlrec->ncommitstats;
256
0
  parsed->nabortstats = xlrec->nabortstats;
257
0
  parsed->nmsgs = xlrec->ninvalmsgs;
258
259
0
  strlcpy(parsed->twophase_gid, bufptr, GIDSIZE);
260
0
  bufptr += MAXALIGN(xlrec->gidlen);
261
262
0
  parsed->subxacts = (TransactionId *) bufptr;
263
0
  bufptr += MAXALIGN(xlrec->nsubxacts * sizeof(TransactionId));
264
265
0
  parsed->xlocators = (RelFileLocator *) bufptr;
266
0
  bufptr += MAXALIGN(xlrec->ncommitrels * sizeof(RelFileLocator));
267
268
0
  parsed->abortlocators = (RelFileLocator *) bufptr;
269
0
  bufptr += MAXALIGN(xlrec->nabortrels * sizeof(RelFileLocator));
270
271
0
  parsed->stats = (xl_xact_stats_item *) bufptr;
272
0
  bufptr += MAXALIGN(xlrec->ncommitstats * sizeof(xl_xact_stats_item));
273
274
0
  parsed->abortstats = (xl_xact_stats_item *) bufptr;
275
0
  bufptr += MAXALIGN(xlrec->nabortstats * sizeof(xl_xact_stats_item));
276
277
0
  parsed->msgs = (SharedInvalidationMessage *) bufptr;
278
0
  bufptr += MAXALIGN(xlrec->ninvalmsgs * sizeof(SharedInvalidationMessage));
279
0
}
280
281
static void
282
xact_desc_relations(StringInfo buf, char *label, int nrels,
283
          RelFileLocator *xlocators)
284
0
{
285
0
  int     i;
286
287
0
  if (nrels > 0)
288
0
  {
289
0
    appendStringInfo(buf, "; %s:", label);
290
0
    for (i = 0; i < nrels; i++)
291
0
    {
292
0
      appendStringInfo(buf, " %s",
293
0
               relpathperm(xlocators[i], MAIN_FORKNUM).str);
294
0
    }
295
0
  }
296
0
}
297
298
static void
299
xact_desc_subxacts(StringInfo buf, int nsubxacts, TransactionId *subxacts)
300
0
{
301
0
  int     i;
302
303
0
  if (nsubxacts > 0)
304
0
  {
305
0
    appendStringInfoString(buf, "; subxacts:");
306
0
    for (i = 0; i < nsubxacts; i++)
307
0
      appendStringInfo(buf, " %u", subxacts[i]);
308
0
  }
309
0
}
310
311
static void
312
xact_desc_stats(StringInfo buf, const char *label,
313
        int ndropped, xl_xact_stats_item *dropped_stats)
314
0
{
315
0
  int     i;
316
317
0
  if (ndropped > 0)
318
0
  {
319
0
    appendStringInfo(buf, "; %sdropped stats:", label);
320
0
    for (i = 0; i < ndropped; i++)
321
0
    {
322
0
      uint64    objid =
323
0
        ((uint64) dropped_stats[i].objid_hi) << 32 | dropped_stats[i].objid_lo;
324
325
0
      appendStringInfo(buf, " %d/%u/%" PRIu64,
326
0
               dropped_stats[i].kind,
327
0
               dropped_stats[i].dboid,
328
0
               objid);
329
0
    }
330
0
  }
331
0
}
332
333
static void
334
xact_desc_commit(StringInfo buf, uint8 info, xl_xact_commit *xlrec, ReplOriginId origin_id)
335
0
{
336
0
  xl_xact_parsed_commit parsed;
337
338
0
  ParseCommitRecord(info, xlrec, &parsed);
339
340
  /* If this is a prepared xact, show the xid of the original xact */
341
0
  if (TransactionIdIsValid(parsed.twophase_xid))
342
0
    appendStringInfo(buf, "%u: ", parsed.twophase_xid);
343
344
0
  appendStringInfoString(buf, timestamptz_to_str(xlrec->xact_time));
345
346
0
  xact_desc_relations(buf, "rels", parsed.nrels, parsed.xlocators);
347
0
  xact_desc_subxacts(buf, parsed.nsubxacts, parsed.subxacts);
348
0
  xact_desc_stats(buf, "", parsed.nstats, parsed.stats);
349
350
0
  standby_desc_invalidations(buf, parsed.nmsgs, parsed.msgs, parsed.dbId,
351
0
                 parsed.tsId,
352
0
                 XactCompletionRelcacheInitFileInval(parsed.xinfo));
353
354
0
  if (XactCompletionApplyFeedback(parsed.xinfo))
355
0
    appendStringInfoString(buf, "; apply_feedback");
356
357
0
  if (XactCompletionForceSyncCommit(parsed.xinfo))
358
0
    appendStringInfoString(buf, "; sync");
359
360
0
  if (parsed.xinfo & XACT_XINFO_HAS_ORIGIN)
361
0
  {
362
0
    appendStringInfo(buf, "; origin: node %u, lsn %X/%08X, at %s",
363
0
             origin_id,
364
0
             LSN_FORMAT_ARGS(parsed.origin_lsn),
365
0
             timestamptz_to_str(parsed.origin_timestamp));
366
0
  }
367
0
}
368
369
static void
370
xact_desc_abort(StringInfo buf, uint8 info, xl_xact_abort *xlrec, ReplOriginId origin_id)
371
0
{
372
0
  xl_xact_parsed_abort parsed;
373
374
0
  ParseAbortRecord(info, xlrec, &parsed);
375
376
  /* If this is a prepared xact, show the xid of the original xact */
377
0
  if (TransactionIdIsValid(parsed.twophase_xid))
378
0
    appendStringInfo(buf, "%u: ", parsed.twophase_xid);
379
380
0
  appendStringInfoString(buf, timestamptz_to_str(xlrec->xact_time));
381
382
0
  xact_desc_relations(buf, "rels", parsed.nrels, parsed.xlocators);
383
0
  xact_desc_subxacts(buf, parsed.nsubxacts, parsed.subxacts);
384
385
0
  if (parsed.xinfo & XACT_XINFO_HAS_ORIGIN)
386
0
  {
387
0
    appendStringInfo(buf, "; origin: node %u, lsn %X/%08X, at %s",
388
0
             origin_id,
389
0
             LSN_FORMAT_ARGS(parsed.origin_lsn),
390
0
             timestamptz_to_str(parsed.origin_timestamp));
391
0
  }
392
393
0
  xact_desc_stats(buf, "", parsed.nstats, parsed.stats);
394
0
}
395
396
static void
397
xact_desc_prepare(StringInfo buf, uint8 info, xl_xact_prepare *xlrec, ReplOriginId origin_id)
398
0
{
399
0
  xl_xact_parsed_prepare parsed;
400
401
0
  ParsePrepareRecord(info, xlrec, &parsed);
402
403
0
  appendStringInfo(buf, "gid %s: ", parsed.twophase_gid);
404
0
  appendStringInfoString(buf, timestamptz_to_str(parsed.xact_time));
405
406
0
  xact_desc_relations(buf, "rels(commit)", parsed.nrels, parsed.xlocators);
407
0
  xact_desc_relations(buf, "rels(abort)", parsed.nabortrels,
408
0
            parsed.abortlocators);
409
0
  xact_desc_stats(buf, "commit ", parsed.nstats, parsed.stats);
410
0
  xact_desc_stats(buf, "abort ", parsed.nabortstats, parsed.abortstats);
411
0
  xact_desc_subxacts(buf, parsed.nsubxacts, parsed.subxacts);
412
413
0
  standby_desc_invalidations(buf, parsed.nmsgs, parsed.msgs, parsed.dbId,
414
0
                 parsed.tsId, xlrec->initfileinval);
415
416
  /*
417
   * Check if the replication origin has been set in this record in the same
418
   * way as PrepareRedoAdd().
419
   */
420
0
  if (origin_id != InvalidReplOriginId)
421
0
    appendStringInfo(buf, "; origin: node %u, lsn %X/%08X, at %s",
422
0
             origin_id,
423
0
             LSN_FORMAT_ARGS(parsed.origin_lsn),
424
0
             timestamptz_to_str(parsed.origin_timestamp));
425
0
}
426
427
static void
428
xact_desc_assignment(StringInfo buf, xl_xact_assignment *xlrec)
429
0
{
430
0
  int     i;
431
432
0
  appendStringInfoString(buf, "subxacts:");
433
434
0
  for (i = 0; i < xlrec->nsubxacts; i++)
435
0
    appendStringInfo(buf, " %u", xlrec->xsub[i]);
436
0
}
437
438
void
439
xact_desc(StringInfo buf, XLogReaderState *record)
440
0
{
441
0
  char     *rec = XLogRecGetData(record);
442
0
  uint8   info = XLogRecGetInfo(record) & XLOG_XACT_OPMASK;
443
444
0
  if (info == XLOG_XACT_COMMIT || info == XLOG_XACT_COMMIT_PREPARED)
445
0
  {
446
0
    xl_xact_commit *xlrec = (xl_xact_commit *) rec;
447
448
0
    xact_desc_commit(buf, XLogRecGetInfo(record), xlrec,
449
0
             XLogRecGetOrigin(record));
450
0
  }
451
0
  else if (info == XLOG_XACT_ABORT || info == XLOG_XACT_ABORT_PREPARED)
452
0
  {
453
0
    xl_xact_abort *xlrec = (xl_xact_abort *) rec;
454
455
0
    xact_desc_abort(buf, XLogRecGetInfo(record), xlrec,
456
0
            XLogRecGetOrigin(record));
457
0
  }
458
0
  else if (info == XLOG_XACT_PREPARE)
459
0
  {
460
0
    xl_xact_prepare *xlrec = (xl_xact_prepare *) rec;
461
462
0
    xact_desc_prepare(buf, XLogRecGetInfo(record), xlrec,
463
0
              XLogRecGetOrigin(record));
464
0
  }
465
0
  else if (info == XLOG_XACT_ASSIGNMENT)
466
0
  {
467
0
    xl_xact_assignment *xlrec = (xl_xact_assignment *) rec;
468
469
    /*
470
     * Note that we ignore the WAL record's xid, since we're more
471
     * interested in the top-level xid that issued the record and which
472
     * xids are being reported here.
473
     */
474
0
    appendStringInfo(buf, "xtop %u: ", xlrec->xtop);
475
0
    xact_desc_assignment(buf, xlrec);
476
0
  }
477
0
  else if (info == XLOG_XACT_INVALIDATIONS)
478
0
  {
479
0
    xl_xact_invals *xlrec = (xl_xact_invals *) rec;
480
481
0
    standby_desc_invalidations(buf, xlrec->nmsgs, xlrec->msgs, InvalidOid,
482
0
                   InvalidOid, false);
483
0
  }
484
0
}
485
486
const char *
487
xact_identify(uint8 info)
488
0
{
489
0
  const char *id = NULL;
490
491
0
  switch (info & XLOG_XACT_OPMASK)
492
0
  {
493
0
    case XLOG_XACT_COMMIT:
494
0
      id = "COMMIT";
495
0
      break;
496
0
    case XLOG_XACT_PREPARE:
497
0
      id = "PREPARE";
498
0
      break;
499
0
    case XLOG_XACT_ABORT:
500
0
      id = "ABORT";
501
0
      break;
502
0
    case XLOG_XACT_COMMIT_PREPARED:
503
0
      id = "COMMIT_PREPARED";
504
0
      break;
505
0
    case XLOG_XACT_ABORT_PREPARED:
506
0
      id = "ABORT_PREPARED";
507
0
      break;
508
0
    case XLOG_XACT_ASSIGNMENT:
509
0
      id = "ASSIGNMENT";
510
0
      break;
511
0
    case XLOG_XACT_INVALIDATIONS:
512
0
      id = "INVALIDATION";
513
0
      break;
514
0
  }
515
516
0
  return id;
517
0
}