aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/optimizer/prep/prepjointree.c2
-rw-r--r--src/backend/optimizer/prep/prepunion.c131
-rw-r--r--src/backend/optimizer/util/tlist.c34
-rw-r--r--src/include/optimizer/tlist.h1
4 files changed, 132 insertions, 36 deletions
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index a40f116bf9a..a70439cc674 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1136,7 +1136,7 @@ is_simple_union_all_recurse(Node *setOp, Query *setOpQuery, List *colTypes)
Assert(subquery != NULL);
/* Leaf nodes are OK if they match the toplevel column types */
- /* We don't have to compare typmods here */
+ /* We don't have to compare typmods or collations here */
return tlist_same_datatypes(subquery->targetList, colTypes, true);
}
else if (IsA(setOp, SetOperationStmt))
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 0ed9535d94d..76adb7cdaec 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -54,7 +54,8 @@
static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root,
double tuple_fraction,
- List *colTypes, bool junkOK,
+ List *colTypes, List *colCollations,
+ bool junkOK,
int flag, List *refnames_tlist,
List **sortClauses, double *pNumGroups);
static Plan *generate_recursion_plan(SetOperationStmt *setOp,
@@ -81,12 +82,14 @@ static bool choose_hashed_setop(PlannerInfo *root, List *groupClauses,
double dNumGroups, double dNumOutputRows,
double tuple_fraction,
const char *construct);
-static List *generate_setop_tlist(List *colTypes, int flag,
+static List *generate_setop_tlist(List *colTypes, List *colCollations,
+ int flag,
Index varno,
bool hack_constants,
List *input_tlist,
List *refnames_tlist);
-static List *generate_append_tlist(List *colTypes, List *colCollations, bool flag,
+static List *generate_append_tlist(List *colTypes, List *colCollations,
+ bool flag,
List *input_plans,
List *refnames_tlist);
static List *generate_setop_grouplist(SetOperationStmt *op, List *targetlist);
@@ -169,7 +172,8 @@ plan_set_operations(PlannerInfo *root, double tuple_fraction,
* on upper-level nodes to deal with that).
*/
return recurse_set_operations((Node *) topop, root, tuple_fraction,
- topop->colTypes, true, -1,
+ topop->colTypes, topop->colCollations,
+ true, -1,
leftmostQuery->targetList,
sortClauses, NULL);
}
@@ -179,7 +183,8 @@ plan_set_operations(PlannerInfo *root, double tuple_fraction,
* Recursively handle one step in a tree of set operations
*
* tuple_fraction: fraction of tuples we expect to retrieve from node
- * colTypes: list of type OIDs of expected output columns
+ * colTypes: OID list of set-op's result column datatypes
+ * colCollations: OID list of set-op's result column collations
* junkOK: if true, child resjunk columns may be left in the result
* flag: if >= 0, add a resjunk output column indicating value of flag
* refnames_tlist: targetlist to take column names from
@@ -196,7 +201,8 @@ plan_set_operations(PlannerInfo *root, double tuple_fraction,
static Plan *
recurse_set_operations(Node *setOp, PlannerInfo *root,
double tuple_fraction,
- List *colTypes, bool junkOK,
+ List *colTypes, List *colCollations,
+ bool junkOK,
int flag, List *refnames_tlist,
List **sortClauses, double *pNumGroups)
{
@@ -239,7 +245,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
* Add a SubqueryScan with the caller-requested targetlist
*/
plan = (Plan *)
- make_subqueryscan(generate_setop_tlist(colTypes, flag,
+ make_subqueryscan(generate_setop_tlist(colTypes, colCollations,
+ flag,
rtr->rtindex,
true,
subplan->targetlist,
@@ -287,11 +294,13 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
* generate_setop_tlist() to use varno 0.
*/
if (flag >= 0 ||
- !tlist_same_datatypes(plan->targetlist, colTypes, junkOK))
+ !tlist_same_datatypes(plan->targetlist, colTypes, junkOK) ||
+ !tlist_same_collations(plan->targetlist, colCollations, junkOK))
{
plan = (Plan *)
make_result(root,
- generate_setop_tlist(colTypes, flag,
+ generate_setop_tlist(colTypes, colCollations,
+ flag,
0,
false,
plan->targetlist,
@@ -336,12 +345,14 @@ generate_recursion_plan(SetOperationStmt *setOp, PlannerInfo *root,
* separately without any intention of combining them into one Append.
*/
lplan = recurse_set_operations(setOp->larg, root, tuple_fraction,
- setOp->colTypes, false, -1,
+ setOp->colTypes, setOp->colCollations,
+ false, -1,
refnames_tlist, sortClauses, NULL);
/* The right plan will want to look at the left one ... */
root->non_recursive_plan = lplan;
rplan = recurse_set_operations(setOp->rarg, root, tuple_fraction,
- setOp->colTypes, false, -1,
+ setOp->colTypes, setOp->colCollations,
+ false, -1,
refnames_tlist, sortClauses, NULL);
root->non_recursive_plan = NULL;
@@ -499,12 +510,14 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root,
/* Recurse on children, ensuring their outputs are marked */
lplan = recurse_set_operations(op->larg, root,
0.0 /* all tuples needed */ ,
- op->colTypes, false, 0,
+ op->colTypes, op->colCollations,
+ false, 0,
refnames_tlist,
&child_sortclauses, &dLeftGroups);
rplan = recurse_set_operations(op->rarg, root,
0.0 /* all tuples needed */ ,
- op->colTypes, false, 1,
+ op->colTypes, op->colCollations,
+ false, 1,
refnames_tlist,
&child_sortclauses, &dRightGroups);
@@ -620,6 +633,13 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root,
*
* NOTE: we can also pull a UNION ALL up into a UNION, since the distinct
* output rows will be lost anyway.
+ *
+ * NOTE: currently, we ignore collations while determining if a child has
+ * the same properties. This is semantically sound only so long as all
+ * collations have the same notion of equality. It is valid from an
+ * implementation standpoint because we don't care about the ordering of
+ * a UNION child's result: UNION ALL results are always unordered, and
+ * generate_union_plan will force a fresh sort if the top level is a UNION.
*/
static List *
recurse_union_children(Node *setOp, PlannerInfo *root,
@@ -660,8 +680,10 @@ recurse_union_children(Node *setOp, PlannerInfo *root,
*/
return list_make1(recurse_set_operations(setOp, root,
tuple_fraction,
- top_union->colTypes, false,
- -1, refnames_tlist,
+ top_union->colTypes,
+ top_union->colCollations,
+ false, -1,
+ refnames_tlist,
&child_sortclauses, NULL));
}
@@ -830,7 +852,8 @@ choose_hashed_setop(PlannerInfo *root, List *groupClauses,
/*
* Generate targetlist for a set-operation plan node
*
- * colTypes: column datatypes for non-junk columns
+ * colTypes: OID list of set-op's result column datatypes
+ * colCollations: OID list of set-op's result column collations
* flag: -1 if no flag column needed, 0 or 1 to create a const flag column
* varno: varno to use in generated Vars
* hack_constants: true to copy up constants (see comments in code)
@@ -838,7 +861,8 @@ choose_hashed_setop(PlannerInfo *root, List *groupClauses,
* refnames_tlist: targetlist to take column names from
*/
static List *
-generate_setop_tlist(List *colTypes, int flag,
+generate_setop_tlist(List *colTypes, List *colCollations,
+ int flag,
Index varno,
bool hack_constants,
List *input_tlist,
@@ -846,19 +870,23 @@ generate_setop_tlist(List *colTypes, int flag,
{
List *tlist = NIL;
int resno = 1;
- ListCell *i,
- *j,
- *k;
+ ListCell *ctlc,
+ *cclc,
+ *itlc,
+ *rtlc;
TargetEntry *tle;
Node *expr;
- j = list_head(input_tlist);
- k = list_head(refnames_tlist);
- foreach(i, colTypes)
+ /* there's no forfour() so we must chase one list manually */
+ rtlc = list_head(refnames_tlist);
+ forthree(ctlc, colTypes, cclc, colCollations, itlc, input_tlist)
{
- Oid colType = lfirst_oid(i);
- TargetEntry *inputtle = (TargetEntry *) lfirst(j);
- TargetEntry *reftle = (TargetEntry *) lfirst(k);
+ Oid colType = lfirst_oid(ctlc);
+ Oid colColl = lfirst_oid(cclc);
+ TargetEntry *inputtle = (TargetEntry *) lfirst(itlc);
+ TargetEntry *reftle = (TargetEntry *) lfirst(rtlc);
+
+ rtlc = lnext(rtlc);
Assert(inputtle->resno == resno);
Assert(reftle->resno == resno);
@@ -887,21 +915,48 @@ generate_setop_tlist(List *colTypes, int flag,
exprTypmod((Node *) inputtle->expr),
exprCollation((Node *) inputtle->expr),
0);
+
if (exprType(expr) != colType)
{
+ /*
+ * Note: it's not really cool to be applying coerce_to_common_type
+ * here; one notable point is that assign_expr_collations never
+ * gets run on any generated nodes. For the moment that's not a
+ * problem because we force the correct exposed collation below.
+ * It would likely be best to make the parser generate the correct
+ * output tlist for every set-op to begin with, though.
+ */
expr = coerce_to_common_type(NULL, /* no UNKNOWNs here */
expr,
colType,
"UNION/INTERSECT/EXCEPT");
}
+
+ /*
+ * Ensure the tlist entry's exposed collation matches the set-op.
+ * This is necessary because plan_set_operations() reports the result
+ * ordering as a list of SortGroupClauses, which don't carry collation
+ * themselves but just refer to tlist entries. If we don't show the
+ * right collation then planner.c might do the wrong thing in
+ * higher-level queries.
+ *
+ * Note we use RelabelType, not CollateExpr, since this expression
+ * will reach the executor without any further processing.
+ */
+ if (exprCollation(expr) != colColl)
+ {
+ expr = (Node *) makeRelabelType((Expr *) expr,
+ exprType(expr),
+ exprTypmod(expr),
+ colColl,
+ COERCE_DONTCARE);
+ }
+
tle = makeTargetEntry((Expr *) expr,
(AttrNumber) resno++,
pstrdup(reftle->resname),
false);
tlist = lappend(tlist, tle);
-
- j = lnext(j);
- k = lnext(k);
}
if (flag >= 0)
@@ -928,17 +983,19 @@ generate_setop_tlist(List *colTypes, int flag,
/*
* Generate targetlist for a set-operation Append node
*
- * colTypes: column datatypes for non-junk columns
+ * colTypes: OID list of set-op's result column datatypes
+ * colCollations: OID list of set-op's result column collations
* flag: true to create a flag column copied up from subplans
* input_plans: list of sub-plans of the Append
* refnames_tlist: targetlist to take column names from
*
* The entries in the Append's targetlist should always be simple Vars;
- * we just have to make sure they have the right datatypes and typmods.
+ * we just have to make sure they have the right datatypes/typmods/collations.
* The Vars are always generated with varno 0.
*/
static List *
-generate_append_tlist(List *colTypes, List *colCollations, bool flag,
+generate_append_tlist(List *colTypes, List *colCollations,
+ bool flag,
List *input_plans,
List *refnames_tlist)
{
@@ -1000,7 +1057,8 @@ generate_append_tlist(List *colTypes, List *colCollations, bool flag,
* Now we can build the tlist for the Append.
*/
colindex = 0;
- forthree(curColType, colTypes, curColCollation, colCollations, ref_tl_item, refnames_tlist)
+ forthree(curColType, colTypes, curColCollation, colCollations,
+ ref_tl_item, refnames_tlist)
{
Oid colType = lfirst_oid(curColType);
int32 colTypmod = colTypmods[colindex++];
@@ -1331,7 +1389,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
* Build the list of translations from parent Vars to child Vars for
* an inheritance child.
*
- * For paranoia's sake, we match type as well as attribute name.
+ * For paranoia's sake, we match type/collation as well as attribute name.
*/
static void
make_inh_translation_list(Relation oldrelation, Relation newrelation,
@@ -1410,10 +1468,13 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation,
attname, RelationGetRelationName(newrelation));
}
- /* Found it, check type */
+ /* Found it, check type and collation match */
if (atttypid != att->atttypid || atttypmod != att->atttypmod)
elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type",
attname, RelationGetRelationName(newrelation));
+ if (attcollation != att->attcollation)
+ elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation",
+ attname, RelationGetRelationName(newrelation));
vars = lappend(vars, makeVar(newvarno,
(AttrNumber) (new_attno + 1),
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
index d7e3a38e6f1..d17424e40f3 100644
--- a/src/backend/optimizer/util/tlist.c
+++ b/src/backend/optimizer/util/tlist.c
@@ -195,6 +195,40 @@ tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK)
return true;
}
+/*
+ * Does tlist have same exposed collations as listed in colCollations?
+ *
+ * Identical logic to the above, but for collations.
+ */
+bool
+tlist_same_collations(List *tlist, List *colCollations, bool junkOK)
+{
+ ListCell *l;
+ ListCell *curColColl = list_head(colCollations);
+
+ foreach(l, tlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (tle->resjunk)
+ {
+ if (!junkOK)
+ return false;
+ }
+ else
+ {
+ if (curColColl == NULL)
+ return false; /* tlist longer than colCollations */
+ if (exprCollation((Node *) tle->expr) != lfirst_oid(curColColl))
+ return false;
+ curColColl = lnext(curColColl);
+ }
+ }
+ if (curColColl != NULL)
+ return false; /* tlist shorter than colCollations */
+ return true;
+}
+
/*
* get_sortgroupref_tle
diff --git a/src/include/optimizer/tlist.h b/src/include/optimizer/tlist.h
index f7606d79a38..7af59589dcb 100644
--- a/src/include/optimizer/tlist.h
+++ b/src/include/optimizer/tlist.h
@@ -25,6 +25,7 @@ extern List *add_to_flat_tlist(List *tlist, List *exprs);
extern List *get_tlist_exprs(List *tlist, bool includeJunk);
extern bool tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK);
+extern bool tlist_same_collations(List *tlist, List *colCollations, bool junkOK);
extern TargetEntry *get_sortgroupref_tle(Index sortref,
List *targetList);