mirror of
https://github.com/postgres/postgres.git
synced 2025-09-02 04:21:28 +03:00
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
This commit is contained in:
@@ -56,6 +56,7 @@
|
||||
#include "miscadmin.h"
|
||||
#include "parser/parse_relation.h"
|
||||
#include "parser/parsetree.h"
|
||||
#include "rewrite/rewriteHandler.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "storage/lmgr.h"
|
||||
#include "tcop/utility.h"
|
||||
@@ -1017,14 +1018,18 @@ InitPlan(QueryDesc *queryDesc, int eflags)
|
||||
* Generally the parser and/or planner should have noticed any such mistake
|
||||
* already, but let's make sure.
|
||||
*
|
||||
* For MERGE, mergeActions is the list of actions that may be performed. The
|
||||
* result relation is required to support every action, regardless of whether
|
||||
* or not they are all executed.
|
||||
*
|
||||
* Note: when changing this function, you probably also need to look at
|
||||
* CheckValidRowMarkRel.
|
||||
*/
|
||||
void
|
||||
CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
|
||||
CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation,
|
||||
List *mergeActions)
|
||||
{
|
||||
Relation resultRel = resultRelInfo->ri_RelationDesc;
|
||||
TriggerDesc *trigDesc = resultRel->trigdesc;
|
||||
FdwRoutine *fdwroutine;
|
||||
|
||||
switch (resultRel->rd_rel->relkind)
|
||||
@@ -1048,42 +1053,14 @@ CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
|
||||
case RELKIND_VIEW:
|
||||
|
||||
/*
|
||||
* Okay only if there's a suitable INSTEAD OF trigger. Messages
|
||||
* here should match rewriteHandler.c's rewriteTargetView and
|
||||
* RewriteQuery, except that we omit errdetail because we haven't
|
||||
* got the information handy (and given that we really shouldn't
|
||||
* get here anyway, it's not worth great exertion to get).
|
||||
* Okay only if there's a suitable INSTEAD OF trigger. Otherwise,
|
||||
* complain, but omit errdetail because we haven't got the
|
||||
* information handy (and given that it really shouldn't happen,
|
||||
* it's not worth great exertion to get).
|
||||
*/
|
||||
switch (operation)
|
||||
{
|
||||
case CMD_INSERT:
|
||||
if (!trigDesc || !trigDesc->trig_insert_instead_row)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||
errmsg("cannot insert into view \"%s\"",
|
||||
RelationGetRelationName(resultRel)),
|
||||
errhint("To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.")));
|
||||
break;
|
||||
case CMD_UPDATE:
|
||||
if (!trigDesc || !trigDesc->trig_update_instead_row)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||
errmsg("cannot update view \"%s\"",
|
||||
RelationGetRelationName(resultRel)),
|
||||
errhint("To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.")));
|
||||
break;
|
||||
case CMD_DELETE:
|
||||
if (!trigDesc || !trigDesc->trig_delete_instead_row)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
|
||||
errmsg("cannot delete from view \"%s\"",
|
||||
RelationGetRelationName(resultRel)),
|
||||
errhint("To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.")));
|
||||
break;
|
||||
default:
|
||||
elog(ERROR, "unrecognized CmdType: %d", (int) operation);
|
||||
break;
|
||||
}
|
||||
if (!view_has_instead_trigger(resultRel, operation, mergeActions))
|
||||
error_view_not_updatable(resultRel, operation, mergeActions,
|
||||
NULL);
|
||||
break;
|
||||
case RELKIND_MATVIEW:
|
||||
if (!MatViewIncrementalMaintenanceIsEnabled())
|
||||
|
@@ -361,7 +361,7 @@ ExecFindPartition(ModifyTableState *mtstate,
|
||||
if (rri)
|
||||
{
|
||||
/* Verify this ResultRelInfo allows INSERTs */
|
||||
CheckValidResultRel(rri, CMD_INSERT);
|
||||
CheckValidResultRel(rri, CMD_INSERT, NIL);
|
||||
|
||||
/*
|
||||
* Initialize information needed to insert this and
|
||||
@@ -527,7 +527,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
|
||||
* partition-key becomes a DELETE+INSERT operation, so this check is still
|
||||
* required when the operation is CMD_UPDATE.
|
||||
*/
|
||||
CheckValidResultRel(leaf_part_rri, CMD_INSERT);
|
||||
CheckValidResultRel(leaf_part_rri, CMD_INSERT, NIL);
|
||||
|
||||
/*
|
||||
* Open partition indices. The user may have asked to check for conflicts
|
||||
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user