/* -------------------------------------------------------------------------
 *
 * nbtdesc.cpp
 *	  rmgr descriptor routines for access/nbtree/nbtxlog.cpp
 *
 * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
 * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/gausskernel/storage/access/rmgrdesc/nbtdesc.cpp
 *
 * -------------------------------------------------------------------------
 */
#include "postgres.h"
#include "knl/knl_variable.h"
#include "access/ubtreepcr.h"

const char* btree_type_name(uint8 subtype)
{
    uint8 info = subtype & ~XLR_INFO_MASK;

    switch (info) {
        case XLOG_BTREE_INSERT_LEAF: {
            return "bt_insert_leaf";
            break;
        }
        case XLOG_BTREE_INSERT_UPPER: {
            return "bt_insert_upper";
            break;
        }
        case XLOG_BTREE_INSERT_META: {
            return "bt_insert_meta";
            break;
        }
        case XLOG_BTREE_SPLIT_L: {
            return "bt_split_left";
            break;
        }
        case XLOG_BTREE_SPLIT_R: {
            return "bt_split_right";
            break;
        }
        case XLOG_BTREE_SPLIT_L_ROOT: {
            return "bt_split_l_root";
            break;
        }
        case XLOG_BTREE_SPLIT_R_ROOT: {
            return "bt_split_r_root";
            break;
        }
        case XLOG_BTREE_VACUUM: {
            return "bt_vacuum";
            break;
        }
        case XLOG_BTREE_DELETE: {
            return "bt_delete";
            break;
        }
        case XLOG_BTREE_UNLINK_PAGE: {
            return "bt_unlink_page";
            break;
        }
        case XLOG_BTREE_UNLINK_PAGE_META: {
            return "bt_unlink_page_meta";
            break;
        }
        case XLOG_BTREE_MARK_PAGE_HALFDEAD: {
            return "bt_mark_page_halfdead";
            break;
        }
        case XLOG_BTREE_NEWROOT: {
            return "bt_newroot";
            break;
        }
        case XLOG_BTREE_REUSE_PAGE: {
            return "bt_reuse_page";
            break;
        }
        case XLOG_BTREE_INSERT_POST: {
            return "bt_insert_post";
            break;
        }
        case XLOG_BTREE_DEDUP: {
            return "bt_dedup";
            break;
        }
        default:
            return "unkown_type";
            break;
    }
}

void btree_desc(StringInfo buf, XLogReaderState *record)
{
    char *rec = XLogRecGetData(record);
    uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
    bool is_dedup_upgrade = (XLogRecGetInfo(record) & BTREE_DEDUPLICATION_FLAG) != 0;

    switch (info) {
        case XLOG_BTREE_INSERT_LEAF: {
            xl_btree_insert *xlrec = (xl_btree_insert *)rec;

            appendStringInfo(buf, "insert leaf: ");
            appendStringInfo(buf, "off %u", (uint32)xlrec->offnum);
            break;
        }
        case XLOG_BTREE_INSERT_UPPER: {
            xl_btree_insert *xlrec = (xl_btree_insert *)rec;

            appendStringInfo(buf, "insert upper: ");
            appendStringInfo(buf, "off %u", (uint32)xlrec->offnum);
            break;
        }
        case XLOG_BTREE_INSERT_META: {
            xl_btree_insert *xlrec = (xl_btree_insert *)rec;
            appendStringInfo(buf, "insert meta: ");
            appendStringInfo(buf, "off %u", (uint32)xlrec->offnum);
            break;
        }
        case XLOG_BTREE_SPLIT_L: {
            if (!is_dedup_upgrade) {
                xl_btree_split *xlrec = (xl_btree_split *)rec;
                appendStringInfo(buf, "split left: ");
                appendStringInfo(buf, "level %u; firstright %u; new off %u", xlrec->level, (uint32)xlrec->firstright,
                                 (uint32)xlrec->newitemoff);
            } else {
                xl_btree_split_posting *xlrec = (xl_btree_split_posting *)rec;
                appendStringInfo(buf, "split left: ");
                appendStringInfo(buf, "level %u; firstright %u; new off %u; posting off %u", xlrec->level,
                                 (uint32)xlrec->firstright, (uint32)xlrec->newitemoff, (uint32)xlrec->posting_off);
            }
            break;
        }
        case XLOG_BTREE_SPLIT_R: {
            if (!is_dedup_upgrade) {
                xl_btree_split *xlrec = (xl_btree_split *)rec;
                appendStringInfo(buf, "split right: ");

                appendStringInfo(buf, "level %u; firstright %u; new off %u", xlrec->level, (uint32)xlrec->firstright,
                                 (uint32)xlrec->newitemoff);
            } else {
                xl_btree_split_posting *xlrec = (xl_btree_split_posting *)rec;
                appendStringInfo(buf, "split right: ");
                appendStringInfo(buf, "level %u; firstright %u; new off %u; posting off %u", xlrec->level,
                                 (uint32)xlrec->firstright, (uint32)xlrec->newitemoff, (uint32)xlrec->posting_off);
            }
            break;
        }
        case XLOG_BTREE_SPLIT_L_ROOT: {
            xl_btree_split *xlrec = (xl_btree_split *)rec;

            appendStringInfo(buf, "split left root: ");

            appendStringInfo(buf, "level %u; firstright %u; new off %u", xlrec->level, (uint32)xlrec->firstright,
                             (uint32)xlrec->newitemoff);

            break;
        }
        case XLOG_BTREE_SPLIT_R_ROOT: {
            xl_btree_split *xlrec = (xl_btree_split *)rec;

            appendStringInfo(buf, "split right root: ");
            appendStringInfo(buf, "level %u; firstright %u; new off %u", xlrec->level, (uint32)xlrec->firstright,
                             (uint32)xlrec->newitemoff);
            break;
        }
        case XLOG_BTREE_VACUUM: {
            if (!is_dedup_upgrade) {
                xl_btree_vacuum *xlrec = (xl_btree_vacuum *)rec;

                appendStringInfo(buf, "vacuum: lastBlockVacuumed %u ", xlrec->lastBlockVacuumed);
            } else {
                xl_btree_vacuum_posting *xlrec = (xl_btree_vacuum_posting *)rec;

                appendStringInfo(buf, "vacuum: lastBlockVacuumed %u; num_deleted: %hu; num_updated: %hu",
                                 xlrec->lastBlockVacuumed, xlrec->num_deleted, xlrec->num_updated);
            }
            break;
        }
        case XLOG_BTREE_DELETE: {
            xl_btree_delete *xlrec = (xl_btree_delete *)rec;
            int bucket_id = XLogRecGetBucketId(record);
            if (bucket_id == -1) {
                appendStringInfo(buf, "delete: %d items; heap %u/%u/%u", xlrec->nitems, xlrec->hnode.spcNode,
                                 xlrec->hnode.dbNode, xlrec->hnode.relNode);
            } else {
                appendStringInfo(buf, "delete: %d items; heap %u/%u/%d/%u", xlrec->nitems, xlrec->hnode.spcNode,
                                 xlrec->hnode.dbNode, bucket_id, xlrec->hnode.relNode);
            }
            break;
        }
        case XLOG_BTREE_UNLINK_PAGE:
        case XLOG_BTREE_UNLINK_PAGE_META: {
            xl_btree_unlink_page *xlrec = (xl_btree_unlink_page *)rec;

            if (info == XLOG_BTREE_UNLINK_PAGE) {
                appendStringInfo(buf, "unlink page: ");
            } else {
                appendStringInfo(buf, "unlink page meta: ");
            }
            appendStringInfo(
                buf, "leftsib %u; rightsib %u; leafleftsib %u; leafrightsib %u; topparent %u; btpo_xact " XID_FMT "",
                xlrec->leftsib, xlrec->rightsib, xlrec->leafleftsib, xlrec->leafrightsib, xlrec->topparent,
                xlrec->btpo_xact);
            break;
        }
        case XLOG_BTREE_MARK_PAGE_HALFDEAD: {
            xl_btree_mark_page_halfdead *xlrec = (xl_btree_mark_page_halfdead *)rec;

            appendStringInfo(buf, "mark page halfdead: ");
            appendStringInfo(buf, "leaf %u; left %u; right %u; parent %u; parent off %u", xlrec->leafblk,
                             xlrec->leftblk, xlrec->rightblk, xlrec->topparent, (uint32)xlrec->poffset);
            break;
        }
        case XLOG_BTREE_NEWROOT: {
            xl_btree_newroot *xlrec = (xl_btree_newroot *)rec;

            appendStringInfo(buf, "lev %u", xlrec->level);
            break;
        }
        case XLOG_BTREE_REUSE_PAGE: {
            xl_btree_reuse_page *xlrec = (xl_btree_reuse_page *)rec;
            int bucket_id = XLogRecGetBucketId(record);
            if (bucket_id == -1) {
                appendStringInfo(buf, "reuse_page: rel %u/%u/%u; latestRemovedXid " XID_FMT, xlrec->node.spcNode,
                                 xlrec->node.dbNode, xlrec->node.relNode, xlrec->latestRemovedXid);
            } else {
                appendStringInfo(buf, "reuse_page: rel %u/%u/%u/%d; latestRemovedXid " XID_FMT, xlrec->node.spcNode,
                                 xlrec->node.dbNode, xlrec->node.relNode, bucket_id, xlrec->latestRemovedXid);
            }
            break;
        }
        case XLOG_BTREE_INSERT_POST: {
            xl_btree_insert *xlrec = (xl_btree_insert *)rec;

            appendStringInfo(buf, "insert leaf posting: ");
            appendStringInfo(buf, "off %u", (uint32)xlrec->offnum);
            break;
        }
        case XLOG_BTREE_DEDUP:{
            xl_btree_dedup *xlrec = (xl_btree_dedup *) rec;

            appendStringInfo(buf, "btree dedup: ");
			appendStringInfo(buf, "num_intervals %u", xlrec->num_intervals);
			break;
        }
        default:
            appendStringInfo(buf, "UNKNOWN");
            break;
    }
}

const char* ubtree_type_name(uint8 subtype)
{
    uint8 info = subtype & ~XLR_INFO_MASK;
    switch (info) {
        case XLOG_UBTREE_INSERT_LEAF: {
            return "ubt_insert_leaf";
            break;
        }
        case XLOG_UBTREE_INSERT_UPPER: {
            return "ubt_insert_upper";
            break;
        }
        case XLOG_UBTREE_INSERT_META: {
            return "ubt_insert_meta";
            break;
        }
        case XLOG_UBTREE_SPLIT_L: {
            return "ubt_split_l";
            break;
        }
        case XLOG_UBTREE_SPLIT_R: {
            return "ubt_split_r";
            break;
        }
        case XLOG_UBTREE_SPLIT_L_ROOT: {
            return "ubt_split_l_root";
            break;
        }
        case XLOG_UBTREE_SPLIT_R_ROOT: {
            return "ubt_split_r_root";
            break;
        }
        case XLOG_UBTREE_VACUUM: {
            return "ubt_vacuum";
            break;
        }
        case XLOG_UBTREE_DELETE: {
            return "ubt_delete";
            break;
        }
        case XLOG_UBTREE_UNLINK_PAGE: {
            return "ubt_unlink_page";
            break;
        }
        case XLOG_UBTREE_UNLINK_PAGE_META: {
            return "ubt_unlink_page_meta";
            break;
        }
        case XLOG_UBTREE_MARK_PAGE_HALFDEAD: {
            return "ubt_mark_page_halfdead";
            break;
        }
        case XLOG_UBTREE_NEWROOT: {
            return "ubt_newroot";
            break;
        }
        case XLOG_UBTREE_REUSE_PAGE: {
            return "ubt_reuse_page";
            break;
        }
        case XLOG_UBTREE_MARK_DELETE: {
            return "ubt_mark_delete";
            break;
        }
        case XLOG_UBTREE_PRUNE_PAGE: {
            return "ubt_prune_page";
            break;
        }
        default:
            return "unknown_type";
            break;
    }
}


void UBTreeDesc(StringInfo buf, XLogReaderState *record)
{
    char *rec = XLogRecGetData(record);
    uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;

    switch (info) {
        case XLOG_UBTREE_INSERT_LEAF: {
            xl_btree_insert *xlrec = (xl_btree_insert *)rec;

            appendStringInfo(buf, "insert leaf: ");
            appendStringInfo(buf, "off %u", (uint32)xlrec->offnum);
            break;
        }
        case XLOG_UBTREE_INSERT_UPPER: {
            xl_btree_insert *xlrec = (xl_btree_insert *)rec;

            appendStringInfo(buf, "insert upper: ");
            appendStringInfo(buf, "off %u", (uint32)xlrec->offnum);
            break;
        }
        case XLOG_UBTREE_INSERT_META: {
            xl_btree_insert *xlrec = (xl_btree_insert *)rec;
            appendStringInfo(buf, "insert meta: ");
            appendStringInfo(buf, "off %u", (uint32)xlrec->offnum);
            break;
        }
        case XLOG_UBTREE_SPLIT_L: {
            xl_btree_split *xlrec = (xl_btree_split *)rec;

            appendStringInfo(buf, "split left: ");
            appendStringInfo(buf, "level %u; firstright %u; new off %u", xlrec->level, (uint32)xlrec->firstright,
                             (uint32)xlrec->newitemoff);
            break;
        }
        case XLOG_UBTREE_SPLIT_R: {
            xl_btree_split *xlrec = (xl_btree_split *)rec;

            appendStringInfo(buf, "split right: ");

            appendStringInfo(buf, "level %u; firstright %u; new off %u", xlrec->level, (uint32)xlrec->firstright,
                             (uint32)xlrec->newitemoff);

            break;
        }
        case XLOG_UBTREE_SPLIT_L_ROOT: {
            xl_btree_split *xlrec = (xl_btree_split *)rec;

            appendStringInfo(buf, "split left root: ");

            appendStringInfo(buf, "level %u; firstright %u; new off %u", xlrec->level, (uint32)xlrec->firstright,
                             (uint32)xlrec->newitemoff);

            break;
        }
        case XLOG_UBTREE_SPLIT_R_ROOT: {
            xl_btree_split *xlrec = (xl_btree_split *)rec;

            appendStringInfo(buf, "split right root: ");
            appendStringInfo(buf, "level %u; firstright %u; new off %u", xlrec->level, (uint32)xlrec->firstright,
                             (uint32)xlrec->newitemoff);
            break;
        }
        case XLOG_UBTREE_VACUUM: {
            xl_btree_vacuum *xlrec = (xl_btree_vacuum *)rec;

            appendStringInfo(buf, "vacuum: lastBlockVacuumed %u ", xlrec->lastBlockVacuumed);
            break;
        }
        case XLOG_UBTREE_DELETE: {
            xl_btree_delete *xlrec = (xl_btree_delete *)rec;
            int bucket_id = XLogRecGetBucketId(record);
            if (bucket_id == -1) {
                appendStringInfo(buf, "delete: %d items; heap %u/%u/%u", xlrec->nitems, xlrec->hnode.spcNode,
                                 xlrec->hnode.dbNode, xlrec->hnode.relNode);
            } else {
                appendStringInfo(buf, "delete: %d items; heap %u/%u/%d/%u", xlrec->nitems, xlrec->hnode.spcNode,
                                 xlrec->hnode.dbNode, bucket_id, xlrec->hnode.relNode);
            }
            break;
        }
        case XLOG_UBTREE_UNLINK_PAGE:
        case XLOG_UBTREE_UNLINK_PAGE_META: {
            xl_btree_unlink_page *xlrec = (xl_btree_unlink_page *)rec;

            if (info == XLOG_UBTREE_UNLINK_PAGE) {
                appendStringInfo(buf, "unlink page: ");
            } else {
                appendStringInfo(buf, "unlink page meta: ");
            }
            appendStringInfo(buf,
                "leftsib %u; rightsib %u; leafleftsib %u; leafrightsib %u; topparent %u; btpo_xact " XID_FMT "",
                xlrec->leftsib, xlrec->rightsib, xlrec->leafleftsib, xlrec->leafrightsib, xlrec->topparent,
                xlrec->btpo_xact);
            break;
        }
        case XLOG_UBTREE_MARK_PAGE_HALFDEAD: {
            xl_btree_mark_page_halfdead *xlrec = (xl_btree_mark_page_halfdead *)rec;

            appendStringInfo(buf, "mark page halfdead: ");
            appendStringInfo(buf, "leaf %u; left %u; right %u; parent %u; parent off %u", xlrec->leafblk,
                             xlrec->leftblk, xlrec->rightblk, xlrec->topparent, (uint32)xlrec->poffset);
            break;
        }
        case XLOG_UBTREE_NEWROOT: {
            xl_btree_newroot *xlrec = (xl_btree_newroot *)rec;

            appendStringInfo(buf, "lev %u", xlrec->level);
            break;
        }
        case XLOG_UBTREE_REUSE_PAGE: {
            xl_btree_reuse_page *xlrec = (xl_btree_reuse_page *)rec;
            int bucket_id = XLogRecGetBucketId(record);
            if (bucket_id == -1) {
                appendStringInfo(buf, "reuse_page: rel %u/%u/%u; latestRemovedXid " XID_FMT, xlrec->node.spcNode,
                        xlrec->node.dbNode, xlrec->node.relNode, xlrec->latestRemovedXid);
            } else {
                appendStringInfo(buf, "reuse_page: rel %u/%u/%u/%d; latestRemovedXid " XID_FMT, xlrec->node.spcNode,
                        xlrec->node.dbNode, xlrec->node.relNode, bucket_id, xlrec->latestRemovedXid);
            }
            break;
        }
        case XLOG_UBTREE_MARK_DELETE: {
            xl_ubtree_mark_delete* xlrec = (xl_ubtree_mark_delete*)rec;
            appendStringInfo(buf,
                             "mark delete: off %d xmax " XID_FMT,
                    xlrec->offset,
                    xlrec->xid);
            break;
        }
        case XLOG_UBTREE_PRUNE_PAGE: {
            xl_ubtree_prune_page* xlrec = (xl_ubtree_prune_page*)rec;

            appendStringInfo(buf, "count %d, new_prune_xid %lu, latestRemovedXid %lu", xlrec->count,
                             xlrec->new_prune_xid, xlrec->latestRemovedXid);
            break;
        }
        default:
            appendStringInfo(buf, "UNKNOWN");
            break;
    }
}

void UBTree2Desc(StringInfo buf, XLogReaderState* record)
{
    char* rec = XLogRecGetData(record);
    uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;

    switch (info) {
        case XLOG_UBTREE2_SHIFT_BASE: {
            xl_ubtree2_shift_base *xlrec = (xl_ubtree2_shift_base *)rec;
            appendStringInfo(buf, "shift base: delta %ld", xlrec->delta);
            break;
        }
        case XLOG_UBTREE2_RECYCLE_QUEUE_INIT_PAGE: {
            xl_ubtree2_recycle_queue_init_page *xlrec = (xl_ubtree2_recycle_queue_init_page *)rec;
            appendStringInfo(buf, "recycle queue init page: inserting %s, prev blkno %u, curr blkno %u next blkno %u",
                (xlrec->insertingNewPage ? "yes" : "no"), xlrec->prevBlkno, xlrec->currBlkno, xlrec->nextBlkno);
            break;
        }
        case XLOG_UBTREE2_RECYCLE_QUEUE_ENDPOINT: {
            xl_ubtree2_recycle_queue_endpoint *xlrec = (xl_ubtree2_recycle_queue_endpoint *)rec;
            appendStringInfo(buf, "recycle queue change endpoint: isHead %s, left blkno %u, right blkno %u",
                (xlrec->isHead ? "yes" : "no"), xlrec->leftBlkno, xlrec->rightBlkno);
            break;
        }
        case XLOG_UBTREE2_RECYCLE_QUEUE_MODIFY: {
            xl_ubtree2_recycle_queue_modify *xlrec = (xl_ubtree2_recycle_queue_modify *)rec;
            appendStringInfo(buf, "recycle queue modify: isInsert %s, blkno %u, offset %u",
                (xlrec->isInsert ? "yes" : "no"), xlrec->blkno, xlrec->offset);
            break;
        }
        case XLOG_UBTREE2_FREEZE: {
            xl_ubtree2_freeze *xlrec = (xl_ubtree2_freeze *)rec;
            OffsetNumber *offsets = (OffsetNumber *)(((char *)xlrec) + SizeOfUBTree2Freeze);
            appendStringInfo(buf, "freeze page: blkno %u, nfrozen: %d, latestRemovedXid: %lu",
                xlrec->blkno, xlrec->nfrozen, xlrec->oldestXmin);
            if (xlrec->nfrozen > 0) {
                appendStringInfo(buf, ", offsets info: ");
                int32 offsetIdx = 0;
                while (offsetIdx < xlrec->nfrozen) {
                    appendStringInfo(buf, "(%d : %u) ", offsetIdx, offsets[offsetIdx]);
                    offsetIdx++;
                }
            }
            break;
        }
        default:
            appendStringInfo(buf, "UNKNOWN");
            break;
    }
}

const char* ubtree2_type_name(uint8 subtype)
{
    uint8 info = subtype & ~XLR_INFO_MASK;

    switch (info) {
        case XLOG_UBTREE2_SHIFT_BASE: {
            return "ubt2_shift_base";
            break;
        }
        case XLOG_UBTREE2_RECYCLE_QUEUE_INIT_PAGE: {
            return "ubt2_recycle_init_page";
            break;
        }
        case XLOG_UBTREE2_RECYCLE_QUEUE_ENDPOINT: {
            return "ubt2_recycle_endpoint";
            break;
        }
        case XLOG_UBTREE2_RECYCLE_QUEUE_MODIFY: {
            return "ubt2_recycle_modify";
            break;
        }
        case XLOG_UBTREE2_FREEZE: {
            return "ubt2_freeze";
            break;
        }
        default:
            return "unknown_type";
            break;
    }
}


void UBTree3PrunePagePcrDesc(StringInfo buf, XLogReaderState* record)
{
    xl_ubtree3_prune_page *xlrec = (xl_ubtree3_prune_page *)XLogRecGetData(record);
    appendStringInfo(buf, "XLOG_UBTREE3_PRUNE_PAGE_PCR : latestRemovedXid: %lu, latestFrozenXid: %lu, ",
                     xlrec->latestRemovedXid, xlrec->latestFrozenXid);

    const int32 pruneTupleCount = xlrec->pruneTupleCount;
    int deadTupleOff = SizeOfUBtree3Prunepage;
    OffsetNumber *deadOffs = (OffsetNumber *)(((char *)xlrec) + deadTupleOff);

    appendStringInfo(buf, "pruneTupleCount: %d, tuple offsets: [", xlrec->activeTupleCount);
    for (int i = 0; i < pruneTupleCount; i++) {
        appendStringInfo(buf, "%hu, ", deadOffs[i]);
    }
    appendStringInfo(buf, "], activeTupleCount: %d, canCompactTdCount: %hhu, ",
                     xlrec->activeTupleCount, xlrec->canCompactTdCount);
    bool* frozenTdMap = (bool*)(((char*)xlrec) + deadTupleOff + pruneTupleCount * sizeof(OffsetNumber));
    appendStringInfo(buf, "the head 4 frozen map: [");
    for (int i = 0; i < UBTREE_DEFAULT_TD_COUNT; i++) {
        appendStringInfo(buf, "%s, ", frozenTdMap[i] ? "true" : "false");
    }
    appendStringInfo(buf, "].");
}

void UBTree3FreezeTdSlotDesc(StringInfo buf, XLogReaderState* record)
{
    xl_ubtree3_freeze_td_slot *xlrec = (xl_ubtree3_freeze_td_slot *)XLogRecGetData(record);
    uint8* frozenSlots = (uint8*)(((char*)xlrec) + SizeOfUbtree3FreezeTDSlot);
    appendStringInfo(buf, "XLOG_UBTREE3_FREEZE_TD_SLOT: latestFrozenXid: %lu, nFrozen: %d, frozen slots: [",
         xlrec->latestFrozenXid, xlrec->nFrozen);
    for (uint16 i = 0; i < xlrec->nFrozen; i ++) {
        appendStringInfo(buf, "%d, ", frozenSlots[i]);
    }
    appendStringInfo(buf, "].");
}

void UBTree3ExtendTdSlotsDesc(StringInfo buf, XLogReaderState* record)
{
    xl_ubtree3_extend_td_slots *xlrec = (xl_ubtree3_extend_td_slots *)XLogRecGetData(record);
    appendStringInfo(buf, "XLOG_UBTRE3_EXTEND_TD_SLOT: nExtended: %u, nPrevSlots: %d.",
        xlrec->nExtended, xlrec->nPrevSlots);
}

void UBTree3InsertOrDeleteSplit(StringInfo buf, char* rec, TransactionId xid)
{
    xl_ubtree3_insert_or_delete *xlrec = (xl_ubtree3_insert_or_delete *)rec;
    rec += SizeOfUbtree3InsertOrDelete;
    IndexTuple itup = (IndexTuple)rec;
    rec += IndexTupleSize(itup);
    appendStringInfo(buf, "curXid: %lu, prevXidOfTuple: %lu, offNum: %hu, tdId: %hhu",
        xlrec->curXid, xlrec->prevXidOfTuple, xlrec->offNum, xlrec->tdId);
    appendStringInfo(buf, "IndexTuple: t_tid(blk: %u, off: %hu), t_info: 0x%hx ",
        BlockIdGetBlockNumber(&itup->t_tid.ip_blkid), itup->t_tid.ip_posid, itup->t_info);
    
    XlUndoHeader *xlundohdr = (XlUndoHeader *)rec;
    rec += SizeOfXLUndoHeader;
    UBTreeUndoInfo undoInfo = (UBTreeUndoInfo)rec;
    rec += SizeOfUBTreeUndoInfoData;
    appendStringInfo(buf, "UBTreeUndoInfo: prevTdId: %hhu", undoInfo->prev_td_id);

    UndoRecPtr blkPrev = INVALID_UNDO_REC_PTR;
    UndoRecPtr prevUrp = INVALID_UNDO_REC_PTR;
    Oid partitionOid = InvalidOid ;
    if ((xlundohdr->flag & UBTREE_XLOG_HAS_BLK_PREV) != 0) {
        blkPrev = *((UndoRecPtr *)rec);
        rec += sizeof(UndoRecPtr);
    }
    if ((xlundohdr->flag & UBTREE_XLOG_HAS_XACT_PREV) != 0) {
        prevUrp = *((UndoRecPtr *)rec);
        rec += sizeof(UndoRecPtr);
    }
    if ((xlundohdr->flag & UBTREE_XLOG_HAS_PARTITION_OID) != 0) {
        partitionOid = *((Oid *)rec);
        rec += sizeof(Oid);
    }
    appendStringInfo(buf, "UndoHeader: flag: %hhu, urecptr: %lu, blkPrev: %lu, prevUrp: %lu, partitionOid: %u",
        xlundohdr->flag, xlundohdr->urecptr, blkPrev, prevUrp, partitionOid);
    
    undo::XlogUndoMeta *undoMeta = (undo::XlogUndoMeta *)rec;
    Oid dbId = (undoMeta->info & XLOG_UNDOMETA_INFO_SLOT) == 0 ? undoMeta->dbid : 0;
    appendStringInfo(buf, "UndoMeta: dbId: %u, xid: %lu, lastRecordSize: %hu, info: %hhu. ",
        dbId, xid, undoMeta->lastRecordSize, undoMeta->info);
}

void UBTree3SplitDesc(StringInfo buf, XLogReaderState* record, const char *desc)
{
    char *rec = XLogRecGetData(record);
    TransactionId xid = XLogRecGetXid(record);

    xl_ubtree3_split *xlrec = (xl_ubtree3_split *)rec;
    rec += SizeOfUbtree3Split;

    appendStringInfo(buf, "%s, ", desc);
    UBTPCRPageOpaque lo = &xlrec->letfPcrOpq;
    if (xlrec->slotNo != UBTreeInvalidTDSlotId) {
        UBTree3InsertOrDeleteSplit(buf, rec, xid);
    }

    appendStringInfo(buf, "UBtree3Split: level: %u, firstRight: %u, newItemOff: %u, rightLower: %hu, slotNo: %hhu, "
        "fxid: %lu, urp: %lu. ",
        xlrec->level, xlrec->firstRight, xlrec->newItemOff, xlrec->rightLower, xlrec->slotNo, xlrec->fxid, xlrec->urp);
    appendStringInfo(buf, "UBTPCRPageOpaque:btpo_prev: %u, btpo_next: %u, btpo(level: %u, xact_old: %u), "
        "btpo_flags: %hu, btpo_cycleid: %hu, xact: %lu, last_delete_xid: %lu, last_commit_xid: %lu, td_count: %hhu, "
        "activeTupleCount: %hu, flags: %lu.", lo->btpo_prev, lo->btpo_next, lo->btpo.level, lo->btpo.xact_old,
        lo->btpo_flags, lo->btpo_cycleid, lo->xact, lo->last_delete_xid, lo->last_commit_xid, lo->td_count,
        lo->activeTupleCount, lo->flags);
}

void UBTree3InsertOrDeletePcrDesc(StringInfo buf, XLogReaderState* record, const char *desc)
{
    char *rec = XLogRecGetData(record);
    TransactionId xid = XLogRecGetXid(record);
    appendStringInfo(buf, "%s, ", desc);
    UBTree3InsertOrDeleteSplit(buf, rec, xid);
}

void UBTree3InsertPcrInternalOrMetaDesc(StringInfo buf, XLogReaderState* record, bool ismeta)
{
    char *rec = XLogRecGetData(record);
    if (!ismeta) {
        xl_btree_insert *xlrec = (xl_btree_insert *)rec;
        appendStringInfo(buf, "UBtree3InsertInternal: offnum %hu", xlrec->offnum);
    } else {
        Size dataLen;
        char *metadata = XLogRecGetBlockData(record, UBTREE3_INSERT_PCR_META_BLOCK_NUM, &dataLen);
        if (metadata == NULL) {
            appendStringInfoString(buf, "WARNING: this xlog contains no data.");
            return;
        }
        xl_btree_metadata *xlrec = (xl_btree_metadata *)metadata;
        appendStringInfo(buf, "UBtree3InsertMeta: fastlevel %u, fastroot %u, level: %u, root: %u, version: %u",
            xlrec->fastlevel, xlrec->fastroot, xlrec->level, xlrec->root, xlrec->version);
    }
}

void UBTree3NewRootDesc(StringInfo buf, XLogReaderState* record)
{
    char *rec = XLogRecGetData(record);
    xl_btree_newroot *xlrecroot = (xl_btree_newroot *)rec;
    appendStringInfo(buf, "UBtree3NewRoot: rootblk %u, level %u", xlrecroot->rootblk, xlrecroot->level);
    Size dataLen;
    char *metadata = XLogRecGetBlockData(record, BTREE_NEWROOT_META_BLOCK_NUM, &dataLen);
    if (metadata == NULL) {
        appendStringInfoString(buf, "WARNING: this xlog contains no data.");
        return;
    }
    xl_btree_metadata *xlrecmeta = (xl_btree_metadata *)metadata;
    appendStringInfo(buf, "UBtree3InsertMeta: fastlevel %u, fastroot %u, level: %u, root: %u, version: %u",
        xlrecmeta->fastlevel, xlrecmeta->fastroot, xlrecmeta->level, xlrecmeta->root, xlrecmeta->version);
}

void UBTree3RollbackTxnDesc(StringInfo buf, XLogReaderState* record)
{
    xl_ubtree3_rollback_txn *xlrec = (xl_ubtree3_rollback_txn *)XLogRecGetData(record);
    UBTreeTD xltd = (UBTreeTD)(((char *)xlrec) + sizeOfUbtree3RollbackTxn);
    appendStringInfo(buf, "XLOG_UBTREE3_ROLLBACK_PCR: xid %lu, td_id %u, td(xid: %lu, urecptr:%lu, status:%d), "
        "n_rollback %u", xlrec->xid, xlrec->td_id, xltd->xactid, xltd->undoRecPtr, xltd->tdStatus, xlrec->n_rollback);
    if (xlrec->n_rollback > 0) {
        appendStringInfo(buf, ", rollback_items:(");
        UBTreeRedoRollbackItem items = 
            (UBTreeRedoRollbackItem)(((char *)xlrec) + sizeOfUbtree3RollbackTxn + sizeof(UBTreeTDData));
        for (int i = 0; i < xlrec->n_rollback; i++) {
            appendStringInfo(buf, "[off:%u, lp_off:%u, lp_flag:%u, "
                "lp_td_id:%u, lp_td_invalid:%u, lp_deleted:%u]",
                items[i].offnum, items[i].iid.lp_off, items[i].iid.lp_flags,
                items[i].iid.lp_td_id, items[i].iid.lp_td_invalid, items[i].iid.lp_deleted);
            if (i != xlrec->n_rollback - 1) {
                appendStringInfo(buf, ",");
            }
        }
        appendStringInfo(buf, ")");
    }
}

void UBTree3ReuseTdSlotDesc(StringInfo buf, XLogReaderState* record)
{
    char *rec = XLogRecGetData(record);
    uint8 *nCompletedXactSlots = (uint8 *)rec;
    uint8 *completedXactSlots = (uint8 *)(((char *)rec) + sizeof(uint8));
    const uint8 tdCount = *nCompletedXactSlots;
    appendStringInfo(buf, "XLOG_UBTREE3_REUSE_TD_SLOT: reuse td count: %hu, slots [", tdCount);
    for (uint8 i = 0; i <tdCount; i++) {
        appendStringInfo(buf, "%hu, ", completedXactSlots[i]);
    }
    appendStringInfo(buf, "]");
}

void UBTree3Desc(StringInfo buf, XLogReaderState* record)
{
    uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
    info &= XLOG_UBTREE_PCR_OP_MASK;

    switch (info) {
        case XLOG_UBTREE3_INSERT_PCR_INTERNAL: {
            UBTree3InsertPcrInternalOrMetaDesc(buf, record, false);
            break;
        }
        case XLOG_UBTREE3_PRUNE_PAGE_PCR: {
            UBTree3PrunePagePcrDesc(buf, record);
            break;
        }
        case XLOG_UBTREE3_ROLLBACK_TXN: {
            UBTree3RollbackTxnDesc(buf, record);
            break;
        }
        case XLOG_UBTREE3_FREEZE_TD_SLOT: {
            UBTree3FreezeTdSlotDesc(buf, record);
            break;
        }
        case XLOG_UBTREE3_EXTEND_TD_SLOTS: {
            UBTree3ExtendTdSlotsDesc(buf, record);
            break;
        }
        case XLOG_UBTREE3_SPLIT_L: {
            UBTree3SplitDesc(buf, record, "XLOG_UBTREE3_SPLIT_L");
            break;
        }
        case XLOG_UBTREE3_SPLIT_R: {
            UBTree3SplitDesc(buf, record, "XLOG_UBTREE3_SPLIT_R");
            break;
        }
        case XLOG_UBTREE3_SPLIT_L_ROOT: {
            UBTree3SplitDesc(buf, record, "XLOG_UBTREE3_SPLIT_L_ROOT");
            break;
        }
        case XLOG_UBTREE3_SPLIT_R_ROOT: {
            UBTree3SplitDesc(buf, record, "XLOG_UBTREE3_SPLIT_R_ROOT");
            break;
        }
        case XLOG_UBTREE3_INSERT_PCR: {
            UBTree3InsertOrDeletePcrDesc(buf, record, "XLOG_UBTREE3_INSERT_PCR");
            break;
        }
        case XLOG_UBTREE3_DUP_INSERT: {
            UBTree3InsertOrDeletePcrDesc(buf, record, "XLOG_UBTREE3_DUP_INSERT");
            break;
        }
        case XLOG_UBTREE3_DELETE_PCR: {
            UBTree3InsertOrDeletePcrDesc(buf, record, "XLOG_UBTREE3_DELETE_PCR");
            break;
        }
        case XLOG_UBTREE3_INSERT_PCR_META: {
            UBTree3InsertPcrInternalOrMetaDesc(buf, record, true);
            break;
        }
        case XLOG_UBTREE3_NEW_ROOT: {
            UBTree3NewRootDesc(buf, record);
            break;
        }
        case XLOG_UBTREE3_REUSE_TD_SLOT: {
            UBTree3ReuseTdSlotDesc(buf, record);
            break;
        }
        default:
            appendStringInfo(buf, "UBTree3Redo: unknown op code %hhu", info);
            break;
    }
    return;
}

const char* ubtree3_type_name(uint8 subtype)
{
    uint8 info = subtype & ~XLR_INFO_MASK;
    info &= XLOG_UBTREE_PCR_OP_MASK;

    switch (info) {
        case XLOG_UBTREE3_INSERT_PCR_INTERNAL: {
            return "ubt3_insert_pcr_internal";
        }
        case XLOG_UBTREE3_PRUNE_PAGE_PCR: {
            return "ubt3_prune_page_pcr";
        }
        case XLOG_UBTREE3_DELETE_PCR: {
            return "ubt3_delete_pcr";
        }
        case XLOG_UBTREE3_INSERT_PCR: {
            return "ubt3_insert_pcr";
        }
        case XLOG_UBTREE3_DUP_INSERT: {
            return "ubt3_duplicate_pcr";
        }
        case XLOG_UBTREE3_ROLLBACK_TXN: {
            return "ubt3_rollback_transaction";
        }
        case XLOG_UBTREE3_FREEZE_TD_SLOT: {
            return "ubt3_freeze_td_slot";
        }
        case XLOG_UBTREE3_REUSE_TD_SLOT: {
            return "ubt3_reuse_td_slot";
        }
        case XLOG_UBTREE3_EXTEND_TD_SLOTS: {
            return "ubt3_extend_td_slots";
        }
        case XLOG_UBTREE3_SPLIT_L: {
            return "ubt3_split_left";
        }
        case XLOG_UBTREE3_SPLIT_R: {
            return "ubt3_split_right";
        }
        case XLOG_UBTREE3_SPLIT_L_ROOT: {
            return "ubt3_split_root_left";
        }
        case XLOG_UBTREE3_SPLIT_R_ROOT: {
            return "ubt3_split_root_right";
        }
        case XLOG_UBTREE3_NEW_ROOT: {
            return "ubt3_new_root";
        }
        case XLOG_UBTREE3_INSERT_PCR_META: {
            return "ubt3_insert_pcr_meta";
        }
        default:
            return "unknown_type";
    }
}

static void UBTree4UnlinkPageDesc(StringInfo buf, XLogReaderState* record, bool isMeta)
{
    char *rec = XLogRecGetData(record);
    xl_btree_unlink_page *xlrec = (xl_btree_unlink_page *)rec;
    if (isMeta) {
        appendStringInfo(buf, "XLOG_UBTREE4_UNLINK_PAGE_META: ");
        Size metaDataLen;
        char *metaData = XLogRecGetBlockData(record, BTREE_UNLINK_PAGE_META_NUM, &metaDataLen);
        if (metaData == NULL) {
            appendStringInfoString(buf, "WARNING: this xlog contains no data.");
            return;
        }
        xl_btree_metadata *metaRec = (xl_btree_metadata *)metaData;
        appendStringInfo(buf, "meta info: [root blk no: %u, level %u, fast root blk no: %u, "
                         "fast level: %u, version: %u]",
                         metaRec->root, metaRec->level, metaRec->fastroot,
                         metaRec->fastlevel, metaRec->version);
    } else {
        appendStringInfo(buf, "XLOG_UBTREE4_UNLINK_PAGE: ");
    }

    appendStringInfo(buf,
        "leftsib %u; rightsib %u; leafleftsib %u; leafrightsib %u; topparent %u; btpo_xact " XID_FMT "",
        xlrec->leftsib, xlrec->rightsib, xlrec->leafleftsib, xlrec->leafrightsib, xlrec->topparent,
        xlrec->btpo_xact);
}

static void UBTree4MarkPageHalfDeadDesc(StringInfo buf, XLogReaderState* record)
{
    char *rec = XLogRecGetData(record);
    xl_btree_mark_page_halfdead *xlrec = (xl_btree_mark_page_halfdead *)rec;
    appendStringInfo(buf, "XLOG_UBTREE4_MARK_PAGE_HALFDEAD: ");
    appendStringInfo(buf, "leaf %u; left %u; right %u; parent %u; parent off %u", xlrec->leafblk,
                     xlrec->leftblk, xlrec->rightblk, xlrec->topparent, (uint32)xlrec->poffset);
}

void UBTree4Desc(StringInfo buf, XLogReaderState* record)
{
    uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
    info &= XLOG_UBTREE_PCR_OP_MASK;

    switch (info) {
        case XLOG_UBTREE4_UNLINK_PAGE: {
            UBTree4UnlinkPageDesc(buf, record, false);
            break;
        }
        case XLOG_UBTREE4_UNLINK_PAGE_META: {
            UBTree4UnlinkPageDesc(buf, record, true);
            break;
        }
        case XLOG_UBTREE4_MARK_PAGE_HALFDEAD: {
            UBTree4MarkPageHalfDeadDesc(buf, record);
            break;
        }
        default:
            appendStringInfo(buf, "UBTree4Redo: unknown op code %hhu", info);
            break;
    }
    return;
}

const char* ubtree4_type_name(uint8 subtype)
{
    uint8 info = subtype & ~XLR_INFO_MASK;
    info &= XLOG_UBTREE_PCR_OP_MASK;

    switch (info) {
        case XLOG_UBTREE4_UNLINK_PAGE: {
            return "ubt4_unlink_page";
        }
        case XLOG_UBTREE4_UNLINK_PAGE_META: {
            return "ubt4_unlink_page_meta";
        }
        case XLOG_UBTREE4_MARK_PAGE_HALFDEAD: {
            return "ubt4_mark_page_halfdead";
        }
        default:
            return "unknown_type";
    }
}