diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c new file mode 100644 index 8f75948..f942767 *** a/src/backend/rewrite/rewriteHandler.c --- b/src/backend/rewrite/rewriteHandler.c *************** fireRules(Query *parsetree, *** 1829,1834 **** --- 1829,2218 ---- /* + * get_view_query - get the query from a view's _RETURN rule. + */ + 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 linitial(rule->actions); + } + } + + elog(ERROR, "failed to find _RETURN rule for view"); + return NULL; /* keep compiler quiet */ + } + + + /* + * test_auto_update_view - + * Test if the specified view can be automatically updated. This will either + * return NULL (if the view can be updated) or the reason that it cannot. + * + * Note that the checks performed here are local to this view. It does not + * check that the view's underlying base relation is updatable. + */ + static const char * + test_auto_update_view(Relation view) + { + Query *viewquery; + RangeTblRef *rtr; + RangeTblEntry *base_rte; + Bitmapset *bms; + ListCell *cell; + + /* + * Check if the view is simply updatable. According to SQL-92 this means: + * - No DISTINCT clauses. + * - Every TLE is a column reference, and appears at most once. + * - Refers to a single base relation. + * - No GROUP BY or HAVING clauses. + * - No set operations (UNION, INTERSECT or EXCEPT). + * - No sub-queries in the WHERE clause. + * + * We relax this last restriction since it would actually be more work to + * enforce it than to simply allow it. In addition (for now) we impose + * the following additional constraints, based on features that are not + * part of SQL-92: + * - No DISTINCT ON clauses. + * - No window functions. + * - No CTEs (WITH or WITH RECURSIVE). + * - No OFFSET or LIMIT clauses. + * - No system columns. + * + * Note that we do these checks without recursively expanding the view. + * The base relation may be a view, and it might have INSTEAD OF triggers + * or rules, in which case it need not satisfy these constraints. + */ + viewquery = get_view_query(view); + + if (viewquery->distinctClause != NIL || + viewquery->hasDistinctOn) + return "Views containing DISTINCT are not updatable"; + + if (viewquery->groupClause != NIL) + return "Views containing GROUP BY are not updatable"; + + if (viewquery->havingQual != NULL) + return "Views containing HAVING are not updatable"; + + if (viewquery->hasAggs) + return "Views containing aggregates are not updatable"; + + if (viewquery->hasWindowFuncs) + return "Views containing window functions are not updatable"; + + if (viewquery->setOperations != NULL) + return "Views containing UNION, INTERSECT or EXCEPT are not updatable"; + + if (viewquery->hasRecursive || + viewquery->cteList != NIL) + return "Views containing WITH [RECURSIVE] are not updatable"; + + if (viewquery->limitOffset != NULL || + viewquery->limitCount != NULL) + return "Views containing OFFSET or LIMIT are not 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) == 0) + return "Views with no base relations are not updatable"; + + if (list_length(viewquery->jointree->fromlist) > 1) + return "Views with multiple base relations are not updatable"; + + rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist); + if (!IsA(rtr, RangeTblRef)) + return "Views that are not based on tables or views are not updatable"; + + base_rte = rt_fetch(rtr->rtindex, viewquery->rtable); + if (base_rte->rtekind != RTE_RELATION) + return "Views that are not based on tables or views are not updatable"; + + /* + * The view's targetlist entries should all be Vars referring to columns + * in the base relation, and no 2 should refer to the same base column. + * + * Note that we don't bother freeing resources allocated here if we + * return early, since that is an error condition. + */ + bms = NULL; + foreach(cell, viewquery->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(cell); + Var *var; + + if (!IsA(tle->expr, Var)) + return "Views with columns that are not simple references to columns in the base relation are not updatable"; + + var = (Var *) tle->expr; + if (var->varattno < 0) + return "Views that refer to system columns are not updatable"; + + if (var->varattno == 0) + return "Views that refer to whole rows from the base relation are not updatable"; + + if (bms_is_member(var->varattno, bms)) + return "Views that refer to the same column more than once are not updatable"; + + bms = bms_add_member(bms, var->varattno); + } + + bms_free(bms); + return NULL; /* the view is simply updatable */ + } + + + /* + * rewriteTargetView - + * attempt to rewrite a query where the target relation is a view, so that + * the view's base relation becomes the target relation. + * + * This only handles the case where there are no INSTEAD OF triggers to update + * the view. If there are INSTEAD OF triggers the view is expanded as part + * of fireRIRrules, which has dedicated code to keep both the original view + * target and the expanded rule actions to act as a source of data. + * + * 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 case is + * handled by recursion in RewriteQuery. + */ + static Query * + rewriteTargetView(Query *parsetree, Relation view) + { + TriggerDesc *trigDesc = view->trigdesc; + const char *auto_update_detail; + Query *viewquery; + RangeTblRef *rtr; + RangeTblEntry *base_rte; + int natt; + AttrNumber *base_attno; + bool reorder_atts; + int attno; + ListCell *cell; + RangeTblEntry *view_rte; + int rt_index; + List *new_rtable; + + /* The view must have INSTEAD OF triggers or be simply updatable */ + switch (parsetree->commandType) + { + case CMD_INSERT: + if (trigDesc && trigDesc->trig_insert_instead_row) + return NULL; /* INSTEAD OF INSERT trigger will be used */ + + auto_update_detail = test_auto_update_view(view); + if (auto_update_detail) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot insert into view \"%s\"", + RelationGetRelationName(view)), + errdetail("%s.", auto_update_detail), + errhint("You need an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger."))); + break; + case CMD_UPDATE: + if (trigDesc && trigDesc->trig_update_instead_row) + return NULL; /* INSTEAD OF UPDATE trigger will be used */ + + auto_update_detail = test_auto_update_view(view); + if (auto_update_detail) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot update view \"%s\"", + RelationGetRelationName(view)), + errdetail("%s.", auto_update_detail), + errhint("You need an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger."))); + break; + case CMD_DELETE: + if (trigDesc && trigDesc->trig_delete_instead_row) + return NULL; /* INSTEAD OF DELETE trigger will be used */ + + auto_update_detail = test_auto_update_view(view); + if (auto_update_detail) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot delete from view \"%s\"", + RelationGetRelationName(view)), + errdetail("%s.", auto_update_detail), + errhint("You need an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger."))); + break; + default: + elog(ERROR, "unrecognized CmdType: %d", (int) parsetree->commandType); + break; + } + + /* + * The view should have 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_rte = rt_fetch(rtr->rtindex, viewquery->rtable); + Assert(base_rte->rtekind == RTE_RELATION); + + /* + * The view's targetlist entries should all be Vars referring to columns + * in the base relation, and no 2 should refer to the same base column. + * Build a mapping from view column number to base relation column number, + * and catch the case where they are in the same order. + */ + natt = list_length(viewquery->targetList); + base_attno = (AttrNumber *) palloc(sizeof(AttrNumber) * natt); + reorder_atts = false; + + attno = 1; + foreach(cell, viewquery->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(cell); + Var *var = (Var *) tle->expr; + + Assert(var->varattno > 0); + base_attno[attno - 1] = var->varattno; + + if (var->varattno != attno) + reorder_atts = true; /* view columns in different order */ + + attno++; + } + + /* + * The view is simply updatable, provided that its base relation is + * updatable, which will be checked later. To avoid having to adjust + * varnos, we replace the old target RTE with the base relation RTE. + * + * Note that we also need to keep the original view RTE even though it + * will not be used so that the correct permissions checks are done + * against it. + */ + rt_index = 1; + new_rtable = NIL; + foreach(cell, parsetree->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(cell); + + if (rt_index == parsetree->resultRelation) + { + /* + * Create the new target RTE using the base relation. Copy any + * permission checks from the original view target RTE, adjusting + * the column numbers if necessary, and performing the checks as + * the view's owner. + */ + RangeTblEntry *newrte = (RangeTblEntry *) copyObject(base_rte); + newrte->requiredPerms = rte->requiredPerms; + newrte->checkAsUser = view->rd_rel->relowner; + newrte->selectedCols = bms_copy(rte->selectedCols); + newrte->modifiedCols = bms_copy(rte->modifiedCols); + + if (reorder_atts) + { + newrte->selectedCols = adjust_col_privs(newrte->selectedCols, + natt, base_attno); + newrte->modifiedCols = adjust_col_privs(newrte->modifiedCols, + natt, base_attno); + } + + /* + * Keep the original view RTE so that executor also checks that + * the current user has the required permissions on the view + */ + view_rte = rte; + rte = newrte; + } + new_rtable = lappend(new_rtable, rte); + rt_index++; + } + parsetree->rtable = lappend(new_rtable, view_rte); + + /* + * 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. + * We remove this now to avoid unnecessary work when we process the + * targetlist. Note that when we recurse through rewriteQuery a new junk + * RTE will be added to allow the executor to find the row in the base + * relation. + */ + if (parsetree->commandType != CMD_INSERT) + { + TargetEntry *tle = (TargetEntry *) + lfirst(list_tail(parsetree->targetList)); + + Assert(IsA(tle->expr, Var) && ((Var *) tle->expr)->varattno == 0); + parsetree->targetList = list_delete_ptr(parsetree->targetList, tle); + } + + /* + * If the view's columns are in a different order, we need to update the + * target list to point to the new columns in the base relation, and + * similarly adjust any Vars in the query that refer to the result + * relation. + */ + if (reorder_atts) + { + /* Update target list entries */ + foreach(cell, parsetree->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(cell); + + if (!tle->resjunk) + { + if (tle->resno > 0 && tle->resno <= natt) + tle->resno = base_attno[tle->resno - 1]; + } + } + + /* Update any Var attribute numbers for the result relation */ + parsetree = + (Query *) ChangeVarAttnos((Node *) parsetree, + parsetree->resultRelation, + natt, base_attno, + viewquery->targetList, + view_rte->eref->colnames, 0); + } + + /* + * For UPDATE/DELETE, add any quals from the view's jointree to the main + * jointree to filter rows. We know that there is just one relation in + * the view, so any of its quals that refer to it need to be adjusted to + * refer to it's new location in the main query. + * + * For INSERT the view's quals are not needed for now. When we implement + * WITH CHECK OPTION, this might be a good time to collect them. + */ + if (parsetree->commandType != CMD_INSERT) + { + Node *viewqual = copyObject(viewquery->jointree->quals); + + ChangeVarNodes(viewqual, rtr->rtindex, parsetree->resultRelation, 0); + AddQual(parsetree, (Node *) viewqual); + } + + /* Tidy up */ + pfree(base_attno); + + return parsetree; + } + + + /* * RewriteQuery - * rewrites the query and apply the rules again on the queries rewritten * *************** RewriteQuery(Query *parsetree, List *rew *** 1842,1847 **** --- 2226,2232 ---- bool instead = false; bool returning = false; Query *qual_product = NULL; + List *product_queries = NIL; List *rewritten = NIL; ListCell *lc1; *************** RewriteQuery(Query *parsetree, List *rew *** 1993,2001 **** result_relation, parsetree); if (locks != NIL) - { - List *product_queries; - product_queries = fireRules(parsetree, result_relation, event, --- 2378,2383 ---- *************** RewriteQuery(Query *parsetree, List *rew *** 2004,2009 **** --- 2386,2416 ---- &returning, &qual_product); + /* + * If there are no unqualified INSTEAD rules, and the target relation + * is a view without any INSTEAD OF triggers, then we have a problem + * unless it can be automatically updated. + * + * If we can automatically update the view, then we do so here and add + * the resulting query to the product queries list, so that it gets + * recursively rewritten if necessary. We mark this as an unqualified + * INSTEAD to prevent the original query from being executed. Note + * also that if this succeeds it will have rewritten any returning + * list too. + */ + if (!instead && rt_entry_relation->rd_rel->relkind == RELKIND_VIEW) + { + Query *query = qual_product ? qual_product : parsetree; + Query *newquery = rewriteTargetView(query, rt_entry_relation); + + if (newquery != NULL) + { + product_queries = lappend(product_queries, newquery); + instead = true; + returning = true; + } + } + /* * If we got any product queries, recursively rewrite them --- but * first check for recursion! *************** RewriteQuery(Query *parsetree, List *rew *** 2040,2046 **** rewrite_events = list_delete_first(rewrite_events); } - } /* * If there is an INSTEAD, and the original query has a RETURNING, we --- 2447,2452 ---- diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c new file mode 100644 index 9c778ef..0ed5666 *** a/src/backend/rewrite/rewriteManip.c --- b/src/backend/rewrite/rewriteManip.c *************** *** 13,18 **** --- 13,19 ---- */ #include "postgres.h" + #include "access/sysattr.h" #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" *************** ResolveNew(Node *node, int target_varno, *** 1451,1453 **** --- 1452,1599 ---- (void *) &context, outer_hasSubLinks); } + + + /* + * Adjust the columns in a set needing particular privileges, using the + * specified attribute number mappings. This is used for simply updatable + * views to map permissions checks on the view columns onto the matching + * columns in the underlying base relation. + */ + Bitmapset * + adjust_col_privs(Bitmapset *cols, int natt, AttrNumber *new_attno) + { + Bitmapset *result = NULL; + Bitmapset *tmpcols; + AttrNumber col; + + tmpcols = bms_copy(cols); + while ((col = bms_first_member(tmpcols)) >= 0) + { + AttrNumber attno = col + FirstLowInvalidHeapAttributeNumber; + + if (attno <= 0) + result = bms_add_member(result, col); /* system column */ + else if (attno <= natt) + result = bms_add_member(result, + new_attno[attno - 1] - FirstLowInvalidHeapAttributeNumber); + } + bms_free(tmpcols); + + return result; + } + + + /* + * ChangeVarAttnos - Adjust the attribute numbers in Var nodes + * + * Find all Var nodes in the given tree belonging to a specific relation + * (identified by sublevels_up and rt_index), and change their varattno + * fields using the new attribute numbers specified. This is used for simply + * updatable views to map references to view columns onto references to + * columns in the underlying base relation. + * + * Any whole-row references to the view are mapped onto ROW expressions + * constructed from the view's targetlist. + */ + + typedef struct + { + int rt_index; + int natt; + AttrNumber *new_attno; + List *view_tlist; + List *view_colnames; + int sublevels_up; + } ChangeVarAttnos_context; + + static Node * + ChangeVarAttnos_mutator(Node *node, ChangeVarAttnos_context *context) + { + if (node == NULL) + return NULL; + if (IsA(node, Var)) + { + Var *var = (Var *) node; + + if (var->varlevelsup == context->sublevels_up && + var->varno == context->rt_index) + { + if (var->varattno > 0) + { + /* + * A regular column in the view is mapped to the corresponding + * column in the base relation + */ + Var *newvar; + + if (var->varattno > context->natt) + elog(ERROR, "varattno %d too large for view", + var->varattno); + + newvar = (Var *) copyObject(var); + newvar->varattno = context->new_attno[var->varattno - 1]; + return (Node *) newvar; + } + else if (var->varattno == 0) + { + /* + * For a wholerow variable in the view, we construct a RowExpr + * from the view's targetlist + */ + RowExpr *row = makeNode(RowExpr); + ListCell *cell; + + row->args = NIL; + foreach(cell, context->view_tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(cell); + Var *view_var = (Var *) copyObject(tle->expr); + + view_var->varno = var->varno; + row->args = lappend(row->args, view_var); + } + + row->row_typeid = var->vartype; + row->row_format = COERCE_IMPLICIT_CAST; + row->colnames = copyObject(context->view_colnames); + row->location = -1; + return (Node *) row; + } + } + } + if (IsA(node, Query)) + { + /* Recurse into subselects */ + Query *result; + + context->sublevels_up++; + result = query_tree_mutator((Query *) node, ChangeVarAttnos_mutator, + (void *) context, 0); + context->sublevels_up--; + return (Node *) result; + } + return expression_tree_mutator(node, ChangeVarAttnos_mutator, + (void *) context); + } + + Node * + ChangeVarAttnos(Node *node, int rt_index, int natt, AttrNumber *new_attno, + List *view_tlist, List *view_colnames, int sublevels_up) + { + ChangeVarAttnos_context context; + + context.rt_index = rt_index; + context.natt = natt; + context.new_attno = new_attno; + context.view_tlist = view_tlist; + context.view_colnames = view_colnames; + context.sublevels_up = sublevels_up; + + /* + * Must be prepared to start with a Query or a bare expression tree; if + * it's a Query, we don't want to increment sublevels_up. + */ + return query_or_expression_tree_mutator(node, ChangeVarAttnos_mutator, + (void *) &context, 0); + } diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h new file mode 100644 index 6f57b37..560305e *** a/src/include/rewrite/rewriteManip.h --- b/src/include/rewrite/rewriteManip.h *************** extern Node *ResolveNew(Node *node, int *** 75,78 **** --- 75,85 ---- List *targetlist, int event, int update_varno, bool *outer_hasSubLinks); + extern Bitmapset *adjust_col_privs(Bitmapset *cols, int natt, + AttrNumber *new_attno); + + extern Node *ChangeVarAttnos(Node *node, int rt_index, + int natt, AttrNumber *new_attno, + List *view_tlist, List *view_colnames, int sublevels_up); + #endif /* REWRITEMANIP_H */