first commit for openGauss server
This commit is contained in:
458
src/gausskernel/runtime/executor/nodeLockRows.cpp
Executable file
458
src/gausskernel/runtime/executor/nodeLockRows.cpp
Executable file
@ -0,0 +1,458 @@
|
||||
/* -------------------------------------------------------------------------
|
||||
*
|
||||
* nodeLockRows.cpp
|
||||
* Routines to handle FOR UPDATE/FOR SHARE row locking
|
||||
*
|
||||
* 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/runtime/executor/nodeLockRows.cpp
|
||||
*
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
* INTERFACE ROUTINES
|
||||
* ExecLockRows - fetch locked rows
|
||||
* ExecInitLockRows - initialize node and subnodes..
|
||||
* ExecEndLockRows - shutdown node and subnodes
|
||||
*/
|
||||
#include "postgres.h"
|
||||
#include "knl/knl_variable.h"
|
||||
|
||||
#include "access/xact.h"
|
||||
#include "executor/executor.h"
|
||||
#include "executor/nodeLockRows.h"
|
||||
#ifdef PGXC
|
||||
#include "pgxc/pgxc.h"
|
||||
#endif
|
||||
#include "storage/bufmgr.h"
|
||||
#include "utils/rel.h"
|
||||
#include "utils/rel_gs.h"
|
||||
#include "utils/tqual.h"
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* ExecLockRows
|
||||
* return: a tuple or NULL
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
TupleTableSlot* ExecLockRows(LockRowsState* node)
|
||||
{
|
||||
TupleTableSlot* slot = NULL;
|
||||
EState* estate = NULL;
|
||||
PlanState* outer_plan = NULL;
|
||||
bool epq_started = false;
|
||||
ListCell* lc = NULL;
|
||||
int2 bucket_id = InvalidOid;
|
||||
Relation target_rel = NULL;
|
||||
Partition target_part = NULL;
|
||||
Relation bucket_rel = NULL;
|
||||
/*
|
||||
* get information from the node
|
||||
*/
|
||||
estate = node->ps.state;
|
||||
outer_plan = outerPlanState(node);
|
||||
|
||||
/*
|
||||
* EvalPlanQual is called when concurrent lockrows or update or delete
|
||||
* we should skip early free.
|
||||
*/
|
||||
bool orig_early_free = outer_plan->state->es_skip_early_free;
|
||||
bool orig_early_deinit = outer_plan->state->es_skip_early_deinit_consumer;
|
||||
|
||||
outer_plan->state->es_skip_early_free = true;
|
||||
outer_plan->state->es_skip_early_deinit_consumer = true;
|
||||
|
||||
/*
|
||||
* Get next tuple from subplan, if any.
|
||||
*/
|
||||
lnext:
|
||||
/*
|
||||
* We must reset the targetPart and targetRel to NULL for correct used
|
||||
* searchFakeReationForPartitionOid in goto condition.
|
||||
*/
|
||||
target_rel = NULL;
|
||||
target_part = NULL;
|
||||
|
||||
slot = ExecProcNode(outer_plan);
|
||||
|
||||
outer_plan->state->es_skip_early_free = orig_early_free;
|
||||
outer_plan->state->es_skip_early_deinit_consumer = orig_early_deinit;
|
||||
|
||||
if (TupIsNull(slot))
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Attempt to lock the source tuple(s). (Note we only have locking
|
||||
* rowmarks in lr_arowMarks.)
|
||||
*/
|
||||
epq_started = false;
|
||||
foreach (lc, node->lr_arowMarks) {
|
||||
ExecAuxRowMark* aerm = (ExecAuxRowMark*)lfirst(lc);
|
||||
ExecRowMark* erm = aerm->rowmark;
|
||||
Datum datum;
|
||||
bool isNull = false;
|
||||
HeapTupleData tuple;
|
||||
Buffer buffer;
|
||||
ItemPointerData update_ctid;
|
||||
TransactionId update_xmax;
|
||||
LockTupleMode lock_mode;
|
||||
HTSU_Result test;
|
||||
HeapTuple copy_tuple;
|
||||
|
||||
/* clear any leftover test tuple for this rel */
|
||||
if (node->lr_epqstate.estate != NULL)
|
||||
EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti, NULL);
|
||||
|
||||
/* if child rel, must check whether it produced this row */
|
||||
if (erm->rti != erm->prti) {
|
||||
Oid table_oid;
|
||||
|
||||
datum = ExecGetJunkAttribute(slot, aerm->toidAttNo, &isNull);
|
||||
/* shouldn't ever get a null result... */
|
||||
if (isNull) {
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_NULL_JUNK_ATTRIBUTE), errmsg("tableoid is NULL when try to lock current row.")));
|
||||
}
|
||||
|
||||
table_oid = DatumGetObjectId(datum);
|
||||
if (table_oid != RelationGetRelid(erm->relation)) {
|
||||
/* this child is inactive right now */
|
||||
ItemPointerSetInvalid(&(erm->curCtid));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (RELATION_OWN_BUCKET(erm->relation)) {
|
||||
Datum bucket_datum;
|
||||
bucket_datum = ExecGetJunkAttribute(slot, aerm->tbidAttNo, &isNull);
|
||||
if (isNull)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_NULL_JUNK_ATTRIBUTE), errmsg("bucketid is NULL when try to lock current row.")));
|
||||
bucket_id = DatumGetObjectId(bucket_datum);
|
||||
Assert(bucket_id != InvalidBktId);
|
||||
}
|
||||
/* for partitioned table */
|
||||
if (PointerIsValid(erm->relation->partMap)) {
|
||||
Oid tblid = InvalidOid;
|
||||
Datum part_datum;
|
||||
|
||||
part_datum = ExecGetJunkAttribute(slot, aerm->toidAttNo, &isNull);
|
||||
|
||||
tblid = DatumGetObjectId(part_datum);
|
||||
/* if it is a partition */
|
||||
if (tblid != erm->relation->rd_id) {
|
||||
searchFakeReationForPartitionOid(estate->esfRelations,
|
||||
estate->es_query_cxt,
|
||||
erm->relation,
|
||||
tblid,
|
||||
target_rel,
|
||||
target_part,
|
||||
RowExclusiveLock);
|
||||
Assert(tblid == target_rel->rd_id);
|
||||
}
|
||||
} else {
|
||||
/* for non-partitioned table */
|
||||
target_rel = erm->relation;
|
||||
}
|
||||
|
||||
if (target_rel == NULL) {
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("ExecLockRows:target relation cannot be NULL")));
|
||||
}
|
||||
|
||||
/* fetch the tuple's ctid */
|
||||
datum = ExecGetJunkAttribute(slot, aerm->ctidAttNo, &isNull);
|
||||
/* shouldn't ever get a null result... */
|
||||
if (isNull)
|
||||
ereport(
|
||||
ERROR, (errcode(ERRCODE_NULL_JUNK_ATTRIBUTE), errmsg("ctid is NULL when try to lock current row.")));
|
||||
tuple.t_self = *((ItemPointer)DatumGetPointer(datum));
|
||||
|
||||
bucket_rel = target_rel;
|
||||
if (RELATION_OWN_BUCKET(target_rel)) {
|
||||
searchHBucketFakeRelation(estate->esfRelations, estate->es_query_cxt, target_rel, bucket_id, bucket_rel);
|
||||
}
|
||||
/* okay, try to lock the tuple */
|
||||
if (erm->markType == ROW_MARK_EXCLUSIVE)
|
||||
lock_mode = LockTupleExclusive;
|
||||
else
|
||||
lock_mode = LockTupleShared;
|
||||
|
||||
test = heap_lock_tuple(
|
||||
bucket_rel, &tuple, &buffer, &update_ctid, &update_xmax, estate->es_output_cid, lock_mode, erm->noWait);
|
||||
ReleaseBuffer(buffer);
|
||||
switch (test) {
|
||||
case HeapTupleSelfUpdated:
|
||||
/* treat it as deleted; do not process */
|
||||
goto lnext;
|
||||
|
||||
case HeapTupleMayBeUpdated:
|
||||
/* got the lock successfully */
|
||||
break;
|
||||
|
||||
case HeapTupleUpdated:
|
||||
if (IsolationUsesXactSnapshot())
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
|
||||
errmsg("could not serialize access due to concurrent update")));
|
||||
if (ItemPointerEquals(&update_ctid, &tuple.t_self)) {
|
||||
/* Tuple was deleted, so don't return it */
|
||||
goto lnext;
|
||||
}
|
||||
|
||||
/*
|
||||
* EvalPlanQual need to reinitialize child plan to do some recheck due to concurrent update,
|
||||
* but we wrap the left tree of Stream node in backend thread. So the child plan cannot be
|
||||
* reinitialized successful now.
|
||||
*/
|
||||
if (IS_PGXC_DATANODE && u_sess->exec_cxt.under_stream_runtime &&
|
||||
estate->es_plannedstmt->num_streams > 0) {
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_STREAM_CONCURRENT_UPDATE),
|
||||
errmsg("concurrent update under Stream mode is not yet supported")));
|
||||
}
|
||||
|
||||
/* updated, so fetch and lock the updated version */
|
||||
copy_tuple = EvalPlanQualFetch(estate, bucket_rel, lock_mode, &update_ctid, update_xmax);
|
||||
if (copy_tuple == NULL) {
|
||||
/* Tuple was deleted, so don't return it */
|
||||
goto lnext;
|
||||
}
|
||||
/* remember the actually locked tuple's TID */
|
||||
tuple.t_self = copy_tuple->t_self;
|
||||
|
||||
/*
|
||||
* Need to run a recheck subquery. Initialize EPQ state if we
|
||||
* didn't do so already.
|
||||
*/
|
||||
if (!epq_started) {
|
||||
EvalPlanQualBegin(&node->lr_epqstate, estate);
|
||||
epq_started = true;
|
||||
}
|
||||
|
||||
/* Store target tuple for relation's scan node */
|
||||
EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti, copy_tuple);
|
||||
|
||||
/* Continue loop until we have all target tuples */
|
||||
break;
|
||||
|
||||
default:
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
|
||||
errmsg("unrecognized heap_lock_tuple status: %d when lock a tuple", test)));
|
||||
}
|
||||
|
||||
/* Remember locked tuple's TID for WHERE CURRENT OF */
|
||||
erm->curCtid = tuple.t_self;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we need to do EvalPlanQual testing, do so.
|
||||
*/
|
||||
if (epq_started) {
|
||||
struct {
|
||||
HeapTupleHeaderData hdr;
|
||||
char data[MaxHeapTupleSize];
|
||||
} tbuf;
|
||||
|
||||
/*
|
||||
* First, fetch a copy of any rows that were successfully locked
|
||||
* without any update having occurred. (We do this in a separate pass
|
||||
* so as to avoid overhead in the common case where there are no
|
||||
* concurrent updates.)
|
||||
*/
|
||||
foreach (lc, node->lr_arowMarks) {
|
||||
ExecAuxRowMark* aerm = (ExecAuxRowMark*)lfirst(lc);
|
||||
ExecRowMark* erm = aerm->rowmark;
|
||||
HeapTupleData tuple;
|
||||
Buffer buffer;
|
||||
|
||||
/* ignore non-active child tables */
|
||||
if (!ItemPointerIsValid(&(erm->curCtid))) {
|
||||
Assert(erm->rti != erm->prti); /* check it's child table */
|
||||
continue;
|
||||
}
|
||||
|
||||
if (EvalPlanQualGetTuple(&node->lr_epqstate, erm->rti) != NULL)
|
||||
continue; /* it was updated and fetched above */
|
||||
|
||||
/* for partitioned table */
|
||||
if (PointerIsValid(erm->relation->partMap)) {
|
||||
Datum partdatum;
|
||||
Oid tblid = InvalidOid;
|
||||
bool partisNull = false;
|
||||
|
||||
partdatum = ExecGetJunkAttribute(slot, aerm->toidAttNo, &partisNull);
|
||||
tblid = DatumGetObjectId(partdatum);
|
||||
/* if it is a partition */
|
||||
if (tblid != erm->relation->rd_id) {
|
||||
searchFakeReationForPartitionOid(estate->esfRelations,
|
||||
estate->es_query_cxt,
|
||||
erm->relation,
|
||||
tblid,
|
||||
target_rel,
|
||||
target_part,
|
||||
RowExclusiveLock);
|
||||
}
|
||||
} else {
|
||||
/* for non-partitioned table */
|
||||
target_rel = erm->relation;
|
||||
}
|
||||
|
||||
if (target_rel == NULL) {
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
|
||||
errmsg("ExecLockRows:target relation cannot be NULL for plan qual recheck.")));
|
||||
}
|
||||
|
||||
/* okay, fetch the tuple */
|
||||
tuple.t_self = erm->curCtid;
|
||||
/* Must set a private data buffer for heap_fetch */
|
||||
tuple.t_data = &tbuf.hdr;
|
||||
bucket_rel = target_rel;
|
||||
if (RELATION_OWN_BUCKET(target_rel)) {
|
||||
bucket_id = computeTupleBucketId(target_rel, &tuple);
|
||||
searchHBucketFakeRelation(estate->esfRelations, estate->es_query_cxt, target_rel, bucket_id, bucket_rel);
|
||||
}
|
||||
if (!heap_fetch(bucket_rel, SnapshotAny, &tuple, &buffer, false, NULL))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FETCH_DATA_FAILED), errmsg("failed to fetch tuple for EvalPlanQual recheck")));
|
||||
|
||||
/* successful, copy and store tuple */
|
||||
EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti, heap_copytuple(&tuple));
|
||||
ReleaseBuffer(buffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Now fetch any non-locked source rows --- the EPQ logic knows how to
|
||||
* do that.
|
||||
*/
|
||||
EvalPlanQualSetSlot(&node->lr_epqstate, slot);
|
||||
EvalPlanQualFetchRowMarks(&node->lr_epqstate);
|
||||
|
||||
/*
|
||||
* And finally we can re-evaluate the tuple.
|
||||
*/
|
||||
slot = EvalPlanQualNext(&node->lr_epqstate);
|
||||
if (TupIsNull(slot)) {
|
||||
/* Updated tuple fails qual, so ignore it and go on */
|
||||
goto lnext;
|
||||
}
|
||||
}
|
||||
|
||||
/* Got all locks, so return the current tuple */
|
||||
return slot;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* ExecInitLockRows
|
||||
*
|
||||
* This initializes the LockRows node state structures and
|
||||
* the node's subplan.
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
LockRowsState* ExecInitLockRows(LockRows* node, EState* estate, int eflags)
|
||||
{
|
||||
LockRowsState* lrstate = makeNode(LockRowsState);
|
||||
Plan* outer_plan = outerPlan(node);
|
||||
List* epq_arowmarks = NIL;
|
||||
ListCell* lc = NULL;
|
||||
|
||||
/* check for unsupported flags */
|
||||
Assert(!(eflags & EXEC_FLAG_MARK));
|
||||
|
||||
lrstate->ps.plan = (Plan*)node;
|
||||
lrstate->ps.state = estate;
|
||||
|
||||
/*
|
||||
* Miscellaneous initialization
|
||||
*
|
||||
* LockRows nodes never call ExecQual or ExecProject.
|
||||
*/
|
||||
/*
|
||||
* Tuple table initialization (XXX not actually used...)
|
||||
*/
|
||||
ExecInitResultTupleSlot(estate, &lrstate->ps);
|
||||
|
||||
/*
|
||||
* then initialize outer plan
|
||||
*/
|
||||
outerPlanState(lrstate) = ExecInitNode(outer_plan, estate, eflags);
|
||||
|
||||
/*
|
||||
* LockRows nodes do no projections, so initialize projection info for
|
||||
* this node appropriately
|
||||
*/
|
||||
ExecAssignResultTypeFromTL(&lrstate->ps);
|
||||
lrstate->ps.ps_ProjInfo = NULL;
|
||||
|
||||
/*
|
||||
* Locate the ExecRowMark(s) that this node is responsible for, and
|
||||
* construct ExecAuxRowMarks for them. (InitPlan should already have
|
||||
* built the global list of ExecRowMarks.)
|
||||
*/
|
||||
lrstate->lr_arowMarks = NIL;
|
||||
epq_arowmarks = NIL;
|
||||
foreach (lc, node->rowMarks) {
|
||||
PlanRowMark* rc = (PlanRowMark*)lfirst(lc);
|
||||
ExecRowMark* erm = NULL;
|
||||
ExecAuxRowMark* aerm = NULL;
|
||||
|
||||
Assert(IsA(rc, PlanRowMark));
|
||||
|
||||
/* ignore "parent" rowmarks; they are irrelevant at runtime */
|
||||
if (rc->isParent)
|
||||
continue;
|
||||
|
||||
if (!(IS_PGXC_COORDINATOR || u_sess->pgxc_cxt.PGXCNodeId < 0 ||
|
||||
bms_is_member(u_sess->pgxc_cxt.PGXCNodeId, rc->bms_nodeids))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* find ExecRowMark and build ExecAuxRowMark */
|
||||
erm = ExecFindRowMark(estate, rc->rti);
|
||||
aerm = ExecBuildAuxRowMark(erm, outer_plan->targetlist);
|
||||
|
||||
/*
|
||||
* Only locking rowmarks go into our own list. Non-locking marks are
|
||||
* passed off to the EvalPlanQual machinery. This is because we don't
|
||||
* want to bother fetching non-locked rows unless we actually have to
|
||||
* do an EPQ recheck.
|
||||
*/
|
||||
if (RowMarkRequiresRowShareLock(erm->markType))
|
||||
lrstate->lr_arowMarks = lappend(lrstate->lr_arowMarks, aerm);
|
||||
else
|
||||
epq_arowmarks = lappend(epq_arowmarks, aerm);
|
||||
}
|
||||
|
||||
/* Now we have the info needed to set up EPQ state */
|
||||
EvalPlanQualInit(&lrstate->lr_epqstate, estate, outer_plan, epq_arowmarks, node->epqParam);
|
||||
|
||||
return lrstate;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* ExecEndLockRows
|
||||
*
|
||||
* This shuts down the subplan and frees resources allocated
|
||||
* to this node.
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
void ExecEndLockRows(LockRowsState* node)
|
||||
{
|
||||
EvalPlanQualEnd(&node->lr_epqstate);
|
||||
ExecEndNode(outerPlanState(node));
|
||||
}
|
||||
|
||||
void ExecReScanLockRows(LockRowsState* node)
|
||||
{
|
||||
/*
|
||||
* if chgParam of subnode is not null then plan will be re-scanned by
|
||||
* first ExecProcNode.
|
||||
*/
|
||||
if (node->ps.lefttree->chgParam == NULL)
|
||||
ExecReScan(node->ps.lefttree);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user