diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 66401f2839..0f2de7e2e0 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -87,6 +87,7 @@ static bool GetTupleForTrigger(EState *estate, LockTupleMode lockmode, TupleTableSlot *oldslot, TupleTableSlot **epqslot, + TM_Result *tmresultp, TM_FailureData *tmfdp); static bool TriggerEnabled(EState *estate, ResultRelInfo *relinfo, Trigger *trigger, TriggerEvent event, @@ -2694,7 +2695,9 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate, ResultRelInfo *relinfo, ItemPointer tupleid, HeapTuple fdw_trigtuple, - TupleTableSlot **epqslot) + TupleTableSlot **epqslot, + TM_Result *tmresult, + TM_FailureData *tmfd) { TupleTableSlot *slot = ExecGetTriggerOldSlot(estate, relinfo); TriggerDesc *trigdesc = relinfo->ri_TrigDesc; @@ -2711,7 +2714,7 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate, if (!GetTupleForTrigger(estate, epqstate, relinfo, tupleid, LockTupleExclusive, slot, &epqslot_candidate, - NULL)) + tmresult, tmfd)) return false; /* @@ -2802,6 +2805,7 @@ ExecARDeleteTriggers(EState *estate, LockTupleExclusive, slot, NULL, + NULL, NULL); else ExecForceStoreHeapTuple(fdw_trigtuple, slot, false); @@ -2943,6 +2947,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, ItemPointer tupleid, HeapTuple fdw_trigtuple, TupleTableSlot *newslot, + TM_Result *tmresult, TM_FailureData *tmfd) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; @@ -2967,7 +2972,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 */ /* @@ -3122,6 +3127,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, LockTupleExclusive, oldslot, NULL, + NULL, NULL); else if (fdw_trigtuple != NULL) ExecForceStoreHeapTuple(fdw_trigtuple, oldslot, false); @@ -3277,6 +3283,7 @@ GetTupleForTrigger(EState *estate, LockTupleMode lockmode, TupleTableSlot *oldslot, TupleTableSlot **epqslot, + TM_Result *tmresultp, TM_FailureData *tmfdp) { Relation relation = relinfo->ri_RelationDesc; @@ -3304,6 +3311,8 @@ GetTupleForTrigger(EState *estate, &tmfd); /* Let the caller know about the status of this operation */ + if (tmresultp) + *tmresultp = test; if (tmfdp) *tmfdp = tmfd; @@ -3331,6 +3340,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/execReplication.c b/src/backend/executor/execReplication.c index c484f5c301..4f5083a598 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -486,7 +486,7 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo, resultRelInfo->ri_TrigDesc->trig_update_before_row) { if (!ExecBRUpdateTriggers(estate, epqstate, resultRelInfo, - tid, NULL, slot, NULL)) + tid, NULL, slot, NULL, NULL)) skip_tuple = true; /* "do nothing" */ } @@ -547,7 +547,7 @@ ExecSimpleRelationDelete(ResultRelInfo *resultRelInfo, resultRelInfo->ri_TrigDesc->trig_delete_before_row) { skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo, - tid, NULL, NULL); + tid, NULL, NULL, NULL, NULL); } if (!skip_tuple) diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 6f44d71f16..e8d655868a 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); /* @@ -738,26 +715,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; @@ -1336,8 +1301,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) @@ -1348,7 +1316,7 @@ ExecDeletePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo, return ExecBRDeleteTriggers(context->estate, context->epqstate, resultRelInfo, tupleid, oldtuple, - epqreturnslot); + epqreturnslot, result, &context->tmfd); } return true; @@ -1465,7 +1433,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 */ @@ -1746,8 +1714,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, @@ -1756,6 +1726,7 @@ ExecCrossPartitionUpdate(ModifyTableContext *context, TupleTableSlot *slot, bool canSetTag, UpdateContext *updateCxt, + TupleTableSlot **retry_slot, TupleTableSlot **inserted_tuple, ResultRelInfo **insert_destrel) { @@ -1766,7 +1737,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 @@ -1845,9 +1816,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 { @@ -1864,9 +1839,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; } } @@ -1907,10 +1881,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); /* @@ -1931,7 +1909,7 @@ ExecUpdatePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo, return ExecBRUpdateTriggers(context->estate, context->epqstate, resultRelInfo, tupleid, oldtuple, slot, - &context->tmfd); + result, &context->tmfd); } return true; @@ -2032,7 +2010,8 @@ lreplace: */ if (partition_constraint_failed) { - TupleTableSlot *inserted_tuple; + TupleTableSlot *inserted_tuple, + *retry_slot; ResultRelInfo *insert_destrel = NULL; /* @@ -2044,6 +2023,7 @@ lreplace: if (ExecCrossPartitionUpdate(context, resultRelInfo, tupleid, oldtuple, slot, canSetTag, updateCxt, + &retry_slot, &inserted_tuple, &insert_destrel)) { @@ -2088,7 +2068,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; } @@ -2132,10 +2112,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) @@ -2154,6 +2134,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 @@ -2272,7 +2254,6 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo, EState *estate = context->estate; Relation resultRelationDesc = resultRelInfo->ri_RelationDesc; UpdateContext updateCxt = {0}; - List *recheckIndexes = NIL; TM_Result result; /* @@ -2285,7 +2266,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 */ @@ -2486,9 +2467,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) @@ -2855,7 +2834,6 @@ lmerge_matched: { MergeActionState *relaction = (MergeActionState *) lfirst(l); CmdType commandType = relaction->mas_action->commandType; - List *recheckIndexes = NIL; TM_Result result; UpdateContext updateCxt = {0}; @@ -2902,13 +2880,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, @@ -2916,18 +2891,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); @@ -2994,34 +2968,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); @@ -3396,25 +3349,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 */ @@ -3870,9 +3804,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 67496d84f9..430e3ca7dd 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -211,7 +211,9 @@ extern bool ExecBRDeleteTriggers(EState *estate, ResultRelInfo *relinfo, ItemPointer tupleid, HeapTuple fdw_trigtuple, - TupleTableSlot **epqslot); + TupleTableSlot **epqslot, + TM_Result *tmresult, + TM_FailureData *tmfd); extern void ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo, ItemPointer tupleid, @@ -232,6 +234,7 @@ extern bool ExecBRUpdateTriggers(EState *estate, ItemPointer tupleid, HeapTuple fdw_trigtuple, TupleTableSlot *newslot, + TM_Result *tmresult, TM_FailureData *tmfd); extern void ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, diff --git a/src/test/isolation/expected/merge-delete.out b/src/test/isolation/expected/merge-delete.out index b2befa8e16..897b935135 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 8183f52ce0..9a44a59592 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 0e7053270e..ba5f70e53d 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 d56400a6a2..298b2bfdcd 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"