Fix concurrent update issues with MERGE.

If MERGE attempts an UPDATE or DELETE on a table with BEFORE ROW
triggers, or a cross-partition UPDATE (with or without triggers), and
a concurrent UPDATE or DELETE happens, the merge code would fail.

In some cases this would lead to a crash, while in others it would
cause the wrong merge action to be executed, or no action at all. The
immediate cause of the crash was the trigger code calling
ExecGetUpdateNewTuple() as part of the EPQ mechanism, which fails
because during a merge ri_projectNew is NULL, since merge has its own
per-action projection information, which ExecGetUpdateNewTuple() knows
nothing about.

Fix by arranging for the trigger code to exit early, returning the
TM_Result and TM_FailureData information, if a concurrent modification
is detected, allowing the merge code to do the necessary EPQ handling
in its own way. Similarly, prevent the cross-partition update code
from doing any EPQ processing for a merge, allowing the merge code to
work out what it needs to do.

This leads to a number of simplifications in nodeModifyTable.c. Most
notably, the ModifyTableContext->GetUpdateNewTuple() callback is no
longer needed, and mergeGetUpdateNewTuple() can be deleted, since
there is no longer any requirement for get-update-new-tuple during a
merge. Similarly, ModifyTableContext->cpUpdateRetrySlot is no longer
needed. Thus ExecGetUpdateNewTuple() and the retry_slot handling of
ExecCrossPartitionUpdate() can be restored to how they were in v14,
before the merge code was added, and ExecMergeMatched() no longer
needs any special-case handling for cross-partition updates.

While at it, tidy up ExecUpdateEpilogue() a bit, making it handle
recheckIndexes locally, rather than passing it in as a parameter,
ensuring that it is freed properly. This dates back to when it was
split off from ExecUpdate() to support merge.

Per bug #17809 from Alexander Lakhin, and follow-up investigation of
bug #17792, also from Alexander Lakhin.

Back-patch to v15, where MERGE was introduced, taking care to preserve
backwards-compatibility of the trigger API in v15 for any extensions
that might use it.

Discussion:
  https://postgr.es/m/17809-9e6650bef133f0fe%40postgresql.org
  https://postgr.es/m/17792-0f89452029662c36%40postgresql.org
This commit is contained in:
Dean Rasheed 2023-03-13 10:23:42 +00:00
parent 4493256c5c
commit 7d9a75713a
7 changed files with 695 additions and 189 deletions

View File

@ -84,8 +84,9 @@ static bool GetTupleForTrigger(EState *estate,
ItemPointer tid,
LockTupleMode lockmode,
TupleTableSlot *oldslot,
TupleTableSlot **newSlot,
TM_FailureData *tmfpd);
TupleTableSlot **epqslot,
TM_Result *tmresultp,
TM_FailureData *tmfdp);
static bool TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
Trigger *trigger, TriggerEvent event,
Bitmapset *modifiedCols,
@ -2753,11 +2754,13 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
* back the concurrently updated tuple if any.
*/
bool
ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
HeapTuple fdw_trigtuple,
TupleTableSlot **epqslot)
ExecBRDeleteTriggersNew(EState *estate, EPQState *epqstate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
HeapTuple fdw_trigtuple,
TupleTableSlot **epqslot,
TM_Result *tmresult,
TM_FailureData *tmfd)
{
TupleTableSlot *slot = ExecGetTriggerOldSlot(estate, relinfo);
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
@ -2774,7 +2777,7 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
if (!GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
LockTupleExclusive, slot, &epqslot_candidate,
NULL))
tmresult, tmfd))
return false;
/*
@ -2837,6 +2840,21 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
return result;
}
/*
* ABI-compatible wrapper to emulate old version of the above function.
* Do not call this version in new code.
*/
bool
ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
HeapTuple fdw_trigtuple,
TupleTableSlot **epqslot)
{
return ExecBRDeleteTriggersNew(estate, epqstate, relinfo, tupleid,
fdw_trigtuple, epqslot, NULL, NULL);
}
/*
* Note: is_crosspart_update must be true if the DELETE is being performed
* as part of a cross-partition update.
@ -2865,6 +2883,7 @@ ExecARDeleteTriggers(EState *estate,
LockTupleExclusive,
slot,
NULL,
NULL,
NULL);
else
ExecForceStoreHeapTuple(fdw_trigtuple, slot, false);
@ -3001,12 +3020,13 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
}
bool
ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
HeapTuple fdw_trigtuple,
TupleTableSlot *newslot,
TM_FailureData *tmfd)
ExecBRUpdateTriggersNew(EState *estate, EPQState *epqstate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
HeapTuple fdw_trigtuple,
TupleTableSlot *newslot,
TM_Result *tmresult,
TM_FailureData *tmfd)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
TupleTableSlot *oldslot = ExecGetTriggerOldSlot(estate, relinfo);
@ -3030,7 +3050,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
/* get a copy of the on-disk tuple we are planning to update */
if (!GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
lockmode, oldslot, &epqslot_candidate,
tmfd))
tmresult, tmfd))
return false; /* cancel the update action */
/*
@ -3134,6 +3154,22 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
return true;
}
/*
* ABI-compatible wrapper to emulate old version of the above function.
* Do not call this version in new code.
*/
bool
ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
HeapTuple fdw_trigtuple,
TupleTableSlot *newslot,
TM_FailureData *tmfd)
{
return ExecBRUpdateTriggersNew(estate, epqstate, relinfo, tupleid,
fdw_trigtuple, newslot, NULL, tmfd);
}
/*
* Note: 'src_partinfo' and 'dst_partinfo', when non-NULL, refer to the source
* and destination partitions, respectively, of a cross-partition update of
@ -3185,6 +3221,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
LockTupleExclusive,
oldslot,
NULL,
NULL,
NULL);
else if (fdw_trigtuple != NULL)
ExecForceStoreHeapTuple(fdw_trigtuple, oldslot, false);
@ -3340,6 +3377,7 @@ GetTupleForTrigger(EState *estate,
LockTupleMode lockmode,
TupleTableSlot *oldslot,
TupleTableSlot **epqslot,
TM_Result *tmresultp,
TM_FailureData *tmfdp)
{
Relation relation = relinfo->ri_RelationDesc;
@ -3367,6 +3405,8 @@ GetTupleForTrigger(EState *estate,
&tmfd);
/* Let the caller know about the status of this operation */
if (tmresultp)
*tmresultp = test;
if (tmfdp)
*tmfdp = tmfd;
@ -3394,6 +3434,18 @@ GetTupleForTrigger(EState *estate,
case TM_Ok:
if (tmfd.traversed)
{
/*
* 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 (estate->es_plannedstmt->commandType == CMD_MERGE)
{
if (tmresultp)
*tmresultp = TM_Updated;
return false;
}
*epqslot = EvalPlanQual(epqstate,
relation,
relinfo->ri_RangeTableIndex,

View File

@ -88,15 +88,6 @@ typedef struct ModifyTableContext
*/
TupleTableSlot *planSlot;
/*
* During EvalPlanQual, project and return the new version of the new
* tuple
*/
TupleTableSlot *(*GetUpdateNewTuple) (ResultRelInfo *resultRelInfo,
TupleTableSlot *epqslot,
TupleTableSlot *oldSlot,
MergeActionState *relaction);
/* MERGE specific */
MergeActionState *relaction; /* MERGE action in progress */
@ -106,12 +97,6 @@ typedef struct ModifyTableContext
*/
TM_FailureData tmfd;
/*
* The tuple produced by EvalPlanQual to retry from, if a cross-partition
* UPDATE requires it
*/
TupleTableSlot *cpUpdateRetrySlot;
/*
* The tuple projected by the INSERT's RETURNING clause, when doing a
* cross-partition UPDATE
@ -162,10 +147,6 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
ResultRelInfo *targetRelInfo,
TupleTableSlot *slot,
ResultRelInfo **partRelInfo);
static TupleTableSlot *internalGetUpdateNewTuple(ResultRelInfo *relinfo,
TupleTableSlot *planSlot,
TupleTableSlot *oldSlot,
MergeActionState *relaction);
static TupleTableSlot *ExecMerge(ModifyTableContext *context,
ResultRelInfo *resultRelInfo,
@ -179,10 +160,6 @@ static bool ExecMergeMatched(ModifyTableContext *context,
static void ExecMergeNotMatched(ModifyTableContext *context,
ResultRelInfo *resultRelInfo,
bool canSetTag);
static TupleTableSlot *mergeGetUpdateNewTuple(ResultRelInfo *relinfo,
TupleTableSlot *planSlot,
TupleTableSlot *oldSlot,
MergeActionState *relaction);
/*
@ -719,26 +696,14 @@ ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
TupleTableSlot *planSlot,
TupleTableSlot *oldSlot)
{
ProjectionInfo *newProj = relinfo->ri_projectNew;
ExprContext *econtext;
/* Use a few extra Asserts to protect against outside callers */
Assert(relinfo->ri_projectNewInfoValid);
Assert(planSlot != NULL && !TTS_EMPTY(planSlot));
Assert(oldSlot != NULL && !TTS_EMPTY(oldSlot));
return internalGetUpdateNewTuple(relinfo, planSlot, oldSlot, NULL);
}
/*
* Callback for ModifyTableState->GetUpdateNewTuple for use by regular UPDATE.
*/
static TupleTableSlot *
internalGetUpdateNewTuple(ResultRelInfo *relinfo,
TupleTableSlot *planSlot,
TupleTableSlot *oldSlot,
MergeActionState *relaction)
{
ProjectionInfo *newProj = relinfo->ri_projectNew;
ExprContext *econtext;
econtext = newProj->pi_exprContext;
econtext->ecxt_outertuple = planSlot;
econtext->ecxt_scantuple = oldSlot;
@ -1317,8 +1282,11 @@ ExecPendingInserts(EState *estate)
static bool
ExecDeletePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
ItemPointer tupleid, HeapTuple oldtuple,
TupleTableSlot **epqreturnslot)
TupleTableSlot **epqreturnslot, TM_Result *result)
{
if (result)
*result = TM_Ok;
/* BEFORE ROW DELETE triggers */
if (resultRelInfo->ri_TrigDesc &&
resultRelInfo->ri_TrigDesc->trig_delete_before_row)
@ -1327,9 +1295,9 @@ ExecDeletePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
if (context->estate->es_insert_pending_result_relations != NIL)
ExecPendingInserts(context->estate);
return ExecBRDeleteTriggers(context->estate, context->epqstate,
resultRelInfo, tupleid, oldtuple,
epqreturnslot);
return ExecBRDeleteTriggersNew(context->estate, context->epqstate,
resultRelInfo, tupleid, oldtuple,
epqreturnslot, result, &context->tmfd);
}
return true;
@ -1446,7 +1414,7 @@ ExecDelete(ModifyTableContext *context,
* done if it says we are.
*/
if (!ExecDeletePrologue(context, resultRelInfo, tupleid, oldtuple,
epqreturnslot))
epqreturnslot, NULL))
return NULL;
/* INSTEAD OF ROW DELETE Triggers */
@ -1727,8 +1695,10 @@ ldelete:;
*
* False is returned if the tuple we're trying to move is found to have been
* concurrently updated. In that case, the caller must check if the updated
* tuple (in updateCxt->cpUpdateRetrySlot) still needs to be re-routed, and
* call this function again or perform a regular update accordingly.
* tuple that's returned in *retry_slot still needs to be re-routed, and call
* this function again or perform a regular update accordingly. For MERGE,
* the updated tuple is not returned in *retry_slot; it has its own retry
* logic.
*/
static bool
ExecCrossPartitionUpdate(ModifyTableContext *context,
@ -1737,6 +1707,7 @@ ExecCrossPartitionUpdate(ModifyTableContext *context,
TupleTableSlot *slot,
bool canSetTag,
UpdateContext *updateCxt,
TupleTableSlot **retry_slot,
TupleTableSlot **inserted_tuple,
ResultRelInfo **insert_destrel)
{
@ -1747,7 +1718,7 @@ ExecCrossPartitionUpdate(ModifyTableContext *context,
TupleTableSlot *epqslot = NULL;
context->cpUpdateReturningSlot = NULL;
context->cpUpdateRetrySlot = NULL;
*retry_slot = NULL;
/*
* Disallow an INSERT ON CONFLICT DO UPDATE that causes the original row
@ -1826,9 +1797,13 @@ ExecCrossPartitionUpdate(ModifyTableContext *context,
* another transaction has concurrently updated the same row, it
* re-fetches the row, skips the delete, and epqslot is set to the
* re-fetched tuple slot. In that case, we need to do all the checks
* again.
* again. For MERGE, we leave everything to the caller (it must do
* additional rechecking, and might end up executing a different
* action entirely).
*/
if (TupIsNull(epqslot))
if (context->relaction != NULL)
return false;
else if (TupIsNull(epqslot))
return true;
else
{
@ -1845,9 +1820,8 @@ ExecCrossPartitionUpdate(ModifyTableContext *context,
oldSlot))
elog(ERROR, "failed to fetch tuple being updated");
/* and project the new tuple to retry the UPDATE with */
context->cpUpdateRetrySlot =
context->GetUpdateNewTuple(resultRelInfo, epqslot, oldSlot,
context->relaction);
*retry_slot = ExecGetUpdateNewTuple(resultRelInfo, epqslot,
oldSlot);
return false;
}
}
@ -1888,10 +1862,14 @@ ExecCrossPartitionUpdate(ModifyTableContext *context,
*/
static bool
ExecUpdatePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot)
ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,
TM_Result *result)
{
Relation resultRelationDesc = resultRelInfo->ri_RelationDesc;
if (result)
*result = TM_Ok;
ExecMaterializeSlot(slot);
/*
@ -1910,9 +1888,9 @@ ExecUpdatePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
if (context->estate->es_insert_pending_result_relations != NIL)
ExecPendingInserts(context->estate);
return ExecBRUpdateTriggers(context->estate, context->epqstate,
resultRelInfo, tupleid, oldtuple, slot,
&context->tmfd);
return ExecBRUpdateTriggersNew(context->estate, context->epqstate,
resultRelInfo, tupleid, oldtuple, slot,
result, &context->tmfd);
}
return true;
@ -2016,7 +1994,8 @@ lreplace:
*/
if (partition_constraint_failed)
{
TupleTableSlot *inserted_tuple;
TupleTableSlot *inserted_tuple,
*retry_slot;
ResultRelInfo *insert_destrel = NULL;
/*
@ -2028,6 +2007,7 @@ lreplace:
if (ExecCrossPartitionUpdate(context, resultRelInfo,
tupleid, oldtuple, slot,
canSetTag, updateCxt,
&retry_slot,
&inserted_tuple,
&insert_destrel))
{
@ -2072,7 +2052,7 @@ lreplace:
* ExecCrossPartitionUpdate installed an updated version of the new
* tuple in the retry slot; start over.
*/
slot = context->cpUpdateRetrySlot;
slot = retry_slot;
goto lreplace;
}
@ -2116,10 +2096,10 @@ lreplace:
static void
ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
ResultRelInfo *resultRelInfo, ItemPointer tupleid,
HeapTuple oldtuple, TupleTableSlot *slot,
List *recheckIndexes)
HeapTuple oldtuple, TupleTableSlot *slot)
{
ModifyTableState *mtstate = context->mtstate;
List *recheckIndexes = NIL;
/* insert index entries for tuple if necessary */
if (resultRelInfo->ri_NumIndices > 0 && updateCxt->updateIndexes)
@ -2138,6 +2118,8 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
mtstate->mt_transition_capture,
false);
list_free(recheckIndexes);
/*
* Check any WITH CHECK OPTION constraints from parent views. We are
* required to do this after testing all constraints and uniqueness
@ -2256,7 +2238,6 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
EState *estate = context->estate;
Relation resultRelationDesc = resultRelInfo->ri_RelationDesc;
UpdateContext updateCxt = {0};
List *recheckIndexes = NIL;
TM_Result result;
/*
@ -2269,7 +2250,7 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
* Prepare for the update. This includes BEFORE ROW triggers, so we're
* done if it says we are.
*/
if (!ExecUpdatePrologue(context, resultRelInfo, tupleid, oldtuple, slot))
if (!ExecUpdatePrologue(context, resultRelInfo, tupleid, oldtuple, slot, NULL))
return NULL;
/* INSTEAD OF ROW UPDATE Triggers */
@ -2470,9 +2451,7 @@ redo_act:
(estate->es_processed)++;
ExecUpdateEpilogue(context, &updateCxt, resultRelInfo, tupleid, oldtuple,
slot, recheckIndexes);
list_free(recheckIndexes);
slot);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
@ -2839,7 +2818,6 @@ lmerge_matched:;
{
MergeActionState *relaction = (MergeActionState *) lfirst(l);
CmdType commandType = relaction->mas_action->commandType;
List *recheckIndexes = NIL;
TM_Result result;
UpdateContext updateCxt = {0};
@ -2886,13 +2864,10 @@ lmerge_matched:;
newslot = ExecProject(relaction->mas_proj);
context->relaction = relaction;
context->GetUpdateNewTuple = mergeGetUpdateNewTuple;
context->cpUpdateRetrySlot = NULL;
if (!ExecUpdatePrologue(context, resultRelInfo,
tupleid, NULL, newslot))
tupleid, NULL, newslot, &result))
{
result = TM_Ok;
/* Blocked by trigger, or concurrent update/delete */
break;
}
result = ExecUpdateAct(context, resultRelInfo, tupleid, NULL,
@ -2900,18 +2875,17 @@ lmerge_matched:;
if (result == TM_Ok && updateCxt.updated)
{
ExecUpdateEpilogue(context, &updateCxt, resultRelInfo,
tupleid, NULL, newslot, recheckIndexes);
tupleid, NULL, newslot);
mtstate->mt_merge_updated += 1;
}
break;
case CMD_DELETE:
context->relaction = relaction;
if (!ExecDeletePrologue(context, resultRelInfo, tupleid,
NULL, NULL))
NULL, NULL, &result))
{
result = TM_Ok;
/* Blocked by trigger, or concurrent update/delete */
break;
}
result = ExecDeleteAct(context, resultRelInfo, tupleid, false);
@ -2978,34 +2952,13 @@ lmerge_matched:;
/*
* The target tuple was concurrently updated by some other
* transaction.
*/
/*
* During an UPDATE, if cpUpdateRetrySlot is set, then
* ExecCrossPartitionUpdate() must have detected that the
* tuple was concurrently updated, so we restart the search
* for an appropriate WHEN MATCHED clause to process the
* updated tuple.
*
* In this case, ExecDelete() would already have performed
* EvalPlanQual() on the latest version of the tuple,
* which in turn would already have been loaded into
* ri_oldTupleSlot, so no need to do either of those
* things.
*/
if (commandType == CMD_UPDATE &&
!TupIsNull(context->cpUpdateRetrySlot))
goto lmerge_matched;
/*
* Otherwise, we run the EvalPlanQual() with the new
* version of the tuple. If EvalPlanQual() 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.
* 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;
lockmode = ExecUpdateLockMode(estate, resultRelInfo);
@ -3380,25 +3333,6 @@ ExecInitMergeTupleSlots(ModifyTableState *mtstate,
resultRelInfo->ri_projectNewInfoValid = true;
}
/*
* Callback for ModifyTableContext->GetUpdateNewTuple for use by MERGE. It
* computes the updated tuple by projecting from the current merge action's
* projection.
*/
static TupleTableSlot *
mergeGetUpdateNewTuple(ResultRelInfo *relinfo,
TupleTableSlot *planSlot,
TupleTableSlot *oldSlot,
MergeActionState *relaction)
{
ExprContext *econtext = relaction->mas_proj->pi_exprContext;
econtext->ecxt_scantuple = oldSlot;
econtext->ecxt_innertuple = planSlot;
return ExecProject(relaction->mas_proj);
}
/*
* Process BEFORE EACH STATEMENT triggers
*/
@ -3854,9 +3788,8 @@ ExecModifyTable(PlanState *pstate)
oldSlot))
elog(ERROR, "failed to fetch tuple being updated");
}
slot = internalGetUpdateNewTuple(resultRelInfo, context.planSlot,
oldSlot, NULL);
context.GetUpdateNewTuple = internalGetUpdateNewTuple;
slot = ExecGetUpdateNewTuple(resultRelInfo, context.planSlot,
oldSlot);
context.relaction = NULL;
/* Now apply the update. */

View File

@ -211,6 +211,14 @@ extern void ExecBSDeleteTriggers(EState *estate,
extern void ExecASDeleteTriggers(EState *estate,
ResultRelInfo *relinfo,
TransitionCaptureState *transition_capture);
extern bool ExecBRDeleteTriggersNew(EState *estate,
EPQState *epqstate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
HeapTuple fdw_trigtuple,
TupleTableSlot **epqslot,
TM_Result *tmresult,
TM_FailureData *tmfd);
extern bool ExecBRDeleteTriggers(EState *estate,
EPQState *epqstate,
ResultRelInfo *relinfo,
@ -231,6 +239,14 @@ extern void ExecBSUpdateTriggers(EState *estate,
extern void ExecASUpdateTriggers(EState *estate,
ResultRelInfo *relinfo,
TransitionCaptureState *transition_capture);
extern bool ExecBRUpdateTriggersNew(EState *estate,
EPQState *epqstate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
HeapTuple fdw_trigtuple,
TupleTableSlot *newslot,
TM_Result *tmresult,
TM_FailureData *tmfd);
extern bool ExecBRUpdateTriggers(EState *estate,
EPQState *epqstate,
ResultRelInfo *relinfo,

View File

@ -10,20 +10,31 @@ key|val
step c2: COMMIT;
starting permutation: merge_delete c1 select2 c2
step merge_delete: MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE;
starting permutation: delete_pa c1 select2_pa c2
step delete_pa: DELETE FROM target_pa t WHERE t.key = 1;
step c1: COMMIT;
step select2: SELECT * FROM target;
step select2_pa: SELECT * FROM target_pa;
key|val
---+---
(0 rows)
step c2: COMMIT;
starting permutation: delete c1 update1 select2 c2
starting permutation: delete_tg c1 select2_tg c2
s1: NOTICE: Delete: (1,setup1)
step delete_tg: DELETE FROM target_tg t WHERE t.key = 1;
step c1: COMMIT;
step select2_tg: SELECT * FROM target_tg;
key|val
---+---
(0 rows)
step c2: COMMIT;
starting permutation: delete c1 update2 select2 c2
step delete: DELETE FROM target t WHERE t.key = 1;
step c1: COMMIT;
step update1: UPDATE target t SET val = t.val || ' updated by update1' WHERE t.key = 1;
step update2: UPDATE target t SET val = t.val || ' updated by update2' WHERE t.key = 1;
step select2: SELECT * FROM target;
key|val
---+---
@ -31,11 +42,23 @@ key|val
step c2: COMMIT;
starting permutation: merge_delete c1 update1 select2 c2
step merge_delete: MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE;
starting permutation: delete_pa c1 update2_pa select2_pa c2
step delete_pa: DELETE FROM target_pa t WHERE t.key = 1;
step c1: COMMIT;
step update1: UPDATE target t SET val = t.val || ' updated by update1' WHERE t.key = 1;
step select2: SELECT * FROM target;
step update2_pa: UPDATE target_pa t SET val = t.val || ' updated by update2_pa' WHERE t.key = 1;
step select2_pa: SELECT * FROM target_pa;
key|val
---+---
(0 rows)
step c2: COMMIT;
starting permutation: delete_tg c1 update2_tg select2_tg c2
s1: NOTICE: Delete: (1,setup1)
step delete_tg: DELETE FROM target_tg t WHERE t.key = 1;
step c1: COMMIT;
step update2_tg: UPDATE target_tg t SET val = t.val || ' updated by update2_tg' WHERE t.key = 1;
step select2_tg: SELECT * FROM target_tg;
key|val
---+---
(0 rows)
@ -45,32 +68,72 @@ step c2: COMMIT;
starting permutation: delete c1 merge2 select2 c2
step delete: DELETE FROM target t WHERE t.key = 1;
step c1: COMMIT;
step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2a' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
step select2: SELECT * FROM target;
key|val
---+-------
1|merge2a
key|val
---+------
1|merge2
(1 row)
step c2: COMMIT;
starting permutation: merge_delete c1 merge2 select2 c2
step merge_delete: MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE;
starting permutation: delete_pa c1 merge2_pa select2_pa c2
step delete_pa: DELETE FROM target_pa t WHERE t.key = 1;
step c1: COMMIT;
step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2a' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
step select2: SELECT * FROM target;
key|val
---+-------
1|merge2a
step merge2_pa: MERGE INTO target_pa t USING (SELECT 1 as key, 'merge2_pa' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
step select2_pa: SELECT * FROM target_pa;
key|val
---+---------
1|merge2_pa
(1 row)
step c2: COMMIT;
starting permutation: delete update1 c1 select2 c2
starting permutation: delete_tg c1 merge2_tg select2_tg c2
s1: NOTICE: Delete: (1,setup1)
step delete_tg: DELETE FROM target_tg t WHERE t.key = 1;
step c1: COMMIT;
s2: NOTICE: Insert: (1,merge2_tg)
step merge2_tg: MERGE INTO target_tg t USING (SELECT 1 as key, 'merge2_tg' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
step select2_tg: SELECT * FROM target_tg;
key|val
---+---------
1|merge2_tg
(1 row)
step c2: COMMIT;
starting permutation: delete c1 merge_delete2 select2 c2
step delete: DELETE FROM target t WHERE t.key = 1;
step update1: UPDATE target t SET val = t.val || ' updated by update1' WHERE t.key = 1; <waiting ...>
step c1: COMMIT;
step update1: <... completed>
step merge_delete2: MERGE INTO target t USING (SELECT 1 as key, 'merge_delete2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN DELETE;
step select2: SELECT * FROM target;
key|val
---+-------------
1|merge_delete2
(1 row)
step c2: COMMIT;
starting permutation: delete_tg c1 merge_delete2_tg select2_tg c2
s1: NOTICE: Delete: (1,setup1)
step delete_tg: DELETE FROM target_tg t WHERE t.key = 1;
step c1: COMMIT;
s2: NOTICE: Insert: (1,merge_delete2_tg)
step merge_delete2_tg: MERGE INTO target_tg t USING (SELECT 1 as key, 'merge_delete2_tg' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN DELETE;
step select2_tg: SELECT * FROM target_tg;
key|val
---+----------------
1|merge_delete2_tg
(1 row)
step c2: COMMIT;
starting permutation: delete update2 c1 select2 c2
step delete: DELETE FROM target t WHERE t.key = 1;
step update2: UPDATE target t SET val = t.val || ' updated by update2' WHERE t.key = 1; <waiting ...>
step c1: COMMIT;
step update2: <... completed>
step select2: SELECT * FROM target;
key|val
---+---
@ -78,12 +141,25 @@ key|val
step c2: COMMIT;
starting permutation: merge_delete update1 c1 select2 c2
step merge_delete: MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE;
step update1: UPDATE target t SET val = t.val || ' updated by update1' WHERE t.key = 1; <waiting ...>
starting permutation: delete_pa update2_pa c1 select2_pa c2
step delete_pa: DELETE FROM target_pa t WHERE t.key = 1;
step update2_pa: UPDATE target_pa t SET val = t.val || ' updated by update2_pa' WHERE t.key = 1; <waiting ...>
step c1: COMMIT;
step update1: <... completed>
step select2: SELECT * FROM target;
step update2_pa: <... completed>
step select2_pa: SELECT * FROM target_pa;
key|val
---+---
(0 rows)
step c2: COMMIT;
starting permutation: delete_tg update2_tg c1 select2_tg c2
s1: NOTICE: Delete: (1,setup1)
step delete_tg: DELETE FROM target_tg t WHERE t.key = 1;
step update2_tg: UPDATE target_tg t SET val = t.val || ' updated by update2_tg' WHERE t.key = 1; <waiting ...>
step c1: COMMIT;
step update2_tg: <... completed>
step select2_tg: SELECT * FROM target_tg;
key|val
---+---
(0 rows)
@ -92,26 +168,69 @@ step c2: COMMIT;
starting permutation: delete merge2 c1 select2 c2
step delete: DELETE FROM target t WHERE t.key = 1;
step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2a' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; <waiting ...>
step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; <waiting ...>
step c1: COMMIT;
step merge2: <... completed>
step select2: SELECT * FROM target;
key|val
---+-------
1|merge2a
key|val
---+------
1|merge2
(1 row)
step c2: COMMIT;
starting permutation: merge_delete merge2 c1 select2 c2
step merge_delete: MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE;
step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2a' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; <waiting ...>
starting permutation: delete_pa merge2_pa c1 select2_pa c2
step delete_pa: DELETE FROM target_pa t WHERE t.key = 1;
step merge2_pa: MERGE INTO target_pa t USING (SELECT 1 as key, 'merge2_pa' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; <waiting ...>
step c1: COMMIT;
step merge2: <... completed>
step select2: SELECT * FROM target;
key|val
---+-------
1|merge2a
step merge2_pa: <... completed>
step select2_pa: SELECT * FROM target_pa;
key|val
---+---------
1|merge2_pa
(1 row)
step c2: COMMIT;
starting permutation: delete_tg merge2_tg c1 select2_tg c2
s1: NOTICE: Delete: (1,setup1)
step delete_tg: DELETE FROM target_tg t WHERE t.key = 1;
step merge2_tg: MERGE INTO target_tg t USING (SELECT 1 as key, 'merge2_tg' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; <waiting ...>
step c1: COMMIT;
s2: NOTICE: Insert: (1,merge2_tg)
step merge2_tg: <... completed>
step select2_tg: SELECT * FROM target_tg;
key|val
---+---------
1|merge2_tg
(1 row)
step c2: COMMIT;
starting permutation: delete merge_delete2 c1 select2 c2
step delete: DELETE FROM target t WHERE t.key = 1;
step merge_delete2: MERGE INTO target t USING (SELECT 1 as key, 'merge_delete2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN DELETE; <waiting ...>
step c1: COMMIT;
step merge_delete2: <... completed>
step select2: SELECT * FROM target;
key|val
---+-------------
1|merge_delete2
(1 row)
step c2: COMMIT;
starting permutation: delete_tg merge_delete2_tg c1 select2_tg c2
s1: NOTICE: Delete: (1,setup1)
step delete_tg: DELETE FROM target_tg t WHERE t.key = 1;
step merge_delete2_tg: MERGE INTO target_tg t USING (SELECT 1 as key, 'merge_delete2_tg' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN DELETE; <waiting ...>
step c1: COMMIT;
s2: NOTICE: Insert: (1,merge_delete2_tg)
step merge_delete2_tg: <... completed>
step select2_tg: SELECT * FROM target_tg;
key|val
---+----------------
1|merge_delete2_tg
(1 row)
step c2: COMMIT;

View File

@ -23,6 +23,31 @@ key|balance|status|val
step c1: COMMIT;
starting permutation: update1_tg merge_status_tg c2 select1_tg c1
s2: NOTICE: Update: (1,160,s1,setup) -> (1,170,s1,"setup updated by update1_tg")
step update1_tg: UPDATE target_tg t SET balance = balance + 10, val = t.val || ' updated by update1_tg' WHERE t.key = 1;
step merge_status_tg:
MERGE INTO target_tg t
USING (SELECT 1 as key) s
ON s.key = t.key
WHEN MATCHED AND status = 's1' THEN
UPDATE SET status = 's2', val = t.val || ' when1'
WHEN MATCHED AND status = 's2' THEN
UPDATE SET status = 's3', val = t.val || ' when2'
WHEN MATCHED AND status = 's3' THEN
UPDATE SET status = 's4', val = t.val || ' when3';
<waiting ...>
step c2: COMMIT;
s1: NOTICE: Update: (1,170,s1,"setup updated by update1_tg") -> (1,170,s2,"setup updated by update1_tg when1")
step merge_status_tg: <... completed>
step select1_tg: SELECT * FROM target_tg;
key|balance|status|val
---+-------+------+---------------------------------
1| 170|s2 |setup updated by update1_tg when1
(1 row)
step c1: COMMIT;
starting permutation: update2 merge_status c2 select1 c1
step update2: UPDATE target t SET status = 's2', val = t.val || ' updated by update2' WHERE t.key = 1;
step merge_status:
@ -46,6 +71,31 @@ key|balance|status|val
step c1: COMMIT;
starting permutation: update2_tg merge_status_tg c2 select1_tg c1
s2: NOTICE: Update: (1,160,s1,setup) -> (1,160,s2,"setup updated by update2_tg")
step update2_tg: UPDATE target_tg t SET status = 's2', val = t.val || ' updated by update2_tg' WHERE t.key = 1;
step merge_status_tg:
MERGE INTO target_tg t
USING (SELECT 1 as key) s
ON s.key = t.key
WHEN MATCHED AND status = 's1' THEN
UPDATE SET status = 's2', val = t.val || ' when1'
WHEN MATCHED AND status = 's2' THEN
UPDATE SET status = 's3', val = t.val || ' when2'
WHEN MATCHED AND status = 's3' THEN
UPDATE SET status = 's4', val = t.val || ' when3';
<waiting ...>
step c2: COMMIT;
s1: NOTICE: Update: (1,160,s2,"setup updated by update2_tg") -> (1,160,s3,"setup updated by update2_tg when2")
step merge_status_tg: <... completed>
step select1_tg: SELECT * FROM target_tg;
key|balance|status|val
---+-------+------+---------------------------------
1| 160|s3 |setup updated by update2_tg when2
(1 row)
step c1: COMMIT;
starting permutation: update3 merge_status c2 select1 c1
step update3: UPDATE target t SET status = 's3', val = t.val || ' updated by update3' WHERE t.key = 1;
step merge_status:
@ -69,6 +119,31 @@ key|balance|status|val
step c1: COMMIT;
starting permutation: update3_tg merge_status_tg c2 select1_tg c1
s2: NOTICE: Update: (1,160,s1,setup) -> (1,160,s3,"setup updated by update3_tg")
step update3_tg: UPDATE target_tg t SET status = 's3', val = t.val || ' updated by update3_tg' WHERE t.key = 1;
step merge_status_tg:
MERGE INTO target_tg t
USING (SELECT 1 as key) s
ON s.key = t.key
WHEN MATCHED AND status = 's1' THEN
UPDATE SET status = 's2', val = t.val || ' when1'
WHEN MATCHED AND status = 's2' THEN
UPDATE SET status = 's3', val = t.val || ' when2'
WHEN MATCHED AND status = 's3' THEN
UPDATE SET status = 's4', val = t.val || ' when3';
<waiting ...>
step c2: COMMIT;
s1: NOTICE: Update: (1,160,s3,"setup updated by update3_tg") -> (1,160,s4,"setup updated by update3_tg when3")
step merge_status_tg: <... completed>
step select1_tg: SELECT * FROM target_tg;
key|balance|status|val
---+-------+------+---------------------------------
1| 160|s4 |setup updated by update3_tg when3
(1 row)
step c1: COMMIT;
starting permutation: update5 merge_status c2 select1 c1
step update5: UPDATE target t SET status = 's5', val = t.val || ' updated by update5' WHERE t.key = 1;
step merge_status:
@ -92,6 +167,30 @@ key|balance|status|val
step c1: COMMIT;
starting permutation: update5_tg merge_status_tg c2 select1_tg c1
s2: NOTICE: Update: (1,160,s1,setup) -> (1,160,s5,"setup updated by update5_tg")
step update5_tg: UPDATE target_tg t SET status = 's5', val = t.val || ' updated by update5_tg' WHERE t.key = 1;
step merge_status_tg:
MERGE INTO target_tg t
USING (SELECT 1 as key) s
ON s.key = t.key
WHEN MATCHED AND status = 's1' THEN
UPDATE SET status = 's2', val = t.val || ' when1'
WHEN MATCHED AND status = 's2' THEN
UPDATE SET status = 's3', val = t.val || ' when2'
WHEN MATCHED AND status = 's3' THEN
UPDATE SET status = 's4', val = t.val || ' when3';
<waiting ...>
step c2: COMMIT;
step merge_status_tg: <... completed>
step select1_tg: SELECT * FROM target_tg;
key|balance|status|val
---+-------+------+---------------------------
1| 160|s5 |setup updated by update5_tg
(1 row)
step c1: COMMIT;
starting permutation: update_bal1 merge_bal c2 select1 c1
step update_bal1: UPDATE target t SET balance = 50, val = t.val || ' updated by update_bal1' WHERE t.key = 1;
step merge_bal:
@ -114,3 +213,137 @@ key|balance|status|val
(1 row)
step c1: COMMIT;
starting permutation: update_bal1_pa merge_bal_pa c2 select1_pa c1
step update_bal1_pa: UPDATE target_pa t SET balance = 50, val = t.val || ' updated by update_bal1_pa' WHERE t.key = 1;
step merge_bal_pa:
MERGE INTO target_pa t
USING (SELECT 1 as key) s
ON s.key = t.key
WHEN MATCHED AND balance < 100 THEN
UPDATE SET balance = balance * 2, val = t.val || ' when1'
WHEN MATCHED AND balance < 200 THEN
UPDATE SET balance = balance * 4, val = t.val || ' when2'
WHEN MATCHED AND balance < 300 THEN
UPDATE SET balance = balance * 8, val = t.val || ' when3';
<waiting ...>
step c2: COMMIT;
step merge_bal_pa: <... completed>
step select1_pa: SELECT * FROM target_pa;
key|balance|status|val
---+-------+------+-------------------------------------
1| 100|s1 |setup updated by update_bal1_pa when1
(1 row)
step c1: COMMIT;
starting permutation: update_bal1_tg merge_bal_tg c2 select1_tg c1
s2: NOTICE: Update: (1,160,s1,setup) -> (1,50,s1,"setup updated by update_bal1_tg")
step update_bal1_tg: UPDATE target_tg t SET balance = 50, val = t.val || ' updated by update_bal1_tg' WHERE t.key = 1;
step merge_bal_tg:
MERGE INTO target_tg t
USING (SELECT 1 as key) s
ON s.key = t.key
WHEN MATCHED AND balance < 100 THEN
UPDATE SET balance = balance * 2, val = t.val || ' when1'
WHEN MATCHED AND balance < 200 THEN
UPDATE SET balance = balance * 4, val = t.val || ' when2'
WHEN MATCHED AND balance < 300 THEN
UPDATE SET balance = balance * 8, val = t.val || ' when3';
<waiting ...>
step c2: COMMIT;
s1: NOTICE: Update: (1,50,s1,"setup updated by update_bal1_tg") -> (1,100,s1,"setup updated by update_bal1_tg when1")
step merge_bal_tg: <... completed>
step select1_tg: SELECT * FROM target_tg;
key|balance|status|val
---+-------+------+-------------------------------------
1| 100|s1 |setup updated by update_bal1_tg when1
(1 row)
step c1: COMMIT;
starting permutation: update1 merge_delete c2 select1 c1
step update1: UPDATE target t SET balance = balance + 10, val = t.val || ' updated by update1' WHERE t.key = 1;
step merge_delete:
MERGE INTO target t
USING (SELECT 1 as key) s
ON s.key = t.key
WHEN MATCHED AND balance < 100 THEN
UPDATE SET balance = balance * 2, val = t.val || ' when1'
WHEN MATCHED AND balance < 200 THEN
DELETE;
<waiting ...>
step c2: COMMIT;
step merge_delete: <... completed>
step select1: SELECT * FROM target;
key|balance|status|val
---+-------+------+---
(0 rows)
step c1: COMMIT;
starting permutation: update1_tg merge_delete_tg c2 select1_tg c1
s2: NOTICE: Update: (1,160,s1,setup) -> (1,170,s1,"setup updated by update1_tg")
step update1_tg: UPDATE target_tg t SET balance = balance + 10, val = t.val || ' updated by update1_tg' WHERE t.key = 1;
step merge_delete_tg:
MERGE INTO target_tg t
USING (SELECT 1 as key) s
ON s.key = t.key
WHEN MATCHED AND balance < 100 THEN
UPDATE SET balance = balance * 2, val = t.val || ' when1'
WHEN MATCHED AND balance < 200 THEN
DELETE;
<waiting ...>
step c2: COMMIT;
s1: NOTICE: Delete: (1,170,s1,"setup updated by update1_tg")
step merge_delete_tg: <... completed>
step select1_tg: SELECT * FROM target_tg;
key|balance|status|val
---+-------+------+---
(0 rows)
step c1: COMMIT;
starting permutation: update_bal1 merge_delete c2 select1 c1
step update_bal1: UPDATE target t SET balance = 50, val = t.val || ' updated by update_bal1' WHERE t.key = 1;
step merge_delete:
MERGE INTO target t
USING (SELECT 1 as key) s
ON s.key = t.key
WHEN MATCHED AND balance < 100 THEN
UPDATE SET balance = balance * 2, val = t.val || ' when1'
WHEN MATCHED AND balance < 200 THEN
DELETE;
<waiting ...>
step c2: COMMIT;
step merge_delete: <... completed>
step select1: SELECT * FROM target;
key|balance|status|val
---+-------+------+----------------------------------
1| 100|s1 |setup updated by update_bal1 when1
(1 row)
step c1: COMMIT;
starting permutation: update_bal1_tg merge_delete_tg c2 select1_tg c1
s2: NOTICE: Update: (1,160,s1,setup) -> (1,50,s1,"setup updated by update_bal1_tg")
step update_bal1_tg: UPDATE target_tg t SET balance = 50, val = t.val || ' updated by update_bal1_tg' WHERE t.key = 1;
step merge_delete_tg:
MERGE INTO target_tg t
USING (SELECT 1 as key) s
ON s.key = t.key
WHEN MATCHED AND balance < 100 THEN
UPDATE SET balance = balance * 2, val = t.val || ' when1'
WHEN MATCHED AND balance < 200 THEN
DELETE;
<waiting ...>
step c2: COMMIT;
s1: NOTICE: Update: (1,50,s1,"setup updated by update_bal1_tg") -> (1,100,s1,"setup updated by update_bal1_tg when1")
step merge_delete_tg: <... completed>
step select1_tg: SELECT * FROM target_tg;
key|balance|status|val
---+-------+------+-------------------------------------
1| 100|s1 |setup updated by update_bal1_tg when1
(1 row)
step c1: COMMIT;

View File

@ -7,11 +7,39 @@ setup
{
CREATE TABLE target (key int primary key, val text);
INSERT INTO target VALUES (1, 'setup1');
CREATE TABLE target_pa (key int primary key, val text) PARTITION BY LIST (key);
CREATE TABLE target_pa1 PARTITION OF target_pa FOR VALUES IN (1);
CREATE TABLE target_pa2 PARTITION OF target_pa FOR VALUES IN (2);
INSERT INTO target_pa VALUES (1, 'setup1');
CREATE TABLE target_tg (key int primary key, val text);
CREATE FUNCTION target_tg_trig_fn() RETURNS trigger LANGUAGE plpgsql AS
$$
BEGIN
IF tg_op = 'INSERT' THEN
RAISE NOTICE 'Insert: %', NEW;
RETURN NEW;
ELSIF tg_op = 'UPDATE' THEN
RAISE NOTICE 'Update: % -> %', OLD, NEW;
RETURN NEW;
ELSE
RAISE NOTICE 'Delete: %', OLD;
RETURN OLD;
END IF;
END
$$;
CREATE TRIGGER target_tg_trig BEFORE INSERT OR UPDATE OR DELETE ON target_tg
FOR EACH ROW EXECUTE FUNCTION target_tg_trig_fn();
INSERT INTO target_tg VALUES (1, 'setup1');
}
teardown
{
DROP TABLE target;
DROP TABLE target_pa;
DROP TABLE target_tg;
DROP FUNCTION target_tg_trig_fn;
}
session "s1"
@ -20,7 +48,8 @@ setup
BEGIN ISOLATION LEVEL READ COMMITTED;
}
step "delete" { DELETE FROM target t WHERE t.key = 1; }
step "merge_delete" { MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE; }
step "delete_pa" { DELETE FROM target_pa t WHERE t.key = 1; }
step "delete_tg" { DELETE FROM target_tg t WHERE t.key = 1; }
step "c1" { COMMIT; }
session "s2"
@ -28,23 +57,40 @@ setup
{
BEGIN ISOLATION LEVEL READ COMMITTED;
}
step "update1" { UPDATE target t SET val = t.val || ' updated by update1' WHERE t.key = 1; }
step "merge2" { MERGE INTO target t USING (SELECT 1 as key, 'merge2a' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; }
step "update2" { UPDATE target t SET val = t.val || ' updated by update2' WHERE t.key = 1; }
step "update2_pa" { UPDATE target_pa t SET val = t.val || ' updated by update2_pa' WHERE t.key = 1; }
step "update2_tg" { UPDATE target_tg t SET val = t.val || ' updated by update2_tg' WHERE t.key = 1; }
step "merge2" { MERGE INTO target t USING (SELECT 1 as key, 'merge2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; }
step "merge2_pa" { MERGE INTO target_pa t USING (SELECT 1 as key, 'merge2_pa' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; }
step "merge2_tg" { MERGE INTO target_tg t USING (SELECT 1 as key, 'merge2_tg' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; }
step "merge_delete2" { MERGE INTO target t USING (SELECT 1 as key, 'merge_delete2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN DELETE; }
step "merge_delete2_tg" { MERGE INTO target_tg t USING (SELECT 1 as key, 'merge_delete2_tg' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN DELETE; }
step "select2" { SELECT * FROM target; }
step "select2_pa" { SELECT * FROM target_pa; }
step "select2_tg" { SELECT * FROM target_tg; }
step "c2" { COMMIT; }
# Basic effects
permutation "delete" "c1" "select2" "c2"
permutation "merge_delete" "c1" "select2" "c2"
permutation "delete_pa" "c1" "select2_pa" "c2"
permutation "delete_tg" "c1" "select2_tg" "c2"
# One after the other, no concurrency
permutation "delete" "c1" "update1" "select2" "c2"
permutation "merge_delete" "c1" "update1" "select2" "c2"
permutation "delete" "c1" "update2" "select2" "c2"
permutation "delete_pa" "c1" "update2_pa" "select2_pa" "c2"
permutation "delete_tg" "c1" "update2_tg" "select2_tg" "c2"
permutation "delete" "c1" "merge2" "select2" "c2"
permutation "merge_delete" "c1" "merge2" "select2" "c2"
permutation "delete_pa" "c1" "merge2_pa" "select2_pa" "c2"
permutation "delete_tg" "c1" "merge2_tg" "select2_tg" "c2"
permutation "delete" "c1" "merge_delete2" "select2" "c2"
permutation "delete_tg" "c1" "merge_delete2_tg" "select2_tg" "c2"
# Now with concurrency
permutation "delete" "update1" "c1" "select2" "c2"
permutation "merge_delete" "update1" "c1" "select2" "c2"
permutation "delete" "update2" "c1" "select2" "c2"
permutation "delete_pa" "update2_pa" "c1" "select2_pa" "c2"
permutation "delete_tg" "update2_tg" "c1" "select2_tg" "c2"
permutation "delete" "merge2" "c1" "select2" "c2"
permutation "merge_delete" "merge2" "c1" "select2" "c2"
permutation "delete_pa" "merge2_pa" "c1" "select2_pa" "c2"
permutation "delete_tg" "merge2_tg" "c1" "select2_tg" "c2"
permutation "delete" "merge_delete2" "c1" "select2" "c2"
permutation "delete_tg" "merge_delete2_tg" "c1" "select2_tg" "c2"

View File

@ -8,11 +8,39 @@ setup
{
CREATE TABLE target (key int primary key, balance integer, status text, val text);
INSERT INTO target VALUES (1, 160, 's1', 'setup');
CREATE TABLE target_pa (key int, balance integer, status text, val text) PARTITION BY RANGE (balance);
CREATE TABLE target_pa1 PARTITION OF target_pa FOR VALUES FROM (0) TO (200);
CREATE TABLE target_pa2 PARTITION OF target_pa FOR VALUES FROM (200) TO (1000);
INSERT INTO target_pa VALUES (1, 160, 's1', 'setup');
CREATE TABLE target_tg (key int primary key, balance integer, status text, val text);
CREATE FUNCTION target_tg_trig_fn() RETURNS trigger LANGUAGE plpgsql AS
$$
BEGIN
IF tg_op = 'INSERT' THEN
RAISE NOTICE 'Insert: %', NEW;
RETURN NEW;
ELSIF tg_op = 'UPDATE' THEN
RAISE NOTICE 'Update: % -> %', OLD, NEW;
RETURN NEW;
ELSE
RAISE NOTICE 'Delete: %', OLD;
RETURN OLD;
END IF;
END
$$;
CREATE TRIGGER target_tg_trig BEFORE INSERT OR UPDATE OR DELETE ON target_tg
FOR EACH ROW EXECUTE FUNCTION target_tg_trig_fn();
INSERT INTO target_tg VALUES (1, 160, 's1', 'setup');
}
teardown
{
DROP TABLE target;
DROP TABLE target_pa;
DROP TABLE target_tg;
DROP FUNCTION target_tg_trig_fn;
}
session "s1"
@ -32,6 +60,18 @@ step "merge_status"
WHEN MATCHED AND status = 's3' THEN
UPDATE SET status = 's4', val = t.val || ' when3';
}
step "merge_status_tg"
{
MERGE INTO target_tg t
USING (SELECT 1 as key) s
ON s.key = t.key
WHEN MATCHED AND status = 's1' THEN
UPDATE SET status = 's2', val = t.val || ' when1'
WHEN MATCHED AND status = 's2' THEN
UPDATE SET status = 's3', val = t.val || ' when2'
WHEN MATCHED AND status = 's3' THEN
UPDATE SET status = 's4', val = t.val || ' when3';
}
step "merge_bal"
{
@ -45,8 +85,55 @@ step "merge_bal"
WHEN MATCHED AND balance < 300 THEN
UPDATE SET balance = balance * 8, val = t.val || ' when3';
}
step "merge_bal_pa"
{
MERGE INTO target_pa t
USING (SELECT 1 as key) s
ON s.key = t.key
WHEN MATCHED AND balance < 100 THEN
UPDATE SET balance = balance * 2, val = t.val || ' when1'
WHEN MATCHED AND balance < 200 THEN
UPDATE SET balance = balance * 4, val = t.val || ' when2'
WHEN MATCHED AND balance < 300 THEN
UPDATE SET balance = balance * 8, val = t.val || ' when3';
}
step "merge_bal_tg"
{
MERGE INTO target_tg t
USING (SELECT 1 as key) s
ON s.key = t.key
WHEN MATCHED AND balance < 100 THEN
UPDATE SET balance = balance * 2, val = t.val || ' when1'
WHEN MATCHED AND balance < 200 THEN
UPDATE SET balance = balance * 4, val = t.val || ' when2'
WHEN MATCHED AND balance < 300 THEN
UPDATE SET balance = balance * 8, val = t.val || ' when3';
}
step "merge_delete"
{
MERGE INTO target t
USING (SELECT 1 as key) s
ON s.key = t.key
WHEN MATCHED AND balance < 100 THEN
UPDATE SET balance = balance * 2, val = t.val || ' when1'
WHEN MATCHED AND balance < 200 THEN
DELETE;
}
step "merge_delete_tg"
{
MERGE INTO target_tg t
USING (SELECT 1 as key) s
ON s.key = t.key
WHEN MATCHED AND balance < 100 THEN
UPDATE SET balance = balance * 2, val = t.val || ' when1'
WHEN MATCHED AND balance < 200 THEN
DELETE;
}
step "select1" { SELECT * FROM target; }
step "select1_pa" { SELECT * FROM target_pa; }
step "select1_tg" { SELECT * FROM target_tg; }
step "c1" { COMMIT; }
session "s2"
@ -55,23 +142,43 @@ setup
BEGIN ISOLATION LEVEL READ COMMITTED;
}
step "update1" { UPDATE target t SET balance = balance + 10, val = t.val || ' updated by update1' WHERE t.key = 1; }
step "update1_tg" { UPDATE target_tg t SET balance = balance + 10, val = t.val || ' updated by update1_tg' WHERE t.key = 1; }
step "update2" { UPDATE target t SET status = 's2', val = t.val || ' updated by update2' WHERE t.key = 1; }
step "update2_tg" { UPDATE target_tg t SET status = 's2', val = t.val || ' updated by update2_tg' WHERE t.key = 1; }
step "update3" { UPDATE target t SET status = 's3', val = t.val || ' updated by update3' WHERE t.key = 1; }
step "update3_tg" { UPDATE target_tg t SET status = 's3', val = t.val || ' updated by update3_tg' WHERE t.key = 1; }
step "update5" { UPDATE target t SET status = 's5', val = t.val || ' updated by update5' WHERE t.key = 1; }
step "update5_tg" { UPDATE target_tg t SET status = 's5', val = t.val || ' updated by update5_tg' WHERE t.key = 1; }
step "update_bal1" { UPDATE target t SET balance = 50, val = t.val || ' updated by update_bal1' WHERE t.key = 1; }
step "update_bal1_pa" { UPDATE target_pa t SET balance = 50, val = t.val || ' updated by update_bal1_pa' WHERE t.key = 1; }
step "update_bal1_tg" { UPDATE target_tg t SET balance = 50, val = t.val || ' updated by update_bal1_tg' WHERE t.key = 1; }
step "c2" { COMMIT; }
# merge_status sees concurrently updated row and rechecks WHEN conditions, but recheck passes and final status = 's2'
permutation "update1" "merge_status" "c2" "select1" "c1"
permutation "update1_tg" "merge_status_tg" "c2" "select1_tg" "c1"
# merge_status sees concurrently updated row and rechecks WHEN conditions, recheck fails, so final status = 's3' not 's2'
permutation "update2" "merge_status" "c2" "select1" "c1"
permutation "update2_tg" "merge_status_tg" "c2" "select1_tg" "c1"
# merge_status sees concurrently updated row and rechecks WHEN conditions, recheck fails, so final status = 's4' not 's2'
permutation "update3" "merge_status" "c2" "select1" "c1"
permutation "update3_tg" "merge_status_tg" "c2" "select1_tg" "c1"
# merge_status sees concurrently updated row and rechecks WHEN conditions, recheck fails, but we skip update and MERGE does nothing
permutation "update5" "merge_status" "c2" "select1" "c1"
permutation "update5_tg" "merge_status_tg" "c2" "select1_tg" "c1"
# merge_bal sees concurrently updated row and rechecks WHEN conditions, recheck fails, so final balance = 100 not 640
permutation "update_bal1" "merge_bal" "c2" "select1" "c1"
permutation "update_bal1_pa" "merge_bal_pa" "c2" "select1_pa" "c1"
permutation "update_bal1_tg" "merge_bal_tg" "c2" "select1_tg" "c1"
# merge_delete sees concurrently updated row and rechecks WHEN conditions, but recheck passes and row is deleted
permutation "update1" "merge_delete" "c2" "select1" "c1"
permutation "update1_tg" "merge_delete_tg" "c2" "select1_tg" "c1"
# merge_delete sees concurrently updated row and rechecks WHEN conditions, recheck fails, so final balance is 100
permutation "update_bal1" "merge_delete" "c2" "select1" "c1"
permutation "update_bal1_tg" "merge_delete_tg" "c2" "select1_tg" "c1"