openGauss-server/src/gausskernel/storage/access/heap/heapam.cpp

7619 lines
272 KiB
C++

/* -------------------------------------------------------------------------
*
* heapam.cpp
* heap access method code
*
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/gausskernel/storage/access/heap/heapam.cpp
*
*
* INTERFACE ROUTINES
* relation_open - open any relation by relation OID
* relation_openrv - open any relation specified by a RangeVar
* relation_close - close any relation
* heap_open - open a heap relation by relation OID
* heap_openrv - open a heap relation specified by a RangeVar
* heap_close - (now just a macro for relation_close)
* heap_beginscan - begin relation scan
* heap_rescan - restart a relation scan
* heap_endscan - end relation scan
* heap_getnext - retrieve next tuple in scan
* heap_fetch - retrieve tuple with given tid
* heap_insert - insert tuple into a relation
* heap_multi_insert - insert multiple tuples into a relation
* heap_delete - delete a tuple from a relation
* heap_update - replace a tuple in a relation with another tuple
* heap_markpos - mark scan position
* heap_restrpos - restore position to marked location
* heap_sync - sync heap, for when no WAL has been written
*
* NOTES
* This file contains the heap_ routines which implement
* the POSTGRES heap access method used for all POSTGRES
* relations.
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include "access/heapam.h"
#include "access/hio.h"
#include "access/multixact.h"
#include "access/relscan.h"
#include "access/sysattr.h"
#include "access/tableam.h"
#include "access/transam.h"
#include "access/tuptoaster.h"
#include "access/valid.h"
#include "access/visibilitymap.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "access/xloginsert.h"
#include "access/xlogutils.h"
#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "commands/dbcommands.h"
#include "commands/verify.h"
#include "executor/nodeModifyTable.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "replication/dataqueue.h"
#include "replication/datasender.h"
#include "replication/walsender.h"
#include "storage/bufmgr.h"
#include "storage/freespace.h"
#include "storage/lmgr.h"
#include "storage/predicate.h"
#include "storage/procarray.h"
#include "storage/smgr.h"
#include "storage/standby.h"
#include "utils/datum.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/relcache.h"
#include "utils/partcache.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/tqual.h"
#include "utils/guc.h"
#include "access/cstore_insert.h"
#include "access/cstore_delete.h"
#include "vecexecutor/vectorbatch.h"
#include "access/xlogproc.h"
#include "access/multi_redo_api.h"
#include "catalog/pg_hashbucket_fn.h"
#include "gstrace/gstrace_infra.h"
#include "gstrace/access_gstrace.h"
#ifdef PGXC
#include "pgxc/pgxc.h"
#include "pgxc/redistrib.h"
#include "replication/bcm.h"
#endif
#define DECOMPRESS_HEAP_TUPLE(_isCompressed, _heapTuple, _destTupleData, _rd_att, _heapPage) \
do { \
if ((_isCompressed)) { \
HeapTupleData _srcTuple = *(_heapTuple); \
Assert((_heapPage)); \
/* Then set the memory for decompressed tuple */ \
(_heapTuple)->t_data = (_destTupleData); \
heapCopyCompressedTuple(&_srcTuple, (_rd_att), (char*)(_heapPage), (_heapTuple)); \
} \
} while (0)
#ifdef USE_ASSERT_CHECKING
#define LatestTransactionStatusError(xid, snapshot, action) PrintCurrentSnapshotInfo(PANIC, xid, snapshot, action)
#else
#define LatestTransactionStatusError(xid, snapshot, action) PrintCurrentSnapshotInfo(ERROR, xid, snapshot, action)
#endif
const TableAm g_HeapTblAm = {.table_endscan = (table_endscan_t)heap_endscan,
.table_rescan = (table_rescan_t)heap_rescan,
.table_getnext = (table_getnext_t)heap_getnext,
.table_markpos = (table_markpos_t)heap_markpos,
.table_restrpos = (table_restrpos_t)heap_restrpos,
.table_getpage = (table_getpage_t)heapgetpage,
.table_init_parallel_seqscan = (table_init_parallel_seqscan_t)heap_init_parallel_seqscan};
static HeapScanDesc heap_beginscan_internal(Relation relation, Snapshot snapshot, int nkeys, ScanKey key,
bool allow_strat, bool allow_sync, bool is_bitmapscan, bool is_range_scan_in_redis = false, bool is_samplescan = false);
static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup, CommandId cid, int options);
static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf, const ItemPointer from, Buffer newbuf, HeapTuple newtup,
HeapTuple old_key_tup, bool all_visible_cleared, bool new_all_visible_cleared);
static void HeapSatisfiesHOTUpdate(Relation relation, Bitmapset* hot_attrs, Bitmapset* id_attrs, bool* satisfies_hot,
bool* satisfies_id, HeapTuple oldtup, HeapTuple newtup, char* page);
static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_modified, bool* copy);
static void SkipToNewPage(
HeapScanDesc scan, ScanDirection dir, BlockNumber page, bool& finished, bool& is_valid_relation_page);
static bool VerifyHeapGetTup(HeapScanDesc scan, ScanDirection dir);
static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup);
extern void Start_Prefetch(HeapScanDesc scan, SeqScanAccessor* pAccessor, ScanDirection dir);
extern void vacuum_set_xid_limits(Relation rel, int64 freeze_min_age, int64 freeze_table_age, TransactionId* oldestXmin,
TransactionId* freezeLimit, TransactionId* freezeTableLimit);
/* ----------------
* initscan - scan code common to heap_beginscan and heap_rescan
* ----------------
*/
static void initscan(HeapScanDesc scan, ScanKey key, bool is_rescan)
{
bool allow_strat = false;
bool allow_sync = false;
BlockNumber nblocks;
bool is_range_scan_in_redis = scan->rs_isRangeScanInRedis;
/*
* Determine the number of blocks we have to scan.
*
* It is sufficient to do this once at scan start, since any tuples added
* while the scan is in progress will be invisible to my snapshot anyway.
* (That is not true when using a non-MVCC snapshot. However, we couldn't
* guarantee to return tuples added after scan start anyway, since they
* might go into pages we already scanned. To guarantee consistent
* results for a non-MVCC snapshot, the caller must hold some higher-level
* lock that ensures the interesting tuple(s) won't change.)
*/
nblocks = RelationGetNumberOfBlocks(scan->rs_rd);
if (nblocks > 0 && is_range_scan_in_redis) {
ItemPointerData start_ctid;
ItemPointerData end_ctid;
RelationGetCtids(scan->rs_rd, &start_ctid, &end_ctid);
scan->rs_nblocks = RedisCtidGetBlockNumber(&end_ctid) - RedisCtidGetBlockNumber(&start_ctid) + 1;
scan->rs_startblock = RedisCtidGetBlockNumber(&start_ctid);
} else
scan->rs_nblocks = nblocks;
/*
* If the table is large relative to NBuffers, use a bulk-read access
* strategy and enable synchronized scanning (see syncscan.c). Although
* the thresholds for these features could be different, we make them the
* same so that there are only two behaviors to tune rather than four.
* (However, some callers need to be able to disable one or both of these
* behaviors, independently of the size of the table; also there is a GUC
* variable that can disable synchronized scanning.)
*
* During a rescan, don't make a new strategy object if we don't have to.
*/
if (scan->rs_nblocks > (uint32)(g_instance.attr.attr_storage.NBuffers / 4)) {
allow_strat = scan->rs_allow_strat;
allow_sync = scan->rs_allow_sync;
} else
allow_strat = allow_sync = false;
if (allow_strat) {
if (scan->rs_strategy == NULL)
scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
} else {
if (scan->rs_strategy != NULL)
FreeAccessStrategy(scan->rs_strategy);
scan->rs_strategy = NULL;
}
if (is_rescan) {
/*
* If rescan, keep the previous startblock setting so that rewinding a
* cursor doesn't generate surprising results. Reset the syncscan
* setting, though.
*/
scan->rs_syncscan = (allow_sync && u_sess->attr.attr_storage.synchronize_seqscans);
} else if (allow_sync && u_sess->attr.attr_storage.synchronize_seqscans) {
scan->rs_syncscan = true;
scan->rs_startblock = ss_get_location(scan->rs_rd, scan->rs_nblocks);
} else {
scan->rs_syncscan = false;
if (scan->rs_nblocks == 0 || !is_range_scan_in_redis) {
scan->rs_startblock = 0;
}
}
scan->rs_inited = false;
scan->rs_ctup.t_data = NULL;
ItemPointerSetInvalid(&scan->rs_ctup.t_self);
scan->rs_cbuf = InvalidBuffer;
scan->rs_cblock = InvalidBlockNumber;
scan->rs_ss_accessor = NULL;
scan->dop = 1;
/* we don't have a marked position... */
ItemPointerSetInvalid(&(scan->rs_mctid));
/* page-at-a-time fields are always invalid when not rs_inited
*
* copy the scan key, if appropriate
*/
if (key != NULL && scan->rs_nkeys > 0) {
errno_t rc = EOK;
rc = memcpy_s(scan->rs_key, scan->rs_nkeys * sizeof(ScanKeyData), key, scan->rs_nkeys * sizeof(ScanKeyData));
securec_check(rc, "\0", "\0");
}
/*
* Currently, we don't have a stats counter for bitmap heap scans (but the
* underlying bitmap index scans will be counted) or sample scans (we only
* update stats for tuple fetches there).
*/
if (!scan->rs_bitmapscan && !scan->rs_samplescan) {
pgstat_count_heap_scan(scan->rs_rd);
}
}
/*
* heapgetpage - subroutine for heapgettup()
*
* This routine reads and pins the specified page of the relation.
* In page-at-a-time mode it performs additional work, namely determining
* which tuples on the page are visible.
*/
void heapgetpage(HeapScanDesc scan, BlockNumber page)
{
Buffer buffer;
Snapshot snapshot;
Page dp;
int lines;
int ntup;
OffsetNumber line_off;
ItemId lpp;
bool all_visible = false;
if (!scan->rs_isRangeScanInRedis) {
Assert(page < scan->rs_nblocks);
} else {
Assert(page < scan->rs_nblocks + scan->rs_startblock);
}
gstrace_entry(GS_TRC_ID_heapgetpage);
/* release previous scan buffer, if any */
if (BufferIsValid(scan->rs_cbuf)) {
ReleaseBuffer(scan->rs_cbuf);
scan->rs_cbuf = InvalidBuffer;
}
/*
* Be sure to check for interrupts at least once per page. Checks at
* higher code levels won't be able to stop a seqscan that encounters many
* pages' worth of consecutive dead tuples.
*/
CHECK_FOR_INTERRUPTS();
/* read page using selected strategy */
scan->rs_cbuf = ReadBufferExtended(scan->rs_rd, MAIN_FORKNUM, page, RBM_NORMAL, scan->rs_strategy);
scan->rs_cblock = page;
/* We've pinned the buffer, nobody can prune this buffer, check whether snapshot is valid. */
CheckSnapshotIsValidException(scan->rs_snapshot, "heapgetpage");
if (!scan->rs_pageatatime) {
gstrace_exit(GS_TRC_ID_heapgetpage);
return;
}
buffer = scan->rs_cbuf;
snapshot = scan->rs_snapshot;
/*
* Prune and repair fragmentation for the whole page, if possible.
* No more page prune if it is a range scan during redistribution time
* since we use append mode and never look back holes in previous pages
* anyway.
*/
if (!scan->rs_isRangeScanInRedis) {
heap_page_prune_opt(scan->rs_rd, buffer);
}
/*
* We must hold share lock on the buffer content while examining tuple
* visibility. Afterwards, however, the tuples we have found to be
* visible are guaranteed good as long as we hold the buffer pin.
*/
LockBuffer(buffer, BUFFER_LOCK_SHARE);
dp = (Page)BufferGetPage(buffer);
lines = PageGetMaxOffsetNumber(dp);
ntup = 0;
/*
* If the all-visible flag indicates that all tuples on the page are
* visible to everyone, we can skip the per-tuple visibility tests. But
* not in hot standby mode. A tuple that's already visible to all
* transactions in the master might still be invisible to a read-only
* transaction in the standby.
*/
all_visible = PageIsAllVisible(dp) && !snapshot->takenDuringRecovery;
for (line_off = FirstOffsetNumber, lpp = PageGetItemId(dp, line_off); line_off <= lines; line_off++, lpp++) {
if (ItemIdIsNormal(lpp)) {
HeapTupleData loctup;
bool valid = false;
loctup.t_tableOid = RelationGetRelid(scan->rs_rd);
loctup.t_bucketId = RelationGetBktid(scan->rs_rd);
loctup.t_data = (HeapTupleHeader)PageGetItem((Page)dp, lpp);
loctup.t_len = ItemIdGetLength(lpp);
HeapTupleCopyBaseFromPage(&loctup, dp);
ItemPointerSet(&(loctup.t_self), page, line_off);
if (all_visible)
valid = true;
else
valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer);
CheckForSerializableConflictOut(valid, scan->rs_rd, &loctup, buffer, snapshot);
if (valid) {
scan->rs_vistuples[ntup++] = line_off;
}
ereport(DEBUG1,
(errmsg(
"heapgetpage xid %lu ctid(%u,%d) valid %d", GetCurrentTransactionIdIfAny(), page, line_off, valid)));
}
}
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
Assert(ntup <= MaxHeapTuplesPerPage);
scan->rs_ntuples = ntup;
gstrace_exit(GS_TRC_ID_heapgetpage);
}
/*
* @Description: if many tuples of the relation are deleted, when load a one page which has normal tuples, so need
* prefetch
* @Param[IN] dir: scan direction
* @Param[IN] scan: heap scan desc
* @See also: heapgettup(); heapgettup_pagemode()
*/
void heap_prefetch(HeapScanDesc scan, ScanDirection dir)
{
ADIO_RUN()
{
/* if tuples in page are all deleted, need prefetch also for performance */
if (scan->rs_ss_accessor != NULL) {
Start_Prefetch(scan, scan->rs_ss_accessor, dir);
}
}
ADIO_END();
}
/*
* @Description: Calculate the next page number.
*
* @param[IN] scan: heap scan describtion.
* @param[IN] dir: scan direction.
* @param[OUT] page: next page number.
* @return bool: true -- scan finished.
*/
FORCE_INLINE
bool next_page(HeapScanDesc scan, ScanDirection dir, BlockNumber& page)
{
bool finished = false;
if (scan->dop > 1) {
if (BackwardScanDirection == dir) {
finished = (page == 0);
if (finished)
return finished;
page--;
if ((scan->rs_startblock - page) % PARALLEL_SCAN_GAP == 0) {
page -= (scan->dop - 1) * PARALLEL_SCAN_GAP;
}
} else {
page++;
if ((page - scan->rs_startblock) % PARALLEL_SCAN_GAP == 0) {
page += (scan->dop - 1) * PARALLEL_SCAN_GAP;
}
if (scan->rs_isRangeScanInRedis) {
/* Parallel workers start from different point. */
finished =
(page >= scan->rs_startblock + scan->rs_nblocks - PARALLEL_SCAN_GAP * u_sess->stream_cxt.smp_id);
} else {
finished = (page >= scan->rs_nblocks);
}
}
} else {
if (BackwardScanDirection == dir) {
finished = (scan->rs_startblock == page);
if (page == 0) {
page = scan->rs_nblocks;
}
page--;
} else {
page++;
if (scan->rs_isRangeScanInRedis) {
if (page >= scan->rs_startblock + scan->rs_nblocks) {
page = 0;
}
finished = (page == 0);
} else {
if (page >= scan->rs_nblocks) {
page = 0;
}
finished = (page == scan->rs_startblock);
}
/*
* Report our new scan position for synchronization purposes. We
* don't do that when moving backwards, however. That would just
* mess up any other forward-moving scanners.
*
* Note: we do this before checking for end of scan so that the
* final state of the position hint is back at the start of the
* rel. That's not strictly necessary, but otherwise when you run
* the same query multiple times the starting position would shift
* a little bit backwards on every invocation, which is confusing.
* We don't guarantee any specific ordering in general, though.
*/
if (scan->rs_syncscan) {
ss_report_location(scan->rs_rd, page);
}
}
}
return finished;
}
/*
* SkipToNewPage
*
* @Description: to get next page. If the data page is corrupted, we wil find the next page and
* the data page will be checked. when we find the normal data page or scan is end
* the function will return.
* @in scan - the relation's heap scan description.
* @in dir - the scan direction, The default scan is ForwardScanDirection.
* @in&out page - the relation's current page
* @in&out finished - judge the scan is in the end.
* @in&out is_valid_relation_page - relation's page is valid return true, else return false.
* @return: bool-- true is scan finished. Otherwise, return false.
*/
static void SkipToNewPage(
HeapScanDesc scan, ScanDirection dir, BlockNumber page, bool& finished, bool& is_valid_relation_page)
{
MemoryContext verify_context = CurrentMemoryContext;
HeapTuple tuple = &(scan->rs_ctup);
bool try_next_page = false;
while (!finished) {
/* advance to next/prior page and detect end of scan */
finished = next_page(scan, dir, page);
/* try_next_page is used to judge whether we need to continue. */
try_next_page = false;
/* return NULL if we've exhausted all the pages. */
if (finished) {
if (BufferIsValid(scan->rs_cbuf)) {
ReleaseBuffer(scan->rs_cbuf);
}
scan->rs_cbuf = InvalidBuffer;
scan->rs_cblock = InvalidBlockNumber;
tuple->t_data = NULL;
scan->rs_inited = false;
return;
}
heap_prefetch(scan, dir);
PG_TRY();
{
heapgetpage(scan, page);
}
PG_CATCH();
{
(void)MemoryContextSwitchTo(verify_context);
is_valid_relation_page = false;
/*
* VerifyAbortBufferIO is used for special error handling for verify after catching exceptions,
* so that it can handle the next operation.
*/
VerifyAbortBufferIO();
FlushErrorState();
ereport(WARNING,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg("Page verification failed on complete mode. "
"The node is %s, invalid page %u of relation %s.%s, the file is %s.",
g_instance.attr.attr_common.PGXCNodeName,
page,
get_namespace_name(RelationGetNamespace(scan->rs_rd), true),
RelationGetRelationName(scan->rs_rd),
relpathperm(scan->rs_rd->rd_node, MAIN_FORKNUM)),
handle_in_client(true)));
try_next_page = true;
}
PG_END_TRY();
if (try_next_page) {
continue;
}
return;
}
return;
}
/*
* VerifyHeapGetTup
*
* @Description: fetch next heap tuple. The main function is same to the function heapgettup, but this batch
* will catch all the error and print the warning and also we must deal with IO\buffer\lock
* exception so that we can continue to check other tuple or page. When the page corrupts, we
* think the page is broken so we need skip this page. We need to clean up the environment and
* skip this page to go into the next page. If the tuple corrupts, we need to judge the tuple
* corrupts or the tuple cannot be read. If tuple corrupts,we need to clean the tuple related
* data and skip to next tuple. If tuple cannot be read, we think the page is broken and we need
* to check the next page.
* @in scan - the relation's heap scan description.
* @in dir - the scan direction, The default scan is ForwardScanDirection.
* @in&out page - the relation's current page
* @in&out finished - judge the scan is in the end.
* @return: bool
*/
static bool VerifyHeapGetTup(HeapScanDesc scan, ScanDirection dir)
{
HeapTuple tuple = &(scan->rs_ctup);
Snapshot snapshot = scan->rs_snapshot;
BlockNumber page = InvalidBlockNumber;
bool finished = false;
Page dp = NULL;
int lines = 0;
OffsetNumber line_off = InvalidOffsetNumber;
int lines_left = 0;
ItemId lpp;
bool is_valid_relation_page = true;
MemoryContext verify_context = CurrentMemoryContext;
/*
* calculate next starting line_off, given scan direction
*/
Assert(ScanDirectionIsForward(dir));
if (!scan->rs_inited) {
/* return null immediately if relation is empty */
if (scan->rs_nblocks == 0) {
Assert(!BufferIsValid(scan->rs_cbuf));
tuple->t_data = NULL;
return is_valid_relation_page;
}
/* first page and first offnum */
page = scan->rs_startblock;
scan->rs_cblock = page;
line_off = FirstOffsetNumber;
scan->rs_inited = true;
PG_TRY();
{
heapgetpage(scan, page);
}
PG_CATCH();
{
(void)MemoryContextSwitchTo(verify_context);
is_valid_relation_page = false;
/*
* VerifyAbortBufferIO is used for special error handling for verify after catching exceptions,
* so that it can handle the next operation.
*/
VerifyAbortBufferIO();
FlushErrorState();
ereport(WARNING,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg("Page verification failed on complete mode."
"The node is %s, invalid page %u of relation %s.%s, the file is %s.",
g_instance.attr.attr_common.PGXCNodeName,
page,
get_namespace_name(RelationGetNamespace(scan->rs_rd), true),
RelationGetRelationName(scan->rs_rd),
relpathperm(scan->rs_rd->rd_node, MAIN_FORKNUM)),
handle_in_client(true)));
SkipToNewPage(scan, dir, scan->rs_cblock, finished, is_valid_relation_page);
}
PG_END_TRY();
if (finished) {
return is_valid_relation_page;
}
} else {
/* continue from previously returned page/tuple */
/* page is the current page and line_off is the next offnum */
page = scan->rs_cblock;
line_off = OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self)));
}
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
dp = (Page)BufferGetPage(scan->rs_cbuf);
lines = PageGetMaxOffsetNumber(dp);
/* page and line_off now reference the physically next tid */
lines_left = lines - line_off + 1;
/* advance the scan until we find a qualifying tuple or run out of stuff to scan */
lpp = PageGetItemId(dp, line_off);
for (;;) {
while (lines_left > 0) {
if (ItemIdIsNormal(lpp)) {
bool valid = false;
tuple->t_data = (HeapTupleHeader)PageGetItem((Page)dp, lpp);
tuple->t_len = ItemIdGetLength(lpp);
ItemPointerSet(&(tuple->t_self), page, line_off);
HeapTupleCopyBaseFromPage(tuple, dp);
/* if current tuple qualifies, return it. */
valid = HeapTupleSatisfiesVisibility(tuple, snapshot, scan->rs_cbuf);
CheckForSerializableConflictOut(valid, scan->rs_rd, tuple, scan->rs_cbuf, snapshot);
if (valid) {
/* make sure this tuple is visible and then uncompress it */
DECOMPRESS_HEAP_TUPLE(
HEAP_TUPLE_IS_COMPRESSED(tuple->t_data), tuple, &(scan->rs_ctbuf_hdr), (scan->rs_tupdesc), dp);
}
if (valid) {
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
return is_valid_relation_page;
}
}
/* otherwise move to the next item on the page */
--lines_left;
/* move forward in this page's ItemId array */
++lpp;
++line_off;
}
/* if we get here, it means we've exhausted the items on this page and it's time to move to the next. */
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
page = scan->rs_cblock;
SkipToNewPage(scan, dir, page, finished, is_valid_relation_page);
if (finished) {
return is_valid_relation_page;
}
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
dp = (Page)BufferGetPage(scan->rs_cbuf);
lines = PageGetMaxOffsetNumber((Page)dp);
lines_left = lines;
line_off = FirstOffsetNumber;
lpp = PageGetItemId(dp, FirstOffsetNumber);
}
}
/* ----------------
* heapgettup - fetch next heap tuple
*
* Initialize the scan if not already done; then advance to the next
* tuple as indicated by "dir"; return the next tuple in scan->rs_ctup,
* or set scan->rs_ctup.t_data = NULL if no more tuples.
*
* dir == NoMovementScanDirection means "re-fetch the tuple indicated
* by scan->rs_ctup".
*
* Note: the reason nkeys/key are passed separately, even though they are
* kept in the scan descriptor, is that the caller may not want us to check
* the scankeys.
*
* Note: when we fall off the end of the scan in either direction, we
* reset rs_inited. This means that a further request with the same
* scan direction will restart the scan, which is a bit odd, but a
* request with the opposite scan direction will start a fresh scan
* in the proper direction. The latter is required behavior for cursors,
* while the former case is generally undefined behavior in Postgres
* so we don't care too much.
* ----------------
*/
static void heapgettup(HeapScanDesc scan, ScanDirection dir, int nkeys, ScanKey key)
{
HeapTuple tuple = &(scan->rs_ctup);
Snapshot snapshot = scan->rs_snapshot;
bool backward = ScanDirectionIsBackward(dir);
BlockNumber page;
bool finished = false;
Page dp;
int lines;
OffsetNumber line_off;
int lines_left;
ItemId lpp;
gstrace_entry(GS_TRC_ID_heapgettup);
/*
* calculate next starting line_off, given scan direction
*/
if (ScanDirectionIsForward(dir)) {
if (!scan->rs_inited) {
/*
* return null immediately if relation is empty
*/
if (scan->rs_nblocks == 0) {
Assert(!BufferIsValid(scan->rs_cbuf));
tuple->t_data = NULL;
gstrace_exit(GS_TRC_ID_heapgettup);
return;
}
page = scan->rs_startblock; /* first page */
heapgetpage(scan, page);
line_off = FirstOffsetNumber; /* first offnum */
scan->rs_inited = true;
} else {
/* continue from previously returned page/tuple */
page = scan->rs_cblock; /* current page */
line_off = OffsetNumberNext(ItemPointerGetOffsetNumber(&(tuple->t_self))); /* next offnum */
}
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
dp = (Page)BufferGetPage(scan->rs_cbuf);
lines = PageGetMaxOffsetNumber(dp);
/* page and line_off now reference the physically next tid */
lines_left = lines - line_off + 1;
} else if (backward) {
if (!scan->rs_inited) {
/* return null immediately if relation is empty */
if (scan->rs_nblocks == 0) {
Assert(!BufferIsValid(scan->rs_cbuf));
tuple->t_data = NULL;
gstrace_exit(GS_TRC_ID_heapgettup);
return;
}
/*
* Disable reporting to syncscan logic in a backwards scan; it's
* not very likely anyone else is doing the same thing at the same
* time, and much more likely that we'll just bollix things for
* forward scanners.
*/
scan->rs_syncscan = false;
/* start from last page of the scan */
if (scan->rs_startblock > 0)
page = scan->rs_startblock - 1;
else
page = scan->rs_nblocks - 1;
heapgetpage(scan, page);
} else {
/* continue from previously returned page/tuple */
page = scan->rs_cblock; /* current page */
}
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
dp = (Page)BufferGetPage(scan->rs_cbuf);
lines = PageGetMaxOffsetNumber(dp);
if (!scan->rs_inited) {
line_off = lines; /* final offnum */
scan->rs_inited = true;
} else {
line_off = /* previous offnum */
OffsetNumberPrev(ItemPointerGetOffsetNumber(&(tuple->t_self)));
}
/* page and line_off now reference the physically previous tid */
lines_left = line_off;
} else {
/* ''no movement'' scan direction: refetch prior tuple */
if (!scan->rs_inited) {
Assert(!BufferIsValid(scan->rs_cbuf));
tuple->t_data = NULL;
gstrace_exit(GS_TRC_ID_heapgettup);
return;
}
page = ItemPointerGetBlockNumber(&(tuple->t_self));
if (page != scan->rs_cblock)
heapgetpage(scan, page);
/* Since the tuple was previously fetched, needn't lock page here */
dp = (Page)BufferGetPage(scan->rs_cbuf);
line_off = ItemPointerGetOffsetNumber(&(tuple->t_self));
/* Prevent concurrent page upgrades */
bool is_lock = false;
if (PageIs4BXidVersion(dp)) {
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
is_lock = true;
}
lpp = PageGetItemId(dp, line_off);
Assert(ItemIdIsNormal(lpp));
tuple->t_data = (HeapTupleHeader)PageGetItem((Page)dp, lpp);
tuple->t_len = ItemIdGetLength(lpp);
HeapTupleCopyBaseFromPage(tuple, dp);
if (is_lock) {
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
is_lock = false;
}
DECOMPRESS_HEAP_TUPLE(
HEAP_TUPLE_IS_COMPRESSED(tuple->t_data), tuple, &(scan->rs_ctbuf_hdr), (scan->rs_tupdesc), dp);
gstrace_exit(GS_TRC_ID_heapgettup);
return;
}
/*
* advance the scan until we find a qualifying tuple or run out of stuff
* to scan
*/
lpp = PageGetItemId(dp, line_off);
for (;;) {
while (lines_left > 0) {
if (ItemIdIsNormal(lpp)) {
bool valid = false;
tuple->t_data = (HeapTupleHeader)PageGetItem((Page)dp, lpp);
tuple->t_len = ItemIdGetLength(lpp);
ItemPointerSet(&(tuple->t_self), page, line_off);
HeapTupleCopyBaseFromPage(tuple, dp);
/*
* if current tuple qualifies, return it.
*/
valid = HeapTupleSatisfiesVisibility(tuple, snapshot, scan->rs_cbuf);
CheckForSerializableConflictOut(valid, scan->rs_rd, tuple, scan->rs_cbuf, snapshot);
if (valid) {
/* make sure this tuple is visible and then uncompress it */
DECOMPRESS_HEAP_TUPLE(
HEAP_TUPLE_IS_COMPRESSED(tuple->t_data), tuple, &(scan->rs_ctbuf_hdr), (scan->rs_tupdesc), dp);
if (key != NULL) {
HeapKeyTest(tuple, (scan->rs_tupdesc), nkeys, key, valid);
}
}
if (valid) {
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
gstrace_exit(GS_TRC_ID_heapgettup);
return;
}
}
/*
* otherwise move to the next item on the page
*/
--lines_left;
if (backward) {
--lpp; /* move back in this page's ItemId array */
--line_off;
} else {
++lpp; /* move forward in this page's ItemId array */
++line_off;
}
}
/*
* if we get here, it means we've exhausted the items on this page and
* it's time to move to the next.
*/
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
/*
* advance to next/prior page and detect end of scan
*/
finished = next_page(scan, dir, page);
/*
* return NULL if we've exhausted all the pages
*/
if (finished) {
if (BufferIsValid(scan->rs_cbuf)) {
ReleaseBuffer(scan->rs_cbuf);
}
scan->rs_cbuf = InvalidBuffer;
scan->rs_cblock = InvalidBlockNumber;
tuple->t_data = NULL;
scan->rs_inited = false;
gstrace_exit(GS_TRC_ID_heapgettup);
return;
}
heap_prefetch(scan, dir);
heapgetpage(scan, page);
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
dp = (Page)BufferGetPage(scan->rs_cbuf);
lines = PageGetMaxOffsetNumber((Page)dp);
lines_left = lines;
if (backward) {
line_off = lines;
lpp = PageGetItemId(dp, lines);
} else {
line_off = FirstOffsetNumber;
lpp = PageGetItemId(dp, FirstOffsetNumber);
}
}
gstrace_exit(GS_TRC_ID_heapgettup);
}
/* ----------------
* heapgettup_pagemode - fetch next heap tuple in page-at-a-time mode
*
* Same API as heapgettup, but used in page-at-a-time mode
*
* The internal logic is much the same as heapgettup's too, but there are some
* differences: we do not take the buffer content lock (that only needs to
* happen inside heapgetpage), and we iterate through just the tuples listed
* in rs_vistuples[] rather than all tuples on the page. Notice that
* line_index is 0-based, where the corresponding loop variable line_off in
* heapgettup is 1-based.
* ----------------
*/
static void heapgettup_pagemode(HeapScanDesc scan, ScanDirection dir, int nkeys, ScanKey key)
{
HeapTuple tuple = &(scan->rs_ctup);
bool backward = ScanDirectionIsBackward(dir);
bool is_range_scan_in_redis = scan->rs_isRangeScanInRedis;
BlockNumber page;
bool finished = false;
Page dp;
int lines;
int line_index;
OffsetNumber line_off;
int lines_left;
ItemId lpp;
gstrace_entry(GS_TRC_ID_heapgettup_pagemode);
/* IO collector and IO scheduler for seqsan */
if (ENABLE_WORKLOAD_CONTROL) {
IOSchedulerAndUpdate(IO_TYPE_READ, 1, IO_TYPE_ROW);
}
/*
* calculate next starting line_index, given scan direction
*/
if (ScanDirectionIsForward(dir) || is_range_scan_in_redis) {
if (!scan->rs_inited) {
/*
* return null immediately if relation is empty
*/
if (scan->rs_nblocks == 0) {
Assert(!BufferIsValid(scan->rs_cbuf));
tuple->t_data = NULL;
gstrace_exit(GS_TRC_ID_heapgettup_pagemode);
return;
}
page = scan->rs_startblock; /* first page */
heapgetpage(scan, page);
line_index = 0;
scan->rs_inited = true;
} else {
/* continue from previously returned page/tuple */
page = scan->rs_cblock; /* current page */
line_index = scan->rs_cindex + 1;
}
dp = (Page)BufferGetPage(scan->rs_cbuf);
lines = scan->rs_ntuples;
/* page and line_index now reference the next visible tid */
lines_left = lines - line_index;
} else if (backward) {
if (!scan->rs_inited) {
/* return null immediately if relation is empty */
if (scan->rs_nblocks == 0) {
Assert(!BufferIsValid(scan->rs_cbuf));
tuple->t_data = NULL;
gstrace_exit(GS_TRC_ID_heapgettup_pagemode);
return;
}
/*
* Disable reporting to syncscan logic in a backwards scan; it's
* not very likely anyone else is doing the same thing at the same
* time, and much more likely that we'll just bollix things for
* forward scanners.
*/
scan->rs_syncscan = false;
/* start from last page of the scan */
if (scan->rs_startblock > 0) {
page = scan->rs_startblock - 1;
} else {
page = scan->rs_nblocks - 1;
}
heapgetpage(scan, page);
} else {
/* continue from previously returned page/tuple */
page = scan->rs_cblock; /* current page */
}
dp = (Page)BufferGetPage(scan->rs_cbuf);
lines = scan->rs_ntuples;
if (!scan->rs_inited) {
line_index = lines - 1;
scan->rs_inited = true;
} else {
line_index = scan->rs_cindex - 1;
}
/* page and line_index now reference the previous visible tid */
lines_left = line_index + 1;
} else {
/* ''no movement'' scan direction: refetch prior tuple */
if (!scan->rs_inited) {
Assert(!BufferIsValid(scan->rs_cbuf));
tuple->t_data = NULL;
gstrace_exit(GS_TRC_ID_heapgettup_pagemode);
return;
}
page = ItemPointerGetBlockNumber(&(tuple->t_self));
if (page != scan->rs_cblock) {
heapgetpage(scan, page);
}
/* Since the tuple was previously fetched, needn't lock page here */
dp = (Page)BufferGetPage(scan->rs_cbuf);
line_off = ItemPointerGetOffsetNumber(&(tuple->t_self));
/* Prevent concurrent page upgrades */
bool is_lock = false;
if (PageIs4BXidVersion(dp)) {
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
is_lock = true;
}
lpp = PageGetItemId(dp, line_off);
Assert(ItemIdIsNormal(lpp));
tuple->t_data = (HeapTupleHeader)PageGetItem((Page)dp, lpp);
tuple->t_len = ItemIdGetLength(lpp);
HeapTupleCopyBaseFromPage(tuple, dp);
if (is_lock) {
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
is_lock = false;
}
/* check that rs_cindex is in sync */
Assert(scan->rs_cindex < scan->rs_ntuples);
Assert(line_off == scan->rs_vistuples[scan->rs_cindex]);
DECOMPRESS_HEAP_TUPLE(
HEAP_TUPLE_IS_COMPRESSED(tuple->t_data), tuple, &(scan->rs_ctbuf_hdr), (scan->rs_tupdesc), dp);
gstrace_exit(GS_TRC_ID_heapgettup_pagemode);
return;
}
/*
* advance the scan until we find a qualifying tuple or run out of stuff
* to scan
*/
bool is_lock = false;
for (;;) {
/* Prevent concurrent page upgrades */
if (PageIs4BXidVersion(dp) && is_lock == false) {
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
is_lock = true;
}
while (lines_left > 0) {
line_off = scan->rs_vistuples[line_index];
lpp = PageGetItemId(dp, line_off);
Assert(ItemIdIsNormal(lpp));
tuple->t_data = (HeapTupleHeader)PageGetItem((Page)dp, lpp);
tuple->t_len = ItemIdGetLength(lpp);
ItemPointerSet(&(tuple->t_self), page, line_off);
HeapTupleCopyBaseFromPage(tuple, dp);
/*
* if the tuple is compressed, uncompress it first, because
* 1. reduce the UNCOMPRESS number within HeapKeyTest();
* 2. maybe reduce the number of palloc() within HeapKeyTest();
*/
DECOMPRESS_HEAP_TUPLE(
HEAP_TUPLE_IS_COMPRESSED(tuple->t_data), tuple, &(scan->rs_ctbuf_hdr), (scan->rs_tupdesc), dp);
/*
* if current tuple qualifies, return it.
*/
if (key != NULL) {
bool valid = false;
HeapKeyTest(tuple, (scan->rs_tupdesc), nkeys, key, valid);
if (valid) {
scan->rs_cindex = line_index;
if (is_lock) {
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
is_lock = false;
}
gstrace_exit(GS_TRC_ID_heapgettup_pagemode);
return;
}
} else {
scan->rs_cindex = line_index;
if (is_lock) {
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
is_lock = false;
}
gstrace_exit(GS_TRC_ID_heapgettup_pagemode);
return;
}
/*
* otherwise move to the next item on the page
*/
--lines_left;
if (backward) {
--line_index;
} else {
++line_index;
}
}
if (is_lock) {
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
is_lock = false;
}
/*
* if we get here, it means we've exhausted the items on this page and
* it's time to move to the next.
*/
finished = next_page(scan, dir, page);
/*
* return NULL if we've exhausted all the pages
*/
if (finished) {
if (BufferIsValid(scan->rs_cbuf)) {
ReleaseBuffer(scan->rs_cbuf);
}
scan->rs_cbuf = InvalidBuffer;
scan->rs_cblock = InvalidBlockNumber;
tuple->t_data = NULL;
scan->rs_inited = false;
gstrace_exit(GS_TRC_ID_heapgettup_pagemode);
return;
}
heap_prefetch(scan, dir);
heapgetpage(scan, page);
dp = (Page)BufferGetPage(scan->rs_cbuf);
lines = scan->rs_ntuples;
lines_left = lines;
if (backward) {
line_index = lines - 1;
} else {
line_index = 0;
}
}
gstrace_exit(GS_TRC_ID_heapgettup_pagemode);
}
#if defined(DISABLE_COMPLEX_MACRO)
/*
* This is formatted so oddly so that the correspondence to the macro
* definition in access/htup.h is maintained.
*/
Datum fastgetattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, bool* isnull)
{
/* make sure this tuple is not row-compressed.
* otherwise, please call fastgetattr_with_dict().
*/
Assert(!HEAP_TUPLE_IS_COMPRESSED(tup->t_data));
if (attnum <= 0) {
return (Datum)NULL;
}
*isnull = false;
if (HeapTupleNoNulls(tup)) {
if (tupleDesc->attrs[attnum - 1]->attcacheoff >= 0) {
return fetchatt(tupleDesc->attrs[attnum - 1],
(char *)tup->t_data + tup->t_data->t_hoff + tupleDesc->attrs[attnum - 1]->attcacheoff);
}
return nocachegetattr(tup, attnum, tupleDesc);
} else {
if (att_isnull(attnum - 1, tup->t_data->t_bits)) {
*isnull = true;
return (Datum)NULL;
}
return nocachegetattr(tup, attnum, tupleDesc);
}
}
Datum fastgetattr_with_dict(HeapTuple tup, int attnum, TupleDesc tupleDesc, bool* isnull, char* pageDict)
{
Assert(HEAP_TUPLE_IS_COMPRESSED(tup->t_data));
Assert(attnum > 0);
*isnull = false;
/* case 1: this tuple has nulls, and the attnum's bit is set */
if (HeapTupleHasNulls(tup) && att_isnull((attnum)-1, (tup)->t_data->t_bits)) {
*(isnull) = true;
return (Datum)NULL;
}
/* case 2: this tuple is compressed and has no nulls */
return nocache_cmprs_get_attr(tup, attnum, tupleDesc, pageDict);
}
#endif /* defined(DISABLE_COMPLEX_MACRO) */
/* ----------------
* relation_open - open any relation by relation OID
*
* If lockmode is not "NoLock", the specified kind of lock is
* obtained on the relation. (Generally, NoLock should only be
* used if the caller knows it has some appropriate lock on the
* relation already.)
*
* An error is raised if the relation does not exist.
*
* NB: a "relation" is anything with a pg_class entry. The caller is
* expected to check whether the relkind is something it can handle.
* ----------------
*/
Relation relation_open(Oid relationId, LOCKMODE lockmode, int2 bucketId)
{
Relation r;
Assert(lockmode >= NoLock && lockmode < MAX_LOCKMODES);
if (IsAbortedTransactionBlockState()) {
force_backtrace_messages = true;
ereport(ERROR,
(errcode(ERRCODE_RELATION_OPEN_ERROR),
errmsg("current transaction is aborted, "
"commands ignored until end of transaction block")));
}
/* Get the lock before trying to open the relcache entry */
if (lockmode != NoLock) {
LockRelationOid(relationId, lockmode);
}
/* The relcache does all the real work... */
r = RelationIdGetRelation(relationId);
if (!RelationIsValid(r)) {
force_backtrace_messages = true;
ereport(
ERROR, (errcode(ERRCODE_RELATION_OPEN_ERROR), errmsg("could not open relation with OID %u", relationId)));
}
/* Make note that we've accessed a temporary relation */
if (RelationUsesLocalBuffers(r)) {
t_thrd.xact_cxt.MyXactAccessedTempRel = true;
}
/* Make note that we've accessed a repliacted relation */
if (r->rd_locator_info != NULL && IsRelationReplicated(r->rd_locator_info)) {
t_thrd.xact_cxt.MyXactAccessedRepRel = true;
}
pgstat_initstats(r);
if (bucketId != InvalidBktId) {
Assert(RELATION_OWN_BUCKET(r));
r = bucketGetRelation(r, NULL, bucketId);
}
return r;
}
/* ----------------
* try_relation_open - open any relation by relation OID
*
* Same as relation_open, except return NULL instead of failing
* if the relation does not exist.
* ----------------
*/
Relation try_relation_open(Oid relationId, LOCKMODE lockmode)
{
Relation r;
Assert(lockmode >= NoLock && lockmode < MAX_LOCKMODES);
/* Get the lock first */
if (lockmode != NoLock) {
LockRelationOid(relationId, lockmode);
}
/*
* Now that we have the lock, probe to see if the relation really exists
* or not.
*/
if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relationId))) {
/* Release useless lock */
if (lockmode != NoLock) {
UnlockRelationOid(relationId, lockmode);
}
return NULL;
}
/* Should be safe to do a relcache load */
r = RelationIdGetRelation(relationId);
if (!RelationIsValid(r)) {
ereport(
ERROR, (errcode(ERRCODE_RELATION_OPEN_ERROR), errmsg("could not open relation with OID %u", relationId)));
}
/* Make note that we've accessed a temporary relation */
if (RelationUsesLocalBuffers(r)) {
t_thrd.xact_cxt.MyXactAccessedTempRel = true;
}
/* Make note that we've accessed a repliacted relation */
if (r->rd_locator_info != NULL && IsRelationReplicated(r->rd_locator_info)) {
t_thrd.xact_cxt.MyXactAccessedRepRel = true;
}
pgstat_initstats(r);
return r;
}
/* ----------------
* relation_openrv - open any relation specified by a RangeVar
*
* Same as relation_open, but the relation is specified by a RangeVar.
* ----------------
*/
Relation relation_openrv(const RangeVar* relation, LOCKMODE lockmode)
{
Oid relOid;
/*
* Check for shared-cache-inval messages before trying to open the
* relation. This is needed even if we already hold a lock on the
* relation, because GRANT/REVOKE are executed without taking any lock on
* the target relation, and we want to be sure we see current ACL
* information. We can skip this if asked for NoLock, on the assumption
* that such a call is not the first one in the current command, and so we
* should be reasonably up-to-date already. (XXX this all could stand to
* be redesigned, but for the moment we'll keep doing this like it's been
* done historically.)
*/
if (lockmode != NoLock) {
AcceptInvalidationMessages();
}
/* Look up and lock the appropriate relation using namespace search */
relOid = RangeVarGetRelid(relation, lockmode, false);
/* Let relation_open do the rest */
return relation_open(relOid, NoLock);
}
/* ----------------
* relation_openrv_extended - open any relation specified by a RangeVar
*
* Same as relation_openrv, but with an additional missing_ok argument
* allowing a NULL return rather than an error if the relation is not
* found. (Note that some other causes, such as permissions problems,
* will still result in an ereport.)
* ----------------
*/
Relation relation_openrv_extended(
const RangeVar* relation, LOCKMODE lockmode, bool missing_ok, bool isSupportSynonym, StringInfo detailInfo)
{
Oid relOid;
Oid refSynOid = InvalidOid;
Relation rel;
/*
* Check for shared-cache-inval messages before trying to open the
* relation. See comments in relation_openrv().
*/
if (lockmode != NoLock) {
AcceptInvalidationMessages();
}
/* Look up and lock the appropriate relation using namespace search */
relOid = RangeVarGetRelidExtended(
relation, lockmode, missing_ok, false, false, isSupportSynonym, NULL, NULL, detailInfo, &refSynOid);
/* Return NULL on not-found */
if (!OidIsValid(relOid)) {
return NULL;
}
/* Let relation_open do the rest */
rel = relation_open(relOid, NoLock);
/* Record the refSynOid into RelationData, if exists. */
rel->rd_refSynOid = refSynOid;
return rel;
}
/* ----------------
* relation_close - close any relation
*
* If lockmode is not "NoLock", we then release the specified lock.
*
* Note that it is often sensible to hold a lock beyond relation_close;
* in that case, the lock is released automatically at xact end.
* ----------------
*/
void relation_close(Relation relation, LOCKMODE lockmode)
{
Relation rel = relation;
LockRelId relid;
if (RelationIsBucket(relation)) {
rel = relation->parent;
Assert(RELATION_OWN_BUCKET(rel));
bucketCloseRelation(relation);
}
Assert(PointerIsValid(rel));
Assert(lockmode >= NoLock && lockmode < MAX_LOCKMODES);
relid = rel->rd_lockInfo.lockRelId;
/* The relcache does the real work... */
RelationClose(rel);
if (lockmode != NoLock) {
UnlockRelationId(&relid, lockmode);
}
}
/* ----------------
* heap_open - open a heap relation by relation OID
*
* This is essentially relation_open plus check that the relation
* is not an index nor a composite type. (The caller should also
* check that it's not a view or foreign table before assuming it has
* storage.)
* ----------------
*/
Relation heap_open(Oid relationId, LOCKMODE lockmode, int2 bucketid)
{
Relation r;
r = relation_open(relationId, lockmode, bucketid);
if (r->rd_rel->relkind == RELKIND_INDEX) {
ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is an index", RelationGetRelationName(r))));
} else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) {
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is a composite type", RelationGetRelationName(r))));
}
return r;
}
/* ----------------
* heap_openrv - open a heap relation specified
* by a RangeVar node
*
* As above, but relation is specified by a RangeVar.
* ----------------
*/
Relation heap_openrv(const RangeVar* relation, LOCKMODE lockmode)
{
Relation r;
r = relation_openrv(relation, lockmode);
if (r->rd_rel->relkind == RELKIND_INDEX) {
ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is an index", RelationGetRelationName(r))));
} else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) {
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is a composite type", RelationGetRelationName(r))));
}
return r;
}
/* ----------------
* heap_openrv_extended - open a heap relation specified
* by a RangeVar node
*
* As above, but optionally return NULL instead of failing for
* relation-not-found.
* ----------------
*/
Relation heap_openrv_extended(
const RangeVar* relation, LOCKMODE lockmode, bool missing_ok, bool isSupportSynonym, StringInfo detailInfo)
{
Relation r = NULL;
r = relation_openrv_extended(relation, lockmode, missing_ok, isSupportSynonym, detailInfo);
if (r) {
if (isSupportSynonym && detailInfo != NULL && detailInfo->len > 0) {
/* If has some error detail infos, report it. */
if (r->rd_rel->relkind == RELKIND_INDEX) {
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an index", RelationGetRelationName(r)),
errdetail("%s", detailInfo->data)));
} else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) {
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is a composite type", RelationGetRelationName(r)),
errdetail("%s", detailInfo->data)));
}
} else {
if (r->rd_rel->relkind == RELKIND_INDEX) {
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is an index", RelationGetRelationName(r))));
}
else if (r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) {
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is a composite type", RelationGetRelationName(r))));
}
}
}
return r;
}
/* ----------------
* heap_beginscan - begin relation scan
*
* heap_beginscan_strat offers an extended API that lets the caller control
* whether a nondefault buffer access strategy can be used, and whether
* syncscan can be chosen (possibly resulting in the scan not starting from
* block zero). Both of these default to TRUE with plain heap_beginscan.
*
* heap_beginscan_bm is an alternative entry point for setting up a
* HeapScanDesc for a bitmap heap scan. Although that scan technology is
* really quite unlike a standard seqscan, there is just enough commonality
* to make it worth using the same data structure.
* ----------------
*/
HeapScanDesc heap_beginscan(Relation relation, Snapshot snapshot, int nkeys, ScanKey key, bool is_range_scan_in_redis)
{
/* We don't allow sync buffer read if it is a range scan in redis */
return heap_beginscan_internal(
relation, snapshot, nkeys, key, !is_range_scan_in_redis, !is_range_scan_in_redis, false, is_range_scan_in_redis);
}
HeapScanDesc heap_beginscan_strat(
Relation relation, Snapshot snapshot, int nkeys, ScanKey key, bool allow_strat, bool allow_sync)
{
return heap_beginscan_internal(relation, snapshot, nkeys, key, allow_strat, allow_sync, false);
}
HeapScanDesc heap_beginscan_bm(Relation relation, Snapshot snapshot, int nkeys, ScanKey key)
{
return heap_beginscan_internal(relation, snapshot, nkeys, key, false, false, true);
}
/*
* Description: Begin scan tuple for sample table.
*
* Parameters:
* @in relation: relation sample table
* @in snapshot: current activity snapshot
* @in nkeys: number of scan keys
* @in key: array of scan key descriptors
* @in allow_strat: allow or disallow use of access strategy
* @in allow_sync: allow or disallow use of syncscan
* @in is_range_scan_in_redis: true if it is a range scan in redistribution
*
* Return: HeapScanDesc
*/
HeapScanDesc heap_beginscan_sampling(Relation relation, Snapshot snapshot, int nkeys, ScanKey key, bool allow_strat,
bool allow_sync, bool is_range_scan_in_redis)
{
return heap_beginscan_internal(
relation, snapshot, nkeys, key, allow_strat, allow_sync, false, is_range_scan_in_redis, true);
}
static HeapScanDesc heap_beginscan_internal(Relation relation, Snapshot snapshot, int nkeys, ScanKey key,
bool allow_strat, bool allow_sync, bool is_bitmapscan, bool is_range_scan_in_redis, bool is_samplescan)
{
HeapScanDesc scan;
/*
* increment relation ref count while scanning relation
*
* This is just to make really sure the relcache entry won't go away while
* the scan has a pointer to it. Caller should be holding the rel open
* anyway, so this is redundant in all normal scenarios...
*/
RelationIncrementReferenceCount(relation);
/*
* allocate and initialize scan descriptor
*/
scan = (HeapScanDesc)palloc(SizeofHeapScanDescData + MaxHeapTupleSize);
scan->sd.type = T_ScanDesc_Heap;
scan->sd.tblAm = &g_HeapTblAm;
scan->rs_rd = relation;
scan->rs_tupdesc = RelationGetDescr(relation);
scan->rs_snapshot = snapshot;
scan->rs_nkeys = nkeys;
scan->rs_bitmapscan = is_bitmapscan;
scan->rs_samplescan = is_samplescan;
scan->rs_strategy = NULL; /* set in initscan */
scan->rs_allow_strat = allow_strat;
scan->rs_allow_sync = allow_sync;
scan->rs_isRangeScanInRedis = is_range_scan_in_redis;
/*
* we can use page-at-a-time mode if it's an MVCC-safe snapshot
*/
scan->rs_pageatatime = IsMVCCSnapshot(snapshot);
/*
* For a seqscan in a serializable transaction, acquire a predicate lock
* on the entire relation. This is required not only to lock all the
* matching tuples, but also to conflict with new insertions into the
* table. In an indexscan, we take page locks on the index pages covering
* the range specified in the scan qual, but in a heap scan there is
* nothing more fine-grained to lock. A bitmap scan is a different story,
* there we have already scanned the index and locked the index pages
* covering the predicate. But in that case we still have to lock any
* matching heap tuples.
*/
if (!is_bitmapscan) {
PredicateLockRelation(relation, snapshot);
}
/* we only need to set this up once */
scan->rs_ctup.t_tableOid = RelationGetRelid(relation);
scan->rs_ctup.t_bucketId = RelationGetBktid(relation);
#ifdef PGXC
scan->rs_ctup.t_xc_node_id = u_sess->pgxc_cxt.PGXCNodeIdentifier;
#endif
/*
* we do this here instead of in initscan() because heap_rescan also calls
* initscan() and we don't want to allocate memory again
*/
if (nkeys > 0) {
scan->rs_key = (ScanKey)palloc(sizeof(ScanKeyData) * nkeys);
} else {
scan->rs_key = NULL;
}
initscan(scan, key, false);
return scan;
}
/* ----------------
* heap_rescan - restart a relation scan
* ----------------
*/
void heap_rescan(HeapScanDesc scan, ScanKey key)
{
/*
* unpin scan buffers
*/
if (BufferIsValid(scan->rs_cbuf)) {
ReleaseBuffer(scan->rs_cbuf);
}
/*
* reinitialize scan descriptor
*/
initscan(scan, key, true);
}
/* ----------------
* heap_endscan - end relation scan
*
* See how to integrate with index scans.
* Check handling if reldesc caching.
* ----------------
*/
void heap_endscan(HeapScanDesc scan)
{
/* Note: no locking manipulations needed
*
* unpin scan buffers
*/
if (BufferIsValid(scan->rs_cbuf)) {
ReleaseBuffer(scan->rs_cbuf);
}
/* decrement relation reference count and free scan descriptor storage */
RelationDecrementReferenceCount(scan->rs_rd);
if (scan->rs_key != NULL) {
pfree(scan->rs_key);
scan->rs_key = NULL;
}
if (scan->rs_strategy != NULL) {
FreeAccessStrategy(scan->rs_strategy);
}
pfree(scan);
scan = NULL;
}
/*
* heap_getnext - retrieve next tuple in scan
*
* Fix to work with index relations.
* We don't return the buffer anymore, but you can get it from the
* returned HeapTuple.
*/
#ifdef HEAPDEBUGALL
#define HEAPDEBUG_1 \
ereport(DEBUG2, \
(errmsg("heap_getnext([%s,nkeys=%d],dir=%d) called", \
RelationGetRelationName(scan->rs_rd), \
scan->rs_nkeys, \
(int)direction)))
#define HEAPDEBUG_2 ereport(DEBUG2, (errmsg("heap_getnext returning EOS")))
#define HEAPDEBUG_3 ereport(DEBUG2, (errmsg("heap_getnext returning tuple")))
#else
#define HEAPDEBUG_1
#define HEAPDEBUG_2
#define HEAPDEBUG_3
#endif /* !defined(HEAPDEBUGALL) */
/*
* heapGetNextForVerify
*
* @Description: fetch next heap tuple for verify.
* @in scan - the relation's heap scan description.
* @in direction - the scan direction, The default scan is ForwardScanDirection.
* @in&out is_valid_relation_page - judge the relation page is valid or corrupted.
* @return: HeapTuple
*/
HeapTuple heapGetNextForVerify(HeapScanDesc scan, ScanDirection direction, bool& is_valid_relation_page)
{
/* Note: no locking manipulations needed */
/* heap_getnext( info ) */
HEAPDEBUG_1;
is_valid_relation_page = VerifyHeapGetTup(scan, direction);
if (scan->rs_ctup.t_data == NULL) {
/* heap_getnext returning EOS */
HEAPDEBUG_2;
return NULL;
}
/*
* if we get here it means we have a new current scan tuple, so point to
* the proper return buffer and return the tuple.
*
* heap_getnext returning tuple
*/
HEAPDEBUG_3;
pgstat_count_heap_getnext(scan->rs_rd);
Assert(!HEAP_TUPLE_IS_COMPRESSED(scan->rs_ctup.t_data));
return &(scan->rs_ctup);
}
HeapTuple heap_getnext(HeapScanDesc scan, ScanDirection direction)
{
/* Note: no locking manipulations needed */
HEAPDEBUG_1; /* heap_getnext( info ) */
if (scan->rs_pageatatime) {
heapgettup_pagemode(scan, direction, scan->rs_nkeys, scan->rs_key);
} else {
heapgettup(scan, direction, scan->rs_nkeys, scan->rs_key);
}
if (scan->rs_ctup.t_data == NULL) {
HEAPDEBUG_2; /* heap_getnext returning EOS */
return NULL;
}
/*
* if we get here it means we have a new current scan tuple, so point to
* the proper return buffer and return the tuple.
*/
HEAPDEBUG_3; /* heap_getnext returning tuple */
pgstat_count_heap_getnext(scan->rs_rd);
Assert(!HEAP_TUPLE_IS_COMPRESSED(scan->rs_ctup.t_data));
return &(scan->rs_ctup);
}
/*
* heap_fetch - retrieve tuple with given tid
*
* On entry, tuple->t_self is the TID to fetch. We pin the buffer holding
* the tuple, fill in the remaining fields of *tuple, and check the tuple
* against the specified snapshot.
*
* If successful (tuple found and passes snapshot time qual), then *userbuf
* is set to the buffer holding the tuple and TRUE is returned. The caller
* must unpin the buffer when done with the tuple.
*
* If the tuple is not found (ie, item number references a deleted slot),
* then tuple->t_data is set to NULL and FALSE is returned.
*
* If the tuple is found but fails the time qual check, then FALSE is returned
* but tuple->t_data is left pointing to the tuple.
*
* keep_buf determines what is done with the buffer in the FALSE-result cases.
* When the caller specifies keep_buf = true, we retain the pin on the buffer
* and return it in *userbuf (so the caller must eventually unpin it); when
* keep_buf = false, the pin is released and *userbuf is set to InvalidBuffer.
*
* stats_relation is the relation to charge the heap_fetch operation against
* for statistical purposes. (This could be the heap rel itself, an
* associated index, or NULL to not count the fetch at all.)
*
* heap_fetch does not follow HOT chains: only the exact TID requested will
* be fetched.
*
* It is somewhat inconsistent that we ereport() on invalid block number but
* return false on invalid item number. There are a couple of reasons though.
* One is that the caller can relatively easily check the block number for
* validity, but cannot check the item number without reading the page
* himself. Another is that when we are following a t_ctid link, we can be
* reasonably confident that the page number is valid (since VACUUM shouldn't
* truncate off the destination page without having killed the referencing
* tuple first), but the item number might well not be good.
*/
bool heap_fetch(
Relation relation, Snapshot snapshot, HeapTuple tuple, Buffer* userbuf, bool keep_buf, Relation stats_relation)
{
ItemPointer tid = &(tuple->t_self);
ItemId lp;
Buffer buffer;
Page page;
HeapTupleData private_tuple_data = *tuple; /* private copy of tuple */
HeapTuple private_tuple = &private_tuple_data;
OffsetNumber offnum;
bool valid = false;
/* another data space must be provided for decomperssing tuple. */
Assert(tuple && tuple->t_data);
gstrace_entry(GS_TRC_ID_heap_fetch);
/*
* Fetch and pin the appropriate page of the relation.
*/
buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
/* We've pinned the buffer, nobody can prune this buffer, check whether snapshot is valid. */
CheckSnapshotIsValidException(snapshot, "heap_fetch");
/*
* Need share lock on buffer to examine tuple commit status.
*/
LockBuffer(buffer, BUFFER_LOCK_SHARE);
page = BufferGetPage(buffer);
/*
* We'd better check for out-of-range offnum in case of VACUUM since the
* TID was obtained.
*/
offnum = ItemPointerGetOffsetNumber(tid);
if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page)) {
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
if (keep_buf) {
*userbuf = buffer;
} else {
ReleaseBuffer(buffer);
*userbuf = InvalidBuffer;
}
tuple->t_data = NULL;
gstrace_exit(GS_TRC_ID_heap_fetch);
return false;
}
/*
* get the item line pointer corresponding to the requested tid
*/
lp = PageGetItemId(page, offnum);
/*
* Must check for deleted tuple.
*/
if (!ItemIdIsNormal(lp)) {
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
if (keep_buf) {
*userbuf = buffer;
} else {
ReleaseBuffer(buffer);
*userbuf = InvalidBuffer;
}
tuple->t_data = NULL;
gstrace_exit(GS_TRC_ID_heap_fetch);
return false;
}
/*
* fill in *tuple fields
*/
private_tuple->t_data = (HeapTupleHeader)PageGetItem(page, lp);
private_tuple->t_len = ItemIdGetLength(lp);
HeapTupleCopyBaseFromPage(private_tuple, page);
private_tuple->t_tableOid = RelationGetRelid(relation);
private_tuple->t_bucketId = RelationGetBktid(relation);
#ifdef PGXC
private_tuple->t_xc_node_id = u_sess->pgxc_cxt.PGXCNodeIdentifier;
#endif
/*
* check time qualification of tuple, then release lock
*/
valid = HeapTupleSatisfiesVisibility(private_tuple, snapshot, buffer);
if (valid) {
PredicateLockTuple(relation, private_tuple, snapshot);
DECOMPRESS_HEAP_TUPLE(HEAP_TUPLE_IS_COMPRESSED(private_tuple->t_data),
private_tuple,
tuple->t_data,
relation->rd_att,
(const char*)page);
}
CheckForSerializableConflictOut(valid, relation, private_tuple, buffer, snapshot);
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
/* copy heap tuple info into output <tuple> */
tuple->t_data = private_tuple->t_data;
tuple->t_len = private_tuple->t_len;
tuple->t_tableOid = private_tuple->t_tableOid;
tuple->t_bucketId = private_tuple->t_bucketId;
HeapTupleCopyBase(tuple, private_tuple);
#ifdef PGXC
tuple->t_xc_node_id = private_tuple->t_xc_node_id;
#endif
if (valid) {
/*
* All checks passed, so return the tuple as valid. Caller is now
* responsible for releasing the buffer.
*/
*userbuf = buffer;
/* Count the successful fetch against appropriate rel, if any */
if (stats_relation != NULL) {
pgstat_count_heap_fetch(stats_relation);
}
gstrace_exit(GS_TRC_ID_heap_fetch);
return true;
}
/* Tuple failed time qual, but maybe caller wants to see it anyway. */
if (keep_buf) {
*userbuf = buffer;
} else {
ReleaseBuffer(buffer);
*userbuf = InvalidBuffer;
}
gstrace_exit(GS_TRC_ID_heap_fetch);
return false;
}
/*
* heap_hot_search_buffer - search HOT chain for tuple satisfying snapshot
*
* On entry, *tid is the TID of a tuple (either a simple tuple, or the root
* of a HOT chain), and buffer is the buffer holding this tuple. We search
* for the first chain member satisfying the given snapshot. If one is
* found, we update *tid to reference that tuple's offset number, and
* return TRUE. If no match, return FALSE without modifying *tid.
*
* heap_tuple is a caller-supplied buffer. When a match is found, we return
* the tuple here, in addition to updating *tid. If no match is found, the
* contents of this buffer on return are undefined.
*
* If all_dead is not NULL, we check non-visible tuples to see if they are
* globally dead; *all_dead is set TRUE if all members of the HOT chain
* are vacuumable, FALSE if not.
*
* Unlike heap_fetch, the caller must already have pin and (at least) share
* lock on the buffer; it is still pinned/locked at exit. Also unlike
* heap_fetch, we do not report any pgstats count; caller may do so if wanted.
*/
bool heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer, Snapshot snapshot, HeapTuple heap_tuple,
HeapTupleHeaderData* uncompress_tup, bool* all_dead, bool first_call)
{
Page dp = (Page)BufferGetPage(buffer);
TransactionId prev_xmax = InvalidTransactionId;
OffsetNumber offnum;
bool at_chain_start = false;
bool valid = false;
bool skip = false;
gstrace_entry(GS_TRC_ID_heap_hot_search_buffer);
/* If this is not the first call, previous call returned a (live!) tuple */
if (all_dead != NULL) {
*all_dead = first_call;
}
Assert(TransactionIdIsValid(u_sess->utils_cxt.RecentGlobalXmin));
Assert(ItemPointerGetBlockNumber(tid) == BufferGetBlockNumber(buffer));
offnum = ItemPointerGetOffsetNumber(tid);
at_chain_start = first_call;
skip = !first_call;
heap_tuple->t_self = *tid;
HeapTupleCopyBaseFromPage(heap_tuple, dp);
/* Scan through possible multiple members of HOT-chain */
for (;;) {
ItemId lp;
/* check for bogus TID */
if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(dp)) {
break;
}
lp = PageGetItemId(dp, offnum);
/* check for unused, dead, or redirected items */
if (!ItemIdIsNormal(lp)) {
/* We should only see a redirect at start of chain */
if (ItemIdIsRedirected(lp) && at_chain_start) {
/* Follow the redirect */
offnum = ItemIdGetRedirect(lp);
at_chain_start = false;
continue;
}
/* else must be end of chain */
break;
}
heap_tuple->t_data = (HeapTupleHeader)PageGetItem(dp, lp);
heap_tuple->t_len = ItemIdGetLength(lp);
heap_tuple->t_tableOid = RelationGetRelid(relation);
heap_tuple->t_bucketId = RelationGetBktid(relation);
HeapTupleCopyBaseFromPage(heap_tuple, dp);
#ifdef PGXC
heap_tuple->t_xc_node_id = u_sess->pgxc_cxt.PGXCNodeIdentifier;
#endif
ItemPointerSetOffsetNumber(&heap_tuple->t_self, offnum);
/*
* Shouldn't see a HEAP_ONLY tuple at chain start.
*/
if (at_chain_start && HeapTupleIsHeapOnly(heap_tuple)) {
break;
}
/*
* The xmin should match the previous xmax value, else chain is
* broken.
*/
if (TransactionIdIsValid(prev_xmax) && !TransactionIdEquals(prev_xmax, HeapTupleGetRawXmin(heap_tuple))) {
break;
}
/*
* When first_call is true (and thus, skip is initially false) we'll
* return the first tuple we find. But on later passes, heap_tuple
* will initially be pointing to the tuple we returned last time.
* Returning it again would be incorrect (and would loop forever), so
* we skip it and return the next match we find.
*/
if (!skip) {
/*
* For the benefit of logical decoding, have t_self point at the
* element of the HOT chain we're currently investigating instead
* of the root tuple of the HOT chain. This is important because
* the *Satisfies routine for historical mvcc snapshots needs the
* correct tid to decide about the visibility in some cases.
*/
ItemPointerSet(&(heap_tuple->t_self), BufferGetBlockNumber(buffer), offnum);
/* If it's visible per the snapshot, we must return it */
valid = HeapTupleSatisfiesVisibility(heap_tuple, snapshot, buffer);
CheckForSerializableConflictOut(valid, relation, heap_tuple, buffer, snapshot);
if (SHOW_DEBUG_MESSAGE()) {
ereport(DEBUG1,
(errmsg("heap_hot_search_buffer xid %lu self(%u,%hu) ctid(%u,%hu) valid %d "
"pointer(%u,%hu)",
GetCurrentTransactionIdIfAny(),
ItemPointerGetBlockNumber(&heap_tuple->t_self),
ItemPointerGetOffsetNumber(&heap_tuple->t_self),
ItemPointerGetBlockNumber(&heap_tuple->t_data->t_ctid),
ItemPointerGetOffsetNumber(&heap_tuple->t_data->t_ctid),
valid,
ItemPointerGetBlockNumber(&heap_tuple->t_data->t_ctid),
ItemPointerGetOffsetNumber(&heap_tuple->t_data->t_ctid))));
}
/* reset to original, non-redirected, tid */
heap_tuple->t_self = *tid;
if (valid) {
ItemPointerSetOffsetNumber(tid, offnum);
PredicateLockTuple(relation, heap_tuple, snapshot);
if (all_dead != NULL) {
*all_dead = false;
}
/*
* If uncompress_tup is NULL, the caller will not need tuple data
* Only check some status
*/
if (uncompress_tup != NULL) {
DECOMPRESS_HEAP_TUPLE(HEAP_TUPLE_IS_COMPRESSED(heap_tuple->t_data),
heap_tuple,
uncompress_tup,
relation->rd_att,
(const char*)BufferGetPage(buffer));
}
gstrace_exit(GS_TRC_ID_heap_hot_search_buffer);
return true;
}
}
skip = false;
/*
* If we can't see it, maybe no one else can either. At caller
* request, check whether all chain members are dead to all
* transactions.
*/
if (all_dead && *all_dead && !HeapTupleIsSurelyDead(heap_tuple, u_sess->utils_cxt.RecentGlobalXmin)) {
*all_dead = false;
}
/*
* Check to see if HOT chain continues past this tuple; if so fetch
* the next offnum and loop around.
*/
if (HeapTupleIsHotUpdated(heap_tuple)) {
Assert(ItemPointerGetBlockNumber(&heap_tuple->t_data->t_ctid) == ItemPointerGetBlockNumber(tid));
offnum = ItemPointerGetOffsetNumber(&heap_tuple->t_data->t_ctid);
at_chain_start = false;
prev_xmax = HeapTupleGetRawXmax(heap_tuple);
} else {
break; /* end of chain */
}
}
gstrace_exit(GS_TRC_ID_heap_hot_search_buffer);
return false;
}
/*
* heap_hot_search - search HOT chain for tuple satisfying snapshot
*
* This has the same API as heap_hot_search_buffer, except that the caller
* does not provide the buffer containing the page, rather we access it
* locally.
*/
bool heap_hot_search(ItemPointer tid, Relation relation, Snapshot snapshot, bool* all_dead)
{
bool result = false;
Buffer buffer;
HeapTupleData heap_tuple;
buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
LockBuffer(buffer, BUFFER_LOCK_SHARE);
result = heap_hot_search_buffer(tid, relation, buffer, snapshot, &heap_tuple, NULL, all_dead, true);
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
ReleaseBuffer(buffer);
return result;
}
void heap_get_max_tid(const Relation rel, ItemPointer ctid)
{
BlockNumber blk;
OffsetNumber offnum;
Buffer buffer;
Page page;
blk = RelationGetNumberOfBlocks(rel) - 1;
if (!BlockNumberIsValid(blk)) {
/* the target table must be empty, we should just return (0,0) only */
ItemPointerZero(ctid);
return;
}
buffer = ReadBuffer(rel, blk);
LockBuffer(buffer, BUFFER_LOCK_SHARE);
page = BufferGetPage(buffer);
offnum = PageGetMaxOffsetNumber(page);
UnlockReleaseBuffer(buffer);
ItemPointerSet(ctid, blk, offnum);
return;
}
/*
* heap_get_latest_tid - get the latest tid of a specified tuple
*
* Actually, this gets the latest version that is visible according to
* the passed snapshot. You can pass SnapshotDirty to get the very latest,
* possibly uncommitted version.
*
* *tid is both an input and an output parameter: it is updated to
* show the latest version of the row. Note that it will not be changed
* if no version of the row passes the snapshot test.
*/
void heap_get_latest_tid(Relation relation, Snapshot snapshot, ItemPointer tid)
{
BlockNumber blk;
ItemPointerData ctid;
TransactionId priorXmax;
/* this is to avoid Assert failures on bad input */
if (!ItemPointerIsValid(tid)) {
return;
}
/*
* Since this can be called with user-supplied TID, don't trust the input
* too much. (RelationGetNumberOfBlocks is an expensive check, so we
* don't check t_ctid links again this way. Note that it would not do to
* call it just once and save the result, either.)
*/
blk = ItemPointerGetBlockNumber(tid);
if (blk >= RelationGetNumberOfBlocks(relation)) {
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg("block number %u is out of range for relation \"%s\"", blk, RelationGetRelationName(relation))));
}
/*
* Loop to chase down t_ctid links. At top of loop, ctid is the tuple we
* need to examine, and *tid is the TID we will return if ctid turns out
* to be bogus.
*
* Note that we will loop until we reach the end of the t_ctid chain.
* Depending on the snapshot passed, there might be at most one visible
* version of the row, but we don't try to optimize for that.
*/
ctid = *tid;
priorXmax = InvalidTransactionId; /* cannot check first XMIN */
for (;;) {
Buffer buffer;
Page page;
OffsetNumber offnum;
ItemId lp;
HeapTupleData tp;
bool valid = false;
/*
* Read, pin, and lock the page.
*/
buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&ctid));
LockBuffer(buffer, BUFFER_LOCK_SHARE);
page = BufferGetPage(buffer);
/*
* Check for bogus item number. This is not treated as an error
* condition because it can happen while following a t_ctid link. We
* just assume that the prior tid is OK and return it unchanged.
*/
offnum = ItemPointerGetOffsetNumber(&ctid);
if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page)) {
UnlockReleaseBuffer(buffer);
break;
}
lp = PageGetItemId(page, offnum);
if (!ItemIdIsNormal(lp)) {
UnlockReleaseBuffer(buffer);
break;
}
/* OK to access the tuple */
tp.t_self = ctid;
tp.t_data = (HeapTupleHeader)PageGetItem(page, lp);
tp.t_len = ItemIdGetLength(lp);
tp.t_tableOid = RelationGetRelid(relation);
tp.t_bucketId = RelationGetBktid(relation);
HeapTupleCopyBaseFromPage(&tp, page);
/*
* After following a t_ctid link, we might arrive at an unrelated
* tuple. Check for XMIN match.
*/
if (TransactionIdIsValid(priorXmax) && !TransactionIdEquals(priorXmax, HeapTupleGetRawXmin(&tp))) {
UnlockReleaseBuffer(buffer);
break;
}
/*
* Check time qualification of tuple; if visible, set it as the new
* result candidate.
*/
valid = HeapTupleSatisfiesVisibility(&tp, snapshot, buffer);
CheckForSerializableConflictOut(valid, relation, &tp, buffer, snapshot);
if (valid) {
*tid = ctid;
}
/*
* If there's a valid t_ctid link, follow it, else we're done.
*/
if ((tp.t_data->t_infomask & (HEAP_XMAX_INVALID | HEAP_IS_LOCKED)) ||
ItemPointerEquals(&tp.t_self, &tp.t_data->t_ctid)) {
UnlockReleaseBuffer(buffer);
break;
}
ctid = tp.t_data->t_ctid;
priorXmax = HeapTupleGetRawXmax(&tp);
UnlockReleaseBuffer(buffer);
} /* end of loop */
}
/*
* UpdateXmaxHintBits - update tuple hint bits after xmax transaction ends
*
* This is called after we have waited for the XMAX transaction to terminate.
* If the transaction aborted, we guarantee the XMAX_INVALID hint bit will
* be set on exit. If the transaction committed, we set the XMAX_COMMITTED
* hint bit if possible --- but beware that that may not yet be possible,
* if the transaction committed asynchronously. Hence callers should look
* only at XMAX_INVALID.
*/
static void UpdateXmaxHintBits(HeapTupleHeader tuple, Buffer buffer, TransactionId xid)
{
Assert(TransactionIdEquals(HeapTupleHeaderGetXmax(BufferGetPage(buffer), tuple), xid));
if (!(tuple->t_infomask & (HEAP_XMAX_COMMITTED | HEAP_XMAX_INVALID))) {
if (TransactionIdDidCommit(xid)) {
HeapTupleSetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, xid);
} else {
if (!LatestFetchTransactionIdDidAbort(xid)) {
LatestTransactionStatusError(xid, NULL, "UpdateXmaxHintBits set HEAP_XMAX_INVALID xid don't abort");
}
HeapTupleSetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId);
}
}
}
/*
* GetBulkInsertState - prepare status object for a bulk insert
*/
BulkInsertState GetBulkInsertState(void)
{
BulkInsertState bistate;
bistate = (BulkInsertState)palloc(sizeof(BulkInsertStateData));
bistate->strategy = GetAccessStrategy(BAS_BULKWRITE);
bistate->current_buf = InvalidBuffer;
return bistate;
}
/*
* FreeBulkInsertState - clean up after finishing a bulk insert
*/
void FreeBulkInsertState(BulkInsertState bistate)
{
if (bistate->current_buf != InvalidBuffer) {
ReleaseBuffer(bistate->current_buf);
}
FreeAccessStrategy(bistate->strategy);
pfree(bistate);
bistate = NULL;
}
/* HeapInsertCStore - insert tuple into CStore */
void HeapInsertCStore(Relation relation, ResultRelInfo* result_rel_info, HeapTuple tup, int option)
{
/* Description: deal with index */
InsertArg args;
CStoreInsert::InitInsertArg(relation, result_rel_info, false, args);
CStoreInsert cstoreInsert(relation, args, false, NULL, NULL);
TupleDesc tupDesc = relation->rd_att;
Datum* val = (Datum*)palloc(sizeof(Datum) * tupDesc->natts);
bool* null = (bool*)palloc(sizeof(bool) * tupDesc->natts);
heap_deform_tuple(tup, tupDesc, val, null);
bulkload_rows batchRow(tupDesc, RelationGetMaxBatchRows(relation), true);
/* ignore returned value because only one tuple is appended into */
(void)batchRow.append_one_tuple(val, null, tupDesc);
cstoreInsert.SetEndFlag();
cstoreInsert.BatchInsert(&batchRow, option);
pfree(val);
pfree(null);
CStoreInsert::DeInitInsertArg(args);
batchRow.Destroy();
cstoreInsert.Destroy();
}
void HeapDeleteCStore(Relation relation, ItemPointer tid, Oid table_oid, Snapshot snapshot)
{
ScalarVector rowid;
CStoreDelete csdelete(relation, NULL, false, NULL, NULL);
ScalarDesc desc;
desc.typeMod = 0;
rowid.init(CurrentMemoryContext, desc);
rowid.m_rows = 1;
rowid.m_vals[0] = 0;
ItemPointer destTid = (ItemPointer)(&rowid.m_vals[0]);
*destTid = *tid;
csdelete.ExecDelete(relation, &rowid, snapshot, table_oid);
pfree(rowid.m_flag);
pfree(rowid.m_vals);
delete rowid.m_buf;
}
/*
* heap_insert - insert tuple into a heap
*
* The new tuple is stamped with current transaction ID and the specified
* command ID.
*
* If the HEAP_INSERT_SKIP_WAL option is specified, the new tuple is not
* logged in WAL, even for a non-temp relation. Safe usage of this behavior
* requires that we arrange that all new tuples go into new pages not
* containing any tuples from other transactions, and that the relation gets
* fsync'd before commit. (See also heap_sync() comments)
*
* The HEAP_INSERT_SKIP_FSM option is passed directly to
* RelationGetBufferForTuple, which see for more info.
*
* Note that these options will be applied when inserting into the heap's
* TOAST table, too, if the tuple requires any out-of-line data.
*
* The BulkInsertState object (if any; bistate can be NULL for default
* behavior) is also just passed through to RelationGetBufferForTuple.
*
* The return value is the OID assigned to the tuple (either here or by the
* caller), or InvalidOid if no OID. The header fields of *tup are updated
* to match the stored tuple; in particular tup->t_self receives the actual
* TID where the tuple was stored. But note that any toasting of fields
* within the tuple data is NOT reflected into *tup.
*/
Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid, int options, BulkInsertState bistate)
{
TransactionId xid = GetCurrentTransactionId();
HeapTuple heaptup;
Buffer buffer;
Buffer vmbuffer = InvalidBuffer;
bool all_visible_cleared = false;
BlockNumber rel_end_block = InvalidBlockNumber;
/*
* Fill in tuple header fields, assign an OID, and toast the tuple if
* necessary.
*
* Note: below this point, heaptup is the data we actually intend to store
* into the relation; tup is the caller's original untoasted data.
*/
heaptup = heap_prepare_insert(relation, tup, cid, options);
/* All built-in functions are hard coded, and thus they should not be inserted into catalog.pg_proc */
if (!IsBootstrapProcessingMode() && u_sess->attr.attr_common.IsInplaceUpgrade == false) {
Assert(!(IsProcRelation(relation) && IsBuiltinFuncOid(HeapTupleGetOid(heaptup))));
}
/*
* We're about to do the actual insert -- but check for conflict first, to
* avoid possibly having to roll back work we've just done.
*
* For a heap insert, we only need to check for table-level SSI locks. Our
* new tuple can't possibly conflict with existing tuple locks, and heap
* page locks are only consolidated versions of tuple locks; they do not
* lock "gaps" as index page locks do. So we don't need to identify a
* buffer before making the call.
*/
CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
if (RelationInClusterResizing(relation) && !RelationInClusterResizingReadOnly(relation)) {
options |= HEAP_INSERT_SKIP_FSM;
rel_end_block = RelationGetEndBlock(relation);
}
/*
* Find buffer to insert this tuple into. If the page is all visible,
* this will also pin the requisite visibility map page.
*/
buffer = RelationGetBufferForTuple(
relation, heaptup->t_len, InvalidBuffer, options, bistate, &vmbuffer, NULL, rel_end_block);
(void)heap_page_prepare_for_xid(relation, buffer, xid, false);
HeapTupleCopyBaseFromPage(heaptup, BufferGetPage(buffer));
/* NO EREPORT(ERROR) from here till changes are logged */
START_CRIT_SECTION();
RelationPutHeapTuple(relation, buffer, heaptup, xid);
if (PageIsAllVisible(BufferGetPage(buffer))) {
all_visible_cleared = true;
PageClearAllVisible(BufferGetPage(buffer));
visibilitymap_clear(relation, ItemPointerGetBlockNumber(&(heaptup->t_self)), vmbuffer);
}
/*
* XXX Should we set PageSetPrunable on this page ?
*
* The inserting transaction may eventually abort thus making this tuple
* DEAD and hence available for pruning. Though we don't want to optimize
* for aborts, if no other tuple in this page is UPDATEd/DELETEd, the
* aborted tuple will never be pruned until next vacuum is triggered.
*
* If you do add PageSetPrunable here, add it in heap_xlog_insert too.
*/
MarkBufferDirty(buffer);
/* XLOG stuff */
if (!(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation)) {
xl_heap_insert xlrec;
xl_heap_header xlhdr;
XLogRecPtr recptr;
Page page = BufferGetPage(buffer);
uint8 info = XLOG_HEAP_INSERT;
int bufflags = 0;
/*
* If this is a catalog, we need to transmit combocids to properly
* decode, so log that as well.
*/
if (RelationIsAccessibleInLogicalDecoding(relation)) {
(void)log_heap_new_cid(relation, heaptup);
}
/*
* If this is the single and first tuple on page, we can reinit the
* page instead of restoring the whole thing. Set flag, and hide
* buffer references from XLogInsert. Moreover, if page is already
* compressed, should not init page, or lead to inconsistency.
*/
if (ItemPointerGetOffsetNumber(&(heaptup->t_self)) == FirstOffsetNumber &&
PageGetMaxOffsetNumber(page) == FirstOffsetNumber &&
!PageIsCompressed(page)) {
info |= XLOG_HEAP_INIT_PAGE;
bufflags |= REGBUF_WILL_INIT;
}
xlrec.offnum = ItemPointerGetOffsetNumber(&heaptup->t_self);
xlrec.flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0;
Assert(ItemPointerGetBlockNumber(&heaptup->t_self) == BufferGetBlockNumber(buffer));
/*
* For logical decoding, we need the tuple even if we're doing a full
* page write, so make sure it's included even if we take a full-page
* image. (XXX We could alternatively store a pointer into the FPW).
*/
if (RelationIsLogicallyLogged(relation)) {
xlrec.flags |= XLOG_HEAP_CONTAINS_NEW_TUPLE;
bufflags |= REGBUF_KEEP_DATA;
}
XLogBeginInsert();
if (info & XLOG_HEAP_INIT_PAGE) {
XLogRegisterData((char*)&((HeapPageHeader)(page))->pd_xid_base, sizeof(TransactionId));
}
XLogRegisterData((char*)&xlrec, SizeOfHeapInsert);
xlhdr.t_infomask2 = heaptup->t_data->t_infomask2;
xlhdr.t_infomask = heaptup->t_data->t_infomask;
xlhdr.t_hoff = heaptup->t_data->t_hoff;
/*
* note we mark xlhdr as belonging to buffer; if XLogInsert decides to
* write the whole page to the xlog, we don't need to store
* xl_heap_header in the xlog.
*/
XLogRegisterBuffer(0, buffer, REGBUF_STANDARD | bufflags);
XLogRegisterBufData(0, (char*)&xlhdr, SizeOfHeapHeader);
/* PG73FORMAT: write bitmap [+ padding] [+ oid] + data */
XLogRegisterBufData(0,
(char*)heaptup->t_data + offsetof(HeapTupleHeaderData, t_bits),
heaptup->t_len - offsetof(HeapTupleHeaderData, t_bits));
/* filtering by origin on a row level is much more efficient */
XLogIncludeOrigin();
recptr = XLogInsert(RM_HEAP_ID, info);
PageSetLSN(page, recptr);
}
END_CRIT_SECTION();
UnlockReleaseBuffer(buffer);
if (vmbuffer != InvalidBuffer) {
ReleaseBuffer(vmbuffer);
}
/*
* If tuple is cachable, mark it for invalidation from the caches in case
* we abort. Note it is OK to do this after releasing the buffer, because
* the heaptup data structure is all in local memory, not in the shared
* buffer.
*/
CacheInvalidateHeapTuple(relation, heaptup, NULL);
pgstat_count_heap_insert(relation, 1);
/*
* If heaptup is a private copy, release it. Don't forget to copy t_self
* back to the caller's image, too.
*/
if (heaptup != tup) {
tup->t_self = heaptup->t_self;
heap_freetuple(heaptup);
}
return HeapTupleGetOid(tup);
}
/**
* @Description: Find minimum and maximum short transaction ids which occurs in the page.
* @in: page, heap page
* @in: multi, Whether multixact
* @out: min, minimum short transaction ids which occurs in the page.
* @out: max, maximum short transaction ids which occurs in the page.
* @return: Whether the minimum and maximum short transaction ids are found
*/
static bool heap_page_xid_min_max(Page page, bool multi, ShortTransactionId* min, ShortTransactionId* max)
{
bool found = false;
OffsetNumber offnum = InvalidOffsetNumber;
OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
for (offnum = FirstOffsetNumber; offnum <= maxoff; offnum = OffsetNumberNext(offnum)) {
ItemId itemid;
HeapTupleHeader htup;
itemid = PageGetItemId(page, offnum);
if (!ItemIdIsNormal(itemid)) {
continue;
}
htup = (HeapTupleHeader)PageGetItem(page, itemid);
if (!multi) {
if (!HeapTupleHeaderXminFrozen(htup) && TransactionIdIsNormal(htup->t_choice.t_heap.t_xmin)) {
if (!found) {
*min = *max = htup->t_choice.t_heap.t_xmin;
found = true;
} else {
*min = Min(*min, htup->t_choice.t_heap.t_xmin);
*max = Max(*max, htup->t_choice.t_heap.t_xmin);
}
}
if (TransactionIdIsNormal(htup->t_choice.t_heap.t_xmax) && !(htup->t_infomask & HEAP_XMAX_IS_MULTI)) {
if (!found) {
*min = *max = htup->t_choice.t_heap.t_xmax;
found = true;
} else {
*min = Min(*min, htup->t_choice.t_heap.t_xmax);
*max = Max(*max, htup->t_choice.t_heap.t_xmax);
}
}
} else {
if (TransactionIdIsNormal(htup->t_choice.t_heap.t_xmax) && (htup->t_infomask & HEAP_XMAX_IS_MULTI)) {
if (!found) {
*min = *max = htup->t_choice.t_heap.t_xmax;
found = true;
} else {
*min = Min(*min, htup->t_choice.t_heap.t_xmax);
*max = Max(*max, htup->t_choice.t_heap.t_xmax);
}
}
}
}
return found;
}
/*
* Shift xid base in the page. WAL-logged if buffer is specified.
* page is the heap page; delta is the size of change about xid base
*/
static void heap_page_shift_base(Buffer buffer, Page page, bool multi, int64 delta)
{
HeapPageHeader phdr = (HeapPageHeader)page;
OffsetNumber offnum, maxoff;
/* base left shift, mininum is 0 */
if (delta < 0) {
if (!multi) {
if ((int64)(phdr->pd_xid_base + delta) < 0) {
delta = -(int64)(phdr->pd_xid_base);
}
} else {
if ((int64)(phdr->pd_multi_base + delta) < 0) {
delta = -(int64)(phdr->pd_multi_base);
}
}
}
/* Iterate over page items */
maxoff = PageGetMaxOffsetNumber(page);
for (offnum = FirstOffsetNumber; offnum <= maxoff; offnum = OffsetNumberNext(offnum)) {
ItemId itemid;
HeapTupleHeader htup;
itemid = PageGetItemId(page, offnum);
if (!ItemIdIsNormal(itemid)) {
continue;
}
htup = (HeapTupleHeader)PageGetItem(page, itemid);
/* Apply xid shift to heap tuple */
if (!multi) {
if (!HeapTupleHeaderXminFrozen(htup) && TransactionIdIsNormal(htup->t_choice.t_heap.t_xmin)) {
Assert((uint32)(htup->t_choice.t_heap.t_xmin - delta) >= FirstNormalTransactionId);
Assert((uint32)(htup->t_choice.t_heap.t_xmin - delta) <= MaxShortTransactionId);
htup->t_choice.t_heap.t_xmin -= delta;
}
if (TransactionIdIsNormal(htup->t_choice.t_heap.t_xmax) && !(htup->t_infomask & HEAP_XMAX_IS_MULTI)) {
Assert((uint32)(htup->t_choice.t_heap.t_xmax - delta) >= FirstNormalTransactionId);
Assert((uint32)(htup->t_choice.t_heap.t_xmax - delta) <= MaxShortTransactionId);
htup->t_choice.t_heap.t_xmax -= delta;
}
} else {
if (TransactionIdIsNormal(htup->t_choice.t_heap.t_xmax) && (htup->t_infomask & HEAP_XMAX_IS_MULTI)) {
Assert((uint32)(htup->t_choice.t_heap.t_xmax - delta) >= FirstNormalTransactionId);
Assert((uint32)(htup->t_choice.t_heap.t_xmax - delta) <= MaxShortTransactionId);
htup->t_choice.t_heap.t_xmax -= delta;
}
}
}
/* Apply xid shift to base as well */
if (!multi) {
phdr->pd_xid_base += delta;
} else {
phdr->pd_multi_base += delta;
}
ereport(DEBUG1, (errmsg("The page xid_base has changed to %lu ", phdr->pd_xid_base)));
/* Write WAL record if needed */
if (BufferIsValid(buffer)) {
XLogRecPtr recptr;
xl_heap_base_shift xlrec;
START_CRIT_SECTION();
MarkBufferDirty(buffer);
xlrec.multi = multi;
xlrec.delta = delta;
XLogBeginInsert();
XLogRegisterData((char*)&xlrec, SizeOfHeapBaseShift);
XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_BASE_SHIFT);
PageSetLSN(page, recptr);
END_CRIT_SECTION();
}
}
/*
* Freeze xids in the single heap page. Useful when we can't fit new xid even
* with base shift.
* @return: nfrozen - the number of tuples successfully frozen.
*/
static int freeze_single_heap_page(Relation relation, Buffer buffer)
{
Page page = BufferGetPage(buffer);
OffsetNumber offnum = InvalidOffsetNumber;
OffsetNumber maxoff = InvalidOffsetNumber;
HeapTupleData tuple;
int nfrozen = 0;
OffsetNumber frozen[MaxOffsetNumber];
TransactionId latest_removed_xid = InvalidTransactionId;
TransactionId oldest_xmin = InvalidTransactionId;
TransactionId freeze_xid = InvalidTransactionId;
bool useLocalSnapshot_change = false;
gstrace_entry(GS_TRC_ID_freeze_single_heap_page);
vacuum_set_xid_limits(relation, 0, 0, &oldest_xmin, &freeze_xid, NULL);
/* since xid_base must be adjusted, heap_page_prune needs to be done,
* so t_thrd.xact_cxt.useLocalSnapshot should be set to false
*/
if (t_thrd.xact_cxt.useLocalSnapshot) {
t_thrd.xact_cxt.useLocalSnapshot = false;
useLocalSnapshot_change = true;
if (TransactionIdIsNormal(t_thrd.xact_cxt.ShmemVariableCache->recentGlobalXmin)) {
oldest_xmin = t_thrd.xact_cxt.ShmemVariableCache->recentGlobalXmin;
if (TransactionIdIsNormal(t_thrd.xact_cxt.ShmemVariableCache->recentLocalXmin) &&
oldest_xmin > t_thrd.xact_cxt.ShmemVariableCache->recentLocalXmin) {
oldest_xmin = t_thrd.xact_cxt.ShmemVariableCache->recentLocalXmin;
}
} else if (TransactionIdIsNormal(t_thrd.xact_cxt.ShmemVariableCache->recentLocalXmin)) {
oldest_xmin = t_thrd.xact_cxt.ShmemVariableCache->recentLocalXmin;
}
if (oldest_xmin <= FirstNormalTransactionId + u_sess->attr.attr_storage.vacuum_defer_cleanup_age) {
oldest_xmin = FirstNormalTransactionId;
} else {
oldest_xmin -= u_sess->attr.attr_storage.vacuum_defer_cleanup_age;
}
freeze_xid = oldest_xmin;
ereport(LOG,
(errmsg("Set useLocalSnapshot to false to force the prune page and then adjust the xid_base. relation is "
"\"%s\", oldest_xmin is %lu",
RelationGetRelationName(relation),
oldest_xmin)));
}
(void)heap_page_prune(relation, buffer, oldest_xmin, false, &latest_removed_xid, false);
if (useLocalSnapshot_change) {
t_thrd.xact_cxt.useLocalSnapshot = true;
useLocalSnapshot_change = false;
}
/*
* Now scan the page to collect vacuumable items and check for tuples
* requiring freezing.
*/
maxoff = PageGetMaxOffsetNumber(page);
/*
* Note: If you change anything in the loop below, also look at
* heap_page_is_all_visible to see if that needs to be changed.
*/
for (offnum = FirstOffsetNumber; offnum <= maxoff; offnum = OffsetNumberNext(offnum)) {
ItemId itemid = PageGetItemId(page, offnum);
if (!ItemIdIsNormal(itemid)) {
continue;
}
tuple.t_data = (HeapTupleHeader)PageGetItem(page, itemid);
tuple.t_len = ItemIdGetLength(itemid);
tuple.t_tableOid = RelationGetRelid(relation);
tuple.t_bucketId = RelationGetBktid(relation);
HeapTupleCopyBaseFromPage(&tuple, page);
/*
* Each non-removable tuple must be checked to see if it needs
* freezing. Note we already have exclusive buffer lock.
*/
if (heap_freeze_tuple(&tuple, freeze_xid)) {
frozen[nfrozen++] = offnum;
}
} /* scan along page */
/*
* If we froze any tuples, mark the buffer dirty, and write a WAL
* record recording the changes. We must log the changes to be
* crash-safe against future truncation of CLOG.
*/
if (nfrozen > 0) {
START_CRIT_SECTION();
MarkBufferDirty(buffer);
/* Now WAL-log freezing if necessary */
if (RelationNeedsWAL(relation)) {
XLogRecPtr recptr = log_heap_freeze(relation, buffer, freeze_xid, frozen, nfrozen);
PageSetLSN(page, recptr);
}
END_CRIT_SECTION();
}
gstrace_exit(GS_TRC_ID_freeze_single_heap_page);
return nfrozen;
}
/*
* Ensure that given xid fits base of given page.
*/
bool heap_page_prepare_for_xid(Relation relation, Buffer buffer, TransactionId xid, bool multi, bool page_replication)
{
Page page = BufferGetPage(buffer);
HeapPageHeader phdr;
TransactionId base = 0;
bool found = false;
ShortTransactionId min = 0;
ShortTransactionId max = 0;
int i;
bool need_wal = false;
if (!TransactionIdIsNormal(xid) || !PageIs8BXidHeapVersion(page)) {
return false;
}
gstrace_entry(GS_TRC_ID_heap_page_prepare_for_xid);
need_wal = page_replication ? false : RelationNeedsWAL(relation);
/*
* if the first change to pd_xid_base or pd_multi_base fails ,
* will attempt to freeze this page.
*/
phdr = (HeapPageHeader)page;
for (i = 0; i < 2; i++) {
if (!multi) {
base = phdr->pd_xid_base;
} else {
base = phdr->pd_multi_base;
}
/* Can we already store this xid? */
if (xid >= base + FirstNormalTransactionId && xid <= base + MaxShortTransactionId) {
gstrace_exit(GS_TRC_ID_heap_page_prepare_for_xid);
return false;
}
/* Find minimum and maximum xids in the page */
found = heap_page_xid_min_max(page, multi, &min, &max);
/* No items on the page? */
if (!found) {
int64 delta;
if (!multi) {
delta = (xid - FirstNormalTransactionId) - phdr->pd_xid_base;
} else {
delta = (xid - FirstNormalTransactionId) - phdr->pd_multi_base;
}
heap_page_shift_base(need_wal ? buffer : InvalidBuffer, page, multi, delta);
MarkBufferDirty(buffer);
gstrace_exit(GS_TRC_ID_heap_page_prepare_for_xid);
return false;
}
/* Can we just shift base on the page */
if (xid < base + FirstNormalTransactionId) {
int64 free_delta = MaxShortTransactionId - max;
int64 required_delta = (base + FirstNormalTransactionId) - xid;
if (required_delta <= free_delta) {
heap_page_shift_base(need_wal ? buffer : InvalidBuffer, page, multi, -(free_delta + required_delta) / 2);
MarkBufferDirty(buffer);
gstrace_exit(GS_TRC_ID_heap_page_prepare_for_xid);
return true;
}
} else {
int64 free_delta = min - FirstNormalTransactionId;
int64 required_delta = xid - (base + MaxShortTransactionId);
if (required_delta <= free_delta) {
heap_page_shift_base(need_wal ? buffer : InvalidBuffer, page, multi, (free_delta + required_delta) / 2);
MarkBufferDirty(buffer);
gstrace_exit(GS_TRC_ID_heap_page_prepare_for_xid);
return true;
}
}
if (i == 1) {
break;
}
/* Have to try freeing the page... */
(void)freeze_single_heap_page(relation, buffer);
}
if (BufferIsValid(buffer)) {
UnlockReleaseBuffer(buffer);
}
ereport(ERROR,
(errcode(ERRCODE_CANNOT_MODIFY_XIDBASE),
errmsg("Can't fit xid into page. relation \"%s\", now xid is %lu, base is %lu, min is %u, max is %u",
RelationGetRelationName(relation),
xid,
base,
min,
max)));
gstrace_exit(GS_TRC_ID_heap_page_prepare_for_xid);
return false;
}
/**
* @Description: It is used to optimize xid_base adjustment, Freeze a page and readjust
* xid_base to avoid the performance degradation during write transaction.
* @in: Relation
* @in: Buffer
*/
bool heap_change_xidbase_after_freeze(Relation relation, Buffer buffer)
{
Page page = BufferGetPage(buffer);
HeapPageHeader phdr = (HeapPageHeader)page;
TransactionId base = phdr->pd_xid_base;
TransactionId xid = u_sess->utils_cxt.RecentXmin;
bool found = false;
ShortTransactionId min = 0;
ShortTransactionId max = 0;
gstrace_entry(GS_TRC_ID_heap_change_xidbase_after_freeze);
/* Find minimum and maximum xids in the page */
found = heap_page_xid_min_max(page, false, &min, &max);
/* No items on the page? */
if (!found) {
int64 delta;
delta = (xid - FirstNormalTransactionId) - phdr->pd_xid_base;
heap_page_shift_base(RelationNeedsWAL(relation) ? buffer : InvalidBuffer, page, false, delta);
MarkBufferDirty(buffer);
gstrace_exit(GS_TRC_ID_heap_change_xidbase_after_freeze);
return false;
}
if (u_sess->utils_cxt.RecentXmin < base + MaxShortTransactionId) {
TransactionId xidmin = u_sess->utils_cxt.RecentXmin > min ? min : u_sess->utils_cxt.RecentXmin;
heap_page_shift_base(
RelationNeedsWAL(relation) ? buffer : InvalidBuffer, page, false, xidmin - FirstNormalTransactionId);
MarkBufferDirty(buffer);
gstrace_exit(GS_TRC_ID_heap_change_xidbase_after_freeze);
return true;
}
int64 free_delta = min - FirstNormalTransactionId;
int64 required_delta = xid - (base + MaxShortTransactionId);
if (required_delta <= free_delta) {
heap_page_shift_base(
RelationNeedsWAL(relation) ? buffer : InvalidBuffer, page, false, (free_delta + required_delta) / 2);
MarkBufferDirty(buffer);
gstrace_exit(GS_TRC_ID_heap_change_xidbase_after_freeze);
return true;
}
ereport(LOG,
(errmsg("Can't fit RecentXmin into page after freeze. relation \"%s\", now xid is %lu, base is %lu, min is %u, "
"max is %u",
RelationGetRelationName(relation),
xid,
base,
min,
max)));
gstrace_exit(GS_TRC_ID_heap_change_xidbase_after_freeze);
return false;
}
/*
* Ensure that given xid fits base of given page.
*/
bool rewrite_page_prepare_for_xid(Page page, TransactionId xid, bool multi)
{
HeapPageHeader phdr = (HeapPageHeader)page;
TransactionId base = 0;
bool found = false;
ShortTransactionId min = 0;
ShortTransactionId max = 0;
if (!TransactionIdIsNormal(xid)) {
return false;
}
if (!multi) {
base = phdr->pd_xid_base;
} else {
base = phdr->pd_multi_base;
}
/* Can we already store this xid? */
if (xid >= base + FirstNormalTransactionId && xid <= base + MaxShortTransactionId) {
return false;
}
/* Find minimum and maximum xids in the page */
found = heap_page_xid_min_max(page, multi, &min, &max);
/* No items on the page? */
if (!found) {
if (!multi) {
phdr->pd_xid_base = xid - FirstNormalTransactionId;
} else {
phdr->pd_multi_base = xid - FirstNormalTransactionId;
}
return false;
}
ereport(DEBUG1, (errmsg("The minimum value of xid in TupleHeader is %u and the maximum value is %u", min, max)));
/* Can we just shift base on the page */
if (xid < base + FirstNormalTransactionId) {
int64 free_delta = MaxShortTransactionId - max;
int64 required_delta = (base + FirstNormalTransactionId) - xid;
if (required_delta <= free_delta) {
heap_page_shift_base(InvalidBuffer, page, multi, -(free_delta + required_delta) / 2);
return true;
}
} else {
int64 free_delta = min - FirstNormalTransactionId;
int64 required_delta = xid - (base + MaxShortTransactionId);
if (required_delta <= free_delta) {
heap_page_shift_base(InvalidBuffer, page, multi, (free_delta + required_delta) / 2);
return true;
}
}
ereport(ERROR,
(errcode(ERRCODE_CANNOT_MODIFY_XIDBASE),
errmsg("Can't fit xid into page, now xid is %lu, base is %lu, min is %u, max is %u", xid, base, min, max)));
return false;
}
/* ----------------
* heap_markpos - mark scan position
* ----------------
*/
void heap_markpos(HeapScanDesc scan)
{
/* Note: no locking manipulations needed */
if (scan->rs_ctup.t_data != NULL) {
scan->rs_mctid = scan->rs_ctup.t_self;
if (scan->rs_pageatatime) {
scan->rs_mindex = scan->rs_cindex;
}
} else
ItemPointerSetInvalid(&scan->rs_mctid);
}
/*
* Subroutine for heap_insert(). Prepares a tuple for insertion. This sets the
* tuple header fields, assigns an OID, and toasts the tuple if necessary.
* Returns a toasted version of the tuple if it was toasted, or the original
* tuple if not. Note that in any case, the header fields are also set in
* the original tuple.
*/
static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup, CommandId cid, int options)
{
if (relation->rd_rel->relhasoids) {
#ifdef NOT_USED
/* this is redundant with an Assert in HeapTupleSetOid */
Assert(tup->t_data->t_infomask & HEAP_HASOID);
#endif
/* For catalogs that do not support DDL, we set new object oids here. */
if (u_sess->attr.attr_common.IsInplaceUpgrade && OidIsValid(u_sess->upg_cxt.Inplace_upgrade_next_general_oid)) {
HeapTupleSetOid(tup, u_sess->upg_cxt.Inplace_upgrade_next_general_oid);
u_sess->upg_cxt.Inplace_upgrade_next_general_oid = InvalidOid;
}
/*
* If the object id of this tuple has already been assigned, trust the
* caller. There are a couple of ways this can happen. At initial db
* creation, the backend program sets oids for tuples. When we define
* an index, we set the oid. Finally, in the future, we may allow
* users to set their own object ids in order to support a persistent
* object store (objects need to contain pointers to one another).
*/
if (!OidIsValid(HeapTupleGetOid(tup))) {
HeapTupleSetOid(tup, GetNewOid(relation));
}
} else {
/* check there is not space for an OID */
Assert(!(tup->t_data->t_infomask & HEAP_HASOID));
}
tup->t_data->t_infomask &= ~(HEAP_XACT_MASK);
tup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK);
tup->t_data->t_infomask |= HEAP_XMAX_INVALID;
HeapTupleSetXmin(tup, InvalidTransactionId);
if (options & HEAP_INSERT_FROZEN) {
HeapTupleHeaderSetXminFrozen(tup->t_data);
}
HeapTupleHeaderSetCmin(tup->t_data, cid);
HeapTupleSetXmax(tup, 0); /* for cleanliness */
tup->t_tableOid = RelationGetRelid(relation);
tup->t_bucketId = RelationGetBktid(relation);
#ifdef PGXC
tup->t_xc_node_id = u_sess->pgxc_cxt.PGXCNodeIdentifier;
#endif
if (RelationIsRedistributeDest(relation)) {
HeapTupleHeaderSetRedisColumns(tup->t_data);
}
/*
* If the new tuple is too big for storage or contains already toasted
* out-of-line attributes from some other relation, invoke the toaster.
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_MATVIEW) {
/* toast table entries should never be recursively toasted */
Assert(!HeapTupleHasExternal(tup));
return tup;
} else if (HeapTupleHasExternal(tup) || tup->t_len > TOAST_TUPLE_THRESHOLD) {
return toast_insert_or_update(relation, tup, NULL, options, NULL);
} else {
return tup;
}
}
/*
* heap_multi_insert - insert multiple tuple into a heap
*
* This is like heap_insert(), but inserts multiple tuples in one operation.
* That's faster than calling heap_insert() in a loop, because when multiple
* tuples can be inserted on a single page, we can write just a single WAL
* record covering all of them, and only need to lock/unlock the page once.
*
* Note: this leaks memory into the current memory context. You can create a
* temporary context before calling this, if that's a problem.
*/
int heap_multi_insert(Relation relation, Relation parent, HeapTuple* tuples, int ntuples, CommandId cid, int options,
BulkInsertState bistate, HeapMultiInsertExtraArgs* args)
{
TransactionId xid = GetCurrentTransactionId();
HeapTuple* heap_tuples = NULL;
const char* cmprs_data = args->dictData;
int cmpr_size = args->dictSize;
int i;
int ndone;
char* scratch = NULL;
Page page;
bool needwal = false;
bool is_compressed = (cmpr_size != 0);
Size save_free_space;
BlockNumber rel_end_block = InvalidBlockNumber;
bool need_tuple_data = RelationIsLogicallyLogged(relation);
bool need_cids = RelationIsAccessibleInLogicalDecoding(relation);
/* 1. heap bcm-based data replication feature is enable
* 2. caller doesn't forbid the feature
* 3. normal process but not initing cluster
* 4. normal relation but not catalog relation
* 5. due to index has no visibility, so relation has index
* do not use data replication
*/
bool page_replication = enable_heap_bcm_data_replication() && !args->disablePageReplication && !IsInitdb &&
(RelationGetRelid(relation) >= FirstNormalObjectId) && !RelationGetIndexNum(parent);
errno_t rc = EOK;
BlockNumber blockNum; /* blknum relative to begin of reln */
CHECK_FOR_INTERRUPTS();
if (page_replication) {
/* We palloc BCMElementArray before START_CRIT_SECTION,
if palloc failed, not core but ereport */
PallocBCMBCMElementArray();
}
Assert(!is_compressed || (cmprs_data != NULL));
needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
if (RelationInClusterResizing(relation) && !RelationInClusterResizingReadOnly(relation)) {
options |= HEAP_INSERT_SKIP_FSM;
rel_end_block = RelationGetEndBlock(relation);
}
save_free_space = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR);
/* Toast and set header data in all the tuples */
heap_tuples = (HeapTupleData**)palloc(ntuples * sizeof(HeapTuple));
for (i = 0; i < ntuples; i++)
heap_tuples[i] = heap_prepare_insert(relation, tuples[i], cid, options);
/*
* Allocate some memory to use for constructing the WAL record. Using
* palloc() within a critical section is not safe, so we allocate this
* beforehand.
*/
if (needwal) {
scratch = (char*)palloc(BLCKSZ);
}
/*
* We're about to do the actual inserts -- but check for conflict first,
* to avoid possibly having to roll back work we've just done.
*
* For a heap insert, we only need to check for table-level SSI locks. Our
* new tuple can't possibly conflict with existing tuple locks, and heap
* page locks are only consolidated versions of tuple locks; they do not
* lock "gaps" as index page locks do. So we don't need to identify a
* buffer before making the call.
*/
CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
ndone = 0;
while (ndone < ntuples) {
Buffer buffer = InvalidBuffer;
Buffer vmbuffer = InvalidBuffer;
bool all_visible_cleared = false;
int nthispage;
bool tmpPageReplication = false;
if (GetDelayXlogRecycle()) {
tmpPageReplication = false;
} else {
tmpPageReplication = page_replication;
}
if (tmpPageReplication) {
(void)LWLockAcquire(RowPageReplicationLock, LW_SHARED);
if (GetDelayXlogRecycle()) {
tmpPageReplication = false;
LWLockRelease(RowPageReplicationLock);
}
}
/* IO collector and IO scheduler */
if (ENABLE_WORKLOAD_CONTROL) {
IOSchedulerAndUpdate(IO_TYPE_WRITE, 1, IO_TYPE_ROW);
}
if (is_compressed) {
buffer = RelationGetNewBufferForBulkInsert(relation, heap_tuples[ndone]->t_len, cmpr_size, bistate);
page = BufferGetPage(buffer);
PageReinitWithDict(page, cmpr_size);
} else {
if (tmpPageReplication) {
/* Get new page and exclusive */
buffer = RelationGetNewBufferForBulkInsert(relation, heap_tuples[ndone]->t_len, cmpr_size, bistate);
} else {
/*
* Find buffer where at least the next tuple will fit. If the page is
* all-visible, this will also pin the requisite visibility map page.
*/
buffer = RelationGetBufferForTuple(relation,
heap_tuples[ndone]->t_len,
InvalidBuffer,
options,
bistate,
&vmbuffer,
NULL,
rel_end_block);
}
}
page = BufferGetPage(buffer);
(void)heap_page_prepare_for_xid(relation, buffer, xid, false, page_replication);
/* NO EREPORT(ERROR) from here till changes are logged */
START_CRIT_SECTION();
HeapTupleCopyBaseFromPage(heap_tuples[ndone], BufferGetPage(buffer));
/* write Page Dictionary data before any tuple is written. */
if (is_compressed) {
rc = memcpy_s((char*)getPageDict(page), (Size)PageGetSpecialSize(page), cmprs_data, cmpr_size);
securec_check(rc, "\0", "\0");
}
/*
* RelationGetBufferForTuple has ensured that the first tuple fits.
* Put that on the page, and then as many other tuples as fit.
*/
RelationPutHeapTuple(relation, buffer, heap_tuples[ndone], xid);
for (nthispage = 1; ndone + nthispage < ntuples; nthispage++) {
HeapTuple heaptup = heap_tuples[ndone + nthispage];
if (PageGetHeapFreeSpace(page) < MAXALIGN(heaptup->t_len) + save_free_space) {
break;
}
HeapTupleCopyBaseFromPage(heaptup, BufferGetPage(buffer));
RelationPutHeapTuple(relation, buffer, heaptup, xid);
/*
* We don't use heap_multi_insert for catalog tuples yet, but
* better be prepared...
*/
if (needwal && need_cids) {
(void)log_heap_new_cid(relation, heaptup);
}
}
if (PageIsAllVisible(page)) {
all_visible_cleared = true;
PageClearAllVisible(page);
visibilitymap_clear(relation, BufferGetBlockNumber(buffer), vmbuffer);
}
/*
* XXX Should we set PageSetPrunable on this page ? See heap_insert()
*/
MarkBufferDirty(buffer);
/* XLOG stuff */
if (needwal && !tmpPageReplication) {
XLogRecPtr recptr;
xl_heap_multi_insert* xlrec = NULL;
uint8 info = XLOG_HEAP2_MULTI_INSERT;
char* tuple_data = NULL;
int total_data_len;
char* scratchptr = scratch;
bool init = false;
int bufflags = 0;
OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
/*
* If the page was previously empty, we can reinit the page
* instead of restoring the whole thing. Moreover, if page is already
* compressed, should not init page, or lead to inconsistency.
*/
init = (ItemPointerGetOffsetNumber(&(heap_tuples[ndone]->t_self)) == FirstOffsetNumber &&
maxoff == FirstOffsetNumber + nthispage - 1 &&
(is_compressed || !PageIsCompressed(page)));
/* allocate xl_heap_multi_insert struct from the scratch area */
xlrec = (xl_heap_multi_insert*)scratchptr;
scratchptr += SizeOfHeapMultiInsert;
/*
* Allocate offsets array. Unless we're reinitializing the page,
* in that case the tuples are stored in order starting at
* FirstOffsetNumber and we don't need to store the offsets
* explicitly.
*/
if (!init) {
scratchptr += nthispage * sizeof(OffsetNumber);
}
/* the rest of the scratch space is used for tuple data */
tuple_data = scratchptr;
xlrec->flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0;
xlrec->ntuples = nthispage;
/* xlog: write the dictionary between header and tuples */
xlrec->isCompressed = is_compressed;
if (xlrec->isCompressed) {
/* PageDictHeaderData should be 2B aligned. */
char* cmprsMeta = (char*)scratchptr;
/* first write the size of this compression meta area. */
*((int16*)cmprsMeta) = cmpr_size;
cmprsMeta += sizeof(int16);
/* then copy all the compression data. */
rc = memcpy_s(cmprsMeta, cmpr_size, cmprs_data, cmpr_size);
securec_check(rc, "\0", "\0");
scratchptr = cmprsMeta + cmpr_size;
}
/*
* Write out an xl_multi_insert_tuple and the tuple data itself
* for each tuple.
*/
for (i = 0; i < nthispage; i++) {
HeapTuple heaptup = heap_tuples[ndone + i];
xl_multi_insert_tuple* tuphdr = NULL;
int datalen;
if (!init) {
xlrec->offsets[i] = ItemPointerGetOffsetNumber(&heaptup->t_self);
}
/* xl_multi_insert_tuple needs two-byte alignment. */
tuphdr = (xl_multi_insert_tuple*)scratchptr;
scratchptr = ((char*)tuphdr) + SizeOfMultiInsertTuple;
tuphdr->t_infomask2 = heaptup->t_data->t_infomask2;
tuphdr->t_infomask = heaptup->t_data->t_infomask;
tuphdr->t_hoff = heaptup->t_data->t_hoff;
/* write bitmap [+ padding] [+ oid] + data */
datalen = heaptup->t_len - offsetof(HeapTupleHeaderData, t_bits);
rc = memcpy_s(
scratchptr, BLCKSZ, (char*)heaptup->t_data + offsetof(HeapTupleHeaderData, t_bits), datalen);
securec_check(rc, "\0", "\0");
tuphdr->datalen = datalen;
scratchptr += datalen;
}
total_data_len = scratchptr - tuple_data;
Assert((scratchptr - scratch) < BLCKSZ);
if (need_tuple_data) {
xlrec->flags |= XLOG_HEAP_CONTAINS_NEW_TUPLE;
}
/*
* Signal that this is the last xl_heap_multi_insert record
* emitted by this call to heap_multi_insert(). Needed for logical
* decoding so it knows when to cleanup temporary data.
*/
if (ndone + nthispage == ntuples) {
xlrec->flags |= XLOG_HEAP_LAST_MULTI_INSERT;
}
/*
* If we're going to reinitialize the whole page using the WAL
* record, hide buffer reference from XLogInsert.
*/
if (init) {
info |= XLOG_HEAP_INIT_PAGE;
bufflags |= REGBUF_WILL_INIT;
}
/*
* If we're doing logical decoding, include the new tuple data
* even if we take a full-page image of the page.
*/
if (need_tuple_data) {
bufflags |= REGBUF_KEEP_DATA;
}
XLogBeginInsert();
if (info & XLOG_HEAP_INIT_PAGE) {
XLogRegisterData((char*)&((HeapPageHeader)(page))->pd_xid_base, sizeof(TransactionId));
}
XLogRegisterData((char*)xlrec, tuple_data - scratch);
XLogRegisterBuffer(0, buffer, REGBUF_STANDARD | bufflags);
XLogRegisterBufData(0, tuple_data, total_data_len);
/* filtering by origin on a row level is much more efficient */
XLogIncludeOrigin();
recptr = XLogInsert(RM_HEAP2_ID, info);
PageSetLSN(page, recptr);
}
END_CRIT_SECTION();
if (RelationNeedsWAL(relation) && tmpPageReplication) {
BlockNumber blkno = BufferGetBlockNumber(buffer);
log_logical_newpage(&relation->rd_node, MAIN_FORKNUM, blkno, page, buffer);
PushHeapPageToDataQueue(buffer);
}
blockNum = BufferGetBlockNumber(buffer);
UnlockReleaseBuffer(buffer);
if (vmbuffer != InvalidBuffer) {
ReleaseBuffer(vmbuffer);
}
/* Set BCM status */
if (RelationNeedsWAL(relation) && tmpPageReplication) {
Buffer bcmbuffer = InvalidBuffer;
BCM_pin(relation, blockNum, &bcmbuffer);
LockBuffer(bcmbuffer, BUFFER_LOCK_EXCLUSIVE);
BCMSetStatusBit(relation, blockNum, bcmbuffer, NOTSYNCED);
UnlockReleaseBuffer(bcmbuffer);
}
ndone += nthispage;
if (tmpPageReplication) {
LWLockRelease(RowPageReplicationLock);
}
if (is_compressed) {
break;
}
}
/*
* If tuples are cachable, mark them for invalidation from the caches in
* case we abort. Note it is OK to do this after releasing the buffer,
* because the heap_tuples data structure is all in local memory, not in
* the shared buffer.
*/
if (IsSystemRelation(relation)) {
for (i = 0; i < ndone; i++)
CacheInvalidateHeapTuple(relation, heap_tuples[i], NULL);
}
/*
* Copy t_self fields back to the caller's original tuples. This does
* nothing for untoasted tuples (tuples[i] == heap_tuples[i)], but it's
* probably faster to always copy than check.
*/
for (i = 0; i < ndone; i++) {
tuples[i]->t_self = heap_tuples[i]->t_self;
}
pgstat_count_heap_insert(relation, ndone);
return ndone;
}
/*
* simple_heap_insert - insert a tuple
*
* Currently, this routine differs from heap_insert only in supplying
* a default command ID and not allowing access to the speedup options.
*
* This should be used rather than using heap_insert directly in most places
* where we are modifying system catalogs.
*/
Oid simple_heap_insert(Relation relation, HeapTuple tup)
{
return heap_insert(relation, tup, GetCurrentCommandId(true), 0, NULL);
}
/*
* heap_delete - delete a tuple
*
* NB: do not call this directly unless you are prepared to deal with
* concurrent-update conditions. Use simple_heap_delete instead.
*
* relation - table to be modified (caller must hold suitable lock)
* tid - TID of tuple to be deleted
* ctid - output parameter, used only for failure case (see below)
* update_xmax - output parameter, used only for failure case (see below)
* cid - delete command ID (used for visibility test, and stored into
* cmax if successful)
* crosscheck - if not InvalidSnapshot, also check tuple against this
* wait - true if should wait for any conflicting update to commit/abort
*
* Normal, successful return value is HeapTupleMayBeUpdated, which
* actually means we did delete it. Failure return codes are
* HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated
* (the last only possible if wait == false).
*
* In the failure cases, the routine returns the tuple's t_ctid and t_xmax.
* If t_ctid is the same as tid, the tuple was deleted; if different, the
* tuple was updated, and t_ctid is the location of the replacement tuple.
* (t_xmax is needed to verify that the replacement tuple matches.)
*/
HTSU_Result heap_delete(Relation relation, ItemPointer tid, ItemPointer ctid, TransactionId* update_xmax, CommandId cid,
Snapshot crosscheck, bool wait)
{
HTSU_Result result;
TransactionId xid = GetCurrentTransactionId();
ItemId lp;
HeapTupleData tp;
Page page;
BlockNumber block;
Buffer buffer;
Buffer vmbuffer = InvalidBuffer;
bool have_tuple_lock = false;
bool is_combo = false;
bool all_visible_cleared = false;
OffsetNumber maxoff;
HeapTuple old_key_tuple = NULL; /* replica identity of the tuple */
bool old_key_copied = false;
Assert(ItemPointerIsValid(tid));
/* Don't allow any write/lock operator in stream. */
Assert(!StreamThreadAmI());
block = ItemPointerGetBlockNumber(tid);
buffer = ReadBuffer(relation, block);
page = BufferGetPage(buffer);
/*
* Before locking the buffer, pin the visibility map page if it appears to
* be necessary. Since we haven't got the lock yet, someone else might be
* in the middle of changing this, so we'll need to recheck after we have
* the lock.
*/
if (PageIsAllVisible(page)) {
visibilitymap_pin(relation, block, &vmbuffer);
}
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
if (PageIs4BXidVersion(page)) {
(void)heap_page_upgrade(relation, buffer);
}
/*
* If we didn't pin the visibility map page and the page has become all
* visible while we were busy locking the buffer, we'll have to unlock and
* re-lock, to avoid holding the buffer lock across an I/O. That's a bit
* unfortunate, but hopefully shouldn't happen often.
*/
if (vmbuffer == InvalidBuffer && PageIsAllVisible(page)) {
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
visibilitymap_pin(relation, block, &vmbuffer);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
}
lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
maxoff = PageGetMaxOffsetNumber(page);
/* check tid */
if (maxoff < ItemPointerGetOffsetNumber(tid) || !ItemIdIsNormal(lp) || !ItemPointerIsValid(tid)) {
ereport(PANIC,
(errmsg("heap_delete: invalid tid %hu, max tid %hu, rnode[%u,%u,%u], block %u",
tid->ip_posid,
maxoff,
relation->rd_node.spcNode,
relation->rd_node.dbNode,
relation->rd_node.relNode,
block)));
}
tp.t_tableOid = RelationGetRelid(relation);
tp.t_bucketId = RelationGetBktid(relation);
tp.t_data = (HeapTupleHeader)PageGetItem(page, lp);
tp.t_len = ItemIdGetLength(lp);
tp.t_self = *tid;
HeapTupleCopyBaseFromPage(&tp, page);
l1:
result = HeapTupleSatisfiesUpdate(&tp, cid, buffer);
if (result == HeapTupleInvisible) {
UnlockReleaseBuffer(buffer);
ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("attempted to delete invisible tuple")));
} else if (result == HeapTupleBeingUpdated && wait) {
TransactionId xwait;
uint16 infomask;
/* must copy state data before unlocking buffer */
HeapTupleCopyBaseFromPage(&tp, BufferGetPage(buffer));
xwait = HeapTupleGetRawXmax(&tp);
infomask = tp.t_data->t_infomask;
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
if (!u_sess->attr.attr_common.allow_concurrent_tuple_update) {
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("abort transaction due to concurrent update")));
}
/*
* Acquire tuple lock to establish our priority for the tuple (see
* heap_lock_tuple). LockTuple will release us when we are
* next-in-line for the tuple.
*
* If we are forced to "start over" below, we keep the tuple lock;
* this arranges that we stay at the head of the line while rechecking
* tuple state.
*/
if (!have_tuple_lock) {
LockTuple(relation, &(tp.t_self), ExclusiveLock, true);
have_tuple_lock = true;
}
/*
* Sleep until concurrent transaction ends. Note that we don't care
* if the locker has an exclusive or shared lock, because we need
* exclusive.
*/
if (infomask & HEAP_XMAX_IS_MULTI) {
/* wait for multixact */
MultiXactIdWait((MultiXactId)xwait, true);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
/*
* If xwait had just locked the tuple then some other xact could
* update this tuple before we get to this point. Check for xmax
* change, and start over if so.
*/
if (!(tp.t_data->t_infomask & HEAP_XMAX_IS_MULTI) || !TransactionIdEquals(HeapTupleGetRawXmax(&tp), xwait)) {
goto l1;
}
/*
* You might think the multixact is necessarily done here, but not
* so: it could have surviving members, namely our own xact or
* other subxacts of this backend. It is legal for us to delete
* the tuple in either case, however (the latter case is
* essentially a situation of upgrading our former shared lock to
* exclusive). We don't bother changing the on-disk hint bits
* since we are about to overwrite the xmax altogether.
*/
} else {
/* wait for regular transaction to end */
XactLockTableWait(xwait, true);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
/*
* xwait is done, but if xwait had just locked the tuple then some
* other xact could update this tuple before we get to this point.
* Check for xmax change, and start over if so.
*/
if ((tp.t_data->t_infomask & HEAP_XMAX_IS_MULTI) || !TransactionIdEquals(HeapTupleGetRawXmax(&tp), xwait)) {
goto l1;
}
/* Otherwise check if it committed or aborted */
UpdateXmaxHintBits(tp.t_data, buffer, xwait);
}
/*
* We may overwrite if previous xmax aborted, or if it committed but
* only locked the tuple without updating it.
*/
if (tp.t_data->t_infomask & (HEAP_XMAX_INVALID | HEAP_IS_LOCKED)) {
result = HeapTupleMayBeUpdated;
} else {
result = HeapTupleUpdated;
}
}
if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated) {
/* Perform additional check for transaction-snapshot mode RI updates */
if (!HeapTupleSatisfiesVisibility(&tp, crosscheck, buffer)) {
result = HeapTupleUpdated;
}
}
if (result != HeapTupleMayBeUpdated) {
Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated || result == HeapTupleBeingUpdated);
Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID));
*ctid = tp.t_data->t_ctid;
*update_xmax = HeapTupleGetRawXmax(&tp);
UnlockReleaseBuffer(buffer);
if (have_tuple_lock) {
UnlockTuple(relation, &(tp.t_self), ExclusiveLock);
}
if (vmbuffer != InvalidBuffer) {
ReleaseBuffer(vmbuffer);
}
return result;
}
/*
* We're about to do the actual delete -- check for conflict first, to
* avoid possibly having to roll back work we've just done.
*/
CheckForSerializableConflictIn(relation, &tp, buffer);
/* replace cid with a combo cid if necessary */
HeapTupleHeaderAdjustCmax(tp.t_data, &cid, &is_combo, buffer);
(void)heap_page_prepare_for_xid(relation, buffer, xid, false);
HeapTupleCopyBaseFromPage(&tp, page);
/*
* Compute replica identity tuple before entering the critical section so
* we don't PANIC upon a memory allocation failure.
*/
old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, &old_key_copied);
START_CRIT_SECTION();
/*
* If this transaction commits, the tuple will become DEAD sooner or
* later. Set flag that this page is a candidate for pruning once our xid
* falls below the oldest_xmin horizon. If the transaction finally aborts,
* the subsequent page pruning will be a no-op and the hint will be
* cleared.
*/
PageSetPrunable(page, xid);
if (PageIsAllVisible(page)) {
all_visible_cleared = true;
PageClearAllVisible(page);
visibilitymap_clear(relation, BufferGetBlockNumber(buffer), vmbuffer);
}
/* store transaction information of xact deleting the tuple */
tp.t_data->t_infomask &=
~(HEAP_XMAX_COMMITTED | HEAP_XMAX_INVALID | HEAP_XMAX_IS_MULTI | HEAP_IS_LOCKED | HEAP_MOVED);
HeapTupleHeaderClearHotUpdated(tp.t_data);
HeapTupleHeaderSetXmax(page, tp.t_data, xid);
HeapTupleHeaderSetCmax(tp.t_data, cid, is_combo);
/* Make sure there is no forward chain link in t_ctid */
tp.t_data->t_ctid = tp.t_self;
MarkBufferDirty(buffer);
/* XLOG stuff */
if (RelationNeedsWAL(relation)) {
xl_heap_delete xlrec;
XLogRecPtr recptr;
xl_heap_header xlhdr;
/* For logical decode we need combocids to properly decode the catalog */
if (RelationIsAccessibleInLogicalDecoding(relation)) {
(void)log_heap_new_cid(relation, &tp);
}
xlrec.flags = all_visible_cleared ? XLOG_HEAP_ALL_VISIBLE_CLEARED : 0;
xlrec.offnum = ItemPointerGetOffsetNumber(&tp.t_self);
if (old_key_tuple != NULL) {
bool is_null = false;
char relreplident;
Relation rel = heap_open(RelationRelationId, AccessShareLock);
Oid relid = RelationIsPartition(relation) ? relation->parentId : relation->rd_id;
HeapTuple tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tuple)) {
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
errmsg("pg_class entry for relid %u vanished during ExtractReplicaIdentity", relid)));
}
Datum replident = heap_getattr(tuple, Anum_pg_class_relreplident, RelationGetDescr(rel), &is_null);
heap_close(rel, AccessShareLock);
if (is_null) {
relreplident = REPLICA_IDENTITY_NOTHING;
} else {
relreplident = CharGetDatum(replident);
}
if (relreplident == REPLICA_IDENTITY_FULL) {
xlrec.flags |= XLOG_HEAP_CONTAINS_OLD_TUPLE;
} else {
xlrec.flags |= XLOG_HEAP_CONTAINS_OLD_KEY;
}
}
XLogBeginInsert();
XLogRegisterData((char*)&xlrec, SizeOfHeapDelete);
XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
/*
* Log replica identity of the deleted tuple if there is one
*/
if (old_key_tuple != NULL) {
xlhdr.t_infomask2 = old_key_tuple->t_data->t_infomask2;
xlhdr.t_infomask = old_key_tuple->t_data->t_infomask;
xlhdr.t_hoff = old_key_tuple->t_data->t_hoff;
XLogRegisterData((char*)&xlhdr, SizeOfHeapHeader);
XLogRegisterData((char*)old_key_tuple->t_data + offsetof(HeapTupleHeaderData, t_bits),
old_key_tuple->t_len - offsetof(HeapTupleHeaderData, t_bits));
}
/* filtering by origin on a row level is much more efficient */
XLogIncludeOrigin();
recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_DELETE);
PageSetLSN(page, recptr);
}
END_CRIT_SECTION();
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
if (vmbuffer != InvalidBuffer) {
ReleaseBuffer(vmbuffer);
}
/*
* If the tuple has toasted out-of-line attributes, we need to delete
* those items too. We have to do this before releasing the buffer
* because we need to look at the contents of the tuple, but it's OK to
* release the content lock on the buffer first.
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_MATVIEW) {
/* toast table entries should never be recursively toasted */
Assert(!HeapTupleHasExternal(&tp));
} else if (HeapTupleHasExternal(&tp))
toast_delete(relation, &tp);
/*
* Mark tuple for invalidation from system caches at next command
* boundary. We have to do this before releasing the buffer because we
* need to look at the contents of the tuple.
*/
CacheInvalidateHeapTuple(relation, &tp, NULL);
/* Now we can release the buffer */
ReleaseBuffer(buffer);
/*
* Release the lmgr tuple lock, if we had it.
*/
if (have_tuple_lock) {
UnlockTuple(relation, &(tp.t_self), ExclusiveLock);
}
pgstat_count_heap_delete(relation);
if (old_key_tuple != NULL && old_key_copied) {
heap_freetuple(old_key_tuple);
}
return HeapTupleMayBeUpdated;
}
/*
* simple_heap_delete - delete a tuple
*
* This routine may be used to delete a tuple when concurrent updates of
* the target tuple are not expected (for example, because we have a lock
* on the relation associated with the tuple). Any failure is reported
* via ereport().
*/
void simple_heap_delete(Relation relation, ItemPointer tid)
{
HTSU_Result result;
ItemPointerData update_ctid;
TransactionId update_xmax;
result = heap_delete(relation,
tid,
&update_ctid,
&update_xmax,
GetCurrentCommandId(true),
InvalidSnapshot,
true /* wait for commit */);
switch (result) {
case HeapTupleSelfUpdated:
/* Tuple was already updated in current command? */
ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("tuple already updated by self")));
break;
case HeapTupleMayBeUpdated:
/* done successfully */
break;
case HeapTupleUpdated:
ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("tuple concurrently updated")));
break;
default:
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("unrecognized heap_delete status: %u", result)));
break;
}
}
/*
* heap_update - replace a tuple
*
* NB: do not call this directly unless you are prepared to deal with
* concurrent-update conditions. Use simple_heap_update instead.
*
* relation - table to be modified (caller must hold suitable lock)
* otid - TID of old tuple to be replaced
* newtup - newly constructed tuple data to store
* ctid - output parameter, used only for failure case (see below)
* update_xmax - output parameter, used only for failure case (see below)
* cid - update command ID (used for visibility test, and stored into
* cmax/cmin if successful)
* crosscheck - if not InvalidSnapshot, also check old tuple against this
* wait - true if should wait for any conflicting update to commit/abort
*
* Normal, successful return value is HeapTupleMayBeUpdated, which
* actually means we *did* update it. Failure return codes are
* HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated
* (the last only possible if wait == false).
*
* On success, the header fields of *newtup are updated to match the new
* stored tuple; in particular, newtup->t_self is set to the TID where the
* new tuple was inserted, and its HEAP_ONLY_TUPLE flag is set iff a HOT
* update was done. However, any TOAST changes in the new tuple's
* data are not reflected into *newtup.
*
* In the failure cases, the routine returns the tuple's t_ctid and t_xmax.
* If t_ctid is the same as otid, the tuple was deleted; if different, the
* tuple was updated, and t_ctid is the location of the replacement tuple.
* (t_xmax is needed to verify that the replacement tuple matches.)
*/
HTSU_Result heap_update(Relation relation, Relation parentRelation, ItemPointer otid, HeapTuple newtup,
ItemPointer ctid, TransactionId* update_xmax, CommandId cid, Snapshot crosscheck, bool wait)
{
HTSU_Result result;
TransactionId xid = GetCurrentTransactionId();
Bitmapset* hot_attrs = NULL;
Bitmapset* id_attrs = NULL;
ItemId lp;
HeapTupleData oldtup;
HeapTuple heaptup;
HeapTuple old_key_tuple = NULL;
bool old_key_copied = false;
Page page, newpage;
BlockNumber block;
Buffer buffer = InvalidBuffer;
Buffer newbuf = InvalidBuffer;
Buffer vmbuffer = InvalidBuffer;
Buffer vmbuffer_new = InvalidBuffer;
bool need_toast = false;
bool already_marked = false;
Size new_tup_size, pagefree;
bool have_tuple_lock = false;
bool is_combo = false;
bool satisfies_hot = false;
bool satisfies_id = false;
bool use_hot_update = false;
bool all_visible_cleared = false;
bool all_visible_cleared_new = false;
int options = 0;
bool rel_in_redis = RelationInClusterResizing(relation);
OffsetNumber maxoff;
BlockNumber rel_end_block = InvalidBlockNumber;
Assert(ItemPointerIsValid(otid));
/* Don't allow any write/lock operator in stream. */
Assert(!StreamThreadAmI());
/*
* Fetch the list of attributes to be checked for HOT update. This is
* wasted effort if we fail to update or have to put the new tuple on a
* different page. But we must compute the list before obtaining buffer
* lock --- in the worst case, if we are doing an update on one of the
* relevant system catalogs, we could deadlock if we try to fetch the list
* later. In any case, the relcache caches the data so this is usually
* pretty cheap.
*
* Note that we get a copy here, so we need not worry about relcache flush
* happening midway through.
*/
if (parentRelation != NULL) {
/*
* For partitioned table , we use the parent relation to calc hot_attrs.
*/
Assert(RELATION_IS_PARTITIONED(parentRelation) || RELATION_OWN_BUCKET(parentRelation));
hot_attrs = RelationGetIndexAttrBitmap(parentRelation, INDEX_ATTR_BITMAP_ALL);
id_attrs = RelationGetIndexAttrBitmap(parentRelation, INDEX_ATTR_BITMAP_IDENTITY_KEY);
} else {
hot_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_ALL);
id_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_IDENTITY_KEY);
}
block = ItemPointerGetBlockNumber(otid);
buffer = ReadBuffer(relation, block);
page = BufferGetPage(buffer);
/*
* Before locking the buffer, pin the visibility map page if it appears to
* be necessary. Since we haven't got the lock yet, someone else might be
* in the middle of changing this, so we'll need to recheck after we have
* the lock.
*/
if (PageIsAllVisible(page)) {
visibilitymap_pin(relation, block, &vmbuffer);
}
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
if (PageIs4BXidVersion(page)) {
(void)heap_page_upgrade(relation, buffer);
}
lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
maxoff = PageGetMaxOffsetNumber(page);
/* check otid */
if (maxoff < ItemPointerGetOffsetNumber(otid) || !ItemIdIsNormal(lp) || !ItemPointerIsValid(otid)) {
ereport(PANIC, (errmsg("heap_update: invalid tid %hu, max tid %hu, rnode[%u,%u,%u], block %u", otid->ip_posid,
maxoff, relation->rd_node.spcNode, relation->rd_node.dbNode, relation->rd_node.relNode, block)));
}
/*
* Note: beyond this point, use oldtup not otid to refer to old tuple.
* otid may very well point at newtup->t_self, which we will overwrite
* with the new tuple's location, so there's great risk of confusion if we
* use otid anymore.
*/
oldtup.t_data = (HeapTupleHeader)PageGetItem(page, lp);
oldtup.t_len = ItemIdGetLength(lp);
oldtup.t_self = *otid;
oldtup.t_tableOid = RelationGetRelid(relation);
oldtup.t_bucketId = RelationGetBktid(relation);
HeapSatisfiesHOTUpdate(relation, hot_attrs, id_attrs, &satisfies_hot, &satisfies_id, &oldtup, newtup, page);
l2:
HeapTupleCopyBaseFromPage(&oldtup, BufferGetPage(buffer));
result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer);
if (result == HeapTupleInvisible) {
UnlockReleaseBuffer(buffer);
ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("attempted to update invisible tuple")));
} else if (result == HeapTupleBeingUpdated && wait) {
TransactionId xwait;
uint16 infomask;
/* must copy state data before unlocking buffer */
HeapTupleCopyBaseFromPage(&oldtup, BufferGetPage(buffer));
xwait = HeapTupleGetRawXmax(&oldtup);
infomask = oldtup.t_data->t_infomask;
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
if (!u_sess->attr.attr_common.allow_concurrent_tuple_update) {
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("abort transaction due to concurrent update")));
}
/*
* Acquire tuple lock to establish our priority for the tuple (see
* heap_lock_tuple). LockTuple will release us when we are
* next-in-line for the tuple.
*
* If we are forced to "start over" below, we keep the tuple lock;
* this arranges that we stay at the head of the line while rechecking
* tuple state.
*/
if (!have_tuple_lock) {
LockTuple(relation, &(oldtup.t_self), ExclusiveLock, true);
have_tuple_lock = true;
}
/*
* Sleep until concurrent transaction ends. Note that we don't care
* if the locker has an exclusive or shared lock, because we need
* exclusive.
*/
if (infomask & HEAP_XMAX_IS_MULTI) {
/* wait for multixact */
MultiXactIdWait((MultiXactId)xwait, true);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
/*
* If xwait had just locked the tuple then some other xact could
* update this tuple before we get to this point. Check for xmax
* change, and start over if so.
*/
if (!(oldtup.t_data->t_infomask & HEAP_XMAX_IS_MULTI) ||
!TransactionIdEquals(HeapTupleGetRawXmax(&oldtup), xwait)) {
goto l2;
}
/*
* You might think the multixact is necessarily done here, but not
* so: it could have surviving members, namely our own xact or
* other subxacts of this backend. It is legal for us to update
* the tuple in either case, however (the latter case is
* essentially a situation of upgrading our former shared lock to
* exclusive). We don't bother changing the on-disk hint bits
* since we are about to overwrite the xmax altogether.
*/
} else {
/* wait for regular transaction to end */
XactLockTableWait(xwait, true);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
/*
* xwait is done, but if xwait had just locked the tuple then some
* other xact could update this tuple before we get to this point.
* Check for xmax change, and start over if so.
*/
if ((oldtup.t_data->t_infomask & HEAP_XMAX_IS_MULTI) ||
!TransactionIdEquals(HeapTupleGetRawXmax(&oldtup), xwait)) {
goto l2;
}
/* Otherwise check if it committed or aborted */
UpdateXmaxHintBits(oldtup.t_data, buffer, xwait);
}
/*
* We may overwrite if previous xmax aborted, or if it committed but
* only locked the tuple without updating it.
*/
if (oldtup.t_data->t_infomask & (HEAP_XMAX_INVALID | HEAP_IS_LOCKED)) {
result = HeapTupleMayBeUpdated;
ereport(DEBUG1,
(errmsg("heap maybe updated ctid (%u,%d) cur_xid "
"%lu xmin %lu xmax %lu infomask %hu",
ItemPointerGetBlockNumber(&oldtup.t_self),
ItemPointerGetOffsetNumber(&oldtup.t_self),
GetCurrentTransactionIdIfAny(),
HeapTupleHeaderGetXmin(page, oldtup.t_data),
HeapTupleHeaderGetXmax(page, oldtup.t_data),
oldtup.t_data->t_infomask)));
} else {
result = HeapTupleUpdated;
}
}
if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated) {
/* Perform additional check for transaction-snapshot mode RI updates */
if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer)) {
result = HeapTupleUpdated;
}
}
if (result != HeapTupleMayBeUpdated) {
Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated || result == HeapTupleBeingUpdated);
Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID));
*ctid = oldtup.t_data->t_ctid;
*update_xmax = HeapTupleGetRawXmax(&oldtup);
UnlockReleaseBuffer(buffer);
if (have_tuple_lock) {
UnlockTuple(relation, &(oldtup.t_self), ExclusiveLock);
}
if (vmbuffer != InvalidBuffer) {
ReleaseBuffer(vmbuffer);
}
bms_free(hot_attrs);
bms_free(id_attrs);
return result;
}
/*
* If we didn't pin the visibility map page and the page has become all
* visible while we were busy locking the buffer, or during some
* subsequent window during which we had it unlocked, we'll have to unlock
* and re-lock, to avoid holding the buffer lock across an I/O. That's a
* bit unfortunate, esepecially since we'll now have to recheck whether
* the tuple has been locked or updated under us, but hopefully it won't
* happen very often.
*/
if (vmbuffer == InvalidBuffer && PageIsAllVisible(page)) {
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
visibilitymap_pin(relation, block, &vmbuffer);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
HeapTupleCopyBaseFromPage(&oldtup, page);
goto l2;
}
/*
* We're about to do the actual update -- check for conflict first, to
* avoid possibly having to roll back work we've just done.
*/
CheckForSerializableConflictIn(relation, &oldtup, buffer);
/* Fill in OID and transaction status data for newtup */
if (relation->rd_rel->relhasoids) {
#ifdef NOT_USED
/* this is redundant with an Assert in HeapTupleSetOid */
Assert(newtup->t_data->t_infomask & HEAP_HASOID);
#endif
HeapTupleSetOid(newtup, HeapTupleGetOid(&oldtup));
} else {
/* check there is not space for an OID */
Assert(!(newtup->t_data->t_infomask & HEAP_HASOID));
}
newtup->t_data->t_infomask &= ~(HEAP_XACT_MASK);
newtup->t_data->t_infomask2 &= ~(HEAP2_XACT_MASK);
newtup->t_data->t_infomask |= (HEAP_XMAX_INVALID | HEAP_UPDATED);
/* Unset the HEAP_HAS_REDIS_COLUMNS bit in the new tuple to make sure hidden
* columns added by redis (if any) are removed from the tuple.
* Note: We never allow updates when the relation is redis destination table. */
Assert(!relation->rd_att->tdisredistable);
HeapTupleHeaderUnsetRedisColumns(newtup->t_data);
heap_page_prepare_for_xid(relation, buffer, xid, false);
HeapTupleCopyBaseFromPage(newtup, page);
HeapTupleSetXmin(newtup, xid);
HeapTupleHeaderSetCmin(newtup->t_data, cid);
HeapTupleHeaderSetXmax(page, newtup->t_data, 0); /* for cleanliness */
newtup->t_tableOid = RelationGetRelid(relation);
newtup->t_bucketId = RelationGetBktid(relation);
#ifdef PGXC
newtup->t_xc_node_id = u_sess->pgxc_cxt.PGXCNodeIdentifier;
#endif
if (rel_in_redis && !RelationInClusterResizingReadOnly(relation)) {
options |= HEAP_INSERT_SKIP_FSM;
rel_end_block = RelationGetEndBlock(relation);
}
/*
* Replace cid with a combo cid if necessary. Note that we already put
* the plain cid into the new tuple.
*/
HeapTupleHeaderAdjustCmax(oldtup.t_data, &cid, &is_combo, buffer);
/*
* If the toaster needs to be activated, OR if the new tuple will not fit
* on the same page as the old, then we need to release the content lock
* (but not the pin!) on the old tuple's buffer while we are off doing
* TOAST and/or table-file-extension work. We must mark the old tuple to
* show that it's already being updated, else other processes may try to
* update it themselves.
*
* We need to invoke the toaster if there are already any out-of-line
* toasted values present, or if the new tuple is over-threshold.
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_MATVIEW) {
/* toast table entries should never be recursively toasted */
Assert(!HeapTupleHasExternal(&oldtup));
Assert(!HeapTupleHasExternal(newtup));
need_toast = false;
} else {
need_toast =
(HeapTupleHasExternal(&oldtup) || HeapTupleHasExternal(newtup) || newtup->t_len > TOAST_TUPLE_THRESHOLD);
}
pagefree = PageGetHeapFreeSpace(page);
new_tup_size = MAXALIGN(newtup->t_len);
if (need_toast || new_tup_size > pagefree || rel_in_redis) {
/* Clear obsolete visibility flags ... */
oldtup.t_data->t_infomask &=
~(HEAP_XMAX_COMMITTED | HEAP_XMAX_INVALID | HEAP_XMAX_IS_MULTI | HEAP_IS_LOCKED | HEAP_MOVED);
HeapTupleClearHotUpdated(&oldtup);
/* ... and store info about transaction updating this tuple */
HeapTupleHeaderSetXmax(page, oldtup.t_data, xid);
HeapTupleHeaderSetCmax(oldtup.t_data, cid, is_combo);
/* temporarily make it look not-updated */
oldtup.t_data->t_ctid = oldtup.t_self;
HeapTupleCopyBaseFromPage(&oldtup, page);
already_marked = true;
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
/*
* Let the toaster do its thing, if needed.
*
* Note: below this point, heaptup is the data we actually intend to
* store into the relation; newtup is the caller's original untoasted
* data.
*/
if (need_toast) {
/* Note we always use WAL and FSM during updates */
heaptup = toast_insert_or_update(relation, newtup, &oldtup, 0, page);
new_tup_size = MAXALIGN(heaptup->t_len);
} else {
heaptup = newtup;
}
/*
* Now, do we need a new page for the tuple, or not? This is a bit
* tricky since someone else could have added tuples to the page while
* we weren't looking. We have to recheck the available space after
* reacquiring the buffer lock. But don't bother to do that if the
* former amount of free space is still not enough; it's unlikely
* there's more free now than before.
*
* What's more, if we need to get a new page, we will need to acquire
* buffer locks on both old and new pages. To avoid deadlock against
* some other backend trying to get the same two locks in the other
* order, we must be consistent about the order we get the locks in.
* We use the rule "lock the lower-numbered page of the relation
* first". To implement this, we must do RelationGetBufferForTuple
* while not holding the lock on the old page, and we must rely on it
* to get the locks on both pages in the correct order.
*/
if (new_tup_size > pagefree || rel_in_redis) {
/* Assume there's no chance to put heaptup on same page. */
newbuf = RelationGetBufferForTuple(
relation, heaptup->t_len, buffer, options, NULL, &vmbuffer_new, &vmbuffer, rel_end_block);
} else {
/* Re-acquire the lock on the old tuple's page. */
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
/* Re-check using the up-to-date free space */
pagefree = PageGetHeapFreeSpace(page);
if (new_tup_size > pagefree) {
/*
* Rats, it doesn't fit anymore. We must now unlock and
* relock to avoid deadlock. Fortunately, this path should
* seldom be taken.
*/
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
newbuf = RelationGetBufferForTuple(
relation, heaptup->t_len, buffer, options, NULL, &vmbuffer_new, &vmbuffer, rel_end_block);
} else {
/* OK, it fits here, so we're done. */
newbuf = buffer;
}
}
} else {
/* No TOAST work needed, and it'll fit on same page */
already_marked = false;
newbuf = buffer;
heaptup = newtup;
}
/*
* We're about to create the new tuple -- check for conflict first, to
* avoid possibly having to roll back work we've just done.
*
* NOTE: For a tuple insert, we only need to check for table locks, since
* predicate locking at the index level will cover ranges for anything
* except a table scan. Therefore, only provide the relation.
*/
CheckForSerializableConflictIn(relation, NULL, InvalidBuffer);
/*
* At this point newbuf and buffer are both pinned and locked, and newbuf
* has enough space for the new tuple. If they are the same buffer, only
* one pin is held.
*/
if (!(options & HEAP_INSERT_SKIP_FSM)) {
if (newbuf == buffer) {
/*
* Since the new tuple is going into the same page, we might be able
* to do a HOT update. Check if any of the index columns have been
* changed. If not, then HOT update is possible.
*/
if (satisfies_hot) {
use_hot_update = true;
}
} else {
/* Set a hint that the old page could use prune/defrag */
PageSetFull(page);
}
}
/*
* Compute replica identity tuple before entering the critical section so
* we don't PANIC upon a memory allocation failure.
* ExtractReplicaIdentity() will return NULL if nothing needs to be
* logged.
*/
old_key_tuple = ExtractReplicaIdentity(relation, &oldtup, !satisfies_id, &old_key_copied);
newpage = BufferGetPage(newbuf);
if (newbuf != buffer) {
/* Prepare new page for xids */
(void)heap_page_prepare_for_xid(relation, newbuf, xid, false);
HeapTupleCopyBaseFromPage(heaptup, newpage);
}
/* NO EREPORT(ERROR) from here till changes are logged */
START_CRIT_SECTION();
/*
* If this transaction commits, the old tuple will become DEAD sooner or
* later. Set flag that this page is a candidate for pruning once our xid
* falls below the oldest_xmin horizon. If the transaction finally aborts,
* the subsequent page pruning will be a no-op and the hint will be
* cleared.
*
* XXX Should we set hint on newbuf as well? If the transaction aborts,
* there would be a prunable tuple in the newbuf; but for now we choose
* not to optimize for aborts. Note that heap_xlog_update must be kept in
* sync if this decision changes.
*/
if (!(options & HEAP_INSERT_SKIP_FSM)) {
PageSetPrunable(page, xid);
}
if (use_hot_update) {
/* Mark the old tuple as HOT-updated */
HeapTupleSetHotUpdated(&oldtup);
/* And mark the new tuple as heap-only */
HeapTupleSetHeapOnly(heaptup);
/* Mark the caller's copy too, in case different from heaptup */
HeapTupleSetHeapOnly(newtup);
} else {
/* Make sure tuples are correctly marked as not-HOT */
HeapTupleClearHotUpdated(&oldtup);
HeapTupleClearHeapOnly(heaptup);
HeapTupleClearHeapOnly(newtup);
}
RelationPutHeapTuple(relation, newbuf, heaptup, xid); /* insert new tuple */
if (!already_marked) {
/* Clear obsolete visibility flags ... */
oldtup.t_data->t_infomask &=
~(HEAP_XMAX_COMMITTED | HEAP_XMAX_INVALID | HEAP_XMAX_IS_MULTI | HEAP_IS_LOCKED | HEAP_MOVED);
/* ... and store info about transaction updating this tuple */
HeapTupleHeaderSetXmax(page, oldtup.t_data, xid);
HeapTupleHeaderSetCmax(oldtup.t_data, cid, is_combo);
}
/* record address of new tuple in t_ctid of old one */
oldtup.t_data->t_ctid = heaptup->t_self;
/* clear PD_ALL_VISIBLE flags */
if (PageIsAllVisible(BufferGetPage(buffer))) {
all_visible_cleared = true;
PageClearAllVisible(BufferGetPage(buffer));
visibilitymap_clear(relation, BufferGetBlockNumber(buffer), vmbuffer);
}
if (newbuf != buffer && PageIsAllVisible(BufferGetPage(newbuf))) {
all_visible_cleared_new = true;
PageClearAllVisible(BufferGetPage(newbuf));
visibilitymap_clear(relation, BufferGetBlockNumber(newbuf), vmbuffer_new);
}
if (newbuf != buffer) {
MarkBufferDirty(newbuf);
}
MarkBufferDirty(buffer);
/* XLOG stuff */
if (RelationNeedsWAL(relation)) {
XLogRecPtr recptr;
/*
* For logical decoding we need combocids to properly decode the
* catalog.
*/
if (RelationIsAccessibleInLogicalDecoding(relation)) {
(void)log_heap_new_cid(relation, &oldtup);
(void)log_heap_new_cid(relation, heaptup);
}
recptr = log_heap_update(relation,
buffer,
&(oldtup.t_self),
newbuf,
heaptup,
old_key_tuple,
all_visible_cleared,
all_visible_cleared_new);
if (newbuf != buffer) {
PageSetLSN(BufferGetPage(newbuf), recptr);
}
PageSetLSN(BufferGetPage(buffer), recptr);
}
END_CRIT_SECTION();
if (newbuf != buffer) {
LockBuffer(newbuf, BUFFER_LOCK_UNLOCK);
}
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
/*
* Mark old tuple for invalidation from system caches at next command
* boundary, and mark the new tuple for invalidation in case we abort. We
* have to do this before releasing the buffer because oldtup is in the
* buffer. (heaptup is all in local memory, but it's necessary to process
* both tuple versions in one call to inval.c so we can avoid redundant
* sinval messages.)
*/
CacheInvalidateHeapTuple(relation, &oldtup, heaptup);
/* Now we can release the buffer(s) */
if (newbuf != buffer) {
ReleaseBuffer(newbuf);
}
ReleaseBuffer(buffer);
if (BufferIsValid(vmbuffer_new)) {
ReleaseBuffer(vmbuffer_new);
}
if (BufferIsValid(vmbuffer)) {
ReleaseBuffer(vmbuffer);
}
/*
* Release the lmgr tuple lock, if we had it.
*/
if (have_tuple_lock) {
UnlockTuple(relation, &(oldtup.t_self), ExclusiveLock);
}
pgstat_count_heap_update(relation, use_hot_update);
/*
* If heaptup is a private copy, release it. Don't forget to copy t_self
* back to the caller's image, too.
*/
if (heaptup != newtup) {
newtup->t_self = heaptup->t_self;
heap_freetuple(heaptup);
}
if (old_key_tuple != NULL && old_key_copied) {
heap_freetuple(old_key_tuple);
}
bms_free(hot_attrs);
bms_free(id_attrs);
return HeapTupleMayBeUpdated;
}
static XLogRecPtr log_heap_new_cid_insert(xl_heap_new_cid *xlrec)
{
XLogRecPtr recptr;
/*
* Note that we don't need to register the buffer here, because this
* operation does not modify the page. The insert/update/delete that
* called us certainly did, but that's WAL-logged separately.
*/
XLogBeginInsert();
XLogRegisterData((char *) xlrec, SizeOfHeapNewCid);
/* will be looked at irrespective of origin */
recptr = XLogInsert(RM_HEAP3_ID, XLOG_HEAP3_NEW_CID);
return recptr;
}
/*
* Perform XLogInsert of an XLOG_HEAP2_NEW_CID record
*
* This is only used in wal_level >= WAL_LEVEL_LOGICAL, and only for catalog
* tuples.
*/
static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup)
{
xl_heap_new_cid xlrec;
XLogRecPtr recptr;
HeapTupleHeader hdr = tup->t_data;
Assert(ItemPointerIsValid(&tup->t_self));
Assert(tup->t_tableOid != InvalidOid);
xlrec.top_xid = GetTopTransactionId();
xlrec.target_node.dbNode = relation->rd_node.dbNode;
xlrec.target_node.relNode = relation->rd_node.relNode;
xlrec.target_node.spcNode = relation->rd_node.spcNode;
xlrec.target_tid = tup->t_self;
/*
* If the tuple got inserted & deleted in the same TX we definitely have a
* combocid, set cmin and cmax.
*/
if (hdr->t_infomask & HEAP_COMBOCID) {
Assert(!(hdr->t_infomask & HEAP_XMAX_INVALID));
Assert(!(hdr->t_infomask & HEAP_XMIN_INVALID));
xlrec.cmin = HeapTupleGetCmin(tup);
xlrec.cmax = HeapTupleGetCmax(tup);
xlrec.combocid = HeapTupleHeaderGetRawCommandId(hdr);
}
/* No combocid, so only cmin or cmax can be set by this TX */
else {
/*
* Tuple inserted.
*
* We need to check for LOCK ONLY because multixacts might be
* transferred to the new tuple in case of FOR KEY SHARE updates in
* which case there will be an xmax, although the tuple just got
* inserted.
*/
if ((hdr->t_infomask & HEAP_XMAX_INVALID) ||
(hdr->t_infomask & HEAP_IS_LOCKED)) {
xlrec.cmin = HeapTupleHeaderGetRawCommandId(hdr);
xlrec.cmax = InvalidCommandId;
} else {
/* Tuple from a different tx updated or deleted. */
xlrec.cmin = InvalidCommandId;
xlrec.cmax = HeapTupleHeaderGetRawCommandId(hdr);
}
xlrec.combocid = InvalidCommandId;
}
recptr = log_heap_new_cid_insert(&xlrec);
return recptr;
}
bool heap_page_upgrade(Relation relation, Buffer buffer)
{
TransactionId xid = GetCurrentTransactionId();
Page page = BufferGetPage(buffer);
Size page_free_space = 0;
Size save_free_space = 0;
bool is_upgrade = true;
int nline;
/* Compute desired extra freespace due to fillfactor option */
save_free_space = RelationGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR);
page_free_space = (int)((PageHeader)page)->pd_upper - (int)((PageHeader)page)->pd_lower;
if (page_free_space - save_free_space >= SizeOfHeapPageUpgradeData) {
if (!PageIs4BXidVersion(page)) {
return true;
}
START_CRIT_SECTION();
PageLocalUpgrade(page);
} else {
nline = PageGetMaxOffsetNumber(page);
if (nline == 1) {
if (xid > MaxShortTransactionId) {
if (BufferIsValid(buffer)) {
UnlockReleaseBuffer(buffer);
}
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("relation \"%s\" has one big row which is not supported under 64bits XID system. "
"Current xid is %lu",
RelationGetRelationName(relation),
xid)));
} else
ereport(WARNING,
(errcode(ERRCODE_WARNING),
errmsg("block number %u for relation \"%s\" has one big row which is not supported under "
"64bits XID system. Current xid is %lu",
BufferGetBlockNumber(buffer),
RelationGetRelationName(relation),
xid),
handle_in_client(true)));
} else {
if (xid > MaxShortTransactionId) {
if (BufferIsValid(buffer)) {
UnlockReleaseBuffer(buffer);
}
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("relation \"%s\" has no free space to upgrade. Current xid is %lu, please VACUUM FULL "
"this relation!!!",
RelationGetRelationName(relation),
xid)));
} else
ereport(DEBUG1,
(errmsg("block number %u for relation \"%s\" has no free space to upgrade. Current xid is %lu, it "
"is safe before XID increased to 4294967296 !",
BufferGetBlockNumber(buffer),
RelationGetRelationName(relation),
xid)));
}
is_upgrade = false;
return false;
}
/* xlog stuff */
if (is_upgrade) {
MarkBufferDirty(buffer);
if (RelationNeedsWAL(relation)) {
XLogRecPtr recptr;
XLogBeginInsert();
XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_PAGE_UPGRADE);
PageSetLSN(BufferGetPage(buffer), recptr);
}
END_CRIT_SECTION();
}
return true;
}
/*
* Check if the specified attribute's value is same in both given tuples.
* Subroutine for HeapSatisfiesHOTUpdate.
*/
static bool heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum, HeapTuple tup1, HeapTuple tup2, char* page)
{
Datum value1, value2;
bool isnull1 = false;
bool isnull2 = false;
Form_pg_attribute att;
/*
* If it's a whole-tuple reference, say "not equal". It's not really
* worth supporting this case, since it could only succeed after a no-op
* update, which is hardly a case worth optimizing for.
*/
if (attrnum == 0) {
return false;
}
/*
* Likewise, automatically say "not equal" for any system attribute other
* than OID and tableOID; we cannot expect these to be consistent in a HOT
* chain, or even to be set correctly yet in the new tuple.
*/
if (attrnum < 0) {
if (attrnum != ObjectIdAttributeNumber &&
#ifdef PGXC
attrnum != XC_NodeIdAttributeNumber && attrnum != BucketIdAttributeNumber &&
#endif
attrnum != TableOidAttributeNumber) {
return false;
}
}
/*
* Extract the corresponding values. XXX this is pretty inefficient if
* there are many indexed columns. Should HeapSatisfiesHOTUpdate do a
* single heap_deform_tuple call on each tuple, instead? But that doesn't
* work for system columns ...
*/
if (HEAP_TUPLE_IS_COMPRESSED(tup1->t_data)) {
value1 = heap_getattr_with_dict(tup1, attrnum, tupdesc, &isnull1, (char*)getPageDict(page));
} else {
value1 = heap_getattr(tup1, attrnum, tupdesc, &isnull1);
}
Assert(!HEAP_TUPLE_IS_COMPRESSED(tup2->t_data));
value2 = heap_getattr(tup2, attrnum, tupdesc, &isnull2);
/*
* If one value is NULL and other is not, then they are certainly not
* equal
*/
if (isnull1 != isnull2) {
return false;
}
/*
* If both are NULL, they can be considered equal.
*/
if (isnull1) {
return true;
}
/*
* We do simple binary comparison of the two datums. This may be overly
* strict because there can be multiple binary representations for the
* same logical value. But we should be OK as long as there are no false
* positives. Using a type-specific equality operator is messy because
* there could be multiple notions of equality in different operator
* classes; furthermore, we cannot safely invoke user-defined functions
* while holding exclusive buffer lock.
*/
if (attrnum <= 0) {
/* The only allowed system columns are OIDs, so do this */
return (DatumGetObjectId(value1) == DatumGetObjectId(value2));
} else {
Assert(attrnum <= tupdesc->natts);
att = tupdesc->attrs[attrnum - 1];
return datumIsEqual(value1, value2, att->attbyval, att->attlen);
}
}
/*
* Check if the old and new tuples represent a HOT-safe update. To be able
* to do a HOT update, we must not have changed any columns used in index
* definitions.
*
* The set of attributes to be checked is passed in (we dare not try to
* compute it while holding exclusive buffer lock...) NOTE that hot_attrs
* is destructively modified! That is OK since this is invoked at most once
* by heap_update().
*
* Returns true if safe to do HOT update.
*/
static void HeapSatisfiesHOTUpdate(Relation relation, Bitmapset* hot_attrs, Bitmapset* id_attrs, bool* satisfies_hot,
bool* satisfies_id, HeapTuple oldtup, HeapTuple newtup, char* page)
{
int next_hot_attnum;
int next_id_attnum;
bool hot_result = true;
bool id_result = true;
/*
* If one of these sets contains no remaining bits, bms_first_member will
* return -1, and after adding FirstLowInvalidHeapAttributeNumber (which
* is negative!) we'll get an attribute number that can't possibly be
* real, and thus won't match any actual attribute number.
*/
next_hot_attnum = bms_first_member(hot_attrs);
next_hot_attnum += FirstLowInvalidHeapAttributeNumber;
next_id_attnum = bms_first_member(id_attrs);
next_id_attnum += FirstLowInvalidHeapAttributeNumber;
for (;;) {
bool changed = false;
int check_now;
/*
* Since the HOT attributes are a superset of the key attributes and
* the key attributes are a superset of the id attributes, this logic
* is guaranteed to identify the next column that needs to be
* checked.
*/
if (hot_result && next_hot_attnum > FirstLowInvalidHeapAttributeNumber) {
check_now = next_hot_attnum;
} else if (id_result && next_id_attnum > FirstLowInvalidHeapAttributeNumber) {
check_now = next_id_attnum;
} else {
break;
}
/* See whether it changed. */
changed = !heap_tuple_attr_equals(RelationGetDescr(relation), check_now, oldtup, newtup, page);
if (changed) {
if (check_now == next_hot_attnum) {
hot_result = false;
}
if (check_now == next_id_attnum) {
id_result = false;
}
/* if all are false now, we can stop checking */
if (!hot_result && !id_result) {
break;
}
}
/*
* Advance the next attribute numbers for the sets that contain
* the attribute we just checked. As we work our way through the
* columns, the next_attnum values will rise; but when each set
* becomes empty, bms_first_member() will return -1 and the attribute
* number will end up with a value less than
* FirstLowInvalidHeapAttributeNumber.
*/
if (hot_result && check_now == next_hot_attnum) {
next_hot_attnum = bms_first_member(hot_attrs);
next_hot_attnum += FirstLowInvalidHeapAttributeNumber;
}
if (id_result && check_now == next_id_attnum) {
next_id_attnum = bms_first_member(id_attrs);
next_id_attnum += FirstLowInvalidHeapAttributeNumber;
}
}
*satisfies_hot = hot_result;
*satisfies_id = id_result;
}
/*
* simple_heap_update - replace a tuple
*
* This routine may be used to update a tuple when concurrent updates of
* the target tuple are not expected (for example, because we have a lock
* on the relation associated with the tuple). Any failure is reported
* via ereport().
*/
void simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
{
HTSU_Result result;
ItemPointerData update_ctid;
TransactionId update_xmax;
/* All built-in functions are hard coded, and thus they should not be updated */
if (u_sess->attr.attr_common.IsInplaceUpgrade == false && IsProcRelation(relation) &&
IsBuiltinFuncOid(HeapTupleGetOid(tup))) {
ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("All built-in functions are hard coded, and they should not be updated.")));
}
result = heap_update(relation,
NULL,
otid,
tup,
&update_ctid,
&update_xmax,
GetCurrentCommandId(true),
InvalidSnapshot,
true /* wait for commit */);
switch (result) {
case HeapTupleSelfUpdated:
/* Tuple was already updated in current command? */
ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("tuple already updated by self")));
break;
case HeapTupleMayBeUpdated:
/* done successfully */
break;
case HeapTupleUpdated:
ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("tuple concurrently updated")));
break;
default:
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("unrecognized heap_update status: %u", result)));
break;
}
}
/*
* heap_lock_tuple - lock a tuple in shared or exclusive mode
*
* Note that this acquires a buffer pin, which the caller must release.
*
* Input parameters:
* relation: relation containing tuple (caller must hold suitable lock)
* tuple->t_self: TID of tuple to lock (rest of struct need not be valid)
* cid: current command ID (used for visibility test, and stored into
* tuple's cmax if lock is successful)
* mode: indicates if shared or exclusive tuple lock is desired
* nowait: if true, ereport rather than blocking if lock not available
*
* Output parameters:
* *tuple: all fields filled in
* *buffer: set to buffer holding tuple (pinned but not locked at exit)
* *ctid: set to tuple's t_ctid, but only in failure cases
* *update_xmax: set to tuple's xmax, but only in failure cases
*
* Function result may be:
* HeapTupleMayBeUpdated: lock was successfully acquired
* HeapTupleSelfUpdated: lock failed because tuple updated by self
* HeapTupleUpdated: lock failed because tuple updated by other xact
*
* In the failure cases, the routine returns the tuple's t_ctid and t_xmax.
* If t_ctid is the same as t_self, the tuple was deleted; if different, the
* tuple was updated, and t_ctid is the location of the replacement tuple.
* (t_xmax is needed to verify that the replacement tuple matches.)
*
*
* NOTES: because the shared-memory lock table is of finite size, but users
* could reasonably want to lock large numbers of tuples, we do not rely on
* the standard lock manager to store tuple-level locks over the long term.
* Instead, a tuple is marked as locked by setting the current transaction's
* XID as its XMAX, and setting additional infomask bits to distinguish this
* usage from the more normal case of having deleted the tuple. When
* multiple transactions concurrently share-lock a tuple, the first locker's
* XID is replaced in XMAX with a MultiTransactionId representing the set of
* XIDs currently holding share-locks.
*
* When it is necessary to wait for a tuple-level lock to be released, the
* basic delay is provided by XactLockTableWait or MultiXactIdWait on the
* contents of the tuple's XMAX. However, that mechanism will release all
* waiters concurrently, so there would be a race condition as to which
* waiter gets the tuple, potentially leading to indefinite starvation of
* some waiters. The possibility of share-locking makes the problem much
* worse --- a steady stream of share-lockers can easily block an exclusive
* locker forever. To provide more reliable semantics about who gets a
* tuple-level lock first, we use the standard lock manager. The protocol
* for waiting for a tuple-level lock is really LockTuple(), XactLockTableWait()
* mark tuple as locked by me UnlockTuple()
* When there are multiple waiters, arbitration of who is to get the lock next
* is provided by LockTuple(). However, at most one tuple-level lock will
* be held or awaited per backend at any time, so we don't risk overflow
* of the lock table. Note that incoming share-lockers are required to
* do LockTuple as well, if there is any conflict, to ensure that they don't
* starve out waiting exclusive-lockers. However, if there is not any active
* conflict for a tuple, we don't incur any extra overhead.
*/
HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple, Buffer* buffer, ItemPointer ctid,
TransactionId* update_xmax, CommandId cid, LockTupleMode mode, bool nowait)
{
HTSU_Result result;
ItemPointer tid = &(tuple->t_self);
ItemId lp;
Page page;
TransactionId xid;
TransactionId xmax;
uint16 old_infomask;
uint16 new_infomask;
LOCKMODE tuple_lock_type;
bool have_tuple_lock = false;
Buffer vmbuffer = InvalidBuffer;
BlockNumber block;
/* Don't allow any write/lock operator in stream. */
AssertEreport(!StreamThreadAmI(), MOD_STREAM, "Unsupported lock tuple in stream.");
/* Not support tuple concurrent update to avoid distributed deadlock. */
if (!u_sess->attr.attr_common.allow_concurrent_tuple_update) {
nowait = true;
}
tuple_lock_type = (mode == LockTupleShared) ? ShareLock : ExclusiveLock;
block = ItemPointerGetBlockNumber(tid);
*buffer = ReadBuffer(relation, block);
/*
* Before locking the buffer, pin the visibility map page if it appears to
* be necessary. Since we haven't got the lock yet, someone else might be
* in the middle of changing this, so we'll need to recheck after we have
* the lock.
*/
if (PageIsAllVisible(BufferGetPage(*buffer))) {
visibilitymap_pin(relation, block, &vmbuffer);
}
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
page = BufferGetPage(*buffer);
if (PageIs4BXidVersion(page)) {
(void)heap_page_upgrade(relation, *buffer);
}
lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid));
Assert(ItemIdIsNormal(lp));
tuple->t_data = (HeapTupleHeader)PageGetItem(page, lp);
tuple->t_len = ItemIdGetLength(lp);
tuple->t_tableOid = RelationGetRelid(relation);
tuple->t_bucketId = RelationGetBktid(relation);
#ifdef PGXC
tuple->t_xc_node_id = u_sess->pgxc_cxt.PGXCNodeIdentifier;
#endif
l3:
HeapTupleCopyBaseFromPage(tuple, page);
result = HeapTupleSatisfiesUpdate(tuple, cid, *buffer);
ereport(DEBUG1,
(errmsg("heap lock tuple ctid (%u,%d) cur_xid %lu xmin "
"%lu xmax %lu infomask %hu result %d",
ItemPointerGetBlockNumber(tid),
ItemPointerGetOffsetNumber(tid),
GetCurrentTransactionIdIfAny(),
HeapTupleHeaderGetXmin(page, tuple->t_data),
HeapTupleHeaderGetXmax(page, tuple->t_data),
tuple->t_data->t_infomask,
result)));
if (result == HeapTupleInvisible) {
UnlockReleaseBuffer(*buffer);
ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("attempted to lock invisible tuple")));
} else if (result == HeapTupleBeingUpdated) {
TransactionId xwait;
uint16 infomask;
/* must copy state data before unlocking buffer */
xwait = HeapTupleGetRawXmax(tuple);
infomask = tuple->t_data->t_infomask;
LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
/*
* If we wish to acquire share lock, and the tuple is already
* share-locked by a multixact that includes any subtransaction of the
* current top transaction, then we effectively hold the desired lock
* already. We *must* succeed without trying to take the tuple lock,
* else we will deadlock against anyone waiting to acquire exclusive
* lock. We don't need to make any state changes in this case.
*/
if (mode == LockTupleShared && (infomask & HEAP_XMAX_IS_MULTI) && MultiXactIdIsCurrent((MultiXactId)xwait)) {
Assert(infomask & HEAP_XMAX_SHARED_LOCK);
result = HeapTupleMayBeUpdated;
goto out_unlocked;
}
/*
* Acquire tuple lock to establish our priority for the tuple.
* LockTuple will release us when we are next-in-line for the tuple.
* We must do this even if we are share-locking.
*
* If we are forced to "start over" below, we keep the tuple lock;
* this arranges that we stay at the head of the line while rechecking
* tuple state.
*/
if (!have_tuple_lock) {
if (nowait) {
if (!ConditionalLockTuple(relation, tid, tuple_lock_type)) {
ereport(ERROR,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg(
"could not obtain lock on row in relation \"%s\"", RelationGetRelationName(relation))));
}
} else {
LockTuple(relation, tid, tuple_lock_type, true);
}
have_tuple_lock = true;
}
if (mode == LockTupleShared && (infomask & HEAP_XMAX_SHARED_LOCK)) {
/*
* Acquiring sharelock when there's at least one sharelocker
* already. We need not wait for him/them to complete.
*/
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
/*
* Make sure it's still a shared lock, else start over. (It's OK
* if the ownership of the shared lock has changed, though.)
*/
if (!(tuple->t_data->t_infomask & HEAP_XMAX_SHARED_LOCK)) {
goto l3;
}
} else if (infomask & HEAP_XMAX_IS_MULTI) {
/* wait for multixact to end */
if (nowait) {
if (!ConditionalMultiXactIdWait((MultiXactId)xwait)) {
ereport(ERROR, (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg("could not obtain lock on row in relation \"%s\"", RelationGetRelationName(relation))));
}
} else {
MultiXactIdWait((MultiXactId)xwait, true);
}
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
/*
* If xwait had just locked the tuple then some other xact could
* update this tuple before we get to this point. Check for xmax
* change, and start over if so.
*/
if (!(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI) ||
!TransactionIdEquals(HeapTupleGetRawXmax(tuple), xwait)) {
goto l3;
}
/*
* You might think the multixact is necessarily done here, but not
* so: it could have surviving members, namely our own xact or
* other subxacts of this backend. It is legal for us to lock the
* tuple in either case, however. We don't bother changing the
* on-disk hint bits since we are about to overwrite the xmax
* altogether.
*/
} else {
/* wait for regular transaction to end */
if (nowait) {
if (!ConditionalXactLockTableWait(xwait))
ereport(ERROR,
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
errmsg(
"could not obtain lock on row in relation \"%s\"", RelationGetRelationName(relation))));
} else {
XactLockTableWait(xwait, true);
}
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
/*
* xwait is done, but if xwait had just locked the tuple then some
* other xact could update this tuple before we get to this point.
* Check for xmax change, and start over if so.
*/
if ((tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI) ||
!TransactionIdEquals(HeapTupleGetRawXmax(tuple), xwait)) {
goto l3;
}
/* Otherwise check if it committed or aborted */
UpdateXmaxHintBits(tuple->t_data, *buffer, xwait);
}
/*
* We may lock if previous xmax aborted, or if it committed but only
* locked the tuple without updating it. The case where we didn't
* wait because we are joining an existing shared lock is correctly
* handled, too.
*/
if (tuple->t_data->t_infomask & (HEAP_XMAX_INVALID | HEAP_IS_LOCKED)) {
result = HeapTupleMayBeUpdated;
} else {
result = HeapTupleUpdated;
}
}
if (result != HeapTupleMayBeUpdated) {
Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated);
Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID));
*ctid = tuple->t_data->t_ctid;
*update_xmax = HeapTupleGetRawXmax(tuple);
goto out_locked;
}
/*
* We might already hold the desired lock (or stronger), possibly under a
* different subtransaction of the current top transaction. If so, there
* is no need to change state or issue a WAL record. We already handled
* the case where this is true for xmax being a MultiXactId, so now check
* for cases where it is a plain TransactionId.
*
* Note in particular that this covers the case where we already hold
* exclusive lock on the tuple and the caller only wants shared lock. It
* would certainly not do to give up the exclusive lock.
*/
xmax = HeapTupleGetRawXmax(tuple);
old_infomask = tuple->t_data->t_infomask;
if (!(old_infomask & (HEAP_XMAX_INVALID | HEAP_XMAX_COMMITTED | HEAP_XMAX_IS_MULTI)) &&
(mode == LockTupleShared ? (old_infomask & HEAP_IS_LOCKED) : (old_infomask & HEAP_XMAX_EXCL_LOCK)) &&
TransactionIdIsCurrentTransactionId(xmax)) {
LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
/* Probably can't hold tuple lock here, but may as well check */
if (have_tuple_lock) {
UnlockTuple(relation, tid, tuple_lock_type);
}
result = HeapTupleMayBeUpdated;
goto out_unlocked;
}
/*
* Compute the new xmax and infomask to store into the tuple. Note we do
* not modify the tuple just yet, because that would leave it in the wrong
* state if multixact.c elogs.
*/
xid = GetCurrentTransactionId();
new_infomask =
old_infomask & ~(HEAP_XMAX_COMMITTED | HEAP_XMAX_INVALID | HEAP_XMAX_IS_MULTI | HEAP_IS_LOCKED | HEAP_MOVED);
if (mode == LockTupleShared) {
/*
* If this is the first acquisition of a shared lock in the current
* transaction, set my per-backend OldestMemberMXactId setting. We can
* be certain that the transaction will never become a member of any
* older MultiXactIds than that. (We have to do this even if we end
* up just using our own TransactionId below, since some other backend
* could incorporate our XID into a MultiXact immediately afterwards.)
*/
MultiXactIdSetOldestMember();
new_infomask |= HEAP_XMAX_SHARED_LOCK;
/*
* Check to see if we need a MultiXactId because there are multiple
* lockers.
*
* HeapTupleSatisfiesUpdate will have set the HEAP_XMAX_INVALID bit if
* the xmax was a MultiXactId but it was not running anymore. There is
* a race condition, which is that the MultiXactId may have finished
* since then, but that uncommon case is handled within
* MultiXactIdExpand.
*
* There is a similar race condition possible when the old xmax was a
* regular TransactionId. We test TransactionIdIsInProgress again
* just to narrow the window, but it's still possible to end up
* creating an unnecessary MultiXactId. Fortunately this is harmless.
*/
if (!(old_infomask & (HEAP_XMAX_INVALID | HEAP_XMAX_COMMITTED))) {
if (old_infomask & HEAP_XMAX_IS_MULTI) {
/*
* If the XMAX is already a MultiXactId, then we need to
* expand it to include our own TransactionId.
*/
xid = MultiXactIdExpand((MultiXactId)xmax, xid);
new_infomask |= HEAP_XMAX_IS_MULTI;
} else if (TransactionIdIsInProgress(xmax)) {
/*
* If the XMAX is a valid TransactionId, then we need to
* create a new MultiXactId that includes both the old locker
* and our own TransactionId.
*/
xid = MultiXactIdCreate(xmax, xid);
new_infomask |= HEAP_XMAX_IS_MULTI;
} else {
/*
* Can get here iff HeapTupleSatisfiesUpdate saw the old xmax
* as running, but it finished before
* TransactionIdIsInProgress() got to run. Treat it like
* there's no locker in the tuple.
*/
}
} else {
/*
* There was no previous locker, so just insert our own
* TransactionId.
*/
}
} else {
/* We want an exclusive lock on the tuple */
new_infomask |= HEAP_XMAX_EXCL_LOCK;
}
/*
* If we didn't pin the visibility map page and the page has become all
* visible while we were busy locking the buffer, or during some
* subsequent window during which we had it unlocked, we'll have to unlock
* and re-lock, to avoid holding the buffer lock across I/O. That's a bit
* unfortunate, especially since we'll now have to recheck whether the
* tuple has been locked or updated under us, but hopefully it won't
* happen very often.
*/
if (vmbuffer == InvalidBuffer && PageIsAllVisible(page)) {
LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
visibilitymap_pin(relation, block, &vmbuffer);
LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
goto l3;
}
(void)heap_page_prepare_for_xid(relation, *buffer, xid, (new_infomask & HEAP_XMAX_IS_MULTI) ? true : false);
HeapTupleCopyBaseFromPage(tuple, page);
START_CRIT_SECTION();
/*
* Store transaction information of xact locking the tuple.
*
* Note: Cmax is meaningless in this context, so don't set it; this avoids
* possibly generating a useless combo CID.
*/
tuple->t_data->t_infomask = new_infomask;
HeapTupleHeaderClearHotUpdated(tuple->t_data);
HeapTupleHeaderSetXmax(page, tuple->t_data, xid);
/* Make sure there is no forward chain link in t_ctid */
tuple->t_data->t_ctid = *tid;
/* Clear bit on visibility map if needed */
if (PageIsAllVisible(BufferGetPage(*buffer))) {
visibilitymap_clear(relation, block, vmbuffer);
}
MarkBufferDirty(*buffer);
/*
* XLOG stuff. You might think that we don't need an XLOG record because
* there is no state change worth restoring after a crash. You would be
* wrong however: we have just written either a TransactionId or a
* MultiXactId that may never have been seen on disk before, and we need
* to make sure that there are XLOG entries covering those ID numbers.
* Else the same IDs might be re-used after a crash, which would be
* disastrous if this page made it to disk before the crash. Essentially
* we have to enforce the WAL log-before-data rule even in this case.
* (Also, in a PITR log-shipping or 2PC environment, we have to have XLOG
* entries for everything anyway.)
*/
if (RelationNeedsWAL(relation)) {
xl_heap_lock xlrec;
XLogRecPtr recptr;
xlrec.locking_xid = xid;
xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
xlrec.xid_is_mxact = ((new_infomask & HEAP_XMAX_IS_MULTI) != 0);
xlrec.shared_lock = (mode == LockTupleShared);
XLogBeginInsert();
XLogRegisterData((char*)&xlrec, SizeOfHeapLock);
XLogRegisterBuffer(0, *buffer, REGBUF_STANDARD);
recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_LOCK);
PageSetLSN(page, recptr);
}
END_CRIT_SECTION();
result = HeapTupleMayBeUpdated;
out_locked:
LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
out_unlocked:
if (BufferIsValid(vmbuffer)) {
ReleaseBuffer(vmbuffer);
}
/*
* Don't update the visibility map here. Locking a tuple doesn't change
* visibility info.
*
* Now that we have successfully marked the tuple as locked, we can
* release the lmgr tuple lock, if we had it.
*/
if (have_tuple_lock) {
UnlockTuple(relation, tid, tuple_lock_type);
}
return result;
}
/*
* heap_inplace_update - update a tuple "in place" (ie, overwrite it)
*
* Overwriting violates both MVCC and transactional safety, so the uses
* of this function in Postgres are extremely limited. Nonetheless we
* find some places to use it.
*
* The tuple cannot change size, and therefore it's reasonable to assume
* that its null bitmap (if any) doesn't change either. So we just
* overwrite the data portion of the tuple without touching the null
* bitmap or any of the header fields.
*
* tuple is an in-memory tuple structure containing the data to be written
* over the target tuple. Also, tuple->t_self identifies the target tuple.
*/
void heap_inplace_update(Relation relation, HeapTuple tuple)
{
Buffer buffer;
Page page;
OffsetNumber offnum, maxoff;
ItemId lp = NULL;
HeapTupleHeader htup;
uint32 oldlen;
uint32 newlen;
errno_t rc;
buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&(tuple->t_self)));
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
page = (Page)BufferGetPage(buffer);
offnum = ItemPointerGetOffsetNumber(&(tuple->t_self));
maxoff = PageGetMaxOffsetNumber(page);
if (maxoff >= offnum) {
lp = PageGetItemId(page, offnum);
}
if (maxoff < offnum || !ItemIdIsNormal(lp)) {
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("heap_inplace_update: invalid lp")));
}
htup = (HeapTupleHeader)PageGetItem(page, lp);
oldlen = ItemIdGetLength(lp) - htup->t_hoff;
newlen = tuple->t_len - tuple->t_data->t_hoff;
if (oldlen != newlen || htup->t_hoff != tuple->t_data->t_hoff) {
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("heap_inplace_update: wrong tuple length")));
}
/* NO EREPORT(ERROR) from here till changes are logged */
START_CRIT_SECTION();
rc = memcpy_s((char*)htup + htup->t_hoff, newlen, (char*)tuple->t_data + tuple->t_data->t_hoff, newlen);
securec_check(rc, "\0", "\0");
MarkBufferDirty(buffer);
/* XLOG stuff */
if (RelationNeedsWAL(relation)) {
xl_heap_inplace xlrec;
XLogRecPtr recptr;
xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self);
XLogBeginInsert();
XLogRegisterData((char*)&xlrec, SizeOfHeapInplace);
XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
XLogRegisterBufData(0, (char*)htup + htup->t_hoff, newlen);
recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_INPLACE);
PageSetLSN(page, recptr);
}
END_CRIT_SECTION();
UnlockReleaseBuffer(buffer);
/*
* Send out shared cache inval if necessary. Note that because we only
* pass the new version of the tuple, this mustn't be used for any
* operations that could change catcache lookup keys. But we aren't
* bothering with index updates either, so that's true a fortiori.
*/
if (!IsBootstrapProcessingMode()) {
CacheInvalidateHeapTuple(relation, tuple, NULL);
}
}
/*
* heap_freeze_tuple
*
* Check to see whether any of the XID fields of a tuple (xmin, xmax, xvac)
* are older than the specified cutoff XID. If so, replace them with
* FrozenTransactionId or InvalidTransactionId as appropriate, and return
* TRUE. Return FALSE if nothing was changed.
*
* It is assumed that the caller has checked the tuple with
* HeapTupleSatisfiesVacuum() and determined that it is not HEAPTUPLE_DEAD
* (else we should be removing the tuple, not freezing it).
*
* NB: cutoff_xid *must* be <= the current global xmin, to ensure that any
* XID older than it could neither be running nor seen as running by any
* open transaction. This ensures that the replacement will not change
* anyone's idea of the tuple state. Also, since we assume the tuple is
* not HEAPTUPLE_DEAD, the fact that an XID is not still running allows us
* to assume that it is either committed good or aborted, as appropriate;
* so we need no external state checks to decide what to do. (This is good
* because this function is applied during WAL recovery, when we don't have
* access to any such state, and can't depend on the hint bits to be set.)
*
* If the tuple is in a shared buffer, caller must hold an exclusive lock on
* that buffer.
*
* Note: it might seem we could make the changes without exclusive lock, since
* TransactionId read/write is assumed atomic anyway. However there is a race
* condition: someone who just fetched an old XID that we overwrite here could
* conceivably not finish checking the XID against pg_clog before we finish
* the VACUUM and perhaps truncate off the part of pg_clog he needs. Getting
* exclusive lock ensures no other backend is in process of checking the
* tuple status. Also, getting exclusive lock makes it safe to adjust the
* infomask bits.
*/
bool heap_freeze_tuple(HeapTuple tuple, TransactionId cutoff_xid)
{
bool changed = false;
TransactionId xid;
xid = HeapTupleGetRawXmin(tuple);
if (TransactionIdIsNormal(xid) && TransactionIdPrecedes(xid, cutoff_xid)) {
if (!RecoveryInProgress() && !TransactionIdDidCommit(xid)) {
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
errmsg_internal(
"uncommitted xmin %lu from before xid cutoff %lu needs to be frozen", xid, cutoff_xid)));
}
HeapTupleSetXmin(tuple, FrozenTransactionId);
/*
* Might as well fix the hint bits too; usually XMIN_COMMITTED will
* already be set here, but there's a small chance not.
*/
Assert(!HeapTupleHeaderXminInvalid(tuple->t_data));
tuple->t_data->t_infomask |= HEAP_XMIN_COMMITTED;
changed = true;
}
if (!(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI)) {
xid = HeapTupleGetRawXmax(tuple);
if (TransactionIdIsNormal(xid) && TransactionIdPrecedes(xid, cutoff_xid)) {
if (!RecoveryInProgress() && !(tuple->t_data->t_infomask & HEAP_IS_LOCKED) && TransactionIdDidCommit(xid)) {
ereport(
ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg_internal("cannot freeze commited xmax %lu", xid)));
}
HeapTupleSetXmax(tuple, InvalidTransactionId);
/*
* The tuple might be marked either XMAX_INVALID or XMAX_COMMITTED
* + LOCKED. Normalize to INVALID just to be sure no one gets
* confused.
*/
tuple->t_data->t_infomask &= ~HEAP_XMAX_COMMITTED;
tuple->t_data->t_infomask |= HEAP_XMAX_INVALID;
HeapTupleHeaderClearHotUpdated(tuple->t_data);
changed = true;
}
} else {
/* ----------
* XXX perhaps someday we should zero out very old MultiXactIds here?
*
* The only way a stale MultiXactId could pose a problem is if a
* tuple, having once been multiply-share-locked, is not touched by
* any vacuum or attempted lock or deletion for just over 4G MultiXact
* creations, and then in the probably-narrow window where its xmax
* is again a live MultiXactId, someone tries to lock or delete it.
* Even then, another share-lock attempt would work fine. An
* exclusive-lock or delete attempt would face unexpected delay, or
* in the very worst case get a deadlock error. This seems an
* extremely low-probability scenario with minimal downside even if
* it does happen, so for now we don't do the extra bookkeeping that
* would be needed to clean out MultiXactIds.
* ----------
*/
}
return changed;
}
/*
* heap_tuple_needs_freeze
*
* Check to see whether any of the XID fields of a tuple (xmin, xmax, xvac)
* are older than the specified cutoff XID. If so, return TRUE.
*
* It doesn't matter whether the tuple is alive or dead, we are checking
* to see if a tuple needs to be removed or frozen.
*/
bool heap_tuple_needs_freeze(HeapTuple htup, TransactionId cutoff_xid, Buffer buf)
{
TransactionId xid;
HeapTupleHeader tuple = htup->t_data;
xid = HeapTupleGetRawXmin(htup);
if (TransactionIdIsNormal(xid) && TransactionIdPrecedes(xid, cutoff_xid)) {
return true;
}
if (!(tuple->t_infomask & HEAP_XMAX_IS_MULTI)) {
xid = HeapTupleGetRawXmax(htup);
if (TransactionIdIsNormal(xid) && TransactionIdPrecedes(xid, cutoff_xid)) {
return true;
}
}
return false;
}
/* ----------------
* heap_restrpos - restore position to marked location
* ----------------
*/
void heap_restrpos(HeapScanDesc scan)
{
/* XXX no amrestrpos checking that ammarkpos called */
if (!ItemPointerIsValid(&scan->rs_mctid)) {
scan->rs_ctup.t_data = NULL;
/*
* unpin scan buffers
*/
if (BufferIsValid(scan->rs_cbuf)) {
ReleaseBuffer(scan->rs_cbuf);
}
scan->rs_cbuf = InvalidBuffer;
scan->rs_cblock = InvalidBlockNumber;
scan->rs_inited = false;
} else {
/*
* If we reached end of scan, rs_inited will now be false. We must
* reset it to true to keep heapgettup from doing the wrong thing.
*/
scan->rs_inited = true;
scan->rs_ctup.t_self = scan->rs_mctid;
if (scan->rs_pageatatime) {
scan->rs_cindex = scan->rs_mindex;
heapgettup_pagemode(scan,
NoMovementScanDirection,
0, /* needn't recheck scan keys */
NULL);
} else
heapgettup(scan,
NoMovementScanDirection,
0, /* needn't recheck scan keys */
NULL);
}
}
/*
* If 'tuple' contains any visible XID greater than latest_removed_xid,
* ratchet forwards latest_removed_xid to the greatest one found.
* This is used as the basis for generating Hot Standby conflicts, so
* if a tuple was never visible then removing it should not conflict
* with queries.
*/
void HeapTupleHeaderAdvanceLatestRemovedXid(HeapTuple tuple, TransactionId* latest_removed_xid)
{
HeapTupleHeader htup = tuple->t_data;
TransactionId xmin = HeapTupleGetRawXmin(tuple);
TransactionId xmax = HeapTupleGetRawXmax(tuple);
/*
* Ignore tuples inserted by an aborted transaction or if the tuple was
* updated/deleted by the inserting transaction.
*
* Look for a committed hint bit, or if no xmin bit is set, check clog.
* This needs to work on both master and standby, where it is used to
* assess btree delete records.
*/
if (HeapTupleHeaderXminCommitted(htup) || (!HeapTupleHeaderXminInvalid(htup) && TransactionIdDidCommit(xmin))) {
if (xmax != xmin && TransactionIdFollows(xmax, *latest_removed_xid)) {
*latest_removed_xid = xmax;
}
}
/* *latest_removed_xid may still be invalid at end */
}
/*
* Perform XLogInsert to register a heap cleanup info message. These
* messages are sent once per VACUUM and are required because
* of the phasing of removal operations during a lazy VACUUM.
* see comments for vacuum_log_cleanup_info().
*/
XLogRecPtr log_heap_cleanup_info(const RelFileNode* rnode, TransactionId latest_removed_xid)
{
xl_heap_cleanup_info xlrec;
XLogRecPtr recptr;
RelFileNodeRelCopy(xlrec.node, *rnode);
xlrec.latestRemovedXid = latest_removed_xid;
XLogBeginInsert();
XLogRegisterData((char*)&xlrec, SizeOfHeapCleanupInfo);
recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_CLEANUP_INFO, false, rnode->bucketNode);
return recptr;
}
/*
* Perform XLogInsert for a heap-clean operation. Caller must already
* have modified the buffer and marked it dirty.
*
* Note: prior to Postgres 8.3, the entries in the nowunused[] array were
* zero-based tuple indexes. Now they are one-based like other uses
* of OffsetNumber.
*
* We also include latest_removed_xid, which is the greatest XID present in
* the removed tuples. That allows recovery processing to cancel or wait
* for long standby queries that can still see these tuples.
*/
XLogRecPtr log_heap_clean(Relation reln, Buffer buffer, OffsetNumber* redirected, int nredirected,
OffsetNumber* nowdead, int ndead, OffsetNumber* nowunused, int nunused, TransactionId latest_removed_xid,
bool repair_fragmentation)
{
xl_heap_clean xlrec;
XLogRecPtr recptr;
RelFileNode rnode;
ForkNumber forkNum;
BlockNumber blkNum;
Page page;
uint8 info;
/* Caller should not call me on a non-WAL-logged relation */
Assert(RelationNeedsWAL(reln));
xlrec.latestRemovedXid = latest_removed_xid;
xlrec.nredirected = nredirected;
xlrec.ndead = ndead;
XLogBeginInsert();
XLogRegisterData((char*)&xlrec, SizeOfHeapClean);
XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
/*
* The OffsetNumber arrays are not actually in the buffer, but we pretend
* that they are. When XLogInsert stores the whole buffer, the offset
* arrays need not be stored too. Note that even if all three arrays are
* empty, we want to expose the buffer as a candidate for whole-page
* storage, since this record type implies a defragmentation operation
* even if no item pointers changed state.
*/
if (nredirected > 0) {
XLogRegisterBufData(0, (char*)redirected, nredirected * sizeof(OffsetNumber) * 2);
}
if (ndead > 0) {
XLogRegisterBufData(0, (char*)nowdead, ndead * sizeof(OffsetNumber));
}
if (nunused > 0) {
XLogRegisterBufData(0, (char*)nowunused, nunused * sizeof(OffsetNumber));
}
info = XLOG_HEAP2_CLEAN;
if (!repair_fragmentation) {
info |= XLOG_HEAP2_NO_REPAIR_PAGE;
}
recptr = XLogInsert(RM_HEAP2_ID, info);
BufferGetTag(buffer, &rnode, &forkNum, &blkNum);
page = BufferGetPage(buffer);
ereport(DEBUG4,
(errmodule(MOD_REDO),
errcode(ERRCODE_LOG),
errmsg("[REDO_LOG_TRACE]log_heap_clean: ProcLastRecPtr:%lu,XactLastRecEnd:%lu,"
"recptr:%lu,oldPageLsn:%lu,newPageLsn:%lu, latest_removed_xid:%lu,"
"rnode(spcNode:%u, dbNode:%u, relNode:%u),forkNum:%d,blkNum:%u",
t_thrd.xlog_cxt.ProcLastRecPtr,
t_thrd.xlog_cxt.XactLastRecEnd,
recptr,
PageGetLSN(page),
recptr,
latest_removed_xid,
rnode.spcNode,
rnode.dbNode,
rnode.relNode,
forkNum,
blkNum)));
return recptr;
}
/*
* Perform XLogInsert for a heap-freeze operation. Caller must already
* have modified the buffer and marked it dirty.
*/
XLogRecPtr log_heap_freeze(Relation reln, Buffer buffer, TransactionId cutoff_xid, OffsetNumber* offsets, int offcnt)
{
xl_heap_freeze xlrec;
XLogRecPtr recptr;
/* Caller should not call me on a non-WAL-logged relation */
Assert(RelationNeedsWAL(reln));
/* nor when there are no tuples to freeze */
Assert(offcnt > 0);
xlrec.cutoff_xid = cutoff_xid;
XLogBeginInsert();
XLogRegisterData((char*)&xlrec, SizeOfHeapFreeze);
/*
* The tuple-offsets array is not actually in the buffer, but pretend that
* it is. When XLogInsert stores the whole buffer, the offsets array need
* not be stored too.
*/
XLogRegisterBuffer(0, buffer, REGBUF_STANDARD);
XLogRegisterBufData(0, (char*)offsets, offcnt * sizeof(OffsetNumber));
recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_FREEZE);
return recptr;
}
XLogRecPtr log_cu_bcm(const RelFileNode* rnode, int col, uint64 block, int status, int count)
{
xl_heap_bcm xlrec;
XLogRecPtr recptr;
/*
* block is pointer to the last cu unit block;
*/
RelFileNodeRelCopy(xlrec.node, *rnode);
xlrec.block = block;
xlrec.count = count;
xlrec.status = status;
xlrec.col = col;
XLogBeginInsert();
XLogRegisterData((char*)&xlrec, SizeOfHeapBcm);
recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_BCM, false, rnode->bucketNode);
return recptr;
}
/*
* Perform XLogInsert for a bcm set operation. 'block' is the block
* being marked as status, and bcm_buffer is the buffer containing the
* corresponding bcm map block. Both should have already been modified
* and dirtied.
*/
XLogRecPtr log_heap_bcm(const RelFileNode* rnode, int col, uint64 block, int status)
{
xl_heap_bcm xlrec;
XLogRecPtr recptr;
RelFileNodeRelCopy(xlrec.node, *rnode);
xlrec.block = block;
xlrec.count = 1;
xlrec.status = status;
xlrec.col = col;
XLogBeginInsert();
XLogRegisterData((char*)&xlrec, SizeOfHeapBcm);
recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_BCM, false, rnode->bucketNode);
return recptr;
}
/*
* Perform XLogInsert for a heap-visible operation. 'block' is the block
* being marked all-visible, and vm_buffer is the buffer containing the
* corresponding visibility map block. Both should have already been modified
* and dirtied.
*
* If checksums are enabled, we also generate a full-page image of
* heap_buffer, if necessary.
*/
XLogRecPtr log_heap_visible(RelFileNode rnode, BlockNumber block, Buffer heap_buffer, Buffer vm_buffer,
TransactionId cutoff_xid, bool free_dict)
{
xl_heap_visible xlrec;
XLogRecPtr recptr;
Page page;
int flags;
Assert(BufferIsValid(vm_buffer));
xlrec.block = block;
xlrec.cutoff_xid = cutoff_xid;
xlrec.free_dict = free_dict;
XLogBeginInsert();
XLogRegisterData((char*)&xlrec, SizeOfHeapVisible);
XLogRegisterBuffer(0, vm_buffer, 0);
if (BufferIsValid(heap_buffer)) {
page = BufferGetPage(heap_buffer);
flags = REGBUF_STANDARD;
if (!PageIsLogical(page) && !XLogHintBitIsNeeded()) {
flags |= REGBUF_NO_IMAGE;
}
XLogRegisterBuffer(1, heap_buffer, flags);
}
recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_VISIBLE);
return recptr;
}
/*
* Perform XLogInsert for a heap-update operation. Caller must already
* have modified the buffer(s) and marked them dirty.
*/
static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf, const ItemPointer from, Buffer newbuf,
HeapTuple newtup, HeapTuple old_key_tuple, bool all_visible_cleared, bool new_all_visible_cleared)
{
xl_heap_update xlrec;
xl_heap_header xlhdr;
xl_heap_header xlhdr_idx;
uint8 info;
XLogRecPtr recptr;
Page page = BufferGetPage(newbuf);
bool need_tuple_data = RelationIsLogicallyLogged(reln);
int bufflags;
OffsetNumber maxoff;
/* Caller should not call me on a non-WAL-logged relation */
Assert(RelationNeedsWAL(reln));
if (HeapTupleIsHeapOnly(newtup)) {
info = XLOG_HEAP_HOT_UPDATE;
} else {
info = XLOG_HEAP_UPDATE;
}
XLogBeginInsert();
maxoff = PageGetMaxOffsetNumber(page);
/*
* If new tuple is the single and first tuple on page...
* If page is already compressed, should not init page,
* or lead to inconsistency.
*/
if (ItemPointerGetOffsetNumber(&(newtup->t_self)) == FirstOffsetNumber &&
maxoff == FirstOffsetNumber &&
!PageIsCompressed(page)) {
info |= XLOG_HEAP_INIT_PAGE;
bufflags = REGBUF_STANDARD | REGBUF_WILL_INIT;
} else
bufflags = REGBUF_STANDARD;
/* Prepare WAL data */
xlrec.old_offnum = ItemPointerGetOffsetNumber(from);
xlrec.new_offnum = ItemPointerGetOffsetNumber(&newtup->t_self);
xlrec.flags = 0;
if (all_visible_cleared) {
xlrec.flags |= XLOG_HEAP_ALL_VISIBLE_CLEARED;
}
if (new_all_visible_cleared) {
xlrec.flags |= XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED;
}
if (need_tuple_data) {
xlrec.flags |= XLOG_HEAP_CONTAINS_NEW_TUPLE;
if (old_key_tuple) {
if (reln->rd_rel->relreplident == REPLICA_IDENTITY_FULL)
xlrec.flags |= XLOG_HEAP_CONTAINS_OLD_TUPLE;
else
xlrec.flags |= XLOG_HEAP_CONTAINS_OLD_KEY;
}
}
if (need_tuple_data) {
bufflags |= REGBUF_KEEP_DATA;
}
xlhdr.t_infomask2 = newtup->t_data->t_infomask2;
xlhdr.t_infomask = newtup->t_data->t_infomask;
xlhdr.t_hoff = newtup->t_data->t_hoff;
/*
* As with insert records, we need not store the rdata[2] segment
* if we decide to store the whole buffer instead unless we're
* doing logical decoding.
*/
XLogRegisterBuffer(0, newbuf, bufflags);
XLogRegisterBufData(0, (char*)&xlhdr, SizeOfHeapHeader);
/* PG73FORMAT: write bitmap [+ padding] [+ oid] + data */
XLogRegisterBufData(0,
(char*)newtup->t_data + offsetof(HeapTupleHeaderData, t_bits),
newtup->t_len - offsetof(HeapTupleHeaderData, t_bits));
if (oldbuf != newbuf) {
XLogRegisterBuffer(1, oldbuf, REGBUF_STANDARD);
}
if (info & XLOG_HEAP_INIT_PAGE) {
XLogRegisterData((char*)&((HeapPageHeader)(page))->pd_xid_base, sizeof(TransactionId));
}
XLogRegisterData((char*)&xlrec, SizeOfHeapUpdate);
/* We need to log a tuple identity */
if (need_tuple_data && old_key_tuple) {
/* don't really need this, but its more comfy to decode */
xlhdr_idx.t_infomask2 = old_key_tuple->t_data->t_infomask2;
xlhdr_idx.t_infomask = old_key_tuple->t_data->t_infomask;
xlhdr_idx.t_hoff = old_key_tuple->t_data->t_hoff;
XLogRegisterData((char*)&xlhdr_idx, SizeOfHeapHeader);
/* PG73FORMAT: write bitmap [+ padding] [+ oid] + data */
XLogRegisterData((char*)old_key_tuple->t_data + offsetof(HeapTupleHeaderData, t_bits),
old_key_tuple->t_len - offsetof(HeapTupleHeaderData, t_bits));
}
/* filtering by origin on a row level is much more efficient */
XLogIncludeOrigin();
recptr = XLogInsert(RM_HEAP_ID, info);
ereport(DEBUG4,
(errmodule(MOD_REDO),
errcode(ERRCODE_LOG),
errmsg("[REDO_LOG_TRACE]log_heap_update: fromBlkNum:%u,fromOffsetNum:%hu,"
"newBlkNum:%u,newOffsetNum:%hu,"
"t_infomask2:%hu,t_infomask:%hu,t_hoff:%hhu,flags:%hhu,bufflags:%d,newLen:%u",
ItemPointerGetBlockNumber(from),
ItemPointerGetOffsetNumber(from),
ItemPointerGetBlockNumber(&newtup->t_self),
ItemPointerGetOffsetNumber(&newtup->t_self),
xlhdr.t_infomask2,
xlhdr.t_infomask,
xlhdr.t_hoff,
xlrec.flags,
bufflags,
newtup->t_len)));
if (old_key_tuple != NULL) {
ereport(DEBUG4,
(errmodule(MOD_REDO),
errcode(ERRCODE_LOG),
errmsg("[REDO_LOG_TRACE]log_heap_update: oldBlkNum:%u, oldOffsetNum:%hu",
ItemPointerGetBlockNumber(&old_key_tuple->t_self),
ItemPointerGetOffsetNumber(&old_key_tuple->t_self))));
}
return recptr;
}
/*
* Build a heap tuple representing the configured REPLICA IDENTITY to represent
* the old tuple in a UPDATE or DELETE.
*
* Returns NULL if there's no need to log an identity or if there's no suitable
* key in the Relation relation.
*/
static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_changed, bool* copy)
{
TupleDesc desc = RelationGetDescr(relation);
Oid replidindex;
Relation idx_rel;
TupleDesc idx_desc;
char relreplident;
HeapTuple key_tuple = NULL;
bool nulls[MaxHeapAttributeNumber];
Datum values[MaxHeapAttributeNumber];
int natt;
errno_t rc = 0;
*copy = false;
if (!RelationIsLogicallyLogged(relation)) {
return NULL;
}
bool is_null = true;
Relation rel = heap_open(RelationRelationId, AccessShareLock);
Oid relid = RelationIsPartition(relation) ? relation->parentId : relation->rd_id;
HeapTuple tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tuple)) {
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
errmsg("pg_class entry for relid %u vanished during ExtractReplicaIdentity", relid)));
}
Datum replident = heap_getattr(tuple, Anum_pg_class_relreplident, RelationGetDescr(rel), &is_null);
heap_close(rel, AccessShareLock);
heap_freetuple(tuple);
if (is_null) {
relreplident = REPLICA_IDENTITY_NOTHING;
} else {
relreplident = CharGetDatum(replident);
}
if (replident == REPLICA_IDENTITY_NOTHING) {
return NULL;
}
if (replident == REPLICA_IDENTITY_FULL) {
/*
* When logging the entire old tuple, it very well could contain
* toasted columns. If so, force them to be inlined.
*/
if (HeapTupleHasExternal(tp)) {
*copy = true;
tp = toast_flatten_tuple(tp, RelationGetDescr(relation));
}
return tp;
}
/* if the key hasn't changed and we're only logging the key, we're done */
if (!key_changed) {
return NULL;
}
/* find the replica identity index */
replidindex = RelationGetReplicaIndex(relation);
if (!OidIsValid(replidindex)) {
ereport(DEBUG4,
(errmsg("could not find configured replica identity for table \"%s\"", RelationGetRelationName(relation))));
return NULL;
}
idx_rel = RelationIdGetRelation(replidindex);
idx_desc = RelationGetDescr(idx_rel);
/* deform tuple, so we have fast access to columns */
heap_deform_tuple(tp, desc, values, nulls);
/* set all columns to NULL, regardless of whether they actually are */
rc = memset_s(nulls, sizeof(nulls), 1, sizeof(nulls));
securec_check(rc, "\0", "\0");
/*
* Now set all columns contained in the index to NOT NULL, they cannot
* currently be NULL.
*/
for (natt = 0; natt < idx_desc->natts; natt++) {
int attno = idx_rel->rd_index->indkey.values[natt];
if (attno < 0) {
/*
* The OID column can appear in an index definition, but that's
* OK, because we always copy the OID if present (see below).
* Other system columns may not.
*/
if (attno == ObjectIdAttributeNumber) {
continue;
}
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("system column in index")));
}
nulls[attno - 1] = false;
}
key_tuple = heap_form_tuple(desc, values, nulls);
*copy = true;
RelationClose(idx_rel);
/*
* Always copy oids if the table has them, even if not included in the
* index. The space in the logged tuple is used anyway, so there's little
* point in not including the information.
*/
if (relation->rd_rel->relhasoids) {
HeapTupleSetOid(key_tuple, HeapTupleGetOid(tp));
}
/*
* If the tuple, which by here only contains indexed columns, still has
* toasted columns, force them to be inlined. This is somewhat unlikely
* since there's limits on the size of indexed columns, so we don't
* duplicate toast_flatten_tuple()s functionality in the above loop over
* the indexed columns, even if it would be more efficient.
*/
if (HeapTupleHasExternal(key_tuple)) {
HeapTuple oldtup = key_tuple;
key_tuple = toast_flatten_tuple(oldtup, RelationGetDescr(relation));
heap_freetuple(oldtup);
}
return key_tuple;
}
/*
* Perform XLogInsert of a HEAP_NEWPAGE record to WAL. Caller is responsible
* for writing the page to disk after calling this routine.
*
* Note: If you're using this function, you should be building pages in private
* memory and writing them directly to smgr. If you're using buffers, call
* log_newpage_buffer instead.
*
* Note: the NEWPAGE log record is used for both heaps and indexes, so do
* not do anything that assumes we are touching a heap.
*/
XLogRecPtr log_newpage(RelFileNode* rnode, ForkNumber forkNum, BlockNumber blkno, Page page, bool page_std)
{
int flags;
XLogRecPtr recptr;
/* NO ELOG(ERROR) from here till newpage op is logged */
START_CRIT_SECTION();
flags = REGBUF_FORCE_IMAGE;
if (page_std) {
flags |= REGBUF_STANDARD;
}
XLogBeginInsert();
XLogRegisterBlock(0, rnode, forkNum, blkno, page, flags);
recptr = XLogInsert(RM_HEAP_ID, XLOG_HEAP_NEWPAGE);
/*
* The page may be uninitialized. If so, we can't set the LSN and TLI
* because that would corrupt the page.
*/
if (!PageIsNew(page)) {
PageSetLSN(page, recptr);
}
END_CRIT_SECTION();
return recptr;
}
XLogRecPtr log_logical_newpage(RelFileNode* rnode, ForkNumber forkNum, BlockNumber blk, Page page, Buffer buffer)
{
xl_heap_logical_newpage xlrec;
XLogRecPtr recptr;
/* NO ELOG(ERROR) from here till newpage op is logged */
START_CRIT_SECTION();
xlrec.blkno = blk;
xlrec.blockSize = BLCKSZ;
RelFileNodeRelCopy(xlrec.node, *rnode);
xlrec.forknum = forkNum;
xlrec.type = ROW_STORE;
xlrec.attid = 0;
xlrec.offset = 0;
XLogBeginInsert();
XLogRegisterData((char*)&xlrec, SizeOfHeapLogicalNewPage);
/*
* We need not to RegisterBuffer for logical newpage. But when
* we use pg_rewind to recover a primary to stanby maybe appear
* another problem; Explame for the scene:
* 1: when occur two primarys; primary1 create table t1(relfilenode
* is 16385), copy page A to t1(LSN for A is 100); primary2 create
* table t1(relfilenode is 16385), copy page B to t1(LSN for B is
* 120); when pg_rewind primary2, then start primary2 to catchup
* to primary1, because of lsn, the page A will not cover page B.
* So we register the newpage Buffer and pg_rewind will copy A
* to cover B.
*/
XLogRegisterBuffer(0, buffer, REGBUF_NO_IMAGE);
recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_LOGICAL_NEWPAGE, false, rnode->bucketNode);
PageSetLSN(page, recptr);
PageSetLogical(page);
END_CRIT_SECTION();
return recptr;
}
XLogRecPtr log_logical_newcu(RelFileNode* rnode, ForkNumber forkNum, int attid, Size offset, int size, char* cuData)
{
xl_heap_logical_newpage xlrec;
XLogRecPtr recptr;
/* NO ELOG(ERROR) from here till newpage op is logged */
START_CRIT_SECTION();
xlrec.blkno = 0;
xlrec.blockSize = size;
RelFileNodeRelCopy(xlrec.node, *rnode);
xlrec.forknum = forkNum;
xlrec.type = COLUMN_STORE;
xlrec.attid = attid;
xlrec.offset = offset;
/* flag of save cu xlog */
if (cuData != NULL) {
xlrec.hasdata = true;
} else {
xlrec.hasdata = false;
}
XLogBeginInsert();
XLogRegisterData((char*)&xlrec, SizeOfHeapLogicalNewPage);
if (cuData != NULL) {
XLogRegisterData(cuData, size);
}
recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_LOGICAL_NEWPAGE, false, rnode->bucketNode);
END_CRIT_SECTION();
return recptr;
}
/*
* Perform XLogInsert of a HEAP_NEWPAGE record to WAL.
*
* Caller should initialize the buffer and mark it dirty before calling this
* function. This function will set the page LSN and TLI.
*
* Note: the NEWPAGE log record is used for both heaps and indexes, so do
* not do anything that assumes we are touching a heap.
*/
XLogRecPtr log_newpage_buffer(Buffer buffer, bool page_std)
{
Page page = BufferGetPage(buffer);
RelFileNode rnode;
ForkNumber forkNum;
BlockNumber blkno;
/* We should be in a critical section. */
Assert(t_thrd.int_cxt.CritSectionCount > 0);
BufferGetTag(buffer, &rnode, &forkNum, &blkno);
return log_newpage(&rnode, forkNum, blkno, page, page_std);
}
/*
* Handles CLEANUP_INFO
*/
static void heap_xlog_cleanup_info(XLogReaderState* record)
{
xl_heap_cleanup_info* xlrec = (xl_heap_cleanup_info*)XLogRecGetData(record);
RelFileNode tmp_node;
RelFileNodeCopy(tmp_node, xlrec->node, XLogRecGetBucketId(record));
if (InHotStandby && SUPPORT_HOT_STANDBY) {
ResolveRecoveryConflictWithSnapshot(xlrec->latestRemovedXid, tmp_node);
}
/*
* Actual operation is a no-op. Record type exists to provide a means for
* conflict processing to occur before we begin index vacuum actions. see
* vacuumlazy.c and also comments in btvacuumpage()
*
* Backup blocks are not used in cleanup_info records
*/
Assert(!XLogRecHasAnyBlockRefs(record));
}
/*
* Handles HEAP2_CLEAN record type
*/
static void heap_xlog_clean(XLogReaderState* record)
{
xl_heap_clean* xlrec = (xl_heap_clean*)XLogRecGetData(record);
RedoBufferInfo buffer;
Size freespace = 0;
XLogRedoAction action;
RelFileNode rnode;
BlockNumber blkno;
bool repair_fragmentation = true;
if ((XLogRecGetInfo(record) & XLOG_HEAP2_NO_REPAIR_PAGE) != 0) {
repair_fragmentation = false;
}
XLogRecGetBlockTag(record, HEAP_CLEAN_ORIG_BLOCK_NUM, &rnode, NULL, &blkno);
/*
* We're about to remove tuples. In Hot Standby mode, ensure that there's
* no queries running for which the removed tuples are still visible.
*
* Not all HEAP2_CLEAN records remove tuples with xids, so we only want to
* conflict on the records that cause MVCC failures for user queries. If
* latest_removed_xid is invalid, skip conflict processing.
*/
if (InHotStandby && SUPPORT_HOT_STANDBY && TransactionIdIsValid(xlrec->latestRemovedXid)) {
ResolveRecoveryConflictWithSnapshot(xlrec->latestRemovedXid, rnode);
}
/*
* If we have a full-page image, restore it (using a cleanup lock) and
* we're done.
*/
action = XLogReadBufferForRedoExtended(record, HEAP_CLEAN_ORIG_BLOCK_NUM, RBM_NORMAL, true, &buffer);
if (action == BLK_NEEDS_REDO) {
char* maindata = XLogRecGetData(record);
Size blkdatalen;
char* blkdata = NULL;
blkdata = XLogRecGetBlockData(record, HEAP_CLEAN_ORIG_BLOCK_NUM, &blkdatalen);
heap_xlog_clean_operator_page(&buffer, (void*)maindata, (void*)blkdata, blkdatalen, &freespace,
repair_fragmentation);
MarkBufferDirty(buffer.buf);
}
if (BufferIsValid(buffer.buf)) {
UnlockReleaseBuffer(buffer.buf);
}
/*
* Update the FSM as well.
*
* XXX: Don't do this if the page was restored from full page image. We
* don't bother to update the FSM in that case, it doesn't need to be
* totally accurate anyway.
*/
if (action == BLK_NEEDS_REDO) {
XLogRecordPageWithFreeSpace(rnode, blkno, freespace);
}
}
static void heap_xlog_freeze(XLogReaderState* record)
{
xl_heap_freeze* xlrec = (xl_heap_freeze*)XLogRecGetData(record);
TransactionId cutoff_xid = xlrec->cutoff_xid;
RedoBufferInfo buffer;
/*
* In Hot Standby mode, ensure that there's no queries running which still
* consider the frozen xids as running.
*/
if (InHotStandby && SUPPORT_HOT_STANDBY) {
RelFileNode rnode;
(void)XLogRecGetBlockTag(record, HEAP_FREEZE_ORIG_BLOCK_NUM, &rnode, NULL, NULL);
ResolveRecoveryConflictWithSnapshot(cutoff_xid, rnode);
}
if (XLogReadBufferForRedo(record, HEAP_FREEZE_ORIG_BLOCK_NUM, &buffer) == BLK_NEEDS_REDO) {
char* maindata = XLogRecGetData(record);
Size blkdatalen;
char* blkdata = NULL;
blkdata = XLogRecGetBlockData(record, HEAP_FREEZE_ORIG_BLOCK_NUM, &blkdatalen);
heap_xlog_freeze_operator_page(&buffer, (void*)maindata, (void*)blkdata, blkdatalen);
MarkBufferDirty(buffer.buf);
}
if (BufferIsValid(buffer.buf)) {
UnlockReleaseBuffer(buffer.buf);
}
}
/*
* Replay XLOG_HEAP2_VISIBLE record.
* The critical integrity requirement here is that we must never end up with
* a situation where the visibility map bit is set, and the page-level
* PD_ALL_VISIBLE bit is clear. If that were to occur, then a subsequent
* page modification would fail to clear the visibility map bit.
*/
static void heap_xlog_visible(XLogReaderState* record)
{
xl_heap_visible* xlrec = (xl_heap_visible*)XLogRecGetData(record);
RedoBufferInfo vmbuffer;
RedoBufferInfo buffer;
XLogRedoAction action;
RelFileNode rnode;
/* In log_heap_visible, block 0 is vm_buffer, block 1 is heap_buffer.
* the vm and heap must have same relfilenode. so whether use block 0 or 1 is correct for relfilenode
* is correct for relfilenode */
(void)XLogRecGetBlockTag(record, HEAP_VISIBLE_VM_BLOCK_NUM, &rnode, NULL, NULL);
/*
* If there are any Hot Standby transactions running that have an xmin
* horizon old enough that this page isn't all-visible for them, they
* might incorrectly decide that an index-only scan can skip a heap fetch.
*
* NB: It might be better to throw some kind of "soft" conflict here that
* forces any index-only scan that is in flight to perform heap fetches,
* rather than killing the transaction outright.
*/
if (InHotStandby && SUPPORT_HOT_STANDBY) {
ResolveRecoveryConflictWithSnapshot(xlrec->cutoff_xid, rnode);
}
if (XLogRecHasBlockRef(record, HEAP_VISIBLE_DATA_BLOCK_NUM)) {
/*
* Read the heap page, if it was append to the buffer portion and still exists.
* If the heap file has dropped or truncated later in recovery, we don't need
* to update the page, but we'd better still update the visibility map.
*/
action = XLogReadBufferForRedo(record, HEAP_VISIBLE_DATA_BLOCK_NUM, &buffer);
if (action == BLK_NEEDS_REDO) {
char* maindata = XLogRecGetData(record);
heap_xlog_visible_operator_page(&buffer, (void*)maindata);
MarkBufferDirty(buffer.buf);
} else if (action == BLK_RESTORED) {
/*
* If heap block was backed up, we already restored it and there's
* nothing more to do. (This can only happen with checksums or
* wal_log_hints enabled.)
*/
}
if (BufferIsValid(buffer.buf)) {
UnlockReleaseBuffer(buffer.buf);
}
}
/*
* Even if we skipped the heap page update due to the LSN interlock, it's
* still safe to update the visibility map. Any WAL record that clears
* the visibility map bit does so before checking the page LSN, so any
* bits that need to be cleared will still be cleared.
*/
if (XLogReadBufferForRedoExtended(record, HEAP_VISIBLE_VM_BLOCK_NUM, RBM_ZERO_ON_ERROR, false, &vmbuffer) ==
BLK_NEEDS_REDO) {
char* maindata = XLogRecGetData(record);
heap_xlog_visible_operator_vmpage(&vmbuffer, (void*)maindata);
} else if (BufferIsValid(vmbuffer.buf)) {
UnlockReleaseBuffer(vmbuffer.buf);
}
}
void heap_bcm_redo(xl_heap_bcm* xlrec, RelFileNode node, XLogRecPtr lsn)
{
int col = xlrec->col;
Relation reln = CreateFakeRelcacheEntry(node);
Buffer bcmbuffer = InvalidBuffer;
if (col > 0) { /* cloumn store */
BlockNumber cur_bcm_block = 0;
BlockNumber next_bcm_block = 0;
int i = 0;
/* read current bcm block */
cur_bcm_block = HEAPBLK_TO_BCMBLOCK(xlrec->block + i);
next_bcm_block = cur_bcm_block;
BCM_CStore_pin(reln, col, ((xlrec->block + i) * ALIGNOF_CUSIZE), &bcmbuffer);
LockBuffer(bcmbuffer, BUFFER_LOCK_EXCLUSIVE);
do {
/* deal with bcm block switch */
if (next_bcm_block != cur_bcm_block) {
cur_bcm_block = next_bcm_block;
/* release last bcm block and read in the next one */
UnlockReleaseBuffer(bcmbuffer);
BCM_CStore_pin(reln, col, ((xlrec->block + i) * ALIGNOF_CUSIZE), &bcmbuffer);
LockBuffer(bcmbuffer, BUFFER_LOCK_EXCLUSIVE);
}
/*
* Don't set the bit if replay has already passed this point.
* and we are in t_thrd.xlog_cxt.InRecovery, no need to consider log_heap_bcm.
*/
if (!XLByteLE(lsn, PageGetLSN(BufferGetPage(bcmbuffer)))) {
BCMSetStatusBit(reln, xlrec->block + i, bcmbuffer, xlrec->status, col);
ereport(DEBUG2,
(errmsg("BCMSetStatusBit: oid:%u col:%d block:%lu status: %d",
reln->rd_node.relNode,
col,
xlrec->block + i,
NOTSYNCED)));
}
i++;
next_bcm_block = HEAPBLK_TO_BCMBLOCK(xlrec->block + i);
} while (i < xlrec->count);
UnlockReleaseBuffer(bcmbuffer);
} else { /* row store */
BCM_pin(reln, xlrec->block, &bcmbuffer);
LockBuffer(bcmbuffer, BUFFER_LOCK_EXCLUSIVE);
if (!XLByteLE(lsn, PageGetLSN(BufferGetPage(bcmbuffer)))) {
BCMSetStatusBit(reln, xlrec->block, bcmbuffer, xlrec->status, col);
}
UnlockReleaseBuffer(bcmbuffer);
}
FreeFakeRelcacheEntry(reln);
}
/*
* Replay XLOG_HEAP2_BCM record.
*
* Code needs to be rewrite soon.
*/
static void heap_xlog_bcm(XLogReaderState* record)
{
XLogRecPtr lsn = record->EndRecPtr;
xl_heap_bcm* xlrec = (xl_heap_bcm*)XLogRecGetData(record);
RelFileNode tmp_node;
RelFileNodeCopy(tmp_node, xlrec->node, XLogRecGetBucketId(record));
heap_bcm_redo(xlrec, tmp_node, lsn);
}
static void heap_xlog_newpage(XLogReaderState* record)
{
RedoBufferInfo buffer;
/*
* Full-page image (FPI) records contain nothing else but a backup
* block. The block reference must include a full-page image -
* otherwise there would be no point in this record.
*
* No recovery conflicts are generated by these generic records - if a
* resource manager needs to generate conflicts, it has to define a
* separate WAL record type and redo routine.
*/
if (XLogReadBufferForRedo(record, HEAP_NEWPAGE_ORIG_BLOCK_NUM, &buffer) != BLK_RESTORED) {
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("unexpected result when restoring backup block")));
}
UnlockReleaseBuffer(buffer.buf);
}
inline static void heap_xlog_allvisiblecleared(RelFileNode target_node, BlockNumber blkno)
{
Relation reln = CreateFakeRelcacheEntry(target_node);
Buffer vmbuffer = InvalidBuffer;
visibilitymap_pin(reln, blkno, &vmbuffer);
visibilitymap_clear(reln, blkno, vmbuffer);
ReleaseBuffer(vmbuffer);
FreeFakeRelcacheEntry(reln);
}
static void heap_xlog_delete(XLogReaderState* record)
{
xl_heap_delete* xlrec = (xl_heap_delete*)XLogRecGetData(record);
RedoBufferInfo buffer;
/*
* The visibility map may need to be fixed even if the heap page is
* already up-to-date.
*/
if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) {
RelFileNode target_node;
BlockNumber blkno;
XLogRecGetBlockTag(record, HEAP_DELETE_ORIG_BLOCK_NUM, &target_node, NULL, &blkno);
heap_xlog_allvisiblecleared(target_node, blkno);
}
if (XLogReadBufferForRedo(record, HEAP_DELETE_ORIG_BLOCK_NUM, &buffer) == BLK_NEEDS_REDO) {
char* maindata = XLogRecGetData(record);
TransactionId recordxid = XLogRecGetXid(record);
heap_xlog_delete_operator_page(&buffer, (void*)maindata, recordxid);
MarkBufferDirty(buffer.buf);
}
if (BufferIsValid(buffer.buf)) {
UnlockReleaseBuffer(buffer.buf);
}
}
static void heap_xlog_insert(XLogReaderState* record)
{
Pointer rec_data = (Pointer)XLogRecGetData(record);
bool isinit = (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE) != 0;
xl_heap_insert* xlrec = NULL;
RedoBufferInfo buffer;
Size freespace = 0;
XLogRedoAction action;
RelFileNode target_node;
BlockNumber blkno;
if (isinit) {
rec_data += sizeof(TransactionId);
}
xlrec = (xl_heap_insert*)rec_data;
XLogRecGetBlockTag(record, HEAP_INSERT_ORIG_BLOCK_NUM, &target_node, NULL, &blkno);
/*
* The visibility map may need to be fixed even if the heap page is
* already up-to-date.
*/
if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) {
heap_xlog_allvisiblecleared(target_node, blkno);
}
/*
* If we inserted the first and only tuple on the page, re-initialize
* the page from scratch.
*/
if (isinit) {
XLogInitBufferForRedo(record, HEAP_INSERT_ORIG_BLOCK_NUM, &buffer);
action = BLK_NEEDS_REDO;
} else
action = XLogReadBufferForRedo(record, HEAP_INSERT_ORIG_BLOCK_NUM, &buffer);
if (action == BLK_NEEDS_REDO) {
char* maindata = XLogRecGetData(record);
TransactionId recordxid = XLogRecGetXid(record);
Size blkdatalen;
char* blkdata = NULL;
blkdata = XLogRecGetBlockData(record, HEAP_INSERT_ORIG_BLOCK_NUM, &blkdatalen);
heap_xlog_insert_operator_page(
&buffer, (void*)maindata, isinit, (void*)blkdata, blkdatalen, recordxid, &freespace);
MarkBufferDirty(buffer.buf);
}
if (BufferIsValid(buffer.buf)) {
UnlockReleaseBuffer(buffer.buf);
}
/*
* If the page is running low on free space, update the FSM as well.
* Arbitrarily, our definition of "low" is less than 20%. We can't do much
* better than that without knowing the fill-factor for the table.
*
* XXX: Don't do this if the page was restored from full page image. We
* don't bother to update the FSM in that case, it doesn't need to be
* totally accurate anyway.
*/
if (action == BLK_NEEDS_REDO && freespace < BLCKSZ / 5) {
XLogRecordPageWithFreeSpace(target_node, blkno, freespace);
}
}
/*
* Handles MULTI_INSERT record type.
*/
static void heap_xlog_multi_insert(XLogReaderState* record)
{
xl_heap_multi_insert* xlrec = NULL;
RelFileNode rnode;
BlockNumber blkno;
RedoBufferInfo buffer;
Size freespace = 0;
bool isinit = (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE) != 0;
XLogRedoAction action;
Pointer rec_data;
/*
* Insertion doesn't overwrite MVCC data, so no conflict processing is
* required.
*/
rec_data = (Pointer)XLogRecGetData(record);
if (isinit) {
rec_data += sizeof(TransactionId);
}
xlrec = (xl_heap_multi_insert*)rec_data;
XLogRecGetBlockTag(record, HEAP_MULTI_INSERT_ORIG_BLOCK_NUM, &rnode, NULL, &blkno);
/*
* The visibility map may need to be fixed even if the heap page is
* already up-to-date.
*/
if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) {
heap_xlog_allvisiblecleared(rnode, blkno);
}
if (isinit) {
XLogInitBufferForRedo(record, HEAP_MULTI_INSERT_ORIG_BLOCK_NUM, &buffer);
action = BLK_NEEDS_REDO;
} else {
action = XLogReadBufferForRedo(record, HEAP_MULTI_INSERT_ORIG_BLOCK_NUM, &buffer);
}
if (action == BLK_NEEDS_REDO) {
char* maindata = XLogRecGetData(record);
TransactionId recordxid = XLogRecGetXid(record);
Size blkdatalen;
char* blkdata = NULL;
blkdata = XLogRecGetBlockData(record, HEAP_MULTI_INSERT_ORIG_BLOCK_NUM, &blkdatalen);
heap_xlog_multi_insert_operator_page(
&buffer, (void*)maindata, isinit, (void*)blkdata, blkdatalen, recordxid, &freespace);
MarkBufferDirty(buffer.buf);
}
if (BufferIsValid(buffer.buf)) {
UnlockReleaseBuffer(buffer.buf);
}
/*
* If the page is running low on free space, update the FSM as well.
* Arbitrarily, our definition of "low" is less than 20%. We can't do much
* better than that without knowing the fill-factor for the table.
*
* XXX: Don't do this if the page was restored from full page image. We
* don't bother to update the FSM in that case, it doesn't need to be
* totally accurate anyway.
*/
if (action == BLK_NEEDS_REDO && freespace < BLCKSZ / 5) {
XLogRecordPageWithFreeSpace(rnode, blkno, freespace);
}
}
/*
* Handles UPDATE and HOT_UPDATE
*/
static void heap_xlog_update(XLogReaderState* record, bool hot_update)
{
xl_heap_update* xlrec = (xl_heap_update*)XLogRecGetData(record);
RelFileNode rnode;
BlockNumber oldblk;
BlockNumber newblk;
RedoBufferInfo obuffer, nbuffer;
Size freespace = 0;
XLogRedoAction oldaction;
XLogRedoAction newaction;
bool isinit = (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE) != 0;
Pointer rec_data;
rec_data = (Pointer)XLogRecGetData(record);
if (isinit) {
rec_data += sizeof(TransactionId);
}
xlrec = (xl_heap_update*)rec_data;
XLogRecGetBlockTag(record, HEAP_UPDATE_NEW_BLOCK_NUM, &rnode, NULL, &newblk);
if (XLogRecGetBlockTag(record, HEAP_UPDATE_OLD_BLOCK_NUM, NULL, NULL, &oldblk)) {
/* HOT updates are never done across pages */
Assert(!hot_update);
} else {
oldblk = newblk;
}
/*
* The visibility map may need to be fixed even if the heap page is
* already up-to-date.
*/
if (xlrec->flags & XLOG_HEAP_ALL_VISIBLE_CLEARED) {
heap_xlog_allvisiblecleared(rnode, oldblk);
}
/*
* In normal operation, it is important to lock the two pages in
* page-number order, to avoid possible deadlocks against other update
* operations going the other way. However, during WAL replay there can
* be no other update happening, so we don't need to worry about that. But
* we *do* need to worry that we don't expose an inconsistent state to Hot
* Standby queries --- so the original page can't be unlocked before we've
* added the new tuple to the new page.
*/
/* Deal with old tuple version */
oldaction = XLogReadBufferForRedo(
record, (oldblk == newblk) ? HEAP_UPDATE_NEW_BLOCK_NUM : HEAP_UPDATE_OLD_BLOCK_NUM, &obuffer);
if (oldaction == BLK_NEEDS_REDO) {
char* maindata = XLogRecGetData(record);
TransactionId recordxid = XLogRecGetXid(record);
heap_xlog_update_operator_oldpage(&obuffer, (void*)maindata, hot_update, isinit, newblk, recordxid);
MarkBufferDirty(obuffer.buf);
}
/*
* Read the page the new tuple goes into, if different from old.
*/
if (oldblk == newblk) {
nbuffer = obuffer;
newaction = oldaction;
} else if (isinit) {
XLogInitBufferForRedo(record, HEAP_UPDATE_NEW_BLOCK_NUM, &nbuffer);
newaction = BLK_NEEDS_REDO;
} else {
newaction = XLogReadBufferForRedo(record, HEAP_UPDATE_NEW_BLOCK_NUM, &nbuffer);
}
/*
* The visibility map may need to be fixed even if the heap page is
* already up-to-date.
*/
if (xlrec->flags & XLOG_HEAP_NEW_ALL_VISIBLE_CLEARED) {
heap_xlog_allvisiblecleared(rnode, newblk);
}
/* Deal with new tuple */
if (newaction == BLK_NEEDS_REDO) {
char* maindata = XLogRecGetData(record);
Size blkdatalen;
char* blkdata = NULL;
TransactionId recordxid = XLogRecGetXid(record);
blkdata = XLogRecGetBlockData(record, HEAP_UPDATE_NEW_BLOCK_NUM, &blkdatalen);
heap_xlog_update_operator_newpage(
&nbuffer, (void*)maindata, isinit, (void*)blkdata, blkdatalen, recordxid, &freespace);
MarkBufferDirty(nbuffer.buf);
}
if (BufferIsValid(nbuffer.buf) && nbuffer.buf != obuffer.buf) {
UnlockReleaseBuffer(nbuffer.buf);
}
if (BufferIsValid(obuffer.buf)) {
UnlockReleaseBuffer(obuffer.buf);
}
/*
* If the new page is running low on free space, update the FSM as well.
* Arbitrarily, our definition of "low" is less than 20%. We can't do much
* better than that without knowing the fill-factor for the table.
*
* However, don't update the FSM on HOT updates, because after crash
* recovery, either the old or the new tuple will certainly be dead and
* prunable. After pruning, the page will have roughly as much free space
* as it did before the update, assuming the new tuple is about the same
* size as the old one.
*
* XXX: Don't do this if the page was restored from full page image. We
* don't bother to update the FSM in that case, it doesn't need to be
* totally accurate anyway.
*/
if (newaction == BLK_NEEDS_REDO && !hot_update && freespace < BLCKSZ / 5) {
XLogRecordPageWithFreeSpace(rnode, newblk, freespace);
}
}
void heap_xlog_page_upgrade(XLogReaderState* record)
{
RedoBufferInfo buffer;
if (BLK_NEEDS_REDO == XLogReadBufferForRedo(record, HEAP_PAGE_UPDATE_ORIG_BLOCK_NUM, &buffer)) {
heap_xlog_page_upgrade_operator_page(&buffer);
MarkBufferDirty(buffer.buf);
}
if (BufferIsValid(buffer.buf)) {
UnlockReleaseBuffer(buffer.buf);
}
}
static void heap_xlog_lock(XLogReaderState* record)
{
RedoBufferInfo buffer;
if (XLogReadBufferForRedo(record, HEAP_LOCK_ORIG_BLOCK_NUM, &buffer) == BLK_NEEDS_REDO) {
char* maindata = XLogRecGetData(record);
heap_xlog_lock_operator_page(&buffer, (void*)maindata);
MarkBufferDirty(buffer.buf);
}
if (BufferIsValid(buffer.buf)) {
UnlockReleaseBuffer(buffer.buf);
}
}
static void heap_xlog_inplace(XLogReaderState* record)
{
RedoBufferInfo buffer;
if (XLogReadBufferForRedo(record, HEAP_INPLACE_ORIG_BLOCK_NUM, &buffer) == BLK_NEEDS_REDO) {
char* maindata = XLogRecGetData(record);
Size blkdatalen;
char* blkdata = NULL;
blkdata = XLogRecGetBlockData(record, HEAP_INPLACE_ORIG_BLOCK_NUM, &blkdatalen);
heap_xlog_inplace_operator_page(&buffer, (void*)maindata, (void*)blkdata, blkdatalen);
MarkBufferDirty(buffer.buf);
}
if (BufferIsValid(buffer.buf)) {
UnlockReleaseBuffer(buffer.buf);
}
}
static void heap_xlog_base_shift(XLogReaderState* record)
{
RedoBufferInfo buffer;
if (XLogReadBufferForRedo(record, HEAP_BASESHIFT_ORIG_BLOCK_NUM, &buffer) == BLK_NEEDS_REDO) {
char* maindata = XLogRecGetData(record);
heap_xlog_base_shift_operator_page(&buffer, (void*)maindata);
MarkBufferDirty(buffer.buf);
}
if (BufferIsValid(buffer.buf)) {
UnlockReleaseBuffer(buffer.buf);
}
}
void heap_redo(XLogReaderState* record)
{
uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
/*
* These operations don't overwrite MVCC data so no conflict processing is
* required. The ones in heap2 rmgr do.
*/
switch (info & XLOG_HEAP_OPMASK) {
case XLOG_HEAP_INSERT:
heap_xlog_insert(record);
break;
case XLOG_HEAP_DELETE:
heap_xlog_delete(record);
break;
case XLOG_HEAP_UPDATE:
heap_xlog_update(record, false);
break;
case XLOG_HEAP_BASE_SHIFT:
heap_xlog_base_shift(record);
break;
case XLOG_HEAP_HOT_UPDATE:
heap_xlog_update(record, true);
break;
case XLOG_HEAP_NEWPAGE:
heap_xlog_newpage(record);
break;
case XLOG_HEAP_LOCK:
heap_xlog_lock(record);
break;
case XLOG_HEAP_INPLACE:
heap_xlog_inplace(record);
break;
default:
ereport(PANIC, (errmsg("heap_redo: unknown op code %hhu", info)));
}
}
void heap2_redo(XLogReaderState* record)
{
uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
switch (info & XLOG_HEAP_OPMASK) {
case XLOG_HEAP2_FREEZE:
heap_xlog_freeze(record);
break;
case XLOG_HEAP2_CLEAN:
heap_xlog_clean(record);
break;
case XLOG_HEAP2_CLEANUP_INFO:
heap_xlog_cleanup_info(record);
break;
case XLOG_HEAP2_VISIBLE:
heap_xlog_visible(record);
break;
case XLOG_HEAP2_BCM:
heap_xlog_bcm(record);
break;
case XLOG_HEAP2_MULTI_INSERT:
heap_xlog_multi_insert(record);
break;
case XLOG_HEAP2_LOGICAL_NEWPAGE:
heap_xlog_logical_new_page(record);
break;
case XLOG_HEAP2_PAGE_UPGRADE:
heap_xlog_page_upgrade(record);
break;
default:
ereport(PANIC, (errmsg("heap2_redo: unknown op code %hhu", info)));
}
}
void heap3_redo(XLogReaderState* record)
{
uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
switch (info & XLOG_HEAP_OPMASK) {
case XLOG_HEAP3_NEW_CID:
break;
case XLOG_HEAP3_REWRITE:
break;
default:
ereport(PANIC, (errmsg("heap3_redo: unknown op code %hhu", info)));
}
}
static HTAB* heap_bucketid_hashtbl_create()
{
HASHCTL hash_ctrl;
HTAB* hashtbl = NULL;
errno_t rc;
rc = memset_s(&hash_ctrl, sizeof(hash_ctrl), 0, sizeof(hash_ctrl));
securec_check(rc, "", "");
hash_ctrl.hcxt = (MemoryContext)CurrentMemoryContext;
hash_ctrl.hash = tag_hash;
hash_ctrl.keysize = sizeof(int4);
hash_ctrl.entrysize = sizeof(int4);
hashtbl = hash_create("CopyFromFlushHashTable", 64, &hash_ctrl, (HASH_CONTEXT | HASH_FUNCTION | HASH_ELEM));
return hashtbl;
}
static void heap_do_sync_disk(Relation rel)
{
HTAB *hashtbl = NULL;
if (RELATION_CREATE_BUCKET(rel)) {
hashtbl = heap_bucketid_hashtbl_create();
}
FlushRelationBuffers(rel, hashtbl);
RelationOpenSmgr(rel);
if (hashtbl == NULL) {
smgrimmedsync(rel->rd_smgr, MAIN_FORKNUM);
} else {
HASH_SEQ_STATUS status;
int *bucketnode = NULL;
hash_seq_init(&status, hashtbl);
RelFileNode rd_node = rel->rd_node;
while ((bucketnode = (int4 *)hash_seq_search(&status)) != NULL) {
if (*bucketnode == InvalidBktId) {
continue;
}
rd_node.bucketNode = *bucketnode;
/* FlushRelationBuffers will have opened rd_smgr */
SMgrRelation oreln = smgropen(rd_node, InvalidBackendId);
smgrimmedsync(oreln, MAIN_FORKNUM);
smgrclose(oreln);
}
hash_destroy(hashtbl);
}
}
/*
* heap_sync_internal() is a internal function called by heap_sync(),
* no matter the rel is a non-partitioned relation or FakeRel of partition,
* this function should work for both.
*/
void heap_sync_internal(Relation rel, Oid toastHeapOid, LOCKMODE lockmode)
{
/* main heap */
heap_do_sync_disk(rel);
/* FSM is not critical, don't bother syncing it
*
* toast heap, if any
*/
if (OidIsValid(toastHeapOid)) {
Relation toastrel;
toastrel = heap_open(toastHeapOid, lockmode);
heap_do_sync_disk(toastrel);
heap_close(toastrel, lockmode);
}
}
/*
* heap_sync - sync a heap, for use when no WAL has been written
*
* This forces the heap contents (including TOAST heap if any) down to disk.
* If we skipped using WAL, and WAL is otherwise needed, we must force the
* relation down to disk before it's safe to commit the transaction. This
* requires writing out any dirty buffers and then doing a forced fsync.
*
* Indexes are not touched. (Currently, index operations associated with
* the commands that use this are WAL-logged and so do not need fsync.
* That behavior might change someday, but in any case it's likely that
* any fsync decisions required would be per-index and hence not appropriate
* to be done here.)
*/
void heap_sync(Relation rel, LOCKMODE lockmode)
{
Assert(!RelationIsBucket(rel));
bool heapIsPartitioned = RELATION_IS_PARTITIONED(rel);
LOCKMODE toast_lockmode = (lockmode == NoLock) ? NoLock : AccessShareLock;
/* non-WAL-logged tables or dfs tables never need fsync */
if (!RelationNeedsWAL(rel) || RelationIsDfsStore(rel)) {
return;
}
if (!heapIsPartitioned) {
heap_sync_internal(rel, rel->rd_rel->reltoastrelid, toast_lockmode);
} else {
List* partitionList = relationGetPartitionList(rel, lockmode);
ListCell* cell = NULL;
foreach (cell, partitionList) {
Partition partition = (Partition)lfirst(cell);
Relation partitionRel = partitionGetRelation(rel, partition);
heap_sync_internal(partitionRel, partition->pd_part->reltoastrelid, toast_lockmode);
releaseDummyRelation(&partitionRel);
}
/* remember to release partition list */
if (partitionList != NULL) {
releasePartitionList(rel, &partitionList, NoLock);
}
}
}
void partition_sync(Relation rel, Oid partition_id, LOCKMODE partition_lockmode)
{
LOCKMODE toast_lockmode = (partition_lockmode == NoLock) ? NoLock : AccessShareLock;
/* non-WAL-logged tables or dfs tables never need fsync */
if (!RelationNeedsWAL(rel) || RelationIsDfsStore(rel)) {
return;
}
if (!RELATION_IS_PARTITIONED(rel) || !OidIsValid(partition_id)) {
return;
}
Partition partition = partitionOpen(rel, partition_id, partition_lockmode);
Relation partionRel = partitionGetRelation(rel, partition);
heap_sync_internal(partionRel, partition->pd_part->reltoastrelid, toast_lockmode);
releaseDummyRelation(&partionRel);
partitionClose(rel, partition, NoLock);
}
/*
* @@GaussDB@@
* Target : data partition
* Brief : open any partition by partition OID
* Description : If lockmode is not "NoLock", retry on performing lock the partition for retryCount times
: if retryCount is reached, return NULL.
* Notes :
*/
Partition partitionOpenWithRetry(Relation relation, Oid partition_id, LOCKMODE lockmode, const char* stmt)
{
Partition p;
Assert(lockmode >= NoLock && lockmode < MAX_LOCKMODES);
Assert(PointerIsValid(relation));
Assert(OidIsValid(partition_id));
if (relation->rd_rel->relkind != RELKIND_RELATION && relation->rd_rel->relkind != RELKIND_INDEX) {
ereport(ERROR,
(errcode(ERRCODE_DATA_CORRUPTED),
(errmsg("relation \"%s\" is not table or index", RelationGetRelationName(relation)))));
}
/* step 1: try to lock the partition */
/* Get the lock before trying to open the relcache entry */
if (lockmode != NoLock && !ConditionalLockPartitionWithRetry(relation, partition_id, lockmode)) {
char* dbname = get_database_name(u_sess->proc_cxt.MyDatabaseId);
if (dbname == NULL) {
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_DATABASE),
errmsg("database with OID %u does not exist",
u_sess->proc_cxt.MyDatabaseId)));
}
ereport(LOG,
(errmsg("try to open partition \"%s.%s.%s.%s\" failed: "
"could not (re)acquire lock \"%d\" within timeout %d seconds, when \"%s\"",
dbname,
get_namespace_name(RelationGetNamespace(relation), true),
RelationGetRelationName(relation),
getPartitionName(partition_id, false),
lockmode,
u_sess->attr.attr_storage.partition_lock_upgrade_timeout,
stmt ? stmt : "unkown operations")));
return NULL;
}
/* step 2: get the partiton object */
/* The partcache does all the real work... */
p = PartitionIdGetPartition(partition_id);
if (!PartitionIsValid(p)) {
ereport(
ERROR, (errcode(ERRCODE_RELATION_OPEN_ERROR), errmsg("could not open partition with OID %u", partition_id)));
}
Assert(relation->rd_id == p->pd_part->parentid);
PartitionOpenSmgr(p);
#ifdef PGXC
if (IS_PGXC_DATANODE) {
#endif
pgstat_initstats_partition(p);
#ifdef PGXC
}
#endif
return p;
}
/*
* @@GaussDB@@
* Target : data partition
* Brief : open any partition by partition OID
* Description : If lockmode is not "NoLock", the specified kind of lock is
* : obtained on the partition. (Generally, NoLock should only
* : be used if the caller knows it has some appropriate lock
* : on the partiiton already.)
* Notes :
*/
Partition partitionOpen(Relation relation, Oid partition_id, LOCKMODE lockmode, int2 bucket_id)
{
Partition p;
if (!OidIsValid(partition_id)) {
ereport(ERROR, (errcode(ERRCODE_RELATION_OPEN_ERROR), errmsg("partition %u is invalid", partition_id)));
}
Assert(lockmode >= NoLock && lockmode < MAX_LOCKMODES);
Assert(PointerIsValid(relation));
/* Get the lock before trying to open the relcache entry */
if (lockmode != NoLock) {
if (relation->rd_rel->relkind == RELKIND_RELATION) {
LockPartition(relation->rd_id, partition_id, lockmode, PARTITION_LOCK);
} else if (relation->rd_rel->relkind == RELKIND_INDEX) {
LockPartition(relation->rd_id, partition_id, lockmode, PARTITION_LOCK);
} else {
ereport(ERROR,
(errcode(ERRCODE_RELATION_OPEN_ERROR),
errmsg("openning partition %u, but relation %s %u is neither table nor index",
partition_id,
RelationGetRelationName(relation),
RelationGetRelid(relation))));
}
}
/* The partcache does all the real work... */
p = PartitionIdGetPartition(partition_id);
if (!PartitionIsValid(p)) {
ereport(
ERROR, (errcode(ERRCODE_RELATION_OPEN_ERROR), errmsg("could not open partition with OID %u", partition_id)));
}
Assert(relation->rd_id == p->pd_part->parentid);
PartitionOpenSmgr(p);
#ifdef PGXC
if (IS_PGXC_DATANODE) {
#endif
pgstat_initstats_partition(p);
#ifdef PGXC
}
#endif
if (bucket_id != InvalidBktId) {
p = bucketGetPartition(p, bucket_id);
}
return p;
}
/*
* @@GaussDB@@
* Target : data partition
* Brief : open any partition by partition OID
* Description : Same as partitionOpen, except return NULL instead of failing
* : if the partition does not exist.
* Notes :
*/
Partition tryPartitionOpen(Relation relation, Oid partition_id, LOCKMODE lockmode)
{
Partition p;
PartitionIdentifier* partID = NULL;
Assert(lockmode >= NoLock && lockmode < MAX_LOCKMODES);
Assert(PointerIsValid(relation));
Assert(OidIsValid(partition_id));
/* Get the lock before trying to open the relcache entry */
if (lockmode != NoLock) {
if (relation->rd_rel->relkind == RELKIND_RELATION) {
partID = partOidGetPartID(relation, partition_id);
switch (partID->partArea) {
case PART_AREA_RANGE:
LockPartition(relation->rd_id, partition_id, lockmode, PARTITION_LOCK);
break;
case PART_AREA_INTERVAL:
LockPartition(relation->rd_id, partition_id, lockmode, PARTITION_LOCK);
break;
default:
break;
}
pfree(partID);
} else if (relation->rd_rel->relkind == RELKIND_INDEX) {
LockPartition(relation->rd_id, partition_id, lockmode, PARTITION_LOCK);
} else {
ereport(ERROR,
(errcode(ERRCODE_RELATION_OPEN_ERROR),
errmsg("openning partition %u, but relation %s %u is neither table nor index",
partition_id,
RelationGetRelationName(relation),
RelationGetRelid(relation))));
}
}
/*
* Now that we have the lock, probe to see if the partition really exists
* or not.
*/
if (!SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(partition_id))) {
/* Release useless lock */
if (lockmode != NoLock) {
if (relation->rd_rel->relkind == RELKIND_RELATION) {
partID = partOidGetPartID(relation, partition_id);
switch (partID->partArea) {
case PART_AREA_RANGE:
UnlockPartition(relation->rd_id, partition_id, lockmode, PARTITION_LOCK);
break;
case PART_AREA_INTERVAL:
UnlockPartition(relation->rd_id, partition_id, lockmode, PARTITION_LOCK);
break;
default:
break;
}
pfree(partID);
} else if (relation->rd_rel->relkind == RELKIND_INDEX) {
UnlockPartition(relation->rd_id, partition_id, lockmode, PARTITION_LOCK);
} else {
ereport(ERROR,
(errcode(ERRCODE_RELATION_OPEN_ERROR),
errmsg("closing partition %u, but relation %u is neither table nor index",
partition_id,
relation->rd_id)));
}
}
return NULL;
}
/* The partcache does all the real work... */
p = PartitionIdGetPartition(partition_id);
if (!PartitionIsValid(p)) {
ereport(
ERROR, (errcode(ERRCODE_RELATION_OPEN_ERROR), errmsg("could not open partition with OID %u", partition_id)));
}
Assert(relation->rd_id == p->pd_part->parentid);
PartitionOpenSmgr(p);
#ifdef PGXC
if (IS_PGXC_DATANODE) {
#endif
pgstat_initstats_partition(p);
#ifdef PGXC
}
#endif
return p;
}
/*
* @brief: close the partiiton
* If lockmode is not "NoLock", we then release the specified lock.
* Notes: it is often sensible to hold a lock beyond partitionClose; in that case,
* the lock is released automatically at xact end.
*/
void partitionClose(Relation relation, Partition partition, LOCKMODE lockmode)
{
PartitionIdentifier* partID = NULL;
Partition part = partition;
if (PartitionIsBucket(partition)) {
part = partition->parent;
bucketClosePartition(partition);
}
Assert(lockmode >= NoLock && lockmode < MAX_LOCKMODES);
Assert(PointerIsValid(relation));
Assert(PointerIsValid(part));
Assert(relation->rd_id == part->pd_part->parentid);
/* The partcache does the real work... */
PartitionClose(part);
if (lockmode != NoLock) {
if (relation->rd_rel->relkind == RELKIND_RELATION) {
partID = partOidGetPartID(relation, part->pd_id);
switch (partID->partArea) {
case PART_AREA_RANGE:
UnlockPartition(relation->rd_id, part->pd_id, lockmode, PARTITION_LOCK);
break;
case PART_AREA_INTERVAL:
UnlockPartition(relation->rd_id, part->pd_id, lockmode, PARTITION_LOCK);
break;
default:
break;
}
pfree(partID);
} else if (relation->rd_rel->relkind == RELKIND_INDEX) {
UnlockPartition(relation->rd_id, part->pd_id, lockmode, PARTITION_LOCK);
} else {
ereport(ERROR,
(errcode(ERRCODE_RELATION_CLOSE_ERROR),
errmsg("closing partition %u, but relation %u is neither table nor index",
part->pd_id,
relation->rd_id)));
}
}
}
void PushHeapPageToDataQueue(Buffer buffer)
{
RelFileNode rnode; /* physical relation identifier */
ForkNumber forkNum;
BlockNumber blockNum; /* blknum relative to begin of reln */
BufferGetTag(buffer, &rnode, &forkNum, &blockNum);
Assert(forkNum == MAIN_FORKNUM);
/* Put page to sender queue */
t_thrd.proc->waitDataSyncPoint =
PushToSenderQueue(rnode, blockNum, ROW_STORE, (char*)BufferGetPage(buffer), BLCKSZ, 0, 0);
if (u_sess->attr.attr_storage.HaModuleDebug) {
ereport(LOG,
(errmsg("HA-PushToSenderQueue done: rnode %u/%u/%u, blockno %u, waitpoint %u/%u",
rnode.spcNode,
rnode.dbNode,
rnode.relNode,
blockNum,
t_thrd.proc->waitDataSyncPoint.queueid,
t_thrd.proc->waitDataSyncPoint.queueoff)));
}
/* Wake up all datasenders to send Page if replication is enabled */
if (g_instance.attr.attr_storage.max_wal_senders > 0) {
DataSndWakeup();
}
}
void heap_init_parallel_seqscan(HeapScanDesc scan, int32 dop, ScanDirection dir)
{
if (!scan || scan->rs_nblocks == 0) {
return;
}
if (dop <= 1) {
return;
}
scan->dop = dop;
uint32 paral_blocks = u_sess->stream_cxt.smp_id * PARALLEL_SCAN_GAP;
/* If not enough pages to divide into every worker. */
if (scan->rs_nblocks <= paral_blocks) {
scan->rs_startblock = 0;
scan->rs_nblocks = 0;
return;
}
if (ScanDirectionIsBackward(dir)) {
paral_blocks = (scan->rs_nblocks - 1) - paral_blocks;
if (scan->rs_isRangeScanInRedis) {
scan->rs_startblock = paral_blocks;
} else {
scan->rs_startblock += paral_blocks;
}
return;
}
/* If not range scan in redistribute, just start from 0. */
if (scan->rs_isRangeScanInRedis) {
scan->rs_startblock += paral_blocks;
} else {
scan->rs_startblock = paral_blocks;
}
}