diff --git a/contrib/pg_xlogdump/rmgrdesc.cpp b/contrib/pg_xlogdump/rmgrdesc.cpp index 5605d004..ce834d13 100644 --- a/contrib/pg_xlogdump/rmgrdesc.cpp +++ b/contrib/pg_xlogdump/rmgrdesc.cpp @@ -13,6 +13,7 @@ #include "access/gin.h" #include "access/gist_private.h" #include "access/hash.h" +#include "access/hash_xlog.h" #include "access/heapam.h" #include "access/multixact.h" #include "access/nbtree.h" diff --git a/contrib/pgstattuple/pgstattuple.cpp b/contrib/pgstattuple/pgstattuple.cpp index 6b18b9a7..0760c570 100644 --- a/contrib/pgstattuple/pgstattuple.cpp +++ b/contrib/pgstattuple/pgstattuple.cpp @@ -363,7 +363,6 @@ static void pgstat_hash_page(pgstattuple_type* stat, Relation rel, BlockNumber b Page page; OffsetNumber maxoff; - _hash_getlock(rel, blkno, HASH_SHARE); buf = _hash_getbuf_with_strategy(rel, blkno, HASH_READ, 0, bstrategy); page = BufferGetPage(buf); @@ -390,7 +389,6 @@ static void pgstat_hash_page(pgstattuple_type* stat, Relation rel, BlockNumber b } _hash_relbuf(rel, buf); - _hash_droplock(rel, blkno, HASH_SHARE); } /* diff --git a/src/common/backend/parser/parse_utilcmd.cpp b/src/common/backend/parser/parse_utilcmd.cpp index 49df36e0..d10b5a89 100644 --- a/src/common/backend/parser/parse_utilcmd.cpp +++ b/src/common/backend/parser/parse_utilcmd.cpp @@ -3353,12 +3353,21 @@ IndexStmt* transformIndexStmt(Oid relid, IndexStmt* stmt, const char* queryStrin if (!isColStore && (0 != pg_strcasecmp(stmt->accessMethod, DEFAULT_INDEX_TYPE)) && (0 != pg_strcasecmp(stmt->accessMethod, DEFAULT_GIN_INDEX_TYPE)) && - (0 != pg_strcasecmp(stmt->accessMethod, DEFAULT_GIST_INDEX_TYPE))) { - /* row store only support btree/gin/gist index */ + (0 != pg_strcasecmp(stmt->accessMethod, DEFAULT_GIST_INDEX_TYPE)) && + (0 != pg_strcasecmp(stmt->accessMethod, DEFAULT_HASH_INDEX_TYPE))) { + /* row store only support btree/gin/gist/hash index */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("access method \"%s\" does not support row store", stmt->accessMethod))); } + + if (0 == pg_strcasecmp(stmt->accessMethod, DEFAULT_HASH_INDEX_TYPE) && + t_thrd.proc->workingVersionNum < SUPPORT_HASH_XLOG_VERSION_NUM) { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("access method \"%s\" does not support row store", stmt->accessMethod))); + } + if (isColStore && (!isPsortMothed && !isCBtreeMethod && !isCGinBtreeMethod)) { /* column store support psort/cbtree/gin index */ ereport(ERROR, diff --git a/src/common/backend/utils/init/globals.cpp b/src/common/backend/utils/init/globals.cpp index 7f11d712..dd94e4d7 100644 --- a/src/common/backend/utils/init/globals.cpp +++ b/src/common/backend/utils/init/globals.cpp @@ -79,6 +79,7 @@ const uint32 ML_OPT_MODEL_VERSION_NUM = 92284; const uint32 FIX_SQL_ADD_RELATION_REF_COUNT = 92291; const uint32 GENERATED_COL_VERSION_NUM = 92303; const uint32 ANALYZER_HOOK_VERSION_NUM = 92306; +const uint32 SUPPORT_HASH_XLOG_VERSION_NUM = 92304; /* This variable indicates wheather the instance is in progress of upgrade as a whole */ uint32 volatile WorkingGrandVersionNum = GRAND_VERSION_NUM; diff --git a/src/gausskernel/storage/access/hash/hash_xlog.cpp b/src/gausskernel/storage/access/hash/hash_xlog.cpp new file mode 100644 index 00000000..a0a491e9 --- /dev/null +++ b/src/gausskernel/storage/access/hash/hash_xlog.cpp @@ -0,0 +1,865 @@ +/*------------------------------------------------------------------------- + * + * hash_xlog.cpp + * WAL replay logic for hash index. + * + * Portions Copyright (c) 2021 Huawei Technologies Co.,Ltd. + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/gausskernel/storage/access/hash/hash_xlog.cpp + * + *------------------------------------------------------------------------- + */ + +#include "access/xlogproc.h" +#include "access/hash.h" +#include "access/hash_xlog.h" +#include "access/xlogutils.h" +#include "access/xlog.h" +#include "access/transam.h" +#include "access/xlogproc.h" +#include "storage/procarray.h" +#include "miscadmin.h" + +/* + * replay a hash index meta page + */ +static void hash_xlog_init_meta_page(XLogReaderState *record) +{ + RedoBufferInfo metabuf; + ForkNumber forknum; + + /* create the index' metapage */ + XLogInitBufferForRedo(record, 0, &metabuf); + Assert(BufferIsValid(metabuf.buf)); + HashRedoInitMetaPageOperatorPage(&metabuf, XLogRecGetData(record)); + MarkBufferDirty(metabuf.buf); + + /* + * Force the on-disk state of init forks to always be in sync with the + * state in shared buffers. See XLogReadBufferForRedoExtended. We need + * special handling for init forks as create index operations don't log a + * full page image of the metapage. + */ + XLogRecGetBlockTag(record, 0, NULL, &forknum, NULL); + if (forknum == INIT_FORKNUM) + FlushOneBuffer(metabuf.buf); + + /* all done */ + UnlockReleaseBuffer(metabuf.buf); +} + +/* + * replay a hash index bitmap page + */ +static void hash_xlog_init_bitmap_page(XLogReaderState *record) +{ + RedoBufferInfo bitmapbuf; + RedoBufferInfo metabuf; + ForkNumber forknum; + + /* + * Initialize bitmap page + */ + XLogInitBufferForRedo(record, 0, &bitmapbuf); + HashRedoInitBitmapPageOperatorBitmapPage(&bitmapbuf, XLogRecGetData(record)); + MarkBufferDirty(bitmapbuf.buf); + + /* + * Force the on-disk state of init forks to always be in sync with the + * state in shared buffers. See XLogReadBufferForRedoExtended. We need + * special handling for init forks as create index operations don't log a + * full page image of the metapage. + */ + XLogRecGetBlockTag(record, 0, NULL, &forknum, NULL); + if (forknum == INIT_FORKNUM) + FlushOneBuffer(bitmapbuf.buf); + UnlockReleaseBuffer(bitmapbuf.buf); + + /* add the new bitmap page to the metapage's list of bitmaps */ + if (XLogReadBufferForRedo(record, 1, &metabuf) == BLK_NEEDS_REDO) { + /* + * Note: in normal operation, we'd update the metapage while still + * holding lock on the bitmap page. But during replay it's not + * necessary to hold that lock, since nobody can see it yet; the + * creating transaction hasn't yet committed. + */ + HashRedoInitBitmapPageOperatorMetaPage(&metabuf); + MarkBufferDirty(metabuf.buf); + + XLogRecGetBlockTag(record, 1, NULL, &forknum, NULL); + if (forknum == INIT_FORKNUM) + FlushOneBuffer(metabuf.buf); + } + if (BufferIsValid(metabuf.buf)) + UnlockReleaseBuffer(metabuf.buf); +} + +/* + * replay a hash index insert without split + */ +static void hash_xlog_insert(XLogReaderState *record) +{ + RedoBufferInfo buffer; + RedoBufferInfo metabuf; + + if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO) { + Size datalen; + char *datapos = XLogRecGetBlockData(record, 0, &datalen); + + HashRedoInsertOperatorPage(&buffer, XLogRecGetData(record), datapos, datalen); + MarkBufferDirty(buffer.buf); + } + if (BufferIsValid(buffer.buf)) + UnlockReleaseBuffer(buffer.buf); + + if (XLogReadBufferForRedo(record, 1, &metabuf) == BLK_NEEDS_REDO) { + /* + * Note: in normal operation, we'd update the metapage while still + * holding lock on the page we inserted into. But during replay it's + * not necessary to hold that lock, since no other index updates can + * be happening concurrently. + */ + HashRedoInsertOperatorMetaPage(&metabuf); + MarkBufferDirty(metabuf.buf); + } + if (BufferIsValid(metabuf.buf)) + UnlockReleaseBuffer(metabuf.buf); +} + +/* + * replay addition of overflow page for hash index + */ +static void hash_xlog_add_ovfl_page(XLogReaderState* record) +{ + RedoBufferInfo leftbuf; + RedoBufferInfo ovflbuf; + RedoBufferInfo metabuf; + BlockNumber leftblk; + BlockNumber rightblk; + char *data; + Size datalen; + + XLogRecGetBlockTag(record, 0, NULL, NULL, &rightblk); + XLogRecGetBlockTag(record, 1, NULL, NULL, &leftblk); + + XLogInitBufferForRedo(record, 0, &ovflbuf); + Assert(BufferIsValid(ovflbuf.buf)); + + data = XLogRecGetBlockData(record, 0, &datalen); + HashRedoAddOvflPageOperatorOvflPage(&ovflbuf, leftblk, data, datalen); + MarkBufferDirty(ovflbuf.buf); + + if (XLogReadBufferForRedo(record, 1, &leftbuf) == BLK_NEEDS_REDO) { + HashRedoAddOvflPageOperatorLeftPage(&leftbuf, rightblk); + MarkBufferDirty(leftbuf.buf); + } + + if (BufferIsValid(leftbuf.buf)) + UnlockReleaseBuffer(leftbuf.buf); + UnlockReleaseBuffer(ovflbuf.buf); + + /* + * Note: in normal operation, we'd update the bitmap and meta page while + * still holding lock on the overflow pages. But during replay it's not + * necessary to hold those locks, since no other index updates can be + * happening concurrently. + */ + if (XLogRecHasBlockRef(record, 2)) { + RedoBufferInfo mapbuffer; + + if (XLogReadBufferForRedo(record, 2, &mapbuffer) == BLK_NEEDS_REDO) { + data = XLogRecGetBlockData(record, 2, &datalen); + + HashRedoAddOvflPageOperatorMapPage(&mapbuffer, data); + MarkBufferDirty(mapbuffer.buf); + } + if (BufferIsValid(mapbuffer.buf)) + UnlockReleaseBuffer(mapbuffer.buf); + } + + if (XLogRecHasBlockRef(record, 3)) { + RedoBufferInfo newmapbuf; + + XLogInitBufferForRedo(record, 3, &newmapbuf); + + HashRedoAddOvflPageOperatorNewmapPage(&newmapbuf, XLogRecGetData(record)); + MarkBufferDirty(newmapbuf.buf); + + UnlockReleaseBuffer(newmapbuf.buf); + } + + if (XLogReadBufferForRedo(record, 4, &metabuf) == BLK_NEEDS_REDO) { + data = XLogRecGetBlockData(record, 4, &datalen); + + HashRedoAddOvflPageOperatorMetaPage(&metabuf, XLogRecGetData(record), data, datalen); + MarkBufferDirty(metabuf.buf); + } + if (BufferIsValid(metabuf.buf)) + UnlockReleaseBuffer(metabuf.buf); +} + +/* + * replay allocation of page for split operation + */ +static void hash_xlog_split_allocate_page(XLogReaderState *record) +{ + RedoBufferInfo oldbuf; + RedoBufferInfo newbuf; + RedoBufferInfo metabuf; + Size datalen PG_USED_FOR_ASSERTS_ONLY; + char *data; + XLogRedoAction action; + + /* + * To be consistent with normal operation, here we take cleanup locks on + * both the old and new buckets even though there can't be any concurrent + * inserts. + */ + + /* replay the record for old bucket */ + action = XLogReadBufferForRedoExtended(record, 0, RBM_NORMAL, true, &oldbuf); + + /* + * Note that we still update the page even if it was restored from a full + * page image, because the special space is not included in the image. + */ + if (action == BLK_NEEDS_REDO || action == BLK_RESTORED) { + HashRedoSplitAllocatePageOperatorObukPage(&oldbuf, XLogRecGetData(record)); + MarkBufferDirty(oldbuf.buf); + } + + /* replay the record for new bucket */ + XLogInitBufferForRedo(record, 1, &newbuf); + HashRedoSplitAllocatePageOperatorNbukPage(&newbuf, XLogRecGetData(record)); + if (!IsBufferCleanupOK(newbuf.buf)) + elog(PANIC, "hash_xlog_split_allocate_page: failed to acquire cleanup lock"); + MarkBufferDirty(newbuf.buf); + + /* + * We can release the lock on old bucket early as well but doing here to + * consistent with normal operation. + */ + if (BufferIsValid(oldbuf.buf)) + UnlockReleaseBuffer(oldbuf.buf); + if (BufferIsValid(newbuf.buf)) + UnlockReleaseBuffer(newbuf.buf); + + /* + * Note: in normal operation, we'd update the meta page while still + * holding lock on the old and new bucket pages. But during replay it's + * not necessary to hold those locks, since no other bucket splits can be + * happening concurrently. + */ + + /* replay the record for metapage changes */ + if (XLogReadBufferForRedo(record, 2, &metabuf) == BLK_NEEDS_REDO) { + data = XLogRecGetBlockData(record, 2, &datalen); + + HashRedoSplitAllocatePageOperatorMetaPage(&metabuf, XLogRecGetData(record), data); + MarkBufferDirty(metabuf.buf); + } + + if (BufferIsValid(metabuf.buf)) + UnlockReleaseBuffer(metabuf.buf); +} + +/* + * replay of split operation + */ +static void hash_xlog_split_page(XLogReaderState *record) +{ + RedoBufferInfo buf; + + if (XLogReadBufferForRedo(record, 0, &buf) != BLK_RESTORED) + elog(ERROR, "Hash split record did not contain a full-page image"); + + UnlockReleaseBuffer(buf.buf); +} + +/* + * replay completion of split operation + */ +static void hash_xlog_split_complete(XLogReaderState *record) +{ + RedoBufferInfo oldbuf; + RedoBufferInfo newbuf; + XLogRedoAction action; + + /* replay the record for old bucket */ + action = XLogReadBufferForRedo(record, 0, &oldbuf); + + /* + * Note that we still update the page even if it was restored from a full + * page image, because the bucket flag is not included in the image. + */ + if (action == BLK_NEEDS_REDO || action == BLK_RESTORED) { + HashRedoSplitCompleteOperatorObukPage(&oldbuf, XLogRecGetData(record)); + MarkBufferDirty(oldbuf.buf); + } + if (BufferIsValid(oldbuf.buf)) + UnlockReleaseBuffer(oldbuf.buf); + + /* replay the record for new bucket */ + action = XLogReadBufferForRedo(record, 1, &newbuf); + + /* + * Note that we still update the page even if it was restored from a full + * page image, because the bucket flag is not included in the image. + */ + if (action == BLK_NEEDS_REDO || action == BLK_RESTORED) { + HashRedoSplitCompleteOperatorNbukPage(&newbuf, XLogRecGetData(record)); + MarkBufferDirty(newbuf.buf); + } + if (BufferIsValid(newbuf.buf)) + UnlockReleaseBuffer(newbuf.buf); +} + +/* + * replay move of page contents for squeeze operation of hash index + */ +static void hash_xlog_move_page_contents(XLogReaderState *record) +{ + XLogRecPtr lsn = record->EndRecPtr; + xl_hash_move_page_contents *xldata = (xl_hash_move_page_contents *) XLogRecGetData(record); + RedoBufferInfo bucketbuf; + RedoBufferInfo writebuf; + RedoBufferInfo deletebuf; + XLogRedoAction action; + + bucketbuf.buf = InvalidBuffer; + writebuf.buf = InvalidBuffer; + deletebuf.buf = InvalidBuffer; + + /* + * Ensure we have a cleanup lock on primary bucket page before we start + * with the actual replay operation. This is to ensure that neither a + * scan can start nor a scan can be already-in-progress during the replay + * of this operation. If we allow scans during this operation, then they + * can miss some records or show the same record multiple times. + */ + if (xldata->is_prim_bucket_same_wrt) + action = XLogReadBufferForRedoExtended(record, 1, RBM_NORMAL, true, &writebuf); + else { + /* + * we don't care for return value as the purpose of reading bucketbuf + * is to ensure a cleanup lock on primary bucket page. + */ + (void) XLogReadBufferForRedoExtended(record, 0, RBM_NORMAL, true, &bucketbuf); + + PageSetLSN(bucketbuf.pageinfo.page, lsn); + + action = XLogReadBufferForRedo(record, 1, &writebuf); + } + + /* replay the record for adding entries in overflow buffer */ + if (action == BLK_NEEDS_REDO) { + Page writepage; + char *begin; + char *data; + Size datalen; + uint16 ninserted = 0; + + data = XLogRecGetBlockData(record, 1, &datalen); + + HashXlogMoveAddPageOperatorPage(&writebuf, XLogRecGetData(record), (void *)data, datalen); + + MarkBufferDirty(writebuf.buf); + } + + /* replay the record for deleting entries from overflow buffer */ + if (XLogReadBufferForRedo(record, 2, &deletebuf) == BLK_NEEDS_REDO) { + Page page; + char *ptr; + Size len; + + ptr = XLogRecGetBlockData(record, 2, &len); + + HashXlogMoveDeleteOvflPageOperatorPage(&deletebuf, (void *)ptr, len); + + MarkBufferDirty(deletebuf.buf); + } + + /* + * Replay is complete, now we can release the buffers. We release locks at + * end of replay operation to ensure that we hold lock on primary bucket + * page till end of operation. We can optimize by releasing the lock on + * write buffer as soon as the operation for same is complete, if it is + * not same as primary bucket page, but that doesn't seem to be worth + * complicating the code. + */ + if (BufferIsValid(deletebuf.buf)) + UnlockReleaseBuffer(deletebuf.buf); + + if (BufferIsValid(writebuf.buf)) + UnlockReleaseBuffer(writebuf.buf); + + if (BufferIsValid(bucketbuf.buf)) + UnlockReleaseBuffer(bucketbuf.buf); +} + +/* + * replay squeeze page operation of hash index + */ +static void hash_xlog_squeeze_page(XLogReaderState *record) +{ + XLogRecPtr lsn = record->EndRecPtr; + xl_hash_squeeze_page *xldata = (xl_hash_squeeze_page *) XLogRecGetData(record); + RedoBufferInfo bucketbuf; + RedoBufferInfo writebuf; + RedoBufferInfo ovflbuf; + RedoBufferInfo prevbuf; + RedoBufferInfo mapbuf; + XLogRedoAction action; + + bucketbuf.buf = InvalidBuffer; + prevbuf.buf = InvalidBuffer; + + /* + * Ensure we have a cleanup lock on primary bucket page before we start + * with the actual replay operation. This is to ensure that neither a + * scan can start nor a scan can be already-in-progress during the replay + * of this operation. If we allow scans during this operation, then they + * can miss some records or show the same record multiple times. + */ + if (xldata->is_prim_bucket_same_wrt) + action = XLogReadBufferForRedoExtended(record, 1, RBM_NORMAL, true, &writebuf); + else { + /* + * we don't care for return value as the purpose of reading bucketbuf + * is to ensure a cleanup lock on primary bucket page. + */ + (void) XLogReadBufferForRedoExtended(record, 0, RBM_NORMAL, true, &bucketbuf); + + PageSetLSN(bucketbuf.pageinfo.page, lsn); + + action = XLogReadBufferForRedo(record, 1, &writebuf); + } + + /* replay the record for adding entries in overflow buffer */ + if (action == BLK_NEEDS_REDO) { + char *data; + Size datalen; + + data = XLogRecGetBlockData(record, 1, &datalen); + + HashXlogSqueezeAddPageOperatorPage(&writebuf, XLogRecGetData(record), (void *)data, datalen); + + MarkBufferDirty(writebuf.buf); + } + + /* replay the record for initializing overflow buffer */ + if (XLogReadBufferForRedo(record, 2, &ovflbuf) == BLK_NEEDS_REDO) { + HashXlogSqueezeInitOvflbufOperatorPage(&ovflbuf, XLogRecGetData(record)); + + MarkBufferDirty(ovflbuf.buf); + } + if (BufferIsValid(ovflbuf.buf)) + UnlockReleaseBuffer(ovflbuf.buf); + + /* replay the record for page previous to the freed overflow page */ + if (!xldata->is_prev_bucket_same_wrt && + XLogReadBufferForRedo(record, 3, &prevbuf) == BLK_NEEDS_REDO) { + HashXlogSqueezeUpdatePrevPageOperatorPage(&prevbuf, XLogRecGetData(record)); + + MarkBufferDirty(prevbuf.buf); + } + if (BufferIsValid(prevbuf.buf)) + UnlockReleaseBuffer(prevbuf.buf); + + /* replay the record for page next to the freed overflow page */ + if (XLogRecHasBlockRef(record, 4)) { + RedoBufferInfo nextbuf; + + if (XLogReadBufferForRedo(record, 4, &nextbuf) == BLK_NEEDS_REDO) { + HashXlogSqueezeUpdateNextPageOperatorPage(&nextbuf, XLogRecGetData(record)); + + MarkBufferDirty(nextbuf.buf); + } + if (BufferIsValid(nextbuf.buf)) + UnlockReleaseBuffer(nextbuf.buf); + } + + if (BufferIsValid(writebuf.buf)) + UnlockReleaseBuffer(writebuf.buf); + + if (BufferIsValid(bucketbuf.buf)) + UnlockReleaseBuffer(bucketbuf.buf); + + /* + * Note: in normal operation, we'd update the bitmap and meta page while + * still holding lock on the primary bucket page and overflow pages. But + * during replay it's not necessary to hold those locks, since no other + * index updates can be happening concurrently. + */ + /* replay the record for bitmap page */ + if (XLogReadBufferForRedo(record, 5, &mapbuf) == BLK_NEEDS_REDO) { + char *data; + Size datalen; + + data = XLogRecGetBlockData(record, 5, &datalen); + HashXlogSqueezeUpdateBitmapOperatorPage(&mapbuf, (void *)data); + + MarkBufferDirty(mapbuf.buf); + } + if (BufferIsValid(mapbuf.buf)) + UnlockReleaseBuffer(mapbuf.buf); + + /* replay the record for meta page */ + if (XLogRecHasBlockRef(record, 6)) { + RedoBufferInfo metabuf; + + if (XLogReadBufferForRedo(record, 6, &metabuf) == BLK_NEEDS_REDO) { + char *data; + Size datalen; + + data = XLogRecGetBlockData(record, 6, &datalen); + HashXlogSqueezeUpdateMateOperatorPage(&metabuf, (void *)data); + + MarkBufferDirty(metabuf.buf); + } + if (BufferIsValid(metabuf.buf)) + UnlockReleaseBuffer(metabuf.buf); + } +} + +/* + * replay delete operation of hash index + */ +static void hash_xlog_delete(XLogReaderState *record) +{ + XLogRecPtr lsn = record->EndRecPtr; + xl_hash_delete *xldata = (xl_hash_delete *) XLogRecGetData(record); + RedoBufferInfo bucketbuf; + RedoBufferInfo deletebuf; + XLogRedoAction action; + + bucketbuf.buf = InvalidBuffer; + + /* + * Ensure we have a cleanup lock on primary bucket page before we start + * with the actual replay operation. This is to ensure that neither a + * scan can start nor a scan can be already-in-progress during the replay + * of this operation. If we allow scans during this operation, then they + * can miss some records or show the same record multiple times. + */ + if (xldata->is_primary_bucket_page) + action = XLogReadBufferForRedoExtended(record, 1, RBM_NORMAL, true, &deletebuf); + else { + /* + * we don't care for return value as the purpose of reading bucketbuf + * is to ensure a cleanup lock on primary bucket page. + */ + (void) XLogReadBufferForRedoExtended(record, 0, RBM_NORMAL, true, &bucketbuf); + + PageSetLSN(bucketbuf.pageinfo.page, lsn); + + action = XLogReadBufferForRedo(record, 1, &deletebuf); + } + + /* replay the record for deleting entries in bucket page */ + if (action == BLK_NEEDS_REDO) { + char *ptr; + Size len; + + ptr = XLogRecGetBlockData(record, 1, &len); + + HashXlogDeleteBlockOperatorPage(&deletebuf, XLogRecGetData(record), (void *)ptr, len); + + MarkBufferDirty(deletebuf.buf); + } + if (BufferIsValid(deletebuf.buf)) + UnlockReleaseBuffer(deletebuf.buf); + + if (BufferIsValid(bucketbuf.buf)) + UnlockReleaseBuffer(bucketbuf.buf); +} + +/* + * replay split cleanup flag operation for primary bucket page. + */ +static void hash_xlog_split_cleanup(XLogReaderState *record) +{ + RedoBufferInfo buffer; + + if (XLogReadBufferForRedo(record, 0, &buffer) == BLK_NEEDS_REDO) { + HashXlogSplitCleanupOperatorPage(&buffer); + + MarkBufferDirty(buffer.buf); + } + if (BufferIsValid(buffer.buf)) + UnlockReleaseBuffer(buffer.buf); +} + +/* + * replay for update meta page + */ +static void hash_xlog_update_meta_page(XLogReaderState *record) +{ + RedoBufferInfo metabuf; + + if (XLogReadBufferForRedo(record, 0, &metabuf) == BLK_NEEDS_REDO) { + HashXlogUpdateMetaOperatorPage(&metabuf, XLogRecGetData(record)); + + MarkBufferDirty(metabuf.buf); + } + if (BufferIsValid(metabuf.buf)) + UnlockReleaseBuffer(metabuf.buf); +} + +/* + * Get the latestRemovedXid from the heap pages pointed at by the index + * tuples being deleted. See also btree_xlog_delete_get_latestRemovedXid, + * on which this function is based. + */ +static TransactionId hash_xlog_vacuum_get_latestRemovedXid(XLogReaderState *record) +{ + xl_hash_vacuum_one_page *xlrec; + OffsetNumber *unused; + Buffer ibuffer; + Buffer hbuffer; + Page ipage; + Page hpage; + RelFileNode rnode; + BlockNumber blkno; + ItemId iitemid; + ItemId hitemid; + IndexTuple itup; + HeapTupleHeader htuphdr; + BlockNumber hblkno; + OffsetNumber hoffnum; + TransactionId latestRemovedXid = InvalidTransactionId; + int i; + + xlrec = (xl_hash_vacuum_one_page *) XLogRecGetData(record); + + /* + * If there's nothing running on the standby we don't need to derive a + * full latestRemovedXid value, so use a fast path out of here. This + * returns InvalidTransactionId, and so will conflict with all HS + * transactions; but since we just worked out that that's zero people, + * it's OK. + * + * XXX There is a race condition here, which is that a new backend might + * start just after we look. If so, it cannot need to conflict, but this + * coding will result in throwing a conflict anyway. + */ + if (CountDBBackends(InvalidOid) == 0) + return latestRemovedXid; + + /* + * Check if WAL replay has reached a consistent database state. If not, we + * must PANIC. See the definition of + * btree_xlog_delete_get_latestRemovedXid for more details. + */ + if (!t_thrd.xlog_cxt.reachedConsistency) + elog(PANIC, "hash_xlog_vacuum_get_latestRemovedXid: cannot operate with inconsistent data"); + + /* + * Get index page. If the DB is consistent, this should not fail, nor + * should any of the heap page fetches below. If one does, we return + * InvalidTransactionId to cancel all HS transactions. That's probably + * overkill, but it's safe, and certainly better than panicking here. + */ + XLogRecGetBlockTag(record, 0, &rnode, NULL, &blkno); + ibuffer = XLogReadBufferExtended(rnode, MAIN_FORKNUM, blkno, RBM_NORMAL); + + if (!BufferIsValid(ibuffer)) + return InvalidTransactionId; + LockBuffer(ibuffer, HASH_READ); + ipage = (Page) BufferGetPage(ibuffer); + + /* + * Loop through the deleted index items to obtain the TransactionId from + * the heap items they point to. + */ + unused = (OffsetNumber *) ((char *) xlrec + SizeOfHashVacuumOnePage); + + for (i = 0; i < xlrec->ntuples; i++) { + /* + * Identify the index tuple about to be deleted. + */ + iitemid = PageGetItemId(ipage, unused[i]); + itup = (IndexTuple) PageGetItem(ipage, iitemid); + + /* + * Locate the heap page that the index tuple points at + */ + hblkno = ItemPointerGetBlockNumber(&(itup->t_tid)); + hbuffer = XLogReadBufferExtended(xlrec->hnode, MAIN_FORKNUM, hblkno, RBM_NORMAL); + + if (!BufferIsValid(hbuffer)) { + UnlockReleaseBuffer(ibuffer); + return InvalidTransactionId; + } + LockBuffer(hbuffer, HASH_READ); + hpage = (Page) BufferGetPage(hbuffer); + + /* + * Look up the heap tuple header that the index tuple points at by + * using the heap node supplied with the xlrec. We can't use + * heap_fetch, since it uses ReadBuffer rather than XLogReadBuffer. + * Note that we are not looking at tuple data here, just headers. + */ + hoffnum = ItemPointerGetOffsetNumber(&(itup->t_tid)); + hitemid = PageGetItemId(hpage, hoffnum); + + /* + * Follow any redirections until we find something useful. + */ + while (ItemIdIsRedirected(hitemid)) { + hoffnum = ItemIdGetRedirect(hitemid); + hitemid = PageGetItemId(hpage, hoffnum); + CHECK_FOR_INTERRUPTS(); + } + + /* + * If the heap item has storage, then read the header and use that to + * set latestRemovedXid. + * + * Some LP_DEAD items may not be accessible, so we ignore them. + */ + if (ItemIdHasStorage(hitemid)) { + HeapTupleData tuple; + tuple.t_data = (HeapTupleHeader) PageGetItem(hpage, hitemid); + HeapTupleCopyBaseFromPage(&tuple, &hpage); + HeapTupleHeaderAdvanceLatestRemovedXid(&tuple, &latestRemovedXid); + } else if (ItemIdIsDead(hitemid)) { + /* + * Conjecture: if hitemid is dead then it had xids before the xids + * marked on LP_NORMAL items. So we just ignore this item and move + * onto the next, for the purposes of calculating + * latestRemovedxids. + */ + } else + Assert(!ItemIdIsUsed(hitemid)); + + UnlockReleaseBuffer(hbuffer); + } + + UnlockReleaseBuffer(ibuffer); + + /* + * If all heap tuples were LP_DEAD then we will be returning + * InvalidTransactionId here, which avoids conflicts. This matches + * existing logic which assumes that LP_DEAD tuples must already be older + * than the latestRemovedXid on the cleanup record that set them as + * LP_DEAD, hence must already have generated a conflict. + */ + return latestRemovedXid; +} + +/* + * replay delete operation in hash index to remove + * tuples marked as DEAD during index tuple insertion. + */ +static void hash_xlog_vacuum_one_page(XLogReaderState *record) +{ + RedoBufferInfo buffer; + RedoBufferInfo metabuf; + XLogRedoAction action; + + /* + * If we have any conflict processing to do, it must happen before we + * update the page. + * + * Hash index records that are marked as LP_DEAD and being removed during + * hash index tuple insertion can conflict with standby queries. You might + * think that vacuum records would conflict as well, but we've handled + * that already. XLOG_HEAP2_CLEANUP_INFO records provide the highest xid + * cleaned by the vacuum of the heap and so we can resolve any conflicts + * just once when that arrives. After that we know that no conflicts + * exist from individual hash index vacuum records on that index. + */ + if (InHotStandby) { + TransactionId latestRemovedXid = hash_xlog_vacuum_get_latestRemovedXid(record); + RelFileNode rnode; + + XLogRecGetBlockTag(record, 0, &rnode, NULL, NULL); + ResolveRecoveryConflictWithSnapshot(latestRemovedXid, rnode); + } + + action = XLogReadBufferForRedoExtended(record, 0, RBM_NORMAL, true, &buffer); + + if (action == BLK_NEEDS_REDO) { + Size len; + + len = XLogRecGetDataLen(record); + HashXlogVacuumOnePageOperatorPage(&buffer, XLogRecGetData(record), len); + + MarkBufferDirty(buffer.buf); + } + if (BufferIsValid(buffer.buf)) + UnlockReleaseBuffer(buffer.buf); + + if (XLogReadBufferForRedo(record, 1, &metabuf) == BLK_NEEDS_REDO) { + HashXlogVacuumMateOperatorPage(&metabuf, XLogRecGetData(record)); + MarkBufferDirty(metabuf.buf); + } + if (BufferIsValid(metabuf.buf)) + UnlockReleaseBuffer(metabuf.buf); +} + +void hash_redo(XLogReaderState *record) +{ + uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; + + switch (info) { + case XLOG_HASH_INIT_META_PAGE: + hash_xlog_init_meta_page(record); + break; + case XLOG_HASH_INIT_BITMAP_PAGE: + hash_xlog_init_bitmap_page(record); + break; + case XLOG_HASH_INSERT: + hash_xlog_insert(record); + break; + case XLOG_HASH_ADD_OVFL_PAGE: + hash_xlog_add_ovfl_page(record); + break; + case XLOG_HASH_SPLIT_ALLOCATE_PAGE: + hash_xlog_split_allocate_page(record); + break; + case XLOG_HASH_SPLIT_PAGE: + hash_xlog_split_page(record); + break; + case XLOG_HASH_SPLIT_COMPLETE: + hash_xlog_split_complete(record); + break; + case XLOG_HASH_MOVE_PAGE_CONTENTS: + hash_xlog_move_page_contents(record); + break; + case XLOG_HASH_SQUEEZE_PAGE: + hash_xlog_squeeze_page(record); + break; + case XLOG_HASH_DELETE: + hash_xlog_delete(record); + break; + case XLOG_HASH_SPLIT_CLEANUP: + hash_xlog_split_cleanup(record); + break; + case XLOG_HASH_UPDATE_META_PAGE: + hash_xlog_update_meta_page(record); + break; + case XLOG_HASH_VACUUM_ONE_PAGE: + hash_xlog_vacuum_one_page(record); + break; + default: + elog(PANIC, "hash_redo: unknown op code %u", info); + } +} + +bool IsHashVacuumPages(XLogReaderState *record) +{ + uint8 info = (XLogRecGetInfo(record) & (~XLR_INFO_MASK)); + + if (XLogRecGetRmid(record) == RM_HASH_ID) { + if (info == XLOG_HASH_DELETE) { + return true; + } + } + + return false; +} diff --git a/src/gausskernel/storage/access/redo/redo_xlogutils.cpp b/src/gausskernel/storage/access/redo/redo_xlogutils.cpp index 8cebaac0..d1fb0482 100644 --- a/src/gausskernel/storage/access/redo/redo_xlogutils.cpp +++ b/src/gausskernel/storage/access/redo/redo_xlogutils.cpp @@ -843,6 +843,9 @@ void XLogBlockDataCommonRedo(XLogBlockHead *blockhead, void *blockrecbody, RedoB case RM_BTREE_ID: BtreeRedoDataBlock(blockhead, blockdatarec, bufferinfo); break; + case RM_HASH_ID: + HashRedoDataBlock(blockhead, blockdatarec, bufferinfo); + break; case RM_XLOG_ID: xlog_redo_data_block(blockhead, blockdatarec, bufferinfo); break; diff --git a/src/gausskernel/storage/access/rmgrdesc/hashdesc.cpp b/src/gausskernel/storage/access/rmgrdesc/hashdesc.cpp index 04caba65..5533f09f 100644 --- a/src/gausskernel/storage/access/rmgrdesc/hashdesc.cpp +++ b/src/gausskernel/storage/access/rmgrdesc/hashdesc.cpp @@ -16,9 +16,155 @@ #include "postgres.h" #include "knl/knl_variable.h" -#include "access/hash.h" +#include "access/rmgr.h" +#include "access/hash_xlog.h" void hash_desc(StringInfo buf, XLogReaderState *record) { - /* nothing to do */ + char *rec = XLogRecGetData(record); + uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; + + switch (info) { + case XLOG_HASH_INIT_META_PAGE: + { + xl_hash_init_meta_page *xlrec = (xl_hash_init_meta_page *) rec; + + appendStringInfo(buf, "num_tuples %g, fillfactor %d", + xlrec->num_tuples, xlrec->ffactor); + break; + } + case XLOG_HASH_INIT_BITMAP_PAGE: + { + xl_hash_init_bitmap_page *xlrec = (xl_hash_init_bitmap_page *) rec; + + appendStringInfo(buf, "bmsize %d", xlrec->bmsize); + break; + } + case XLOG_HASH_INSERT: + { + xl_hash_insert *xlrec = (xl_hash_insert *) rec; + + appendStringInfo(buf, "off %u", xlrec->offnum); + break; + } + case XLOG_HASH_ADD_OVFL_PAGE: + { + xl_hash_add_ovfl_page *xlrec = (xl_hash_add_ovfl_page *) rec; + + appendStringInfo(buf, "bmsize %d, bmpage_found %c", + xlrec->bmsize, (xlrec->bmpage_found) ? 'T' : 'F'); + break; + } + case XLOG_HASH_SPLIT_ALLOCATE_PAGE: + { + xl_hash_split_allocate_page *xlrec = (xl_hash_split_allocate_page *) rec; + + appendStringInfo(buf, "new_bucket %u, meta_page_masks_updated %c, issplitpoint_changed %c", + xlrec->new_bucket, + (xlrec->flags & XLH_SPLIT_META_UPDATE_MASKS) ? 'T' : 'F', + (xlrec->flags & XLH_SPLIT_META_UPDATE_SPLITPOINT) ? 'T' : 'F'); + break; + } + case XLOG_HASH_SPLIT_COMPLETE: + { + xl_hash_split_complete *xlrec = (xl_hash_split_complete *) rec; + + appendStringInfo(buf, "old_bucket_flag %u, new_bucket_flag %u", + xlrec->old_bucket_flag, xlrec->new_bucket_flag); + break; + } + case XLOG_HASH_MOVE_PAGE_CONTENTS: + { + xl_hash_move_page_contents *xlrec = (xl_hash_move_page_contents *) rec; + + appendStringInfo(buf, "ntups %d, is_primary %c", + xlrec->ntups, + xlrec->is_prim_bucket_same_wrt ? 'T' : 'F'); + break; + } + case XLOG_HASH_SQUEEZE_PAGE: + { + xl_hash_squeeze_page *xlrec = (xl_hash_squeeze_page *) rec; + + appendStringInfo(buf, "prevblkno %u, nextblkno %u, ntups %d, is_primary %c", + xlrec->prevblkno, + xlrec->nextblkno, + xlrec->ntups, + xlrec->is_prim_bucket_same_wrt ? 'T' : 'F'); + break; + } + case XLOG_HASH_DELETE: + { + xl_hash_delete *xlrec = (xl_hash_delete *) rec; + + appendStringInfo(buf, "clear_dead_marking %c, is_primary %c", + xlrec->clear_dead_marking ? 'T' : 'F', + xlrec->is_primary_bucket_page ? 'T' : 'F'); + break; + } + case XLOG_HASH_UPDATE_META_PAGE: + { + xl_hash_update_meta_page *xlrec = (xl_hash_update_meta_page *) rec; + + appendStringInfo(buf, "ntuples %g", + xlrec->ntuples); + break; + } + case XLOG_HASH_VACUUM_ONE_PAGE: + { + xl_hash_vacuum_one_page *xlrec = (xl_hash_vacuum_one_page *) rec; + + appendStringInfo(buf, "ntuples %d", + xlrec->ntuples); + break; + } + } +} + +const char *hash_identify(uint8 info) +{ + const char *id = NULL; + + switch (info & ~XLR_INFO_MASK) { + case XLOG_HASH_INIT_META_PAGE: + id = "INIT_META_PAGE"; + break; + case XLOG_HASH_INIT_BITMAP_PAGE: + id = "INIT_BITMAP_PAGE"; + break; + case XLOG_HASH_INSERT: + id = "INSERT"; + break; + case XLOG_HASH_ADD_OVFL_PAGE: + id = "ADD_OVFL_PAGE"; + break; + case XLOG_HASH_SPLIT_ALLOCATE_PAGE: + id = "SPLIT_ALLOCATE_PAGE"; + break; + case XLOG_HASH_SPLIT_PAGE: + id = "SPLIT_PAGE"; + break; + case XLOG_HASH_SPLIT_COMPLETE: + id = "SPLIT_COMPLETE"; + break; + case XLOG_HASH_MOVE_PAGE_CONTENTS: + id = "MOVE_PAGE_CONTENTS"; + break; + case XLOG_HASH_SQUEEZE_PAGE: + id = "SQUEEZE_PAGE"; + break; + case XLOG_HASH_DELETE: + id = "DELETE"; + break; + case XLOG_HASH_SPLIT_CLEANUP: + id = "SPLIT_CLEANUP"; + break; + case XLOG_HASH_UPDATE_META_PAGE: + id = "UPDATE_META_PAGE"; + break; + case XLOG_HASH_VACUUM_ONE_PAGE: + id = "VACUUM_ONE_PAGE"; + } + + return id; } \ No newline at end of file diff --git a/src/gausskernel/storage/access/transam/parallel_recovery/dispatcher.cpp b/src/gausskernel/storage/access/transam/parallel_recovery/dispatcher.cpp index 284ba410..3a76002b 100644 --- a/src/gausskernel/storage/access/transam/parallel_recovery/dispatcher.cpp +++ b/src/gausskernel/storage/access/transam/parallel_recovery/dispatcher.cpp @@ -31,6 +31,7 @@ #include "access/xact.h" #include "access/xlog_internal.h" #include "access/nbtree.h" +#include "access/hash_xlog.h" #include "access/xlogreader.h" #include "access/gist_private.h" #include "access/multixact.h" @@ -165,7 +166,7 @@ static const RmgrDispatchData g_dispatchTable[RM_MAX_ID + 1] = { { DispatchHeap2Record, RmgrRecordInfoValid, RM_HEAP2_ID, XLOG_HEAP2_FREEZE, XLOG_HEAP2_LOGICAL_NEWPAGE }, { DispatchHeapRecord, RmgrRecordInfoValid, RM_HEAP_ID, XLOG_HEAP_INSERT, XLOG_HEAP_INPLACE }, { DispatchBtreeRecord, RmgrRecordInfoValid, RM_BTREE_ID, XLOG_BTREE_INSERT_LEAF, XLOG_BTREE_REUSE_PAGE }, - { DispatchHashRecord, NULL, RM_HASH_ID, 0, 0 }, + { DispatchHashRecord, RmgrRecordInfoValid, RM_HASH_ID, XLOG_HASH_INIT_META_PAGE, XLOG_HASH_VACUUM_ONE_PAGE }, { DispatchGinRecord, RmgrRecordInfoValid, RM_GIN_ID, XLOG_GIN_CREATE_INDEX, XLOG_GIN_VACUUM_DATA_LEAF_PAGE }, /* XLOG_GIST_PAGE_DELETE is not used and info isn't continus */ { DispatchGistRecord, RmgrGistRecordInfoValid, RM_GIST_ID, 0, 0 }, @@ -912,8 +913,20 @@ static bool DispatchCLogRecord(XLogReaderState *record, List *expectedTLIs, Time /* Run from the dispatcher thread. */ static bool DispatchHashRecord(XLogReaderState *record, List *expectedTLIs, TimestampTz recordXTime) { - DispatchTxnRecord(record, expectedTLIs, recordXTime, false); - return true; + bool isNeedFullSync = false; + + /* index not support mvcc, so we need to sync with trx thread when the record is vacuum */ + if (IsHashVacuumPages(record) && g_supportHotStandby) { + GetWorkerIds(record, ANY_WORKER, true); + /* sync with trxn thread */ + /* only need to process in pageworker thread, wait trxn sync */ + /* pageworker exe, trxn don't need exe */ + DispatchToSpecPageWorker(record, expectedTLIs, true); + } else { + DispatchRecordWithPages(record, expectedTLIs, true); + } + + return isNeedFullSync; } static bool DispatchBtreeRecord(XLogReaderState *record, List *expectedTLIs, TimestampTz recordXTime) diff --git a/src/include/access/hash_xlog.h b/src/include/access/hash_xlog.h new file mode 100644 index 00000000..36ca8915 --- /dev/null +++ b/src/include/access/hash_xlog.h @@ -0,0 +1,352 @@ +/*------------------------------------------------------------------------- + * + * hash_xlog.h + * header file for Postgres hash AM implementation + * + * Portions Copyright (c) 2021 Huawei Technologies Co.,Ltd. + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/access/hash_xlog.h + * + *------------------------------------------------------------------------- + */ + +#ifndef HASH_XLOG_H +#define HASH_XLOG_H + +#include "access/xlogreader.h" +#include "lib/stringinfo.h" +#include "storage/off.h" + +/* Number of buffers required for XLOG_HASH_SQUEEZE_PAGE operation */ +#define HASH_XLOG_FREE_OVFL_BUFS 6 + +/* + * XLOG records for hash operations + */ +#define XLOG_HASH_INIT_META_PAGE 0x00 /* initialize the meta page */ +#define XLOG_HASH_INIT_BITMAP_PAGE 0x10 /* initialize the bitmap page */ +#define XLOG_HASH_INSERT 0x20 /* add index tuple without split */ +#define XLOG_HASH_ADD_OVFL_PAGE 0x30 /* add overflow page */ +#define XLOG_HASH_SPLIT_ALLOCATE_PAGE 0x40 /* allocate new page for split */ +#define XLOG_HASH_SPLIT_PAGE 0x50 /* split page */ +#define XLOG_HASH_SPLIT_COMPLETE 0x60 /* completion of split operation */ +#define XLOG_HASH_MOVE_PAGE_CONTENTS 0x70 /* remove tuples from one page + * and add to another page */ +#define XLOG_HASH_SQUEEZE_PAGE 0x80 /* add tuples to one of the previous + * pages in chain and free the ovfl + * page */ +#define XLOG_HASH_DELETE 0x90 /* delete index tuples from a page */ +#define XLOG_HASH_SPLIT_CLEANUP 0xA0 /* clear split-cleanup flag in primary + * bucket page after deleting tuples + * that are moved due to split */ +#define XLOG_HASH_UPDATE_META_PAGE 0xB0 /* update meta page after vacuum */ +#define XLOG_HASH_VACUUM_ONE_PAGE 0xC0 /* remove dead tuples from index page */ + +enum { + XLOG_HASH_INIT_META_PAGE_NUM = 0, +}; + +enum { + XLOG_HASH_INIT_BITMAP_PAGE_BITMAP_NUM = 0, + XLOG_HASH_INIT_BITMAP_PAGE_META_NUM, +}; + +enum { + XLOG_HASH_INSERT_PAGE_NUM = 0, + XLOG_HASH_INSERT_META_NUM, +}; + +enum { + XLOG_HASH_ADD_OVFL_PAGE_OVFL_NUM = 0, + XLOG_HASH_ADD_OVFL_PAGE_LEFT_NUM, + XLOG_HASH_ADD_OVFL_PAGE_MAP_NUM, + XLOG_HASH_ADD_OVFL_PAGE_NEWMAP_NUM, + XLOG_HASH_ADD_OVFL_PAGE_META_NUM, +}; + +enum { + XLOG_HASH_SPLIT_ALLOCATE_PAGE_OBUK_NUM = 0, + XLOG_HASH_SPLIT_ALLOCATE_PAGE_NBUK_NUM, + XLOG_HASH_SPLIT_ALLOCATE_PAGE_META_NUM, +}; + +enum { + XLOG_HASH_SPLIT_PAGE_NUM = 0, +}; + +enum { + XLOG_HASH_SPLIT_COMPLETE_OBUK_NUM = 0, + XLOG_HASH_SPLIT_COMPLETE_NBUK_NUM, +}; + +typedef enum { + HASH_MOVE_BUK_BLOCK_NUM = 0, + HASH_MOVE_ADD_BLOCK_NUM, + HASH_MOVE_DELETE_OVFL_BLOCK_NUM, +}XLogHashMovePageEnum; + +typedef enum { + HASH_SQUEEZE_BUK_BLOCK_NUM = 0, + HASH_SQUEEZE_ADD_BLOCK_NUM, + HASH_SQUEEZE_INIT_OVFLBUF_BLOCK_NUM, + HASH_SQUEEZE_UPDATE_PREV_BLOCK_NUM, + HASH_SQUEEZE_UPDATE_NEXT_BLOCK_NUM, + HASH_SQUEEZE_UPDATE_BITMAP_BLOCK_NUM, + HASH_SQUEEZE_UPDATE_MATE_BLOCK_NUM, +}XLogHashSqueezePageEnum; + +typedef enum { + HASH_DELETE_BUK_BLOCK_NUM = 0, + HASH_DELETE_OVFL_BLOCK_NUM, +}XLogHashDeleteEnum; + +typedef enum { + HASH_SPLIT_CLEANUP_BLOCK_NUM, +}XLogHashSplitCleanupEnum; + +typedef enum { + HASH_UPDATE_MATE_BLOCK_NUM, +} XLogHashUpdateMateEnum; + +typedef enum { + HASH_VACUUM_PAGE_BLOCK_NUM = 0, + HASH_VACUUM_META_BLOCK_NUM, +} XLogHashVacuumPageEnum; + +/* + * xl_hash_split_allocate_page flag values, 8 bits are available. + */ +#define XLH_SPLIT_META_UPDATE_MASKS (1<<0) +#define XLH_SPLIT_META_UPDATE_SPLITPOINT (1<<1) + +/* + * This is what we need to know about a HASH index create. + * + * Backup block 0: metapage + */ +typedef struct xl_hash_createidx +{ + double num_tuples; + RegProcedure procid; + uint16 ffactor; +} xl_hash_createidx; + +#define SizeOfHashCreateIdx (offsetof(xl_hash_createidx, ffactor) + sizeof(uint16)) + +/* + * This is what we need to know about simple (without split) insert. + * + * This data record is used for XLOG_HASH_INSERT + * + * Backup Blk 0: original page (data contains the inserted tuple) + * Backup Blk 1: metapage (HashMetaPageData) + */ +typedef struct xl_hash_insert +{ + OffsetNumber offnum; +} xl_hash_insert; + +#define SizeOfHashInsert (offsetof(xl_hash_insert, offnum) + sizeof(OffsetNumber)) + +/* + * This is what we need to know about addition of overflow page. + * + * This data record is used for XLOG_HASH_ADD_OVFL_PAGE + * + * Backup Blk 0: newly allocated overflow page + * Backup Blk 1: page before new overflow page in the bucket chain + * Backup Blk 2: bitmap page + * Backup Blk 3: new bitmap page + * Backup Blk 4: metapage + */ +typedef struct xl_hash_add_ovfl_page +{ + uint16 bmsize; + bool bmpage_found; +} xl_hash_add_ovfl_page; + +#define SizeOfHashAddOvflPage \ + (offsetof(xl_hash_add_ovfl_page, bmpage_found) + sizeof(bool)) + +/* + * This is what we need to know about allocating a page for split. + * + * This data record is used for XLOG_HASH_SPLIT_ALLOCATE_PAGE + * + * Backup Blk 0: page for old bucket + * Backup Blk 1: page for new bucket + * Backup Blk 2: metapage + */ +typedef struct xl_hash_split_allocate_page +{ + uint32 new_bucket; + uint16 old_bucket_flag; + uint16 new_bucket_flag; + uint8 flags; +} xl_hash_split_allocate_page; + +#define SizeOfHashSplitAllocPage \ + (offsetof(xl_hash_split_allocate_page, flags) + sizeof(uint8)) + +/* + * This is what we need to know about completing the split operation. + * + * This data record is used for XLOG_HASH_SPLIT_COMPLETE + * + * Backup Blk 0: page for old bucket + * Backup Blk 1: page for new bucket + */ +typedef struct xl_hash_split_complete +{ + uint16 old_bucket_flag; + uint16 new_bucket_flag; +} xl_hash_split_complete; + +#define SizeOfHashSplitComplete \ + (offsetof(xl_hash_split_complete, new_bucket_flag) + sizeof(uint16)) + +/* + * This is what we need to know about move page contents required during + * squeeze operation. + * + * This data record is used for XLOG_HASH_MOVE_PAGE_CONTENTS + * + * Backup Blk 0: bucket page + * Backup Blk 1: page containing moved tuples + * Backup Blk 2: page from which tuples will be removed + */ +typedef struct xl_hash_move_page_contents +{ + uint16 ntups; + bool is_prim_bucket_same_wrt; /* true if the page to which + * tuples are moved is same as + * primary bucket page */ +} xl_hash_move_page_contents; + +#define SizeOfHashMovePageContents \ + (offsetof(xl_hash_move_page_contents, is_prim_bucket_same_wrt) + sizeof(bool)) + +/* + * This is what we need to know about the squeeze page operation. + * + * This data record is used for XLOG_HASH_SQUEEZE_PAGE + * + * Backup Blk 0: page containing tuples moved from freed overflow page + * Backup Blk 1: freed overflow page + * Backup Blk 2: page previous to the freed overflow page + * Backup Blk 3: page next to the freed overflow page + * Backup Blk 4: bitmap page containing info of freed overflow page + * Backup Blk 5: meta page + */ +typedef struct xl_hash_squeeze_page +{ + BlockNumber prevblkno; + BlockNumber nextblkno; + uint16 ntups; + bool is_prim_bucket_same_wrt; /* true if the page to which + * tuples are moved is same as + * primary bucket page */ + bool is_prev_bucket_same_wrt; /* true if the page to which + * tuples are moved is the page + * previous to the freed overflow + * page */ +} xl_hash_squeeze_page; + +#define SizeOfHashSqueezePage \ + (offsetof(xl_hash_squeeze_page, is_prev_bucket_same_wrt) + sizeof(bool)) + +/* + * This is what we need to know about the deletion of index tuples from a page. + * + * This data record is used for XLOG_HASH_DELETE + * + * Backup Blk 0: primary bucket page + * Backup Blk 1: page from which tuples are deleted + */ +typedef struct xl_hash_delete +{ + bool clear_dead_marking; /* true if this operation clears + * LH_PAGE_HAS_DEAD_TUPLES flag */ + bool is_primary_bucket_page; /* true if the operation is for + * primary bucket page */ +} xl_hash_delete; + +#define SizeOfHashDelete \ + (offsetof(xl_hash_delete, is_primary_bucket_page) + sizeof(bool)) + +/* + * This is what we need for metapage update operation. + * + * This data record is used for XLOG_HASH_UPDATE_META_PAGE + * + * Backup Blk 0: meta page + */ +typedef struct xl_hash_update_meta_page +{ + double ntuples; +} xl_hash_update_meta_page; + +#define SizeOfHashUpdateMetaPage \ + (offsetof(xl_hash_update_meta_page, ntuples) + sizeof(double)) + +/* + * This is what we need to initialize metapage. + * + * This data record is used for XLOG_HASH_INIT_META_PAGE + * + * Backup Blk 0: meta page + */ +typedef struct xl_hash_init_meta_page +{ + double num_tuples; + RegProcedure procid; + uint16 ffactor; +} xl_hash_init_meta_page; + +#define SizeOfHashInitMetaPage \ + (offsetof(xl_hash_init_meta_page, ffactor) + sizeof(uint16)) + +/* + * This is what we need to initialize bitmap page. + * + * This data record is used for XLOG_HASH_INIT_BITMAP_PAGE + * + * Backup Blk 0: bitmap page + * Backup Blk 1: meta page + */ +typedef struct xl_hash_init_bitmap_page +{ + uint16 bmsize; +} xl_hash_init_bitmap_page; + +#define SizeOfHashInitBitmapPage \ + (offsetof(xl_hash_init_bitmap_page, bmsize) + sizeof(uint16)) + +/* + * This is what we need for index tuple deletion and to + * update the meta page. + * + * This data record is used for XLOG_HASH_VACUUM_ONE_PAGE + * + * Backup Blk 0: bucket page + * Backup Blk 1: meta page + */ +typedef struct xl_hash_vacuum_one_page +{ + RelFileNode hnode; + int ntuples; + + /* TARGET OFFSET NUMBERS FOLLOW AT THE END */ +} xl_hash_vacuum_one_page; + +#define SizeOfHashVacuumOnePage \ + (offsetof(xl_hash_vacuum_one_page, ntuples) + sizeof(int)) + +extern void hash_redo(XLogReaderState *record); +extern void hash_desc(StringInfo buf, XLogReaderState *record); +extern const char *hash_identify(uint8 info); +extern bool IsHashVacuumPages(XLogReaderState *record); + +#endif /* HASH_XLOG_H */ diff --git a/src/include/access/xlogproc.h b/src/include/access/xlogproc.h index 438809c0..fb882a4c 100644 --- a/src/include/access/xlogproc.h +++ b/src/include/access/xlogproc.h @@ -754,6 +754,47 @@ void BtreeXlogUnlinkPageOperatorChildpage(RedoBufferInfo* cbuf, void* recorddata void BtreeXlogClearIncompleteSplit(RedoBufferInfo* buffer); +void HashRedoInitMetaPageOperatorPage(RedoBufferInfo *metabuf, void *recorddata); + +void HashRedoInitBitmapPageOperatorBitmapPage(RedoBufferInfo *bitmapbuf, void *recorddata); +void HashRedoInitBitmapPageOperatorMetaPage(RedoBufferInfo *metabuf); + +void HashRedoInsertOperatorPage(RedoBufferInfo *buffer, void *recorddata, void *data, Size datalen); +void HashRedoInsertOperatorMetaPage(RedoBufferInfo *metabuf); + +void HashRedoAddOvflPageOperatorOvflPage(RedoBufferInfo *ovflbuf, BlockNumber leftblk, void *data, Size datalen); +void HashRedoAddOvflPageOperatorLeftPage(RedoBufferInfo *ovflbuf, BlockNumber rightblk); +void HashRedoAddOvflPageOperatorMapPage(RedoBufferInfo *mapbuf, void *data); +void HashRedoAddOvflPageOperatorNewmapPage(RedoBufferInfo *newmapbuf, void *recorddata); +void HashRedoAddOvflPageOperatorMetaPage(RedoBufferInfo *metabuf, void *recorddata, void *data, Size datalen); + +void HashRedoSplitAllocatePageOperatorObukPage(RedoBufferInfo *oldbukbuf, void *recorddata); +void HashRedoSplitAllocatePageOperatorNbukPage(RedoBufferInfo *newbukbuf, void *recorddata); +void HashRedoSplitAllocatePageOperatorMetaPage(RedoBufferInfo *metabuf, void *recorddata, void *data); + +void HashRedoSplitCompleteOperatorObukPage(RedoBufferInfo *oldbukbuf, void *recorddata); +void HashRedoSplitCompleteOperatorNbukPage(RedoBufferInfo *newbukbuf, void *recorddata); + +void HashXlogMoveAddPageOperatorPage(RedoBufferInfo *redobuffer, void *recorddata, void *blkdata, Size len); +void HashXlogMoveDeleteOvflPageOperatorPage(RedoBufferInfo *redobuffer, void *blkdata, Size len); + +void HashXlogSqueezeAddPageOperatorPage(RedoBufferInfo *redobuffer, void *recorddata, void *blkdata, Size len); +void HashXlogSqueezeInitOvflbufOperatorPage(RedoBufferInfo *redobuffer, void *recorddata); +void HashXlogSqueezeUpdatePrevPageOperatorPage(RedoBufferInfo *redobuffer, void *recorddata); +void HashXlogSqueezeUpdateNextPageOperatorPage(RedoBufferInfo *redobuffer, void *recorddata); +void HashXlogSqueezeUpdateBitmapOperatorPage(RedoBufferInfo *redobuffer, void *blkdata); +void HashXlogSqueezeUpdateMateOperatorPage(RedoBufferInfo *redobuffer, void *blkdata); + +void HashXlogDeleteBlockOperatorPage(RedoBufferInfo *redobuffer, void *recorddata, void *blkdata, Size len); + +void HashXlogSplitCleanupOperatorPage(RedoBufferInfo *redobuffer); + +void HashXlogUpdateMetaOperatorPage(RedoBufferInfo *redobuffer, void *recorddata); + +void HashXlogVacuumOnePageOperatorPage(RedoBufferInfo *redobuffer, void *recorddata, Size len); + +void HashXlogVacuumMateOperatorPage(RedoBufferInfo *redobuffer, void *recorddata); + void XLogRecSetBlockCommonState(XLogReaderState* record, XLogBlockParseEnum blockvalid, ForkNumber forknum, BlockNumber blockknum, RelFileNode* relnode, XLogRecParseState* recordblockstate); @@ -787,6 +828,7 @@ extern void XLogRecSetBlockDdlState(XLogBlockDdlParse* blockddlstate, uint32 blo char *mainData, Oid ownerid = InvalidOid); XLogRedoAction XLogCheckBlockDataRedoAction(XLogBlockDataParse* datadecode, RedoBufferInfo* bufferinfo); void BtreeRedoDataBlock(XLogBlockHead* blockhead, XLogBlockDataParse* blockdatarec, RedoBufferInfo* bufferinfo); +extern void HashRedoDataBlock(XLogBlockHead* blockhead, XLogBlockDataParse* blockdatarec, RedoBufferInfo* bufferinfo); XLogRecParseState* XactXlogCsnlogParseToBlock(XLogReaderState* record, uint32* blocknum, TransactionId xid, int nsubxids, TransactionId* subxids, CommitSeqNo csn, XLogRecParseState* recordstatehead); extern void XLogRecSetVmBlockState(XLogReaderState* record, uint32 blockid, XLogRecParseState* recordblockstate); @@ -914,5 +956,4 @@ extern void XLogBlockDdlDoSmgrAction(XLogBlockHead* blockhead, void* blockrecbod extern void GinRedoDataBlock(XLogBlockHead* blockhead, XLogBlockDataParse* blockdatarec, RedoBufferInfo* bufferinfo); extern void GistRedoDataBlock(XLogBlockHead *blockhead, XLogBlockDataParse *blockdatarec, RedoBufferInfo *bufferinfo); extern bool IsCheckPoint(const XLogRecParseState *parseState); - #endif diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index a7576b95..500ac0ef 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -55,6 +55,7 @@ extern const uint32 RANGE_LIST_DISTRIBUTION_VERSION_NUM; extern const uint32 FIX_SQL_ADD_RELATION_REF_COUNT; extern const uint32 GENERATED_COL_VERSION_NUM; extern const uint32 ANALYZER_HOOK_VERSION_NUM; +extern const uint32 SUPPORT_HASH_XLOG_VERSION_NUM; #define INPLACE_UPGRADE_PRECOMMIT_VERSION 1