forked from huawei/openGauss-server
987 lines
34 KiB
C++
987 lines
34 KiB
C++
/*
|
|
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
|
* Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* openGauss is licensed under Mulan PSL v2.
|
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
* You may obtain a copy of Mulan PSL v2 at:
|
|
*
|
|
* http://license.coscl.org.cn/MulanPSL2
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
* See the Mulan PSL v2 for more details.
|
|
* -------------------------------------------------------------------------
|
|
*
|
|
* nbtxlog.cpp
|
|
* parse btree xlog
|
|
*
|
|
* IDENTIFICATION
|
|
*
|
|
* src/gausskernel/storage/access/redo/nbtxlog.cpp
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
#include "knl/knl_variable.h"
|
|
|
|
#include "access/nbtree.h"
|
|
#include "access/transam.h"
|
|
#include "access/xlog.h"
|
|
#include "access/xlogutils.h"
|
|
#include "access/xlogproc.h"
|
|
#include "pgxc/pgxc.h"
|
|
#include "access/multi_redo_api.h"
|
|
#include "miscadmin.h"
|
|
#include "access/redo_common.h"
|
|
|
|
#ifdef ENABLE_UT
|
|
#define static
|
|
#endif
|
|
|
|
enum {
|
|
BTREE_INSERT_ORIG_BLOCK_NUM = 0,
|
|
BTREE_INSERT_CHILD_BLOCK_NUM,
|
|
BTREE_INSERT_META_BLOCK_NUM
|
|
};
|
|
|
|
enum {
|
|
BTREE_SPLIT_LEFT_BLOCK_NUM = 0,
|
|
BTREE_SPLIT_RIGHT_BLOCK_NUM,
|
|
BTREE_SPLIT_RIGHTNEXT_BLOCK_NUM,
|
|
BTREE_SPLIT_CHILD_BLOCK_NUM
|
|
};
|
|
|
|
enum {
|
|
BTREE_VACUUM_ORIG_BLOCK_NUM = 0,
|
|
};
|
|
|
|
enum {
|
|
BTREE_DELETE_ORIG_BLOCK_NUM = 0,
|
|
};
|
|
|
|
enum {
|
|
BTREE_DELETE_PAGE_TARGET_BLOCK_NUM = 0,
|
|
BTREE_DELETE_PAGE_LEFT_BLOCK_NUM,
|
|
BTREE_DELETE_PAGE_RIGHT_BLOCK_NUM,
|
|
BTREE_DELETE_PAGE_PARENT_BLOCK_NUM,
|
|
BTREE_DELETE_PAGE_META_BLOCK_NUM,
|
|
};
|
|
|
|
enum {
|
|
BTREE_NEWROOT_ORIG_BLOCK_NUM = 0,
|
|
BTREE_NEWROOT_LEFT_BLOCK_NUM,
|
|
BTREE_NEWROOT_META_BLOCK_NUM,
|
|
};
|
|
|
|
/*
|
|
* _bt_restore_page -- re-enter all the index tuples on a page
|
|
*
|
|
* The page is freshly init'd, and *from (length len) is a copy of what
|
|
* had been its upper part (pd_upper to pd_special). We assume that the
|
|
* tuples had been added to the page in item-number order, and therefore
|
|
* the one with highest item number appears first (lowest on the page).
|
|
*
|
|
* NOTE: the way this routine is coded, the rebuilt page will have the items
|
|
* in correct itemno sequence, but physically the opposite order from the
|
|
* original, because we insert them in the opposite of itemno order. This
|
|
* does not matter in any current btree code, but it's something to keep an
|
|
* eye on. Is it worth changing just on general principles? See also the
|
|
* notes in btree_xlog_split().
|
|
*/
|
|
void _bt_restore_page(Page page, char* from, int len)
|
|
{
|
|
IndexTupleData itupdata;
|
|
Size itemsz;
|
|
char* end = from + len;
|
|
|
|
for (; from < end;) {
|
|
/* Need to copy tuple header due to alignment considerations */
|
|
errno_t rc = memcpy_s(&itupdata, sizeof(IndexTupleData), from, sizeof(IndexTupleData));
|
|
securec_check(rc, "\0", "\0");
|
|
itemsz = IndexTupleDSize(itupdata);
|
|
itemsz = MAXALIGN(itemsz);
|
|
if (PageAddItem(page, (Item)from, itemsz, FirstOffsetNumber, false, false) == InvalidOffsetNumber)
|
|
ereport(PANIC, (errmsg("_bt_restore_page: cannot add item to page")));
|
|
from += itemsz;
|
|
}
|
|
}
|
|
|
|
void DumpBtreeDeleteInfo(XLogRecPtr lsn, OffsetNumber offsetList[], uint64 offsetNum)
|
|
{
|
|
ereport(DEBUG4,
|
|
(errmodule(MOD_REDO),
|
|
errcode(ERRCODE_LOG),
|
|
errmsg("DumpBtreeDeleteInfo: lsn:%X/%X, offsetnum %lu", (uint32)(lsn >> 32), (uint32)lsn, offsetNum)));
|
|
for (uint64 i = 0; i < offsetNum; ++i) {
|
|
ereport(DEBUG4,
|
|
(errmodule(MOD_REDO),
|
|
errcode(ERRCODE_LOG),
|
|
errmsg("DumpBtreeDeleteInfo: %lu offset %u", i, offsetList[i])));
|
|
}
|
|
}
|
|
|
|
void btree_restore_meta_operator_page(RedoBufferInfo* metabuf, void* recorddata, Size datalen)
|
|
{
|
|
char* ptr = (char*)recorddata;
|
|
Page metapg = metabuf->pageinfo.page;
|
|
BTMetaPageData* md = NULL;
|
|
BTPageOpaqueInternal pageop;
|
|
xl_btree_metadata* xlrec = NULL;
|
|
|
|
Assert(datalen == sizeof(xl_btree_metadata));
|
|
Assert(metabuf->blockinfo.blkno == BTREE_METAPAGE);
|
|
xlrec = (xl_btree_metadata*)ptr;
|
|
|
|
metapg = metabuf->pageinfo.page;
|
|
|
|
_bt_pageinit(metapg, metabuf->pageinfo.pagesize);
|
|
|
|
md = BTPageGetMeta(metapg);
|
|
md->btm_magic = BTREE_MAGIC;
|
|
md->btm_version = BTREE_VERSION;
|
|
md->btm_root = xlrec->root;
|
|
md->btm_level = xlrec->level;
|
|
md->btm_fastroot = xlrec->fastroot;
|
|
md->btm_fastlevel = xlrec->fastlevel;
|
|
|
|
pageop = (BTPageOpaqueInternal)PageGetSpecialPointer(metapg);
|
|
pageop->btpo_flags = BTP_META;
|
|
|
|
/*
|
|
* Set pd_lower just past the end of the metadata. This is not essential
|
|
* but it makes the page look compressible to xlog.c.
|
|
*/
|
|
((PageHeader)metapg)->pd_lower = ((char*)md + sizeof(BTMetaPageData)) - (char*)metapg;
|
|
|
|
PageSetLSN(metapg, metabuf->lsn);
|
|
}
|
|
|
|
void btree_xlog_insert_operator_page(RedoBufferInfo* buffer, void* recorddata, void* data, Size datalen)
|
|
{
|
|
xl_btree_insert* xlrec = (xl_btree_insert*)recorddata;
|
|
Page page = buffer->pageinfo.page;
|
|
char* datapos = (char*)data;
|
|
|
|
if (PageAddItem(page, (Item)datapos, datalen, xlrec->offnum, false, false) == InvalidOffsetNumber)
|
|
ereport(PANIC, (errmsg("btree_insert_redo: failed to add item")));
|
|
|
|
PageSetLSN(page, buffer->lsn);
|
|
}
|
|
|
|
void btree_xlog_split_operator_rightpage(
|
|
RedoBufferInfo* rbuf, void* recorddata, BlockNumber leftsib, BlockNumber rnext, void* blkdata, Size datalen)
|
|
{
|
|
xl_btree_split* xlrec = (xl_btree_split*)recorddata;
|
|
bool isleaf = (xlrec->level == 0);
|
|
Page rpage = rbuf->pageinfo.page;
|
|
char* datapos = (char*)blkdata;
|
|
BTPageOpaqueInternal ropaque;
|
|
|
|
_bt_pageinit(rpage, rbuf->pageinfo.pagesize);
|
|
ropaque = (BTPageOpaqueInternal)PageGetSpecialPointer(rpage);
|
|
|
|
ropaque->btpo_prev = leftsib;
|
|
ropaque->btpo_next = rnext;
|
|
ropaque->btpo.level = xlrec->level;
|
|
ropaque->btpo_flags = isleaf ? BTP_LEAF : 0;
|
|
ropaque->btpo_cycleid = 0;
|
|
|
|
_bt_restore_page(rpage, datapos, (int)datalen);
|
|
|
|
PageSetLSN(rpage, rbuf->lsn);
|
|
}
|
|
|
|
void btree_xlog_split_operator_nextpage(RedoBufferInfo* buffer, BlockNumber rightsib)
|
|
{
|
|
Page page = buffer->pageinfo.page;
|
|
|
|
BTPageOpaqueInternal pageop = (BTPageOpaqueInternal)PageGetSpecialPointer(page);
|
|
|
|
pageop->btpo_prev = rightsib;
|
|
PageSetLSN(page, buffer->lsn);
|
|
}
|
|
|
|
void btree_xlog_split_operator_leftpage(RedoBufferInfo* lbuf, void* recorddata, BlockNumber rightsib, bool onleft,
|
|
void* blkdata, Size datalen, Item input_left_hikey, Size input_left_hikeysz)
|
|
{
|
|
xl_btree_split* xlrec = (xl_btree_split*)recorddata;
|
|
bool isleaf = (xlrec->level == 0);
|
|
Page lpage = lbuf->pageinfo.page;
|
|
char* datapos = (char*)blkdata;
|
|
Item left_hikey = input_left_hikey;
|
|
Size left_hikeysz = input_left_hikeysz;
|
|
|
|
/*
|
|
* To retain the same physical order of the tuples that they had, we
|
|
* initialize a temporary empty page for the left page and add all the
|
|
* items to that in item number order. This mirrors how _bt_split()
|
|
* works. It's not strictly required to retain the same physical
|
|
* order, as long as the items are in the correct item number order,
|
|
* but it helps debugging. See also _bt_restore_page(), which does
|
|
* the same for the right page.
|
|
*/
|
|
|
|
BTPageOpaqueInternal lopaque = (BTPageOpaqueInternal)PageGetSpecialPointer(lpage);
|
|
OffsetNumber off;
|
|
Item newitem = NULL;
|
|
Size newitemsz = 0;
|
|
Page newlpage;
|
|
OffsetNumber leftoff;
|
|
|
|
if (onleft) {
|
|
newitem = (Item)datapos;
|
|
newitemsz = MAXALIGN(IndexTupleSize(newitem));
|
|
datapos += newitemsz;
|
|
datalen -= newitemsz;
|
|
}
|
|
|
|
/* Extract left hikey and its size (assuming 16-bit alignment) */
|
|
left_hikey = (Item)datapos;
|
|
left_hikeysz = MAXALIGN(IndexTupleSize(left_hikey));
|
|
datapos += left_hikeysz;
|
|
datalen -= left_hikeysz;
|
|
Assert(datalen == 0);
|
|
|
|
/* assure that memory is properly allocated, prevent from core dump caused by buffer unpin */
|
|
START_CRIT_SECTION();
|
|
newlpage = PageGetTempPageCopySpecial(lpage, true);
|
|
END_CRIT_SECTION();
|
|
|
|
/* Set high key */
|
|
leftoff = P_HIKEY;
|
|
if (PageAddItem(newlpage, left_hikey, left_hikeysz, P_HIKEY, false, false) == InvalidOffsetNumber)
|
|
ereport(PANIC, (errmsg("failed to add high key to left page after split")));
|
|
leftoff = OffsetNumberNext(leftoff);
|
|
|
|
for (off = P_FIRSTDATAKEY(lopaque); off < xlrec->firstright; off++) {
|
|
ItemId itemid;
|
|
Size itemsz;
|
|
Item item;
|
|
|
|
/* add the new item if it was inserted on left page */
|
|
if (onleft && off == xlrec->newitemoff) {
|
|
if (PageAddItem(newlpage, newitem, newitemsz, leftoff, false, false) == InvalidOffsetNumber)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INDEX_CORRUPTED), errmsg("failed to add new item to left page after split")));
|
|
leftoff = OffsetNumberNext(leftoff);
|
|
}
|
|
|
|
itemid = PageGetItemId(lpage, off);
|
|
itemsz = ItemIdGetLength(itemid);
|
|
item = PageGetItem(lpage, itemid);
|
|
if (PageAddItem(newlpage, item, itemsz, leftoff, false, false) == InvalidOffsetNumber)
|
|
ereport(
|
|
ERROR, (errcode(ERRCODE_INDEX_CORRUPTED), errmsg("failed to add old item to left page after split")));
|
|
leftoff = OffsetNumberNext(leftoff);
|
|
}
|
|
|
|
/* cope with possibility that newitem goes at the end */
|
|
if (onleft && off == xlrec->newitemoff) {
|
|
if (PageAddItem(newlpage, newitem, newitemsz, leftoff, false, false) == InvalidOffsetNumber)
|
|
ereport(
|
|
ERROR, (errcode(ERRCODE_INDEX_CORRUPTED), errmsg("failed to add new item to left page after split")));
|
|
leftoff = OffsetNumberNext(leftoff);
|
|
}
|
|
|
|
PageRestoreTempPage(newlpage, lpage);
|
|
|
|
/* Fix opaque fields */
|
|
lopaque = (BTPageOpaqueInternal)PageGetSpecialPointer(lpage);
|
|
lopaque->btpo_flags = BTP_INCOMPLETE_SPLIT;
|
|
if (isleaf) {
|
|
lopaque->btpo_flags |= BTP_LEAF;
|
|
}
|
|
lopaque->btpo_next = rightsib;
|
|
lopaque->btpo_cycleid = 0;
|
|
|
|
PageSetLSN(lpage, lbuf->lsn);
|
|
}
|
|
|
|
void btree_xlog_vacuum_operator_page(RedoBufferInfo* redobuffer, void* recorddata, void* blkdata, Size len)
|
|
{
|
|
Page page = redobuffer->pageinfo.page;
|
|
char* ptr = (char*)blkdata;
|
|
BTPageOpaqueInternal opaque;
|
|
|
|
if (len > 0) {
|
|
OffsetNumber* unused = NULL;
|
|
OffsetNumber* unend = NULL;
|
|
|
|
unused = (OffsetNumber*)ptr;
|
|
unend = (OffsetNumber*)((char*)ptr + len);
|
|
|
|
if (module_logging_is_on(MOD_REDO)) {
|
|
DumpBtreeDeleteInfo(redobuffer->lsn, unused, unend - unused);
|
|
DumpPageInfo(page, redobuffer->lsn);
|
|
}
|
|
|
|
if ((unend - unused) > 0)
|
|
PageIndexMultiDelete(page, unused, unend - unused);
|
|
}
|
|
|
|
/*
|
|
* Mark the page as not containing any LP_DEAD items --- see comments in
|
|
* _bt_delitems_vacuum().
|
|
*/
|
|
opaque = (BTPageOpaqueInternal)PageGetSpecialPointer(page);
|
|
opaque->btpo_flags &= ~BTP_HAS_GARBAGE;
|
|
|
|
PageSetLSN(page, redobuffer->lsn);
|
|
if (module_logging_is_on(MOD_REDO)) {
|
|
DumpPageInfo(page, redobuffer->lsn);
|
|
}
|
|
}
|
|
|
|
void btree_xlog_delete_operator_page(RedoBufferInfo* buffer, void* recorddata, Size recorddatalen)
|
|
{
|
|
xl_btree_delete* xlrec = (xl_btree_delete*)recorddata;
|
|
Page page = buffer->pageinfo.page;
|
|
BTPageOpaqueInternal opaque;
|
|
|
|
if (recorddatalen > SizeOfBtreeDelete) {
|
|
OffsetNumber* unused = NULL;
|
|
|
|
unused = (OffsetNumber*)((char*)xlrec + SizeOfBtreeDelete);
|
|
|
|
if (module_logging_is_on(MOD_REDO)) {
|
|
DumpPageInfo(page, buffer->lsn);
|
|
DumpBtreeDeleteInfo(buffer->lsn, unused, xlrec->nitems);
|
|
}
|
|
|
|
PageIndexMultiDelete(page, unused, xlrec->nitems);
|
|
}
|
|
|
|
/*
|
|
* Mark the page as not containing any LP_DEAD items --- see comments in
|
|
* _bt_delitems_delete().
|
|
*/
|
|
opaque = (BTPageOpaqueInternal)PageGetSpecialPointer(page);
|
|
opaque->btpo_flags &= ~BTP_HAS_GARBAGE;
|
|
|
|
PageSetLSN(page, buffer->lsn);
|
|
|
|
if (module_logging_is_on(MOD_REDO)) {
|
|
DumpPageInfo(page, buffer->lsn);
|
|
}
|
|
}
|
|
|
|
void btree_xlog_delete_page_operator_parentpage(RedoBufferInfo* buffer, void* recorddata, uint8 info)
|
|
{
|
|
xl_btree_delete_page* xlrec = (xl_btree_delete_page*)recorddata;
|
|
Page page = buffer->pageinfo.page;
|
|
OffsetNumber poffset, maxoff;
|
|
BTPageOpaqueInternal pageop;
|
|
|
|
pageop = (BTPageOpaqueInternal)PageGetSpecialPointer(page);
|
|
poffset = xlrec->poffset;
|
|
maxoff = PageGetMaxOffsetNumber(page);
|
|
if (poffset >= maxoff) {
|
|
Assert(info == XLOG_BTREE_DELETE_PAGE_HALF);
|
|
Assert(poffset == P_FIRSTDATAKEY(pageop));
|
|
PageIndexTupleDelete(page, poffset);
|
|
pageop->btpo_flags |= BTP_HALF_DEAD;
|
|
} else {
|
|
ItemId itemid;
|
|
IndexTuple itup;
|
|
OffsetNumber nextoffset;
|
|
|
|
Assert(info != XLOG_BTREE_DELETE_PAGE_HALF);
|
|
itemid = PageGetItemId(page, poffset);
|
|
itup = (IndexTuple)PageGetItem(page, itemid);
|
|
BTreeInnerTupleSetDownLink(itup, xlrec->rightblk);
|
|
nextoffset = OffsetNumberNext(poffset);
|
|
PageIndexTupleDelete(page, nextoffset);
|
|
}
|
|
|
|
PageSetLSN(page, buffer->lsn);
|
|
}
|
|
|
|
void btree_xlog_delete_page_operator_rightpage(RedoBufferInfo* buffer, void* recorddata)
|
|
{
|
|
xl_btree_delete_page* xlrec = (xl_btree_delete_page*)recorddata;
|
|
Page page = buffer->pageinfo.page;
|
|
BTPageOpaqueInternal pageop;
|
|
|
|
pageop = (BTPageOpaqueInternal)PageGetSpecialPointer(page);
|
|
pageop->btpo_prev = xlrec->leftblk;
|
|
|
|
PageSetLSN(page, buffer->lsn);
|
|
}
|
|
|
|
void btree_xlog_delete_page_operator_leftpage(RedoBufferInfo* buffer, void* recorddata)
|
|
{
|
|
xl_btree_delete_page* xlrec = (xl_btree_delete_page*)recorddata;
|
|
Page page = buffer->pageinfo.page;
|
|
BTPageOpaqueInternal pageop;
|
|
|
|
pageop = (BTPageOpaqueInternal)PageGetSpecialPointer(page);
|
|
pageop->btpo_next = xlrec->rightblk;
|
|
|
|
PageSetLSN(page, buffer->lsn);
|
|
}
|
|
|
|
void btree_xlog_delete_page_operator_currentpage(RedoBufferInfo* buffer, void* recorddata)
|
|
{
|
|
xl_btree_delete_page* xlrec = (xl_btree_delete_page*)recorddata;
|
|
Page page = buffer->pageinfo.page;
|
|
BTPageOpaqueInternal pageop;
|
|
|
|
_bt_pageinit(page, buffer->pageinfo.pagesize);
|
|
pageop = (BTPageOpaqueInternal)PageGetSpecialPointer(page);
|
|
|
|
pageop->btpo_prev = xlrec->leftblk;
|
|
pageop->btpo_next = xlrec->rightblk;
|
|
pageop->btpo_flags = BTP_DELETED;
|
|
pageop->btpo_cycleid = 0;
|
|
((BTPageOpaque)pageop)->xact = xlrec->btpo_xact;
|
|
|
|
PageSetLSN(page, buffer->lsn);
|
|
}
|
|
|
|
void btree_xlog_newroot_operator_page(
|
|
RedoBufferInfo* buffer, void* record, void* blkdata, Size len, BlockNumber* downlink)
|
|
{
|
|
xl_btree_newroot* xlrec = (xl_btree_newroot*)record;
|
|
Page page = buffer->pageinfo.page;
|
|
char* ptr = (char*)blkdata;
|
|
BTPageOpaqueInternal pageop;
|
|
|
|
_bt_pageinit(page, buffer->pageinfo.pagesize);
|
|
pageop = (BTPageOpaqueInternal)PageGetSpecialPointer(page);
|
|
|
|
pageop->btpo_flags = BTP_ROOT;
|
|
pageop->btpo_prev = pageop->btpo_next = P_NONE;
|
|
pageop->btpo.level = xlrec->level;
|
|
if (xlrec->level == 0) {
|
|
pageop->btpo_flags |= BTP_LEAF;
|
|
}
|
|
pageop->btpo_cycleid = 0;
|
|
|
|
if (xlrec->level > 0) {
|
|
_bt_restore_page(page, ptr, len);
|
|
}
|
|
|
|
PageSetLSN(page, buffer->lsn);
|
|
}
|
|
|
|
void btree_xlog_clear_incomplete_split(RedoBufferInfo* buffer)
|
|
{
|
|
Page page = buffer->pageinfo.page;
|
|
BTPageOpaqueInternal pageop = (BTPageOpaqueInternal)PageGetSpecialPointer(page);
|
|
|
|
Assert(P_INCOMPLETE_SPLIT(pageop));
|
|
pageop->btpo_flags &= ~BTP_INCOMPLETE_SPLIT;
|
|
|
|
PageSetLSN(page, buffer->lsn);
|
|
}
|
|
|
|
XLogRecParseState* btree_xlog_insert_parse_block(XLogReaderState* record, uint32* blocknum)
|
|
{
|
|
uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
|
|
XLogRecParseState* recordstatehead = NULL;
|
|
XLogRecParseState* blockstate = NULL;
|
|
|
|
*blocknum = 1;
|
|
XLogParseBufferAllocListFunc(record, &recordstatehead, NULL);
|
|
if (recordstatehead == NULL) {
|
|
return NULL;
|
|
}
|
|
XLogRecSetBlockDataState(record, BTREE_INSERT_ORIG_BLOCK_NUM, recordstatehead);
|
|
|
|
if (info != XLOG_BTREE_INSERT_LEAF) {
|
|
(*blocknum)++;
|
|
XLogParseBufferAllocListFunc(record, &blockstate, recordstatehead);
|
|
if (blockstate == NULL) {
|
|
return NULL;
|
|
}
|
|
XLogRecSetBlockDataState(record, BTREE_INSERT_CHILD_BLOCK_NUM, blockstate);
|
|
}
|
|
|
|
if (info == XLOG_BTREE_INSERT_META) {
|
|
(*blocknum)++;
|
|
XLogParseBufferAllocListFunc(record, &blockstate, recordstatehead);
|
|
if (blockstate == NULL) {
|
|
return NULL;
|
|
}
|
|
XLogRecSetBlockDataState(record, BTREE_INSERT_META_BLOCK_NUM, blockstate);
|
|
}
|
|
|
|
return recordstatehead;
|
|
}
|
|
|
|
static XLogRecParseState* btree_xlog_split_parse_block(XLogReaderState* record, uint32* blocknum)
|
|
{
|
|
xl_btree_split* xlrec = (xl_btree_split*)XLogRecGetData(record);
|
|
bool isleaf = (xlrec->level == 0);
|
|
BlockNumber leftsib;
|
|
BlockNumber rightsib;
|
|
BlockNumber rnext;
|
|
XLogRecParseState* recordstatehead = NULL;
|
|
XLogRecParseState* blockstate = NULL;
|
|
|
|
XLogRecGetBlockTag(record, BTREE_SPLIT_LEFT_BLOCK_NUM, NULL, NULL, &leftsib);
|
|
XLogRecGetBlockTag(record, BTREE_SPLIT_RIGHT_BLOCK_NUM, NULL, NULL, &rightsib);
|
|
if (!XLogRecGetBlockTag(record, BTREE_SPLIT_RIGHTNEXT_BLOCK_NUM, NULL, NULL, &rnext))
|
|
rnext = P_NONE;
|
|
|
|
*blocknum = 1;
|
|
XLogParseBufferAllocListFunc(record, &recordstatehead, NULL);
|
|
if (recordstatehead == NULL) {
|
|
return NULL;
|
|
}
|
|
XLogRecSetBlockDataState(record, BTREE_SPLIT_LEFT_BLOCK_NUM, recordstatehead);
|
|
XLogRecSetAuxiBlkNumState(&recordstatehead->blockparse.extra_rec.blockdatarec, rightsib, InvalidForkNumber);
|
|
|
|
(*blocknum)++;
|
|
XLogParseBufferAllocListFunc(record, &blockstate, recordstatehead);
|
|
if (blockstate == NULL) {
|
|
return NULL;
|
|
}
|
|
XLogRecSetBlockDataState(record, BTREE_SPLIT_RIGHT_BLOCK_NUM, blockstate);
|
|
XLogRecSetAuxiBlkNumState(&blockstate->blockparse.extra_rec.blockdatarec, rnext, leftsib);
|
|
|
|
if (rnext != P_NONE) {
|
|
(*blocknum)++;
|
|
XLogParseBufferAllocListFunc(record, &blockstate, recordstatehead);
|
|
if (blockstate == NULL) {
|
|
return NULL;
|
|
}
|
|
XLogRecSetBlockDataState(record, BTREE_SPLIT_RIGHTNEXT_BLOCK_NUM, blockstate);
|
|
XLogRecSetAuxiBlkNumState(&blockstate->blockparse.extra_rec.blockdatarec, rightsib, InvalidForkNumber);
|
|
}
|
|
|
|
if (!isleaf) {
|
|
(*blocknum)++;
|
|
XLogParseBufferAllocListFunc(record, &blockstate, recordstatehead);
|
|
if (blockstate == NULL) {
|
|
return NULL;
|
|
}
|
|
XLogRecSetBlockDataState(record, BTREE_SPLIT_CHILD_BLOCK_NUM, blockstate);
|
|
}
|
|
|
|
return recordstatehead;
|
|
}
|
|
|
|
static XLogRecParseState* btree_xlog_vacuum_parse_block(XLogReaderState* record, uint32* blocknum)
|
|
{
|
|
XLogRecParseState* recordstatehead = NULL;
|
|
XLogRecParseState* blockstate = NULL;
|
|
|
|
*blocknum = 1;
|
|
XLogParseBufferAllocListFunc(record, &recordstatehead, NULL);
|
|
if (recordstatehead == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
XLogRecSetBlockDataState(record, BTREE_VACUUM_ORIG_BLOCK_NUM, recordstatehead);
|
|
|
|
if (SUPPORT_HOT_STANDBY) {
|
|
BlockNumber thisblkno;
|
|
RelFileNode thisrnode;
|
|
|
|
xl_btree_vacuum* xlrec = (xl_btree_vacuum*)XLogRecGetData(record);
|
|
XLogRecGetBlockTag(record, BTREE_VACUUM_ORIG_BLOCK_NUM, &thisrnode, NULL, &thisblkno);
|
|
|
|
if ((xlrec->lastBlockVacuumed + 1) < thisblkno) {
|
|
(*blocknum)++;
|
|
XLogParseBufferAllocListFunc(record, &blockstate, recordstatehead);
|
|
if (blockstate == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
XLogRecSetBlockCommonState(
|
|
record, BLOCK_DATA_VACUUM_PIN_TYPE, MAIN_FORKNUM, thisblkno, &thisrnode, blockstate);
|
|
XLogRecSetPinVacuumState(&blockstate->blockparse.extra_rec.blockvacuumpin, xlrec->lastBlockVacuumed);
|
|
}
|
|
}
|
|
|
|
return recordstatehead;
|
|
}
|
|
|
|
static XLogRecParseState* btree_xlog_delete_parse_block(XLogReaderState* record, uint32* blocknum)
|
|
{
|
|
XLogRecParseState* recordstatehead = NULL;
|
|
|
|
*blocknum = 1;
|
|
XLogParseBufferAllocListFunc(record, &recordstatehead, NULL);
|
|
if (recordstatehead == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
XLogRecSetBlockDataState(record, BTREE_DELETE_ORIG_BLOCK_NUM, recordstatehead);
|
|
|
|
/* for hot standby, need to reslove the conflict */
|
|
{
|
|
/* wait for syn with pg > 9.6 */
|
|
}
|
|
return recordstatehead;
|
|
}
|
|
|
|
static XLogRecParseState* btree_xlog_delete_page_parse_block(XLogReaderState* record, uint32* blocknum)
|
|
{
|
|
uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
|
|
xl_btree_delete_page* xlrec = (xl_btree_delete_page*)XLogRecGetData(record);
|
|
XLogRecParseState* recordstatehead = NULL;
|
|
XLogRecParseState* blockstate = NULL;
|
|
|
|
*blocknum = 1;
|
|
XLogParseBufferAllocListFunc(record, &recordstatehead, NULL);
|
|
if (recordstatehead == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
XLogRecSetBlockDataState(record, BTREE_DELETE_PAGE_TARGET_BLOCK_NUM, recordstatehead);
|
|
|
|
(*blocknum)++;
|
|
XLogParseBufferAllocListFunc(record, &blockstate, recordstatehead);
|
|
if (blockstate == NULL) {
|
|
return NULL;
|
|
}
|
|
XLogRecSetBlockDataState(record, BTREE_DELETE_PAGE_PARENT_BLOCK_NUM, blockstate);
|
|
|
|
(*blocknum)++;
|
|
XLogParseBufferAllocListFunc(record, &blockstate, recordstatehead);
|
|
if (blockstate == NULL) {
|
|
return NULL;
|
|
}
|
|
XLogRecSetBlockDataState(record, BTREE_DELETE_PAGE_RIGHT_BLOCK_NUM, blockstate);
|
|
|
|
if (xlrec->leftblk != P_NONE) {
|
|
(*blocknum)++;
|
|
XLogParseBufferAllocListFunc(record, &blockstate, recordstatehead);
|
|
if (blockstate == NULL) {
|
|
return NULL;
|
|
}
|
|
XLogRecSetBlockDataState(record, BTREE_DELETE_PAGE_LEFT_BLOCK_NUM, blockstate);
|
|
}
|
|
|
|
/* Update metapage if needed */
|
|
if (info == XLOG_BTREE_DELETE_PAGE_META) {
|
|
(*blocknum)++;
|
|
XLogParseBufferAllocListFunc(record, &blockstate, recordstatehead);
|
|
if (blockstate == NULL) {
|
|
return NULL;
|
|
}
|
|
XLogRecSetBlockDataState(record, BTREE_DELETE_PAGE_META_BLOCK_NUM, blockstate);
|
|
}
|
|
|
|
/* forget incomplete action */
|
|
|
|
if (info == XLOG_BTREE_DELETE_PAGE_HALF) {
|
|
/* log incomplete action */
|
|
}
|
|
|
|
return recordstatehead;
|
|
}
|
|
|
|
static XLogRecParseState* btree_xlog_newroot_parse_block(XLogReaderState* record, uint32* blocknum)
|
|
{
|
|
xl_btree_newroot* xlrec = (xl_btree_newroot*)XLogRecGetData(record);
|
|
XLogRecParseState* recordstatehead = NULL;
|
|
XLogRecParseState* blockstate = NULL;
|
|
|
|
*blocknum = 1;
|
|
XLogParseBufferAllocListFunc(record, &recordstatehead, NULL);
|
|
if (recordstatehead == NULL) {
|
|
return NULL;
|
|
}
|
|
XLogRecSetBlockDataState(record, BTREE_NEWROOT_ORIG_BLOCK_NUM, recordstatehead);
|
|
|
|
if (xlrec->level > 0) {
|
|
(*blocknum)++;
|
|
XLogParseBufferAllocListFunc(record, &blockstate, recordstatehead);
|
|
if (blockstate == NULL) {
|
|
return NULL;
|
|
}
|
|
XLogRecSetBlockDataState(record, BTREE_NEWROOT_LEFT_BLOCK_NUM, blockstate);
|
|
}
|
|
|
|
(*blocknum)++;
|
|
XLogParseBufferAllocListFunc(record, &blockstate, recordstatehead);
|
|
if (blockstate == NULL) {
|
|
return NULL;
|
|
}
|
|
XLogRecSetBlockDataState(record, BTREE_NEWROOT_META_BLOCK_NUM, blockstate);
|
|
|
|
return recordstatehead;
|
|
}
|
|
|
|
static XLogRecParseState* btree_xlog_reuse_page_parse_block(XLogReaderState* record, uint32* blocknum)
|
|
{
|
|
XLogRecParseState* recordstatehead = NULL;
|
|
xl_btree_reuse_page* xlrec = (xl_btree_reuse_page*)XLogRecGetData(record);
|
|
|
|
*blocknum = 0;
|
|
if (SUPPORT_HOT_STANDBY) {
|
|
(*blocknum)++;
|
|
XLogParseBufferAllocListFunc(record, &recordstatehead, NULL);
|
|
if (recordstatehead == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
RelFileNode rnode;
|
|
RelFileNodeCopy(rnode, xlrec->node, XLogRecGetBucketId(record));
|
|
XLogRecSetBlockCommonState(
|
|
record, BLOCK_DATA_INVALIDMSG_TYPE, InvalidForkNumber, InvalidBlockNumber, &rnode, recordstatehead);
|
|
XLogRecSetInvalidMsgState(&recordstatehead->blockparse.extra_rec.blockinvalidmsg, xlrec->latestRemovedXid);
|
|
}
|
|
return recordstatehead;
|
|
}
|
|
|
|
XLogRecParseState* btree_redo_parse_to_block(XLogReaderState* record, uint32* blocknum)
|
|
{
|
|
uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
|
|
XLogRecParseState* recordblockstate = NULL;
|
|
|
|
*blocknum = 0;
|
|
switch (info) {
|
|
case XLOG_BTREE_INSERT_LEAF:
|
|
case XLOG_BTREE_INSERT_UPPER:
|
|
case XLOG_BTREE_INSERT_META:
|
|
recordblockstate = btree_xlog_insert_parse_block(record, blocknum);
|
|
break;
|
|
case XLOG_BTREE_SPLIT_L:
|
|
case XLOG_BTREE_SPLIT_R:
|
|
case XLOG_BTREE_SPLIT_L_ROOT:
|
|
case XLOG_BTREE_SPLIT_R_ROOT:
|
|
recordblockstate = btree_xlog_split_parse_block(record, blocknum);
|
|
break;
|
|
case XLOG_BTREE_VACUUM:
|
|
recordblockstate = btree_xlog_vacuum_parse_block(record, blocknum);
|
|
break;
|
|
case XLOG_BTREE_DELETE:
|
|
recordblockstate = btree_xlog_delete_parse_block(record, blocknum);
|
|
break;
|
|
case XLOG_BTREE_DELETE_PAGE:
|
|
case XLOG_BTREE_DELETE_PAGE_META:
|
|
case XLOG_BTREE_DELETE_PAGE_HALF:
|
|
recordblockstate = btree_xlog_delete_page_parse_block(record, blocknum);
|
|
break;
|
|
case XLOG_BTREE_NEWROOT:
|
|
recordblockstate = btree_xlog_newroot_parse_block(record, blocknum);
|
|
break;
|
|
case XLOG_BTREE_REUSE_PAGE:
|
|
recordblockstate = btree_xlog_reuse_page_parse_block(record, blocknum);
|
|
break;
|
|
default:
|
|
ereport(PANIC, (errmsg("btree_redo_parse_to_block: unknown op code %u", info)));
|
|
}
|
|
|
|
return recordblockstate;
|
|
}
|
|
|
|
static void btree_xlog_insert_block(
|
|
XLogBlockHead* blockhead, XLogBlockDataParse* blockdatarec, RedoBufferInfo* bufferinfo)
|
|
{
|
|
XLogBlockDataParse* datadecode = blockdatarec;
|
|
Size blkdatalen;
|
|
char* blkdata = NULL;
|
|
blkdata = XLogBlockDataGetBlockData(datadecode, &blkdatalen);
|
|
|
|
if (XLogBlockDataGetBlockId(datadecode) == BTREE_INSERT_ORIG_BLOCK_NUM) {
|
|
XLogRedoAction action;
|
|
action = XLogCheckBlockDataRedoAction(datadecode, bufferinfo);
|
|
if (action == BLK_NEEDS_REDO) {
|
|
Assert(blkdata != NULL);
|
|
char* maindata = XLogBlockDataGetMainData(datadecode, NULL);
|
|
|
|
btree_xlog_insert_operator_page(bufferinfo, (void*)maindata, (void*)blkdata, blkdatalen);
|
|
MakeRedoBufferDirty(bufferinfo);
|
|
}
|
|
} else if (XLogBlockDataGetBlockId(datadecode) == BTREE_INSERT_CHILD_BLOCK_NUM) {
|
|
/* child */
|
|
XLogRedoAction action;
|
|
action = XLogCheckBlockDataRedoAction(datadecode, bufferinfo);
|
|
if (action == BLK_NEEDS_REDO) {
|
|
btree_xlog_clear_incomplete_split(bufferinfo);
|
|
MakeRedoBufferDirty(bufferinfo);
|
|
}
|
|
} else {
|
|
/* meta */
|
|
btree_restore_meta_operator_page(bufferinfo, (void*)blkdata, blkdatalen);
|
|
MakeRedoBufferDirty(bufferinfo);
|
|
}
|
|
}
|
|
|
|
static void btree_xlog_split_block(
|
|
XLogBlockHead* blockhead, XLogBlockDataParse* blockdatarec, RedoBufferInfo* bufferinfo)
|
|
{
|
|
uint8 info = XLogBlockHeadGetInfo(blockhead) & ~XLR_INFO_MASK;
|
|
XLogBlockDataParse* datadecode = blockdatarec;
|
|
|
|
if (XLogBlockDataGetBlockId(datadecode) == BTREE_SPLIT_RIGHT_BLOCK_NUM) {
|
|
/* right page */
|
|
BlockNumber leftsib;
|
|
BlockNumber rnext;
|
|
Size blkdatalen;
|
|
char* blkdata = NULL;
|
|
char* maindata = XLogBlockDataGetMainData(datadecode, NULL);
|
|
|
|
blkdata = XLogBlockDataGetBlockData(datadecode, &blkdatalen);
|
|
rnext = XLogBlockDataGetAuxiBlock1(datadecode);
|
|
leftsib = XLogBlockDataGetAuxiBlock2(datadecode);
|
|
|
|
btree_xlog_split_operator_rightpage(bufferinfo, (void*)maindata, leftsib, rnext, (void*)blkdata, blkdatalen);
|
|
MakeRedoBufferDirty(bufferinfo);
|
|
} else {
|
|
XLogRedoAction action;
|
|
action = XLogCheckBlockDataRedoAction(datadecode, bufferinfo);
|
|
if (action == BLK_NEEDS_REDO) {
|
|
BlockNumber rightsib;
|
|
rightsib = XLogBlockDataGetAuxiBlock1(datadecode);
|
|
|
|
if (XLogBlockDataGetBlockId(datadecode) == BTREE_SPLIT_LEFT_BLOCK_NUM) {
|
|
/* left page */
|
|
Size blkdatalen;
|
|
char* blkdata = NULL;
|
|
char* maindata = XLogBlockDataGetMainData(datadecode, NULL);
|
|
bool onleft = ((info == XLOG_BTREE_SPLIT_L) || (info == XLOG_BTREE_SPLIT_L_ROOT));
|
|
|
|
blkdata = XLogBlockDataGetBlockData(datadecode, &blkdatalen);
|
|
btree_xlog_split_operator_leftpage(
|
|
bufferinfo, (void*)maindata, rightsib, onleft, (void*)blkdata, blkdatalen, NULL, 0);
|
|
} else if (XLogBlockDataGetBlockId(datadecode) == BTREE_SPLIT_RIGHTNEXT_BLOCK_NUM) {
|
|
/* right next */
|
|
btree_xlog_split_operator_nextpage(bufferinfo, rightsib);
|
|
} else {
|
|
/* child */
|
|
btree_xlog_clear_incomplete_split(bufferinfo);
|
|
}
|
|
MakeRedoBufferDirty(bufferinfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void btree_xlog_vacuum_block(
|
|
XLogBlockHead* blockhead, XLogBlockDataParse* blockdatarec, RedoBufferInfo* bufferinfo)
|
|
{
|
|
XLogBlockDataParse* datadecode = blockdatarec;
|
|
XLogRedoAction action;
|
|
action = XLogCheckBlockDataRedoAction(datadecode, bufferinfo);
|
|
if (action == BLK_NEEDS_REDO) {
|
|
char* maindata = XLogBlockDataGetMainData(datadecode, NULL);
|
|
Size blkdatalen = 0;
|
|
char* blkdata = NULL;
|
|
|
|
blkdata = XLogBlockDataGetBlockData(datadecode, &blkdatalen);
|
|
|
|
btree_xlog_vacuum_operator_page(bufferinfo, (void*)maindata, (void*)blkdata, blkdatalen);
|
|
|
|
MakeRedoBufferDirty(bufferinfo);
|
|
}
|
|
}
|
|
|
|
static void btree_xlog_delete_block(
|
|
XLogBlockHead* blockhead, XLogBlockDataParse* blockdatarec, RedoBufferInfo* bufferinfo)
|
|
{
|
|
XLogBlockDataParse* datadecode = blockdatarec;
|
|
XLogRedoAction action;
|
|
action = XLogCheckBlockDataRedoAction(datadecode, bufferinfo);
|
|
if (action == BLK_NEEDS_REDO) {
|
|
Size maindatalen;
|
|
char* maindata = XLogBlockDataGetMainData(datadecode, &maindatalen);
|
|
btree_xlog_delete_operator_page(bufferinfo, (void*)maindata, maindatalen);
|
|
|
|
MakeRedoBufferDirty(bufferinfo);
|
|
}
|
|
}
|
|
|
|
static void btree_xlog_delete_page_block(
|
|
XLogBlockHead* blockhead, XLogBlockDataParse* blockdatarec, RedoBufferInfo* bufferinfo)
|
|
{
|
|
uint8 info = XLogBlockHeadGetInfo(blockhead) & ~XLR_INFO_MASK;
|
|
XLogBlockDataParse* datadecode = blockdatarec;
|
|
uint8 block_id = XLogBlockDataGetBlockId(datadecode);
|
|
char* maindata = XLogBlockDataGetMainData(datadecode, NULL);
|
|
|
|
if (block_id == BTREE_DELETE_PAGE_TARGET_BLOCK_NUM) {
|
|
btree_xlog_delete_page_operator_currentpage(bufferinfo, (void*)maindata);
|
|
MakeRedoBufferDirty(bufferinfo);
|
|
} else if (block_id == BTREE_DELETE_PAGE_META_BLOCK_NUM) {
|
|
Size blkdatalen;
|
|
char* blkdata = NULL;
|
|
|
|
blkdata = XLogBlockDataGetBlockData(datadecode, &blkdatalen);
|
|
|
|
btree_restore_meta_operator_page(bufferinfo, (void*)blkdata, blkdatalen);
|
|
MakeRedoBufferDirty(bufferinfo);
|
|
} else {
|
|
XLogRedoAction action;
|
|
action = XLogCheckBlockDataRedoAction(datadecode, bufferinfo);
|
|
if (action == BLK_NEEDS_REDO) {
|
|
if (block_id == BTREE_DELETE_PAGE_PARENT_BLOCK_NUM) {
|
|
btree_xlog_delete_page_operator_parentpage(bufferinfo, (void*)maindata, info);
|
|
} else if (block_id == BTREE_DELETE_PAGE_RIGHT_BLOCK_NUM) {
|
|
btree_xlog_delete_page_operator_rightpage(bufferinfo, (void*)maindata);
|
|
} else {
|
|
btree_xlog_delete_page_operator_leftpage(bufferinfo, (void*)maindata);
|
|
}
|
|
MakeRedoBufferDirty(bufferinfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void btree_xlog_newroot_block(
|
|
XLogBlockHead* blockhead, XLogBlockDataParse* blockdatarec, RedoBufferInfo* bufferinfo)
|
|
{
|
|
XLogBlockDataParse* datadecode = blockdatarec;
|
|
Size blkdatalen;
|
|
char* blkdata = NULL;
|
|
BlockNumber downlink = 0;
|
|
|
|
blkdata = XLogBlockDataGetBlockData(datadecode, &blkdatalen);
|
|
|
|
if (XLogBlockDataGetBlockId(datadecode) == BTREE_NEWROOT_ORIG_BLOCK_NUM) {
|
|
char* maindata = XLogBlockDataGetMainData(datadecode, NULL);
|
|
btree_xlog_newroot_operator_page(bufferinfo, (void*)maindata, (void*)blkdata, blkdatalen, &downlink);
|
|
} else if (XLogBlockDataGetBlockId(datadecode) == BTREE_NEWROOT_LEFT_BLOCK_NUM) {
|
|
XLogRedoAction action;
|
|
action = XLogCheckBlockDataRedoAction(datadecode, bufferinfo);
|
|
if (action == BLK_NEEDS_REDO) {
|
|
btree_xlog_clear_incomplete_split(bufferinfo);
|
|
}
|
|
} else {
|
|
btree_restore_meta_operator_page(bufferinfo, (void*)blkdata, blkdatalen);
|
|
}
|
|
MakeRedoBufferDirty(bufferinfo);
|
|
}
|
|
|
|
void btree_redo_data_block(XLogBlockHead* blockhead, XLogBlockDataParse* blockdatarec, RedoBufferInfo* bufferinfo)
|
|
{
|
|
uint8 info = XLogBlockHeadGetInfo(blockhead) & ~XLR_INFO_MASK;
|
|
|
|
switch (info) {
|
|
case XLOG_BTREE_INSERT_LEAF:
|
|
case XLOG_BTREE_INSERT_UPPER:
|
|
case XLOG_BTREE_INSERT_META:
|
|
btree_xlog_insert_block(blockhead, blockdatarec, bufferinfo);
|
|
break;
|
|
case XLOG_BTREE_SPLIT_L:
|
|
case XLOG_BTREE_SPLIT_R:
|
|
case XLOG_BTREE_SPLIT_L_ROOT:
|
|
case XLOG_BTREE_SPLIT_R_ROOT:
|
|
btree_xlog_split_block(blockhead, blockdatarec, bufferinfo);
|
|
break;
|
|
case XLOG_BTREE_VACUUM:
|
|
btree_xlog_vacuum_block(blockhead, blockdatarec, bufferinfo);
|
|
break;
|
|
case XLOG_BTREE_DELETE:
|
|
btree_xlog_delete_block(blockhead, blockdatarec, bufferinfo);
|
|
break;
|
|
case XLOG_BTREE_DELETE_PAGE:
|
|
case XLOG_BTREE_DELETE_PAGE_META:
|
|
case XLOG_BTREE_DELETE_PAGE_HALF:
|
|
btree_xlog_delete_page_block(blockhead, blockdatarec, bufferinfo);
|
|
break;
|
|
case XLOG_BTREE_NEWROOT:
|
|
btree_xlog_newroot_block(blockhead, blockdatarec, bufferinfo);
|
|
break;
|
|
default:
|
|
ereport(PANIC, (errmsg("btree_redo_block: unknown op code %u", info)));
|
|
}
|
|
}
|