diff options
Diffstat (limited to 'src/backend/rewrite/rewriteHandler.c')
-rw-r--r-- | src/backend/rewrite/rewriteHandler.c | 403 |
1 files changed, 287 insertions, 116 deletions
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. |