diff options
Diffstat (limited to 'src/backend/optimizer/plan')
-rw-r--r-- | src/backend/optimizer/plan/createplan.c | 53 | ||||
-rw-r--r-- | src/backend/optimizer/plan/initsplan.c | 90 | ||||
-rw-r--r-- | src/backend/optimizer/plan/planner.c | 125 |
3 files changed, 171 insertions, 97 deletions
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index c7bcd9b84c8..c4ada214ed2 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -4500,21 +4500,32 @@ get_switched_clauses(List *clauses, Relids outerrelids) * plan node, sort the list into the order we want to check the quals * in at runtime. * + * When security barrier quals are used in the query, we may have quals with + * different security levels in the list. Quals of lower security_level + * must go before quals of higher security_level, except that we can grant + * exceptions to move up quals that are leakproof. When security level + * doesn't force the decision, we prefer to order clauses by estimated + * execution cost, cheapest first. + * * Ideally the order should be driven by a combination of execution cost and * selectivity, but it's not immediately clear how to account for both, * and given the uncertainty of the estimates the reliability of the decisions - * would be doubtful anyway. So we just order by estimated per-tuple cost, - * being careful not to change the order when (as is often the case) the - * estimates are identical. + * would be doubtful anyway. So we just order by security level then + * estimated per-tuple cost, being careful not to change the order when + * (as is often the case) the estimates are identical. * * Although this will work on either bare clauses or RestrictInfos, it's * much faster to apply it to RestrictInfos, since it can re-use cost - * information that is cached in RestrictInfos. + * information that is cached in RestrictInfos. XXX in the bare-clause + * case, we are also not able to apply security considerations. That is + * all right for the moment, because the bare-clause case doesn't occur + * anywhere that barrier quals could be present, but it would be better to + * get rid of it. * * Note: some callers pass lists that contain entries that will later be * removed; this is the easiest way to let this routine see RestrictInfos - * instead of bare clauses. It's OK because we only sort by cost, but - * a cost/selectivity combination would likely do the wrong thing. + * instead of bare clauses. This is another reason why trying to consider + * selectivity in the ordering would likely do the wrong thing. */ static List * order_qual_clauses(PlannerInfo *root, List *clauses) @@ -4523,6 +4534,7 @@ order_qual_clauses(PlannerInfo *root, List *clauses) { Node *clause; Cost cost; + Index security_level; } QualItem; int nitems = list_length(clauses); QualItem *items; @@ -4548,6 +4560,27 @@ order_qual_clauses(PlannerInfo *root, List *clauses) cost_qual_eval_node(&qcost, clause, root); items[i].clause = clause; items[i].cost = qcost.per_tuple; + if (IsA(clause, RestrictInfo)) + { + RestrictInfo *rinfo = (RestrictInfo *) clause; + + /* + * If a clause is leakproof, it doesn't have to be constrained by + * its nominal security level. If it's also reasonably cheap + * (here defined as 10X cpu_operator_cost), pretend it has + * security_level 0, which will allow it to go in front of + * more-expensive quals of lower security levels. Of course, that + * will also force it to go in front of cheaper quals of its own + * security level, which is not so great, but we can alleviate + * that risk by applying the cost limit cutoff. + */ + if (rinfo->leakproof && items[i].cost < 10 * cpu_operator_cost) + items[i].security_level = 0; + else + items[i].security_level = rinfo->security_level; + } + else + items[i].security_level = 0; i++; } @@ -4564,9 +4597,13 @@ order_qual_clauses(PlannerInfo *root, List *clauses) /* insert newitem into the already-sorted subarray */ for (j = i; j > 0; j--) { - if (newitem.cost >= items[j - 1].cost) + QualItem *olditem = &items[j - 1]; + + if (newitem.security_level > olditem->security_level || + (newitem.security_level == olditem->security_level && + newitem.cost >= olditem->cost)) break; - items[j] = items[j - 1]; + items[j] = *olditem; } items[j] = newitem; } diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 6ceb80192e1..c170e9614f6 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -51,6 +51,9 @@ static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, Relids *qualscope, Relids *inner_join_rels, List **postponed_qual_list); +static void process_security_barrier_quals(PlannerInfo *root, + int rti, Relids qualscope, + bool below_outer_join); static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root, Relids left_rels, Relids right_rels, Relids inner_join_rels, @@ -60,6 +63,7 @@ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause, bool is_deduced, bool below_outer_join, JoinType jointype, + Index security_level, Relids qualscope, Relids ojscope, Relids outerjoin_nonnullable, @@ -745,8 +749,14 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, { int varno = ((RangeTblRef *) jtnode)->rtindex; - /* No quals to deal with, just return correct result */ + /* qualscope is just the one RTE */ *qualscope = bms_make_singleton(varno); + /* Deal with any securityQuals attached to the RTE */ + if (root->qual_security_level > 0) + process_security_barrier_quals(root, + varno, + *qualscope, + below_outer_join); /* A single baserel does not create an inner join */ *inner_join_rels = NULL; joinlist = list_make1(jtnode); @@ -810,6 +820,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, if (bms_is_subset(pq->relids, *qualscope)) distribute_qual_to_rels(root, pq->qual, false, below_outer_join, JOIN_INNER, + root->qual_security_level, *qualscope, NULL, NULL, NULL, NULL); else @@ -825,6 +836,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, distribute_qual_to_rels(root, qual, false, below_outer_join, JOIN_INNER, + root->qual_security_level, *qualscope, NULL, NULL, NULL, postponed_qual_list); } @@ -1002,6 +1014,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, distribute_qual_to_rels(root, qual, false, below_outer_join, j->jointype, + root->qual_security_level, *qualscope, ojscope, nonnullable_rels, NULL, postponed_qual_list); @@ -1059,6 +1072,67 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, } /* + * process_security_barrier_quals + * Transfer security-barrier quals into relation's baserestrictinfo list. + * + * The rewriter put any relevant security-barrier conditions into the RTE's + * securityQuals field, but it's now time to copy them into the rel's + * baserestrictinfo. + * + * In inheritance cases, we only consider quals attached to the parent rel + * here; they will be valid for all children too, so it's okay to consider + * them for purposes like equivalence class creation. Quals attached to + * individual child rels will be dealt with during path creation. + */ +static void +process_security_barrier_quals(PlannerInfo *root, + int rti, Relids qualscope, + bool below_outer_join) +{ + RangeTblEntry *rte = root->simple_rte_array[rti]; + Index security_level = 0; + ListCell *lc; + + /* + * Each element of the securityQuals list has been preprocessed into an + * implicitly-ANDed list of clauses. All the clauses in a given sublist + * should get the same security level, but successive sublists get higher + * levels. + */ + foreach(lc, rte->securityQuals) + { + List *qualset = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, qualset) + { + Node *qual = (Node *) lfirst(lc2); + + /* + * We cheat to the extent of passing ojscope = qualscope rather + * than its more logical value of NULL. The only effect this has + * is to force a Var-free qual to be evaluated at the rel rather + * than being pushed up to top of tree, which we don't want. + */ + distribute_qual_to_rels(root, qual, + false, + below_outer_join, + JOIN_INNER, + security_level, + qualscope, + qualscope, + NULL, + NULL, + NULL); + } + security_level++; + } + + /* Assert that qual_security_level is higher than anything we just used */ + Assert(security_level <= root->qual_security_level); +} + +/* * make_outerjoininfo * Build a SpecialJoinInfo for the current outer join * @@ -1516,6 +1590,7 @@ compute_semijoin_info(SpecialJoinInfo *sjinfo, List *clause) * 'below_outer_join': TRUE if the qual is from a JOIN/ON that is below the * nullable side of a higher-level outer join * 'jointype': type of join the qual is from (JOIN_INNER for a WHERE clause) + * 'security_level': security_level to assign to the qual * 'qualscope': set of baserels the qual's syntactic scope covers * 'ojscope': NULL if not an outer-join qual, else the minimum set of baserels * needed to form this join @@ -1545,6 +1620,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, bool is_deduced, bool below_outer_join, JoinType jointype, + Index security_level, Relids qualscope, Relids ojscope, Relids outerjoin_nonnullable, @@ -1794,6 +1870,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, is_pushed_down, outerjoin_delayed, pseudoconstant, + security_level, relids, outerjoin_nonnullable, nullable_relids); @@ -2142,6 +2219,9 @@ distribute_restrictinfo_to_rels(PlannerInfo *root, /* Add clause to rel's restriction list */ rel->baserestrictinfo = lappend(rel->baserestrictinfo, restrictinfo); + /* Update security level info */ + rel->baserestrict_min_security = Min(rel->baserestrict_min_security, + restrictinfo->security_level); break; case BMS_MULTIPLE: @@ -2189,6 +2269,8 @@ distribute_restrictinfo_to_rels(PlannerInfo *root, * caller because this function is used after deconstruct_jointree, so we * don't have knowledge of where the clause items came from.) * + * "security_level" is the security level to assign to the new restrictinfo. + * * "both_const" indicates whether both items are known pseudo-constant; * in this case it is worth applying eval_const_expressions() in case we * can produce constant TRUE or constant FALSE. (Otherwise it's not, @@ -2209,6 +2291,7 @@ process_implied_equality(PlannerInfo *root, Expr *item2, Relids qualscope, Relids nullable_relids, + Index security_level, bool below_outer_join, bool both_const) { @@ -2247,6 +2330,7 @@ process_implied_equality(PlannerInfo *root, */ distribute_qual_to_rels(root, (Node *) clause, true, below_outer_join, JOIN_INNER, + security_level, qualscope, NULL, NULL, nullable_relids, NULL); } @@ -2270,7 +2354,8 @@ build_implied_join_equality(Oid opno, Expr *item1, Expr *item2, Relids qualscope, - Relids nullable_relids) + Relids nullable_relids, + Index security_level) { RestrictInfo *restrictinfo; Expr *clause; @@ -2294,6 +2379,7 @@ build_implied_join_equality(Oid opno, true, /* is_pushed_down */ false, /* outerjoin_delayed */ false, /* pseudoconstant */ + security_level, /* security_level */ qualscope, /* required_relids */ NULL, /* outer_relids */ nullable_relids); /* nullable_relids */ diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index f936710171c..25f2c5a6147 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -490,6 +490,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, root->processed_tlist = NIL; root->grouping_map = NULL; root->minmax_aggs = NIL; + root->qual_security_level = 0; root->hasInheritedTarget = false; root->hasRecursion = hasRecursion; if (hasRecursion) @@ -669,6 +670,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse, { RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); int kind; + ListCell *lcsq; if (rte->rtekind == RTE_RELATION) { @@ -704,6 +706,19 @@ subquery_planner(PlannerGlobal *glob, Query *parse, rte->values_lists = (List *) preprocess_expression(root, (Node *) rte->values_lists, kind); } + + /* + * Process each element of the securityQuals list as if it were a + * separate qual expression (as indeed it is). We need to do it this + * way to get proper canonicalization of AND/OR structure. Note that + * this converts each element into an implicit-AND sublist. + */ + foreach(lcsq, rte->securityQuals) + { + lfirst(lcsq) = preprocess_expression(root, + (Node *) lfirst(lcsq), + EXPRKIND_QUAL); + } } /* @@ -978,7 +993,6 @@ inheritance_planner(PlannerInfo *root) { Query *parse = root->parse; int parentRTindex = parse->resultRelation; - Bitmapset *resultRTindexes; Bitmapset *subqueryRTindexes; Bitmapset *modifiableARIindexes; int nominalRelation = -1; @@ -1012,26 +1026,7 @@ inheritance_planner(PlannerInfo *root) * at least O(N^3) work expended here; and (2) would greatly complicate * management of the rowMarks list. * - * Note that any RTEs with security barrier quals will be turned into - * subqueries during planning, and so we must create copies of them too, - * except where they are target relations, which will each only be used in - * a single plan. - * - * To begin with, we'll need a bitmapset of the target relation relids. - */ - resultRTindexes = bms_make_singleton(parentRTindex); - foreach(lc, root->append_rel_list) - { - AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); - - if (appinfo->parent_relid == parentRTindex) - resultRTindexes = bms_add_member(resultRTindexes, - appinfo->child_relid); - } - - /* - * Now, generate a bitmapset of the relids of the subquery RTEs, including - * security-barrier RTEs that will become subqueries, as just explained. + * To begin with, generate a bitmapset of the relids of the subquery RTEs. */ subqueryRTindexes = NULL; rti = 1; @@ -1039,9 +1034,7 @@ inheritance_planner(PlannerInfo *root) { RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); - if (rte->rtekind == RTE_SUBQUERY || - (rte->securityQuals != NIL && - !bms_is_member(rti, resultRTindexes))) + if (rte->rtekind == RTE_SUBQUERY) subqueryRTindexes = bms_add_member(subqueryRTindexes, rti); rti++; } @@ -1079,6 +1072,8 @@ inheritance_planner(PlannerInfo *root) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc); PlannerInfo *subroot; + RangeTblEntry *parent_rte; + RangeTblEntry *child_rte; RelOptInfo *sub_final_rel; Path *subpath; @@ -1105,6 +1100,15 @@ inheritance_planner(PlannerInfo *root) appinfo); /* + * If there are securityQuals attached to the parent, move them to the + * child rel (they've already been transformed properly for that). + */ + parent_rte = rt_fetch(parentRTindex, subroot->parse->rtable); + child_rte = rt_fetch(appinfo->child_relid, subroot->parse->rtable); + child_rte->securityQuals = parent_rte->securityQuals; + parent_rte->securityQuals = NIL; + + /* * The rowMarks list might contain references to subquery RTEs, so * make a copy that we can apply ChangeVarNodes to. (Fortunately, the * executor doesn't need to see the modified copies --- we can just @@ -1151,11 +1155,11 @@ inheritance_planner(PlannerInfo *root) /* * If this isn't the first child Query, generate duplicates of all - * subquery (or subquery-to-be) RTEs, and adjust Var numbering to - * reference the duplicates. To simplify the loop logic, we scan the - * original rtable not the copy just made by adjust_appendrel_attrs; - * that should be OK since subquery RTEs couldn't contain any - * references to the target rel. + * subquery RTEs, and adjust Var numbering to reference the + * duplicates. To simplify the loop logic, we scan the original rtable + * not the copy just made by adjust_appendrel_attrs; that should be OK + * since subquery RTEs couldn't contain any references to the target + * rel. */ if (final_rtable != NIL && subqueryRTindexes != NULL) { @@ -1172,9 +1176,9 @@ inheritance_planner(PlannerInfo *root) /* * The RTE can't contain any references to its own RT - * index, except in the security barrier quals, so we can - * save a few cycles by applying ChangeVarNodes before we - * append the RTE to the rangetable. + * index, except in its securityQuals, so we can save a + * few cycles by applying ChangeVarNodes to the rest of + * the rangetable before we append the RTE to it. */ newrti = list_length(subroot->parse->rtable) + 1; ChangeVarNodes((Node *) subroot->parse, rti, newrti, 0); @@ -1213,12 +1217,6 @@ inheritance_planner(PlannerInfo *root) grouping_planner(subroot, true, 0.0 /* retrieve all tuples */ ); /* - * Planning may have modified the query result relation (if there were - * security barrier quals on the result RTE). - */ - appinfo->child_relid = subroot->parse->resultRelation; - - /* * We'll use the first child relation (even if it's excluded) as the * nominal target relation of the ModifyTable node. Because of the * way expand_inherited_rtentry works, this should always be the RTE @@ -1256,41 +1254,9 @@ inheritance_planner(PlannerInfo *root) if (final_rtable == NIL) final_rtable = subroot->parse->rtable; else - { - List *tmp_rtable = NIL; - ListCell *cell1, - *cell2; - - /* - * Check to see if any of the original RTEs were turned into - * subqueries during planning. Currently, this should only ever - * happen due to securityQuals being involved which push a - * relation down under a subquery, to ensure that the security - * barrier quals are evaluated first. - * - * When this happens, we want to use the new subqueries in the - * final rtable. - */ - forboth(cell1, final_rtable, cell2, subroot->parse->rtable) - { - RangeTblEntry *rte1 = (RangeTblEntry *) lfirst(cell1); - RangeTblEntry *rte2 = (RangeTblEntry *) lfirst(cell2); - - if (rte1->rtekind == RTE_RELATION && - rte2->rtekind == RTE_SUBQUERY) - { - /* Should only be when there are securityQuals today */ - Assert(rte1->securityQuals != NIL); - tmp_rtable = lappend(tmp_rtable, rte2); - } - else - tmp_rtable = lappend(tmp_rtable, rte1); - } - - final_rtable = list_concat(tmp_rtable, + final_rtable = list_concat(final_rtable, list_copy_tail(subroot->parse->rtable, list_length(final_rtable))); - } /* * We need to collect all the RelOptInfos from all child plans into @@ -1635,12 +1601,6 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, parse->rtable); /* - * Expand any rangetable entries that have security barrier quals. - * This may add new security barrier subquery RTEs to the rangetable. - */ - expand_security_quals(root, tlist); - - /* * We are now done hacking up the query's targetlist. Most of the * remaining planning work will be done with the PathTarget * representation of tlists, but save aside the full representation so @@ -2297,17 +2257,8 @@ select_rowmark_type(RangeTblEntry *rte, LockClauseStrength strength) /* * We don't need a tuple lock, only the ability to re-fetch - * the row. Regular tables support ROW_MARK_REFERENCE, but if - * this RTE has security barrier quals, it will be turned into - * a subquery during planning, so use ROW_MARK_COPY. - * - * This is only necessary for LCS_NONE, since real tuple locks - * on an RTE with security barrier quals are supported by - * pushing the lock down into the subquery --- see - * expand_security_qual. + * the row. */ - if (rte->securityQuals != NIL) - return ROW_MARK_COPY; return ROW_MARK_REFERENCE; break; case LCS_FORKEYSHARE: |