diff options
Diffstat (limited to 'src/backend/rewrite/rewriteHandler.c')
-rw-r--r-- | src/backend/rewrite/rewriteHandler.c | 205 |
1 files changed, 108 insertions, 97 deletions
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 1e3efbbd36f..ea56ff79c86 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -395,36 +395,43 @@ rewriteRuleAction(Query *parsetree, * Generate expanded rtable consisting of main parsetree's rtable plus * rule action's rtable; this becomes the complete rtable for the rule * action. Some of the entries may be unused after we finish rewriting, - * but we leave them all in place for two reasons: + * but we leave them all in place to avoid having to adjust the query's + * varnos. RT entries that are not referenced in the completed jointree + * will be ignored by the planner, so they do not affect query semantics. * - * We'd have a much harder job to adjust the query's varnos if we - * selectively removed RT entries. + * Also merge RTEPermissionInfo lists to ensure that all permissions are + * checked correctly. * * If the rule is INSTEAD, then the original query won't be executed at - * all, and so its rtable must be preserved so that the executor will do - * the correct permissions checks on it. + * all, and so its rteperminfos must be preserved so that the executor + * will do the correct permissions checks on the relations referenced in + * it. This allows us to check that the caller has, say, insert-permission + * on a view, when the view is not semantically referenced at all in the + * resulting query. * - * RT entries that are not referenced in the completed jointree will be - * ignored by the planner, so they do not affect query semantics. But any - * permissions checks specified in them will be applied during executor - * startup (see ExecCheckRTEPerms()). This allows us to check that the - * caller has, say, insert-permission on a view, when the view is not - * semantically referenced at all in the resulting query. + * When a rule is not INSTEAD, the permissions checks done using the + * copied entries will be redundant with those done during execution of + * the original query, but we don't bother to treat that case differently. * - * When a rule is not INSTEAD, the permissions checks done on its copied - * RT entries will be redundant with those done during execution of the - * original query, but we don't bother to treat that case differently. - * - * NOTE: because planner will destructively alter rtable, we must ensure - * that rule action's rtable is separate and shares no substructure with - * the main rtable. Hence do a deep copy here. - * - * Note also that RewriteQuery() relies on the fact that RT entries from - * the original query appear at the start of the expanded rtable, so - * beware of changing this. + * NOTE: because planner will destructively alter rtable and rteperminfos, + * we must ensure that rule action's lists are separate and shares no + * substructure with the main query's lists. Hence do a deep copy here + * for both. */ - sub_action->rtable = list_concat(copyObject(parsetree->rtable), - sub_action->rtable); + { + List *rtable_tail = sub_action->rtable; + List *perminfos_tail = sub_action->rteperminfos; + + /* + * RewriteQuery relies on the fact that RT entries from the original + * query appear at the start of the expanded rtable, so we put the + * action's original table at the end of the list. + */ + sub_action->rtable = copyObject(parsetree->rtable); + sub_action->rteperminfos = copyObject(parsetree->rteperminfos); + CombineRangeTables(&sub_action->rtable, &sub_action->rteperminfos, + rtable_tail, perminfos_tail); + } /* * There could have been some SubLinks in parsetree's rtable, in which @@ -1628,10 +1635,13 @@ rewriteValuesRTEToNulls(Query *parsetree, RangeTblEntry *rte) /* * Record in target_rte->extraUpdatedCols the indexes of any generated columns - * that depend on any columns mentioned in target_rte->updatedCols. + * columns that depend on any columns mentioned in + * target_perminfo->updatedCols. */ void -fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation) +fill_extraUpdatedCols(RangeTblEntry *target_rte, + RTEPermissionInfo *target_perminfo, + Relation target_relation) { TupleDesc tupdesc = RelationGetDescr(target_relation); TupleConstr *constr = tupdesc->constr; @@ -1654,7 +1664,7 @@ fill_extraUpdatedCols(RangeTblEntry *target_rte, Relation target_relation) expr = stringToNode(defval->adbin); pull_varattnos(expr, 1, &attrs_used); - if (bms_overlap(target_rte->updatedCols, attrs_used)) + if (bms_overlap(target_perminfo->updatedCols, attrs_used)) target_rte->extraUpdatedCols = bms_add_member(target_rte->extraUpdatedCols, defval->adnum - FirstLowInvalidHeapAttributeNumber); @@ -1747,6 +1757,8 @@ ApplyRetrieveRule(Query *parsetree, Query *rule_action; RangeTblEntry *rte, *subrte; + RTEPermissionInfo *perminfo, + *sub_perminfo; RowMarkClause *rc; if (list_length(rule->actions) != 1) @@ -1788,18 +1800,6 @@ ApplyRetrieveRule(Query *parsetree, parsetree->resultRelation = list_length(parsetree->rtable); /* - * 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->insertedCols = NULL; - rte->updatedCols = NULL; - rte->extraUpdatedCols = NULL; - - /* * For the most part, Vars referencing the view should remain as * they are, meaning that they implicitly represent OLD values. * But in the RETURNING list if any, we want such Vars to @@ -1862,12 +1862,6 @@ ApplyRetrieveRule(Query *parsetree, /* * Recursively expand any view references inside the view. - * - * Note: this must happen after markQueryForLocking. That way, any UPDATE - * permission bits needed for sub-views are initially applied to their - * RTE_RELATION RTEs by markQueryForLocking, and then transferred to their - * OLD rangetable entries by the action below (in a recursive call of this - * routine). */ rule_action = fireRIRrules(rule_action, activeRIRs); @@ -1876,6 +1870,7 @@ ApplyRetrieveRule(Query *parsetree, * original RTE to a subquery RTE. */ rte = rt_fetch(rt_index, parsetree->rtable); + perminfo = getRTEPermissionInfo(parsetree->rteperminfos, rte); rte->rtekind = RTE_SUBQUERY; rte->subquery = rule_action; @@ -1885,6 +1880,7 @@ ApplyRetrieveRule(Query *parsetree, rte->relkind = 0; rte->rellockmode = 0; rte->tablesample = NULL; + rte->perminfoindex = 0; /* no permission checking for this RTE */ rte->inh = false; /* must not be set for a subquery */ /* @@ -1893,19 +1889,12 @@ ApplyRetrieveRule(Query *parsetree, */ subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable); Assert(subrte->relid == relation->rd_id); - subrte->requiredPerms = rte->requiredPerms; - subrte->checkAsUser = rte->checkAsUser; - subrte->selectedCols = rte->selectedCols; - subrte->insertedCols = rte->insertedCols; - subrte->updatedCols = rte->updatedCols; - subrte->extraUpdatedCols = rte->extraUpdatedCols; - - rte->requiredPerms = 0; /* no permission check on subquery itself */ - rte->checkAsUser = InvalidOid; - rte->selectedCols = NULL; - rte->insertedCols = NULL; - rte->updatedCols = NULL; - rte->extraUpdatedCols = NULL; + sub_perminfo = getRTEPermissionInfo(rule_action->rteperminfos, subrte); + sub_perminfo->requiredPerms = perminfo->requiredPerms; + sub_perminfo->checkAsUser = perminfo->checkAsUser; + sub_perminfo->selectedCols = perminfo->selectedCols; + sub_perminfo->insertedCols = perminfo->insertedCols; + sub_perminfo->updatedCols = perminfo->updatedCols; return parsetree; } @@ -1935,8 +1924,12 @@ markQueryForLocking(Query *qry, Node *jtnode, if (rte->rtekind == RTE_RELATION) { + RTEPermissionInfo *perminfo; + applyLockingClause(qry, rti, strength, waitPolicy, pushedDown); - rte->requiredPerms |= ACL_SELECT_FOR_UPDATE; + + perminfo = getRTEPermissionInfo(qry->rteperminfos, rte); + perminfo->requiredPerms |= ACL_SELECT_FOR_UPDATE; } else if (rte->rtekind == RTE_SUBQUERY) { @@ -3077,6 +3070,9 @@ rewriteTargetView(Query *parsetree, Relation view) RangeTblEntry *base_rte; RangeTblEntry *view_rte; RangeTblEntry *new_rte; + RTEPermissionInfo *base_perminfo; + RTEPermissionInfo *view_perminfo; + RTEPermissionInfo *new_perminfo; Relation base_rel; List *view_targetlist; ListCell *lc; @@ -3213,6 +3209,7 @@ rewriteTargetView(Query *parsetree, Relation view) base_rt_index = rtr->rtindex; base_rte = rt_fetch(base_rt_index, viewquery->rtable); Assert(base_rte->rtekind == RTE_RELATION); + base_perminfo = getRTEPermissionInfo(viewquery->rteperminfos, base_rte); /* * Up to now, the base relation hasn't been touched at all in our query. @@ -3284,57 +3281,69 @@ rewriteTargetView(Query *parsetree, Relation view) 0); /* - * If the view has "security_invoker" set, mark the new target RTE for the - * permissions checks that we want to enforce against the query caller. - * Otherwise we want to enforce them against the view owner. + * If the view has "security_invoker" set, mark the new target relation + * for the permissions checks that we want to enforce against the query + * caller. Otherwise we want to enforce them against the view owner. * * 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. + * ACL_SELECT bit that is presumably in new_perminfo->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. + * Note: the original view's RTEPermissionInfo remains in the query's + * rteperminfos so that the executor still performs appropriate + * permissions checks for the query caller's use of the view. */ + view_perminfo = getRTEPermissionInfo(parsetree->rteperminfos, view_rte); + + /* + * Disregard the perminfo in viewquery->rteperminfos that the base_rte + * would currently be pointing at, because we'd like it to point now to a + * new one that will be filled below. Must set perminfoindex to 0 to not + * trip over the Assert in addRTEPermissionInfo(). + */ + new_rte->perminfoindex = 0; + new_perminfo = addRTEPermissionInfo(&parsetree->rteperminfos, new_rte); if (RelationHasSecurityInvoker(view)) - new_rte->checkAsUser = InvalidOid; + new_perminfo->checkAsUser = InvalidOid; else - new_rte->checkAsUser = view->rd_rel->relowner; - - new_rte->requiredPerms = view_rte->requiredPerms; + new_perminfo->checkAsUser = view->rd_rel->relowner; + new_perminfo->requiredPerms = view_perminfo->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 insertedCols/updatedCols is empty. We set insertedCols and - * updatedCols 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.) + * Initially, new_perminfo (base_perminfo) contains selectedCols + * permission check bits for all base-rel columns referenced by the view, + * but since the view is a SELECT query its insertedCols/updatedCols is + * empty. We set insertedCols and updatedCols 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_perminfo->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->insertedCols) && - bms_is_empty(new_rte->updatedCols)); + Assert(bms_is_empty(new_perminfo->insertedCols) && + bms_is_empty(new_perminfo->updatedCols)); + + new_perminfo->selectedCols = base_perminfo->selectedCols; - new_rte->insertedCols = adjust_view_column_set(view_rte->insertedCols, - view_targetlist); + new_perminfo->insertedCols = + adjust_view_column_set(view_perminfo->insertedCols, view_targetlist); - new_rte->updatedCols = adjust_view_column_set(view_rte->updatedCols, - view_targetlist); + new_perminfo->updatedCols = + adjust_view_column_set(view_perminfo->updatedCols, view_targetlist); /* * Move any security barrier quals from the view RTE onto the new target @@ -3438,7 +3447,7 @@ rewriteTargetView(Query *parsetree, Relation view) * from the view, hence we need a new column alias list). This should * match transformOnConflictClause. In particular, note that the * relkind is set to composite to signal that we're not dealing with - * an actual relation, and no permissions checks are wanted. + * an actual relation. */ old_exclRelIndex = parsetree->onConflict->exclRelIndex; @@ -3449,8 +3458,8 @@ rewriteTargetView(Query *parsetree, Relation view) false, false); new_exclRte = new_exclNSItem->p_rte; new_exclRte->relkind = RELKIND_COMPOSITE_TYPE; - new_exclRte->requiredPerms = 0; - /* other permissions fields in new_exclRte are already empty */ + /* Ignore the RTEPermissionInfo that would've been added. */ + new_exclRte->perminfoindex = 0; parsetree->rtable = lappend(parsetree->rtable, new_exclRte); new_exclRelIndex = parsetree->onConflict->exclRelIndex = @@ -3728,6 +3737,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length) { int result_relation; RangeTblEntry *rt_entry; + RTEPermissionInfo *rt_perminfo; Relation rt_entry_relation; List *locks; int product_orig_rt_length; @@ -3740,6 +3750,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length) Assert(result_relation != 0); rt_entry = rt_fetch(result_relation, parsetree->rtable); Assert(rt_entry->rtekind == RTE_RELATION); + rt_perminfo = getRTEPermissionInfo(parsetree->rteperminfos, rt_entry); /* * We can use NoLock here since either the parser or @@ -3833,7 +3844,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length) NULL, 0, NULL); /* Also populate extraUpdatedCols (for generated columns) */ - fill_extraUpdatedCols(rt_entry, rt_entry_relation); + fill_extraUpdatedCols(rt_entry, rt_perminfo, rt_entry_relation); } else if (event == CMD_MERGE) { |