/*------------------------------------------------------------------------- * * heapdesc.c * rmgr descriptor routines for access/heap/heapam.c * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/backend/access/rmgrdesc/heapdesc.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/heapam_xlog.h" #include "access/rmgrdesc_utils.h" #include "storage/standbydefs.h" /* * NOTE: "keyname" argument cannot have trailing spaces or punctuation * characters */ static void infobits_desc(StringInfo buf, uint8 infobits, const char *keyname) { appendStringInfo(buf, "%s: [", keyname); Assert(buf->data[buf->len - 1] != ' '); if (infobits & XLHL_XMAX_IS_MULTI) appendStringInfoString(buf, "IS_MULTI, "); if (infobits & XLHL_XMAX_LOCK_ONLY) appendStringInfoString(buf, "LOCK_ONLY, "); if (infobits & XLHL_XMAX_EXCL_LOCK) appendStringInfoString(buf, "EXCL_LOCK, "); if (infobits & XLHL_XMAX_KEYSHR_LOCK) appendStringInfoString(buf, "KEYSHR_LOCK, "); if (infobits & XLHL_KEYS_UPDATED) appendStringInfoString(buf, "KEYS_UPDATED, "); if (buf->data[buf->len - 1] == ' ') { /* Truncate-away final unneeded ", " */ Assert(buf->data[buf->len - 2] == ','); buf->len -= 2; buf->data[buf->len] = '\0'; } appendStringInfoChar(buf, ']'); } static void truncate_flags_desc(StringInfo buf, uint8 flags) { appendStringInfoString(buf, "flags: ["); if (flags & XLH_TRUNCATE_CASCADE) appendStringInfoString(buf, "CASCADE, "); if (flags & XLH_TRUNCATE_RESTART_SEQS) appendStringInfoString(buf, "RESTART_SEQS, "); if (buf->data[buf->len - 1] == ' ') { /* Truncate-away final unneeded ", " */ Assert(buf->data[buf->len - 2] == ','); buf->len -= 2; buf->data[buf->len] = '\0'; } appendStringInfoChar(buf, ']'); } static void plan_elem_desc(StringInfo buf, void *plan, void *data) { xlhp_freeze_plan *new_plan = (xlhp_freeze_plan *) plan; OffsetNumber **offsets = data; appendStringInfo(buf, "{ xmax: %u, infomask: %u, infomask2: %u, ntuples: %u", new_plan->xmax, new_plan->t_infomask, new_plan->t_infomask2, new_plan->ntuples); appendStringInfoString(buf, ", offsets:"); array_desc(buf, *offsets, sizeof(OffsetNumber), new_plan->ntuples, &offset_elem_desc, NULL); *offsets += new_plan->ntuples; appendStringInfoString(buf, " }"); } /* * Given a MAXALIGNed buffer returned by XLogRecGetBlockData() and pointed to * by cursor and any xl_heap_prune flags, deserialize the arrays of * OffsetNumbers contained in an XLOG_HEAP2_PRUNE_* record. * * This is in heapdesc.c so it can be shared between heap2_redo and heap2_desc * code, the latter of which is used in frontend (pg_waldump) code. */ void heap_xlog_deserialize_prune_and_freeze(char *cursor, uint8 flags, int *nplans, xlhp_freeze_plan **plans, OffsetNumber **frz_offsets, int *nredirected, OffsetNumber **redirected, int *ndead, OffsetNumber **nowdead, int *nunused, OffsetNumber **nowunused) { if (flags & XLHP_HAS_FREEZE_PLANS) { xlhp_freeze_plans *freeze_plans = (xlhp_freeze_plans *) cursor; *nplans = freeze_plans->nplans; Assert(*nplans > 0); *plans = freeze_plans->plans; cursor += offsetof(xlhp_freeze_plans, plans); cursor += sizeof(xlhp_freeze_plan) * *nplans; } else { *nplans = 0; *plans = NULL; } if (flags & XLHP_HAS_REDIRECTIONS) { xlhp_prune_items *subrecord = (xlhp_prune_items *) cursor; *nredirected = subrecord->ntargets; Assert(*nredirected > 0); *redirected = &subrecord->data[0]; cursor += offsetof(xlhp_prune_items, data); cursor += sizeof(OffsetNumber[2]) * *nredirected; } else { *nredirected = 0; *redirected = NULL; } if (flags & XLHP_HAS_DEAD_ITEMS) { xlhp_prune_items *subrecord = (xlhp_prune_items *) cursor; *ndead = subrecord->ntargets; Assert(*ndead > 0); *nowdead = subrecord->data; cursor += offsetof(xlhp_prune_items, data); cursor += sizeof(OffsetNumber) * *ndead; } else { *ndead = 0; *nowdead = NULL; } if (flags & XLHP_HAS_NOW_UNUSED_ITEMS) { xlhp_prune_items *subrecord = (xlhp_prune_items *) cursor; *nunused = subrecord->ntargets; Assert(*nunused > 0); *nowunused = subrecord->data; cursor += offsetof(xlhp_prune_items, data); cursor += sizeof(OffsetNumber) * *nunused; } else { *nunused = 0; *nowunused = NULL; } *frz_offsets = (OffsetNumber *) cursor; } void heap_desc(StringInfo buf, XLogReaderState *record) { char *rec = XLogRecGetData(record); uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; info &= XLOG_HEAP_OPMASK; if (info == XLOG_HEAP_INSERT) { xl_heap_insert *xlrec = (xl_heap_insert *) rec; appendStringInfo(buf, "off: %u, flags: 0x%02X", xlrec->offnum, xlrec->flags); } else if (info == XLOG_HEAP_DELETE) { xl_heap_delete *xlrec = (xl_heap_delete *) rec; appendStringInfo(buf, "xmax: %u, off: %u, ", xlrec->xmax, xlrec->offnum); infobits_desc(buf, xlrec->infobits_set, "infobits"); appendStringInfo(buf, ", flags: 0x%02X", xlrec->flags); } else if (info == XLOG_HEAP_UPDATE) { xl_heap_update *xlrec = (xl_heap_update *) rec; appendStringInfo(buf, "old_xmax: %u, old_off: %u, ", xlrec->old_xmax, xlrec->old_offnum); infobits_desc(buf, xlrec->old_infobits_set, "old_infobits"); appendStringInfo(buf, ", flags: 0x%02X, new_xmax: %u, new_off: %u", xlrec->flags, xlrec->new_xmax, xlrec->new_offnum); } else if (info == XLOG_HEAP_HOT_UPDATE) { xl_heap_update *xlrec = (xl_heap_update *) rec; appendStringInfo(buf, "old_xmax: %u, old_off: %u, ", xlrec->old_xmax, xlrec->old_offnum); infobits_desc(buf, xlrec->old_infobits_set, "old_infobits"); appendStringInfo(buf, ", flags: 0x%02X, new_xmax: %u, new_off: %u", xlrec->flags, xlrec->new_xmax, xlrec->new_offnum); } else if (info == XLOG_HEAP_TRUNCATE) { xl_heap_truncate *xlrec = (xl_heap_truncate *) rec; truncate_flags_desc(buf, xlrec->flags); appendStringInfo(buf, ", nrelids: %u", xlrec->nrelids); appendStringInfoString(buf, ", relids:"); array_desc(buf, xlrec->relids, sizeof(Oid), xlrec->nrelids, &oid_elem_desc, NULL); } else if (info == XLOG_HEAP_CONFIRM) { xl_heap_confirm *xlrec = (xl_heap_confirm *) rec; appendStringInfo(buf, "off: %u", xlrec->offnum); } else if (info == XLOG_HEAP_LOCK) { xl_heap_lock *xlrec = (xl_heap_lock *) rec; appendStringInfo(buf, "xmax: %u, off: %u, ", xlrec->xmax, xlrec->offnum); infobits_desc(buf, xlrec->infobits_set, "infobits"); appendStringInfo(buf, ", flags: 0x%02X", xlrec->flags); } else if (info == XLOG_HEAP_INPLACE) { xl_heap_inplace *xlrec = (xl_heap_inplace *) rec; appendStringInfo(buf, "off: %u", xlrec->offnum); standby_desc_invalidations(buf, xlrec->nmsgs, xlrec->msgs, xlrec->dbId, xlrec->tsId, xlrec->relcacheInitFileInval); } } void heap2_desc(StringInfo buf, XLogReaderState *record) { char *rec = XLogRecGetData(record); uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; info &= XLOG_HEAP_OPMASK; if (info == XLOG_HEAP2_PRUNE_ON_ACCESS || info == XLOG_HEAP2_PRUNE_VACUUM_SCAN || info == XLOG_HEAP2_PRUNE_VACUUM_CLEANUP) { xl_heap_prune *xlrec = (xl_heap_prune *) rec; if (xlrec->flags & XLHP_HAS_CONFLICT_HORIZON) { TransactionId conflict_xid; memcpy(&conflict_xid, rec + SizeOfHeapPrune, sizeof(TransactionId)); appendStringInfo(buf, "snapshotConflictHorizon: %u", conflict_xid); } appendStringInfo(buf, ", isCatalogRel: %c", xlrec->flags & XLHP_IS_CATALOG_REL ? 'T' : 'F'); if (XLogRecHasBlockData(record, 0)) { Size datalen; OffsetNumber *redirected; OffsetNumber *nowdead; OffsetNumber *nowunused; int nredirected; int nunused; int ndead; int nplans; xlhp_freeze_plan *plans; OffsetNumber *frz_offsets; char *cursor = XLogRecGetBlockData(record, 0, &datalen); heap_xlog_deserialize_prune_and_freeze(cursor, xlrec->flags, &nplans, &plans, &frz_offsets, &nredirected, &redirected, &ndead, &nowdead, &nunused, &nowunused); appendStringInfo(buf, ", nplans: %u, nredirected: %u, ndead: %u, nunused: %u", nplans, nredirected, ndead, nunused); if (nplans > 0) { appendStringInfoString(buf, ", plans:"); array_desc(buf, plans, sizeof(xlhp_freeze_plan), nplans, &plan_elem_desc, &frz_offsets); } if (nredirected > 0) { appendStringInfoString(buf, ", redirected:"); array_desc(buf, redirected, sizeof(OffsetNumber) * 2, nredirected, &redirect_elem_desc, NULL); } if (ndead > 0) { appendStringInfoString(buf, ", dead:"); array_desc(buf, nowdead, sizeof(OffsetNumber), ndead, &offset_elem_desc, NULL); } if (nunused > 0) { appendStringInfoString(buf, ", unused:"); array_desc(buf, nowunused, sizeof(OffsetNumber), nunused, &offset_elem_desc, NULL); } } } else if (info == XLOG_HEAP2_VISIBLE) { xl_heap_visible *xlrec = (xl_heap_visible *) rec; appendStringInfo(buf, "snapshotConflictHorizon: %u, flags: 0x%02X", xlrec->snapshotConflictHorizon, xlrec->flags); } else if (info == XLOG_HEAP2_MULTI_INSERT) { xl_heap_multi_insert *xlrec = (xl_heap_multi_insert *) rec; bool isinit = (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE) != 0; appendStringInfo(buf, "ntuples: %d, flags: 0x%02X", xlrec->ntuples, xlrec->flags); if (XLogRecHasBlockData(record, 0) && !isinit) { appendStringInfoString(buf, ", offsets:"); array_desc(buf, xlrec->offsets, sizeof(OffsetNumber), xlrec->ntuples, &offset_elem_desc, NULL); } } else if (info == XLOG_HEAP2_LOCK_UPDATED) { xl_heap_lock_updated *xlrec = (xl_heap_lock_updated *) rec; appendStringInfo(buf, "xmax: %u, off: %u, ", xlrec->xmax, xlrec->offnum); infobits_desc(buf, xlrec->infobits_set, "infobits"); appendStringInfo(buf, ", flags: 0x%02X", xlrec->flags); } else if (info == XLOG_HEAP2_NEW_CID) { xl_heap_new_cid *xlrec = (xl_heap_new_cid *) rec; appendStringInfo(buf, "rel: %u/%u/%u, tid: %u/%u", xlrec->target_locator.spcOid, xlrec->target_locator.dbOid, xlrec->target_locator.relNumber, ItemPointerGetBlockNumber(&(xlrec->target_tid)), ItemPointerGetOffsetNumber(&(xlrec->target_tid))); appendStringInfo(buf, ", cmin: %u, cmax: %u, combo: %u", xlrec->cmin, xlrec->cmax, xlrec->combocid); } } const char * heap_identify(uint8 info) { const char *id = NULL; switch (info & ~XLR_INFO_MASK) { case XLOG_HEAP_INSERT: id = "INSERT"; break; case XLOG_HEAP_INSERT | XLOG_HEAP_INIT_PAGE: id = "INSERT+INIT"; break; case XLOG_HEAP_DELETE: id = "DELETE"; break; case XLOG_HEAP_UPDATE: id = "UPDATE"; break; case XLOG_HEAP_UPDATE | XLOG_HEAP_INIT_PAGE: id = "UPDATE+INIT"; break; case XLOG_HEAP_HOT_UPDATE: id = "HOT_UPDATE"; break; case XLOG_HEAP_HOT_UPDATE | XLOG_HEAP_INIT_PAGE: id = "HOT_UPDATE+INIT"; break; case XLOG_HEAP_TRUNCATE: id = "TRUNCATE"; break; case XLOG_HEAP_CONFIRM: id = "HEAP_CONFIRM"; break; case XLOG_HEAP_LOCK: id = "LOCK"; break; case XLOG_HEAP_INPLACE: id = "INPLACE"; break; } return id; } const char * heap2_identify(uint8 info) { const char *id = NULL; switch (info & ~XLR_INFO_MASK) { case XLOG_HEAP2_PRUNE_ON_ACCESS: id = "PRUNE_ON_ACCESS"; break; case XLOG_HEAP2_PRUNE_VACUUM_SCAN: id = "PRUNE_VACUUM_SCAN"; break; case XLOG_HEAP2_PRUNE_VACUUM_CLEANUP: id = "PRUNE_VACUUM_CLEANUP"; break; case XLOG_HEAP2_VISIBLE: id = "VISIBLE"; break; case XLOG_HEAP2_MULTI_INSERT: id = "MULTI_INSERT"; break; case XLOG_HEAP2_MULTI_INSERT | XLOG_HEAP_INIT_PAGE: id = "MULTI_INSERT+INIT"; break; case XLOG_HEAP2_LOCK_UPDATED: id = "LOCK_UPDATED"; break; case XLOG_HEAP2_NEW_CID: id = "NEW_CID"; break; case XLOG_HEAP2_REWRITE: id = "REWRITE"; break; } return id; }