!4197 修复表上有BRU触发器时的一些问题

Merge pull request !4197 from april01xxx/master_merge_into
This commit is contained in:
opengauss_bot
2023-09-26 11:37:48 +00:00
committed by Gitee
17 changed files with 1303 additions and 106 deletions

View File

@ -366,7 +366,7 @@ static const struct behavior_compat_entry behavior_compat_options[OPT_MAX] = {
{"unbind_divide_bound", OPT_UNBIND_DIVIDE_BOUND},
{"correct_to_number", OPT_CORRECT_TO_NUMBER},
{"compat_concat_variadic", OPT_CONCAT_VARIADIC},
{"merge_update_multi", OPT_MEGRE_UPDATE_MULTI},
{"merge_update_multi", OPT_MERGE_UPDATE_MULTI},
{"convert_string_digit_to_numeric", OPT_CONVERT_TO_NUMERIC},
{"plstmt_implicit_savepoint", OPT_PLSTMT_IMPLICIT_SAVEPOINT},
{"hide_tailing_zero", OPT_HIDE_TAILING_ZERO},

View File

@ -88,8 +88,6 @@
/* Local function prototypes */
static void ConvertTriggerToFK(CreateTrigStmt* stmt, Oid funcoid);
static void SetTriggerFlags(TriggerDesc* trigdesc, const Trigger* trigger);
HeapTuple GetTupleForTrigger(EState* estate, EPQState* epqstate, ResultRelInfo* relinfo, Oid targetPartitionOid,
int2 bucketid, ItemPointer tid, LockTupleMode lockmode, TupleTableSlot** newSlot);
static void ReleaseFakeRelation(Relation relation, Partition part, Relation* fakeRelation);
static bool TriggerEnabled(EState* estate, ResultRelInfo* relinfo, Trigger* trigger, TriggerEvent event,
const Bitmapset* modifiedCols, HeapTuple oldtup, HeapTuple newtup);
@ -2718,7 +2716,7 @@ TupleTableSlot* ExecBRUpdateTriggers(EState* estate, EPQState* epqstate, ResultR
#ifdef PGXC
HeapTupleHeader datanode_tuphead,
#endif
ItemPointer tupleid, TupleTableSlot* slot)
ItemPointer tupleid, TupleTableSlot* slot, TM_Result* result, TM_FailureData* tmfd)
{
TriggerDesc* trigdesc = get_and_check_trigdesc_value(relinfo->ri_TrigDesc);
HeapTuple slottuple = ExecMaterializeSlot(slot);
@ -2780,7 +2778,7 @@ TupleTableSlot* ExecBRUpdateTriggers(EState* estate, EPQState* epqstate, ResultR
#endif
/* get a copy of the on-disk tuple we are planning to update */
trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, oldPartitionOid,
bucketid, tupleid, lockmode, &newSlot);
bucketid, tupleid, lockmode, &newSlot, result, tmfd);
if (trigtuple == NULL)
return NULL; /* cancel the update action */
@ -3031,7 +3029,7 @@ void ExecASTruncateTriggers(EState* estate, ResultRelInfo* relinfo)
}
HeapTuple GetTupleForTrigger(EState* estate, EPQState* epqstate, ResultRelInfo* relinfo, Oid targetPartitionOid,
int2 bucketid, ItemPointer tid, LockTupleMode lockmode, TupleTableSlot** newSlot)
int2 bucketid, ItemPointer tid, LockTupleMode lockmode, TupleTableSlot** newSlot, TM_Result* tmresultp, TM_FailureData* tmfdp)
{
Relation relation = relinfo->ri_RelationDesc;
HeapTupleData tuple;
@ -3203,6 +3201,13 @@ ltrmark:
&tmfd,
true, // fake params below are for uheap implementation
false, false, NULL, NULL, false);
/* Let the caller know about the status of this operation */
if (tmresultp)
*tmresultp = test;
if (tmfdp)
*tmfdp = tmfd;
switch (test) {
case TM_SelfUpdated:
case TM_SelfModified:
@ -3236,6 +3241,15 @@ ltrmark:
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
errmsg("could not serialize access due to concurrent update")));
Assert(!ItemPointerEquals(&tmfd.ctid, &tuple.t_self));
/*
* Recheck the tuple using EPQ. For MERGE, we leave this
* to the caller (it must do additional rechecking, and
* might end up executing a different action entirely).
*/
if (tmresultp && estate->es_plannedstmt->commandType == CMD_MERGE)
return NULL;
/* it was updated, so look at the updated version */
TupleTableSlot* epqslot = NULL;

View File

@ -34,10 +34,43 @@
#include "utils/memutils.h"
#include "utils/rel.h"
#include "access/heapam.h"
#include "utils/relcache.h"
#include "utils/snapshot.h"
static void ExecMergeNotMatched(ModifyTableState* mtstate, EState* estate, TupleTableSlot* slot, char* partExprKeyStr = NULL);
static bool ExecMergeMatched(ModifyTableState* mtstate, EState* estate, TupleTableSlot* slot, JunkFilter* junkfilter,
ItemPointer tupleid, HeapTupleHeader oldtuple, Oid oldPartitionOid, int2 bucketid, char* partExprKeyStr = NULL);
static LockTupleMode ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo);
#define GET_ALL_UPDATED_COLUMNS(relinfo, estate) \
(bms_union(exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols, \
exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->extraUpdatedCols))
/*
* ExecUpdateLockMode -- find the appropriate UPDATE tuple lock mode for a
* given ResultRelInfo
*/
static LockTupleMode
ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo)
{
Bitmapset *keyCols;
Bitmapset *updatedCols;
/*
* Compute lock mode to use. If columns that are part of the key have not
* been modified, then we can use a weaker lock, allowing for better
* concurrency.
*/
updatedCols = GET_ALL_UPDATED_COLUMNS(relinfo, estate);
keyCols = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc,
INDEX_ATTR_BITMAP_KEY);
if (bms_overlap(keyCols, updatedCols))
return LockTupleExclusive;
return LockTupleNoKeyExclusive;
}
/*
* Perform MERGE.
*/
@ -391,10 +424,12 @@ static bool ExecMergeMatched(ModifyTableState* mtstate, EState* estate, TupleTab
if (mergeMatchedActionStates != NIL) {
MergeActionState* action = (MergeActionState*)linitial(mergeMatchedActionStates);
lmerge_matched:
slot = ExecMergeProjQual(mtstate, mergeMatchedActionStates, econtext, slot, slot, estate);
if (slot != NULL) {
TM_Result out_result;
TM_Result result = TM_Ok;
TM_FailureData tmfd;
(void)ExecUpdate(tupleid,
oldPartitionOid,
bucketid,
@ -405,11 +440,145 @@ static bool ExecMergeMatched(ModifyTableState* mtstate, EState* estate, TupleTab
mtstate,
mtstate->canSetTag,
partKeyUpdated,
&out_result,
partExprKeyStr);
/* the matched row has been delted or after updated, the row does not matched, change to insert. */
if (out_result == TM_Deleted || out_result == TM_Updated) {
return false;
&result,
partExprKeyStr,
&tmfd);
/*
* The matched tuple has been updated or deleted by trigger or
* other session, we have to check the updated version of the
* tuple to see if we want to process it under RC rules.
*/
switch (result) {
case TM_Ok:
/* all good; perform final actions */
break;
case TM_SelfUpdated:
case TM_SelfModified:
/* The SQL standard disallows this for MERGE, but... */
if (TransactionIdIsCurrentTransactionId(tmfd.xmax)) {
if (!MERGE_UPDATE_MULTI)
ereport(ERROR,
(errcode(ERRCODE_CARDINALITY_VIOLATION),
errmsg("MERGE command cannot affect row a second time"),
errhint("Ensure that not more than one source row matches any one target row.")));
/* already updated or deleted. */
break;
}
/* This shouldn't happen */
elog(ERROR, "attempted to update or delete invisible tuple");
break;
case TM_Deleted:
if (IsolationUsesXactSnapshot())
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
errmsg("could not serialize access due to concurrent delete")));
if (resultRelInfo->ri_RelationDesc->rd_rel->relrowmovement) {
Assert(RELATION_IS_PARTITIONED(resultRelInfo->ri_RelationDesc));
/*
* the may be a row movement update action which delete tuple from original
* partition and insert tuple to new partition or we can add lock on the tuple to
* be delete or updated to avoid throw exception
*/
ereport(ERROR,
(errcode(ERRCODE_TRANSACTION_ROLLBACK),
errmsg("partition table update conflict"),
errdetail("disable row movement of table can avoid this conflict")));
}
/*
* If the tuple was already deleted, return to let caller
* handle it under NOT MATCHED clauses.
*/
return false;
case TM_Updated:
{
Relation resultRelationDesc;
Relation partRelationDesc;
TupleTableSlot *epqslot;
LockTupleMode lockmode;
bool isNull,
isPartition;
/*
* The target tuple was concurrently updated by some other
* transaction. Run EvalPlanQual() with the new version of
* the tuple. If it does not return a tuple, then we
* switch to the NOT MATCHED list of actions. If it does
* return a tuple and the join qual is still satisfied,
* then we just need to recheck the MATCHED actions,
* starting from the top, and execute the first qualifying
* action.
*/
resultRelationDesc = resultRelInfo->ri_RelationDesc;
isPartition = RELATION_IS_PARTITIONED(resultRelationDesc);
if (isPartition) {
Partition partition = NULL;
searchFakeReationForPartitionOid(estate->esfRelations,
estate->es_query_cxt,
resultRelationDesc,
oldPartitionOid,
GetCurrentPartitionNo(oldPartitionOid),
partRelationDesc,
partition,
RowExclusiveLock);
}
lockmode = ExecUpdateLockMode(estate, resultRelInfo);
epqslot = EvalPlanQual(estate, epqstate,
isPartition ? partRelationDesc : resultRelationDesc,
resultRelInfo->ri_RangeTableIndex,
lockmode, &tmfd.ctid, tmfd.xmax,
resultRelationDesc->rd_rel->relrowmovement);
/*
* If we got no tuple, or the tuple we get has a
* NULL ctid, go back to caller: this one is not a
* MATCHED tuple anymore, so they can retry with
* NOT MATCHED actions.
*/
if (TupIsNull(epqslot))
return false;
(void) ExecGetJunkAttribute(epqslot,
resultRelInfo->ri_junkFilter->jf_junkAttNo,
&isNull);
if (isNull)
return false;
/*
* For partitioned table we have to check if the partition oid
* is NULL.
*/
if (isPartition) {
Datum partoid;
partoid = ExecGetJunkAttribute(epqslot,
resultRelInfo->ri_partOidAttNum,
&isNull);
if (isNull)
ereport(ERROR,
(errcode(ERRCODE_NULL_JUNK_ATTRIBUTE),
errmsg("tableoid is null when merge partitioned table")));
Assert(oldPartitionOid == DatumGetObjectId(partoid));
}
/*
* A non-NULL ctid means that we are still dealing
* with MATCHED case. Restart the loop so that we
* apply all the MATCHED rules again, to ensure
* that the first qualifying WHEN MATCHED action
* is executed.
*
* Update tupleid to that of the new tuple, for
* the refetch we do at the top.
*/
saved_slot = slot = epqslot;
*tupleid = tmfd.ctid;
goto lmerge_matched;
}
default:
elog(ERROR, "unexpected tuple operation result: %d", result);
break;
}
}
if (action->commandType == CMD_UPDATE /* && tuple_updated*/)

View File

@ -720,10 +720,9 @@ checktest:
ExecProject(resultRelInfo->ri_updateProj, NULL);
/* Evaluate where qual if exists, add to count if filtered */
if (ExecQual(upsertState->us_updateWhere, econtext, false)) {
TM_Result out_result;
*returning = ExecUpdate(conflictTid, oldPartitionOid, bucketid, NULL,
upsertState->us_updateproj, planSlot, &mtstate->mt_epqstate,
mtstate, canSetTag, ((ModifyTable*)mtstate->ps.plan)->partKeyUpdated, &out_result, partExprKeyStr);
mtstate, canSetTag, ((ModifyTable*)mtstate->ps.plan)->partKeyUpdated, NULL, partExprKeyStr);
} else {
InstrCountFiltered1(&mtstate->ps, 1);
}
@ -2010,33 +2009,6 @@ end:;
return NULL;
}
/*
* check whether the tuple still match the merge condition after the tuple has been updated by other transaction.
* [in] node, executor node
* [in] epq_slot, the tuple slot after recheck on the updated tuple
* [in] mergeMatchedActionStates, update action state
* [in] fake_relation, heap table relation
* [in/out] slot, in: origin tuple slot, out: slot after merge projection
* [out] tuple, if the updated tuple still match the merge condition, return the new projected tuple
* [return value] true for match, false for not match.
*/
static bool MatchMergeCondition(ModifyTableState* node, TupleTableSlot* epq_slot, List* mergeMatchedActionStates,
Relation fake_relation, AttrNumber junkAttno, TupleTableSlot** slot, Tuple *tuple)
{
/* resultRelInfo->ri_mergeState is always not null */
*slot = ExecMergeProjQual(node, mergeMatchedActionStates, node->ps.ps_ExprContext, epq_slot, *slot, node->ps.state);
if (*slot != NULL) {
bool is_null = false;
/* Check after epq, whether the ctid is null. null means the updated row does not match */
(void)ExecGetJunkAttribute(epq_slot, junkAttno, &is_null);
if (!is_null) {
*tuple = tableam_tslot_get_tuple_from_slot(fake_relation, *slot);
return true;
}
}
return false;
}
/* ----------------------------------------------------------------
* ExecUpdate
*
@ -2060,7 +2032,7 @@ static bool MatchMergeCondition(ModifyTableState* node, TupleTableSlot* epq_slot
TupleTableSlot* ExecUpdate(ItemPointer tupleid,
Oid oldPartitionOid, /* when update a partitioned table , give a partitionOid to find the tuple */
int2 bucketid, HeapTupleHeader oldtuple, TupleTableSlot* slot, TupleTableSlot* planSlot, EPQState* epqstate,
ModifyTableState* node, bool canSetTag, bool partKeyUpdate, TM_Result* out_result, char* partExprKeyStr)
ModifyTableState* node, bool canSetTag, bool partKeyUpdate, TM_Result* uresultp, char* partExprKeyStr, TM_FailureData* tmfdp)
{
EState* estate = node->ps.state;
Tuple tuple = NULL;
@ -2085,13 +2057,13 @@ TupleTableSlot* ExecUpdate(ItemPointer tupleid,
ConflictInfoData conflictInfo;
Oid conflictPartOid = InvalidOid;
int2 conflictBucketid = InvalidBktId;
bool cross_partition = (partKeyUpdate || partExprKeyStr != NULL);
#ifdef PGXC
RemoteQueryState* result_remote_rel = NULL;
#endif
bool allow_update_self = (node->mt_upsert != NULL &&
node->mt_upsert->us_action != UPSERT_NONE) ? true : false;
*out_result = TM_Ok;
/*
* get information on the (current) result relation
*/
@ -2146,9 +2118,9 @@ TupleTableSlot* ExecUpdate(ItemPointer tupleid,
result_rel_info->ri_TrigDesc && result_rel_info->ri_TrigDesc->trig_update_before_row) {
#ifdef PGXC
slot = ExecBRUpdateTriggers(estate, epqstate, result_rel_info, oldPartitionOid,
bucketid, oldtuple, tupleid, slot);
bucketid, oldtuple, tupleid, slot, uresultp, tmfdp);
#else
slot = ExecBRUpdateTriggers(estate, epqstate, result_rel_info, tupleid, slot);
slot = ExecBRUpdateTriggers(estate, epqstate, result_rel_info, tupleid, slot, uresultp, tmfdp);
#endif
if (slot == NULL) {
@ -2159,6 +2131,9 @@ TupleTableSlot* ExecUpdate(ItemPointer tupleid,
// tableam
/* trigger might have changed tuple */
tuple = tableam_tslot_get_tuple_from_slot(result_relation_desc, slot);
/* The partition key might have been updated by the trigger. */
cross_partition = true;
}
/* INSTEAD OF ROW UPDATE Triggers */
@ -2246,6 +2221,8 @@ lreplace:
bool update_fix_result = ExecComputeStoredUpdateExpr(result_rel_info, estate, slot, tuple, CMD_UPDATE, tupleid, oldPartitionOid, bucketid);
if (!update_fix_result) {
tuple = slot->tts_tuple;
/* The partition key might have been updated by on update rule. */
cross_partition = true;
}
}
@ -2255,6 +2232,8 @@ lreplace:
if (result_relation_desc->rd_att->constr && result_relation_desc->rd_att->constr->has_generated_stored) {
ExecComputeStoredGenerated(result_rel_info, estate, slot, tuple, CMD_UPDATE);
tuple = slot->tts_tuple;
/* The partition key might have been updated by generated expr. */
cross_partition = true;
}
if (result_relation_desc->rd_att->constr) {
@ -2333,12 +2312,18 @@ lreplace:
estate->es_crosscheck_snapshot, estate->es_snapshot, true, // wait for commit
&oldslot, &tmfd, &update_indexes, &modifiedIdxAttrs, allow_update_self,
allowInplaceUpdate, &lockmode);
*out_result = result;
/* Let the caller know about the status of this operation */
if (uresultp)
*uresultp = result;
if (tmfdp)
*tmfdp = tmfd;
switch (result) {
case TM_SelfUpdated:
case TM_SelfModified:
/* can not update one row more than once for merge into */
if (node->operation == CMD_MERGE && !MEGRE_UPDATE_MULTI) {
if (node->operation == CMD_MERGE && !MERGE_UPDATE_MULTI) {
ereport(ERROR,
(errmodule(MOD_EXECUTOR),
(errcode(ERRCODE_TOO_MANY_ROWS),
@ -2404,27 +2389,23 @@ lreplace:
errmsg("concurrent update under Stream mode is not yet supported")));
}
/*
* Recheck the tuple using EPQ. For MERGE, we leave this
* to the caller (it must do additional rechecking, and
* might end up executing a different action entirely).
*/
if (uresultp && estate->es_plannedstmt->commandType == CMD_MERGE)
return NULL;
TupleTableSlot *epq_slot = EvalPlanQual(estate, epqstate, fake_relation,
result_rel_info->ri_RangeTableIndex, lockmode, &tmfd.ctid, tmfd.xmax, false);
if (!TupIsNull(epq_slot)) {
*tupleid = tmfd.ctid;
/*
* For merge into query, mergeMatchedAction's targetlist is not same as junk filter's
* targetlist. Here, epqslot is a plan slot, target table needs slot to be projected
* from plan slot.
*/
if (node->operation == CMD_MERGE) {
if (MatchMergeCondition(node, epq_slot, result_rel_info->ri_mergeState->matchedActionStates,
fake_relation, result_rel_info->ri_junkFilter->jf_junkAttNo, &slot, &tuple)) {
goto lreplace;
}
} else {
slot = ExecFilterJunk(result_rel_info->ri_junkFilter, epq_slot);
slot = ExecFilterJunk(result_rel_info->ri_junkFilter, epq_slot);
tuple = tableam_tslot_get_tuple_from_slot(fake_relation, slot);
goto lreplace;
}
tuple = tableam_tslot_get_tuple_from_slot(fake_relation, slot);
goto lreplace;
}
/* Updated tuple not matched; nothing to do */
@ -2486,7 +2467,7 @@ lreplace:
if (partExprKeyStr) {
newval = ComputePartKeyExprTuple(result_relation_desc, estate, slot, NULL, partExprKeyStr);
}
if (!partExprKeyStr && !partKeyUpdate) {
if (!cross_partition) {
row_movement = false;
new_partId = oldPartitionOid;
} else {
@ -2649,12 +2630,18 @@ lreplace:
allow_update_self,
allowInplaceUpdate,
&lockmode);
*out_result = result;
/* Let the caller know about the status of this operation */
if (uresultp)
*uresultp = result;
if (tmfdp)
*tmfdp = tmfd;
switch (result) {
case TM_SelfUpdated:
case TM_SelfModified:
/* can not update one row more than once for merge into */
if (node->operation == CMD_MERGE && !MEGRE_UPDATE_MULTI) {
if (node->operation == CMD_MERGE && !MERGE_UPDATE_MULTI) {
ereport(ERROR, (errmodule(MOD_EXECUTOR), (errcode(ERRCODE_TOO_MANY_ROWS),
errmsg("unable to get a stable set of rows in the source tables"))));
}
@ -2707,6 +2694,15 @@ lreplace:
errmsg("concurrent update under Stream mode is not yet supported")));
}
/*
* Recheck the tuple using EPQ. For MERGE, we leave this
* to the caller (it must do additional rechecking, and
* might end up executing a different action entirely).
*/
if (uresultp && estate->es_plannedstmt->commandType == CMD_MERGE)
return NULL;
TupleTableSlot *epq_slot = EvalPlanQual(estate, epqstate, fake_relation,
result_rel_info->ri_RangeTableIndex, lockmode, &tmfd.ctid, tmfd.xmax,
result_relation_desc->rd_rel->relrowmovement);
@ -2714,22 +2710,10 @@ lreplace:
if (!TupIsNull(epq_slot)) {
*tupleid = tmfd.ctid;
/*
* For merge into query, mergeMatchedAction's targetlist is not same as junk
* filter's targetlist. Here, epq_slot is a plan slot, target table needs slot to be
* projected from plan slot.
*/
if (node->operation == CMD_MERGE) {
if (MatchMergeCondition(node, epq_slot, result_rel_info->ri_mergeState->matchedActionStates,
fake_relation, result_rel_info->ri_junkFilter->jf_junkAttNo, &slot, &tuple)) {
goto lreplace;
}
} else {
slot = ExecFilterJunk(result_rel_info->ri_junkFilter, epq_slot);
slot = ExecFilterJunk(result_rel_info->ri_junkFilter, epq_slot);
tuple = tableam_tslot_get_tuple_from_slot(fake_relation, slot);
goto lreplace;
}
tuple = tableam_tslot_get_tuple_from_slot(fake_relation, slot);
goto lreplace;
}
/* Updated tuple not matched; nothing to do */
@ -2857,12 +2841,18 @@ ldelete:
&oldslot,
&tmfd,
allow_update_self);
*out_result = result;
/* Let the caller know about the status of this operation */
if (uresultp)
*uresultp = result;
if (tmfdp)
*tmfdp = tmfd;
switch (result) {
case TM_SelfUpdated:
case TM_SelfModified:
/* can not update one row more than once for merge into */
if (node->operation == CMD_MERGE && !MEGRE_UPDATE_MULTI) {
if (node->operation == CMD_MERGE && !MERGE_UPDATE_MULTI) {
ereport(ERROR, (errmodule(MOD_EXECUTOR), (errcode(ERRCODE_TOO_MANY_ROWS),
errmsg("unable to get a stable set of rows in the source tables"))));
}
@ -2939,6 +2929,14 @@ ldelete:
errmsg("concurrent update under Stream mode is not yet supported")));
}
/*
* Recheck the tuple using EPQ. For MERGE, we leave this
* to the caller (it must do additional rechecking, and
* might end up executing a different action entirely).
*/
if (uresultp && estate->es_plannedstmt->commandType == CMD_MERGE)
return NULL;
TupleTableSlot* epq_slot = EvalPlanQual(estate,
epqstate,
old_fake_relation,
@ -2950,22 +2948,10 @@ ldelete:
/* Try to fetch latest tuple values in row movement case */
if (!TupIsNull(epq_slot)) {
*tupleid = tmfd.ctid;
/*
* For merge into query, mergeMatchedAction's targetlist is not same as
* junk filter's targetlist. Here, epqslot is a plan slot, target table
* needs slot to be projected from plan slot.
*/
if (node->operation == CMD_MERGE) {
if (MatchMergeCondition(node, epq_slot, result_rel_info->ri_mergeState->matchedActionStates,
old_fake_relation, result_rel_info->ri_junkFilter->jf_junkAttNo, &slot, &tuple)) {
goto ldelete;
}
} else {
slot = ExecFilterJunk(result_rel_info->ri_junkFilter, epq_slot);
slot = ExecFilterJunk(result_rel_info->ri_junkFilter, epq_slot);
tuple = tableam_tslot_get_tuple_from_slot(old_fake_relation, slot);
goto ldelete;
}
tuple = tableam_tslot_get_tuple_from_slot(old_fake_relation, slot);
goto ldelete;
}
/* Updated tuple not matched; nothing to do */
@ -3721,7 +3707,6 @@ static TupleTableSlot* ExecModifyTable(PlanState* state)
}
break;
case CMD_UPDATE: {
TM_Result out_result;
slot = ExecUpdate(tuple_id,
old_partition_oid,
bucketid,
@ -3732,7 +3717,7 @@ static TupleTableSlot* ExecModifyTable(PlanState* state)
node,
node->canSetTag,
part_key_updated,
&out_result,
NULL,
partExprKeyStr);
} break;
case CMD_DELETE:

View File

@ -146,7 +146,7 @@ extern TupleTableSlot* ExecBRUpdateTriggers(EState* estate, EPQState* epqstate,
#ifdef PGXC
HeapTupleHeader datanode_tuphead,
#endif
ItemPointer tupleid, TupleTableSlot* slot);
ItemPointer tupleid, TupleTableSlot* slot, TM_Result* result = NULL, TM_FailureData* tmfd = NULL);
extern void ExecARUpdateTriggers(EState* estate, ResultRelInfo* relinfo, Oid oldPartitionOid, int2 bucketid, Oid newPartitionOid,
ItemPointer tupleid, HeapTuple newtuple,
#ifdef PGXC
@ -201,6 +201,7 @@ extern void InvalidRelcacheForTriggerFunction(Oid funcoid, Oid returnType);
extern void ResetTrigShipFlag();
extern HeapTuple GetTupleForTrigger(EState* estate, EPQState* epqstate, ResultRelInfo* relinfo, Oid targetPartitionOid,
int2 bucketid, ItemPointer tid, LockTupleMode lockmode, TupleTableSlot** newSlot);
int2 bucketid, ItemPointer tid, LockTupleMode lockmode, TupleTableSlot** newSlot, TM_Result* result = NULL,
TM_FailureData* tmfd = NULL);
#endif /* TRIGGER_H */

View File

@ -46,7 +46,7 @@ extern TupleTableSlot* ExecDelete(ItemPointer tupleid, Oid deletePartitionOid, i
extern TupleTableSlot* ExecUpdate(ItemPointer tupleid, Oid oldPartitionOid, int2 bucketid, HeapTupleHeader oldtuple,
TupleTableSlot* slot, TupleTableSlot* planSlot, EPQState* epqstate, ModifyTableState* node, bool canSetTag,
bool partKeyUpdate, TM_Result* out_result, char* partExprKeyStr = NULL);
bool partKeyUpdate, TM_Result* uresultp = NULL, char* partExprKeyStr = NULL, TM_FailureData* tmfd = NULL);
template <bool useHeapMultiInsert>
extern TupleTableSlot* ExecInsertT(ModifyTableState* state, TupleTableSlot* slot, TupleTableSlot* planSlot,

View File

@ -179,7 +179,7 @@ extern bool contain_backend_version(uint32 version_number);
#define OPT_UNBIND_DIVIDE_BOUND 64
#define OPT_CORRECT_TO_NUMBER 128
#define OPT_CONCAT_VARIADIC 256
#define OPT_MEGRE_UPDATE_MULTI 512
#define OPT_MERGE_UPDATE_MULTI 512
#define OPT_CONVERT_TO_NUMERIC 1024
#define OPT_PLSTMT_IMPLICIT_SAVEPOINT 2048
#define OPT_HIDE_TAILING_ZERO 4096
@ -217,7 +217,7 @@ extern bool contain_backend_version(uint32 version_number);
* option is blank and the behavior is new and compatible with current A and C mode, if the option is set, the
* behavior is old and the same as previous GAUSSDB kernel. */
#define CONCAT_VARIADIC (!(u_sess->utils_cxt.behavior_compat_flags & OPT_CONCAT_VARIADIC))
#define MEGRE_UPDATE_MULTI (u_sess->utils_cxt.behavior_compat_flags & OPT_MEGRE_UPDATE_MULTI)
#define MERGE_UPDATE_MULTI (u_sess->utils_cxt.behavior_compat_flags & OPT_MERGE_UPDATE_MULTI)
#define CONVERT_STRING_DIGIT_TO_NUMERIC (u_sess->utils_cxt.behavior_compat_flags & OPT_CONVERT_TO_NUMERIC)
#define PLSTMT_IMPLICIT_SAVEPOINT (u_sess->utils_cxt.behavior_compat_flags & OPT_PLSTMT_IMPLICIT_SAVEPOINT)
#define HIDE_TAILING_ZERO (u_sess->utils_cxt.behavior_compat_flags & OPT_HIDE_TAILING_ZERO)

View File

@ -0,0 +1,53 @@
create schema merge_into_deleted;
set search_path = 'merge_into_deleted';
create table t1 (c1 int, c2 text, c3 timestamp);
insert into t1 values (1, 'a', '2023-09-15');
-- concurrently deleted and insert, do update and insert
\parallel on 2
begin
delete from t1 where c1 = 1;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
insert into t1 values (1, 'b', '2023-09-16');
merge into t1 using (select 1 c1) t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'c'
when not matched then insert values (2, 'c', '2023-09-17');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
c1 | c2 | to_char
----+----+------------
1 | c | 2023-09-16
2 | c | 2023-09-17
(2 rows)
-- concurrently deleted, not matched, do insert
\parallel on 2
begin
delete from t1 where c1 = 1;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
merge into t1 using (select 1 c1) t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (3, 'd', '2023-09-18');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
c1 | c2 | to_char
----+----+------------
2 | c | 2023-09-17
3 | d | 2023-09-18
(2 rows)
drop schema merge_into_deleted cascade;
NOTICE: drop cascades to table t1

View File

@ -0,0 +1,267 @@
create schema merge_into_partition_row_movement;
set search_path = 'merge_into_partition_row_movement';
create table t1 (c1 int, c2 text, c3 timestamp)
partition by range(c1)
(
partition p1 values less than (100),
partition p2 values less than (200)
);
create table t2 (c1 int);
insert into t1 values (1, 'a', '2023-09-15'), (101, 'b', '2023-09-16');
insert into t2 values (2), (102);
begin
update t1 set c1 = 102 where c1 = 1;
update t1 set c1 = 2 where c1 = 101;
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (3, 'd', '2023-09-18');
end;
/
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p1) order by c1;
c1 | c2 | to_char
----+----+------------
2 | d | 2023-09-16
(1 row)
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p2) order by c1;
c1 | c2 | to_char
-----+----+------------
102 | d | 2023-09-15
(1 row)
delete from t1;
insert into t1 values (1, 'a', '2023-09-15'), (101, 'b', '2023-09-16');
delete from t2;
insert into t2 values (1), (2);
-- success, concurrently update
\parallel on 2
begin
update t1 set c1 = 2 where c1 = 1;
update t1 set c1 = 1 where c1 = 101;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (3, 'd', '2023-09-18');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p1) order by c1;
c1 | c2 | to_char
----+----+------------
1 | b | 2023-09-16
2 | a | 2023-09-15
3 | d | 2023-09-18
3 | d | 2023-09-18
(4 rows)
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p2) order by c1;
c1 | c2 | to_char
----+----+---------
(0 rows)
delete from t1;
insert into t1 values (1, 'a', '2023-09-15'), (101, 'b', '2023-09-16');
delete from t2;
insert into t2 values (1), (101);
-- error
\parallel on 2
begin
update t1 set c1 = 102 where c1 = 1;
update t1 set c1 = 2 where c1 = 101;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (3, 'd', '2023-09-18');
end;
/
\parallel off
ERROR: partition table update conflict
DETAIL: disable row movement of table can avoid this conflict
CONTEXT: SQL statement "merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (3, 'd', '2023-09-18')"
PL/pgSQL function inline_code_block line 3 at SQL statement
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p1) order by c1;
c1 | c2 | to_char
----+----+------------
2 | b | 2023-09-16
(1 row)
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p2) order by c1;
c1 | c2 | to_char
-----+----+------------
102 | a | 2023-09-15
(1 row)
delete from t1;
insert into t1 values (1, 'a', '2023-09-15'), (101, 'b', '2023-09-16');
delete from t2;
insert into t2 values (1), (2);
create or replace function t1_tri_func() return trigger as
begin
if (old.c1 < 101) then
new.c1 = 150;
else
new.c1 = 50;
end if;
return new;
end;
/
create trigger t1_tri
before update on t1
for each row
execute procedure t1_tri_func();
-- error
\parallel on 2
begin
update t1 set c1 = 2 where c1 = 1;
update t1 set c1 = 2 where c1 = 101;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (4, 'e', '2023-09-18');
end;
/
\parallel off
ERROR: partition table update conflict
DETAIL: disable row movement of table can avoid this conflict
CONTEXT: SQL statement "merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (4, 'e', '2023-09-18')"
PL/pgSQL function inline_code_block line 3 at SQL statement
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p1) order by c1;
c1 | c2 | to_char
----+----+------------
50 | b | 2023-09-16
(1 row)
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p2) order by c1;
c1 | c2 | to_char
-----+----+------------
150 | a | 2023-09-15
(1 row)
delete from t1;
insert into t1 values (1, 'a', '2023-09-15'), (101, 'b', '2023-09-16');
delete from t2;
insert into t2 values (1), (101);
-- error
\parallel on 2
begin
update t1 set c3 = '2023-09-19' where c1 = 1;
update t1 set c3 = '2023-09-20' where c1 = 101;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (4, 'e', '2023-09-18');
end;
/
\parallel off
ERROR: partition table update conflict
DETAIL: disable row movement of table can avoid this conflict
CONTEXT: SQL statement "merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (4, 'e', '2023-09-18')"
PL/pgSQL function inline_code_block line 3 at SQL statement
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p1) order by c1;
c1 | c2 | to_char
----+----+------------
50 | b | 2023-09-20
(1 row)
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p2) order by c1;
c1 | c2 | to_char
-----+----+------------
150 | a | 2023-09-19
(1 row)
delete from t1;
insert into t1 values (1, 'a', '2023-09-15'), (101, 'b', '2023-09-16');
delete from t2;
insert into t2 values (1), (101);
begin
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (4, 'e', '2023-09-18');
end;
/
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p1) order by c1;
c1 | c2 | to_char
----+----+------------
50 | d | 2023-09-16
(1 row)
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p2) order by c1;
c1 | c2 | to_char
-----+----+------------
150 | d | 2023-09-15
(1 row)
delete from t1;
insert into t1 values (1, 'a', '2023-09-15'), (101, 'b', '2023-09-16');
delete from t2;
insert into t2 values (1), (101);
-- error
\parallel on 2
begin
delete from t1 where c1 = 1;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (4, 'e', '2023-09-18');
end;
/
\parallel off
ERROR: partition table update conflict
DETAIL: disable row movement of table can avoid this conflict
CONTEXT: SQL statement "merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (4, 'e', '2023-09-18')"
PL/pgSQL function inline_code_block line 3 at SQL statement
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p1) order by c1;
c1 | c2 | to_char
----+----+---------
(0 rows)
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p2) order by c1;
c1 | c2 | to_char
-----+----+------------
101 | b | 2023-09-16
(1 row)
drop schema merge_into_partition_row_movement cascade;
NOTICE: drop cascades to 3 other objects
DETAIL: drop cascades to table t1
drop cascades to table t2
drop cascades to function t1_tri_func()

View File

@ -0,0 +1,133 @@
create schema merge_into_selfmodified;
set search_path = 'merge_into_selfmodified';
create table t1 (c1 int, c2 text, c3 timestamp);
create table t2 (c1 int);
insert into t1 values (1, 'a', '2023-09-15');
create or replace function t1_tri_func() return trigger as
begin
new.c3 = '2023-09-16';
return new;
end;
/
create trigger t1_tri
before update on t1
for each row
execute procedure t1_tri_func();
-- success, t2 is null, nothing to do
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'b'
when not matched then insert values (2, 'b', '2023-09-17');
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
c1 | c2 | to_char
----+----+------------
1 | a | 2023-09-15
(1 row)
-- success, t2 has one row, not matched, do insert
merge into t1 using (select null c1) t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'c'
when not matched then insert values (3, 'c', '2023-09-18');
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
c1 | c2 | to_char
----+----+------------
1 | a | 2023-09-15
3 | c | 2023-09-18
(2 rows)
insert into t2 values (1);
-- success, matched, do update
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (4, 'd', '2023-09-19');
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
c1 | c2 | to_char
----+----+------------
1 | d | 2023-09-16
3 | c | 2023-09-18
(2 rows)
insert into t2 values (1);
-- error, affect one row a second time
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'e'
when not matched then insert values (5, 'e', '2023-09-20');
ERROR: MERGE command cannot affect row a second time
HINT: Ensure that not more than one source row matches any one target row.
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
c1 | c2 | to_char
----+----+------------
1 | d | 2023-09-16
3 | c | 2023-09-18
(2 rows)
set behavior_compat_options = 'merge_update_multi';
-- success, but update only once
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'f'
when not matched then insert values (6, 'f', '2023-09-21');
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
c1 | c2 | to_char
----+----+------------
1 | f | 2023-09-16
3 | c | 2023-09-18
(2 rows)
insert into t2 values (7);
-- success, do update and insert
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'g'
when not matched then insert values (7, 'g', '2023-09-22');
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
c1 | c2 | to_char
----+----+------------
1 | g | 2023-09-16
3 | c | 2023-09-18
7 | g | 2023-09-22
(3 rows)
insert into t2 values (8),(8);
-- success, do update and insert only once
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'h'
when not matched then insert values (8, 'h', '2023-09-23');
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
c1 | c2 | to_char
----+----+------------
1 | h | 2023-09-16
3 | c | 2023-09-18
7 | h | 2023-09-16
8 | h | 2023-09-23
8 | h | 2023-09-23
(5 rows)
insert into t2 values (9),(10);
-- success, do update and insert only once
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'i'
when not matched then insert values (9, 'i', '2023-09-24');
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
c1 | c2 | to_char
----+----+------------
1 | i | 2023-09-16
3 | c | 2023-09-18
7 | i | 2023-09-16
8 | i | 2023-09-16
8 | i | 2023-09-16
9 | i | 2023-09-24
9 | i | 2023-09-24
(7 rows)
reset behavior_compat_options;
drop schema merge_into_selfmodified cascade;
NOTICE: drop cascades to 3 other objects
DETAIL: drop cascades to table t1
drop cascades to table t2
drop cascades to function t1_tri_func()

View File

@ -0,0 +1,140 @@
create schema merge_into_updated;
set search_path = 'merge_into_updated';
create table t1 (c1 int, c2 text, c3 timestamp);
insert into t1 values (1, 'a', '2023-09-15');
create or replace function t1_tri_func() return trigger as
begin
new.c3 = '2023-09-16';
return new;
end;
/
create trigger t1_tri
before update on t1
for each row
execute procedure t1_tri_func();
-- success, matched, do update
\parallel on 2
begin
update t1 set c2 = 'b' where c1 = 1;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
merge into t1 using (select 1 c1) t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'c'
when not matched then insert values (2, 'c', '2023-09-17');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
c1 | c2 | to_char
----+----+------------
1 | c | 2023-09-16
(1 row)
-- success, matched, do update
\parallel on 2
begin
update t1 set c2 = 'b' where c1 = 1;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
insert into t1 values (1, 'hello', '2023-09-17');
delete from t1 where c2 = 'hello';
merge into t1 using (select 1 c1) t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (2, 'c', '2023-09-17');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
c1 | c2 | to_char
----+----+------------
1 | d | 2023-09-16
(1 row)
-- success, concurrently update join condition, not matched, do insert
\parallel on 2
begin
update t1 set c1 = 2 where c1 = 1;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
merge into t1 using (select 1 c1) t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (3, 'd', '2023-09-18');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
c1 | c2 | to_char
----+----+------------
2 | d | 2023-09-16
3 | d | 2023-09-18
(2 rows)
-- success, not matched, do insert
\parallel on 2
begin
update t1 set c2 = 'b';
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
insert into t1 values (1, 'hello', '2023-09-17');
delete from t1;
merge into t1 using (select 1 c1) t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'c'
when not matched then insert values (2, 'c', '2023-09-17');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
c1 | c2 | to_char
----+----+------------
2 | c | 2023-09-17
(1 row)
-- trigger update the join condition, not matched, do insert
create or replace function t1_tri_func() return trigger as
begin
new.c1 = 4;
return new;
end;
/
\parallel on 2
begin
update t1 set c2 = 'b';
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
merge into t1 using (select 2 c1) t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'e'
when not matched then insert values (4, 'e', '2023-09-19');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
c1 | c2 | to_char
----+----+------------
4 | b | 2023-09-17
4 | e | 2023-09-19
(2 rows)
drop schema merge_into_updated cascade;
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to table t1
drop cascades to function t1_tri_func()

View File

@ -277,7 +277,7 @@ test: single_node_forbidden
test: single_node_mergeinto merge_subquery merge_subquery3 merge_1
test: merge_where_col
test: merge_concurrent_update_delete_1 merge_concurrent_update_delete_2 merge_concurrent_update_delete_3
test: merge_concurrent_update_delete_1 merge_concurrent_update_delete_2 merge_concurrent_update_delete_3 merge_into_deleted merge_into_partition_row_movement merge_into_selfmodified merge_into_updated
# Trigger tests
test: single_node_triggers

View File

@ -269,7 +269,7 @@ test: single_node_forbidden
test: single_node_mergeinto merge_subquery merge_subquery3 merge_1
test: merge_where_col
test: merge_concurrent_update_delete_1 merge_concurrent_update_delete_2 merge_concurrent_update_delete_3
test: merge_concurrent_update_delete_1 merge_concurrent_update_delete_2 merge_concurrent_update_delete_3 merge_into_deleted merge_into_partition_row_movement merge_into_selfmodified merge_into_updated
# Trigger tests
test: single_node_triggers

View File

@ -0,0 +1,44 @@
create schema merge_into_deleted;
set search_path = 'merge_into_deleted';
create table t1 (c1 int, c2 text, c3 timestamp);
insert into t1 values (1, 'a', '2023-09-15');
-- concurrently deleted and insert, do update and insert
\parallel on 2
begin
delete from t1 where c1 = 1;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
insert into t1 values (1, 'b', '2023-09-16');
merge into t1 using (select 1 c1) t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'c'
when not matched then insert values (2, 'c', '2023-09-17');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
-- concurrently deleted, not matched, do insert
\parallel on 2
begin
delete from t1 where c1 = 1;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
merge into t1 using (select 1 c1) t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (3, 'd', '2023-09-18');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
drop schema merge_into_deleted cascade;

View File

@ -0,0 +1,182 @@
create schema merge_into_partition_row_movement;
set search_path = 'merge_into_partition_row_movement';
create table t1 (c1 int, c2 text, c3 timestamp)
partition by range(c1)
(
partition p1 values less than (100),
partition p2 values less than (200)
);
create table t2 (c1 int);
insert into t1 values (1, 'a', '2023-09-15'), (101, 'b', '2023-09-16');
insert into t2 values (2), (102);
begin
update t1 set c1 = 102 where c1 = 1;
update t1 set c1 = 2 where c1 = 101;
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (3, 'd', '2023-09-18');
end;
/
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p1) order by c1;
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p2) order by c1;
delete from t1;
insert into t1 values (1, 'a', '2023-09-15'), (101, 'b', '2023-09-16');
delete from t2;
insert into t2 values (1), (2);
-- success, concurrently update
\parallel on 2
begin
update t1 set c1 = 2 where c1 = 1;
update t1 set c1 = 1 where c1 = 101;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (3, 'd', '2023-09-18');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p1) order by c1;
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p2) order by c1;
delete from t1;
insert into t1 values (1, 'a', '2023-09-15'), (101, 'b', '2023-09-16');
delete from t2;
insert into t2 values (1), (101);
-- error
\parallel on 2
begin
update t1 set c1 = 102 where c1 = 1;
update t1 set c1 = 2 where c1 = 101;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (3, 'd', '2023-09-18');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p1) order by c1;
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p2) order by c1;
delete from t1;
insert into t1 values (1, 'a', '2023-09-15'), (101, 'b', '2023-09-16');
delete from t2;
insert into t2 values (1), (2);
create or replace function t1_tri_func() return trigger as
begin
if (old.c1 < 101) then
new.c1 = 150;
else
new.c1 = 50;
end if;
return new;
end;
/
create trigger t1_tri
before update on t1
for each row
execute procedure t1_tri_func();
-- error
\parallel on 2
begin
update t1 set c1 = 2 where c1 = 1;
update t1 set c1 = 2 where c1 = 101;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (4, 'e', '2023-09-18');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p1) order by c1;
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p2) order by c1;
delete from t1;
insert into t1 values (1, 'a', '2023-09-15'), (101, 'b', '2023-09-16');
delete from t2;
insert into t2 values (1), (101);
-- error
\parallel on 2
begin
update t1 set c3 = '2023-09-19' where c1 = 1;
update t1 set c3 = '2023-09-20' where c1 = 101;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (4, 'e', '2023-09-18');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p1) order by c1;
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p2) order by c1;
delete from t1;
insert into t1 values (1, 'a', '2023-09-15'), (101, 'b', '2023-09-16');
delete from t2;
insert into t2 values (1), (101);
begin
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (4, 'e', '2023-09-18');
end;
/
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p1) order by c1;
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p2) order by c1;
delete from t1;
insert into t1 values (1, 'a', '2023-09-15'), (101, 'b', '2023-09-16');
delete from t2;
insert into t2 values (1), (101);
-- error
\parallel on 2
begin
delete from t1 where c1 = 1;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (4, 'e', '2023-09-18');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p1) order by c1;
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 partition (p2) order by c1;
drop schema merge_into_partition_row_movement cascade;

View File

@ -0,0 +1,91 @@
create schema merge_into_selfmodified;
set search_path = 'merge_into_selfmodified';
create table t1 (c1 int, c2 text, c3 timestamp);
create table t2 (c1 int);
insert into t1 values (1, 'a', '2023-09-15');
create or replace function t1_tri_func() return trigger as
begin
new.c3 = '2023-09-16';
return new;
end;
/
create trigger t1_tri
before update on t1
for each row
execute procedure t1_tri_func();
-- success, t2 is null, nothing to do
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'b'
when not matched then insert values (2, 'b', '2023-09-17');
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
-- success, t2 has one row, not matched, do insert
merge into t1 using (select null c1) t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'c'
when not matched then insert values (3, 'c', '2023-09-18');
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
insert into t2 values (1);
-- success, matched, do update
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (4, 'd', '2023-09-19');
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
insert into t2 values (1);
-- error, affect one row a second time
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'e'
when not matched then insert values (5, 'e', '2023-09-20');
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
set behavior_compat_options = 'merge_update_multi';
-- success, but update only once
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'f'
when not matched then insert values (6, 'f', '2023-09-21');
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
insert into t2 values (7);
-- success, do update and insert
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'g'
when not matched then insert values (7, 'g', '2023-09-22');
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
insert into t2 values (8),(8);
-- success, do update and insert only once
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'h'
when not matched then insert values (8, 'h', '2023-09-23');
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
insert into t2 values (9),(10);
-- success, do update and insert only once
merge into t1 using t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'i'
when not matched then insert values (9, 'i', '2023-09-24');
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
reset behavior_compat_options;
drop schema merge_into_selfmodified cascade;

View File

@ -0,0 +1,118 @@
create schema merge_into_updated;
set search_path = 'merge_into_updated';
create table t1 (c1 int, c2 text, c3 timestamp);
insert into t1 values (1, 'a', '2023-09-15');
create or replace function t1_tri_func() return trigger as
begin
new.c3 = '2023-09-16';
return new;
end;
/
create trigger t1_tri
before update on t1
for each row
execute procedure t1_tri_func();
-- success, matched, do update
\parallel on 2
begin
update t1 set c2 = 'b' where c1 = 1;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
merge into t1 using (select 1 c1) t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'c'
when not matched then insert values (2, 'c', '2023-09-17');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
-- success, matched, do update
\parallel on 2
begin
update t1 set c2 = 'b' where c1 = 1;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
insert into t1 values (1, 'hello', '2023-09-17');
delete from t1 where c2 = 'hello';
merge into t1 using (select 1 c1) t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (2, 'c', '2023-09-17');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
-- success, concurrently update join condition, not matched, do insert
\parallel on 2
begin
update t1 set c1 = 2 where c1 = 1;
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
merge into t1 using (select 1 c1) t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'd'
when not matched then insert values (3, 'd', '2023-09-18');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
-- success, not matched, do insert
\parallel on 2
begin
update t1 set c2 = 'b';
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
insert into t1 values (1, 'hello', '2023-09-17');
delete from t1;
merge into t1 using (select 1 c1) t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'c'
when not matched then insert values (2, 'c', '2023-09-17');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
-- trigger update the join condition, not matched, do insert
create or replace function t1_tri_func() return trigger as
begin
new.c1 = 4;
return new;
end;
/
\parallel on 2
begin
update t1 set c2 = 'b';
perform pg_sleep(3);
end;
/
begin
perform pg_sleep(1);
merge into t1 using (select 2 c1) t2
on (t1.c1 = t2.c1)
when matched then update set c2 = 'e'
when not matched then insert values (4, 'e', '2023-09-19');
end;
/
\parallel off
select c1, c2, to_char(c3, 'yyyy-mm-dd') from t1 order by c1;
drop schema merge_into_updated cascade;