diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/commands/copyfrom.c | 2 | ||||
-rw-r--r-- | src/backend/executor/execMain.c | 51 | ||||
-rw-r--r-- | src/backend/executor/execPartition.c | 4 | ||||
-rw-r--r-- | src/backend/executor/nodeModifyTable.c | 138 | ||||
-rw-r--r-- | src/backend/optimizer/prep/prepjointree.c | 20 | ||||
-rw-r--r-- | src/backend/optimizer/util/appendinfo.c | 3 | ||||
-rw-r--r-- | src/backend/parser/parse_merge.c | 21 | ||||
-rw-r--r-- | src/backend/rewrite/rewriteHandler.c | 403 | ||||
-rw-r--r-- | src/backend/rewrite/rewriteManip.c | 20 |
9 files changed, 450 insertions, 212 deletions
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c index 1fe70b91338..c3bc897028a 100644 --- a/src/backend/commands/copyfrom.c +++ b/src/backend/commands/copyfrom.c @@ -767,7 +767,7 @@ CopyFrom(CopyFromState cstate) ExecInitResultRelation(estate, resultRelInfo, 1); /* Verify the named relation is a valid target for INSERT */ - CheckValidResultRel(resultRelInfo, CMD_INSERT); + CheckValidResultRel(resultRelInfo, CMD_INSERT, NIL); ExecOpenIndices(resultRelInfo, false); diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 13a9b7da83b..79ef46f2614 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -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()) diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index b22040ae8ee..429f0a8dd98 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -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 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, diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index aa83dd3636f..300691cc4db 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -198,12 +198,17 @@ transform_MERGE_to_join(Query *parse) /* * Create a JOIN between the target and the source relation. + * + * Here the target is identified by parse->mergeTargetRelation. For a + * regular table, this will equal parse->resultRelation, but for a + * trigger-updatable view, it will be the expanded view subquery that we + * need to pull data from. */ joinexpr = makeNode(JoinExpr); joinexpr->jointype = jointype; joinexpr->isNatural = false; joinexpr->larg = (Node *) makeNode(RangeTblRef); - ((RangeTblRef *) joinexpr->larg)->rtindex = parse->resultRelation; + ((RangeTblRef *) joinexpr->larg)->rtindex = parse->mergeTargetRelation; joinexpr->rarg = linitial(parse->jointree->fromlist); /* original join */ joinexpr->usingClause = NIL; joinexpr->join_using_alias = NULL; @@ -215,6 +220,19 @@ transform_MERGE_to_join(Query *parse) /* Make the new join be the sole entry in the query's jointree */ parse->jointree->fromlist = list_make1(joinexpr); parse->jointree->quals = NULL; + + /* + * If necessary, mark parse->targetlist entries that refer to the target + * as nullable by the join. Normally the targetlist will be empty for a + * MERGE, but if the target is a trigger-updatable view, it will contain a + * whole-row Var referring to the expanded view query. + */ + if (parse->targetList != NIL && + (jointype == JOIN_RIGHT || jointype == JOIN_FULL)) + parse->targetList = (List *) + add_nulling_relids((Node *) parse->targetList, + bms_make_singleton(parse->mergeTargetRelation), + bms_make_singleton(joinrti)); } /* diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index 51fdeace7d2..6ba4eba224a 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -891,8 +891,7 @@ add_row_identity_columns(PlannerInfo *root, Index rtindex, Assert(commandType == CMD_UPDATE || commandType == CMD_DELETE || commandType == CMD_MERGE); - if (commandType == CMD_MERGE || - relkind == RELKIND_RELATION || + if (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW || relkind == RELKIND_PARTITIONED_TABLE) { diff --git a/src/backend/parser/parse_merge.c b/src/backend/parser/parse_merge.c index 73f7a48b3c6..4356d61f8ed 100644 --- a/src/backend/parser/parse_merge.c +++ b/src/backend/parser/parse_merge.c @@ -172,28 +172,27 @@ transformMergeStmt(ParseState *pstate, MergeStmt *stmt) * Set up the MERGE target table. The target table is added to the * namespace below and to joinlist in transform_MERGE_to_join, so don't do * it here. + * + * Initially mergeTargetRelation is the same as resultRelation, so data is + * read from the table being updated. However, that might be changed by + * the rewriter, if the target is a trigger-updatable view, to allow + * target data to be read from the expanded view query while updating the + * original view relation. */ qry->resultRelation = setTargetTable(pstate, stmt->relation, stmt->relation->inh, false, targetPerms); + qry->mergeTargetRelation = qry->resultRelation; - /* - * MERGE is unsupported in various cases - */ + /* The target relation must be a table or a view */ if (pstate->p_target_relation->rd_rel->relkind != RELKIND_RELATION && - pstate->p_target_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + pstate->p_target_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE && + pstate->p_target_relation->rd_rel->relkind != RELKIND_VIEW) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot execute MERGE on relation \"%s\"", RelationGetRelationName(pstate->p_target_relation)), errdetail_relkind_not_supported(pstate->p_target_relation->rd_rel->relkind))); - if (pstate->p_target_relation->rd_rules != NULL && - pstate->p_target_relation->rd_rules->numLocks > 0) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot execute MERGE on relation \"%s\"", - RelationGetRelationName(pstate->p_target_relation)), - errdetail("MERGE is not supported for relations with rules."))); /* Now transform the source relation to produce the source RTE. */ transformFromClause(pstate, diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index f60b34deb64..b8839b56b7a 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -87,10 +87,9 @@ static void rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte); static void markQueryForLocking(Query *qry, Node *jtnode, LockClauseStrength strength, LockWaitPolicy waitPolicy, bool pushedDown); -static List *matchLocks(CmdType event, RuleLock *rulelocks, +static List *matchLocks(CmdType event, Relation relation, int varno, Query *parsetree, bool *hasUpdate); static Query *fireRIRrules(Query *parsetree, List *activeRIRs); -static bool view_has_instead_trigger(Relation view, CmdType event); static Bitmapset *adjust_view_column_set(Bitmapset *cols, List *targetlist); @@ -1482,7 +1481,7 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti, */ isAutoUpdatableView = false; if (target_relation->rd_rel->relkind == RELKIND_VIEW && - !view_has_instead_trigger(target_relation, CMD_INSERT)) + !view_has_instead_trigger(target_relation, CMD_INSERT, NIL)) { List *locks; bool hasUpdate; @@ -1490,7 +1489,7 @@ rewriteValuesRTE(Query *parsetree, RangeTblEntry *rte, int rti, ListCell *l; /* Look for an unconditional DO INSTEAD rule */ - locks = matchLocks(CMD_INSERT, target_relation->rd_rules, + locks = matchLocks(CMD_INSERT, target_relation, parsetree->resultRelation, parsetree, &hasUpdate); found = false; @@ -1654,15 +1653,16 @@ rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte) /* * matchLocks - - * match the list of locks and returns the matching rules + * match a relation's list of locks and returns the matching rules */ static List * matchLocks(CmdType event, - RuleLock *rulelocks, + Relation relation, int varno, Query *parsetree, bool *hasUpdate) { + RuleLock *rulelocks = relation->rd_rules; List *matching_locks = NIL; int nlocks; int i; @@ -1670,10 +1670,6 @@ matchLocks(CmdType event, if (rulelocks == NULL) return NIL; - /* No rule support for MERGE */ - if (parsetree->commandType == CMD_MERGE) - return NIL; - if (parsetree->commandType != CMD_SELECT) { if (parsetree->resultRelation != varno) @@ -1691,7 +1687,7 @@ matchLocks(CmdType event, /* * Suppress ON INSERT/UPDATE/DELETE rules that are disabled or - * configured to not fire during the current sessions replication + * configured to not fire during the current session's replication * role. ON SELECT rules will always be applied in order to keep views * working even in LOCAL or REPLICA role. */ @@ -1709,6 +1705,14 @@ matchLocks(CmdType event, oneLock->enabled == RULE_DISABLED) continue; } + + /* Non-SELECT rules are not supported for MERGE */ + if (parsetree->commandType == CMD_MERGE) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot execute MERGE on relation \"%s\"", + RelationGetRelationName(relation)), + errdetail("MERGE is not supported for relations with rules.")); } if (oneLock->event == event) @@ -1755,9 +1759,9 @@ ApplyRetrieveRule(Query *parsetree, * For INSERT, we needn't do anything. The unmodified RTE will serve * fine as the result relation. * - * For UPDATE/DELETE, we need to expand the view so as to have source - * data for the operation. But we also need an unmodified RTE to - * serve as the target. So, copy the RTE and add the copy to the + * For UPDATE/DELETE/MERGE, we need to expand the view so as to have + * source data for the operation. But we also need an unmodified RTE + * to serve as the target. So, copy the RTE and add the copy to the * rangetable. Note that the copy does not get added to the jointree. * Also note that there's a hack in fireRIRrules to avoid calling this * function again when it arrives at the copied RTE. @@ -1765,7 +1769,8 @@ ApplyRetrieveRule(Query *parsetree, if (parsetree->commandType == CMD_INSERT) return parsetree; else if (parsetree->commandType == CMD_UPDATE || - parsetree->commandType == CMD_DELETE) + parsetree->commandType == CMD_DELETE || + parsetree->commandType == CMD_MERGE) { RangeTblEntry *newrte; Var *var; @@ -1775,6 +1780,7 @@ ApplyRetrieveRule(Query *parsetree, newrte = copyObject(rte); parsetree->rtable = lappend(parsetree->rtable, newrte); parsetree->resultRelation = list_length(parsetree->rtable); + /* parsetree->mergeTargetRelation unchanged (use expanded view) */ /* * For the most part, Vars referencing the view should remain as @@ -2470,9 +2476,15 @@ get_view_query(Relation view) * If it does, we don't want to treat it as auto-updatable. This test can't * be folded into view_query_is_auto_updatable because it's not an error * condition. + * + * For MERGE, this will return true if there is an INSTEAD OF trigger for + * every action in mergeActionList, and false if there are any actions that + * lack an INSTEAD OF trigger. If there are no data-modifying MERGE actions + * (only DO NOTHING actions), true is returned so that the view is treated + * as trigger-updatable, rather than erroring out if it's not auto-updatable. */ -static bool -view_has_instead_trigger(Relation view, CmdType event) +bool +view_has_instead_trigger(Relation view, CmdType event, List *mergeActionList) { TriggerDesc *trigDesc = view->trigdesc; @@ -2490,6 +2502,32 @@ view_has_instead_trigger(Relation view, CmdType event) if (trigDesc && trigDesc->trig_delete_instead_row) return true; break; + case CMD_MERGE: + foreach_node(MergeAction, action, mergeActionList) + { + switch (action->commandType) + { + case CMD_INSERT: + if (!trigDesc || !trigDesc->trig_insert_instead_row) + return false; + break; + case CMD_UPDATE: + if (!trigDesc || !trigDesc->trig_update_instead_row) + return false; + break; + case CMD_DELETE: + if (!trigDesc || !trigDesc->trig_delete_instead_row) + return false; + break; + case CMD_NOTHING: + /* No trigger required */ + break; + default: + elog(ERROR, "unrecognized commandType: %d", action->commandType); + break; + } + } + return true; /* no actions without an INSTEAD OF trigger */ default: elog(ERROR, "unrecognized CmdType: %d", (int) event); break; @@ -3031,6 +3069,105 @@ adjust_view_column_set(Bitmapset *cols, List *targetlist) /* + * error_view_not_updatable - + * Report an error due to an attempt to update a non-updatable view. + * + * Generally this is expected to be called from the rewriter, with suitable + * error detail explaining why the view is not updatable. Note, however, that + * the executor also performs a just-in-case check that the target view is + * updatable. That check is expected to never fail, but if it does, it will + * call this function with NULL error detail --- see CheckValidResultRel(). + * + * Note: for MERGE, at least one of the actions in mergeActionList is expected + * to lack a suitable INSTEAD OF trigger --- see view_has_instead_trigger(). + */ +void +error_view_not_updatable(Relation view, + CmdType command, + List *mergeActionList, + const char *detail) +{ + TriggerDesc *trigDesc = view->trigdesc; + + switch (command) + { + case CMD_INSERT: + ereport(ERROR, + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot insert into view \"%s\"", + RelationGetRelationName(view)), + detail ? errdetail_internal("%s", _(detail)) : 0, + 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: + ereport(ERROR, + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot update view \"%s\"", + RelationGetRelationName(view)), + detail ? errdetail_internal("%s", _(detail)) : 0, + errhint("To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.")); + break; + case CMD_DELETE: + ereport(ERROR, + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot delete from view \"%s\"", + RelationGetRelationName(view)), + detail ? errdetail_internal("%s", _(detail)) : 0, + errhint("To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.")); + break; + case CMD_MERGE: + + /* + * Note that the error hints here differ from above, since MERGE + * doesn't support rules. + */ + foreach_node(MergeAction, action, mergeActionList) + { + switch (action->commandType) + { + 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(view)), + detail ? errdetail_internal("%s", _(detail)) : 0, + errhint("To enable inserting into the view using MERGE, provide an INSTEAD OF INSERT trigger.")); + 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(view)), + detail ? errdetail_internal("%s", _(detail)) : 0, + errhint("To enable updating the view using MERGE, provide an INSTEAD OF UPDATE trigger.")); + 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(view)), + detail ? errdetail_internal("%s", _(detail)) : 0, + errhint("To enable deleting from the view using MERGE, provide an INSTEAD OF DELETE trigger.")); + break; + case CMD_NOTHING: + break; + default: + elog(ERROR, "unrecognized commandType: %d", action->commandType); + break; + } + } + break; + default: + elog(ERROR, "unrecognized CmdType: %d", (int) command); + break; + } +} + + +/* * rewriteTargetView - * Attempt to rewrite a query where the target relation is a view, so that * the view's base relation becomes the target relation. @@ -3043,6 +3180,7 @@ static Query * rewriteTargetView(Query *parsetree, Relation view) { Query *viewquery; + bool insert_or_update; const char *auto_update_detail; RangeTblRef *rtr; int base_rt_index; @@ -3066,55 +3204,52 @@ rewriteTargetView(Query *parsetree, Relation view) */ viewquery = copyObject(get_view_query(view)); - /* The view must be updatable, else fail */ - auto_update_detail = - view_query_is_auto_updatable(viewquery, - parsetree->commandType != CMD_DELETE); + /* + * Are we doing INSERT/UPDATE, or MERGE containing INSERT/UPDATE? If so, + * various additional checks on the view columns need to be applied, and + * any view CHECK OPTIONs need to be enforced. + */ + insert_or_update = + (parsetree->commandType == CMD_INSERT || + parsetree->commandType == CMD_UPDATE); - if (auto_update_detail) + if (parsetree->commandType == CMD_MERGE) { - /* messages here should match execMain.c's CheckValidResultRel */ - switch (parsetree->commandType) + foreach_node(MergeAction, action, parsetree->mergeActionList) { - case CMD_INSERT: - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("cannot insert into view \"%s\"", - RelationGetRelationName(view)), - errdetail_internal("%s", _(auto_update_detail)), - 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: - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("cannot update view \"%s\"", - RelationGetRelationName(view)), - errdetail_internal("%s", _(auto_update_detail)), - errhint("To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule."))); - break; - case CMD_DELETE: - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("cannot delete from view \"%s\"", - RelationGetRelationName(view)), - errdetail_internal("%s", _(auto_update_detail)), - 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) parsetree->commandType); + if (action->commandType == CMD_INSERT || + action->commandType == CMD_UPDATE) + { + insert_or_update = true; break; + } } } /* - * For INSERT/UPDATE the modified columns must all be updatable. Note that - * we get the modified columns from the query's targetlist, not from the - * result RTE's insertedCols and/or updatedCols set, since - * rewriteTargetListIU may have added additional targetlist entries for - * view defaults, and these must also be updatable. + * The view must be updatable, else fail. + * + * If we are doing INSERT/UPDATE (or MERGE containing INSERT/UPDATE), we + * also check that there is at least one updatable column. */ - if (parsetree->commandType != CMD_DELETE) + auto_update_detail = + view_query_is_auto_updatable(viewquery, insert_or_update); + + if (auto_update_detail) + error_view_not_updatable(view, + parsetree->commandType, + parsetree->mergeActionList, + auto_update_detail); + + /* + * For INSERT/UPDATE (or MERGE containing INSERT/UPDATE) the modified + * columns must all be updatable. Note that we get the modified columns + * from the query's targetlist, not from the result RTE's insertedCols + * and/or updatedCols set, since rewriteTargetListIU may have added + * additional targetlist entries for view defaults, and these must also be + * updatable. + */ + if (insert_or_update) { Bitmapset *modified_cols = NULL; char *non_updatable_col; @@ -3140,6 +3275,20 @@ rewriteTargetView(Query *parsetree, Relation view) } } + foreach_node(MergeAction, action, parsetree->mergeActionList) + { + if (action->commandType == CMD_INSERT || + action->commandType == CMD_UPDATE) + { + foreach_node(TargetEntry, tle, action->targetList) + { + if (!tle->resjunk) + modified_cols = bms_add_member(modified_cols, + tle->resno - FirstLowInvalidHeapAttributeNumber); + } + } + } + auto_update_detail = view_cols_are_auto_updatable(viewquery, modified_cols, NULL, @@ -3168,6 +3317,14 @@ rewriteTargetView(Query *parsetree, Relation view) RelationGetRelationName(view)), errdetail_internal("%s", _(auto_update_detail)))); break; + case CMD_MERGE: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot merge into column \"%s\" of view \"%s\"", + non_updatable_col, + RelationGetRelationName(view)), + errdetail_internal("%s", _(auto_update_detail)))); + break; default: elog(ERROR, "unrecognized CmdType: %d", (int) parsetree->commandType); @@ -3176,6 +3333,28 @@ rewriteTargetView(Query *parsetree, Relation view) } } + /* + * For MERGE, there must not be any INSTEAD OF triggers on an otherwise + * updatable view. The caller already checked that there isn't a full set + * of INSTEAD OF triggers, so this is to guard against having a partial + * set (mixing auto-update and trigger-update actions in a single command + * isn't supported). + */ + if (parsetree->commandType == CMD_MERGE) + { + foreach_node(MergeAction, action, parsetree->mergeActionList) + { + if (action->commandType != CMD_NOTHING && + view_has_instead_trigger(view, action->commandType, NIL)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot merge into view \"%s\"", + RelationGetRelationName(view)), + errdetail("MERGE is not supported for views with INSTEAD OF triggers for some actions, but not others."), + errhint("To enable merging into the view, either provide a full set of INSTEAD OF triggers or drop the existing INSTEAD OF triggers.")); + } + } + /* Locate RTE describing the view in the outer query */ view_rte = rt_fetch(parsetree->resultRelation, parsetree->rtable); @@ -3239,8 +3418,8 @@ rewriteTargetView(Query *parsetree, Relation view) new_rt_index = list_length(parsetree->rtable); /* - * INSERTs never inherit. For UPDATE/DELETE, we use the view query's - * inheritance flag for the base relation. + * INSERTs never inherit. For UPDATE/DELETE/MERGE, we use the view + * query's inheritance flag for the base relation. */ if (parsetree->commandType == CMD_INSERT) new_rte->inh = false; @@ -3362,11 +3541,12 @@ rewriteTargetView(Query *parsetree, Relation view) /* * For INSERT/UPDATE we must also update resnos in the targetlist to refer * to columns of the base relation, since those indicate the target - * columns to be affected. + * columns to be affected. Similarly, for MERGE we must update the resnos + * in the merge action targetlists of any INSERT/UPDATE actions. * - * Note that this destroys the resno ordering of the targetlist, but that + * Note that this destroys the resno ordering of the targetlists, but that * will be fixed when we recurse through RewriteQuery, which will invoke - * rewriteTargetListIU again on the updated targetlist. + * rewriteTargetListIU again on the updated targetlists. */ if (parsetree->commandType != CMD_DELETE) { @@ -3385,6 +3565,28 @@ rewriteTargetView(Query *parsetree, Relation view) elog(ERROR, "attribute number %d not found in view targetlist", tle->resno); } + + foreach_node(MergeAction, action, parsetree->mergeActionList) + { + if (action->commandType == CMD_INSERT || + action->commandType == CMD_UPDATE) + { + foreach_node(TargetEntry, tle, action->targetList) + { + TargetEntry *view_tle; + + if (tle->resjunk) + continue; + + view_tle = get_tle_by_resno(view_targetlist, tle->resno); + if (view_tle != NULL && !view_tle->resjunk && IsA(view_tle->expr, Var)) + tle->resno = ((Var *) view_tle->expr)->varattno; + else + elog(ERROR, "attribute number %d not found in view targetlist", + tle->resno); + } + } + } } /* @@ -3477,10 +3679,10 @@ rewriteTargetView(Query *parsetree, Relation view) } /* - * For UPDATE/DELETE, pull up any WHERE quals from the view. We know that - * any Vars in the quals must reference the one base relation, so we need - * only adjust their varnos to reference the new target (just the same as - * we did with the view targetlist). + * For UPDATE/DELETE/MERGE, pull up any WHERE quals from the view. We + * know that any Vars in the quals must reference the one base relation, + * so we need only adjust their varnos to reference the new target (just + * the same as we did with the view targetlist). * * If it's a security-barrier view, its WHERE quals must be applied before * quals from the outer query, so we attach them to the RTE as security @@ -3532,11 +3734,12 @@ rewriteTargetView(Query *parsetree, Relation view) } /* - * For INSERT/UPDATE, if the view has the WITH CHECK OPTION, or any parent - * view specified WITH CASCADED CHECK OPTION, add the quals from the view - * to the query's withCheckOptions list. + * For INSERT/UPDATE (or MERGE containing INSERT/UPDATE), if the view has + * the WITH CHECK OPTION, or any parent view specified WITH CASCADED CHECK + * OPTION, add the quals from the view to the query's withCheckOptions + * list. */ - if (parsetree->commandType != CMD_DELETE) + if (insert_or_update) { bool has_wco = RelationHasCheckOption(view); bool cascaded = RelationHasCascadedCheckOption(view); @@ -3590,14 +3793,13 @@ rewriteTargetView(Query *parsetree, Relation view) ChangeVarNodes(wco->qual, base_rt_index, new_rt_index, 0); /* - * Make sure that the query is marked correctly if the added - * qual has sublinks. We can skip this check if the query is - * already marked, or if the command is an UPDATE, in which - * case the same qual will have already been added, and this - * check will already have been done. + * For INSERT, make sure that the query is marked correctly if + * the added qual has sublinks. This can be skipped for + * UPDATE/MERGE, since the same qual will have already been + * added above, and the check will already have been done. */ if (!parsetree->hasSubLinks && - parsetree->commandType != CMD_UPDATE) + parsetree->commandType == CMD_INSERT) parsetree->hasSubLinks = checkExprHasSubLink(wco->qual); } } @@ -3867,7 +4069,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length) /* * Collect and apply the appropriate rules. */ - locks = matchLocks(event, rt_entry_relation->rd_rules, + locks = matchLocks(event, rt_entry_relation, result_relation, parsetree, &hasUpdate); product_orig_rt_length = list_length(parsetree->rtable); @@ -3938,7 +4140,8 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length) * automatically updated. If so, we perform the necessary query * transformation here and add the resulting query to the * product_queries list, so that it gets recursively rewritten if - * necessary. + * necessary. For MERGE, the view must be automatically updatable if + * any of the merge actions lack a corresponding INSTEAD OF trigger. * * If the view cannot be automatically updated, we throw an error here * which is OK since the query would fail at runtime anyway. Throwing @@ -3948,51 +4151,19 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length) */ if (!instead && rt_entry_relation->rd_rel->relkind == RELKIND_VIEW && - !view_has_instead_trigger(rt_entry_relation, event)) + !view_has_instead_trigger(rt_entry_relation, event, + parsetree->mergeActionList)) { /* * If there were any qualified INSTEAD rules, don't allow the view * to be automatically updated (an unqualified INSTEAD rule or * INSTEAD OF trigger is required). - * - * The messages here should match execMain.c's CheckValidResultRel - * and in principle make those checks in executor unnecessary, but - * we keep them just in case. */ if (qual_product != NULL) - { - switch (parsetree->commandType) - { - case CMD_INSERT: - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("cannot insert into view \"%s\"", - RelationGetRelationName(rt_entry_relation)), - errdetail("Views with conditional DO INSTEAD rules are not automatically updatable."), - 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: - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("cannot update view \"%s\"", - RelationGetRelationName(rt_entry_relation)), - errdetail("Views with conditional DO INSTEAD rules are not automatically updatable."), - errhint("To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule."))); - break; - case CMD_DELETE: - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("cannot delete from view \"%s\"", - RelationGetRelationName(rt_entry_relation)), - errdetail("Views with conditional DO INSTEAD rules are not automatically updatable."), - 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) parsetree->commandType); - break; - } - } + error_view_not_updatable(rt_entry_relation, + parsetree->commandType, + parsetree->mergeActionList, + gettext_noop("Views with conditional DO INSTEAD rules are not automatically updatable.")); /* * Attempt to rewrite the query to automatically update the view. diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c index 76c97a5b28e..191f2dc0b1d 100644 --- a/src/backend/rewrite/rewriteManip.c +++ b/src/backend/rewrite/rewriteManip.c @@ -496,9 +496,10 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up) /* * If we are starting at a Query, and sublevels_up is zero, then we * must also fix rangetable indexes in the Query itself --- namely - * resultRelation, exclRelIndex and rowMarks entries. sublevels_up - * cannot be zero when recursing into a subquery, so there's no need - * to have the same logic inside OffsetVarNodes_walker. + * resultRelation, mergeTargetRelation, exclRelIndex and rowMarks + * entries. sublevels_up cannot be zero when recursing into a + * subquery, so there's no need to have the same logic inside + * OffsetVarNodes_walker. */ if (sublevels_up == 0) { @@ -507,6 +508,9 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up) if (qry->resultRelation) qry->resultRelation += offset; + if (qry->mergeTargetRelation) + qry->mergeTargetRelation += offset; + if (qry->onConflict && qry->onConflict->exclRelIndex) qry->onConflict->exclRelIndex += offset; @@ -687,9 +691,10 @@ ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up) /* * If we are starting at a Query, and sublevels_up is zero, then we * must also fix rangetable indexes in the Query itself --- namely - * resultRelation and rowMarks entries. sublevels_up cannot be zero - * when recursing into a subquery, so there's no need to have the same - * logic inside ChangeVarNodes_walker. + * resultRelation, mergeTargetRelation, exclRelIndex and rowMarks + * entries. sublevels_up cannot be zero when recursing into a + * subquery, so there's no need to have the same logic inside + * ChangeVarNodes_walker. */ if (sublevels_up == 0) { @@ -698,6 +703,9 @@ ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up) if (qry->resultRelation == rt_index) qry->resultRelation = new_index; + if (qry->mergeTargetRelation == rt_index) + qry->mergeTargetRelation = new_index; + /* this is unlikely to ever be used, but ... */ if (qry->onConflict && qry->onConflict->exclRelIndex == rt_index) qry->onConflict->exclRelIndex = new_index; |