diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index d9d9201ac38..7326ece5565 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -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, diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 802bea0e6d3..e1fc8ff501b 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -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. */ diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 0d7558ee6ef..fff8d16c416 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -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, diff --git a/src/test/isolation/expected/merge-delete.out b/src/test/isolation/expected/merge-delete.out index b2befa8e167..897b9351355 100644 --- a/src/test/isolation/expected/merge-delete.out +++ b/src/test/isolation/expected/merge-delete.out @@ -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; 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; +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; +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; 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; +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; +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 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; +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; 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; +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; +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; +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; diff --git a/src/test/isolation/expected/merge-match-recheck.out b/src/test/isolation/expected/merge-match-recheck.out index 8183f52ce02..9a44a595927 100644 --- a/src/test/isolation/expected/merge-match-recheck.out +++ b/src/test/isolation/expected/merge-match-recheck.out @@ -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'; + +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'; + +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'; + +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'; + +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'; + +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'; + +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; + +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; + +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; + +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; + +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; diff --git a/src/test/isolation/specs/merge-delete.spec b/src/test/isolation/specs/merge-delete.spec index 0e7053270ec..ba5f70e53dc 100644 --- a/src/test/isolation/specs/merge-delete.spec +++ b/src/test/isolation/specs/merge-delete.spec @@ -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" diff --git a/src/test/isolation/specs/merge-match-recheck.spec b/src/test/isolation/specs/merge-match-recheck.spec index d56400a6a22..298b2bfdcd6 100644 --- a/src/test/isolation/specs/merge-match-recheck.spec +++ b/src/test/isolation/specs/merge-match-recheck.spec @@ -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"