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,
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);
* 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);
}
* 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
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)
{
* 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,
* 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,
* 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;
/* 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);
*
* 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,
*/
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));
}
/*
* 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)
* 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,
{
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);
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)
/*
* 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)
{
* 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++];
* 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,
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),