Fix concurrent update issues with MERGE.
authorDean Rasheed <dean.a.rasheed@gmail.com>
Mon, 13 Mar 2023 10:22:22 +0000 (10:22 +0000)
committerDean Rasheed <dean.a.rasheed@gmail.com>
Mon, 13 Mar 2023 10:22:22 +0000 (10:22 +0000)
If MERGE attempts an UPDATE or DELETE on a table with BEFORE ROW
triggers, or a cross-partition UPDATE (with or without triggers), and
a concurrent UPDATE or DELETE happens, the merge code would fail.

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

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

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

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

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

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

Discussion:
  https://postgr.es/m/17809-9e6650bef133f0fe%40postgresql.org
  https://postgr.es/m/17792-0f89452029662c36%40postgresql.org

src/backend/commands/trigger.c
src/backend/executor/execReplication.c
src/backend/executor/nodeModifyTable.c
src/include/commands/trigger.h
src/test/isolation/expected/merge-delete.out
src/test/isolation/expected/merge-match-recheck.out
src/test/isolation/specs/merge-delete.spec
src/test/isolation/specs/merge-match-recheck.spec

index 66401f283927bd0ba726d83c4c31f443bbb193af..0f2de7e2e01937d28fa9fa0e70ed1bcf3c0f9296 100644 (file)
@@ -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,
index c484f5c3019eb3238d69dc992625dad00810ee30..4f5083a598a69d63b09c682f8e198ab47e129818 100644 (file)
@@ -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)
index 6f44d71f16b683d35176da259d8fb25b1fada8b2..e8d655868ae2724c645136d39a678b57f55ea16a 100644 (file)
@@ -88,15 +88,6 @@ typedef struct ModifyTableContext
     */
    TupleTableSlot *planSlot;
 
-   /*
-    * During EvalPlanQual, project and return the new version of the new
-    * tuple
-    */
-   TupleTableSlot *(*GetUpdateNewTuple) (ResultRelInfo *resultRelInfo,
-                                         TupleTableSlot *epqslot,
-                                         TupleTableSlot *oldSlot,
-                                         MergeActionState *relaction);
-
    /* MERGE specific */
    MergeActionState *relaction;    /* MERGE action in progress */
 
@@ -106,12 +97,6 @@ typedef struct ModifyTableContext
     */
    TM_FailureData tmfd;
 
-   /*
-    * The tuple produced by EvalPlanQual to retry from, if a cross-partition
-    * UPDATE requires it
-    */
-   TupleTableSlot *cpUpdateRetrySlot;
-
    /*
     * The tuple projected by the INSERT's RETURNING clause, when doing a
     * cross-partition UPDATE
@@ -162,10 +147,6 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
                                               ResultRelInfo *targetRelInfo,
                                               TupleTableSlot *slot,
                                               ResultRelInfo **partRelInfo);
-static TupleTableSlot *internalGetUpdateNewTuple(ResultRelInfo *relinfo,
-                                                TupleTableSlot *planSlot,
-                                                TupleTableSlot *oldSlot,
-                                                MergeActionState *relaction);
 
 static TupleTableSlot *ExecMerge(ModifyTableContext *context,
                                 ResultRelInfo *resultRelInfo,
@@ -179,10 +160,6 @@ static bool ExecMergeMatched(ModifyTableContext *context,
 static void ExecMergeNotMatched(ModifyTableContext *context,
                                ResultRelInfo *resultRelInfo,
                                bool canSetTag);
-static TupleTableSlot *mergeGetUpdateNewTuple(ResultRelInfo *relinfo,
-                                             TupleTableSlot *planSlot,
-                                             TupleTableSlot *oldSlot,
-                                             MergeActionState *relaction);
 
 
 /*
@@ -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. */
index 67496d84f9847259db2b0af1c0768b7d557394af..430e3ca7ddf35ff59db76e5e5e0bc9bf28db5d9a 100644 (file)
@@ -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,
index b2befa8e16767f196bccdc56c16e55ba735ec865..897b93513551058f71e2fbcebf006ccae74718cf 100644 (file)
@@ -10,20 +10,31 @@ key|val
 
 step c2: COMMIT;
 
-starting permutation: merge_delete c1 select2 c2
-step merge_delete: MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE;
+starting permutation: delete_pa c1 select2_pa c2
+step delete_pa: DELETE FROM target_pa t WHERE t.key = 1;
 step c1: COMMIT;
-step select2: SELECT * FROM target;
+step select2_pa: SELECT * FROM target_pa;
+key|val
+---+---
+(0 rows)
+
+step c2: COMMIT;
+
+starting permutation: delete_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 update1 select2 c2
+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: 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_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_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: 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 c1 merge_delete2 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 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|merge2a
+key|val          
+---+-------------
+  1|merge_delete2
 (1 row)
 
 step c2: COMMIT;
 
-starting permutation: delete update1 c1 select2 c2
+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 update1: UPDATE target t SET val = t.val || ' updated by update1' WHERE t.key = 1; <waiting ...>
+step update2: UPDATE target t SET val = t.val || ' updated by update2' WHERE t.key = 1; <waiting ...>
 step c1: COMMIT;
-step update1: <... completed>
+step update2: <... completed>
 step select2: SELECT * FROM target;
 key|val
 ---+---
@@ -78,12 +141,25 @@ key|val
 
 step c2: COMMIT;
 
-starting permutation: merge_delete update1 c1 select2 c2
-step merge_delete: MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE;
-step update1: UPDATE target t SET val = t.val || ' updated by update1' WHERE t.key = 1; <waiting ...>
+starting permutation: delete_pa update2_pa c1 select2_pa c2
+step delete_pa: DELETE FROM target_pa t WHERE t.key = 1;
+step update2_pa: UPDATE target_pa t SET val = t.val || ' updated by update2_pa' WHERE t.key = 1; <waiting ...>
 step c1: COMMIT;
-step update1: <... completed>
-step select2: SELECT * FROM target;
+step update2_pa: <... completed>
+step select2_pa: SELECT * FROM target_pa;
+key|val
+---+---
+(0 rows)
+
+step c2: COMMIT;
+
+starting permutation: delete_tg update2_tg c1 select2_tg c2
+s1: NOTICE:  Delete: (1,setup1)
+step delete_tg: DELETE FROM target_tg t WHERE t.key = 1;
+step update2_tg: UPDATE target_tg t SET val = t.val || ' updated by update2_tg' WHERE t.key = 1; <waiting ...>
+step c1: COMMIT;
+step update2_tg: <... completed>
+step select2_tg: SELECT * FROM target_tg;
 key|val
 ---+---
 (0 rows)
@@ -92,26 +168,69 @@ step c2: COMMIT;
 
 starting permutation: delete merge2 c1 select2 c2
 step delete: DELETE FROM target t WHERE t.key = 1;
-step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2a' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; <waiting ...>
+step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; <waiting ...>
 step c1: COMMIT;
 step merge2: <... completed>
 step select2: SELECT * FROM target;
-key|val    
----+-------
-  1|merge2a
+key|val   
+---+------
+  1|merge2
 (1 row)
 
 step c2: COMMIT;
 
-starting permutation: merge_delete merge2 c1 select2 c2
-step merge_delete: MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE;
-step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2a' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; <waiting ...>
+starting permutation: delete_pa merge2_pa c1 select2_pa c2
+step delete_pa: DELETE FROM target_pa t WHERE t.key = 1;
+step merge2_pa: MERGE INTO target_pa t USING (SELECT 1 as key, 'merge2_pa' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; <waiting ...>
 step c1: COMMIT;
-step merge2: <... completed>
+step merge2_pa: <... completed>
+step select2_pa: SELECT * FROM target_pa;
+key|val      
+---+---------
+  1|merge2_pa
+(1 row)
+
+step c2: COMMIT;
+
+starting permutation: delete_tg merge2_tg c1 select2_tg c2
+s1: NOTICE:  Delete: (1,setup1)
+step delete_tg: DELETE FROM target_tg t WHERE t.key = 1;
+step merge2_tg: MERGE INTO target_tg t USING (SELECT 1 as key, 'merge2_tg' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; <waiting ...>
+step c1: COMMIT;
+s2: NOTICE:  Insert: (1,merge2_tg)
+step merge2_tg: <... completed>
+step select2_tg: SELECT * FROM target_tg;
+key|val      
+---+---------
+  1|merge2_tg
+(1 row)
+
+step c2: COMMIT;
+
+starting permutation: delete merge_delete2 c1 select2 c2
+step delete: DELETE FROM target t WHERE t.key = 1;
+step merge_delete2: MERGE INTO target t USING (SELECT 1 as key, 'merge_delete2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN DELETE; <waiting ...>
+step c1: COMMIT;
+step merge_delete2: <... completed>
 step select2: SELECT * FROM target;
-key|val    
----+-------
-  1|merge2a
+key|val          
+---+-------------
+  1|merge_delete2
+(1 row)
+
+step c2: COMMIT;
+
+starting permutation: delete_tg merge_delete2_tg c1 select2_tg c2
+s1: NOTICE:  Delete: (1,setup1)
+step delete_tg: DELETE FROM target_tg t WHERE t.key = 1;
+step merge_delete2_tg: MERGE INTO target_tg t USING (SELECT 1 as key, 'merge_delete2_tg' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN DELETE; <waiting ...>
+step c1: COMMIT;
+s2: NOTICE:  Insert: (1,merge_delete2_tg)
+step merge_delete2_tg: <... completed>
+step select2_tg: SELECT * FROM target_tg;
+key|val             
+---+----------------
+  1|merge_delete2_tg
 (1 row)
 
 step c2: COMMIT;
index 8183f52ce02d3eb0549eea1d88070111baf1faad..9a44a5959270b796aa4b1661ab06ee833c695cc9 100644 (file)
@@ -23,6 +23,31 @@ key|balance|status|val
 
 step c1: COMMIT;
 
+starting permutation: update1_tg merge_status_tg c2 select1_tg c1
+s2: NOTICE:  Update: (1,160,s1,setup) -> (1,170,s1,"setup updated by update1_tg")
+step update1_tg: UPDATE target_tg t SET balance = balance + 10, val = t.val || ' updated by update1_tg' WHERE t.key = 1;
+step merge_status_tg: 
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND status = 's1' THEN
+   UPDATE SET status = 's2', val = t.val || ' when1'
+  WHEN MATCHED AND status = 's2' THEN
+   UPDATE SET status = 's3', val = t.val || ' when2'
+  WHEN MATCHED AND status = 's3' THEN
+   UPDATE SET status = 's4', val = t.val || ' when3';
+ <waiting ...>
+step c2: COMMIT;
+s1: NOTICE:  Update: (1,170,s1,"setup updated by update1_tg") -> (1,170,s2,"setup updated by update1_tg when1")
+step merge_status_tg: <... completed>
+step select1_tg: SELECT * FROM target_tg;
+key|balance|status|val                              
+---+-------+------+---------------------------------
+  1|    170|s2    |setup updated by update1_tg when1
+(1 row)
+
+step c1: COMMIT;
+
 starting permutation: update2 merge_status c2 select1 c1
 step update2: UPDATE target t SET status = 's2', val = t.val || ' updated by update2' WHERE t.key = 1;
 step merge_status: 
@@ -46,6 +71,31 @@ key|balance|status|val
 
 step c1: COMMIT;
 
+starting permutation: update2_tg merge_status_tg c2 select1_tg c1
+s2: NOTICE:  Update: (1,160,s1,setup) -> (1,160,s2,"setup updated by update2_tg")
+step update2_tg: UPDATE target_tg t SET status = 's2', val = t.val || ' updated by update2_tg' WHERE t.key = 1;
+step merge_status_tg: 
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND status = 's1' THEN
+   UPDATE SET status = 's2', val = t.val || ' when1'
+  WHEN MATCHED AND status = 's2' THEN
+   UPDATE SET status = 's3', val = t.val || ' when2'
+  WHEN MATCHED AND status = 's3' THEN
+   UPDATE SET status = 's4', val = t.val || ' when3';
+ <waiting ...>
+step c2: COMMIT;
+s1: NOTICE:  Update: (1,160,s2,"setup updated by update2_tg") -> (1,160,s3,"setup updated by update2_tg when2")
+step merge_status_tg: <... completed>
+step select1_tg: SELECT * FROM target_tg;
+key|balance|status|val                              
+---+-------+------+---------------------------------
+  1|    160|s3    |setup updated by update2_tg when2
+(1 row)
+
+step c1: COMMIT;
+
 starting permutation: update3 merge_status c2 select1 c1
 step update3: UPDATE target t SET status = 's3', val = t.val || ' updated by update3' WHERE t.key = 1;
 step merge_status: 
@@ -69,6 +119,31 @@ key|balance|status|val
 
 step c1: COMMIT;
 
+starting permutation: update3_tg merge_status_tg c2 select1_tg c1
+s2: NOTICE:  Update: (1,160,s1,setup) -> (1,160,s3,"setup updated by update3_tg")
+step update3_tg: UPDATE target_tg t SET status = 's3', val = t.val || ' updated by update3_tg' WHERE t.key = 1;
+step merge_status_tg: 
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND status = 's1' THEN
+   UPDATE SET status = 's2', val = t.val || ' when1'
+  WHEN MATCHED AND status = 's2' THEN
+   UPDATE SET status = 's3', val = t.val || ' when2'
+  WHEN MATCHED AND status = 's3' THEN
+   UPDATE SET status = 's4', val = t.val || ' when3';
+ <waiting ...>
+step c2: COMMIT;
+s1: NOTICE:  Update: (1,160,s3,"setup updated by update3_tg") -> (1,160,s4,"setup updated by update3_tg when3")
+step merge_status_tg: <... completed>
+step select1_tg: SELECT * FROM target_tg;
+key|balance|status|val                              
+---+-------+------+---------------------------------
+  1|    160|s4    |setup updated by update3_tg when3
+(1 row)
+
+step c1: COMMIT;
+
 starting permutation: update5 merge_status c2 select1 c1
 step update5: UPDATE target t SET status = 's5', val = t.val || ' updated by update5' WHERE t.key = 1;
 step merge_status: 
@@ -92,6 +167,30 @@ key|balance|status|val
 
 step c1: COMMIT;
 
+starting permutation: update5_tg merge_status_tg c2 select1_tg c1
+s2: NOTICE:  Update: (1,160,s1,setup) -> (1,160,s5,"setup updated by update5_tg")
+step update5_tg: UPDATE target_tg t SET status = 's5', val = t.val || ' updated by update5_tg' WHERE t.key = 1;
+step merge_status_tg: 
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND status = 's1' THEN
+   UPDATE SET status = 's2', val = t.val || ' when1'
+  WHEN MATCHED AND status = 's2' THEN
+   UPDATE SET status = 's3', val = t.val || ' when2'
+  WHEN MATCHED AND status = 's3' THEN
+   UPDATE SET status = 's4', val = t.val || ' when3';
+ <waiting ...>
+step c2: COMMIT;
+step merge_status_tg: <... completed>
+step select1_tg: SELECT * FROM target_tg;
+key|balance|status|val                        
+---+-------+------+---------------------------
+  1|    160|s5    |setup updated by update5_tg
+(1 row)
+
+step c1: COMMIT;
+
 starting permutation: update_bal1 merge_bal c2 select1 c1
 step update_bal1: UPDATE target t SET balance = 50, val = t.val || ' updated by update_bal1' WHERE t.key = 1;
 step merge_bal: 
@@ -114,3 +213,137 @@ key|balance|status|val
 (1 row)
 
 step c1: COMMIT;
+
+starting permutation: update_bal1_pa merge_bal_pa c2 select1_pa c1
+step update_bal1_pa: UPDATE target_pa t SET balance = 50, val = t.val || ' updated by update_bal1_pa' WHERE t.key = 1;
+step merge_bal_pa: 
+  MERGE INTO target_pa t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+   UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+   UPDATE SET balance = balance * 4, val = t.val || ' when2'
+  WHEN MATCHED AND balance < 300 THEN
+   UPDATE SET balance = balance * 8, val = t.val || ' when3';
+ <waiting ...>
+step c2: COMMIT;
+step merge_bal_pa: <... completed>
+step select1_pa: SELECT * FROM target_pa;
+key|balance|status|val                                  
+---+-------+------+-------------------------------------
+  1|    100|s1    |setup updated by update_bal1_pa when1
+(1 row)
+
+step c1: COMMIT;
+
+starting permutation: update_bal1_tg merge_bal_tg c2 select1_tg c1
+s2: NOTICE:  Update: (1,160,s1,setup) -> (1,50,s1,"setup updated by update_bal1_tg")
+step update_bal1_tg: UPDATE target_tg t SET balance = 50, val = t.val || ' updated by update_bal1_tg' WHERE t.key = 1;
+step merge_bal_tg: 
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+   UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+   UPDATE SET balance = balance * 4, val = t.val || ' when2'
+  WHEN MATCHED AND balance < 300 THEN
+   UPDATE SET balance = balance * 8, val = t.val || ' when3';
+ <waiting ...>
+step c2: COMMIT;
+s1: NOTICE:  Update: (1,50,s1,"setup updated by update_bal1_tg") -> (1,100,s1,"setup updated by update_bal1_tg when1")
+step merge_bal_tg: <... completed>
+step select1_tg: SELECT * FROM target_tg;
+key|balance|status|val                                  
+---+-------+------+-------------------------------------
+  1|    100|s1    |setup updated by update_bal1_tg when1
+(1 row)
+
+step c1: COMMIT;
+
+starting permutation: update1 merge_delete c2 select1 c1
+step update1: UPDATE target t SET balance = balance + 10, val = t.val || ' updated by update1' WHERE t.key = 1;
+step merge_delete: 
+  MERGE INTO target t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+   UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+    DELETE;
+ <waiting ...>
+step c2: COMMIT;
+step merge_delete: <... completed>
+step select1: SELECT * FROM target;
+key|balance|status|val
+---+-------+------+---
+(0 rows)
+
+step c1: COMMIT;
+
+starting permutation: update1_tg merge_delete_tg c2 select1_tg c1
+s2: NOTICE:  Update: (1,160,s1,setup) -> (1,170,s1,"setup updated by update1_tg")
+step update1_tg: UPDATE target_tg t SET balance = balance + 10, val = t.val || ' updated by update1_tg' WHERE t.key = 1;
+step merge_delete_tg: 
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+   UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+    DELETE;
+ <waiting ...>
+step c2: COMMIT;
+s1: NOTICE:  Delete: (1,170,s1,"setup updated by update1_tg")
+step merge_delete_tg: <... completed>
+step select1_tg: SELECT * FROM target_tg;
+key|balance|status|val
+---+-------+------+---
+(0 rows)
+
+step c1: COMMIT;
+
+starting permutation: update_bal1 merge_delete c2 select1 c1
+step update_bal1: UPDATE target t SET balance = 50, val = t.val || ' updated by update_bal1' WHERE t.key = 1;
+step merge_delete: 
+  MERGE INTO target t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+   UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+    DELETE;
+ <waiting ...>
+step c2: COMMIT;
+step merge_delete: <... completed>
+step select1: SELECT * FROM target;
+key|balance|status|val                               
+---+-------+------+----------------------------------
+  1|    100|s1    |setup updated by update_bal1 when1
+(1 row)
+
+step c1: COMMIT;
+
+starting permutation: update_bal1_tg merge_delete_tg c2 select1_tg c1
+s2: NOTICE:  Update: (1,160,s1,setup) -> (1,50,s1,"setup updated by update_bal1_tg")
+step update_bal1_tg: UPDATE target_tg t SET balance = 50, val = t.val || ' updated by update_bal1_tg' WHERE t.key = 1;
+step merge_delete_tg: 
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+   UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+    DELETE;
+ <waiting ...>
+step c2: COMMIT;
+s1: NOTICE:  Update: (1,50,s1,"setup updated by update_bal1_tg") -> (1,100,s1,"setup updated by update_bal1_tg when1")
+step merge_delete_tg: <... completed>
+step select1_tg: SELECT * FROM target_tg;
+key|balance|status|val                                  
+---+-------+------+-------------------------------------
+  1|    100|s1    |setup updated by update_bal1_tg when1
+(1 row)
+
+step c1: COMMIT;
index 0e7053270eca77041d915d2be82f08a6a10b06f3..ba5f70e53dca51f68121f853c29501f0335627e9 100644 (file)
@@ -7,11 +7,39 @@ setup
 {
   CREATE TABLE target (key int primary key, val text);
   INSERT INTO target VALUES (1, 'setup1');
+
+  CREATE TABLE target_pa (key int primary key, val text) PARTITION BY LIST (key);
+  CREATE TABLE target_pa1 PARTITION OF target_pa FOR VALUES IN (1);
+  CREATE TABLE target_pa2 PARTITION OF target_pa FOR VALUES IN (2);
+  INSERT INTO target_pa VALUES (1, 'setup1');
+
+  CREATE TABLE target_tg (key int primary key, val text);
+  CREATE FUNCTION target_tg_trig_fn() RETURNS trigger LANGUAGE plpgsql AS
+  $$
+  BEGIN
+    IF tg_op = 'INSERT' THEN
+      RAISE NOTICE 'Insert: %', NEW;
+      RETURN NEW;
+    ELSIF tg_op = 'UPDATE' THEN
+      RAISE NOTICE 'Update: % -> %', OLD, NEW;
+      RETURN NEW;
+    ELSE
+      RAISE NOTICE 'Delete: %', OLD;
+      RETURN OLD;
+    END IF;
+  END
+  $$;
+  CREATE TRIGGER target_tg_trig BEFORE INSERT OR UPDATE OR DELETE ON target_tg
+    FOR EACH ROW EXECUTE FUNCTION target_tg_trig_fn();
+  INSERT INTO target_tg VALUES (1, 'setup1');
 }
 
 teardown
 {
   DROP TABLE target;
+  DROP TABLE target_pa;
+  DROP TABLE target_tg;
+  DROP FUNCTION target_tg_trig_fn;
 }
 
 session "s1"
@@ -20,7 +48,8 @@ setup
   BEGIN ISOLATION LEVEL READ COMMITTED;
 }
 step "delete" { DELETE FROM target t WHERE t.key = 1; }
-step "merge_delete" { MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE; }
+step "delete_pa" { DELETE FROM target_pa t WHERE t.key = 1; }
+step "delete_tg" { DELETE FROM target_tg t WHERE t.key = 1; }
 step "c1" { COMMIT; }
 
 session "s2"
@@ -28,23 +57,40 @@ setup
 {
   BEGIN ISOLATION LEVEL READ COMMITTED;
 }
-step "update1" { UPDATE target t SET val = t.val || ' updated by update1' WHERE t.key = 1; }
-step "merge2" { MERGE INTO target t USING (SELECT 1 as key, 'merge2a' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; }
+step "update2" { UPDATE target t SET val = t.val || ' updated by update2' WHERE t.key = 1; }
+step "update2_pa" { UPDATE target_pa t SET val = t.val || ' updated by update2_pa' WHERE t.key = 1; }
+step "update2_tg" { UPDATE target_tg t SET val = t.val || ' updated by update2_tg' WHERE t.key = 1; }
+step "merge2" { MERGE INTO target t USING (SELECT 1 as key, 'merge2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; }
+step "merge2_pa" { MERGE INTO target_pa t USING (SELECT 1 as key, 'merge2_pa' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; }
+step "merge2_tg" { MERGE INTO target_tg t USING (SELECT 1 as key, 'merge2_tg' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; }
+step "merge_delete2" { MERGE INTO target t USING (SELECT 1 as key, 'merge_delete2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN DELETE; }
+step "merge_delete2_tg" { MERGE INTO target_tg t USING (SELECT 1 as key, 'merge_delete2_tg' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN DELETE; }
 step "select2" { SELECT * FROM target; }
+step "select2_pa" { SELECT * FROM target_pa; }
+step "select2_tg" { SELECT * FROM target_tg; }
 step "c2" { COMMIT; }
 
 # Basic effects
 permutation "delete" "c1" "select2" "c2"
-permutation "merge_delete" "c1" "select2" "c2"
+permutation "delete_pa" "c1" "select2_pa" "c2"
+permutation "delete_tg" "c1" "select2_tg" "c2"
 
 # One after the other, no concurrency
-permutation "delete" "c1" "update1" "select2" "c2"
-permutation "merge_delete" "c1" "update1" "select2" "c2"
+permutation "delete" "c1" "update2" "select2" "c2"
+permutation "delete_pa" "c1" "update2_pa" "select2_pa" "c2"
+permutation "delete_tg" "c1" "update2_tg" "select2_tg" "c2"
 permutation "delete" "c1" "merge2" "select2" "c2"
-permutation "merge_delete" "c1" "merge2" "select2" "c2"
+permutation "delete_pa" "c1" "merge2_pa" "select2_pa" "c2"
+permutation "delete_tg" "c1" "merge2_tg" "select2_tg" "c2"
+permutation "delete" "c1" "merge_delete2" "select2" "c2"
+permutation "delete_tg" "c1" "merge_delete2_tg" "select2_tg" "c2"
 
 # Now with concurrency
-permutation "delete" "update1" "c1" "select2" "c2"
-permutation "merge_delete" "update1" "c1" "select2" "c2"
+permutation "delete" "update2" "c1" "select2" "c2"
+permutation "delete_pa" "update2_pa" "c1" "select2_pa" "c2"
+permutation "delete_tg" "update2_tg" "c1" "select2_tg" "c2"
 permutation "delete" "merge2" "c1" "select2" "c2"
-permutation "merge_delete" "merge2" "c1" "select2" "c2"
+permutation "delete_pa" "merge2_pa" "c1" "select2_pa" "c2"
+permutation "delete_tg" "merge2_tg" "c1" "select2_tg" "c2"
+permutation "delete" "merge_delete2" "c1" "select2" "c2"
+permutation "delete_tg" "merge_delete2_tg" "c1" "select2_tg" "c2"
index d56400a6a2271ca0b46cbc0f862a151ed5e300dd..298b2bfdcd6099179b3e904881b5f25a3bbc67a1 100644 (file)
@@ -8,11 +8,39 @@ setup
 {
   CREATE TABLE target (key int primary key, balance integer, status text, val text);
   INSERT INTO target VALUES (1, 160, 's1', 'setup');
+
+  CREATE TABLE target_pa (key int, balance integer, status text, val text) PARTITION BY RANGE (balance);
+  CREATE TABLE target_pa1 PARTITION OF target_pa FOR VALUES FROM (0) TO (200);
+  CREATE TABLE target_pa2 PARTITION OF target_pa FOR VALUES FROM (200) TO (1000);
+  INSERT INTO target_pa VALUES (1, 160, 's1', 'setup');
+
+  CREATE TABLE target_tg (key int primary key, balance integer, status text, val text);
+  CREATE FUNCTION target_tg_trig_fn() RETURNS trigger LANGUAGE plpgsql AS
+  $$
+  BEGIN
+    IF tg_op = 'INSERT' THEN
+      RAISE NOTICE 'Insert: %', NEW;
+      RETURN NEW;
+    ELSIF tg_op = 'UPDATE' THEN
+      RAISE NOTICE 'Update: % -> %', OLD, NEW;
+      RETURN NEW;
+    ELSE
+      RAISE NOTICE 'Delete: %', OLD;
+      RETURN OLD;
+    END IF;
+  END
+  $$;
+  CREATE TRIGGER target_tg_trig BEFORE INSERT OR UPDATE OR DELETE ON target_tg
+    FOR EACH ROW EXECUTE FUNCTION target_tg_trig_fn();
+  INSERT INTO target_tg VALUES (1, 160, 's1', 'setup');
 }
 
 teardown
 {
   DROP TABLE target;
+  DROP TABLE target_pa;
+  DROP TABLE target_tg;
+  DROP FUNCTION target_tg_trig_fn;
 }
 
 session "s1"
@@ -32,6 +60,18 @@ step "merge_status"
   WHEN MATCHED AND status = 's3' THEN
    UPDATE SET status = 's4', val = t.val || ' when3';
 }
+step "merge_status_tg"
+{
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND status = 's1' THEN
+   UPDATE SET status = 's2', val = t.val || ' when1'
+  WHEN MATCHED AND status = 's2' THEN
+   UPDATE SET status = 's3', val = t.val || ' when2'
+  WHEN MATCHED AND status = 's3' THEN
+   UPDATE SET status = 's4', val = t.val || ' when3';
+}
 
 step "merge_bal"
 {
@@ -45,8 +85,55 @@ step "merge_bal"
   WHEN MATCHED AND balance < 300 THEN
    UPDATE SET balance = balance * 8, val = t.val || ' when3';
 }
+step "merge_bal_pa"
+{
+  MERGE INTO target_pa t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+   UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+   UPDATE SET balance = balance * 4, val = t.val || ' when2'
+  WHEN MATCHED AND balance < 300 THEN
+   UPDATE SET balance = balance * 8, val = t.val || ' when3';
+}
+step "merge_bal_tg"
+{
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+   UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+   UPDATE SET balance = balance * 4, val = t.val || ' when2'
+  WHEN MATCHED AND balance < 300 THEN
+   UPDATE SET balance = balance * 8, val = t.val || ' when3';
+}
+
+step "merge_delete"
+{
+  MERGE INTO target t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+   UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+    DELETE;
+}
+step "merge_delete_tg"
+{
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+   UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+    DELETE;
+}
 
 step "select1" { SELECT * FROM target; }
+step "select1_pa" { SELECT * FROM target_pa; }
+step "select1_tg" { SELECT * FROM target_tg; }
 step "c1" { COMMIT; }
 
 session "s2"
@@ -55,23 +142,43 @@ setup
   BEGIN ISOLATION LEVEL READ COMMITTED;
 }
 step "update1" { UPDATE target t SET balance = balance + 10, val = t.val || ' updated by update1' WHERE t.key = 1; }
+step "update1_tg" { UPDATE target_tg t SET balance = balance + 10, val = t.val || ' updated by update1_tg' WHERE t.key = 1; }
 step "update2" { UPDATE target t SET status = 's2', val = t.val || ' updated by update2' WHERE t.key = 1; }
+step "update2_tg" { UPDATE target_tg t SET status = 's2', val = t.val || ' updated by update2_tg' WHERE t.key = 1; }
 step "update3" { UPDATE target t SET status = 's3', val = t.val || ' updated by update3' WHERE t.key = 1; }
+step "update3_tg" { UPDATE target_tg t SET status = 's3', val = t.val || ' updated by update3_tg' WHERE t.key = 1; }
 step "update5" { UPDATE target t SET status = 's5', val = t.val || ' updated by update5' WHERE t.key = 1; }
+step "update5_tg" { UPDATE target_tg t SET status = 's5', val = t.val || ' updated by update5_tg' WHERE t.key = 1; }
 step "update_bal1" { UPDATE target t SET balance = 50, val = t.val || ' updated by update_bal1' WHERE t.key = 1; }
+step "update_bal1_pa" { UPDATE target_pa t SET balance = 50, val = t.val || ' updated by update_bal1_pa' WHERE t.key = 1; }
+step "update_bal1_tg" { UPDATE target_tg t SET balance = 50, val = t.val || ' updated by update_bal1_tg' WHERE t.key = 1; }
 step "c2" { COMMIT; }
 
 # merge_status sees concurrently updated row and rechecks WHEN conditions, but recheck passes and final status = 's2'
 permutation "update1" "merge_status" "c2" "select1" "c1"
+permutation "update1_tg" "merge_status_tg" "c2" "select1_tg" "c1"
 
 # merge_status sees concurrently updated row and rechecks WHEN conditions, recheck fails, so final status = 's3' not 's2'
 permutation "update2" "merge_status" "c2" "select1" "c1"
+permutation "update2_tg" "merge_status_tg" "c2" "select1_tg" "c1"
 
 # merge_status sees concurrently updated row and rechecks WHEN conditions, recheck fails, so final status = 's4' not 's2'
 permutation "update3" "merge_status" "c2" "select1" "c1"
+permutation "update3_tg" "merge_status_tg" "c2" "select1_tg" "c1"
 
 # merge_status sees concurrently updated row and rechecks WHEN conditions, recheck fails, but we skip update and MERGE does nothing
 permutation "update5" "merge_status" "c2" "select1" "c1"
+permutation "update5_tg" "merge_status_tg" "c2" "select1_tg" "c1"
 
 # merge_bal sees concurrently updated row and rechecks WHEN conditions, recheck fails, so final balance = 100 not 640
 permutation "update_bal1" "merge_bal" "c2" "select1" "c1"
+permutation "update_bal1_pa" "merge_bal_pa" "c2" "select1_pa" "c1"
+permutation "update_bal1_tg" "merge_bal_tg" "c2" "select1_tg" "c1"
+
+# merge_delete sees concurrently updated row and rechecks WHEN conditions, but recheck passes and row is deleted
+permutation "update1" "merge_delete" "c2" "select1" "c1"
+permutation "update1_tg" "merge_delete_tg" "c2" "select1_tg" "c1"
+
+# merge_delete sees concurrently updated row and rechecks WHEN conditions, recheck fails, so final balance is 100
+permutation "update_bal1" "merge_delete" "c2" "select1" "c1"
+permutation "update_bal1_tg" "merge_delete_tg" "c2" "select1_tg" "c1"