diff options
Diffstat (limited to 'src/backend/optimizer/path/equivclass.c')
-rw-r--r-- | src/backend/optimizer/path/equivclass.c | 147 |
1 files changed, 134 insertions, 13 deletions
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 3d87a5b9037..9a32e16940b 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -17,6 +17,8 @@ #include "postgres.h" #include "access/skey.h" +#include "catalog/pg_type.h" +#include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" @@ -97,6 +99,7 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, { Expr *clause = restrictinfo->clause; Oid opno, + collation, item1_type, item2_type; Expr *item1; @@ -117,12 +120,24 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, /* Extract info from given clause */ Assert(is_opclause(clause)); opno = ((OpExpr *) clause)->opno; + collation = ((OpExpr *) clause)->inputcollid; item1 = (Expr *) get_leftop(clause); item2 = (Expr *) get_rightop(clause); item1_relids = restrictinfo->left_relids; item2_relids = restrictinfo->right_relids; /* + * Ensure both input expressions expose the desired collation (their types + * should be OK already); see comments for canonicalize_ec_expression. + */ + item1 = canonicalize_ec_expression(item1, + exprType((Node *) item1), + collation); + item2 = canonicalize_ec_expression(item2, + exprType((Node *) item2), + collation); + + /* * Reject clauses of the form X=X. These are not as redundant as they * might seem at first glance: assuming the operator is strict, this is * really an expensive way to write X IS NOT NULL. So we must not risk @@ -189,6 +204,13 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, continue; /* + * The collation has to match; check this first since it's cheaper + * than the opfamily comparison. + */ + if (collation != cur_ec->ec_collation) + continue; + + /* * A "match" requires matching sets of btree opfamilies. Use of * equal() for this test has implications discussed in the comments * for get_mergejoin_opfamilies(). @@ -315,6 +337,7 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, EquivalenceClass *ec = makeNode(EquivalenceClass); ec->ec_opfamilies = opfamilies; + ec->ec_collation = collation; ec->ec_members = NIL; ec->ec_sources = list_make1(restrictinfo); ec->ec_derives = NIL; @@ -342,6 +365,84 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, } /* + * canonicalize_ec_expression + * + * This function ensures that the expression exposes the expected type and + * collation, so that it will be equal() to other equivalence-class expressions + * that it ought to be equal() to. + * + * The rule for datatypes is that the exposed type should match what it would + * be for an input to an operator of the EC's opfamilies; which is usually + * the declared input type of the operator, but in the case of polymorphic + * operators no relabeling is wanted (compare the behavior of parse_coerce.c). + * Expressions coming in from quals will generally have the right type + * already, but expressions coming from indexkeys may not (because they are + * represented without any explicit relabel in pg_index), and the same problem + * occurs for sort expressions (because the parser is likewise cavalier about + * putting relabels on them). Such cases will be binary-compatible with the + * real operators, so adding a RelabelType is sufficient. + * + * Also, the expression's exposed collation must match the EC's collation. + * This is important because in comparisons like "foo < bar COLLATE baz", + * only one of the expressions has the correct exposed collation as we receive + * it from the parser. Forcing both of them to have it ensures that all + * variant spellings of such a construct behave the same. Again, we can + * stick on a RelabelType to force the right exposed collation. (It might + * work to not label the collation at all in EC members, but this is risky + * since some parts of the system expect exprCollation() to deliver the + * right answer for a sort key.) + * + * Note this code assumes that the expression has already been through + * eval_const_expressions, so there are no CollateExprs and no redundant + * RelabelTypes. + */ +Expr * +canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation) +{ + Oid expr_type = exprType((Node *) expr); + + /* + * For a polymorphic-input-type opclass, just keep the same exposed type. + */ + if (IsPolymorphicType(req_type)) + req_type = expr_type; + + /* + * No work if the expression exposes the right type/collation already. + */ + if (expr_type != req_type || + exprCollation((Node *) expr) != req_collation) + { + /* + * Strip any existing RelabelType, then add a new one if needed. + * This is to preserve the invariant of no redundant RelabelTypes. + * + * If we have to change the exposed type of the stripped expression, + * set typmod to -1 (since the new type may not have the same typmod + * interpretation). If we only have to change collation, preserve + * the exposed typmod. + */ + while (expr && IsA(expr, RelabelType)) + expr = (Expr *) ((RelabelType *) expr)->arg; + + if (exprType((Node *) expr) != req_type) + expr = (Expr *) makeRelabelType(expr, + req_type, + -1, + req_collation, + COERCE_DONTCARE); + else if (exprCollation((Node *) expr) != req_collation) + expr = (Expr *) makeRelabelType(expr, + req_type, + exprTypmod((Node *) expr), + req_collation, + COERCE_DONTCARE); + } + + return expr; +} + +/* * add_eq_member - build a new EquivalenceMember and add it to an EC */ static EquivalenceMember * @@ -383,9 +484,9 @@ add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, /* * get_eclass_for_sort_expr - * Given an expression and opfamily info, find an existing equivalence - * class it is a member of; if none, optionally build a new single-member - * EquivalenceClass for it. + * Given an expression and opfamily/collation info, find an existing + * equivalence class it is a member of; if none, optionally build a new + * single-member EquivalenceClass for it. * * sortref is the SortGroupRef of the originating SortGroupClause, if any, * or zero if not. (It should never be zero if the expression is volatile!) @@ -406,8 +507,9 @@ add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids, EquivalenceClass * get_eclass_for_sort_expr(PlannerInfo *root, Expr *expr, - Oid expr_datatype, List *opfamilies, + Oid opcintype, + Oid collation, Index sortref, bool create_it) { @@ -417,6 +519,11 @@ get_eclass_for_sort_expr(PlannerInfo *root, MemoryContext oldcontext; /* + * Ensure the expression exposes the correct type and collation. + */ + expr = canonicalize_ec_expression(expr, opcintype, collation); + + /* * Scan through the existing EquivalenceClasses for a match */ foreach(lc1, root->eq_classes) @@ -432,6 +539,8 @@ get_eclass_for_sort_expr(PlannerInfo *root, (sortref == 0 || sortref != cur_ec->ec_sortref)) continue; + if (collation != cur_ec->ec_collation) + continue; if (!equal(opfamilies, cur_ec->ec_opfamilies)) continue; @@ -447,7 +556,7 @@ get_eclass_for_sort_expr(PlannerInfo *root, cur_em->em_is_const) continue; - if (expr_datatype == cur_em->em_datatype && + if (opcintype == cur_em->em_datatype && equal(expr, cur_em->em_expr)) return cur_ec; /* Match! */ } @@ -460,13 +569,13 @@ get_eclass_for_sort_expr(PlannerInfo *root, /* * OK, build a new single-member EC * - * Here, we must be sure that we construct the EC in the right context. We - * can assume, however, that the passed expr is long-lived. + * Here, we must be sure that we construct the EC in the right context. */ oldcontext = MemoryContextSwitchTo(root->planner_cxt); newec = makeNode(EquivalenceClass); newec->ec_opfamilies = list_copy(opfamilies); + newec->ec_collation = collation; newec->ec_members = NIL; newec->ec_sources = NIL; newec->ec_derives = NIL; @@ -481,8 +590,8 @@ get_eclass_for_sort_expr(PlannerInfo *root, if (newec->ec_has_volatile && sortref == 0) /* should not happen */ elog(ERROR, "volatile EquivalenceClass has no sortref"); - newem = add_eq_member(newec, expr, pull_varnos((Node *) expr), - false, expr_datatype); + newem = add_eq_member(newec, copyObject(expr), pull_varnos((Node *) expr), + false, opcintype); /* * add_eq_member doesn't check for volatile functions, set-returning @@ -660,7 +769,7 @@ generate_base_implied_equalities_const(PlannerInfo *root, ec->ec_broken = true; break; } - process_implied_equality(root, eq_op, + process_implied_equality(root, eq_op, ec->ec_collation, cur_em->em_expr, const_em->em_expr, ec->ec_relids, ec->ec_below_outer_join, @@ -715,7 +824,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root, ec->ec_broken = true; break; } - process_implied_equality(root, eq_op, + process_implied_equality(root, eq_op, ec->ec_collation, prev_em->em_expr, cur_em->em_expr, ec->ec_relids, ec->ec_below_outer_join, @@ -1117,6 +1226,7 @@ create_join_clause(PlannerInfo *root, oldcontext = MemoryContextSwitchTo(root->planner_cxt); rinfo = build_implied_join_equality(opno, + ec->ec_collation, leftem->em_expr, rightem->em_expr, bms_union(leftem->em_relids, @@ -1338,6 +1448,7 @@ reconsider_outer_join_clause(PlannerInfo *root, RestrictInfo *rinfo, Expr *outervar, *innervar; Oid opno, + collation, left_type, right_type, inner_datatype; @@ -1346,6 +1457,7 @@ reconsider_outer_join_clause(PlannerInfo *root, RestrictInfo *rinfo, Assert(is_opclause(rinfo->clause)); opno = ((OpExpr *) rinfo->clause)->opno; + collation = ((OpExpr *) rinfo->clause)->inputcollid; /* If clause is outerjoin_delayed, operator must be strict */ if (rinfo->outerjoin_delayed && !op_strict(opno)) @@ -1381,7 +1493,9 @@ reconsider_outer_join_clause(PlannerInfo *root, RestrictInfo *rinfo, /* Never match to a volatile EC */ if (cur_ec->ec_has_volatile) continue; - /* It has to match the outer-join clause as to opfamilies, too */ + /* It has to match the outer-join clause as to semantics, too */ + if (collation != cur_ec->ec_collation) + continue; if (!equal(rinfo->mergeopfamilies, cur_ec->ec_opfamilies)) continue; /* Does it contain a match to outervar? */ @@ -1419,6 +1533,7 @@ reconsider_outer_join_clause(PlannerInfo *root, RestrictInfo *rinfo, if (!OidIsValid(eq_op)) continue; /* can't generate equality */ newrinfo = build_implied_join_equality(eq_op, + cur_ec->ec_collation, innervar, cur_em->em_expr, inner_relids); @@ -1451,6 +1566,7 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo) Expr *leftvar; Expr *rightvar; Oid opno, + collation, left_type, right_type; Relids left_relids, @@ -1464,6 +1580,7 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo) /* Extract needed info from the clause */ Assert(is_opclause(rinfo->clause)); opno = ((OpExpr *) rinfo->clause)->opno; + collation = ((OpExpr *) rinfo->clause)->inputcollid; op_input_types(opno, &left_type, &right_type); leftvar = (Expr *) get_leftop(rinfo->clause); rightvar = (Expr *) get_rightop(rinfo->clause); @@ -1485,7 +1602,9 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo) /* Never match to a volatile EC */ if (cur_ec->ec_has_volatile) continue; - /* It has to match the outer-join clause as to opfamilies, too */ + /* It has to match the outer-join clause as to semantics, too */ + if (collation != cur_ec->ec_collation) + continue; if (!equal(rinfo->mergeopfamilies, cur_ec->ec_opfamilies)) continue; @@ -1548,6 +1667,7 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo) if (OidIsValid(eq_op)) { newrinfo = build_implied_join_equality(eq_op, + cur_ec->ec_collation, leftvar, cur_em->em_expr, left_relids); @@ -1560,6 +1680,7 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo) if (OidIsValid(eq_op)) { newrinfo = build_implied_join_equality(eq_op, + cur_ec->ec_collation, rightvar, cur_em->em_expr, right_relids); |