aboutsummaryrefslogtreecommitdiff
path: root/src/backend/optimizer/path/equivclass.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/optimizer/path/equivclass.c')
-rw-r--r--src/backend/optimizer/path/equivclass.c147
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);