auto-update-views.patch
application/octet-stream
Filename: auto-update-views.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| src/backend/nodes/copyfuncs.c | 0 | 0 |
| src/backend/nodes/equalfuncs.c | 0 | 0 |
| src/backend/nodes/nodeFuncs.c | 0 | 0 |
| src/backend/nodes/outfuncs.c | 0 | 0 |
| src/backend/nodes/readfuncs.c | 0 | 0 |
| src/backend/optimizer/plan/setrefs.c | 0 | 0 |
| src/backend/rewrite/rewriteHandler.c | 0 | 0 |
| src/backend/rewrite/rewriteManip.c | 0 | 0 |
| src/include/catalog/catversion.h | 0 | 0 |
| src/include/nodes/parsenodes.h | 0 | 0 |
| src/include/rewrite/rewriteManip.h | 0 | 0 |
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index 71d5323..603ed16
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyRangeTblEntry(const RangeTblEntry *
*** 1980,1985 ****
--- 1980,1986 ----
COPY_SCALAR_FIELD(checkAsUser);
COPY_BITMAPSET_FIELD(selectedCols);
COPY_BITMAPSET_FIELD(modifiedCols);
+ COPY_NODE_FIELD(securityQuals);
return newnode;
}
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
new file mode 100644
index d690ca7..3d1ba98
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalRangeTblEntry(const RangeTblEntry
*** 2296,2301 ****
--- 2296,2302 ----
COMPARE_SCALAR_FIELD(checkAsUser);
COMPARE_BITMAPSET_FIELD(selectedCols);
COMPARE_BITMAPSET_FIELD(modifiedCols);
+ COMPARE_NODE_FIELD(securityQuals);
return true;
}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
new file mode 100644
index b130902..4d461b2
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
*************** range_table_walker(List *rtable,
*** 1942,1947 ****
--- 1942,1950 ----
return true;
break;
}
+
+ if (walker(rte->securityQuals, context))
+ return true;
}
return false;
}
*************** range_table_mutator(List *rtable,
*** 2653,2658 ****
--- 2656,2662 ----
MUTATE(newrte->values_lists, rte->values_lists, List *);
break;
}
+ MUTATE(newrte->securityQuals, rte->securityQuals, List *);
newrt = lappend(newrt, newrte);
}
return newrt;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index 9dee041..b98c2c9
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outRangeTblEntry(StringInfo str, const
*** 2369,2374 ****
--- 2369,2375 ----
WRITE_OID_FIELD(checkAsUser);
WRITE_BITMAPSET_FIELD(selectedCols);
WRITE_BITMAPSET_FIELD(modifiedCols);
+ WRITE_NODE_FIELD(securityQuals);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
new file mode 100644
index 1eb7582..bb4009c
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readRangeTblEntry(void)
*** 1229,1234 ****
--- 1229,1235 ----
READ_OID_FIELD(checkAsUser);
READ_BITMAPSET_FIELD(selectedCols);
READ_BITMAPSET_FIELD(modifiedCols);
+ READ_NODE_FIELD(securityQuals);
READ_DONE();
}
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
new file mode 100644
index ccd69fc..3cb0a65
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
*************** set_plan_references(PlannerInfo *root, P
*** 224,229 ****
--- 224,230 ----
newrte->ctecoltypes = NIL;
newrte->ctecoltypmods = NIL;
newrte->ctecolcollations = NIL;
+ newrte->securityQuals = NIL;
glob->finalrtable = lappend(glob->finalrtable, newrte);
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
new file mode 100644
index 8f75948..b087715
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 14,19 ****
--- 14,20 ----
#include "postgres.h"
#include "access/sysattr.h"
+ #include "catalog/heap.h"
#include "catalog/pg_type.h"
#include "commands/trigger.h"
#include "nodes/makefuncs.h"
*************** static List *matchLocks(CmdType event, R
*** 60,65 ****
--- 61,67 ----
int varno, Query *parsetree);
static Query *fireRIRrules(Query *parsetree, List *activeRIRs,
bool forUpdatePushedDown);
+ static Query *rewriteSecurityQuals(Query *parsetree);
/*
*************** fireRules(Query *parsetree,
*** 1829,1834 ****
--- 1831,2652 ----
/*
+ * 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 = (Var *) tle->expr;
+
+ if (!IsA(var, Var))
+ return "Views with columns that are not simple references to columns in the base relation are not updatable";
+
+ 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, this function will return NULL
+ * and the view modification is handled later by fireRIRrules.
+ *
+ * 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)
+ {
+ TriggerDesc *trigDesc = view->trigdesc;
+ const char *auto_update_detail;
+ Query *viewquery;
+ RangeTblRef *rtr;
+ int base_rt_index;
+ RangeTblEntry *base_rte;
+ List *view_targetlist;
+ bool same_cols;
+ ListCell *cell;
+ int rt_index;
+ List *new_rtable;
+ RangeTblEntry *view_rte;
+
+ /* 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 is simply updatable, which means that it 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_rt_index = rtr->rtindex;
+ base_rte = rt_fetch(base_rt_index, viewquery->rtable);
+ Assert(base_rte->rtekind == RTE_RELATION);
+
+ /*
+ * Make a copy of the view's targetlist, adjusting its Vars to allow it
+ * to be plugged into the main query.
+ */
+ view_targetlist = copyObject(viewquery->targetList);
+
+ ChangeVarNodes((Node *) view_targetlist,
+ base_rt_index, parsetree->resultRelation, 0);
+
+ /**
+ * Test for the simple case where the view has all the same columns as the
+ * base relation, in the same order.
+ */
+ same_cols = true;
+ foreach(cell, view_targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(cell);
+ Var *var = (Var *) tle->expr;
+
+ if (var->varattno != tle->resno)
+ {
+ /* view columns are in a different order */
+ same_cols = false;
+ break;
+ }
+ }
+
+ /*
+ * Replace the old view target RTE with a new RTE referring to the base
+ * relation. Since we are not changing the result relation index, there
+ * is no need to update any varnos in the original query.
+ *
+ * Note that we need to keep the original view RTE, even though it will
+ * not be used directly in the query, so that the correct permissions
+ * checks are still done against it. This is appended to the end of the
+ * rangetable.
+ */
+ 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
+ * permissions 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 (!same_cols)
+ {
+ newrte->selectedCols = adjust_column_set(newrte->selectedCols,
+ view_targetlist);
+ newrte->modifiedCols = adjust_column_set(newrte->modifiedCols,
+ view_targetlist);
+ }
+
+ /*
+ * Move any security barrier quals from the view RTE onto the new
+ * base relation RTE, since the original view RTE will not be
+ * referenced in the final query.
+ */
+ newrte->securityQuals = rte->securityQuals;
+ rte->securityQuals = NIL;
+
+ /*
+ * 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
+ * TLE 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 don't match the base relation's columns, we need
+ * to update the targetlist to point to the columns in the base relation,
+ * and similarly update any Vars in the query that refer to the result
+ * relation.
+ *
+ * Note that this destroys the resno ordering of the targetlist, but that
+ * will be restored when we recurse through rewriteQuery which will invoke
+ * rewriteTargetListIU on this updated targetlist.
+ */
+ if (!same_cols)
+ {
+ /* Update the main query's targetlist */
+ foreach(cell, parsetree->targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(cell);
+ TargetEntry *view_tle;
+
+ /* Ignore system columns */
+ if (tle->resjunk || tle->resno <= 0)
+ continue;
+
+ view_tle = get_tle_by_resno(view_targetlist, tle->resno);
+ if (view_tle == NULL)
+ elog(ERROR, "View column %d not found", tle->resno);
+
+ tle->resno = ((Var *) view_tle->expr)->varattno;
+ }
+
+ /* Update any Vars referring to the result relation. */
+ parsetree =
+ (Query *) ResolveNew((Node *) parsetree,
+ parsetree->resultRelation,
+ 0,
+ view_rte,
+ view_targetlist,
+ parsetree->commandType,
+ parsetree->resultRelation,
+ &parsetree->hasSubLinks);
+ }
+
+ /*
+ * For UPDATE/DELETE, deal with any quals from the view's jointree. We
+ * know that there is just one relation in the view, so any references to
+ * that relation in the view's quals need to be updated to refer to the
+ * corresponding relation in the main query (the result relation).
+ *
+ * If the view is not a security barrier then we simply add the view quals
+ * to the main query. However, if the view is a security barrier then its
+ * quals must take precedence over any of the user's quals. Here we just
+ * make a note of such quals on the relevant RTE so that we can deal with
+ * them later in rewriteSecurityQuals, which will turn the RTE into a
+ * subquery.
+ *
+ * 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 &&
+ viewquery->jointree->quals != NULL)
+ {
+ Node *viewqual = copyObject(viewquery->jointree->quals);
+
+ ChangeVarNodes(viewqual, base_rt_index, parsetree->resultRelation, 0);
+
+ if (RelationIsSecurityView(view))
+ {
+ base_rte = rt_fetch(parsetree->resultRelation, parsetree->rtable);
+ base_rte->securityQuals = lcons(viewqual,
+ base_rte->securityQuals);
+ }
+ else
+ AddQual(parsetree, (Node *) viewqual);
+ }
+
+ return parsetree;
+ }
+
+
+ /*
+ * rewriteSecurityQual -
+ * rewrite the specified security barrier qual on a query RTE, turning the
+ * RTE into a subquery.
+ */
+ static Query *
+ rewriteSecurityQual(Query *parsetree, int rt_index,
+ bool is_result_relation, Node *qual)
+ {
+ RangeTblEntry *rte;
+ List *targetlist = NIL;
+ List *inverse_targetlist = NIL;
+ List *colnames = NIL;
+ Relation relation;
+ AttrNumber attno;
+ Var *var;
+ TargetEntry *tle;
+ Query *subquery;
+ RangeTblEntry *subrte;
+ RangeTblRef *subrtr;
+ ListCell *cell;
+
+ rte = rt_fetch(rt_index, parsetree->rtable);
+
+ /*
+ * There are 2 possible cases:
+ *
+ * 1). A relation RTE, which we turn into a subquery RTE containing all
+ * referenced columns.
+ *
+ * 2). A subquery RTE (either from a prior call to this function or from
+ * an expanded view). In this case we build a new subquery on top of it
+ * to isolate this security barrier qual from any other quals.
+ */
+ switch (rte->rtekind)
+ {
+ case RTE_RELATION:
+ /*
+ * Build the subquery targetlist from the columns used from the
+ * underlying table and an inverse targetlist to map varattnos in
+ * the main query onto the new subquery columns.
+ */
+ relation = heap_open(rte->relid, NoLock);
+
+ for (attno = FirstLowInvalidHeapAttributeNumber;
+ attno <= relation->rd_att->natts; attno++)
+ {
+ Form_pg_attribute att_tup;
+ char *attname;
+
+ /* Ignore columns that aren't used */
+ if (!attribute_used((Node *) parsetree, rt_index, attno, 0))
+ continue;
+
+ if (attno == InvalidAttrNumber)
+ {
+ /* whole-row attribute */
+ var = makeWholeRowVar(rte, 1, 0, false);
+ attname = "wholerow";
+ }
+ else
+ {
+ /* regular table column or system attribute */
+ if (attno >= 1)
+ att_tup = relation->rd_att->attrs[attno - 1];
+ else
+ att_tup = SystemAttributeDefinition(attno,
+ relation->rd_rel->relhasoids);
+
+ var = makeVar(1,
+ attno,
+ att_tup->atttypid,
+ att_tup->atttypmod,
+ att_tup->attcollation,
+ 0);
+ attname = NameStr(att_tup->attname);
+ }
+
+ /* target entry for new subquery targetlist */
+ tle = makeTargetEntry((Expr *) var,
+ list_length(targetlist) + 1,
+ pstrdup(attname),
+ false);
+ targetlist = lappend(targetlist, tle);
+
+ /* inverse target entry for rewriting the main query */
+ var = copyObject(var);
+ var->varattno = list_length(targetlist);
+ tle = makeTargetEntry((Expr *) var,
+ attno,
+ pstrdup(attname),
+ false);
+ inverse_targetlist = lappend(inverse_targetlist, tle);
+
+ colnames = lappend(colnames, makeString(pstrdup(attname)));
+ }
+ heap_close(relation, NoLock);
+
+ /*
+ * Turn the main relation RTE into a security barrier subquery
+ * RTE, moving all permissions checks and rowMarks down into the
+ * subquery.
+ */
+ subquery = makeNode(Query);
+ subquery->commandType = CMD_SELECT;
+ subquery->querySource = QSRC_INSTEAD_RULE;
+ subquery->targetList = targetlist;
+
+ subrte = copyObject(rte);
+ subrte->inFromCl = true;
+ subrte->securityQuals = NIL;
+ subquery->rtable = list_make1(subrte);
+
+ subrtr = makeNode(RangeTblRef);
+ subrtr->rtindex = 1;
+ subquery->jointree = makeFromExpr(list_make1(subrtr), qual);
+ subquery->hasSubLinks = checkExprHasSubLink(qual);
+
+ foreach(cell, parsetree->rowMarks)
+ {
+ RowMarkClause *rc = (RowMarkClause *) lfirst(cell);
+ if (rc->rti == rt_index)
+ {
+ parsetree->rowMarks = list_delete(parsetree->rowMarks,
+ rc);
+ rc->rti = 1;
+ subquery->rowMarks = list_make1(rc);
+ subquery->hasForUpdate = rc->forUpdate;
+ break;
+ }
+ }
+
+ /*
+ * If this RTE was the result relation, then we need to lock the
+ * rows coming from it. Note that by the time we get here, this
+ * RTE will no longer be the result relation, so we have to rely
+ * on the flag passed in.
+ */
+ if (is_result_relation)
+ applyLockingClause(subquery, 1, true, false, true);
+
+ rte->rtekind = RTE_SUBQUERY;
+ rte->relid = InvalidOid;
+ rte->subquery = subquery;
+ rte->security_barrier = true;
+ rte->eref = makeAlias(rte->eref->aliasname, colnames);
+ rte->inh = false; /* must not be set for a subquery */
+
+ /* the permissions checks have now been move down */
+ rte->requiredPerms = 0;
+ rte->checkAsUser = InvalidOid;
+ rte->selectedCols = NULL;
+ rte->modifiedCols = NULL;
+
+ /*
+ * Update any varattnos in the main query that refer to this RTE,
+ * using the entries from the inverse targetlist.
+ */
+ return (Query *) ResolveNew((Node *) parsetree,
+ rt_index,
+ 0,
+ rte,
+ inverse_targetlist,
+ parsetree->commandType,
+ rt_index,
+ &parsetree->hasSubLinks);
+
+ case RTE_SUBQUERY:
+ /*
+ * Build a new subquery that includes all the same columns as the
+ * original subquery.
+ */
+ foreach(cell, rte->subquery->targetList)
+ {
+ tle = (TargetEntry *) lfirst(cell);
+ var = makeVarFromTargetEntry(1, tle);
+
+ tle = makeTargetEntry((Expr *) var,
+ list_length(targetlist) + 1,
+ pstrdup(tle->resname),
+ tle->resjunk);
+ targetlist = lappend(targetlist, tle);
+ }
+
+ subquery = makeNode(Query);
+ subquery->commandType = CMD_SELECT;
+ subquery->querySource = QSRC_INSTEAD_RULE;
+ subquery->targetList = targetlist;
+
+ subrte = makeNode(RangeTblEntry);
+ subrte->rtekind = RTE_SUBQUERY;
+ subrte->subquery = rte->subquery;
+ subrte->security_barrier = rte->security_barrier;
+ subrte->eref = copyObject(rte->eref);
+ subrte->inFromCl = true;
+ subquery->rtable = list_make1(subrte);
+
+ subrtr = makeNode(RangeTblRef);
+ subrtr->rtindex = 1;
+ subquery->jointree = makeFromExpr(list_make1(subrtr), qual);
+ subquery->hasSubLinks = checkExprHasSubLink(qual);
+
+ rte->subquery = subquery;
+ rte->security_barrier = true;
+
+ return parsetree;
+
+ default:
+ elog(ERROR, "invalid range table entry for security barrier qual");
+ }
+
+ return NULL;
+ }
+
+
+ /*
+ * rewriteSecurityQualsOnSubLink -
+ * Apply rewriteSecurityQuals() to each SubLink (subselect in expression)
+ * found in the given tree.
+ *
+ * NOTE: although this has the form of a walker, we cheat and modify the
+ * SubLink nodes in-place. This is safe because we are not descending into
+ * subqueries, so no parts of the tree we are modifying are being traversed.
+ *
+ * Each SubLink subselect is replaced with a possibly-rewritten subquery.
+ */
+ static bool
+ rewriteSecurityQualsOnSubLink(Node *node, void *ctx)
+ {
+ if (node == NULL)
+ return false;
+ if (IsA(node, SubLink))
+ {
+ SubLink *sub = (SubLink *) node;
+
+ sub->subselect = (Node *)
+ rewriteSecurityQuals((Query *) sub->subselect);
+ /* Fall through to process lefthand args of SubLink */
+ }
+
+ /*
+ * Do NOT recurse into Query nodes, because rewriteSecurityQuals already
+ * processed all subqueries in the rtable and cteList.
+ */
+ return expression_tree_walker(node, rewriteSecurityQualsOnSubLink, ctx);
+ }
+
+
+ /*
+ * rewriteSecurityQuals -
+ * rewrites any security barrier quals on RTEs in the query, turning them
+ * into subqueries to allow the planner to enforce them before any user
+ * quals where necessary. Currently such security barrier quals can only
+ * have come from automatically updatable security barrier views.
+ *
+ * We do this at the end of the rewriting process (after any SELECT rules
+ * have been applied) so that the new security barrier subqueries wrap any
+ * remaining views after they are expanded.
+ *
+ * Any given RTE may have multiple security barrier quals in a list, from
+ * which we create a set of nested subqueries to isolate each security barrier
+ * from the others, providing protection against malicious user security
+ * barriers. The first item in the list represents the innermost subquery.
+ */
+ static Query *
+ rewriteSecurityQuals(Query *parsetree)
+ {
+ ListCell *l;
+ int rt_index;
+
+ /*
+ * Process each RTE in the rtable list. Security barrier quals are
+ * initially only added to the result relation, but subsequent rules
+ * may change that, so they may be anywhere.
+ *
+ * Note that this is deliberately not a foreach loop, since the whole
+ * parsetree may be mutated each time through the loop.
+ */
+ rt_index = 0;
+ while (rt_index < list_length(parsetree->rtable))
+ {
+ RangeTblEntry *rte;
+ bool is_result_relation;
+ int qual_idx;
+
+ ++rt_index;
+ rte = rt_fetch(rt_index, parsetree->rtable);
+
+ if (rte->securityQuals == NIL)
+ continue;
+
+ /*
+ * Ignore any RTEs that aren't used in the query (such RTEs may be
+ * present for permissions checks).
+ */
+ if (rt_index != parsetree->resultRelation &&
+ !rangeTableEntry_used((Node *) parsetree, rt_index, 0))
+ continue;
+
+ /*
+ * Recursively process any security barrier quals in subquery RTEs
+ * before processing any at this query level.
+ */
+ if (rte->rtekind == RTE_SUBQUERY)
+ rte->subquery = rewriteSecurityQuals(rte->subquery);
+
+ /*
+ * If this RTE is the target then we need to make a copy of it before
+ * expanding it. The unexpanded copy will become the new target, and
+ * the expanded RTE will be the source of rows to update/delete.
+ */
+ is_result_relation = rt_index == parsetree->resultRelation;
+ if (is_result_relation)
+ {
+ RangeTblEntry *newrte = copyObject(rte);
+ parsetree->rtable = lappend(parsetree->rtable, newrte);
+ parsetree->resultRelation = list_length(parsetree->rtable);
+
+ /*
+ * Wipe out any copied security quals on the new target to prevent
+ * infinite recursion.
+ */
+ newrte->securityQuals = NIL;
+
+ /*
+ * There's no need to do permissions checks twice, so wipe out the
+ * permissions info for the original RTE (we prefer to keep the
+ * bits set on the result RTE).
+ */
+ rte->requiredPerms = 0;
+ rte->checkAsUser = InvalidOid;
+ rte->selectedCols = NULL;
+ rte->modifiedCols = NULL;
+
+ /*
+ * For the most part, Vars referencing the original relation
+ * should remain as as they are, meaning that they implicitly
+ * represent OLD values. But in the RETURNING list if any, we
+ * want such Vars to represent NEW values, so change them to
+ * reference the new RTE.
+ *
+ * Since ChangeVarNodes scribbles on the tree in-place, copy the
+ * RETURNING list first for safety.
+ */
+ parsetree->returningList = copyObject(parsetree->returningList);
+ ChangeVarNodes((Node *) parsetree->returningList, rt_index,
+ parsetree->resultRelation, 0);
+ }
+
+ /*
+ * Process each security qual in turn, starting with the innermost
+ * one and working outwards.
+ *
+ * Note that we can't use a foreach loop here because the whole
+ * parsetree may be mutated each time through the loop. For the same
+ * reason we must re-fetch the RTE each time.
+ */
+ qual_idx = 0;
+ while (qual_idx < list_length(rte->securityQuals))
+ {
+ Node *qual = (Node *) list_nth(rte->securityQuals, qual_idx);
+
+ parsetree = rewriteSecurityQual(parsetree, rt_index,
+ is_result_relation, qual);
+
+ /* re-fetch the RTE in case it has been re-written */
+ rte = rt_fetch(rt_index, parsetree->rtable);
+
+ qual_idx++;
+ }
+ rte->securityQuals = NIL;
+ }
+
+ /* Recurse into subqueries in WITH */
+ foreach(l, parsetree->cteList)
+ {
+ CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
+
+ cte->ctequery = (Node *)
+ rewriteSecurityQuals((Query *) cte->ctequery);
+ }
+
+ /*
+ * Recurse into sublink subqueries too, without descending into the rtable
+ * or the cteList, which we have already processed.
+ *
+ * XXX: Can such SubLink subselects ever actually have security quals?
+ */
+ if (parsetree->hasSubLinks)
+ query_tree_walker(parsetree, rewriteSecurityQualsOnSubLink, NULL,
+ QTW_IGNORE_RC_SUBQUERIES);
+
+ return parsetree;
+ }
+
+
+ /*
* RewriteQuery -
* rewrites the query and apply the rules again on the queries rewritten
*
*************** RewriteQuery(Query *parsetree, List *rew
*** 1842,1847 ****
--- 2660,2666 ----
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,
--- 2812,2817 ----
*************** RewriteQuery(Query *parsetree, List *rew
*** 2004,2009 ****
--- 2820,2850 ----
&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
--- 2881,2886 ----
*************** QueryRewrite(Query *parsetree)
*** 2181,2187 ****
/*
* Step 2
*
! * Apply all the RIR rules on each query
*
* This is also a handy place to mark each query with the original queryId
*/
--- 3021,3028 ----
/*
* Step 2
*
! * Apply all the RIR rules on each query, and then expand any security
! * quals that apply to RTEs in the query.
*
* This is also a handy place to mark each query with the original queryId
*/
*************** QueryRewrite(Query *parsetree)
*** 2191,2196 ****
--- 3032,3038 ----
Query *query = (Query *) lfirst(l);
query = fireRIRrules(query, NIL, false);
+ query = rewriteSecurityQuals(query);
query->queryId = input_query_id;
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
new file mode 100644
index 5fcf274..7fe2be5
*** 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,
*** 1440,1442 ****
--- 1441,1483 ----
(void *) &context,
outer_hasSubLinks);
}
+
+
+ /*
+ * adjust_column_set - replace columns in a set with entries from a targetlist
+ *
+ * Non-system columns in the set are replaced by the entry with matching resno
+ * from targetlist, and system columns are left unchanged. 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_column_set(Bitmapset *cols, List *targetlist)
+ {
+ Bitmapset *tmpcols = bms_copy(cols);
+ Bitmapset *result = NULL;
+ AttrNumber col;
+
+ while ((col = bms_first_member(tmpcols)) >= 0)
+ {
+ AttrNumber attno = col + FirstLowInvalidHeapAttributeNumber;
+
+ if (attno <= 0)
+ /* system column - leave it unchanged */
+ result = bms_add_member(result, col);
+ else if (attno <= list_length(targetlist))
+ {
+ /* non-system column - update it from the targetlist */
+ TargetEntry *tle = get_tle_by_resno(targetlist, attno);
+ if (tle != NULL && IsA(tle->expr, Var))
+ {
+ Var *var = (Var *) tle->expr;
+ result = bms_add_member(result,
+ var->varattno - FirstLowInvalidHeapAttributeNumber);
+ }
+ }
+ }
+ bms_free(tmpcols);
+
+ return result;
+ }
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
new file mode 100644
index 82b6c0c..0b4e0b5
*** a/src/include/catalog/catversion.h
--- b/src/include/catalog/catversion.h
***************
*** 53,58 ****
*/
/* yyyymmddN */
! #define CATALOG_VERSION_NO 201208071
#endif
--- 53,58 ----
*/
/* yyyymmddN */
! #define CATALOG_VERSION_NO 201208111
#endif
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
new file mode 100644
index f433166..217134e
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef struct RangeTblEntry
*** 765,770 ****
--- 765,771 ----
Oid checkAsUser; /* if valid, check access as this role */
Bitmapset *selectedCols; /* columns needing SELECT permission */
Bitmapset *modifiedCols; /* columns needing INSERT/UPDATE permission */
+ List *securityQuals; /* any security barrier quals to apply */
} RangeTblEntry;
/*
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
new file mode 100644
index e13331d..4e0b867
*** a/src/include/rewrite/rewriteManip.h
--- b/src/include/rewrite/rewriteManip.h
*************** extern Node *ResolveNew(Node *node, int
*** 74,77 ****
--- 74,79 ----
List *targetlist, int event, int update_varno,
bool *outer_hasSubLinks);
+ extern Bitmapset *adjust_column_set(Bitmapset *cols, List *targetlist);
+
#endif /* REWRITEMANIP_H */