> While checking for shippability, we build the target list which is passed > to > the foreign server as fdw_scan_tlist. The target list contains > a. All the GROUP BY expressions > b. Shippable entries from the target list of upper relation > c. Var and Aggref nodes from non-shippable entries from the target list of > upper relation >
The code in the patch doesn't seem to add Var nodes explicitly. It assumes that the Var nodes will be part of GROUP BY clause. The code is correct, I think. > d. Var and Aggref nodes from non-shippable HAVING conditions. > This needs to be changed as per the comments below. > > Costing: > > If use_foreign_estimate is true for input relation, like JOIN case, we use > EXPLAIN output to get the cost of query with aggregation/grouping on the > foreign server. If not we calculate the costs locally. Similar to core, we > use > get_agg_clause_costs() to get costs for aggregation and then using logic > similar to cost_agg() we calculate startup and total cost. Since we have no > idea which aggregation strategy will be used at foreign side, we add all > startup cost (startup cost of input relation, aggregates etc.) into startup > cost for the grouping path and similarly for total cost. > This looks OK to me. > > Deparsing the query: > > Target list created while checking for shippability is deparsed using > deparseExplicitTargetList(). sortgroupref are adjusted according to this > target list. Most of the logic to deparse an Aggref is inspired from > get_agg_expr(). For an upper relation, FROM and WHERE clauses come from the > underlying scan relation and thus for simplicity, FROM clause deparsing > logic > is moved from deparseSelectSql() to a new function deparseFromClause(). The > same function adds WHERE clause to the remote SQL. > Ok. > > > Area of future work: > > 1. Adding path with path-keys to push ORDER BY clause along with > aggregation/ > grouping. Should be supported as a separate patch. > > 2. Grouping Sets/Rollup/Cube is not supported in current version. I have > left > this aside to keep patch smaller. If required I can add that support in the > next version of the patch. > I am fine with these limitations for first cut of this feature. I think we should try to measure performance gain because of aggregate pushdown. The EXPLAIN doesn't show actual improvement in the execution times. Here are the comments on the patch. Patch compiles without errors/warnings - Yes make check passes - Yes. make check in contrib/postgres_fdw passes - Yes Attached patch (based on your patch) has some typos corrected, some comments rephrased. It also has some code changes, as explained in various comments below. Please see if those look good. 1. Usually for any deparseSomething function, Something is the type of node produced by the parser when the string output by that function is parsed. deparseColumnRef, for example, produces a string, which when parsed produces a columnRef node. There is are nodes of type FromClause and AggOrderBy. I guess, you meant FromExpr instead of FromClause. deparseAggOrderBy() may be renamed as appendOrderByFromList() or something similar. It may be utilized for window functions if required. 2. Can we infer the value of foragg flag from the RelOptInfo passed to is_foreign_expr()? For any upper relation, it is ok to have aggregate in there. For any other relation aggregate is not expected. 3. In function foreign_grouping_ok(), should we use classifyConditions()? The function is written and used for base relations. There's nothing in that function, which prohibits it being used for other relations. I feel that foreign_join_ok() should have used the same function to classify the other clauses. 4. May be the individual criteria in the comment block + /* + * Aggregate is safe to pushdown if + * 1. It is a built-in aggregate + * 2. All its arguments are safe to push-down + * 3. The functions involved are immutable. + * 4. Other expressions involved like aggorder, aggdistinct are + * safe to be pushed down. + */ should be associated with the code blocks which implement those. As the criteria change it's difficult to maintain the numbered list in sync with the code. 5. The comment + /* Aggregates other than simple one are non-pushable. */ should read /* Only non-split aggregates are pushable. */ as AGGSPLIT_SIMPLE means a complete, non-split aggregation step. 6. An aggregate function has transition, combination and finalization function associated with it. Should we check whether all of the functions are shippable? But probably it suffices to check whether aggregate function as whole is shippable or not using is_shippable() since it's the whole aggregate we are interested in and not the intermediate results. Probably, we should add a comment explaining why it's sufficient to check the aggregate function as a whole. I wondered whether we should check shippability of aggregate return type, but we don't do that for functions as well. So it's fine. 7. It looks like aggdirectargs, aggdistinct, aggorder expressions are all present in args list. If all the expressions in args are shippable, it means those lists are also shippable and hence corresponding aggregate subclauses are shippable. Are we unnecessarily checking shippability of those other lists? 8. The prologue of build_tlist_to_deparse() mentions that the output targetlist contains columns, which is not true with your patch. The targetlist returned by this function can have expressions for upper relations. Please update the prologue to reflect this change. 9. In build_tlist_to_deparse(), we are including aggregates from unshippable conditions without checking whether they are shippable or not. This can cause an unshippable expression to be pushed to the foreign server as seen below explain verbose select avg(c1) from ft1 having avg(c1 * random()) > 100; QUERY PLAN -------------------------------------------------------------------------------------- Foreign Scan (cost=112.50..133.53 rows=1 width=32) Output: (avg(c1)) Filter: ((avg(((ft1.c1)::double precision * random()))) > '100'::double precision) Relations: Aggregate on (public.ft1) Remote SQL: SELECT avg(r1."C 1"), avg((r1."C 1" * random())) FROM "S 1"."T 1" r1 (5 rows) We should check shippability of aggregates in unshippable conditions in foreign_grouping_ok(). If any of those are not shippable, aggregation can not be pushed down. Otherwise, include those in the grouped_tlist. 10. Comment /* Construct FROM clause */ should read "Construct FROM and WHERE clauses." to be in sync with the next function call. deparseFromClause() should be renamed as deparseFromExpr() inline with the naming convention of deparse<something> functions. From the function signature of deparseFromClause(), it doesn't become clear as to what contributes to the FROM clause. Should we add a comment in the prologue of that function explaining the same. Also it strikes odd that for an upper relation, we pass remote_conds to this function, but it doesn't deparse those but deparses the remote conditions from the scan relation and later deparses the same remote_conds as HAVING clause. Although this is correct, the code looks odd. May be the codeblock in deparseFuncClause() 1008 /* 1009 * For aggregates the FROM clause will be build from underneath scan rel. 1010 * WHERE clause conditions too taken from there. 1011 */ 1012 if (foreignrel->reloptkind == RELOPT_UPPER_REL) 1013 { 1014 PgFdwRelationInfo *ofpinfo; 1015 1016 ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private; 1017 scan_rel = fpinfo->outerrel; 1018 context->foreignrel = scan_rel; 1019 remote_conds = ofpinfo->remote_conds; 1020 } should be moved into deparseSelectStmtForRel() to make the things clear. 11. Whether to qualify a column name by an alias should be based on whether the underlying scan relation is a join or base relation scan. That can be achieved by setting scanrel in the deparse_context. Attached patch has this implemented. Instead of scanrel, we may also have use_alias value as part of the context, which is used everywhere. 12. The code to print the function name in deparseAggref() seems similar to that in deparseFuncExpr(). Should we extract it out into a separate function and call that function in deparseAggref() and deparseFuncExpr() both? 13. While deparsing the direct aggregates, the code is not adding "," between two arguments, resulting in error like below. select rank(c1, c2) within group (order by c1, c2) from ft1 group by c1, c2; ERROR: syntax error at or near "c2" CONTEXT: Remote SQL command: SELECT rank("C 1"c2) WITHIN GROUP (ORDER BY "C 1", c2), "C 1", c2 FROM "S 1"."T 1" GROUP BY "C 1", c2 May be we should add support to deparse List expressions by separating list items by "," similar to get_rule_expr() and use that here. 14. In deparseAggOrderBy() we do not output anything for default cases like ASC or NULLS FIRST (for DESC). But like appendOrderByClause() it's better to be explicit and output even the default specifications. That way, we do not leave anything to the interpretation of the foreign server. 15. deparseAggOrderBy supports deparsing USING subclause for ORDER BY for an aggregate, but there is no corresponding shippability check for ordering operator. Also, we should try to produce a testcase for the same. 16. The code to deparse a sort/group reference in deparseAggOrderBy() and appendGroupByClause() looks similar. Should we extract it out into a function by itself and call that function in those two places similar to get_rule_sortgroupclause()? 17. In postgresGetForeignPlan() the code to extract conditions and targetlist is duplicated for join and upper relation. Attached patch removes this duplication. Since DML, FOR SHARE/UPDATE is not allowed with aggregation and grouping, we should get an outer plan in that code. 18. estimate_path_cost_size() has grown quite long (~400 lines). Half of that function deals with estimating costs locally. Should we split this function into smaller functions, one for estimating size and costs of each kind of relations locally? 19. Condition below + if (sgref && query->groupClause && query->groupingSets == NIL && + get_sortgroupref_clause_noerr(sgref, query->groupClause) != NULL) can be rewritten as if (sgref && get_sortgroupref_clause_noerr(sgref, query->groupClause)) since we won't come here with non-NULL query->groupingSets and get_sortgroupref_clause_noerr() will return NULL, if there aren't any groupClause. 20. Please use word "foreign" instead of "remote" in comments. 21. This code looks dubious + /* If not GROUP BY ref, reset it as we are not pushing those */ + if (sgref) + grouping_target->sortgrouprefs[i] = 0; grouping_target comes from RelOptInfo, which is being modified here. We shouldn't be modifying anything in the RelOptInfo while checking whether the aggregate/grouping is shippable. You may want to copy the pathtaget and modify the copy. 22. Following comment needs more elaboration. + /* + * Add aggregates, if any, into tlist. Plain Var nodes pulled + * are already part of GROUP BY and thus no need to add them + * explicitly. + */ Plain Var nodes will either be same as some GROUP BY expression or should be part of some GROUP BY expression. In the later case, the query can not refer those without the surrounding expression. which will be part of the targetlist list. Hence we don't need to add plain Var nodes in the targetlist. In fact adding those in SELECT clause will cause error on the foreign server if they are not part of GROUP BY clause. 23. Probably you don't need to save this in fpinfo. You may want to calculate it whenever it's needed. + fpinfo->grouped_exprs = get_sortgrouplist_exprs(query->groupClause, tlist); 24. Can this ever happen for postgres_fdw? The core sets fdwroutine and calls this function when the underlying scan relation has fdwroutine set, which implies that it called corresponding GetForeignPath hooks, which should have set fdw_private. Nonetheless, I think, still the condition is useful to avoid crashing the server in case the fdw_private is not set. But we need better comments. + /* If input rel is not aware of fdw, simply return */ + if (!input_rel->fdw_private) + return; 25. Although it's desirable to get a switch case in postgresGetForeignUpperPaths() eventually, for this patch I guess we should have an if condition there. 26. Attached patch has restructured code in appendGroupByClause() to reduce indentation and make the code readable. Let me know if this looks good to you. 27. Prologue of create_foreign_grouping_paths() does not have formatting similar to the surrounding functions. Please fix it. Since the function "create"s and "add"s paths, it might be better to rename it as add_foreign_grouping_paths(). 28. Isn't the following true for any upper relation and shouldn't it be part of postgresGetForeignUpperPaths(), rather than create_foreign_grouping_paths()? + /* + * If input rel is not safe to pushdown, we cannot do grouping and/or + * aggregation on it. + */ + if (!ifpinfo || !ifpinfo->pushdown_safe) + return; 29. We require RelOptInfo::relids since create_foreignscan_plan() copies them into ForeignPlan::fs_relids and executor uses them. So, I guess, we have to set those in the core somewhere. May be while calling fetch_upper_rel() in grouping_planner(), we should call it with relids of the underlying scan relation. I don't see any function calling fetch_upper_rel() with non-NULL relids. In fact, if we are to create an upper relation with relids in future, this code is going to wipe that out, which will be undesirable. Also, generally, when copying relids, it's better to use bms_copy() to make a copy of Bitmapset, instead of just assigning the pointer. 30. By the time postgresGetForeignUpperPaths() gets called, the core has already added its own paths, so it doesn't make much sense to set rows and width grouped_rel in create_foreign_grouping_paths(). 31. fpinfo->server and user fields are being set twice, once in create_foreign_grouping_paths() then in foreign_grouping_ok(). Do we need that? 32. + /* + * Do we want to create paths with pathkeys corresponding for + * root->query_pathkeys. + */ Yes, it would be desirable to do that. If we are not going to do that in this patch, may be remove that line or add a TODO kind of comment. 33. The patch marks build_path_tlist() as non-static but does not use it anywhere outside creatplan.c. Is this change intentional? -- Best Wishes, Ashutosh Bapat EnterpriseDB Corporation The Postgres Database Company
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index 5c35414..92a82a0 100644 --- a/contrib/postgres_fdw/deparse.c +++ b/contrib/postgres_fdw/deparse.c @@ -98,7 +98,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; @@ -903,6 +905,7 @@ deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel, List **retrieved_attrs, List **params_list) { deparse_expr_cxt context; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *)rel->fdw_private; /* We handle all relations other than dead one. */ Assert(rel->reloptkind != RELOPT_DEADREL); @@ -911,6 +914,7 @@ deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel, 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 */ @@ -991,10 +995,11 @@ deparseSelectSql(List *tlist, List **retrieved_attrs, deparse_expr_cxt *context) } /* - * Construct a FROM clause and WHERE clause, if any. And append it to "buf". - * The final output contains "SELECT ... FROM ... [WHERE ... ]". + * Construct a FROM clause and a WHERE clause, if any, and append those to + * "buf". * - * remote_conds is the list of WHERE clauses, NIL if none. + * remote_conds is the list of clauses to be included in the WHERE clause, + * NIL if none. */ static void deparseFromClause(List *remote_conds, deparse_expr_cxt *context) @@ -1003,26 +1008,23 @@ deparseFromClause(List *remote_conds, deparse_expr_cxt *context) RelOptInfo *foreignrel = context->foreignrel; PlannerInfo *root = context->root; PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; - RelOptInfo *scan_rel = foreignrel; + RelOptInfo *scan_rel = context->scanrel; /* - * For aggregates the FROM clause will be build from underneath scan rel. - * WHERE clause conditions too taken from there. + * For aggregates the FROM clause will be built from the underneath scan + * relation. Conditions in the WHERE clause are also taken from there. */ if (foreignrel->reloptkind == RELOPT_UPPER_REL) { PgFdwRelationInfo *ofpinfo; ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private; - scan_rel = fpinfo->outerrel; - context->foreignrel = scan_rel; remote_conds = ofpinfo->remote_conds; } /* Construct FROM clause */ appendStringInfoString(buf, " FROM "); deparseFromExprForRel(buf, root, scan_rel, - foreignrel->reloptkind == RELOPT_UPPER_REL ? true : (scan_rel->reloptkind == RELOPT_JOINREL), context->params_list); @@ -1032,10 +1034,6 @@ deparseFromClause(List *remote_conds, deparse_expr_cxt *context) appendStringInfo(buf, " WHERE "); appendConditions(remote_conds, context); } - - /* Restore context's foreignrel */ - if (foreignrel->reloptkind == RELOPT_UPPER_REL) - context->foreignrel = foreignrel; } /* @@ -1138,14 +1136,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) @@ -1366,6 +1364,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; @@ -1534,6 +1533,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; @@ -1618,6 +1618,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; @@ -2044,10 +2045,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 || - context->foreignrel->reloptkind == RELOPT_UPPER_REL); + 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); @@ -2824,42 +2825,45 @@ appendGroupByClause(List *tlist, deparse_expr_cxt *context) { StringInfo buf = context->buf; Query *query = context->root->parse; + ListCell *lc; + bool first = true; - if (query->groupClause != NULL) - { - appendStringInfo(buf, " GROUP BY "); + /* Nothing to be done, if there's no GROUP BY clause in the query. */ + if (!query->groupClause) + return; - if (query->groupingSets == NIL) - { - ListCell *lc; - bool first = true; + appendStringInfo(buf, " GROUP BY "); - foreach(lc, query->groupClause) - { - SortGroupClause *grp = (SortGroupClause *) lfirst(lc); - Index ref = grp->tleSortGroupRef; - TargetEntry *tle; - Expr *expr; + /* + * Queries with grouping sets are not pushed down, so we don't grouping + * sets here. + */ + Assert(!query->groupingSets); - if (!first) - appendStringInfoString(buf, ", "); - first = false; + foreach(lc, query->groupClause) + { + SortGroupClause *grp = (SortGroupClause *) lfirst(lc); + Index ref = grp->tleSortGroupRef; + TargetEntry *tle; + Expr *expr; + + if (!first) + appendStringInfoString(buf, ", "); + first = false; - tle = get_sortgroupref_tle(ref, tlist); - expr = tle->expr; + tle = get_sortgroupref_tle(ref, tlist); + expr = tle->expr; - if (expr && IsA(expr, Const)) - deparseConst((Const *) expr, context); - else if (!expr || IsA(expr, Var)) - deparseExpr(expr, context); - else - { - /* Must force parens for other expressions */ - appendStringInfoString(buf, "("); - deparseExpr(expr, context); - appendStringInfoString(buf, ")"); - } - } + if (expr && IsA(expr, Const)) + deparseConst((Const *) expr, context); + else if (!expr || IsA(expr, Var)) + deparseExpr(expr, context); + else + { + /* Must force parens for other expressions */ + appendStringInfoString(buf, "("); + deparseExpr(expr, context); + appendStringInfoString(buf, ")"); } } } @@ -2875,7 +2879,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 */ diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 6ba1895..8079071 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -861,12 +861,12 @@ 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 ---------------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------- Foreign Scan Output: (count(c3)) Relations: Aggregate on (public.ft1 t1) - Remote SQL: SELECT count(r1.c3) FROM "S 1"."T 1" r1 WHERE (("C 1" = abs(c2))) + 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); @@ -877,12 +877,12 @@ 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 ----------------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------- Foreign Scan Output: (count(c3)) Relations: Aggregate on (public.ft1 t1) - Remote SQL: SELECT count(r1.c3) FROM "S 1"."T 1" r1 WHERE (("C 1" = c2)) + 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; @@ -935,12 +935,12 @@ 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 ------------------------------------------------------------------------------------------------------ + QUERY PLAN +----------------------------------------------------------------------------------------------- Foreign Scan Output: (count(c3)) Relations: Aggregate on (public.ft1 t1) - Remote SQL: SELECT count(r1.c3) FROM "S 1"."T 1" r1 WHERE (("C 1" = public.postgres_fdw_abs(c2))) + 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); @@ -951,12 +951,12 @@ 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 ------------------------------------------------------------------------------------------------ + QUERY PLAN +----------------------------------------------------------------------------------------- Foreign Scan Output: (count(c3)) Relations: Aggregate on (public.ft1 t1) - Remote SQL: SELECT count(r1.c3) FROM "S 1"."T 1" r1 WHERE (("C 1" OPERATOR(public.===) c2)) + 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; @@ -4437,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 --------------------------------------------------------------------- + QUERY PLAN +----------------------------------------------------------------- Foreign Scan Output: (count(*)) Relations: Aggregate on (public.ft1) - Remote SQL: SELECT count(*) FROM "S 1"."T 1" r1 WHERE ((c2 < 0)) + Remote SQL: SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 < 0)) (4 rows) SELECT count(*) FROM ft1 WHERE c2 < 0; @@ -4481,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 ---------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------ Foreign Scan Output: (count(*)) Relations: Aggregate on (public.ft1) - Remote SQL: SELECT count(*) FROM "S 1"."T 1" r1 WHERE ((c2 >= 0)) + Remote SQL: SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 >= 0)) (4 rows) SELECT count(*) FROM ft1 WHERE c2 >= 0; @@ -5883,12 +5883,12 @@ ROLLBACK; -- A. Simple aggregates explain (verbose, costs off) select sum(c1), avg(c1), min(c2), max(c1), stddev(c2) from ft1; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------ + QUERY PLAN +----------------------------------------------------------------------------------------------- Foreign Scan Output: (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)) Relations: Aggregate on (public.ft1) - Remote SQL: SELECT sum(r1."C 1"), avg(r1."C 1"), min(r1.c2), max(r1."C 1"), stddev(r1.c2) FROM "S 1"."T 1" r1 + 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; @@ -5905,22 +5905,22 @@ 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; - QUERY PLAN --------------------------------------------------------- + QUERY PLAN +------------------------------------------------------ Foreign Scan Output: (((sum(c1)))::double precision * random()) Relations: Aggregate on (public.ft1) - Remote SQL: SELECT sum(r1."C 1") FROM "S 1"."T 1" r1 + Remote SQL: SELECT sum("C 1") FROM "S 1"."T 1" (4 rows) explain (verbose, costs off) select count(c6) from ft1; - QUERY PLAN -------------------------------------------------------- + QUERY PLAN +------------------------------------------------- Foreign Scan Output: (count(c6)) Relations: Aggregate on (public.ft1) - Remote SQL: SELECT count(r1.c6) FROM "S 1"."T 1" r1 + Remote SQL: SELECT count(c6) FROM "S 1"."T 1" (4 rows) select count(c6) from ft1; @@ -5949,12 +5949,12 @@ select sum(c1 * random()), avg(c1) from ft1; explain (verbose, costs off) select sum(c1) from ft2 where c2 < 5; - QUERY PLAN -------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------- Foreign Scan Output: (sum(c1)) Relations: Aggregate on (public.ft2) - Remote SQL: SELECT sum(r1."C 1") FROM "S 1"."T 1" r1 WHERE ((c2 < 5)) + Remote SQL: SELECT sum("C 1") FROM "S 1"."T 1" WHERE ((c2 < 5)) (4 rows) select sum(c1) from ft2 where c2 < 5; @@ -6035,15 +6035,15 @@ 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; - QUERY PLAN -------------------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------- Sort Output: c2, (count(*)) Sort Key: ft1.c2 -> Foreign Scan Output: c2, (count(*)) Relations: Aggregate on (public.ft1) - Remote SQL: SELECT r1.c2, count(*) FROM "S 1"."T 1" r1 GROUP BY r1.c2 + 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; @@ -6100,15 +6100,15 @@ 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; - QUERY PLAN ------------------------------------------------------------------------------------------------------ + 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(r1."C 1"), length(r1.c6), r1.c6 FROM "S 1"."T 1" r1 GROUP BY r1.c6 + 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; @@ -6143,15 +6143,15 @@ 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; - QUERY PLAN ---------------------------------------------------------------------------------------------------------------- + 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 (r1.c2 / 2), (sum(r1.c2) * (r1.c2 / 2)) FROM "S 1"."T 1" r1 GROUP BY ((r1.c2 / 2)) + 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; @@ -6204,8 +6204,8 @@ 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; - QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------------------------------------- Aggregate Output: count(ft1.c2), sum(ft1.c2) -> Sort @@ -6214,7 +6214,7 @@ select count(a), sum(a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt( -> Foreign Scan Output: ft1.c2, (sum(ft1.c1)), (sqrt((ft1.c1)::double precision)) Relations: Aggregate on (public.ft1) - Remote SQL: SELECT r1.c2, sum(r1."C 1"), sqrt(r1."C 1") FROM "S 1"."T 1" r1 GROUP BY r1.c2, (sqrt(r1."C 1")) + 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; @@ -6232,12 +6232,12 @@ select count(a), sum(a) from (select c2 a, sum("C 1") b from "S 1"."T 1" group b -- 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 -------------------------------------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------------- Foreign Scan Output: ((c1)::double precision * random()), ((sum(c1) * c1)), c1 Relations: Aggregate on (public.ft1) - Remote SQL: SELECT (sum(r1."C 1") * r1."C 1"), r1."C 1" FROM "S 1"."T 1" r1 GROUP BY r1."C 1" + 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; @@ -6258,15 +6258,15 @@ select c1 * random() from ft2 group by c1 * random(), c2; -- 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 ----------------------------------------------------------------------------------------------------------------------------- + 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 r1.c2, sum(r1."C 1") FROM "S 1"."T 1" r1 GROUP BY r1.c2 HAVING ((avg(r1."C 1") < 500::numeric)) + 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; @@ -6285,15 +6285,15 @@ select c2, sum("C 1") from "S 1"."T 1" group by c2 having avg("C 1") < 500 order 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------ + 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 r1.c2, sum(r1."C 1") FROM "S 1"."T 1" r1 GROUP BY r1.c2 HAVING ((avg(r1."C 1") < 500::numeric)) AND ((sum(r1."C 1") < 200000)) + 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; @@ -6312,15 +6312,15 @@ select c2, sum("C 1") from "S 1"."T 1" group by c2 having avg("C 1") < 500 and s 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------ + 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 r1.c5, count(r1.c2), sqrt(r1.c2) FROM "S 1"."T 1" r1 GROUP BY r1.c5, (sqrt(r1.c2)) HAVING ((sqrt(max(r1.c2)) < 2::double precision)) + 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; @@ -6369,7 +6369,7 @@ select c2, sum(c1) from ft2 group by c2 having sum(c1) * random() < 500000 order Output: c2, (sum(c1)) Filter: ((((sum(ft2.c1)))::double precision * random()) < '500000'::double precision) Relations: Aggregate on (public.ft2) - Remote SQL: SELECT r1.c2, sum(r1."C 1") FROM "S 1"."T 1" r1 GROUP BY r1.c2 + 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; @@ -6427,15 +6427,15 @@ select c2, sum("C 1") from "S 1"."T 1" group by c2 having sum("C 1") * random() -- 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 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 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 r1.c5, NULL::bigint, sqrt(r1.c2), avg(r1."C 1") FROM "S 1"."T 1" r1 GROUP BY r1.c5, (sqrt(r1.c2)) HAVING ((avg(r1."C 1") < 500::numeric)) + 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; @@ -6453,12 +6453,12 @@ select count(*) from (select c5, count("C 1") from "S 1"."T 1" group by c5, sqrt -- 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 ------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------------------------- Foreign Scan Output: (array_agg(c1 ORDER BY c3)) Relations: Aggregate on (public.ft1) - Remote SQL: SELECT array_agg(r1."C 1" ORDER BY r1.c3) FROM "S 1"."T 1" r1 WHERE (("C 1" < 100)) AND ((c2 = 6)) + Remote SQL: SELECT array_agg("C 1" ORDER BY c3) 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; @@ -6475,15 +6475,15 @@ select array_agg("C 1" order by c3) from "S 1"."T 1" where c2 = 6 and "C 1" < 10 explain (verbose, costs off) select array_agg(c1 order by c1) from ft2 where c1 < 100 group by c2 order by 1; - QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------- + 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(r1."C 1" ORDER BY r1."C 1"), r1.c2 FROM "S 1"."T 1" r1 WHERE (("C 1" < 100)) GROUP BY r1.c2 + Remote SQL: SELECT array_agg("C 1" ORDER BY "C 1"), 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; @@ -6516,12 +6516,12 @@ select array_agg("C 1" order by "C 1") from "S 1"."T 1" where "C 1" < 100 group 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 ------------------------------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (array_agg(c1 ORDER BY c1 DESC)), c2 Relations: Aggregate on (public.ft1) - Remote SQL: SELECT array_agg(r1."C 1" ORDER BY r1."C 1" DESC), r1.c2 FROM "S 1"."T 1" r1 WHERE (("C 1" < 100)) AND ((c2 = 6)) GROUP BY r1.c2 + Remote SQL: SELECT array_agg("C 1" ORDER BY "C 1" DESC), 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; @@ -6538,12 +6538,12 @@ select array_agg("C 1" order by "C 1" desc) from "S 1"."T 1" where c2 = 6 and "C explain (verbose, costs off) select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; - QUERY PLAN ----------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (array_agg(c5 ORDER BY c1 DESC)) Relations: Aggregate on (public.ft2) - Remote SQL: SELECT array_agg(r1.c5 ORDER BY r1."C 1" DESC) FROM "S 1"."T 1" r1 WHERE (("C 1" < 50)) AND ((c2 = 6)) + Remote SQL: SELECT array_agg(c5 ORDER BY "C 1" DESC) 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; @@ -6668,12 +6668,12 @@ select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from "S 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + 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((r1."C 1" % 3)), sum(DISTINCT (r1."C 1" % 3)) FILTER (WHERE ((r1."C 1" % 3) < 2)), r1.c2 FROM "S 1"."T 1" r1 WHERE ((c2 = 6)) GROUP BY r1.c2 + 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; @@ -6691,8 +6691,8 @@ select sum("C 1"%3), sum(distinct "C 1"%3) filter (where "C 1"%3 < 2), c2 from " -- 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 ------------------------------------------------------------------------------------------------------------ + QUERY PLAN +----------------------------------------------------------------------------------------------- Unique Output: (sum(c1)), c2 -> Sort @@ -6701,7 +6701,7 @@ select distinct sum(c1), c2 from ft2 where c2 < 6 group by c2 order by c2; -> Foreign Scan Output: (sum(c1)), c2 Relations: Aggregate on (public.ft2) - Remote SQL: SELECT sum(r1."C 1"), r1.c2 FROM "S 1"."T 1" r1 WHERE ((c2 < 6)) GROUP BY r1.c2 + 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; @@ -6720,15 +6720,15 @@ select distinct sum("C 1"), c2 from "S 1"."T 1" where c2 < 6 group by c2 order b explain (verbose, costs off) select sum(c1) filter (where c1 < 100) from ft1 group by c2 order by 1 nulls last; - QUERY PLAN --------------------------------------------------------------------------------------------------------------------- + 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(r1."C 1") FILTER (WHERE (r1."C 1" < 100)), r1.c2 FROM "S 1"."T 1" r1 GROUP BY r1.c2 + 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; @@ -6785,15 +6785,15 @@ select sum("C 1") filter (where "C 1" < 100) from "S 1"."T 1" group by c2 order 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 --------------------------------------------------------------------------------------------------------------------------------------- + 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(r1."C 1") FILTER (WHERE ((r1."C 1" < 100) AND (r1.c2 > 5))), r1.c2 FROM "S 1"."T 1" r1 GROUP BY r1.c2 + 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; @@ -6851,8 +6851,8 @@ select sum("C 1") filter (where "C 1" < 100 and c2 > 5) from "S 1"."T 1" group b -- 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 ----------------------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------------- Unique Output: ((SubPlan 1)) -> Sort @@ -6861,7 +6861,7 @@ select distinct (select count(*) filter (where t2.c2 = 6 and t2.c1 < 10) from ft -> Foreign Scan Output: (SubPlan 1) Relations: Aggregate on (public.ft1 t2) - Remote SQL: SELECT count(*) FILTER (WHERE ((r1.c2 = 6) AND (r1."C 1" < 10))) FROM "S 1"."T 1" r1 + 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)))) @@ -6883,8 +6883,8 @@ select distinct (select count(*) filter (where t2.c2 = 6 and t2."C 1" < 10) from -- 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 ------------------------------------------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------ Unique Output: ((SubPlan 1)) -> Sort @@ -6897,7 +6897,7 @@ select distinct (select count(t1.c1) filter (where t2.c2 = 6 and t2.c1 < 10) fro -> 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(r1."C 1") FILTER (WHERE (($1::integer = 6) AND ($2::integer < 10))) FROM "S 1"."T 1" r1 WHERE (("C 1" = 6)) + 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; @@ -6944,12 +6944,12 @@ 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; - QUERY PLAN ----------------------------------------------------------------------------------------------------------------- + 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 r1.c2) FROM "S 1"."T 1" r1 + Remote SQL: SELECT percentile_disc(0.25::double precision) WITHIN GROUP (ORDER BY c2) FROM "S 1"."T 1" (4 rows) select percentile_disc(0.25) within group (order by c2) from ft1; @@ -6966,15 +6966,15 @@ 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; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 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 r1.c2, percentile_cont(0.5::double precision) WITHIN GROUP (ORDER BY (r1."C 1")) FROM "S 1"."T 1" r1 WHERE ((c2 < 10)) GROUP BY r1.c2 + Remote SQL: SELECT c2, percentile_cont(0.5::double precision) WITHIN GROUP (ORDER BY ("C 1")) 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; @@ -6996,33 +6996,33 @@ select c2, percentile_cont(0.5) within group (order by "C 1") from "S 1"."T 1" w -- Error from remote 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 --------------------------------------------------------------------------------------------------------------------------------- + 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 r1.c2, percentile_cont(r1.c2) WITHIN GROUP (ORDER BY (r1."C 1")) FROM "S 1"."T 1" r1 GROUP BY r1.c2 + Remote SQL: SELECT c2, percentile_cont(c2) WITHIN GROUP (ORDER BY ("C 1")) 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 r1.c2, percentile_cont(r1.c2) WITHIN GROUP (ORDER BY (r1."C 1")) FROM "S 1"."T 1" r1 GROUP BY r1.c2 +CONTEXT: Remote SQL command: SELECT c2, percentile_cont(c2) WITHIN GROUP (ORDER BY ("C 1")) 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 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 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 r1.c2, percentile_cont((r1.c2 / 10::numeric)) WITHIN GROUP (ORDER BY (r1."C 1")) FROM "S 1"."T 1" r1 WHERE ((c2 < 10)) GROUP BY r1.c2 HAVING ((percentile_cont((r1.c2 / 10::numeric)) WITHIN GROUP (ORDER BY (r1."C 1")) < 500::double precision)) + Remote SQL: 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::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; @@ -7041,12 +7041,12 @@ select c2, percentile_cont(c2/10::numeric) within group (order by "C 1") from "S explain (verbose, costs off) select rank('10'::varchar) within group (order by c6) from ft1; - QUERY PLAN ------------------------------------------------------------------------------------------------------- + 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 r1.c6) FROM "S 1"."T 1" r1 + Remote SQL: SELECT rank('10'::character varying) WITHIN GROUP (ORDER BY c6) FROM "S 1"."T 1" (4 rows) select rank('10'::varchar) within group (order by c6) from ft1; @@ -7091,15 +7091,15 @@ 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 ------------------------------------------------------------------------------------------------------------------------------------- + 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 r1.c2, public.least_agg(VARIADIC ARRAY[r1."C 1"]) FROM "S 1"."T 1" r1 WHERE ((c2 < 100)) GROUP BY r1.c2 + 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; @@ -7126,15 +7126,15 @@ select c2, least_agg("C 1") from "S 1"."T 1" where c2 < 100 group by c2 order by -- 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 --------------------------------------------------------------------------------------------------------------------------------------------- + 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, r1.c2, sum(r1."C 1") FROM "S 1"."T 1" r1 GROUP BY r1.c2 HAVING ((avg(r1."C 1") < 50000::numeric)) + 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; @@ -7151,15 +7151,15 @@ select count(*) from (select c2 * random(), count("C 1") from "S 1"."T 1" group explain (verbose, costs off) select c2, sum(c2) from ft1 group by c2 having avg(c2) + c2 < 10 order by c2; - QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------- + 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 r1.c2, sum(r1.c2) FROM "S 1"."T 1" r1 GROUP BY r1.c2 HAVING (((avg(r1.c2) + r1.c2) < 10::numeric)) + 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; @@ -7197,8 +7197,8 @@ 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; - QUERY PLAN ------------------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------ Sort Output: (count(*)), x.b Sort Key: (count(*)), x.b @@ -7218,7 +7218,7 @@ select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x w -> Foreign Scan Output: ft1_1.c2, (sum(ft1_1.c1)) Relations: Aggregate on (public.ft1) - Remote SQL: SELECT r1.c2, sum(r1."C 1") FROM "S 1"."T 1" r1 GROUP BY r1.c2 + 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; diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index c33916f..4dda273 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -1183,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; @@ -1204,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) @@ -1223,17 +1231,6 @@ postgresGetForeignPlan(PlannerInfo *root, } } } - else if (foreignrel->reloptkind == RELOPT_UPPER_REL) - { - /* - * For a grouping relation, get the conditions from fdw_private - * structure. - */ - remote_conds = fpinfo->remote_conds; - local_exprs = fpinfo->local_conds; - - fdw_scan_tlist = build_tlist_to_deparse(root, foreignrel); - } /* * Build the query string to be sent for execution, and identify @@ -2478,7 +2475,7 @@ 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 or an upper - * relation. + * 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. @@ -4443,7 +4440,7 @@ 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 passed in. + * this function to PgFdwRelationInfo of the input relation. */ static bool foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel) @@ -4461,7 +4458,7 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel) if (query->groupingSets) return false; - /* Get the fpinfo of the outerrel */ + /* Get the fpinfo of the underlying scan relation. */ ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private; /* @@ -4474,10 +4471,10 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel) /* * Evaluate grouping targets and check whether they are safe to push down - * to the remote side. All GROUP BY expressions will be part of the - * grouping target and thus no need to evaluate it separately. While doing - * so, add required expressions into target list which can then be used to - * pass to remote server. + * 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) @@ -4490,7 +4487,10 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel) if (sgref && query->groupClause && query->groupingSets == NIL && get_sortgroupref_clause_noerr(sgref, query->groupClause) != NULL) { - /* Expression matched with GROUP BY, check as is */ + /* + * 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, true)) return false; @@ -4521,7 +4521,7 @@ foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel) /* * Add aggregates, if any, into tlist. Plain Var nodes pulled - * are already part of GROUP BY and thus no need to add them + * are already part of some and thus no need to add them * explicitly. */ foreach(l, aggvars) @@ -4654,8 +4654,8 @@ postgresGetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, * * Add foreign path for grouping and/or aggregation. * - * input_rel contains the underneath scan rel - * grouped_rel is the rel for which paths need to be added + * input_rel represents the underlying scan. The paths are added to the + * grouped_rel. */ static void create_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers