Commit e9f2703a authored by Tom Lane's avatar Tom Lane

Fix postgres_fdw to cope with duplicate GROUP BY entries.

Commit 7012b132, which added the ability to push down aggregates and
grouping to the remote server, wasn't careful to ensure that the remote
server would have the same idea we do about which columns are the grouping
columns, in cases where there are textually identical GROUP BY expressions.
Such cases typically led to "targetlist item has multiple sortgroupref
labels" errors.

To fix this reliably, switch over to using "GROUP BY column-number" syntax
rather than "GROUP BY expression" in transmitted queries, and adjust
foreign_grouping_ok() to be more careful about duplicating the sortgroupref
labeling of the local pathtarget.

Per bug #14890 from Sean Johnston.  Back-patch to v10 where the buggy code
was introduced.

Jeevan Chalke, reviewed by Ashutosh Bapat

Discussion: https://postgr.es/m/20171107134948.1508.94783@wrigleys.postgresql.org
parent 680d5405
...@@ -178,7 +178,7 @@ static void appendGroupByClause(List *tlist, deparse_expr_cxt *context); ...@@ -178,7 +178,7 @@ static void appendGroupByClause(List *tlist, deparse_expr_cxt *context);
static void appendAggOrderBy(List *orderList, List *targetList, static void appendAggOrderBy(List *orderList, List *targetList,
deparse_expr_cxt *context); deparse_expr_cxt *context);
static void appendFunctionName(Oid funcid, deparse_expr_cxt *context); static void appendFunctionName(Oid funcid, deparse_expr_cxt *context);
static Node *deparseSortGroupClause(Index ref, List *tlist, static Node *deparseSortGroupClause(Index ref, List *tlist, bool force_colno,
deparse_expr_cxt *context); deparse_expr_cxt *context);
/* /*
...@@ -2853,7 +2853,7 @@ appendAggOrderBy(List *orderList, List *targetList, deparse_expr_cxt *context) ...@@ -2853,7 +2853,7 @@ appendAggOrderBy(List *orderList, List *targetList, deparse_expr_cxt *context)
first = false; first = false;
sortexpr = deparseSortGroupClause(srt->tleSortGroupRef, targetList, sortexpr = deparseSortGroupClause(srt->tleSortGroupRef, targetList,
context); false, context);
sortcoltype = exprType(sortexpr); sortcoltype = exprType(sortexpr);
/* See whether operator is default < or > for datatype */ /* See whether operator is default < or > for datatype */
typentry = lookup_type_cache(sortcoltype, typentry = lookup_type_cache(sortcoltype,
...@@ -2960,7 +2960,7 @@ appendGroupByClause(List *tlist, deparse_expr_cxt *context) ...@@ -2960,7 +2960,7 @@ appendGroupByClause(List *tlist, deparse_expr_cxt *context)
appendStringInfoString(buf, ", "); appendStringInfoString(buf, ", ");
first = false; first = false;
deparseSortGroupClause(grp->tleSortGroupRef, tlist, context); deparseSortGroupClause(grp->tleSortGroupRef, tlist, true, context);
} }
} }
...@@ -3047,7 +3047,8 @@ appendFunctionName(Oid funcid, deparse_expr_cxt *context) ...@@ -3047,7 +3047,8 @@ appendFunctionName(Oid funcid, deparse_expr_cxt *context)
* need not find it again. * need not find it again.
*/ */
static Node * static Node *
deparseSortGroupClause(Index ref, List *tlist, deparse_expr_cxt *context) deparseSortGroupClause(Index ref, List *tlist, bool force_colno,
deparse_expr_cxt *context)
{ {
StringInfo buf = context->buf; StringInfo buf = context->buf;
TargetEntry *tle; TargetEntry *tle;
...@@ -3056,7 +3057,13 @@ deparseSortGroupClause(Index ref, List *tlist, deparse_expr_cxt *context) ...@@ -3056,7 +3057,13 @@ deparseSortGroupClause(Index ref, List *tlist, deparse_expr_cxt *context)
tle = get_sortgroupref_tle(ref, tlist); tle = get_sortgroupref_tle(ref, tlist);
expr = tle->expr; expr = tle->expr;
if (expr && IsA(expr, Const)) if (force_colno)
{
/* Use column-number form when requested by caller. */
Assert(!tle->resjunk);
appendStringInfo(buf, "%d", tle->resno);
}
else if (expr && IsA(expr, Const))
{ {
/* /*
* Force a typecast here so that we don't emit something like "GROUP * Force a typecast here so that we don't emit something like "GROUP
......
...@@ -4591,7 +4591,7 @@ static bool ...@@ -4591,7 +4591,7 @@ static bool
foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel) foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel)
{ {
Query *query = root->parse; Query *query = root->parse;
PathTarget *grouping_target; PathTarget *grouping_target = root->upper_targets[UPPERREL_GROUP_AGG];
PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) grouped_rel->fdw_private; PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) grouped_rel->fdw_private;
PgFdwRelationInfo *ofpinfo; PgFdwRelationInfo *ofpinfo;
List *aggvars; List *aggvars;
...@@ -4599,7 +4599,7 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel) ...@@ -4599,7 +4599,7 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel)
int i; int i;
List *tlist = NIL; List *tlist = NIL;
/* Grouping Sets are not pushable */ /* We currently don't support pushing Grouping Sets. */
if (query->groupingSets) if (query->groupingSets)
return false; return false;
...@@ -4607,7 +4607,7 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel) ...@@ -4607,7 +4607,7 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel)
ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private; ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private;
/* /*
* If underneath input relation has any local conditions, those conditions * If underlying scan relation has any local conditions, those conditions
* are required to be applied before performing aggregation. Hence the * are required to be applied before performing aggregation. Hence the
* aggregate cannot be pushed down. * aggregate cannot be pushed down.
*/ */
...@@ -4615,21 +4615,11 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel) ...@@ -4615,21 +4615,11 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel)
return false; return false;
/* /*
* The targetlist expected from this node and the targetlist pushed down * Examine grouping expressions, as well as other expressions we'd need to
* to the foreign server may be different. The latter requires * compute, and check whether they are safe to push down to the foreign
* sortgrouprefs to be set to push down GROUP BY clause, but should not * server. All GROUP BY expressions will be part of the grouping target
* have those arising from ORDER BY clause. These sortgrouprefs may be * and thus there is no need to search for them separately. Add grouping
* different from those in the plan's targetlist. Use a copy of path * expressions into target list which will be passed to foreign server.
* target to record the new sortgrouprefs.
*/
grouping_target = copy_pathtarget(root->upper_targets[UPPERREL_GROUP_AGG]);
/*
* Evaluate grouping targets and check whether they are safe to push down
* to the foreign side. All GROUP BY expressions will be part of the
* grouping target and thus there is no need to evaluate it separately.
* While doing so, add required expressions into target list which can
* then be used to pass to foreign server.
*/ */
i = 0; i = 0;
foreach(lc, grouping_target->exprs) foreach(lc, grouping_target->exprs)
...@@ -4641,51 +4631,59 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel) ...@@ -4641,51 +4631,59 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel)
/* Check whether this expression is part of GROUP BY clause */ /* Check whether this expression is part of GROUP BY clause */
if (sgref && get_sortgroupref_clause_noerr(sgref, query->groupClause)) if (sgref && get_sortgroupref_clause_noerr(sgref, query->groupClause))
{ {
TargetEntry *tle;
/* /*
* If any of the GROUP BY expression is not shippable we can not * If any GROUP BY expression is not shippable, then we cannot
* push down aggregation to the foreign server. * push down aggregation to the foreign server.
*/ */
if (!is_foreign_expr(root, grouped_rel, expr)) if (!is_foreign_expr(root, grouped_rel, expr))
return false; return false;
/* Pushable, add to tlist */ /*
tlist = add_to_flat_tlist(tlist, list_make1(expr)); * Pushable, so add to tlist. We need to create a TLE for this
* expression and apply the sortgroupref to it. We cannot use
* add_to_flat_tlist() here because that avoids making duplicate
* entries in the tlist. If there are duplicate entries with
* distinct sortgrouprefs, we have to duplicate that situation in
* the output tlist.
*/
tle = makeTargetEntry(expr, list_length(tlist) + 1, NULL, false);
tle->ressortgroupref = sgref;
tlist = lappend(tlist, tle);
} }
else else
{ {
/* Check entire expression whether it is pushable or not */ /*
* Non-grouping expression we need to compute. Is it shippable?
*/
if (is_foreign_expr(root, grouped_rel, expr)) if (is_foreign_expr(root, grouped_rel, expr))
{ {
/* Pushable, add to tlist */ /* Yes, so add to tlist as-is; OK to suppress duplicates */
tlist = add_to_flat_tlist(tlist, list_make1(expr)); tlist = add_to_flat_tlist(tlist, list_make1(expr));
} }
else else
{ {
/* /* Not pushable as a whole; extract its Vars and aggregates */
* If we have sortgroupref set, then it means that we have an
* ORDER BY entry pointing to this expression. Since we are
* not pushing ORDER BY with GROUP BY, clear it.
*/
if (sgref)
grouping_target->sortgrouprefs[i] = 0;
/* Not matched exactly, pull the var with aggregates then */
aggvars = pull_var_clause((Node *) expr, aggvars = pull_var_clause((Node *) expr,
PVC_INCLUDE_AGGREGATES); PVC_INCLUDE_AGGREGATES);
/*
* If any aggregate expression is not shippable, then we
* cannot push down aggregation to the foreign server.
*/
if (!is_foreign_expr(root, grouped_rel, (Expr *) aggvars)) if (!is_foreign_expr(root, grouped_rel, (Expr *) aggvars))
return false; return false;
/* /*
* Add aggregates, if any, into the targetlist. Plain var * Add aggregates, if any, into the targetlist. Plain Vars
* nodes should be either same as some GROUP BY expression or * outside an aggregate can be ignored, because they should be
* part of some GROUP BY expression. In later case, the query * either same as some GROUP BY column or part of some GROUP
* cannot refer plain var nodes without the surrounding * BY expression. In either case, they are already part of
* expression. In both the cases, they are already part of
* the targetlist and thus no need to add them again. In fact * the targetlist and thus no need to add them again. In fact
* adding pulled plain var nodes in SELECT clause will cause * including plain Vars in the tlist when they do not match a
* an error on the foreign server if they are not same as some * GROUP BY column would cause the foreign server to complain
* GROUP BY expression. * that the shipped query is invalid.
*/ */
foreach(l, aggvars) foreach(l, aggvars)
{ {
...@@ -4701,7 +4699,7 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel) ...@@ -4701,7 +4699,7 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel)
} }
/* /*
* Classify the pushable and non-pushable having clauses and save them in * Classify the pushable and non-pushable HAVING clauses and save them in
* remote_conds and local_conds of the grouped rel's fpinfo. * remote_conds and local_conds of the grouped rel's fpinfo.
*/ */
if (root->hasHavingQual && query->havingQual) if (root->hasHavingQual && query->havingQual)
...@@ -4771,9 +4769,6 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel) ...@@ -4771,9 +4769,6 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel)
} }
} }
/* Transfer any sortgroupref data to the replacement tlist */
apply_pathtarget_labeling_to_tlist(tlist, grouping_target);
/* Store generated targetlist */ /* Store generated targetlist */
fpinfo->grouped_tlist = tlist; fpinfo->grouped_tlist = tlist;
......
...@@ -636,6 +636,12 @@ explain (verbose, costs off) ...@@ -636,6 +636,12 @@ explain (verbose, costs off)
select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2;
select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2;
-- GROUP BY clause referring to same column multiple times
-- Also, ORDER BY contains an aggregate function
explain (verbose, costs off)
select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1);
select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1);
-- Testing HAVING clause shippability -- Testing HAVING clause shippability
explain (verbose, costs off) explain (verbose, costs off)
select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment