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 */