diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index 691658f..b4e5893 100644 --- a/contrib/postgres_fdw/deparse.c +++ b/contrib/postgres_fdw/deparse.c @@ -38,6 +38,7 @@ #include "access/heapam.h" #include "access/htup_details.h" #include "access/sysattr.h" +#include "catalog/pg_aggregate.h" #include "catalog/pg_collation.h" #include "catalog/pg_namespace.h" #include "catalog/pg_operator.h" @@ -47,6 +48,7 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "nodes/plannodes.h" +#include "nodes/print.h" #include "optimizer/clauses.h" #include "optimizer/prep.h" #include "optimizer/tlist.h" @@ -55,7 +57,9 @@ #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/rel.h" +#include "utils/ruleutils.h" #include "utils/syscache.h" +#include "utils/typcache.h" /* @@ -93,7 +97,9 @@ typedef struct foreign_loc_cxt typedef struct deparse_expr_cxt { PlannerInfo *root; /* global planner state */ + RelOptInfo *foreignrel; /* the foreign relation we are planning for */ + RelOptInfo *scanrel; /* the underlying scan relation. */ StringInfo buf; /* output buffer to append to */ List **params_list; /* exprs that will become remote Params */ } deparse_expr_cxt; @@ -135,7 +141,7 @@ static void deparseColumnRef(StringInfo buf, int varno, int varattno, static void deparseRelation(StringInfo buf, Relation rel); static void deparseExpr(Expr *expr, deparse_expr_cxt *context); static void deparseVar(Var *node, deparse_expr_cxt *context); -static void deparseConst(Const *node, deparse_expr_cxt *context); +static void deparseConst(Const *node, deparse_expr_cxt *context, int showtype); static void deparseParam(Param *node, deparse_expr_cxt *context); static void deparseArrayRef(ArrayRef *node, deparse_expr_cxt *context); static void deparseFuncExpr(FuncExpr *node, deparse_expr_cxt *context); @@ -159,6 +165,14 @@ static void appendOrderByClause(List *pathkeys, deparse_expr_cxt *context); static void appendConditions(List *exprs, deparse_expr_cxt *context); static void deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *joinrel, bool use_alias, List **params_list); +static void deparseFromExpr(List *quals, deparse_expr_cxt *context); +static void deparseAggref(Aggref *node, deparse_expr_cxt *context); +static void appendGroupByClause(List *tlist, deparse_expr_cxt *context); +static void appendAggOrderBy(List *orderList, List *targetList, + deparse_expr_cxt *context); +static void appendFunctionName(Oid funcid, deparse_expr_cxt *context); +static Node *appendSortGroupClause(Index ref, List *tlist, + deparse_expr_cxt *context); /* @@ -631,6 +645,111 @@ foreign_expr_walker(Node *node, check_type = false; } break; + case T_Aggref: + { + Aggref *agg = (Aggref *) node; + ListCell *lc; + + /* Not safe to pushdown when not in grouping context */ + if (glob_cxt->foreignrel->reloptkind != RELOPT_UPPER_REL) + return false; + + /* Only non-split aggregates are pushable. */ + if (agg->aggsplit != AGGSPLIT_SIMPLE) + return false; + + /* + * If aggregate function used by the expression is not + * shippable, it can't be sent to remote because it might + * have incompatible semantics on remote side. + */ + if (!is_shippable(agg->aggfnoid, ProcedureRelationId, fpinfo)) + return false; + + /* + * Recurse to input args. aggdirectargs and aggdistinct are + * all present in args, so no need to check their shippability + * explicitly. + */ + foreach(lc, agg->args) + { + Node *n = (Node *) lfirst(lc); + + /* If TargetEntry, extract the expression from it */ + if (IsA(n, TargetEntry)) + { + TargetEntry *tle = (TargetEntry *) n; + n = (Node *) tle->expr; + } + + if (!foreign_expr_walker(n, glob_cxt, &inner_cxt)) + return false; + } + + /* + * aggorder too present into args so no need to check its + * shippability explicitly. However, if any expression has + * USING clause with sort operator, we need to make sure the + * shippability of that operator. + */ + if (agg->aggorder) + { + ListCell *lc; + + foreach (lc, agg->aggorder) + { + SortGroupClause *srt = (SortGroupClause *) lfirst(lc); + Oid sortcoltype; + TypeCacheEntry *typentry; + TargetEntry *tle; + + tle = get_sortgroupref_tle(srt->tleSortGroupRef, + agg->args); + sortcoltype = exprType((Node *) tle->expr); + typentry = lookup_type_cache(sortcoltype, + TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); + /* Check shippability of non-default sort operator. */ + if (srt->sortop != typentry->lt_opr && + srt->sortop != typentry->gt_opr) + if (!is_shippable(srt->sortop, OperatorRelationId, + fpinfo)) + return false; + } + } + + /* Check aggregate filter */ + if (!foreign_expr_walker((Node *) agg->aggfilter, + glob_cxt, &inner_cxt)) + return false; + + /* + * If aggregate's input collation is not derived from a foreign + * Var, it can't be sent to remote. + */ + if (agg->inputcollid == InvalidOid) + /* OK, inputs are all noncollatable */ ; + else if (inner_cxt.state != FDW_COLLATE_SAFE || + agg->inputcollid != inner_cxt.collation) + return false; + + /* + * Detect whether node is introducing a collation not derived + * from a foreign Var. (If so, we just mark it unsafe for now + * rather than immediately returning false, since the parent + * node might not care.) + */ + collation = agg->aggcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && + collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; default: /* @@ -720,7 +839,9 @@ deparse_type_name(Oid type_oid, int32 typemod) * Build the targetlist for given relation to be deparsed as SELECT clause. * * The output targetlist contains the columns that need to be fetched from the - * foreign server for the given relation. + * foreign server for the given relation. If foreignrel is an upper relation, + * then the output targetlist can also contains expressions to be evaluated on + * foreign server. */ List * build_tlist_to_deparse(RelOptInfo *foreignrel) @@ -729,6 +850,13 @@ build_tlist_to_deparse(RelOptInfo *foreignrel) PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; /* + * For upper relation, we have already built the target list while checking + * shippability, return that. + */ + if (foreignrel->reloptkind == RELOPT_UPPER_REL) + return fpinfo->grouped_tlist; + + /* * We require columns specified in foreignrel->reltarget->exprs and those * required for evaluating the local conditions. */ @@ -750,6 +878,7 @@ build_tlist_to_deparse(RelOptInfo *foreignrel) * hence the tlist is ignored for a base relation. * * remote_conds is the list of conditions to be deparsed as WHERE clause. + * However for upper relation it is deparsed as HAVING clause. * * If params_list is not NULL, it receives a list of Params and other-relation * Vars used in the clauses; these values must be transmitted to the remote @@ -768,28 +897,50 @@ deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel, List **retrieved_attrs, List **params_list) { deparse_expr_cxt context; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)rel->fdw_private; + List *quals; - /* We handle relations for foreign tables and joins between those */ - Assert(rel->reloptkind == RELOPT_JOINREL || - rel->reloptkind == RELOPT_BASEREL || - rel->reloptkind == RELOPT_OTHER_MEMBER_REL); + /* We handle all relations other than dead one. */ + Assert(rel->reloptkind != RELOPT_DEADREL); - /* Fill portions of context common to join and base relation */ + /* Fill portions of context common to upper, join and base relation */ context.buf = buf; context.root = root; context.foreignrel = rel; + context.scanrel = (rel->reloptkind == RELOPT_UPPER_REL) ? fpinfo->outerrel : rel; context.params_list = params_list; - /* Construct SELECT clause and FROM clause */ + /* Construct SELECT clause */ deparseSelectSql(tlist, retrieved_attrs, &context); /* - * Construct WHERE clause + * For upper relations, WHERE clause is built from the underneath scan + * relation's remote conditions. */ - if (remote_conds) + if (rel->reloptkind == RELOPT_UPPER_REL) { - appendStringInfo(buf, " WHERE "); - appendConditions(remote_conds, &context); + PgFdwRelationInfo *ofpinfo; + + ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private; + quals = ofpinfo->remote_conds; + } + else + quals = remote_conds; + + /* Construct FROM and WHERE clauses */ + deparseFromExpr(quals, &context); + + if (rel->reloptkind == RELOPT_UPPER_REL) + { + /* Append GROUP BY clause */ + appendGroupByClause(tlist, &context); + + /* Append HAVING clause */ + if (remote_conds) + { + appendStringInfo(buf, " HAVING "); + appendConditions(remote_conds, &context); + } } /* Add ORDER BY clause if we found any useful pathkeys */ @@ -803,7 +954,7 @@ deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel, /* * Construct a simple SELECT statement that retrieves desired columns * of the specified foreign table, and append it to "buf". The output - * contains just "SELECT ... FROM ....". + * contains just "SELECT ... ". * * We also create an integer List of the columns being retrieved, which is * returned to *retrieved_attrs. @@ -824,7 +975,8 @@ deparseSelectSql(List *tlist, List **retrieved_attrs, deparse_expr_cxt *context) */ appendStringInfoString(buf, "SELECT "); - if (foreignrel->reloptkind == RELOPT_JOINREL) + if (foreignrel->reloptkind == RELOPT_JOINREL || + foreignrel->reloptkind == RELOPT_UPPER_REL) { /* For a join relation use the input tlist */ deparseExplicitTargetList(tlist, retrieved_attrs, context); @@ -847,14 +999,33 @@ deparseSelectSql(List *tlist, List **retrieved_attrs, deparse_expr_cxt *context) fpinfo->attrs_used, false, retrieved_attrs); heap_close(rel, NoLock); } +} - /* - * Construct FROM clause - */ +/* + * Construct a FROM clause and a WHERE clause, if any, and append those to + * "buf". + * + * quals is the list of clauses to be included in the WHERE clause, + * NIL if none. + */ +static void +deparseFromExpr(List *quals, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + RelOptInfo *scan_rel = context->scanrel; + + /* Construct FROM clause */ appendStringInfoString(buf, " FROM "); - deparseFromExprForRel(buf, root, foreignrel, - (foreignrel->reloptkind == RELOPT_JOINREL), + deparseFromExprForRel(buf, context->root, scan_rel, + (scan_rel->reloptkind == RELOPT_JOINREL), context->params_list); + + /* Construct WHERE clause */ + if (quals) + { + appendStringInfo(buf, " WHERE "); + appendConditions(quals, context); + } } /* @@ -957,14 +1128,14 @@ deparseTargetList(StringInfo buf, /* * Deparse the appropriate locking clause (FOR UPDATE or FOR SHARE) for a - * given relation (context->foreignrel). + * given relation (context->scanrel). */ static void deparseLockingClause(deparse_expr_cxt *context) { StringInfo buf = context->buf; PlannerInfo *root = context->root; - RelOptInfo *rel = context->foreignrel; + RelOptInfo *rel = context->scanrel; int relid = -1; while ((relid = bms_next_member(rel->relids, relid)) >= 0) @@ -1036,7 +1207,7 @@ deparseLockingClause(deparse_expr_cxt *context) * Deparse conditions from the provided list and append them to buf. * * The conditions in the list are assumed to be ANDed. This function is used to - * deparse both WHERE clauses and JOIN .. ON clauses. + * deparse WHERE clauses, JOIN .. ON clauses and HAVING clauses. */ static void appendConditions(List *exprs, deparse_expr_cxt *context) @@ -1126,22 +1297,15 @@ deparseExplicitTargetList(List *tlist, List **retrieved_attrs, foreach(lc, tlist) { TargetEntry *tle = (TargetEntry *) lfirst(lc); - Var *var; /* Extract expression if TargetEntry node */ Assert(IsA(tle, TargetEntry)); - var = (Var *) tle->expr; - - /* We expect only Var nodes here */ - if (!IsA(var, Var)) - elog(ERROR, "non-Var not expected in target list"); if (i > 0) appendStringInfoString(buf, ", "); - deparseVar(var, context); + deparseExpr((Expr *) tle->expr, context); *retrieved_attrs = lappend_int(*retrieved_attrs, i + 1); - i++; } @@ -1192,6 +1356,7 @@ deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, context.buf = buf; context.foreignrel = foreignrel; + context.scanrel = foreignrel; context.root = root; context.params_list = params_list; @@ -1360,6 +1525,7 @@ deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root, /* Set up context struct for recursion */ context.root = root; context.foreignrel = baserel; + context.scanrel = baserel; context.buf = buf; context.params_list = params_list; @@ -1444,6 +1610,7 @@ deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root, /* Set up context struct for recursion */ context.root = root; context.foreignrel = baserel; + context.scanrel = baserel; context.buf = buf; context.params_list = params_list; @@ -1817,7 +1984,7 @@ deparseExpr(Expr *node, deparse_expr_cxt *context) deparseVar((Var *) node, context); break; case T_Const: - deparseConst((Const *) node, context); + deparseConst((Const *) node, context, 0); break; case T_Param: deparseParam((Param *) node, context); @@ -1849,6 +2016,9 @@ deparseExpr(Expr *node, deparse_expr_cxt *context) case T_ArrayExpr: deparseArrayExpr((ArrayExpr *) node, context); break; + case T_Aggref: + deparseAggref((Aggref *)node, context); + break; default: elog(ERROR, "unsupported expression type for deparse: %d", (int) nodeTag(node)); @@ -1867,9 +2037,10 @@ deparseExpr(Expr *node, deparse_expr_cxt *context) static void deparseVar(Var *node, deparse_expr_cxt *context) { - bool qualify_col = (context->foreignrel->reloptkind == RELOPT_JOINREL); + bool qualify_col = (context->scanrel->reloptkind == RELOPT_JOINREL || + context->scanrel->reloptkind == RELOPT_UPPER_REL); - if (bms_is_member(node->varno, context->foreignrel->relids) && + if (bms_is_member(node->varno, context->scanrel->relids) && node->varlevelsup == 0) deparseColumnRef(context->buf, node->varno, node->varattno, context->root, qualify_col); @@ -1910,7 +2081,7 @@ deparseVar(Var *node, deparse_expr_cxt *context) * This function has to be kept in sync with ruleutils.c's get_const_expr. */ static void -deparseConst(Const *node, deparse_expr_cxt *context) +deparseConst(Const *node, deparse_expr_cxt *context, int showtype) { StringInfo buf = context->buf; Oid typoutput; @@ -1922,9 +2093,10 @@ deparseConst(Const *node, deparse_expr_cxt *context) if (node->constisnull) { appendStringInfoString(buf, "NULL"); - appendStringInfo(buf, "::%s", - deparse_type_name(node->consttype, - node->consttypmod)); + if (showtype >= 0) + appendStringInfo(buf, "::%s", + deparse_type_name(node->consttype, + node->consttypmod)); return; } @@ -1974,9 +2146,14 @@ deparseConst(Const *node, deparse_expr_cxt *context) break; } + pfree(extval); + + if (showtype < 0) + return; + /* - * Append ::typename unless the constant will be implicitly typed as the - * right type when it is read in. + * For showtype == 0, append ::typename unless the constant will be + * implicitly typed as the right type when it is read in. * * XXX this code has to be kept in sync with the behavior of the parser, * especially make_const. @@ -1995,7 +2172,7 @@ deparseConst(Const *node, deparse_expr_cxt *context) needlabel = true; break; } - if (needlabel) + if (needlabel || showtype > 0) appendStringInfo(buf, "::%s", deparse_type_name(node->consttype, node->consttypmod)); @@ -2092,9 +2269,6 @@ static void deparseFuncExpr(FuncExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; - HeapTuple proctup; - Form_pg_proc procform; - const char *proname; bool use_variadic; bool first; ListCell *arg; @@ -2127,29 +2301,15 @@ deparseFuncExpr(FuncExpr *node, deparse_expr_cxt *context) return; } - /* - * Normal function: display as proname(args). - */ - proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(node->funcid)); - if (!HeapTupleIsValid(proctup)) - elog(ERROR, "cache lookup failed for function %u", node->funcid); - procform = (Form_pg_proc) GETSTRUCT(proctup); - /* Check if need to print VARIADIC (cf. ruleutils.c) */ use_variadic = node->funcvariadic; - /* Print schema name only if it's not pg_catalog */ - if (procform->pronamespace != PG_CATALOG_NAMESPACE) - { - const char *schemaname; - - schemaname = get_namespace_name(procform->pronamespace); - appendStringInfo(buf, "%s.", quote_identifier(schemaname)); - } + /* + * Normal function: display as proname(args). + */ + appendFunctionName(node->funcid, context); + appendStringInfoChar(buf, '('); - /* Deparse the function name ... */ - proname = NameStr(procform->proname); - appendStringInfo(buf, "%s(", quote_identifier(proname)); /* ... and all the arguments */ first = true; foreach(arg, node->args) @@ -2162,8 +2322,6 @@ deparseFuncExpr(FuncExpr *node, deparse_expr_cxt *context) first = false; } appendStringInfoChar(buf, ')'); - - ReleaseSysCache(proctup); } /* @@ -2420,6 +2578,151 @@ deparseArrayExpr(ArrayExpr *node, deparse_expr_cxt *context) } /* + * Deparse an Aggref node. + */ +static void +deparseAggref(Aggref *node, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + bool use_variadic; + + /* Only basic, non-split aggregation accepted. */ + Assert(node->aggsplit == AGGSPLIT_SIMPLE); + + /* Check if need to print VARIADIC (cf. ruleutils.c) */ + use_variadic = node->aggvariadic; + + /* Find aggregate name from aggfnoid which is a pg_proc entry */ + appendFunctionName(node->aggfnoid, context); + appendStringInfoChar(buf, '('); + + /* Add DISTINCT */ + appendStringInfo(buf, "%s", (node->aggdistinct != NIL) ? "DISTINCT " : ""); + + if (AGGKIND_IS_ORDERED_SET(node->aggkind)) + { + /* Add WITHIN GROUP (ORDER BY ..) */ + ListCell *arg; + bool first = true; + + Assert(!node->aggvariadic); + Assert(node->aggorder != NIL); + + foreach(arg, node->aggdirectargs) + { + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + deparseExpr((Expr *) lfirst(arg), context); + } + + appendStringInfoString(buf, ") WITHIN GROUP (ORDER BY "); + appendAggOrderBy(node->aggorder, node->args, context); + } + else + { + /* aggstar can be set only in zero-argument aggregates */ + if (node->aggstar) + appendStringInfoChar(buf, '*'); + else + { + ListCell *arg; + bool first = true; + + /* ... and all the arguments */ + foreach(arg, node->args) + { + TargetEntry *tle = (TargetEntry *) lfirst(arg); + Node *n = (Node *) tle->expr; + + if (tle->resjunk) + continue; + + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + /* Add VARIADIC */ + if (use_variadic && lnext(arg) == NULL) + appendStringInfoString(buf, "VARIADIC "); + + deparseExpr((Expr *) n, context); + } + } + + /* Add ORDER BY */ + if (node->aggorder != NIL) + { + appendStringInfoString(buf, " ORDER BY "); + appendAggOrderBy(node->aggorder, node->args, context); + } + } + + /* Add FILTER (WHERE ..) */ + if (node->aggfilter != NULL) + { + appendStringInfoString(buf, ") FILTER (WHERE "); + deparseExpr((Expr *) node->aggfilter, context); + } + + appendStringInfoChar(buf, ')'); +} + +/* + * Append ORDER BY within aggregate function. + */ +static void +appendAggOrderBy(List *orderList, List *targetList, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + ListCell *lc; + bool first = true; + + foreach (lc, orderList) + { + SortGroupClause *srt = (SortGroupClause *) lfirst(lc); + Node *sortexpr; + Oid sortcoltype; + TypeCacheEntry *typentry; + + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + sortexpr = appendSortGroupClause(srt->tleSortGroupRef, targetList, context); + sortcoltype = exprType(sortexpr); + /* See whether operator is default < or > for datatype */ + typentry = lookup_type_cache(sortcoltype, + TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); + if (srt->sortop == typentry->lt_opr) + appendStringInfoString(buf, " ASC"); + else if (srt->sortop == typentry->gt_opr) + appendStringInfoString(buf, " DESC"); + else + { + HeapTuple opertup; + Form_pg_operator operform; + + appendStringInfoString(buf, " USING "); + + /* Append operator name. */ + opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(srt->sortop)); + if (!HeapTupleIsValid(opertup)) + elog(ERROR, "cache lookup failed for operator %u", srt->sortop); + operform = (Form_pg_operator) GETSTRUCT(opertup); + deparseOperatorName(buf, operform); + ReleaseSysCache(opertup); + } + + if (srt->nulls_first) + appendStringInfoString(buf, " NULLS FIRST"); + else + appendStringInfoString(buf, " NULLS LAST"); + } +} + +/* * Print the representation of a parameter to be sent to the remote side. * * Note: we always label the Param's type explicitly rather than relying on @@ -2464,6 +2767,41 @@ printRemotePlaceholder(Oid paramtype, int32 paramtypmod, } /* + * Deparse GROUP BY clause. + */ +static void +appendGroupByClause(List *tlist, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + Query *query = context->root->parse; + ListCell *lc; + bool first = true; + + /* Nothing to be done, if there's no GROUP BY clause in the query. */ + if (!query->groupClause) + return; + + appendStringInfo(buf, " GROUP BY "); + + /* + * Queries with grouping sets are not pushed down, so we don't grouping + * sets here. + */ + Assert(!query->groupingSets); + + foreach(lc, query->groupClause) + { + SortGroupClause *grp = (SortGroupClause *) lfirst(lc); + + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + appendSortGroupClause(grp->tleSortGroupRef, tlist, context); + } +} + +/* * Deparse ORDER BY clause according to the given pathkeys for given base * relation. From given pathkeys expressions belonging entirely to the given * base relation are obtained and deparsed. @@ -2474,7 +2812,7 @@ appendOrderByClause(List *pathkeys, deparse_expr_cxt *context) ListCell *lcell; int nestlevel; char *delim = " "; - RelOptInfo *baserel = context->foreignrel; + RelOptInfo *baserel = context->scanrel; StringInfo buf = context->buf; /* Make sure any constants in the exprs are printed portably */ @@ -2505,3 +2843,67 @@ appendOrderByClause(List *pathkeys, deparse_expr_cxt *context) } reset_transmission_modes(nestlevel); } + +/* + * appendFunctionName + * Deparses function name from given function oid. + */ +static void +appendFunctionName(Oid funcid, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + HeapTuple proctup; + Form_pg_proc procform; + const char *proname; + + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(proctup)) + elog(ERROR, "cache lookup failed for function %u", funcid); + procform = (Form_pg_proc) GETSTRUCT(proctup); + + /* Print schema name only if it's not pg_catalog */ + if (procform->pronamespace != PG_CATALOG_NAMESPACE) + { + const char *schemaname; + + schemaname = get_namespace_name(procform->pronamespace); + appendStringInfo(buf, "%s.", quote_identifier(schemaname)); + } + + /* Deparse the function name ... */ + proname = NameStr(procform->proname); + appendStringInfo(buf, "%s", quote_identifier(proname)); + + ReleaseSysCache(proctup); +} + +/* + * Appends a sort or group clause. + * + * Like get_rule_sortgroupclause(), returns the expression tree, so caller + * need not find it again. + */ +static Node * +appendSortGroupClause(Index ref, List *tlist, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + TargetEntry *tle; + Expr *expr; + + tle = get_sortgroupref_tle(ref, tlist); + expr = tle->expr; + + if (expr && IsA(expr, Const)) + deparseConst((Const *) expr, context, 1); + else if (!expr || IsA(expr, Var)) + deparseExpr(expr, context); + else + { + /* Must force parens for other expressions */ + appendStringInfoString(buf, "("); + deparseExpr(expr, context); + appendStringInfoString(buf, ")"); + } + + return (Node *) expr; +} diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index d97e694..84713b9 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -861,14 +861,13 @@ CREATE OPERATOR === ( -- built-in operators and functions can be shipped for remote execution EXPLAIN (VERBOSE, COSTS OFF) SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); - QUERY PLAN --------------------------------------------------------------------------- - Aggregate - Output: count(c3) - -> Foreign Scan on public.ft1 t1 - Output: c3 - Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" = abs(c2))) -(5 rows) + QUERY PLAN +--------------------------------------------------------------------------- + Foreign Scan + Output: (count(c3)) + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = abs(c2))) +(4 rows) SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); count @@ -878,14 +877,13 @@ SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); EXPLAIN (VERBOSE, COSTS OFF) SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; - QUERY PLAN ---------------------------------------------------------------------- - Aggregate - Output: count(c3) - -> Foreign Scan on public.ft1 t1 - Output: c3 - Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" = c2)) -(5 rows) + QUERY PLAN +---------------------------------------------------------------------- + Foreign Scan + Output: (count(c3)) + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = c2)) +(4 rows) SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; count @@ -937,14 +935,13 @@ ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); -- ... now they can be shipped EXPLAIN (VERBOSE, COSTS OFF) SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); - QUERY PLAN ----------------------------------------------------------------------------------------------- - Aggregate - Output: count(c3) - -> Foreign Scan on public.ft1 t1 - Output: c3 - Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" = public.postgres_fdw_abs(c2))) -(5 rows) + QUERY PLAN +----------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c3)) + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = public.postgres_fdw_abs(c2))) +(4 rows) SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); count @@ -954,14 +951,13 @@ SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); EXPLAIN (VERBOSE, COSTS OFF) SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; - QUERY PLAN ----------------------------------------------------------------------------------------- - Aggregate - Output: count(c3) - -> Foreign Scan on public.ft1 t1 - Output: c3 - Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(public.===) c2)) -(5 rows) + QUERY PLAN +----------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c3)) + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(public.===) c2)) +(4 rows) SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; count @@ -4441,12 +4437,12 @@ SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10; -- Consistent check constraints provide consistent results ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0); EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; - QUERY PLAN -------------------------------------------------------------------- - Aggregate - Output: count(*) - -> Foreign Scan on public.ft1 - Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE ((c2 < 0)) + QUERY PLAN +----------------------------------------------------------------- + Foreign Scan + Output: (count(*)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 < 0)) (4 rows) SELECT count(*) FROM ft1 WHERE c2 < 0; @@ -4485,12 +4481,12 @@ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive; -- But inconsistent check constraints provide inconsistent results ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0); EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; - QUERY PLAN --------------------------------------------------------------------- - Aggregate - Output: count(*) - -> Foreign Scan on public.ft1 - Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE ((c2 >= 0)) + QUERY PLAN +------------------------------------------------------------------ + Foreign Scan + Output: (count(*)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 >= 0)) (4 rows) SELECT count(*) FROM ft1 WHERE c2 >= 0; @@ -5881,3 +5877,2086 @@ AND ftoptions @> array['fetch_size=60000']; (1 row) ROLLBACK; +-- Test Aggregate Push Down +-- Both ft1 and ft2 are used to exercise cost estimates when +-- use_remote_estimate is false and true respectively. +-- A. Simple aggregates +explain (verbose, costs off) +select sum(c1), avg(c1), min(c2), max(c1), stddev(c2) from ft1; + QUERY PLAN +----------------------------------------------------------------------------------------------- + Foreign Scan + Output: (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2) FROM "S 1"."T 1" +(4 rows) + +select sum(c1), avg(c1), min(c2), max(c1), stddev(c2) from ft1; + sum | avg | min | max | stddev +--------+----------------------+-----+------+------------------ + 423821 | 515.5973236009732360 | 1 | 1218 | 233.490919491320 +(1 row) + +select sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2) from "S 1"."T 1"; + sum | avg | min | max | stddev +--------+----------------------+-----+------+------------------ + 423821 | 515.5973236009732360 | 1 | 1218 | 233.490919491320 +(1 row) + +explain (verbose, costs off) +select sum(c1) * random() from ft1; + QUERY PLAN +------------------------------------------------------ + Foreign Scan + Output: (((sum(c1)))::double precision * random()) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum("C 1") FROM "S 1"."T 1" +(4 rows) + +explain (verbose, costs off) +select count(c6) from ft1; + QUERY PLAN +------------------------------------------------- + Foreign Scan + Output: (count(c6)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c6) FROM "S 1"."T 1" +(4 rows) + +select count(c6) from ft1; + count +------- + 801 +(1 row) + +select count(c6) from "S 1"."T 1"; + count +------- + 801 +(1 row) + +-- Aggregate is not pushed down as aggregation contains random() +explain (verbose, costs off) +select sum(c1 * random()), avg(c1) from ft1; + QUERY PLAN +------------------------------------------------------------- + Aggregate + Output: sum(((c1)::double precision * random())), avg(c1) + -> Foreign Scan on public.ft1 + Output: c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" +(5 rows) + +explain (verbose, costs off) +select sum(c1) from ft2 where c2 < 5; + QUERY PLAN +------------------------------------------------------------------- + Foreign Scan + Output: (sum(c1)) + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT sum("C 1") FROM "S 1"."T 1" WHERE ((c2 < 5)) +(4 rows) + +select sum(c1) from ft2 where c2 < 5; + sum +------- + 50711 +(1 row) + +select sum("C 1") from "S 1"."T 1" where c2 < 5; + sum +------- + 50711 +(1 row) + +explain (verbose, costs off) +select sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (sum(t1.c1)), (avg(t2.c1)) + Relations: Aggregate on ((public.ft1 t1) INNER JOIN (public.ft1 t2)) + Remote SQL: SELECT sum(r1."C 1"), avg(r2."C 1") FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2.c2 = 6)) AND ((r1.c2 = 6)))) +(4 rows) + +select sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; + sum | avg +---------+---------------------- + 5010000 | 501.0000000000000000 +(1 row) + +select sum(t1."C 1"), avg(t1."C 1") from "S 1"."T 1" t1 inner join "S 1"."T 1" t2 on (t1.c2 = t2.c2) where t1.c2 = 6; + sum | avg +---------+---------------------- + 5010000 | 501.0000000000000000 +(1 row) + +explain (verbose, costs off) +select count(*) from ft1 t1 inner join ft2 t2 on (t1.c2 = t2.c2) where t2.c2 = 6; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(*)) + Relations: Aggregate on ((public.ft1 t1) INNER JOIN (public.ft2 t2)) + Remote SQL: SELECT count(*) FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2.c2 = 6)) AND ((r1.c2 = 6)))) +(4 rows) + +select count(*) from ft1 t1 inner join ft2 t2 on (t1.c2 = t2.c2) where t2.c2 = 6; + count +------- + 10000 +(1 row) + +select count(*) from "S 1"."T 1" t1 inner join "S 1"."T 1" t2 on (t1.c2 = t2.c2) where t2.c2 = 6; + count +------- + 10000 +(1 row) + +-- Not pushed down due to local conditions present in underneath input rel +explain (verbose, costs off) +select count(*) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2 * random()); + QUERY PLAN +------------------------------------------------------------------------------------------- + Aggregate + Output: count(*) + -> Nested Loop + Join Filter: ((t1.c2)::double precision = ((t2.c2)::double precision * random())) + -> Foreign Scan on public.ft1 t1 + Output: t1.c2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" + -> Materialize + Output: t2.c2 + -> Foreign Scan on public.ft1 t2 + Output: t2.c2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" +(12 rows) + +-- B. Aggregates with GROUP BY +explain (verbose, costs off) +select c2, count(*) from ft1 group by c2 order by 1; + QUERY PLAN +---------------------------------------------------------------------- + Sort + Output: c2, (count(*)) + Sort Key: ft1.c2 + -> Foreign Scan + Output: c2, (count(*)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, count(*) FROM "S 1"."T 1" GROUP BY c2 +(7 rows) + +select c2, count(*) from ft1 group by c2 order by 1; + c2 | count +-----+------- + 1 | 99 + 2 | 2 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 + 503 | 1 + 507 | 2 + 509 | 100 + 608 | 100 + 609 | 2 + 708 | 2 + 818 | 2 +(21 rows) + +select c2, count(*) from "S 1"."T 1" group by c2 order by 1; + c2 | count +-----+------- + 1 | 99 + 2 | 2 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 + 503 | 1 + 507 | 2 + 509 | 100 + 608 | 100 + 609 | 2 + 708 | 2 + 818 | 2 +(21 rows) + +explain (verbose, costs off) +select count(c1), length(c6) from ft2 group by c6 order by 1, 2; + QUERY PLAN +-------------------------------------------------------------------------------------- + Sort + Output: (count(c1)), (length((c6)::text)), c6 + Sort Key: (count(ft2.c1)), (length((ft2.c6)::text)) + -> Foreign Scan + Output: (count(c1)), (length((c6)::text)), c6 + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT count("C 1"), length(c6), c6 FROM "S 1"."T 1" GROUP BY c6 +(7 rows) + +select count(c1), length(c6) from ft2 group by c6 order by 1, 2; + count | length +-------+-------- + 1 | 4 + 21 | + 100 | 1 + 100 | 1 + 100 | 1 + 100 | 1 + 100 | 1 + 100 | 1 + 100 | 1 + 100 | 1 +(10 rows) + +select count("C 1"), length(c6) from "S 1"."T 1" group by c6 order by 1, 2; + count | length +-------+-------- + 1 | 4 + 21 | + 100 | 1 + 100 | 1 + 100 | 1 + 100 | 1 + 100 | 1 + 100 | 1 + 100 | 1 + 100 | 1 +(10 rows) + +explain (verbose, costs off) +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; + QUERY PLAN +------------------------------------------------------------------------------------------------ + Sort + Output: ((c2 / 2)), ((sum(c2) * (c2 / 2))) + Sort Key: ((ft1.c2 / 2)) + -> Foreign Scan + Output: ((c2 / 2)), ((sum(c2) * (c2 / 2))) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT (c2 / 2), (sum(c2) * (c2 / 2)) FROM "S 1"."T 1" GROUP BY ((c2 / 2)) +(7 rows) + +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; + ?column? | ?column? +----------+---------- + 0 | 0 + 1 | 4 + 3 | 1800 + 21 | 88200 + 22 | 96800 + 50 | 20100 + 52 | 10816 + 53 | 11236 + 100 | 20100 + 102 | 20808 + 151 | 4575300 + 201 | 162006 + 203 | 8262100 + 251 | 126253 + 253 | 256542 + 254 | 12928600 + 304 | 18853472 + 354 | 501264 + 409 | 669124 +(19 rows) + +select c2/2, sum(c2) * (c2/2) from "S 1"."T 1" group by c2/2 order by c2/2; + ?column? | ?column? +----------+---------- + 0 | 0 + 1 | 4 + 3 | 1800 + 21 | 88200 + 22 | 96800 + 50 | 20100 + 52 | 10816 + 53 | 11236 + 100 | 20100 + 102 | 20808 + 151 | 4575300 + 201 | 162006 + 203 | 8262100 + 251 | 126253 + 253 | 256542 + 254 | 12928600 + 304 | 18853472 + 354 | 501264 + 409 | 669124 +(19 rows) + +explain (verbose, costs off) +select count(a), sum(a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; + QUERY PLAN +---------------------------------------------------------------------------------------------------------- + Aggregate + Output: count(ft1.c2), sum(ft1.c2) + -> Sort + Output: ft1.c2, (sum(ft1.c1)), (sqrt((ft1.c1)::double precision)) + Sort Key: ft1.c2, (sum(ft1.c1)) + -> Foreign Scan + Output: ft1.c2, (sum(ft1.c1)), (sqrt((ft1.c1)::double precision)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, sum("C 1"), sqrt("C 1") FROM "S 1"."T 1" GROUP BY c2, (sqrt("C 1")) +(9 rows) + +select count(a), sum(a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; + count | sum +-------+-------- + 822 | 199823 +(1 row) + +select count(a), sum(a) from (select c2 a, sum("C 1") b from "S 1"."T 1" group by c2, sqrt("C 1") order by 1, 2) x; + count | sum +-------+-------- + 822 | 199823 +(1 row) + +-- Aggregate is still pushed down by taking random() out +explain (verbose, costs off) +select c1 * random(), sum(c1) * c1 from ft1 group by c1; + QUERY PLAN +---------------------------------------------------------------------------------- + Foreign Scan + Output: ((c1)::double precision * random()), ((sum(c1) * c1)), c1 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT (sum("C 1") * "C 1"), "C 1" FROM "S 1"."T 1" GROUP BY "C 1" +(4 rows) + +--select c1 * random(), sum(c1) * c1 from ft1 group by c1; +--select "C 1" * random(), sum("C 1") * "C 1" from "S 1"."T 1" group by "C 1"; +-- Aggregate is not pushed down as random() is part of group by expression +explain (verbose, costs off) +select c1 * random() from ft2 group by c1 * random(), c2; + QUERY PLAN +-------------------------------------------------------------- + HashAggregate + Output: (((c1)::double precision * random())), c2 + Group Key: ((ft2.c1)::double precision * random()), ft2.c2 + -> Foreign Scan on public.ft2 + Output: ((c1)::double precision * random()), c2 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" +(6 rows) + +explain (verbose, costs off) +select count(c2), 5 from ft1 group by 2; + QUERY PLAN +------------------------------------------------------------------------ + Foreign Scan + Output: (count(c2)), (5) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c2), 5 FROM "S 1"."T 1" GROUP BY 5::integer +(4 rows) + +select count(c2), 5 from ft1 group by 2; + count | ?column? +-------+---------- + 822 | 5 +(1 row) + +select count(c2), 5 from "S 1"."T 1" group by 2; + count | ?column? +-------+---------- + 822 | 5 +(1 row) + +explain (verbose, costs off) +select count(c2) a, 5.0 b from ft1 group by b; + QUERY PLAN +---------------------------------------------------------------------------- + Foreign Scan + Output: (count(c2)), (5.0) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c2), 5.0 FROM "S 1"."T 1" GROUP BY 5.0::numeric +(4 rows) + +select count(c2) a, 5.0 b from ft1 group by b; + a | b +-----+----- + 822 | 5.0 +(1 row) + +select count(c2) a, 5.0 b from "S 1"."T 1" group by b; + a | b +-----+----- + 822 | 5.0 +(1 row) + +explain (verbose, costs off) +select count(c2) from ft1 group by 5::int; + QUERY PLAN +------------------------------------------------------------------------ + Foreign Scan + Output: (count(c2)), (5) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c2), 5 FROM "S 1"."T 1" GROUP BY 5::integer +(4 rows) + +select count(c2) from ft1 group by 5::int; + count +------- + 822 +(1 row) + +select count(c2) from "S 1"."T 1" group by 5::int; + count +------- + 822 +(1 row) + +-- C. Aggregates with HAVING clause +explain (verbose, costs off) +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, (sum(c1)) + Sort Key: ft2.c2 + -> Foreign Scan + Output: c2, (sum(c1)) + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY c2 HAVING ((avg("C 1") < 500::numeric)) +(7 rows) + +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 order by 1; + c2 | sum +-----+------- + 44 | 49900 + 303 | 49800 +(2 rows) + +select c2, sum("C 1") from "S 1"."T 1" group by c2 having avg("C 1") < 500 order by 1; + c2 | sum +-----+------- + 44 | 49900 + 303 | 49800 +(2 rows) + +explain (verbose, costs off) +select c2, sum(c1) from ft1 group by c2 having avg(c1) < 500 and sum(c1) < 200000 order by c2; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, (sum(c1)) + Sort Key: ft1.c2 + -> Foreign Scan + Output: c2, (sum(c1)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY c2 HAVING ((avg("C 1") < 500::numeric)) AND ((sum("C 1") < 200000)) +(7 rows) + +select c2, sum(c1) from ft1 group by c2 having avg(c1) < 500 and sum(c1) < 200000 order by c2; + c2 | sum +-----+------- + 44 | 49900 + 303 | 49800 +(2 rows) + +select c2, sum("C 1") from "S 1"."T 1" group by c2 having avg("C 1") < 500 and sum("C 1") < 200000 order by c2; + c2 | sum +-----+------- + 44 | 49900 + 303 | 49800 +(2 rows) + +explain (verbose, costs off) +select c5, count(c2) from ft1 group by c5, sqrt(c2) having sqrt(max(c2)) < 2 order by 1, 2; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c5, (count(c2)), (sqrt((c2)::double precision)) + Sort Key: ft1.c5, (count(ft1.c2)) + -> Foreign Scan + Output: c5, (count(c2)), (sqrt((c2)::double precision)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c5, count(c2), sqrt(c2) FROM "S 1"."T 1" GROUP BY c5, (sqrt(c2)) HAVING ((sqrt(max(c2)) < 2::double precision)) +(7 rows) + +select c5, count(c2) from ft1 group by c5, sqrt(c2) having sqrt(max(c2)) < 2 order by 1, 2; + c5 | count +--------------------------+------- + Fri Jan 02 00:00:00 1970 | 1 + Fri Jan 02 00:00:00 1970 | 9 + Mon Jan 12 00:00:00 1970 | 10 + Thu Jan 22 00:00:00 1970 | 10 + Sun Feb 01 00:00:00 1970 | 10 + Wed Feb 11 00:00:00 1970 | 10 + Sat Feb 21 00:00:00 1970 | 10 + Tue Mar 03 00:00:00 1970 | 10 + Fri Mar 13 00:00:00 1970 | 10 + Mon Mar 23 00:00:00 1970 | 10 + Thu Apr 02 00:00:00 1970 | 10 + | 1 +(12 rows) + +select c5, count(c2) from "S 1"."T 1" group by c5, sqrt(c2) having sqrt(max(c2)) < 2 order by 1, 2; + c5 | count +--------------------------+------- + Fri Jan 02 00:00:00 1970 | 1 + Fri Jan 02 00:00:00 1970 | 9 + Mon Jan 12 00:00:00 1970 | 10 + Thu Jan 22 00:00:00 1970 | 10 + Sun Feb 01 00:00:00 1970 | 10 + Wed Feb 11 00:00:00 1970 | 10 + Sat Feb 21 00:00:00 1970 | 10 + Tue Mar 03 00:00:00 1970 | 10 + Fri Mar 13 00:00:00 1970 | 10 + Mon Mar 23 00:00:00 1970 | 10 + Thu Apr 02 00:00:00 1970 | 10 + | 1 +(12 rows) + +-- Having clause with random() will be evaluated locally +explain (verbose, costs off) +select c2, sum(c1) from ft2 group by c2 having sum(c1) * random() < 500000 order by c2; + QUERY PLAN +----------------------------------------------------------------------------------------------- + Sort + Output: c2, (sum(c1)) + Sort Key: ft2.c2 + -> Foreign Scan + Output: c2, (sum(c1)) + Filter: ((((sum(ft2.c1)))::double precision * random()) < '500000'::double precision) + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY c2 +(8 rows) + +select c2, sum(c1) from ft2 group by c2 having sum(c1) * random() < 500000 order by c2; + c2 | sum +-----+------- + 1 | 49599 + 2 | 1112 + 6 | 50100 + 42 | 50500 + 44 | 49900 + 100 | 2030 + 101 | 2012 + 104 | 2018 + 106 | 2022 + 201 | 1101 + 204 | 1104 + 303 | 49800 + 403 | 2016 + 407 | 50200 + 503 | 1103 + 507 | 2024 + 509 | 50400 + 608 | 50300 + 609 | 2028 + 708 | 2026 + 818 | 2426 +(21 rows) + +select c2, sum("C 1") from "S 1"."T 1" group by c2 having sum("C 1") * random() < 500000 order by c2; + c2 | sum +-----+------- + 1 | 49599 + 2 | 1112 + 6 | 50100 + 42 | 50500 + 44 | 49900 + 100 | 2030 + 101 | 2012 + 104 | 2018 + 106 | 2022 + 201 | 1101 + 204 | 1104 + 303 | 49800 + 403 | 2016 + 407 | 50200 + 503 | 1103 + 507 | 2024 + 509 | 50400 + 608 | 50300 + 609 | 2028 + 708 | 2026 + 818 | 2426 +(21 rows) + +-- Having clause with random() will be evaluated locally, and other having qual is pushed +explain (verbose, costs off) +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having avg(c1) * random() < 500 and avg(c1) < 500) x; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: count(*) + -> Foreign Scan + Output: ft1.c5, (NULL::bigint), (sqrt((ft1.c2)::double precision)) + Filter: ((((avg(ft1.c1)))::double precision * random()) < '500'::double precision) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c5, NULL::bigint, sqrt(c2), avg("C 1") FROM "S 1"."T 1" GROUP BY c5, (sqrt(c2)) HAVING ((avg("C 1") < 500::numeric)) +(7 rows) + +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having avg(c1) * random() < 500 and avg(c1) < 500) x; + count +------- + 39 +(1 row) + +select count(*) from (select c5, count("C 1") from "S 1"."T 1" group by c5, sqrt(c2) having avg("C 1") * random() < 500 and avg("C 1") < 500) x; + count +------- + 39 +(1 row) + +-- Aggregate in having clause is not pushable, and thus aggregation is not pushed down +explain (verbose, costs off) +select sum(c1) from ft1 group by c2 having avg(c1 * random()) > 100; + QUERY PLAN +------------------------------------------------------------------------------------ + HashAggregate + Output: sum(c1), c2 + Group Key: ft1.c2 + Filter: (avg(((ft1.c1)::double precision * random())) > '100'::double precision) + -> Foreign Scan on public.ft1 + Output: c2, c1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" +(7 rows) + +-- D. ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates +explain (verbose, costs off) +select array_agg(c1 order by c3) from ft1 where c2 = 6 and c1 < 100; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: (array_agg(c1 ORDER BY c3)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT array_agg("C 1" ORDER BY c3 ASC NULLS LAST) FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND ((c2 = 6)) +(4 rows) + +select array_agg(c1 order by c3) from ft1 where c2 = 6 and c1 < 100; + array_agg +-------------------------------- + {6,16,26,36,46,56,66,76,86,96} +(1 row) + +select array_agg("C 1" order by c3) from "S 1"."T 1" where c2 = 6 and "C 1" < 100; + array_agg +-------------------------------- + {6,16,26,36,46,56,66,76,86,96} +(1 row) + +explain (verbose, costs off) +select array_agg(c1 order by c1) from ft2 where c1 < 100 group by c2 order by 1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: (array_agg(c1 ORDER BY c1)), c2 + Sort Key: (array_agg(ft2.c1 ORDER BY ft2.c1)) + -> Foreign Scan + Output: (array_agg(c1 ORDER BY c1)), c2 + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT array_agg("C 1" ORDER BY "C 1" ASC NULLS LAST), c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) GROUP BY c2 +(7 rows) + +select array_agg(c1 order by c1) from ft2 where c1 < 100 group by c2 order by 1; + array_agg +-------------------------------- + {1} + {3,13,23,33,43,53,63,73,83,93} + {4,14,24,34,44,54,64,74,84,94} + {6,16,26,36,46,56,66,76,86,96} + {7,17,27,37,47,57,67,77,87,97} + {8,18,28,38,48,58,68,78,88,98} + {9,19,29,39,49,59,69,79,89,99} + {10,20,30,40,50,60,70,80,90} + {11,21,31,41,51,61,71,81,91} +(9 rows) + +select array_agg("C 1" order by "C 1") from "S 1"."T 1" where "C 1" < 100 group by c2 order by 1; + array_agg +-------------------------------- + {1} + {3,13,23,33,43,53,63,73,83,93} + {4,14,24,34,44,54,64,74,84,94} + {6,16,26,36,46,56,66,76,86,96} + {7,17,27,37,47,57,67,77,87,97} + {8,18,28,38,48,58,68,78,88,98} + {9,19,29,39,49,59,69,79,89,99} + {10,20,30,40,50,60,70,80,90} + {11,21,31,41,51,61,71,81,91} +(9 rows) + +explain (verbose, costs off) +select array_agg(c1 order by c1 desc) from ft1 where c2 = 6 and c1 < 100 group by c2; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(c1 ORDER BY c1 DESC)), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT array_agg("C 1" ORDER BY "C 1" DESC NULLS FIRST), c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND ((c2 = 6)) GROUP BY c2 +(4 rows) + +select array_agg(c1 order by c1 desc) from ft1 where c2 = 6 and c1 < 100 group by c2; + array_agg +-------------------------------- + {96,86,76,66,56,46,36,26,16,6} +(1 row) + +select array_agg("C 1" order by "C 1" desc) from "S 1"."T 1" where c2 = 6 and "C 1" < 100 group by c2; + array_agg +-------------------------------- + {96,86,76,66,56,46,36,26,16,6} +(1 row) + +explain (verbose, costs off) +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(c5 ORDER BY c1 DESC)) + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT array_agg(c5 ORDER BY "C 1" DESC NULLS FIRST) FROM "S 1"."T 1" WHERE (("C 1" < 50)) AND ((c2 = 6)) +(4 rows) + +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; + array_agg +------------------------------------------------------------------------------------------------------------------------------------------ + {"Mon Feb 16 00:00:00 1970","Fri Feb 06 00:00:00 1970","Tue Jan 27 00:00:00 1970","Sat Jan 17 00:00:00 1970","Wed Jan 07 00:00:00 1970"} +(1 row) + +select array_agg(c5 order by "C 1" desc) from "S 1"."T 1" where c2 = 6 and "C 1" < 50; + array_agg +------------------------------------------------------------------------------------------------------------------------------------------ + {"Mon Feb 16 00:00:00 1970","Fri Feb 06 00:00:00 1970","Tue Jan 27 00:00:00 1970","Sat Jan 17 00:00:00 1970","Wed Jan 07 00:00:00 1970"} +(1 row) + +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: (array_agg(DISTINCT (t1.c1 % 5))), ((t2.c1 % 3)) + Sort Key: (array_agg(DISTINCT (t1.c1 % 5))) + -> Foreign Scan + Output: (array_agg(DISTINCT (t1.c1 % 5))), ((t2.c1 % 3)) + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5)), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY ((r2.c1 % 3)) +(7 rows) + +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {0,1,2,3,4} + {1,2,3,NULL} +(2 rows) + +select array_agg(distinct (t1.c1)%5) from "S 1"."T 3" t1 full join "S 1"."T 4" t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {0,1,2,3,4} + {1,2,3,NULL} +(2 rows) + +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Sort + Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5))), ((t2.c1 % 3)) + Sort Key: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5))) + -> Foreign Scan + Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5))), ((t2.c1 % 3)) + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) ASC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY ((r2.c1 % 3)) +(7 rows) + +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {0,1,2,3,4} + {1,2,3,NULL} +(2 rows) + +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from "S 1"."T 3" t1 full join "S 1"."T 4" t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {0,1,2,3,4} + {1,2,3,NULL} +(2 rows) + +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5) DESC)), ((t2.c1 % 3)) + Sort Key: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5) DESC)) + -> Foreign Scan + Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5) DESC)), ((t2.c1 % 3)) + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS FIRST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY ((r2.c1 % 3)) +(7 rows) + +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {4,3,2,1,0} + {NULL,3,2,1} +(2 rows) + +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc) from "S 1"."T 3" t1 full join "S 1"."T 4" t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {4,3,2,1,0} + {NULL,3,2,1} +(2 rows) + +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5) DESC NULLS LAST)), ((t2.c1 % 3)) + Sort Key: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5) DESC NULLS LAST)) + -> Foreign Scan + Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5) DESC NULLS LAST)), ((t2.c1 % 3)) + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY ((r2.c1 % 3)) +(7 rows) + +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {3,2,1,NULL} + {4,3,2,1,0} +(2 rows) + +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from "S 1"."T 3" t1 full join "S 1"."T 4" t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {3,2,1,NULL} + {4,3,2,1,0} +(2 rows) + +explain (verbose, costs off) +select sum(c1%3), sum(distinct c1%3) filter (where c1%3 < 2), c2 from ft1 where c2 = 6 group by c2; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (sum((c1 % 3))), (sum(DISTINCT (c1 % 3)) FILTER (WHERE ((c1 % 3) < 2))), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum(("C 1" % 3)), sum(DISTINCT ("C 1" % 3)) FILTER (WHERE (("C 1" % 3) < 2)), c2 FROM "S 1"."T 1" WHERE ((c2 = 6)) GROUP BY c2 +(4 rows) + +select sum(c1%3), sum(distinct c1%3) filter (where c1%3 < 2), c2 from ft1 where c2 = 6 group by c2; + sum | sum | c2 +-----+-----+---- + 99 | 1 | 6 +(1 row) + +select sum("C 1"%3), sum(distinct "C 1"%3) filter (where "C 1"%3 < 2), c2 from "S 1"."T 1" where c2 = 6 group by c2; + sum | sum | c2 +-----+-----+---- + 99 | 1 | 6 +(1 row) + +-- DISTINCT itself is not pushed down, whereas underneath aggregate is pushed +explain (verbose, costs off) +select distinct sum(c1), c2 from ft2 where c2 < 6 group by c2 order by c2; + QUERY PLAN +----------------------------------------------------------------------------------------------- + Unique + Output: (sum(c1)), c2 + -> Sort + Output: (sum(c1)), c2 + Sort Key: ft2.c2, (sum(ft2.c1)) + -> Foreign Scan + Output: (sum(c1)), c2 + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT sum("C 1"), c2 FROM "S 1"."T 1" WHERE ((c2 < 6)) GROUP BY c2 +(9 rows) + +select distinct sum(c1), c2 from ft2 where c2 < 6 group by c2 order by c2; + sum | c2 +-------+---- + 49599 | 1 + 1112 | 2 +(2 rows) + +select distinct sum("C 1"), c2 from "S 1"."T 1" where c2 < 6 group by c2 order by c2; + sum | c2 +-------+---- + 49599 | 1 + 1112 | 2 +(2 rows) + +explain (verbose, costs off) +select sum(c1) filter (where c1 < 100) from ft1 group by c2 order by 1 nulls last; + QUERY PLAN +----------------------------------------------------------------------------------------------------- + Sort + Output: (sum(c1) FILTER (WHERE (c1 < 100))), c2 + Sort Key: (sum(ft1.c1) FILTER (WHERE (ft1.c1 < 100))) + -> Foreign Scan + Output: (sum(c1) FILTER (WHERE (c1 < 100))), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum("C 1") FILTER (WHERE ("C 1" < 100)), c2 FROM "S 1"."T 1" GROUP BY c2 +(7 rows) + +select sum(c1) filter (where c1 < 100) from ft1 group by c2 order by 1 nulls last; + sum +----- + 1 + 450 + 459 + 480 + 490 + 510 + 520 + 530 + 540 + + + + + + + + + + + + +(21 rows) + +select sum("C 1") filter (where "C 1" < 100) from "S 1"."T 1" group by c2 order by 1 nulls last; + sum +----- + 1 + 450 + 459 + 480 + 490 + 510 + 520 + 530 + 540 + + + + + + + + + + + + +(21 rows) + +explain (verbose, costs off) +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------- + Sort + Output: (sum(c1) FILTER (WHERE ((c1 < 100) AND (c2 > 5)))), c2 + Sort Key: (sum(ft1.c1) FILTER (WHERE ((ft1.c1 < 100) AND (ft1.c2 > 5)))) + -> Foreign Scan + Output: (sum(c1) FILTER (WHERE ((c1 < 100) AND (c2 > 5)))), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum("C 1") FILTER (WHERE (("C 1" < 100) AND (c2 > 5))), c2 FROM "S 1"."T 1" GROUP BY c2 +(7 rows) + +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; + sum +----- + 450 + 480 + 490 + 510 + 520 + 530 + 540 + + + + + + + + + + + + + + +(21 rows) + +select sum("C 1") filter (where "C 1" < 100 and c2 > 5) from "S 1"."T 1" group by c2 order by 1 nulls last; + sum +----- + 450 + 480 + 490 + 510 + 520 + 530 + 540 + + + + + + + + + + + + + + +(21 rows) + +-- Outer query is aggregation query +explain (verbose, costs off) +select distinct (select count(*) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft1 t2 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------- + Unique + Output: ((SubPlan 1)) + -> Sort + Output: ((SubPlan 1)) + Sort Key: ((SubPlan 1)) + -> Foreign Scan + Output: (SubPlan 1) + Relations: Aggregate on (public.ft1 t2) + Remote SQL: SELECT count(*) FILTER (WHERE ((c2 = 6) AND ("C 1" < 10))) FROM "S 1"."T 1" + SubPlan 1 + -> Foreign Scan on public.ft1 t1 + Output: (count(*) FILTER (WHERE ((t2.c2 = 6) AND (t2.c1 < 10)))) + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 6)) +(13 rows) + +select distinct (select count(*) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft1 t2 order by 1; + count +------- + 1 +(1 row) + +select distinct (select count(*) filter (where t2.c2 = 6 and t2."C 1" < 10) from "S 1"."T 1" t1 where t1."C 1" = 6) from "S 1"."T 1" t2 order by 1; + count +------- + 1 +(1 row) + +-- Inner query is aggregation query +explain (verbose, costs off) +select distinct (select count(t1.c1) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft1 t2 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------ + Unique + Output: ((SubPlan 1)) + -> Sort + Output: ((SubPlan 1)) + Sort Key: ((SubPlan 1)) + -> Foreign Scan on public.ft1 t2 + Output: (SubPlan 1) + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" + SubPlan 1 + -> Foreign Scan + Output: (count(t1.c1) FILTER (WHERE ((t2.c2 = 6) AND (t2.c1 < 10)))) + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count("C 1") FILTER (WHERE (($1::integer = 6) AND ($2::integer < 10))) FROM "S 1"."T 1" WHERE (("C 1" = 6)) +(13 rows) + +select distinct (select count(t1.c1) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft1 t2 order by 1; + count +------- + 0 + 1 +(2 rows) + +select distinct (select count(t1."C 1") filter (where t2.c2 = 6 and t2."C 1" < 10) from "S 1"."T 1" t1 where t1."C 1" = 6) from "S 1"."T 1" t2 order by 1; + count +------- + 0 + 1 +(2 rows) + +-- Aggregate not pushed down +explain (verbose, costs off) +select sum(c1) filter (where c1 * random() < 100) from ft1 group by c2; + QUERY PLAN +------------------------------------------------------------------------------------------------------ + HashAggregate + Output: sum(c1) FILTER (WHERE (((c1)::double precision * random()) < '100'::double precision)), c2 + Group Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2, c1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" +(6 rows) + +explain (verbose, costs off) +select sum(c2) filter (where c2 in (select c2 from ft1 where c2 < 5)) from ft1; + QUERY PLAN +------------------------------------------------------------------- + Aggregate + Output: sum(ft1.c2) FILTER (WHERE (hashed SubPlan 1)) + -> Foreign Scan on public.ft1 + Output: ft1.c2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" + SubPlan 1 + -> Foreign Scan on public.ft1 ft1_1 + Output: ft1_1.c2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) +(9 rows) + +explain (verbose, costs off) +select percentile_disc(0.25) within group (order by c2) from ft1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (percentile_disc('0.25'::double precision) WITHIN GROUP (ORDER BY c2)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT percentile_disc(0.25::double precision) WITHIN GROUP (ORDER BY c2 ASC NULLS LAST) FROM "S 1"."T 1" +(4 rows) + +select percentile_disc(0.25) within group (order by c2) from ft1; + percentile_disc +----------------- + 42 +(1 row) + +select percentile_disc(0.25) within group (order by c2) from "S 1"."T 1"; + percentile_disc +----------------- + 42 +(1 row) + +explain (verbose, costs off) +select c2, percentile_cont(0.5) within group (order by c1) from ft1 where c2 < 10 group by c2 order by 1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, (percentile_cont('0.5'::double precision) WITHIN GROUP (ORDER BY ((c1)::double precision))) + Sort Key: ft1.c2 + -> Foreign Scan + Output: c2, (percentile_cont('0.5'::double precision) WITHIN GROUP (ORDER BY ((c1)::double precision))) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, percentile_cont(0.5::double precision) WITHIN GROUP (ORDER BY ("C 1") ASC NULLS LAST) FROM "S 1"."T 1" WHERE ((c2 < 10)) GROUP BY c2 +(7 rows) + +select c2, percentile_cont(0.5) within group (order by c1) from ft1 where c2 < 10 group by c2 order by 1; + c2 | percentile_cont +----+----------------- + 1 | 501 + 2 | 556 + 6 | 501 +(3 rows) + +select c2, percentile_cont(0.5) within group (order by "C 1") from "S 1"."T 1" where c2 < 10 group by c2 order by 1; + c2 | percentile_cont +----+----------------- + 1 | 501 + 2 | 556 + 6 | 501 +(3 rows) + +-- Error from foreign server +explain (verbose, costs off) +select c2, percentile_cont(c2) within group (order by c1) from ft1 group by c2 order by c2; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, (percentile_cont((c2)::double precision) WITHIN GROUP (ORDER BY ((c1)::double precision))) + Sort Key: ft1.c2 + -> Foreign Scan + Output: c2, (percentile_cont((c2)::double precision) WITHIN GROUP (ORDER BY ((c1)::double precision))) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, percentile_cont(c2) WITHIN GROUP (ORDER BY ("C 1") ASC NULLS LAST) FROM "S 1"."T 1" GROUP BY c2 +(7 rows) + +select c2, percentile_cont(c2) within group (order by c1) from ft1 group by c2 order by c2; +ERROR: percentile value 2 is not between 0 and 1 +CONTEXT: Remote SQL command: SELECT c2, percentile_cont(c2) WITHIN GROUP (ORDER BY ("C 1") ASC NULLS LAST) FROM "S 1"."T 1" GROUP BY c2 +select c2, percentile_cont(c2) within group (order by "C 1") from "S 1"."T 1" group by c2 order by c2; +ERROR: percentile value 2 is not between 0 and 1 +explain (verbose, costs off) +select c2, percentile_cont(c2/10::numeric) within group (order by c1) from ft1 where c2 < 10 group by c2 having percentile_cont(c2/10::numeric) within group (order by c1) < 500 order by c2; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, (percentile_cont((((c2)::numeric / '10'::numeric))::double precision) WITHIN GROUP (ORDER BY ((c1)::double precision))) + Sort Key: ft1.c2 + -> Foreign Scan + Output: c2, (percentile_cont((((c2)::numeric / '10'::numeric))::double precision) WITHIN GROUP (ORDER BY ((c1)::double precision))) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, percentile_cont((c2 / 10::numeric)) WITHIN GROUP (ORDER BY ("C 1") ASC NULLS LAST) FROM "S 1"."T 1" WHERE ((c2 < 10)) GROUP BY c2 HAVING ((percentile_cont((c2 / 10::numeric)) WITHIN GROUP (ORDER BY ("C 1") ASC NULLS LAST) < 500::double precision)) +(7 rows) + +select c2, percentile_cont(c2/10::numeric) within group (order by c1) from ft1 where c2 < 10 group by c2 having percentile_cont(c2/10::numeric) within group (order by c1) < 500 order by c2; + c2 | percentile_cont +----+----------------- + 1 | 109 + 2 | 223 +(2 rows) + +select c2, percentile_cont(c2/10::numeric) within group (order by "C 1") from "S 1"."T 1" where c2 < 10 group by c2 having percentile_cont(c2/10::numeric) within group (order by "C 1") < 500 order by c2; + c2 | percentile_cont +----+----------------- + 1 | 109 + 2 | 223 +(2 rows) + +explain (verbose, costs off) +select rank('10'::varchar) within group (order by c6) from ft1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (rank('10'::character varying) WITHIN GROUP (ORDER BY c6)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT rank('10'::character varying) WITHIN GROUP (ORDER BY c6 ASC NULLS LAST) FROM "S 1"."T 1" +(4 rows) + +select rank('10'::varchar) within group (order by c6) from ft1; + rank +------ + 202 +(1 row) + +select rank('10'::varchar) within group (order by c6) from "S 1"."T 1"; + rank +------ + 202 +(1 row) + +explain (verbose, costs off) +select c1, rank(c1, c2) within group (order by c1, c2) from ft1 group by c1, c2 having c1 = 6 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: c1, (rank(c1, c2) WITHIN GROUP (ORDER BY c1, c2)), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT "C 1", rank("C 1", c2) WITHIN GROUP (ORDER BY "C 1" ASC NULLS LAST, c2 ASC NULLS LAST), c2 FROM "S 1"."T 1" WHERE (("C 1" = 6)) GROUP BY "C 1", c2 +(4 rows) + +select c1, rank(c1, c2) within group (order by c1, c2) from ft1 group by c1, c2 having c1 = 6 order by 1; + c1 | rank +----+------ + 6 | 1 +(1 row) + +select "C 1", rank("C 1", c2) within group (order by "C 1", c2) from "S 1"."T 1" group by "C 1", c2 having "C 1" = 6 order by 1; + C 1 | rank +-----+------ + 6 | 1 +(1 row) + +-- User defined function for user defined aggregate, VARIADIC +create function least_accum(anyelement, variadic anyarray) +returns anyelement language sql as + 'select least($1, min($2[i])) from generate_subscripts($2,1) g(i)'; +create aggregate least_agg(variadic items anyarray) ( + stype = anyelement, sfunc = least_accum +); +-- Not pushed down due to user defined aggregate +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; + QUERY PLAN +------------------------------------------------------------- + Sort + Output: c2, (least_agg(VARIADIC ARRAY[c1])) + Sort Key: ft1.c2 + -> HashAggregate + Output: c2, least_agg(VARIADIC ARRAY[c1]) + Group Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2, c1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" +(9 rows) + +-- Add into extension +alter extension postgres_fdw add function least_accum(anyelement, variadic anyarray); +alter extension postgres_fdw add aggregate least_agg(variadic items anyarray); +alter server loopback options (set extensions 'postgres_fdw'); +-- Now aggregate with VARIADIC will be pushed +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------ + Sort + Output: c2, (least_agg(VARIADIC ARRAY[c1])) + Sort Key: ft1.c2 + -> Foreign Scan + Output: c2, (least_agg(VARIADIC ARRAY[c1])) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, public.least_agg(VARIADIC ARRAY["C 1"]) FROM "S 1"."T 1" WHERE ((c2 < 100)) GROUP BY c2 +(7 rows) + +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; + c2 | least_agg +----+----------- + 1 | 11 + 2 | 1 + 6 | 6 + 42 | 10 + 44 | 4 +(5 rows) + +select c2, least_agg("C 1") from "S 1"."T 1" where c2 < 100 group by c2 order by c2; + c2 | least_agg +----+----------- + 1 | 11 + 2 | 1 + 6 | 6 + 42 | 10 + 44 | 4 +(5 rows) + +alter extension postgres_fdw drop function least_accum(anyelement, variadic anyarray); +alter extension postgres_fdw drop aggregate least_agg(variadic items anyarray); +alter server loopback options (set extensions 'postgres_fdw'); +-- Not pushed down as we have dropped objects from extension. +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; + QUERY PLAN +------------------------------------------------------------- + Sort + Output: c2, (least_agg(VARIADIC ARRAY[c1])) + Sort Key: ft1.c2 + -> HashAggregate + Output: c2, least_agg(VARIADIC ARRAY[c1]) + Group Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2, c1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" +(9 rows) + +drop aggregate least_agg(variadic items anyarray); +drop function least_accum(anyelement, variadic anyarray); +-- Testing USING OPERATOR() +create operator public.<^ ( + leftarg = int4, + rightarg = int4, + procedure = int4eq +); +create operator public.=^ ( + leftarg = int4, + rightarg = int4, + procedure = int4lt +); +create operator public.>^ ( + leftarg = int4, + rightarg = int4, + procedure = int4gt +); +create operator family my_op_family using btree; +create function my_op_cmp(a int, b int) returns int as + $$begin return btint4cmp(a, b); end $$ language plpgsql; +create operator class my_op_class for type int using btree family my_op_family as + operator 1 public.<^, + operator 3 public.=^, + operator 5 public.>^, + function 1 my_op_cmp(int, int); +-- This will not be pushed as sort operator is not known to extension. +explain (verbose, costs off) +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; + QUERY PLAN +-------------------------------------------------------------------------------------------- + GroupAggregate + Output: array_agg(c1 ORDER BY c1 USING <^ NULLS LAST), c2 + Group Key: ft2.c2 + -> Foreign Scan on public.ft2 + Output: c2, c1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND ((c2 = 6)) +(6 rows) + +alter extension postgres_fdw add operator class my_op_class using btree; +alter extension postgres_fdw add function my_op_cmp(a int, b int); +alter extension postgres_fdw add operator family my_op_family using btree; +alter extension postgres_fdw add operator public.<^(int, int); +alter extension postgres_fdw add operator public.=^(int, int); +alter extension postgres_fdw add operator public.>^(int, int); +alter server loopback options (set extensions 'postgres_fdw'); +-- Now this will be pushed as sort operator is known to extension. +explain (verbose, costs off) +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(c1 ORDER BY c1 USING <^ NULLS LAST)), c2 + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT array_agg("C 1" ORDER BY "C 1" USING OPERATOR(public.<^) NULLS LAST), c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND ((c2 = 6)) GROUP BY c2 +(4 rows) + +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; + array_agg +-------------------------------- + {6,16,26,36,46,56,66,76,86,96} +(1 row) + +select array_agg("C 1" order by "C 1" using operator(public.<^)) from "S 1"."T 1" where c2 = 6 and "C 1" < 100 group by c2; + array_agg +-------------------------------- + {6,16,26,36,46,56,66,76,86,96} +(1 row) + +alter extension postgres_fdw drop operator class my_op_class using btree; +alter extension postgres_fdw drop function my_op_cmp(a int, b int); +alter extension postgres_fdw drop operator family my_op_family using btree; +alter extension postgres_fdw drop operator public.<^(int, int); +alter extension postgres_fdw drop operator public.=^(int, int); +alter extension postgres_fdw drop operator public.>^(int, int); +alter server loopback options (set extensions 'postgres_fdw'); +-- This will not be pushed as sort operator is not known to extension. +explain (verbose, costs off) +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; + QUERY PLAN +-------------------------------------------------------------------------------------------- + GroupAggregate + Output: array_agg(c1 ORDER BY c1 USING <^ NULLS LAST), c2 + Group Key: ft2.c2 + -> Foreign Scan on public.ft2 + Output: c2, c1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND ((c2 = 6)) +(6 rows) + +drop operator class my_op_class using btree; +drop function my_op_cmp(a int, b int); +drop operator family my_op_family using btree; +drop operator public.>^(int, int); +drop operator public.=^(int, int); +drop operator public.<^(int, int); +-- E. Mix aggregate tests +-- Clauses with random() will be evaluated locally, and other clauses are pushed +explain (verbose, costs off) +select count(*) from (select c2 * random(), count(c1) from ft2 group by c2 having sum(c1) * random() < 100000 and avg(c1) < 50000) x; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: count(*) + -> Foreign Scan + Output: ((ft2.c2)::double precision * random()), (NULL::bigint), ft2.c2 + Filter: ((((sum(ft2.c1)))::double precision * random()) < '100000'::double precision) + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT NULL::bigint, c2, sum("C 1") FROM "S 1"."T 1" GROUP BY c2 HAVING ((avg("C 1") < 50000::numeric)) +(7 rows) + +select count(*) from (select c2 * random(), count(c1) from ft2 group by c2 having sum(c1) * random() < 100000 and avg(c1) < 50000) x; + count +------- + 21 +(1 row) + +select count(*) from (select c2 * random(), count("C 1") from "S 1"."T 1" group by c2 having sum("C 1") * random() < 100000 and avg("C 1") < 50000) x; + count +------- + 21 +(1 row) + +explain (verbose, costs off) +select c2, sum(c2) from ft1 group by c2 having avg(c2) + c2 < 10 order by c2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, (sum(c2)) + Sort Key: ft1.c2 + -> Foreign Scan + Output: c2, (sum(c2)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, sum(c2) FROM "S 1"."T 1" GROUP BY c2 HAVING (((avg(c2) + c2) < 10::numeric)) +(7 rows) + +select c2, sum(c2) from ft1 group by c2 having avg(c2) + c2 < 10 order by c2; + c2 | sum +----+----- + 1 | 99 + 2 | 4 +(2 rows) + +select c2, sum(c2) from "S 1"."T 1" group by c2 having avg(c2) + c2 < 10 order by c2; + c2 | sum +----+----- + 1 | 99 + 2 | 4 +(2 rows) + +-- Input rel to aggregate push down hook is not safe to pushdown and thus the aggregate cannot be pushed down to foreign server +explain (verbose, costs off) +select count(t1.c3) from ft1 t1, ft1 t2 where t1.c1 = postgres_fdw_abs(t1.c2); + QUERY PLAN +---------------------------------------------------------------------------------------------------------- + Aggregate + Output: count(t1.c3) + -> Nested Loop + Output: t1.c3 + -> Foreign Scan on public.ft1 t2 + Remote SQL: SELECT NULL FROM "S 1"."T 1" + -> Materialize + Output: t1.c3 + -> Foreign Scan on public.ft1 t1 + Output: t1.c3 + Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" = public.postgres_fdw_abs(c2))) +(11 rows) + +-- Subquery in FROM clause having aggregate +explain (verbose, costs off) +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; + QUERY PLAN +------------------------------------------------------------------------------------------------ + Sort + Output: (count(*)), x.b + Sort Key: (count(*)), x.b + -> HashAggregate + Output: count(*), x.b + Group Key: x.b + -> Hash Join + Output: x.b + Hash Cond: (ft1.c2 = x.a) + -> Foreign Scan on public.ft1 + Output: ft1.c2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" + -> Hash + Output: x.b, x.a + -> Subquery Scan on x + Output: x.b, x.a + -> Foreign Scan + Output: ft1_1.c2, (sum(ft1_1.c1)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY c2 +(20 rows) + +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; + count | b +-------+------- + 1 | 1101 + 1 | 1103 + 1 | 1104 + 2 | 1112 + 2 | 2012 + 2 | 2016 + 2 | 2018 + 2 | 2022 + 2 | 2024 + 2 | 2026 + 2 | 2028 + 2 | 2030 + 2 | 2426 + 99 | 49599 + 100 | 49800 + 100 | 49900 + 100 | 50100 + 100 | 50200 + 100 | 50300 + 100 | 50400 + 100 | 50500 +(21 rows) + +select count(*), x.b from "S 1"."T 1", (select c2 a, sum("C 1") b from "S 1"."T 1" group by c2) x where "S 1"."T 1".c2 = x.a group by x.b order by 1, 2; + count | b +-------+------- + 1 | 1101 + 1 | 1103 + 1 | 1104 + 2 | 1112 + 2 | 2012 + 2 | 2016 + 2 | 2018 + 2 | 2022 + 2 | 2024 + 2 | 2026 + 2 | 2028 + 2 | 2030 + 2 | 2426 + 99 | 49599 + 100 | 49800 + 100 | 49900 + 100 | 50100 + 100 | 50200 + 100 | 50300 + 100 | 50400 + 100 | 50500 +(21 rows) + +-- FULL join with IS NULL check in HAVING +explain (verbose, costs off) +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (avg(t1.c1)), (sum(t2.c1)), t2.c1 + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT avg(r1.c1), sum(r2.c1), r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) GROUP BY r2.c1 HAVING ((((avg(r1.c1) IS NULL) AND (sum(r2.c1) < 10)) OR (sum(r2.c1) IS NULL))) +(4 rows) + +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null; + avg | sum +---------------------+----- + 51.0000000000000000 | + | 3 + | 9 +(3 rows) + +select avg(t1.c1), sum(t2.c1) from "S 1"."T 3" t1 full join "S 1"."T 4" t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null; + avg | sum +---------------------+----- + 51.0000000000000000 | + | 3 + | 9 +(3 rows) + +-- ORDER BY expression is not present as is in target list of remote query. This needed clearing out sortgrouprefs flag. +explain (verbose, costs off) +select (select sum(c2)) from ft1 order by 1; + QUERY PLAN +----------------------------------------------------- + Sort + Output: ((SubPlan 1)) + Sort Key: ((SubPlan 1)) + -> Foreign Scan + Output: (SubPlan 1) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum(c2) FROM "S 1"."T 1" + SubPlan 1 + -> Result + Output: (sum(ft1.c2)) +(10 rows) + +select (select sum(c2)) from ft1 order by 1; + sum +-------- + 199823 +(1 row) + +select (select sum(c2)) from "S 1"."T 1" order by 1; + sum +-------- + 199823 +(1 row) + +-- LATERAL join, with parameterization +set enable_hashagg to false; +explain (verbose, costs off) +select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2.c1 + t1."C 1") sum from ft2 t2 group by t2.c1) qry where t1.c2 * 2 = qry.sum and t1.c2 < 10 order by 1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------- + Sort + Output: t1.c2, qry.sum + Sort Key: t1.c2 + -> Nested Loop + Output: t1.c2, qry.sum + -> Seq Scan on "S 1"."T 1" t1 + Output: t1."C 1", t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Filter: (t1.c2 < 10) + -> Subquery Scan on qry + Output: qry.sum, t2.c1 + Filter: ((t1.c2 * 2) = qry.sum) + -> Foreign Scan + Output: (sum((t2.c1 + t1."C 1"))), t2.c1 + Relations: Aggregate on (public.ft2 t2) + Remote SQL: SELECT sum(("C 1" + $1::integer)), "C 1" FROM "S 1"."T 1" GROUP BY "C 1" +(15 rows) + +select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2.c1 + t1."C 1") sum from ft2 t2 group by t2.c1) qry where t1.c2 * 2 = qry.sum and t1.c2 < 10 order by 1; + c2 | sum +----+----- + 2 | 4 + 6 | 12 +(2 rows) + +select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2."C 1" + t1."C 1") sum from "S 1"."T 1" t2 group by t2."C 1") qry where t1.c2 * 2 = qry.sum and t1.c2 < 10 order by 1; + c2 | sum +----+----- + 2 | 4 + 6 | 12 +(2 rows) + +-- Check with placeHolderVars +explain (verbose, costs off) +select count(ft4.c1), sum(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1) where ft1.c1 = 12) q(a, b, c) on (ft4.c1 = q.b) where ft4.c1 between 10 and 15 group by q.b order by 1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: (count(ft4.c1)), (sum(q.b)), q.b + Sort Key: (count(ft4.c1)) + -> GroupAggregate + Output: count(ft4.c1), sum(q.b), q.b + Group Key: q.b + -> Sort + Output: q.b, ft4.c1 + Sort Key: q.b + -> Hash Left Join + Output: q.b, ft4.c1 + Hash Cond: ((ft4.c1)::numeric = q.b) + -> Foreign Scan on public.ft4 + Output: ft4.c1, ft4.c2, ft4.c3 + Remote SQL: SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 10)) AND ((c1 <= 15)) + -> Hash + Output: q.b + -> Subquery Scan on q + Output: q.b + -> Aggregate + Output: NULL::integer, avg(ft1.c1), NULL::bigint + -> Foreign Scan + Output: ft1.c1 + Relations: (public.ft1) INNER JOIN (public.ft2) + Remote SQL: SELECT r1."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = 12)) AND ((r1."C 1" = 12)))) +(25 rows) + +select count(ft4.c1), sum(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1) where ft1.c1 = 12) q(a, b, c) on (ft4.c1 = q.b) where ft4.c1 between 10 and 15 group by q.b order by 1; + count | sum +-------+----- + 3 | +(1 row) + +select count(t4.c1), sum(q.b) from "S 1"."T 3" t4 left join (select 13, avg(t1."C 1"), sum(t2."C 1") from "S 1"."T 1" t1 right join "S 1"."T 1" t2 on (t1."C 1" = t2."C 1") where t1."C 1" = 12) q(a, b, c) on (t4.c1 = q.b) where t4.c1 between 10 and 15 group by q.b order by 1; + count | sum +-------+----- + 3 | +(1 row) + +explain (verbose, costs off) +select q.a, count(q.a), avg(ft2.c1) from (select 13 from ft1 where c1 = 13) q(a) right join ft2 on (q.a = ft2.c1) where ft2.c1 between 10 and 15 group by q.a order by 1 nulls last; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------- + GroupAggregate + Output: (13), count((13)), avg(ft2.c1) + Group Key: (13) + -> Sort + Output: (13), ft2.c1 + Sort Key: (13) + -> Nested Loop Left Join + Output: (13), ft2.c1 + Join Filter: (13 = ft2.c1) + -> Foreign Scan on public.ft2 + Output: ft2.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" >= 10)) AND (("C 1" <= 15)) ORDER BY "C 1" ASC NULLS LAST + -> Materialize + Output: (13) + -> Foreign Scan on public.ft1 + Output: 13 + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 13)) +(17 rows) + +select q.a, count(q.a), avg(ft2.c1) from (select 13 from ft1 where c1 = 13) q(a) right join ft2 on (q.a = ft2.c1) where ft2.c1 between 10 and 15 group by q.a order by 1 nulls last; + a | count | avg +----+-------+--------------------- + 13 | 1 | 13.0000000000000000 + | 0 | 11.6666666666666667 +(2 rows) + +select q.a, count(q.a), avg("S 1"."T 1"."C 1") from (select 13 from "S 1"."T 1" where "C 1" = 13) q(a) right join "S 1"."T 1" on (q.a = "S 1"."T 1"."C 1") where "S 1"."T 1"."C 1" between 10 and 15 group by q.a order by 1 nulls last; + a | count | avg +----+-------+--------------------- + 13 | 1 | 13.0000000000000000 + | 0 | 11.6666666666666667 +(2 rows) + +-- F. Not supported cases +-- Grouping sets +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; + QUERY PLAN +--------------------------------------------------------------------------------------------------- + GroupAggregate + Output: c2, sum(c1) + Group Key: ft1.c2 + Group Key: () + -> Foreign Scan on public.ft1 + Output: c2, c1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST +(7 rows) + +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; + c2 | sum +----+------- + 1 | 49599 + 2 | 1112 + | 50711 +(3 rows) + +select c2, sum("C 1") from "S 1"."T 1" where c2 < 3 group by rollup(c2) order by 1 nulls last; + c2 | sum +----+------- + 1 | 49599 + 2 | 1112 + | 50711 +(3 rows) + +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; + QUERY PLAN +--------------------------------------------------------------------------------------------------- + GroupAggregate + Output: c2, sum(c1) + Group Key: ft1.c2 + Group Key: () + -> Foreign Scan on public.ft1 + Output: c2, c1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST +(7 rows) + +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; + c2 | sum +----+------- + 1 | 49599 + 2 | 1112 + | 50711 +(3 rows) + +select c2, sum("C 1") from "S 1"."T 1" where c2 < 3 group by cube(c2) order by 1 nulls last; + c2 | sum +----+------- + 1 | 49599 + 2 | 1112 + | 50711 +(3 rows) + +explain (verbose, costs off) +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; + QUERY PLAN +------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, c6, (sum(c1)) + Sort Key: ft1.c2, ft1.c6 + -> GroupAggregate + Output: c2, c6, sum(c1) + Group Key: ft1.c2 + Sort Key: ft1.c6 + Group Key: ft1.c6 + -> Foreign Scan on public.ft1 + Output: c2, c6, c1 + Remote SQL: SELECT "C 1", c2, c6 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST +(11 rows) + +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; + c2 | c6 | sum +----+----+------- + 1 | | 49599 + 2 | | 1112 + | 1 | 49600 + | | 1111 +(4 rows) + +select c2, c6, sum("C 1") from "S 1"."T 1" where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; + c2 | c6 | sum +----+----+------- + 1 | | 49599 + 2 | | 1112 + | 1 | 49600 + | | 1111 +(4 rows) + +explain (verbose, costs off) +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; + QUERY PLAN +--------------------------------------------------------------------------------------------------- + GroupAggregate + Output: c2, sum(c1), GROUPING(c2) + Group Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2, c1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) ORDER BY c2 ASC NULLS LAST +(6 rows) + +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; + c2 | sum | grouping +----+-------+---------- + 1 | 49599 | 0 + 2 | 1112 | 0 +(2 rows) + +select c2, sum("C 1"), grouping(c2) from "S 1"."T 1" where c2 < 3 group by c2 order by 1 nulls last; + c2 | sum | grouping +----+-------+---------- + 1 | 49599 | 0 + 2 | 1112 | 0 +(2 rows) + +-- WindowAgg +explain (verbose, costs off) +select c2, count(c2) over (partition by c2%2) from ft2 where c2 < 10 order by 1 limit 5 offset 95; + QUERY PLAN +------------------------------------------------------------------------------------ + Limit + Output: c2, (count(c2) OVER (?)), ((c2 % 2)) + -> Sort + Output: c2, (count(c2) OVER (?)), ((c2 % 2)) + Sort Key: ft2.c2 + -> WindowAgg + Output: c2, count(c2) OVER (?), ((c2 % 2)) + -> Sort + Output: ((c2 % 2)), c2 + Sort Key: ((ft2.c2 % 2)) + -> Foreign Scan on public.ft2 + Output: (c2 % 2), c2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 10)) +(13 rows) + +select c2, count(c2) over (partition by c2%2) from ft2 where c2 < 10 order by 1 limit 5 offset 95; + c2 | count +----+------- + 1 | 99 + 1 | 99 + 1 | 99 + 1 | 99 + 2 | 102 +(5 rows) + +select c2, count(c2) over (partition by c2%2) from "S 1"."T 1" where c2 < 10 order by 1 limit 5 offset 95; + c2 | count +----+------- + 1 | 99 + 1 | 99 + 1 | 99 + 1 | 99 + 2 | 102 +(5 rows) + +explain (verbose, costs off) +select c2, count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Sort + Output: c2, (count(c2) OVER (?)), ((c2 % 2)) + Sort Key: ft2.c2 + -> WindowAgg + Output: c2, count(c2) OVER (?), ((c2 % 2)) + -> Sort + Output: c2, ((c2 % 2)) + Sort Key: ((ft2.c2 % 2)) + -> Foreign Scan + Output: c2, ((c2 % 2)) + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT c2, (c2 % 2) FROM "S 1"."T 1" WHERE ((c2 < 10)) GROUP BY c2 +(12 rows) + +select c2, count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; + c2 | count +----+------- + 1 | 1 + 2 | 2 + 6 | 2 +(3 rows) + +select c2, count(c2) over (partition by c2%2) from "S 1"."T 1" where c2 < 10 group by c2 order by 1; + c2 | count +----+------- + 1 | 1 + 2 | 2 + 6 | 2 +(3 rows) + +explain (verbose, costs off) +select c2, sum(c2) over (partition by c2%2 order by c2) from ft1 where c2 < 10 group by c2 order by 1; + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Sort + Output: c2, (sum(c2) OVER (?)), ((c2 % 2)) + Sort Key: ft1.c2 + -> WindowAgg + Output: c2, sum(c2) OVER (?), ((c2 % 2)) + -> Sort + Output: c2, ((c2 % 2)) + Sort Key: ((ft1.c2 % 2)), ft1.c2 + -> Foreign Scan + Output: c2, ((c2 % 2)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, (c2 % 2) FROM "S 1"."T 1" WHERE ((c2 < 10)) GROUP BY c2 +(12 rows) + +select c2, sum(c2) over (partition by c2%2 order by c2) from ft1 where c2 < 10 group by c2 order by 1; + c2 | sum +----+----- + 1 | 1 + 2 | 2 + 6 | 8 +(3 rows) + +select c2, sum(c2) over (partition by c2%2 order by c2) from "S 1"."T 1" where c2 < 10 group by c2 order by 1; + c2 | sum +----+----- + 1 | 1 + 2 | 2 + 6 | 8 +(3 rows) + +explain (verbose, costs off) +select c2, sum(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Sort + Output: c2, (sum(c2) OVER (?)), ((c2 % 2)) + Sort Key: ft1.c2 + -> WindowAgg + Output: c2, sum(c2) OVER (?), ((c2 % 2)) + -> Sort + Output: c2, ((c2 % 2)) + Sort Key: ((ft1.c2 % 2)), ft1.c2 + -> Foreign Scan + Output: c2, ((c2 % 2)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, (c2 % 2) FROM "S 1"."T 1" WHERE ((c2 < 10)) GROUP BY c2 +(12 rows) + +select c2, sum(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; + c2 | sum +----+----- + 1 | 1 + 2 | 8 + 6 | 6 +(3 rows) + +select c2, sum(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from "S 1"."T 1" where c2 < 10 group by c2 order by 1; + c2 | sum +----+----- + 1 | 1 + 2 | 8 + 6 | 6 +(3 rows) + +explain (verbose, costs off) +select sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------- + Sort + Output: (sum(c2)), (count(c2) OVER (?)), c2, ((c2 % 2)) + Sort Key: (sum(ft2.c2)) + -> WindowAgg + Output: (sum(c2)), count(c2) OVER (?), c2, ((c2 % 2)) + -> Sort + Output: c2, ((c2 % 2)), (sum(c2)) + Sort Key: ((ft2.c2 % 2)) + -> Foreign Scan + Output: c2, ((c2 % 2)), (sum(c2)) + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT c2, (c2 % 2), sum(c2) FROM "S 1"."T 1" WHERE ((c2 < 10)) GROUP BY c2 +(12 rows) + +select sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; + sum | count +-----+------- + 4 | 2 + 99 | 1 + 600 | 2 +(3 rows) + +select sum(c2), count(c2) over (partition by c2%2) from "S 1"."T 1" where c2 < 10 group by c2 order by 1; + sum | count +-----+------- + 4 | 2 + 99 | 1 + 600 | 2 +(3 rows) + diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index daf0438..05b2189 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -24,7 +24,9 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "nodes/print.h" #include "optimizer/cost.h" +#include "optimizer/clauses.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/planmain.h" @@ -38,6 +40,7 @@ #include "utils/memutils.h" #include "utils/rel.h" #include "utils/sampling.h" +#include "utils/selfuncs.h" PG_MODULE_MAGIC; @@ -343,6 +346,10 @@ static void postgresGetForeignJoinPaths(PlannerInfo *root, JoinPathExtraData *extra); static bool postgresRecheckForeignScan(ForeignScanState *node, TupleTableSlot *slot); +static void postgresGetForeignUpperPaths(PlannerInfo *root, + UpperRelationKind stage, + RelOptInfo *input_rel, + RelOptInfo *output_rel); /* * Helper functions @@ -400,12 +407,15 @@ static void conversion_error_callback(void *arg); static bool foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, RelOptInfo *outerrel, RelOptInfo *innerrel, JoinPathExtraData *extra); +static bool foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel); static List *get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel); static List *get_useful_ecs_for_relation(PlannerInfo *root, RelOptInfo *rel); static void add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel, Path *epq_path); - +static void add_foreign_grouping_paths(PlannerInfo *root, + RelOptInfo *input_rel, + RelOptInfo *grouped_rel); /* * Foreign-data wrapper handler function: return a struct with pointers @@ -455,6 +465,9 @@ postgres_fdw_handler(PG_FUNCTION_ARGS) /* Support functions for join push-down */ routine->GetForeignJoinPaths = postgresGetForeignJoinPaths; + /* Support functions for upper relation push-down */ + routine->GetForeignUpperPaths = postgresGetForeignUpperPaths; + PG_RETURN_POINTER(routine); } @@ -1120,7 +1133,7 @@ postgresGetForeignPlan(PlannerInfo *root, * rel->baserestrictinfo + parameterization clauses through * scan_clauses. For a join rel->baserestrictinfo is NIL and we are * not considering parameterization right now, so there should be no - * scan_clauses for a joinrel. + * scan_clauses for a joinrel and upper rel either. */ Assert(!scan_clauses); } @@ -1170,7 +1183,8 @@ postgresGetForeignPlan(PlannerInfo *root, local_exprs = lappend(local_exprs, rinfo->clause); } - if (foreignrel->reloptkind == RELOPT_JOINREL) + if (foreignrel->reloptkind == RELOPT_JOINREL || + foreignrel->reloptkind == RELOPT_UPPER_REL) { /* For a join relation, get the conditions from fdw_private structure */ remote_conds = fpinfo->remote_conds; @@ -1191,6 +1205,13 @@ postgresGetForeignPlan(PlannerInfo *root, { ListCell *lc; + /* + * Right now, we only consider grouping and aggregation beyond + * joins. Queries involving aggregates or grouping do not require + * EPQ mechanism, hence should not have an outer plan here. + */ + Assert(foreignrel->reloptkind != RELOPT_UPPER_REL); + outer_plan->targetlist = fdw_scan_tlist; foreach(lc, local_exprs) @@ -1228,7 +1249,8 @@ postgresGetForeignPlan(PlannerInfo *root, remote_conds, retrieved_attrs, makeInteger(fpinfo->fetch_size)); - if (foreignrel->reloptkind == RELOPT_JOINREL) + if (foreignrel->reloptkind == RELOPT_JOINREL || + foreignrel->reloptkind == RELOPT_UPPER_REL) fdw_private = lappend(fdw_private, makeString(fpinfo->relation_name->data)); @@ -2452,7 +2474,8 @@ postgresExplainDirectModify(ForeignScanState *node, ExplainState *es) /* * estimate_path_cost_size * Get cost and size estimates for a foreign scan on given foreign relation - * either a base relation or a join between foreign relations. + * either a base relation or a join between foreign relations or an upper + * relation containing foreign relations. * * param_join_conds are the parameterization clauses with outer relations. * pathkeys specify the expected sort order if any for given path being costed. @@ -2505,7 +2528,8 @@ estimate_path_cost_size(PlannerInfo *root, &remote_param_join_conds, &local_param_join_conds); /* Build the list of columns to be fetched from the foreign server. */ - if (foreignrel->reloptkind == RELOPT_JOINREL) + if (foreignrel->reloptkind == RELOPT_JOINREL || + foreignrel->reloptkind == RELOPT_UPPER_REL) fdw_scan_tlist = build_tlist_to_deparse(foreignrel); else fdw_scan_tlist = NIL; @@ -2586,25 +2610,7 @@ estimate_path_cost_size(PlannerInfo *root, startup_cost = fpinfo->rel_startup_cost; run_cost = fpinfo->rel_total_cost - fpinfo->rel_startup_cost; } - else if (foreignrel->reloptkind != RELOPT_JOINREL) - { - /* Clamp retrieved rows estimates to at most foreignrel->tuples. */ - retrieved_rows = Min(retrieved_rows, foreignrel->tuples); - - /* - * Cost as though this were a seqscan, which is pessimistic. We - * effectively imagine the local_conds are being evaluated - * remotely, too. - */ - startup_cost = 0; - run_cost = 0; - run_cost += seq_page_cost * foreignrel->pages; - - startup_cost += foreignrel->baserestrictcost.startup; - cpu_per_tuple = cpu_tuple_cost + foreignrel->baserestrictcost.per_tuple; - run_cost += cpu_per_tuple * foreignrel->tuples; - } - else + else if (foreignrel->reloptkind == RELOPT_JOINREL) { PgFdwRelationInfo *fpinfo_i; PgFdwRelationInfo *fpinfo_o; @@ -2670,6 +2676,97 @@ estimate_path_cost_size(PlannerInfo *root, run_cost += nrows * remote_conds_cost.per_tuple; run_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows; } + else if (foreignrel->reloptkind == RELOPT_UPPER_REL) + { + PgFdwRelationInfo *ofpinfo; + PathTarget *ptarget = root->upper_targets[UPPERREL_GROUP_AGG]; + AggClauseCosts aggcosts; + double input_rows; + int numGroupCols; + double numGroups = 1; + + /* + * This cost model is mixture of costing done for sorted and hashed + * aggregates in cost_agg(). We are not sure which strategy will + * be considered at remote side, thus for simplicity, we put all + * startup related costs in startup_cost and all finalization and + * run cost are added in total_cost. + * + * Also, core does not care about costing HAVING expressions and + * adding that to the costs. So similarly, here too we are not + * considering remote and local conditions for costing. + */ + + ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private; + + /* Get rows and width from input rel */ + input_rows = ofpinfo->rows; + width = ofpinfo->width; + + /* Collect statistics about aggregates for estimating costs. */ + MemSet(&aggcosts, 0, sizeof(AggClauseCosts)); + if (root->parse->hasAggs) + { + get_agg_clause_costs(root, (Node *) fpinfo->grouped_tlist, + AGGSPLIT_SIMPLE, &aggcosts); + get_agg_clause_costs(root, (Node *) root->parse->havingQual, + AGGSPLIT_SIMPLE, &aggcosts); + } + + /* Get number of grouping columns and possible number of groups */ + numGroupCols = list_length(root->parse->groupClause); + numGroups = estimate_num_groups(root, + get_sortgrouplist_exprs(root->parse->groupClause, + fpinfo->grouped_tlist), + input_rows, NULL); + + /* + * Number of rows expected from foreign server will be same as that + * of number of groups. + */ + rows = retrieved_rows = numGroups; + + /* + * Startup cost includes: + * 1. Startup cost for underneath input relation + * 2. Cost of performing aggregation, per cost_agg() + * 3. Startup cost for PathTarget eval + */ + startup_cost = ofpinfo->rel_startup_cost; + startup_cost += aggcosts.transCost.startup; + startup_cost += aggcosts.transCost.per_tuple * input_rows; + startup_cost += (cpu_operator_cost * numGroupCols) * input_rows; + startup_cost += ptarget->cost.startup; + + /* + * Run time cost includes: + * 1. Run time cost of underneath input relation + * 2. Run time cost of performing aggregation, per cost_agg() + * 4. PathTarget eval cost for each output row + */ + run_cost = ofpinfo->rel_total_cost - ofpinfo->rel_startup_cost; + run_cost += aggcosts.finalCost * numGroups; + run_cost += cpu_tuple_cost * numGroups; + run_cost += ptarget->cost.per_tuple * numGroups; + } + else + { + /* Clamp retrieved rows estimates to at most foreignrel->tuples. */ + retrieved_rows = Min(retrieved_rows, foreignrel->tuples); + + /* + * Cost as though this were a seqscan, which is pessimistic. We + * effectively imagine the local_conds are being evaluated + * remotely, too. + */ + startup_cost = 0; + run_cost = 0; + run_cost += seq_page_cost * foreignrel->pages; + + startup_cost += foreignrel->baserestrictcost.startup; + cpu_per_tuple = cpu_tuple_cost + foreignrel->baserestrictcost.per_tuple; + run_cost += cpu_per_tuple * foreignrel->tuples; + } /* * Without remote estimates, we have no real way to estimate the cost @@ -4343,6 +4440,331 @@ postgresGetForeignJoinPaths(PlannerInfo *root, } /* + * Assess whether the aggregation, grouping and having operations can be pushed + * down to the foreign server. As a side effect, save information we obtain in + * this function to PgFdwRelationInfo of the input relation. + */ +static bool +foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel) +{ + Query *query = root->parse; + PathTarget *grouping_target; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) grouped_rel->fdw_private; + PgFdwRelationInfo *ofpinfo; + List *aggvars; + ListCell *lc; + int i; + List *tlist = NIL; + + /* Grouping Sets are not pushable */ + if (query->groupingSets) + return false; + + /* Get the fpinfo of the underlying scan relation. */ + ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private; + + /* + * If underneath input relation has any local conditions, those conditions + * are required to be applied before performing aggregation. Hence the + * aggregate cannot be pushed down. + */ + if (ofpinfo->local_conds) + return false; + + /* + * We may need to modify the sortgrouprefs from path target, thus copy it + * so that we will not have any undesired effect. We need to modify the + * sortgrouprefs when it points to one of the ORDER BY expression but not + * to any GROUP BY expression and that expression is not pushed as is. If + * we do not clear such entries, then we will end up into an error. + */ + 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; + foreach(lc, grouping_target->exprs) + { + Expr *expr = (Expr *) lfirst(lc); + Index sgref = get_pathtarget_sortgroupref(grouping_target, i); + ListCell *l; + + /* Check whether this expression is part of GROUP BY clause */ + if (sgref && get_sortgroupref_clause_noerr(sgref, query->groupClause)) + { + /* + * If any of the GROUP BY expression is not shippable we can not + * push down aggregation to the foreign server. + */ + if (!is_foreign_expr(root, grouped_rel, expr)) + return false; + + /* Pushable, add to tlist */ + tlist = add_to_flat_tlist(tlist, list_make1(expr)); + } + else + { + /* Check entire expression whether it is pushable or not */ + if (is_foreign_expr(root, grouped_rel, expr)) + { + /* Pushable, add to tlist */ + tlist = add_to_flat_tlist(tlist, list_make1(expr)); + } + else + { + /* + * 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, + PVC_INCLUDE_AGGREGATES); + + if (!is_foreign_expr(root, grouped_rel, (Expr *) aggvars)) + return false; + + /* + * Add aggregates, if any, into the targetlist. Plain var + * nodes either be same as some GROUP BY expression or part of + * some GROUP BY expression. In later case, the query cannot + * refer plain var nodes without the surrounding expression. + * In both the cases, they are already part of the targetlist + * and thus no need to add them again. In fact adding pulled + * plain var nodes in SELECT clause will cause an error on the + * foreign server if they are not same as some GROUP BY + * expression. + */ + foreach(l, aggvars) + { + Expr *expr = (Expr *) lfirst(l); + + if (IsA(expr, Aggref)) + tlist = add_to_flat_tlist(tlist, list_make1(expr)); + } + } + } + + i++; + } + + /* + * Classify the pushable and non-pushable having clauses and save them in + * remote_conds and local_conds of grouped_rel::fpinfo. + */ + if (root->hasHavingQual && query->havingQual) + { + ListCell *lc; + + foreach(lc, (List *) query->havingQual) + { + Expr *expr = (Expr *) lfirst(lc); + + if (!is_foreign_expr(root, grouped_rel, expr)) + fpinfo->local_conds = lappend(fpinfo->local_conds, expr); + else + fpinfo->remote_conds = lappend(fpinfo->remote_conds, expr); + } + } + + /* + * If there are any local conditions, pull Vars and aggregates from it and + * check whether they are safe to pushdown or not. + */ + if (fpinfo->local_conds) + { + ListCell *lc; + List *aggvars = pull_var_clause((Node *) fpinfo->local_conds, + PVC_INCLUDE_AGGREGATES); + + foreach(lc, aggvars) + { + Expr *expr = (Expr *) lfirst(lc); + + /* + * If aggregates within local conditions are not safe to push down, + * then we cannot push down the query. Vars are already part of + * GROUP BY clause which are checked above, so no need to access + * them again here. + */ + if (IsA(expr, Aggref) && !is_foreign_expr(root, grouped_rel, expr)) + return false; + } + + /* Add Vars and aggregates into the target list. */ + tlist = add_to_flat_tlist(tlist, aggvars); + } + + /* Transfer any sortgroupref data to the replacement tlist */ + apply_pathtarget_labeling_to_tlist(tlist, grouping_target); + /* Store generated targetlist */ + fpinfo->grouped_tlist = tlist; + + /* Safe to pushdown */ + fpinfo->pushdown_safe = true; + + /* + * If user is willing to estimate cost for a scan using EXPLAIN, he intends + * to estimate scans on that relation more accurately. Then, it makes sense + * to estimate the cost of the grouping on that relation more accurately + * using EXPLAIN. + */ + fpinfo->use_remote_estimate = ofpinfo->use_remote_estimate; + + /* Copy startup and tuple cost as is from underneath input rel's fpinfo */ + fpinfo->fdw_startup_cost = ofpinfo->fdw_startup_cost; + fpinfo->fdw_tuple_cost = ofpinfo->fdw_tuple_cost; + + /* + * Set cached relation costs to some negative value, so that we can detect + * when they are set to some sensible costs, during one (usually the + * first) of the calls to estimate_path_cost_size(). + */ + fpinfo->rel_startup_cost = -1; + fpinfo->rel_total_cost = -1; + + /* Set fetch size same as that of underneath input rel's fpinfo */ + fpinfo->fetch_size = ofpinfo->fetch_size; + + /* + * Set the string describing this grouped relation to be used in EXPLAIN + * output of corresponding ForeignScan. + */ + fpinfo->relation_name = makeStringInfo(); + appendStringInfo(fpinfo->relation_name, "Aggregate on (%s)", + ofpinfo->relation_name->data); + + return true; +} + +/* + * postgresGetForeignUpperPaths + * Add paths for post-join operations like aggregation, grouping etc. if + * corresponding operations are safe to push down. + * + * Right now, we only support aggregate, grouping and having clause pushdown. + */ +static void +postgresGetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, + RelOptInfo *input_rel, RelOptInfo *output_rel) +{ + PgFdwRelationInfo *fpinfo; + + /* + * If input rel is not safe to pushdown, then simply return as we cannot + * perform any post-join operations remotely on it. + */ + if (!input_rel->fdw_private || + !((PgFdwRelationInfo *) input_rel->fdw_private)->pushdown_safe) + return; + + /* + * This should not happen normally. In case we have already added paths for + * this output rel, nothing to be done again. + */ + if (output_rel->fdw_private) + return; + + fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo)); + fpinfo->pushdown_safe = false; + output_rel->fdw_private = fpinfo; + + if (stage == UPPERREL_GROUP_AGG) + return add_foreign_grouping_paths(root, input_rel, output_rel); + + /* If it's anything else, we are not adding any foreign path. */ + return; +} + +/* + * add_foreign_grouping_paths + * Add foreign path for grouping and/or aggregation. + * + * Given input_rel represents the underlying scan. The paths are added to the + * given grouped_rel. + */ +static void +add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, + RelOptInfo *grouped_rel) +{ + Query *parse = root->parse; + PgFdwRelationInfo *ifpinfo = input_rel->fdw_private; + PgFdwRelationInfo *fpinfo = grouped_rel->fdw_private; + ForeignPath *grouppath; + PathTarget *grouping_target; + double rows; + int width; + Cost startup_cost; + Cost total_cost; + + /* Nothing to be done, if there is no grouping or aggregation required. */ + if (!parse->groupClause && !parse->groupingSets && !parse->hasAggs && + !root->hasHavingQual) + return; + + grouping_target = root->upper_targets[UPPERREL_GROUP_AGG]; + + /* + * We need relids to deparse column references from target list. + * FIXME: Is this a correct place to copy relids from underneath input_rel + * to the grouped_rel for which we are adding a path here? Or this should + * have been already set by core itself? + */ + grouped_rel->relids = bms_copy(input_rel->relids); + + /* save the input_rel as outerrel in fpinfo */ + fpinfo->outerrel = input_rel; + + /* + * Copy foreign table, foreign server, user mapping, shippable extensions + * etc. details from underneath input relation's fpinfo. + */ + fpinfo->table = ifpinfo->table; + fpinfo->server = ifpinfo->server; + fpinfo->user = ifpinfo->user; + fpinfo->shippable_extensions = ifpinfo->shippable_extensions; + + /* Assess if it is safe to push down aggregation and grouping. */ + if (!foreign_grouping_ok(root, grouped_rel)) + return; + + /* Estimate the cost of push down */ + estimate_path_cost_size(root, grouped_rel, NIL, NIL, &rows, + &width, &startup_cost, &total_cost); + + /* Now update this information in the fpinfo */ + fpinfo->rows = rows; + fpinfo->width = width; + fpinfo->startup_cost = startup_cost; + fpinfo->total_cost = total_cost; + + /* Create and add foreign path to the grouping relation. */ + grouppath = create_foreignscan_path(root, + grouped_rel, + grouping_target, + rows, + startup_cost, + total_cost, + NIL, /* no pathkeys */ + NULL, /* no required_outer */ + NULL, + NULL); /* no fdw_private */ + + /* Add generated path into grouped_rel by add_path(). */ + add_path(grouped_rel, (Path *) grouppath); + + return; +} + +/* * Create a tuple from the specified row of the PGresult. * * rel is the local representation of the foreign table, attinmeta is diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h index 67126bc..f8c255e 100644 --- a/contrib/postgres_fdw/postgres_fdw.h +++ b/contrib/postgres_fdw/postgres_fdw.h @@ -92,6 +92,9 @@ typedef struct PgFdwRelationInfo RelOptInfo *innerrel; JoinType jointype; List *joinclauses; + + /* Grouping information */ + List *grouped_tlist; } PgFdwRelationInfo; /* in postgres_fdw.c */ @@ -155,7 +158,7 @@ extern void deparseAnalyzeSql(StringInfo buf, Relation rel, List **retrieved_attrs); extern void deparseStringLiteral(StringInfo buf, const char *val); extern Expr *find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel); -extern List *build_tlist_to_deparse(RelOptInfo *foreign_rel); +extern List *build_tlist_to_deparse(RelOptInfo *foreignrel); extern void deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, List *tlist, List *remote_conds, List *pathkeys, diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index 4f68e89..b79e08f 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -1356,3 +1356,433 @@ WHERE ftrelid = 'table30000'::regclass AND ftoptions @> array['fetch_size=60000']; ROLLBACK; + + +-- Test Aggregate Push Down +-- Both ft1 and ft2 are used to exercise cost estimates when +-- use_remote_estimate is false and true respectively. + +-- A. Simple aggregates +explain (verbose, costs off) +select sum(c1), avg(c1), min(c2), max(c1), stddev(c2) from ft1; +select sum(c1), avg(c1), min(c2), max(c1), stddev(c2) from ft1; +select sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2) from "S 1"."T 1"; + +explain (verbose, costs off) +select sum(c1) * random() from ft1; + +explain (verbose, costs off) +select count(c6) from ft1; +select count(c6) from ft1; +select count(c6) from "S 1"."T 1"; + +-- Aggregate is not pushed down as aggregation contains random() +explain (verbose, costs off) +select sum(c1 * random()), avg(c1) from ft1; + +explain (verbose, costs off) +select sum(c1) from ft2 where c2 < 5; +select sum(c1) from ft2 where c2 < 5; +select sum("C 1") from "S 1"."T 1" where c2 < 5; + +explain (verbose, costs off) +select sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; +select sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; +select sum(t1."C 1"), avg(t1."C 1") from "S 1"."T 1" t1 inner join "S 1"."T 1" t2 on (t1.c2 = t2.c2) where t1.c2 = 6; + +explain (verbose, costs off) +select count(*) from ft1 t1 inner join ft2 t2 on (t1.c2 = t2.c2) where t2.c2 = 6; +select count(*) from ft1 t1 inner join ft2 t2 on (t1.c2 = t2.c2) where t2.c2 = 6; +select count(*) from "S 1"."T 1" t1 inner join "S 1"."T 1" t2 on (t1.c2 = t2.c2) where t2.c2 = 6; + +-- Not pushed down due to local conditions present in underneath input rel +explain (verbose, costs off) +select count(*) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2 * random()); + + +-- B. Aggregates with GROUP BY +explain (verbose, costs off) +select c2, count(*) from ft1 group by c2 order by 1; +select c2, count(*) from ft1 group by c2 order by 1; +select c2, count(*) from "S 1"."T 1" group by c2 order by 1; + +explain (verbose, costs off) +select count(c1), length(c6) from ft2 group by c6 order by 1, 2; +select count(c1), length(c6) from ft2 group by c6 order by 1, 2; +select count("C 1"), length(c6) from "S 1"."T 1" group by c6 order by 1, 2; + +explain (verbose, costs off) +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; +select c2/2, sum(c2) * (c2/2) from "S 1"."T 1" group by c2/2 order by c2/2; + +explain (verbose, costs off) +select count(a), sum(a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; +select count(a), sum(a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; +select count(a), sum(a) from (select c2 a, sum("C 1") b from "S 1"."T 1" group by c2, sqrt("C 1") order by 1, 2) x; + +-- Aggregate is still pushed down by taking random() out +explain (verbose, costs off) +select c1 * random(), sum(c1) * c1 from ft1 group by c1; +--select c1 * random(), sum(c1) * c1 from ft1 group by c1; +--select "C 1" * random(), sum("C 1") * "C 1" from "S 1"."T 1" group by "C 1"; + +-- Aggregate is not pushed down as random() is part of group by expression +explain (verbose, costs off) +select c1 * random() from ft2 group by c1 * random(), c2; + +explain (verbose, costs off) +select count(c2), 5 from ft1 group by 2; +select count(c2), 5 from ft1 group by 2; +select count(c2), 5 from "S 1"."T 1" group by 2; + +explain (verbose, costs off) +select count(c2) a, 5.0 b from ft1 group by b; +select count(c2) a, 5.0 b from ft1 group by b; +select count(c2) a, 5.0 b from "S 1"."T 1" group by b; + +explain (verbose, costs off) +select count(c2) from ft1 group by 5::int; +select count(c2) from ft1 group by 5::int; +select count(c2) from "S 1"."T 1" group by 5::int; + + +-- C. Aggregates with HAVING clause +explain (verbose, costs off) +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 order by 1; +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 order by 1; +select c2, sum("C 1") from "S 1"."T 1" group by c2 having avg("C 1") < 500 order by 1; + +explain (verbose, costs off) +select c2, sum(c1) from ft1 group by c2 having avg(c1) < 500 and sum(c1) < 200000 order by c2; +select c2, sum(c1) from ft1 group by c2 having avg(c1) < 500 and sum(c1) < 200000 order by c2; +select c2, sum("C 1") from "S 1"."T 1" group by c2 having avg("C 1") < 500 and sum("C 1") < 200000 order by c2; + +explain (verbose, costs off) +select c5, count(c2) from ft1 group by c5, sqrt(c2) having sqrt(max(c2)) < 2 order by 1, 2; +select c5, count(c2) from ft1 group by c5, sqrt(c2) having sqrt(max(c2)) < 2 order by 1, 2; +select c5, count(c2) from "S 1"."T 1" group by c5, sqrt(c2) having sqrt(max(c2)) < 2 order by 1, 2; + +-- Having clause with random() will be evaluated locally +explain (verbose, costs off) +select c2, sum(c1) from ft2 group by c2 having sum(c1) * random() < 500000 order by c2; +select c2, sum(c1) from ft2 group by c2 having sum(c1) * random() < 500000 order by c2; +select c2, sum("C 1") from "S 1"."T 1" group by c2 having sum("C 1") * random() < 500000 order by c2; + +-- Having clause with random() will be evaluated locally, and other having qual is pushed +explain (verbose, costs off) +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having avg(c1) * random() < 500 and avg(c1) < 500) x; +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having avg(c1) * random() < 500 and avg(c1) < 500) x; +select count(*) from (select c5, count("C 1") from "S 1"."T 1" group by c5, sqrt(c2) having avg("C 1") * random() < 500 and avg("C 1") < 500) x; + +-- Aggregate in having clause is not pushable, and thus aggregation is not pushed down +explain (verbose, costs off) +select sum(c1) from ft1 group by c2 having avg(c1 * random()) > 100; + + +-- D. ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates +explain (verbose, costs off) +select array_agg(c1 order by c3) from ft1 where c2 = 6 and c1 < 100; +select array_agg(c1 order by c3) from ft1 where c2 = 6 and c1 < 100; +select array_agg("C 1" order by c3) from "S 1"."T 1" where c2 = 6 and "C 1" < 100; + +explain (verbose, costs off) +select array_agg(c1 order by c1) from ft2 where c1 < 100 group by c2 order by 1; +select array_agg(c1 order by c1) from ft2 where c1 < 100 group by c2 order by 1; +select array_agg("C 1" order by "C 1") from "S 1"."T 1" where "C 1" < 100 group by c2 order by 1; + +explain (verbose, costs off) +select array_agg(c1 order by c1 desc) from ft1 where c2 = 6 and c1 < 100 group by c2; +select array_agg(c1 order by c1 desc) from ft1 where c2 = 6 and c1 < 100 group by c2; +select array_agg("C 1" order by "C 1" desc) from "S 1"."T 1" where c2 = 6 and "C 1" < 100 group by c2; + +explain (verbose, costs off) +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; +select array_agg(c5 order by "C 1" desc) from "S 1"."T 1" where c2 = 6 and "C 1" < 50; + +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5) from "S 1"."T 3" t1 full join "S 1"."T 4" t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from "S 1"."T 3" t1 full join "S 1"."T 4" t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc) from "S 1"."T 3" t1 full join "S 1"."T 4" t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from "S 1"."T 3" t1 full join "S 1"."T 4" t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + +explain (verbose, costs off) +select sum(c1%3), sum(distinct c1%3) filter (where c1%3 < 2), c2 from ft1 where c2 = 6 group by c2; +select sum(c1%3), sum(distinct c1%3) filter (where c1%3 < 2), c2 from ft1 where c2 = 6 group by c2; +select sum("C 1"%3), sum(distinct "C 1"%3) filter (where "C 1"%3 < 2), c2 from "S 1"."T 1" where c2 = 6 group by c2; + +-- DISTINCT itself is not pushed down, whereas underneath aggregate is pushed +explain (verbose, costs off) +select distinct sum(c1), c2 from ft2 where c2 < 6 group by c2 order by c2; +select distinct sum(c1), c2 from ft2 where c2 < 6 group by c2 order by c2; +select distinct sum("C 1"), c2 from "S 1"."T 1" where c2 < 6 group by c2 order by c2; + +explain (verbose, costs off) +select sum(c1) filter (where c1 < 100) from ft1 group by c2 order by 1 nulls last; +select sum(c1) filter (where c1 < 100) from ft1 group by c2 order by 1 nulls last; +select sum("C 1") filter (where "C 1" < 100) from "S 1"."T 1" group by c2 order by 1 nulls last; + +explain (verbose, costs off) +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; +select sum("C 1") filter (where "C 1" < 100 and c2 > 5) from "S 1"."T 1" group by c2 order by 1 nulls last; + +-- Outer query is aggregation query +explain (verbose, costs off) +select distinct (select count(*) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft1 t2 order by 1; +select distinct (select count(*) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft1 t2 order by 1; +select distinct (select count(*) filter (where t2.c2 = 6 and t2."C 1" < 10) from "S 1"."T 1" t1 where t1."C 1" = 6) from "S 1"."T 1" t2 order by 1; +-- Inner query is aggregation query +explain (verbose, costs off) +select distinct (select count(t1.c1) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft1 t2 order by 1; +select distinct (select count(t1.c1) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft1 t2 order by 1; +select distinct (select count(t1."C 1") filter (where t2.c2 = 6 and t2."C 1" < 10) from "S 1"."T 1" t1 where t1."C 1" = 6) from "S 1"."T 1" t2 order by 1; + +-- Aggregate not pushed down +explain (verbose, costs off) +select sum(c1) filter (where c1 * random() < 100) from ft1 group by c2; +explain (verbose, costs off) +select sum(c2) filter (where c2 in (select c2 from ft1 where c2 < 5)) from ft1; + +explain (verbose, costs off) +select percentile_disc(0.25) within group (order by c2) from ft1; +select percentile_disc(0.25) within group (order by c2) from ft1; +select percentile_disc(0.25) within group (order by c2) from "S 1"."T 1"; + +explain (verbose, costs off) +select c2, percentile_cont(0.5) within group (order by c1) from ft1 where c2 < 10 group by c2 order by 1; +select c2, percentile_cont(0.5) within group (order by c1) from ft1 where c2 < 10 group by c2 order by 1; +select c2, percentile_cont(0.5) within group (order by "C 1") from "S 1"."T 1" where c2 < 10 group by c2 order by 1; + +-- Error from foreign server +explain (verbose, costs off) +select c2, percentile_cont(c2) within group (order by c1) from ft1 group by c2 order by c2; +select c2, percentile_cont(c2) within group (order by c1) from ft1 group by c2 order by c2; +select c2, percentile_cont(c2) within group (order by "C 1") from "S 1"."T 1" group by c2 order by c2; + +explain (verbose, costs off) +select c2, percentile_cont(c2/10::numeric) within group (order by c1) from ft1 where c2 < 10 group by c2 having percentile_cont(c2/10::numeric) within group (order by c1) < 500 order by c2; +select c2, percentile_cont(c2/10::numeric) within group (order by c1) from ft1 where c2 < 10 group by c2 having percentile_cont(c2/10::numeric) within group (order by c1) < 500 order by c2; +select c2, percentile_cont(c2/10::numeric) within group (order by "C 1") from "S 1"."T 1" where c2 < 10 group by c2 having percentile_cont(c2/10::numeric) within group (order by "C 1") < 500 order by c2; + +explain (verbose, costs off) +select rank('10'::varchar) within group (order by c6) from ft1; +select rank('10'::varchar) within group (order by c6) from ft1; +select rank('10'::varchar) within group (order by c6) from "S 1"."T 1"; + +explain (verbose, costs off) +select c1, rank(c1, c2) within group (order by c1, c2) from ft1 group by c1, c2 having c1 = 6 order by 1; +select c1, rank(c1, c2) within group (order by c1, c2) from ft1 group by c1, c2 having c1 = 6 order by 1; +select "C 1", rank("C 1", c2) within group (order by "C 1", c2) from "S 1"."T 1" group by "C 1", c2 having "C 1" = 6 order by 1; + +-- User defined function for user defined aggregate, VARIADIC +create function least_accum(anyelement, variadic anyarray) +returns anyelement language sql as + 'select least($1, min($2[i])) from generate_subscripts($2,1) g(i)'; +create aggregate least_agg(variadic items anyarray) ( + stype = anyelement, sfunc = least_accum +); + +-- Not pushed down due to user defined aggregate +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; + +-- Add into extension +alter extension postgres_fdw add function least_accum(anyelement, variadic anyarray); +alter extension postgres_fdw add aggregate least_agg(variadic items anyarray); +alter server loopback options (set extensions 'postgres_fdw'); + +-- Now aggregate with VARIADIC will be pushed +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; +select c2, least_agg("C 1") from "S 1"."T 1" where c2 < 100 group by c2 order by c2; + +alter extension postgres_fdw drop function least_accum(anyelement, variadic anyarray); +alter extension postgres_fdw drop aggregate least_agg(variadic items anyarray); +alter server loopback options (set extensions 'postgres_fdw'); + +-- Not pushed down as we have dropped objects from extension. +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; + +drop aggregate least_agg(variadic items anyarray); +drop function least_accum(anyelement, variadic anyarray); + + +-- Testing USING OPERATOR() +create operator public.<^ ( + leftarg = int4, + rightarg = int4, + procedure = int4eq +); + +create operator public.=^ ( + leftarg = int4, + rightarg = int4, + procedure = int4lt +); + +create operator public.>^ ( + leftarg = int4, + rightarg = int4, + procedure = int4gt +); + +create operator family my_op_family using btree; + +create function my_op_cmp(a int, b int) returns int as + $$begin return btint4cmp(a, b); end $$ language plpgsql; + +create operator class my_op_class for type int using btree family my_op_family as + operator 1 public.<^, + operator 3 public.=^, + operator 5 public.>^, + function 1 my_op_cmp(int, int); + +-- This will not be pushed as sort operator is not known to extension. +explain (verbose, costs off) +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; + +alter extension postgres_fdw add operator class my_op_class using btree; +alter extension postgres_fdw add function my_op_cmp(a int, b int); +alter extension postgres_fdw add operator family my_op_family using btree; +alter extension postgres_fdw add operator public.<^(int, int); +alter extension postgres_fdw add operator public.=^(int, int); +alter extension postgres_fdw add operator public.>^(int, int); +alter server loopback options (set extensions 'postgres_fdw'); + +-- Now this will be pushed as sort operator is known to extension. +explain (verbose, costs off) +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; +select array_agg("C 1" order by "C 1" using operator(public.<^)) from "S 1"."T 1" where c2 = 6 and "C 1" < 100 group by c2; + +alter extension postgres_fdw drop operator class my_op_class using btree; +alter extension postgres_fdw drop function my_op_cmp(a int, b int); +alter extension postgres_fdw drop operator family my_op_family using btree; +alter extension postgres_fdw drop operator public.<^(int, int); +alter extension postgres_fdw drop operator public.=^(int, int); +alter extension postgres_fdw drop operator public.>^(int, int); +alter server loopback options (set extensions 'postgres_fdw'); + +-- This will not be pushed as sort operator is not known to extension. +explain (verbose, costs off) +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; + +drop operator class my_op_class using btree; +drop function my_op_cmp(a int, b int); +drop operator family my_op_family using btree; +drop operator public.>^(int, int); +drop operator public.=^(int, int); +drop operator public.<^(int, int); + + +-- E. Mix aggregate tests +-- Clauses with random() will be evaluated locally, and other clauses are pushed +explain (verbose, costs off) +select count(*) from (select c2 * random(), count(c1) from ft2 group by c2 having sum(c1) * random() < 100000 and avg(c1) < 50000) x; +select count(*) from (select c2 * random(), count(c1) from ft2 group by c2 having sum(c1) * random() < 100000 and avg(c1) < 50000) x; +select count(*) from (select c2 * random(), count("C 1") from "S 1"."T 1" group by c2 having sum("C 1") * random() < 100000 and avg("C 1") < 50000) x; + +explain (verbose, costs off) +select c2, sum(c2) from ft1 group by c2 having avg(c2) + c2 < 10 order by c2; +select c2, sum(c2) from ft1 group by c2 having avg(c2) + c2 < 10 order by c2; +select c2, sum(c2) from "S 1"."T 1" group by c2 having avg(c2) + c2 < 10 order by c2; + +-- Input rel to aggregate push down hook is not safe to pushdown and thus the aggregate cannot be pushed down to foreign server +explain (verbose, costs off) +select count(t1.c3) from ft1 t1, ft1 t2 where t1.c1 = postgres_fdw_abs(t1.c2); + +-- Subquery in FROM clause having aggregate +explain (verbose, costs off) +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; +select count(*), x.b from "S 1"."T 1", (select c2 a, sum("C 1") b from "S 1"."T 1" group by c2) x where "S 1"."T 1".c2 = x.a group by x.b order by 1, 2; + +-- FULL join with IS NULL check in HAVING +explain (verbose, costs off) +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null; +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null; +select avg(t1.c1), sum(t2.c1) from "S 1"."T 3" t1 full join "S 1"."T 4" t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null; + +-- ORDER BY expression is not present as is in target list of remote query. This needed clearing out sortgrouprefs flag. +explain (verbose, costs off) +select (select sum(c2)) from ft1 order by 1; +select (select sum(c2)) from ft1 order by 1; +select (select sum(c2)) from "S 1"."T 1" order by 1; + +-- LATERAL join, with parameterization +set enable_hashagg to false; +explain (verbose, costs off) +select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2.c1 + t1."C 1") sum from ft2 t2 group by t2.c1) qry where t1.c2 * 2 = qry.sum and t1.c2 < 10 order by 1; +select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2.c1 + t1."C 1") sum from ft2 t2 group by t2.c1) qry where t1.c2 * 2 = qry.sum and t1.c2 < 10 order by 1; +select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2."C 1" + t1."C 1") sum from "S 1"."T 1" t2 group by t2."C 1") qry where t1.c2 * 2 = qry.sum and t1.c2 < 10 order by 1; + +-- Check with placeHolderVars +explain (verbose, costs off) +select count(ft4.c1), sum(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1) where ft1.c1 = 12) q(a, b, c) on (ft4.c1 = q.b) where ft4.c1 between 10 and 15 group by q.b order by 1; +select count(ft4.c1), sum(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1) where ft1.c1 = 12) q(a, b, c) on (ft4.c1 = q.b) where ft4.c1 between 10 and 15 group by q.b order by 1; +select count(t4.c1), sum(q.b) from "S 1"."T 3" t4 left join (select 13, avg(t1."C 1"), sum(t2."C 1") from "S 1"."T 1" t1 right join "S 1"."T 1" t2 on (t1."C 1" = t2."C 1") where t1."C 1" = 12) q(a, b, c) on (t4.c1 = q.b) where t4.c1 between 10 and 15 group by q.b order by 1; + +explain (verbose, costs off) +select q.a, count(q.a), avg(ft2.c1) from (select 13 from ft1 where c1 = 13) q(a) right join ft2 on (q.a = ft2.c1) where ft2.c1 between 10 and 15 group by q.a order by 1 nulls last; +select q.a, count(q.a), avg(ft2.c1) from (select 13 from ft1 where c1 = 13) q(a) right join ft2 on (q.a = ft2.c1) where ft2.c1 between 10 and 15 group by q.a order by 1 nulls last; +select q.a, count(q.a), avg("S 1"."T 1"."C 1") from (select 13 from "S 1"."T 1" where "C 1" = 13) q(a) right join "S 1"."T 1" on (q.a = "S 1"."T 1"."C 1") where "S 1"."T 1"."C 1" between 10 and 15 group by q.a order by 1 nulls last; + + +-- F. Not supported cases +-- Grouping sets +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; +select c2, sum("C 1") from "S 1"."T 1" where c2 < 3 group by rollup(c2) order by 1 nulls last; +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; +select c2, sum("C 1") from "S 1"."T 1" where c2 < 3 group by cube(c2) order by 1 nulls last; +explain (verbose, costs off) +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; +select c2, c6, sum("C 1") from "S 1"."T 1" where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; +explain (verbose, costs off) +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; +select c2, sum("C 1"), grouping(c2) from "S 1"."T 1" where c2 < 3 group by c2 order by 1 nulls last; + +-- WindowAgg +explain (verbose, costs off) +select c2, count(c2) over (partition by c2%2) from ft2 where c2 < 10 order by 1 limit 5 offset 95; +select c2, count(c2) over (partition by c2%2) from ft2 where c2 < 10 order by 1 limit 5 offset 95; +select c2, count(c2) over (partition by c2%2) from "S 1"."T 1" where c2 < 10 order by 1 limit 5 offset 95; +explain (verbose, costs off) +select c2, count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; +select c2, count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; +select c2, count(c2) over (partition by c2%2) from "S 1"."T 1" where c2 < 10 group by c2 order by 1; +explain (verbose, costs off) +select c2, sum(c2) over (partition by c2%2 order by c2) from ft1 where c2 < 10 group by c2 order by 1; +select c2, sum(c2) over (partition by c2%2 order by c2) from ft1 where c2 < 10 group by c2 order by 1; +select c2, sum(c2) over (partition by c2%2 order by c2) from "S 1"."T 1" where c2 < 10 group by c2 order by 1; +explain (verbose, costs off) +select c2, sum(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; +select c2, sum(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; +select c2, sum(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from "S 1"."T 1" where c2 < 10 group by c2 order by 1; +explain (verbose, costs off) +select sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; +select sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; +select sum(c2), count(c2) over (partition by c2%2) from "S 1"."T 1" where c2 < 10 group by c2 order by 1;