diff options
Diffstat (limited to 'src/backend/rewrite/rewriteHandler.c')
-rw-r--r-- | src/backend/rewrite/rewriteHandler.c | 684 |
1 files changed, 674 insertions, 10 deletions
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index b785c269a0..990ca34681 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -1834,6 +1834,633 @@ fireRules(Query *parsetree, /* + * get_view_query - get the Query from a view's _RETURN rule. + * + * Caller should have verified that the relation is a view, and therefore + * we should find an ON SELECT action. + */ +static Query * +get_view_query(Relation view) +{ + int i; + + Assert(view->rd_rel->relkind == RELKIND_VIEW); + + for (i = 0; i < view->rd_rules->numLocks; i++) + { + RewriteRule *rule = view->rd_rules->rules[i]; + + if (rule->event == CMD_SELECT) + { + /* A _RETURN rule should have only one action */ + if (list_length(rule->actions) != 1) + elog(ERROR, "invalid _RETURN rule action specification"); + + return (Query *) linitial(rule->actions); + } + } + + elog(ERROR, "failed to find _RETURN rule for view"); + return NULL; /* keep compiler quiet */ +} + + +/* + * view_has_instead_trigger - does view have an INSTEAD OF trigger for event? + * + * If it does, we don't want to treat it as auto-updatable. This test can't + * be folded into view_is_auto_updatable because it's not an error condition. + */ +static bool +view_has_instead_trigger(Relation view, CmdType event) +{ + TriggerDesc *trigDesc = view->trigdesc; + + switch (event) + { + case CMD_INSERT: + if (trigDesc && trigDesc->trig_insert_instead_row) + return true; + break; + case CMD_UPDATE: + if (trigDesc && trigDesc->trig_update_instead_row) + return true; + break; + case CMD_DELETE: + if (trigDesc && trigDesc->trig_delete_instead_row) + return true; + break; + default: + elog(ERROR, "unrecognized CmdType: %d", (int) event); + break; + } + return false; +} + + +/* + * view_is_auto_updatable - + * Test if the specified view can be automatically updated. This will + * either return NULL (if the view can be updated) or a message string + * giving the reason that it cannot be. + * + * Caller must have verified that relation is a view! + * + * Note that the checks performed here are local to this view. We do not + * check whether the view's underlying base relation is updatable; that + * will be dealt with in later, recursive processing. + * + * Also note that we don't check for INSTEAD triggers or rules here; those + * also prevent auto-update, but they must be checked for by the caller. + */ +static const char * +view_is_auto_updatable(Relation view) +{ + Query *viewquery = get_view_query(view); + RangeTblRef *rtr; + RangeTblEntry *base_rte; + Bitmapset *bms; + ListCell *cell; + + /*---------- + * Check if the view is simply updatable. According to SQL-92 this means: + * - No DISTINCT clause. + * - Each TLE is a column reference, and each column appears at most once. + * - FROM contains exactly one base relation. + * - No GROUP BY or HAVING clauses. + * - No set operations (UNION, INTERSECT or EXCEPT). + * - No sub-queries in the WHERE clause that reference the target table. + * + * We ignore that last restriction since it would be complex to enforce + * and there isn't any actual benefit to disallowing sub-queries. (The + * semantic issues that the standard is presumably concerned about don't + * arise in Postgres, since any such sub-query will not see any updates + * executed by the outer query anyway, thanks to MVCC snapshotting.) + * + * In addition we impose these constraints, involving features that are + * not part of SQL-92: + * - No CTEs (WITH clauses). + * - No OFFSET or LIMIT clauses (this matches a SQL:2008 restriction). + * - No system columns (including whole-row references) in the tlist. + * + * Note that we do these checks without recursively expanding the view. + * If the base relation is a view, we'll recursively deal with it later. + *---------- + */ + if (viewquery->distinctClause != NIL) + return gettext_noop("Views containing DISTINCT are not automatically updatable."); + + if (viewquery->groupClause != NIL) + return gettext_noop("Views containing GROUP BY are not automatically updatable."); + + if (viewquery->havingQual != NULL) + return gettext_noop("Views containing HAVING are not automatically updatable."); + + if (viewquery->setOperations != NULL) + return gettext_noop("Views containing UNION, INTERSECT or EXCEPT are not automatically updatable."); + + if (viewquery->cteList != NIL) + return gettext_noop("Views containing WITH are not automatically updatable."); + + if (viewquery->limitOffset != NULL || viewquery->limitCount != NULL) + return gettext_noop("Views containing LIMIT or OFFSET are not automatically updatable."); + + /* + * For now, we also don't support security-barrier views, because of the + * difficulty of keeping upper-level qual expressions away from + * lower-level data. This might get relaxed in future. + */ + if (RelationIsSecurityView(view)) + return gettext_noop("Security-barrier views are not automatically updatable."); + + /* + * The view query should select from a single base relation, which must be + * a table or another view. + */ + if (list_length(viewquery->jointree->fromlist) != 1) + return gettext_noop("Views that do not select from a single table or view are not automatically updatable."); + + rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist); + if (!IsA(rtr, RangeTblRef)) + return gettext_noop("Views that do not select from a single table or view are not automatically updatable."); + + base_rte = rt_fetch(rtr->rtindex, viewquery->rtable); + if (base_rte->rtekind != RTE_RELATION || + (base_rte->relkind != RELKIND_RELATION && + base_rte->relkind != RELKIND_VIEW)) + return gettext_noop("Views that do not select from a single table or view are not automatically updatable."); + + /* + * The view's targetlist entries should all be Vars referring to user + * columns of the base relation, and no two should refer to the same + * column. + * + * Note however that we should ignore resjunk entries. This proviso is + * relevant because ORDER BY is not disallowed, and we shouldn't reject a + * view defined like "SELECT * FROM t ORDER BY a+b". + */ + bms = NULL; + foreach(cell, viewquery->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(cell); + Var *var = (Var *) tle->expr; + + if (tle->resjunk) + continue; + + if (!IsA(var, Var) || + var->varno != rtr->rtindex || + var->varlevelsup != 0) + return gettext_noop("Views that return columns that are not columns of their base relation are not automatically updatable."); + + if (var->varattno < 0) + return gettext_noop("Views that return system columns are not automatically updatable."); + + if (var->varattno == 0) + return gettext_noop("Views that return whole-row references are not automatically updatable."); + + if (bms_is_member(var->varattno, bms)) + return gettext_noop("Views that return the same column more than once are not automatically updatable."); + + bms = bms_add_member(bms, var->varattno); + } + bms_free(bms); /* just for cleanliness */ + + return NULL; /* the view is simply updatable */ +} + + +/* + * relation_is_updatable - test if the specified relation is updatable. + * + * This is used for the information_schema views, which have separate concepts + * of "updatable" and "trigger updatable". A relation is "updatable" if it + * can be updated without the need for triggers (either because it has a + * suitable RULE, or because it is simple enough to be automatically updated). + * + * A relation is "trigger updatable" if it has a suitable INSTEAD OF trigger. + * The SQL standard regards this as not necessarily updatable, presumably + * because there is no way of knowing what the trigger will actually do. + * That's currently handled directly in the information_schema views, so + * need not be considered here. + * + * In the case of an automatically updatable view, the base relation must + * also be updatable. + * + * reloid is the pg_class OID to examine. req_events is a bitmask of + * rule event numbers; the relation is considered rule-updatable if it has + * all the specified rules. (We do it this way so that we can test for + * UPDATE plus DELETE rules in a single call.) + */ +bool +relation_is_updatable(Oid reloid, int req_events) +{ + Relation rel; + RuleLock *rulelocks; + + rel = try_relation_open(reloid, AccessShareLock); + + /* + * If the relation doesn't exist, say "false" rather than throwing an + * error. This is helpful since scanning an information_schema view + * under MVCC rules can result in referencing rels that were just + * deleted according to a SnapshotNow probe. + */ + if (rel == NULL) + return false; + + /* Look for unconditional DO INSTEAD rules, and note supported events */ + rulelocks = rel->rd_rules; + if (rulelocks != NULL) + { + int events = 0; + int i; + + for (i = 0; i < rulelocks->numLocks; i++) + { + if (rulelocks->rules[i]->isInstead && + rulelocks->rules[i]->qual == NULL) + { + events |= 1 << rulelocks->rules[i]->event; + } + } + + /* If we have all rules needed, say "yes" */ + if ((events & req_events) == req_events) + { + relation_close(rel, AccessShareLock); + return true; + } + } + + /* Check if this is an automatically updatable view */ + if (rel->rd_rel->relkind == RELKIND_VIEW && + view_is_auto_updatable(rel) == NULL) + { + Query *viewquery; + RangeTblRef *rtr; + RangeTblEntry *base_rte; + Oid baseoid; + + /* The base relation must also be updatable */ + viewquery = get_view_query(rel); + rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist); + base_rte = rt_fetch(rtr->rtindex, viewquery->rtable); + + if (base_rte->relkind == RELKIND_RELATION) + { + /* Tables are always updatable */ + relation_close(rel, AccessShareLock); + return true; + } + else + { + /* Do a recursive check for any other kind of base relation */ + baseoid = base_rte->relid; + relation_close(rel, AccessShareLock); + return relation_is_updatable(baseoid, req_events); + } + } + + /* If we reach here, the relation is not updatable */ + relation_close(rel, AccessShareLock); + return false; +} + + +/* + * adjust_view_column_set - map a set of column numbers according to targetlist + * + * This is used with simply-updatable views to map column-permissions sets for + * the view columns onto the matching columns in the underlying base relation. + * The targetlist is expected to be a list of plain Vars of the underlying + * relation (as per the checks above in view_is_auto_updatable). + */ +static Bitmapset * +adjust_view_column_set(Bitmapset *cols, List *targetlist) +{ + Bitmapset *result = NULL; + Bitmapset *tmpcols; + AttrNumber col; + + tmpcols = bms_copy(cols); + while ((col = bms_first_member(tmpcols)) >= 0) + { + /* bit numbers are offset by FirstLowInvalidHeapAttributeNumber */ + AttrNumber attno = col + FirstLowInvalidHeapAttributeNumber; + + if (attno == InvalidAttrNumber) + { + /* + * There's a whole-row reference to the view. For permissions + * purposes, treat it as a reference to each column available from + * the view. (We should *not* convert this to a whole-row + * reference to the base relation, since the view may not touch + * all columns of the base relation.) + */ + ListCell *lc; + + foreach(lc, targetlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + Var *var; + + if (tle->resjunk) + continue; + var = (Var *) tle->expr; + Assert(IsA(var, Var)); + result = bms_add_member(result, + var->varattno - FirstLowInvalidHeapAttributeNumber); + } + } + else + { + /* + * Views do not have system columns, so we do not expect to see + * any other system attnos here. If we do find one, the error + * case will apply. + */ + TargetEntry *tle = get_tle_by_resno(targetlist, attno); + + if (tle != NULL && !tle->resjunk && IsA(tle->expr, Var)) + { + Var *var = (Var *) tle->expr; + + result = bms_add_member(result, + var->varattno - FirstLowInvalidHeapAttributeNumber); + } + else + elog(ERROR, "attribute number %d not found in view targetlist", + attno); + } + } + bms_free(tmpcols); + + return result; +} + + +/* + * rewriteTargetView - + * Attempt to rewrite a query where the target relation is a view, so that + * the view's base relation becomes the target relation. + * + * Note that the base relation here may itself be a view, which may or may not + * have INSTEAD OF triggers or rules to handle the update. That is handled by + * the recursion in RewriteQuery. + */ +static Query * +rewriteTargetView(Query *parsetree, Relation view) +{ + const char *auto_update_detail; + Query *viewquery; + RangeTblRef *rtr; + int base_rt_index; + int new_rt_index; + RangeTblEntry *base_rte; + RangeTblEntry *view_rte; + RangeTblEntry *new_rte; + Relation base_rel; + List *view_targetlist; + ListCell *lc; + + /* The view must be simply updatable, else fail */ + auto_update_detail = view_is_auto_updatable(view); + if (auto_update_detail) + { + /* messages here should match execMain.c's CheckValidResultRel */ + switch (parsetree->commandType) + { + 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 make the view insertable, provide an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger."))); + 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 make the view updatable, provide an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger."))); + 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 make the view updatable, provide an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger."))); + break; + default: + elog(ERROR, "unrecognized CmdType: %d", + (int) parsetree->commandType); + break; + } + } + + /* Locate RTE describing the view in the outer query */ + view_rte = rt_fetch(parsetree->resultRelation, parsetree->rtable); + + /* + * If we get here, view_is_auto_updatable() has verified that the view + * contains a single base relation. + */ + viewquery = get_view_query(view); + + Assert(list_length(viewquery->jointree->fromlist) == 1); + rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist); + Assert(IsA(rtr, RangeTblRef)); + + base_rt_index = rtr->rtindex; + base_rte = rt_fetch(base_rt_index, viewquery->rtable); + Assert(base_rte->rtekind == RTE_RELATION); + + /* + * Up to now, the base relation hasn't been touched at all in our query. + * We need to acquire lock on it before we try to do anything with it. + * (The subsequent recursive call of RewriteQuery will suppose that we + * already have the right lock!) Since it will become the query target + * relation, RowExclusiveLock is always the right thing. + */ + base_rel = heap_open(base_rte->relid, RowExclusiveLock); + + /* + * While we have the relation open, update the RTE's relkind, just in case + * it changed since this view was made (cf. AcquireRewriteLocks). + */ + base_rte->relkind = base_rel->rd_rel->relkind; + + heap_close(base_rel, NoLock); + + /* + * Create a new target RTE describing the base relation, and add it to the + * outer query's rangetable. (What's happening in the next few steps is + * very much like what the planner would do to "pull up" the view into the + * outer query. Perhaps someday we should refactor things enough so that + * we can share code with the planner.) + */ + new_rte = (RangeTblEntry *) copyObject(base_rte); + parsetree->rtable = lappend(parsetree->rtable, new_rte); + new_rt_index = list_length(parsetree->rtable); + + /* + * Make a copy of the view's targetlist, adjusting its Vars to reference + * the new target RTE, ie make their varnos be new_rt_index instead of + * base_rt_index. There can be no Vars for other rels in the tlist, so + * this is sufficient to pull up the tlist expressions for use in the + * outer query. The tlist will provide the replacement expressions used + * by ReplaceVarsFromTargetList below. + */ + view_targetlist = copyObject(viewquery->targetList); + + ChangeVarNodes((Node *) view_targetlist, + base_rt_index, + new_rt_index, + 0); + + /* + * Mark the new target RTE for the permissions checks that we want to + * enforce against the view owner, as distinct from the query caller. At + * the relation level, require the same INSERT/UPDATE/DELETE permissions + * that the query caller needs against the view. We drop the ACL_SELECT + * bit that is presumably in new_rte->requiredPerms initially. + * + * Note: the original view RTE remains in the query's rangetable list. + * Although it will be unused in the query plan, we need it there so that + * the executor still performs appropriate permissions checks for the + * query caller's use of the view. + */ + new_rte->checkAsUser = view->rd_rel->relowner; + new_rte->requiredPerms = view_rte->requiredPerms; + + /* + * Now for the per-column permissions bits. + * + * Initially, new_rte contains selectedCols permission check bits for all + * base-rel columns referenced by the view, but since the view is a SELECT + * query its modifiedCols is empty. We set modifiedCols to include all + * the columns the outer query is trying to modify, adjusting the column + * numbers as needed. But we leave selectedCols as-is, so the view owner + * must have read permission for all columns used in the view definition, + * even if some of them are not read by the outer query. We could try to + * limit selectedCols to only columns used in the transformed query, but + * that does not correspond to what happens in ordinary SELECT usage of a + * view: all referenced columns must have read permission, even if + * optimization finds that some of them can be discarded during query + * transformation. The flattening we're doing here is an optional + * optimization, too. (If you are unpersuaded and want to change this, + * note that applying adjust_view_column_set to view_rte->selectedCols is + * clearly *not* the right answer, since that neglects base-rel columns + * used in the view's WHERE quals.) + * + * This step needs the modified view targetlist, so we have to do things + * in this order. + */ + Assert(bms_is_empty(new_rte->modifiedCols)); + new_rte->modifiedCols = adjust_view_column_set(view_rte->modifiedCols, + view_targetlist); + + /* + * For UPDATE/DELETE, rewriteTargetListUD will have added a wholerow junk + * TLE for the view to the end of the targetlist, which we no longer need. + * Remove it to avoid unnecessary work when we process the targetlist. + * Note that when we recurse through rewriteQuery a new junk TLE will be + * added to allow the executor to find the proper row in the new target + * relation. (So, if we failed to do this, we might have multiple junk + * TLEs with the same name, which would be disastrous.) + */ + if (parsetree->commandType != CMD_INSERT) + { + TargetEntry *tle = (TargetEntry *) llast(parsetree->targetList); + + Assert(tle->resjunk); + Assert(IsA(tle->expr, Var) && + ((Var *) tle->expr)->varno == parsetree->resultRelation && + ((Var *) tle->expr)->varattno == 0); + parsetree->targetList = list_delete_ptr(parsetree->targetList, tle); + } + + /* + * Now update all Vars in the outer query that reference the view to + * reference the appropriate column of the base relation instead. + */ + parsetree = (Query *) + ReplaceVarsFromTargetList((Node *) parsetree, + parsetree->resultRelation, + 0, + view_rte, + view_targetlist, + REPLACEVARS_REPORT_ERROR, + 0, + &parsetree->hasSubLinks); + + /* + * Update all other RTI references in the query that point to the view + * (for example, parsetree->resultRelation itself) to point to the new + * base relation instead. Vars will not be affected since none of them + * reference parsetree->resultRelation any longer. + */ + ChangeVarNodes((Node *) parsetree, + parsetree->resultRelation, + new_rt_index, + 0); + Assert(parsetree->resultRelation == new_rt_index); + + /* + * 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. + * + * Note that this destroys the resno ordering of the targetlist, but that + * will be fixed when we recurse through rewriteQuery, which will invoke + * rewriteTargetListIU again on the updated targetlist. + */ + if (parsetree->commandType != CMD_DELETE) + { + foreach(lc, parsetree->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + 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); + } + } + + /* + * 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 INSERT, the view's quals can be ignored for now. When we implement + * WITH CHECK OPTION, this might be a good place to collect them. + */ + if (parsetree->commandType != CMD_INSERT && + viewquery->jointree->quals != NULL) + { + Node *viewqual = (Node *) copyObject(viewquery->jointree->quals); + + ChangeVarNodes(viewqual, base_rt_index, new_rt_index, 0); + AddQual(parsetree, (Node *) viewqual); + } + + return parsetree; +} + + +/* * RewriteQuery - * rewrites the query and apply the rules again on the queries rewritten * @@ -1927,6 +2554,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events) RangeTblEntry *rt_entry; Relation rt_entry_relation; List *locks; + List *product_queries; result_relation = parsetree->resultRelation; Assert(result_relation != 0); @@ -1997,17 +2625,54 @@ RewriteQuery(Query *parsetree, List *rewrite_events) locks = matchLocks(event, rt_entry_relation->rd_rules, result_relation, parsetree); - if (locks != NIL) + product_queries = fireRules(parsetree, + result_relation, + event, + locks, + &instead, + &returning, + &qual_product); + + /* + * If there were no INSTEAD rules, and the target relation is a view + * without any INSTEAD OF triggers, see if the view can be + * 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. + */ + if (!instead && qual_product == NULL && + rt_entry_relation->rd_rel->relkind == RELKIND_VIEW && + !view_has_instead_trigger(rt_entry_relation, event)) { - List *product_queries; + /* + * This throws an error if the view can't be automatically + * updated, but that's OK since the query would fail at runtime + * anyway. + */ + parsetree = rewriteTargetView(parsetree, rt_entry_relation); - product_queries = fireRules(parsetree, - result_relation, - event, - locks, - &instead, - &returning, - &qual_product); + /* + * At this point product_queries contains any DO ALSO rule actions. + * Add the rewritten query before or after those. This must match + * the handling the original query would have gotten below, if + * we allowed it to be included again. + */ + if (parsetree->commandType == CMD_INSERT) + product_queries = lcons(parsetree, product_queries); + else + product_queries = lappend(product_queries, parsetree); + + /* + * Set the "instead" flag, as if there had been an unqualified + * INSTEAD, to prevent the original query from being included a + * second time below. The transformation will have rewritten any + * RETURNING list, so we can also set "returning" to forestall + * throwing an error below. + */ + instead = true; + returning = true; + } /* * If we got any product queries, recursively rewrite them --- but @@ -2045,7 +2710,6 @@ RewriteQuery(Query *parsetree, List *rewrite_events) rewrite_events = list_delete_first(rewrite_events); } - } /* * If there is an INSTEAD, and the original query has a RETURNING, we |