diff options
author | Dean Rasheed <dean.a.rasheed@gmail.com> | 2024-02-29 15:56:59 +0000 |
---|---|---|
committer | Dean Rasheed <dean.a.rasheed@gmail.com> | 2024-02-29 15:56:59 +0000 |
commit | 5f2e179bd31e5f5803005101eb12a8d7bf8db8f3 (patch) | |
tree | 838a9f273c1d3d825db322161c0b8cdf3fbb0ce1 /src/backend/executor/nodeModifyTable.c | |
parent | 8b29a119fdaa381d6f75105f539b1e658c0f8cdb (diff) | |
download | postgresql-5f2e179bd31e5f5803005101eb12a8d7bf8db8f3.tar.gz postgresql-5f2e179bd31e5f5803005101eb12a8d7bf8db8f3.zip |
Support MERGE into updatable views.
This allows the target relation of MERGE to be an auto-updatable or
trigger-updatable view, and includes support for WITH CHECK OPTION,
security barrier views, and security invoker views.
A trigger-updatable view must have INSTEAD OF triggers for every type
of action (INSERT, UPDATE, and DELETE) mentioned in the MERGE command.
An auto-updatable view must not have any INSTEAD OF triggers. Mixing
auto-update and trigger-update actions (i.e., having a partial set of
INSTEAD OF triggers) is not supported.
Rule-updatable views are also not supported, since there is no
rewriter support for non-SELECT rules with MERGE operations.
Dean Rasheed, reviewed by Jian He and Alvaro Herrera.
Discussion: https://postgr.es/m/CAEZATCVcB1g0nmxuEc-A+gGB0HnfcGQNGYH7gS=7rq0u0zOBXA@mail.gmail.com
Diffstat (limited to 'src/backend/executor/nodeModifyTable.c')
-rw-r--r-- | src/backend/executor/nodeModifyTable.c | 138 |
1 files changed, 102 insertions, 36 deletions
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 098ed4026b6..ff7ec8419bb 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -150,11 +150,13 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate, static TupleTableSlot *ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, + HeapTuple oldtuple, bool canSetTag); static void ExecInitMerge(ModifyTableState *mtstate, EState *estate); static bool ExecMergeMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo, ItemPointer tupleid, + HeapTuple oldtuple, bool canSetTag); static void ExecMergeNotMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo, @@ -2712,13 +2714,14 @@ ExecOnConflictUpdate(ModifyTableContext *context, */ static TupleTableSlot * ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo, - ItemPointer tupleid, bool canSetTag) + ItemPointer tupleid, HeapTuple oldtuple, bool canSetTag) { bool matched; /*----- - * If we are dealing with a WHEN MATCHED case (tupleid is valid), we - * execute the first action for which the additional WHEN MATCHED AND + * If we are dealing with a WHEN MATCHED case (tupleid or oldtuple is + * valid, depending on whether the result relation is a table or a view), + * we execute the first action for which the additional WHEN MATCHED AND * quals pass. If an action without quals is found, that action is * executed. * @@ -2759,9 +2762,10 @@ ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo, * from ExecMergeNotMatched to ExecMergeMatched, there is no risk of a * livelock. */ - matched = tupleid != NULL; + matched = tupleid != NULL || oldtuple != NULL; if (matched) - matched = ExecMergeMatched(context, resultRelInfo, tupleid, canSetTag); + matched = ExecMergeMatched(context, resultRelInfo, tupleid, oldtuple, + canSetTag); /* * Either we were dealing with a NOT MATCHED tuple or ExecMergeMatched() @@ -2776,8 +2780,10 @@ ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo, } /* - * Check and execute the first qualifying MATCHED action. The current target - * tuple is identified by tupleid. + * Check and execute the first qualifying MATCHED action. If the target + * relation is a table, the current target tuple is identified by tupleid. + * Otherwise, if the target relation is a view, oldtuple is the current target + * tuple from the view. * * We start from the first WHEN MATCHED action and check if the WHEN quals * pass, if any. If the WHEN quals for the first action do not pass, we @@ -2798,7 +2804,7 @@ ExecMerge(ModifyTableContext *context, ResultRelInfo *resultRelInfo, */ static bool ExecMergeMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo, - ItemPointer tupleid, bool canSetTag) + ItemPointer tupleid, HeapTuple oldtuple, bool canSetTag) { ModifyTableState *mtstate = context->mtstate; TupleTableSlot *newslot; @@ -2824,22 +2830,33 @@ ExecMergeMatched(ModifyTableContext *context, ResultRelInfo *resultRelInfo, econtext->ecxt_innertuple = context->planSlot; econtext->ecxt_outertuple = NULL; + /* + * This routine is only invoked for matched rows, so we should either have + * the tupleid of the target row, or an old tuple from the target wholerow + * junk attr. + */ + Assert(tupleid != NULL || oldtuple != NULL); + if (oldtuple != NULL) + ExecForceStoreHeapTuple(oldtuple, resultRelInfo->ri_oldTupleSlot, + false); + lmerge_matched: /* - * This routine is only invoked for matched rows, and we must have found - * the tupleid of the target row in that case; fetch that tuple. + * If passed a tupleid, use it to fetch the old target row. * * We use SnapshotAny for this because we might get called again after * EvalPlanQual returns us a new tuple, which may not be visible to our * MVCC snapshot. */ - - if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc, - tupleid, - SnapshotAny, - resultRelInfo->ri_oldTupleSlot)) - elog(ERROR, "failed to fetch the target tuple"); + if (tupleid != NULL) + { + if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc, + tupleid, + SnapshotAny, + resultRelInfo->ri_oldTupleSlot)) + elog(ERROR, "failed to fetch the target tuple"); + } foreach(l, resultRelInfo->ri_matchedMergeAction) { @@ -2899,20 +2916,33 @@ lmerge_matched: return true; /* "do nothing" */ break; /* concurrent update/delete */ } - result = ExecUpdateAct(context, resultRelInfo, tupleid, NULL, - newslot, canSetTag, &updateCxt); - /* - * As in ExecUpdate(), if ExecUpdateAct() reports that a - * cross-partition update was done, then there's nothing else - * for us to do --- the UPDATE has been turned into a DELETE - * and an INSERT, and we must not perform any of the usual - * post-update tasks. - */ - if (updateCxt.crossPartUpdate) + /* INSTEAD OF ROW UPDATE Triggers */ + if (resultRelInfo->ri_TrigDesc && + resultRelInfo->ri_TrigDesc->trig_update_instead_row) { - mtstate->mt_merge_updated += 1; - return true; + if (!ExecIRUpdateTriggers(estate, resultRelInfo, + oldtuple, newslot)) + return true; /* "do nothing" */ + } + else + { + result = ExecUpdateAct(context, resultRelInfo, tupleid, + NULL, newslot, canSetTag, + &updateCxt); + + /* + * As in ExecUpdate(), if ExecUpdateAct() reports that a + * cross-partition update was done, then there's nothing + * else for us to do --- the UPDATE has been turned into a + * DELETE and an INSERT, and we must not perform any of + * the usual post-update tasks. + */ + if (updateCxt.crossPartUpdate) + { + mtstate->mt_merge_updated += 1; + return true; + } } if (result == TM_Ok) @@ -2932,7 +2962,19 @@ lmerge_matched: return true; /* "do nothing" */ break; /* concurrent update/delete */ } - result = ExecDeleteAct(context, resultRelInfo, tupleid, false); + + /* INSTEAD OF ROW DELETE Triggers */ + if (resultRelInfo->ri_TrigDesc && + resultRelInfo->ri_TrigDesc->trig_delete_instead_row) + { + if (!ExecIRDeleteTriggers(estate, resultRelInfo, + oldtuple)) + return true; /* "do nothing" */ + } + else + result = ExecDeleteAct(context, resultRelInfo, tupleid, + false); + if (result == TM_Ok) { ExecDeleteEpilogue(context, resultRelInfo, tupleid, NULL, @@ -3663,7 +3705,8 @@ ExecModifyTable(PlanState *pstate) { EvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot); - ExecMerge(&context, node->resultRelInfo, NULL, node->canSetTag); + ExecMerge(&context, node->resultRelInfo, NULL, NULL, + node->canSetTag); continue; /* no RETURNING support yet */ } @@ -3741,7 +3784,8 @@ ExecModifyTable(PlanState *pstate) { EvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot); - ExecMerge(&context, node->resultRelInfo, NULL, node->canSetTag); + ExecMerge(&context, node->resultRelInfo, NULL, NULL, + node->canSetTag); continue; /* no RETURNING support yet */ } @@ -3774,9 +3818,28 @@ ExecModifyTable(PlanState *pstate) datum = ExecGetJunkAttribute(slot, resultRelInfo->ri_RowIdAttNo, &isNull); - /* shouldn't ever get a null result... */ + + /* + * For commands other than MERGE, any tuples having a null row + * identifier are errors. For MERGE, we may need to handle + * them as WHEN NOT MATCHED clauses if any, so do that. + * + * Note that we use the node's toplevel resultRelInfo, not any + * specific partition's. + */ if (isNull) + { + if (operation == CMD_MERGE) + { + EvalPlanQualSetSlot(&node->mt_epqstate, context.planSlot); + + ExecMerge(&context, node->resultRelInfo, NULL, NULL, + node->canSetTag); + continue; /* no RETURNING support yet */ + } + elog(ERROR, "wholerow is NULL"); + } oldtupdata.t_data = DatumGetHeapTupleHeader(datum); oldtupdata.t_len = @@ -3847,7 +3910,8 @@ ExecModifyTable(PlanState *pstate) break; case CMD_MERGE: - slot = ExecMerge(&context, resultRelInfo, tupleid, node->canSetTag); + slot = ExecMerge(&context, resultRelInfo, tupleid, oldtuple, + node->canSetTag); break; default: @@ -4025,6 +4089,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) foreach(l, node->resultRelations) { Index resultRelation = lfirst_int(l); + List *mergeActions = NIL; + + if (node->mergeActionLists) + mergeActions = list_nth(node->mergeActionLists, i); if (resultRelInfo != mtstate->rootResultRelInfo) { @@ -4046,7 +4114,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) /* * Verify result relation is a valid target for the current operation */ - CheckValidResultRel(resultRelInfo, operation); + CheckValidResultRel(resultRelInfo, operation, mergeActions); resultRelInfo++; i++; @@ -4122,8 +4190,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) } else { - /* No support for MERGE */ - Assert(operation != CMD_MERGE); /* Other valid target relkinds must provide wholerow */ resultRelInfo->ri_RowIdAttNo = ExecFindJunkAttributeInTlist(subplan->targetlist, |