mirror of
https://github.com/postgres/postgres.git
synced 2025-07-28 23:42:10 +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:
@ -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;
|
||||
@ -3030,6 +3068,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
|
||||
@ -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.
|
||||
|
@ -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;
|
||||
|
Reference in New Issue
Block a user