Commit d50d172e authored by Etsuro Fujita's avatar Etsuro Fujita

postgres_fdw: Perform the (FINAL, NULL) upperrel operations remotely.

The upper-planner pathification allows FDWs to arrange to push down
different types of upper-stage operations to the remote side.  This
commit teaches postgres_fdw to do it for the (FINAL, NULL) upperrel,
which is responsible for doing LockRows, LIMIT, and/or ModifyTable.
This provides the ability for postgres_fdw to handle SELECT commands
so that it 1) skips the LockRows step (if any) (note that this is
safe since it performs early locking) and 2) pushes down the LIMIT
and/or OFFSET restrictions (if any) to the remote side.  This doesn't
handle the INSERT/UPDATE/DELETE cases.

Author: Etsuro Fujita
Reviewed-By: Antonin Houska and Jeff Janes
Discussion: https://postgr.es/m/87pnz1aby9.fsf@news-spur.riddles.org.uk
parent aef65db6
...@@ -169,6 +169,7 @@ static void deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_att ...@@ -169,6 +169,7 @@ static void deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_att
static void deparseLockingClause(deparse_expr_cxt *context); static void deparseLockingClause(deparse_expr_cxt *context);
static void appendOrderByClause(List *pathkeys, bool has_final_sort, static void appendOrderByClause(List *pathkeys, bool has_final_sort,
deparse_expr_cxt *context); deparse_expr_cxt *context);
static void appendLimitClause(deparse_expr_cxt *context);
static void appendConditions(List *exprs, deparse_expr_cxt *context); static void appendConditions(List *exprs, deparse_expr_cxt *context);
static void deparseFromExprForRel(StringInfo buf, PlannerInfo *root, static void deparseFromExprForRel(StringInfo buf, PlannerInfo *root,
RelOptInfo *foreignrel, bool use_alias, RelOptInfo *foreignrel, bool use_alias,
...@@ -930,7 +931,7 @@ build_tlist_to_deparse(RelOptInfo *foreignrel) ...@@ -930,7 +931,7 @@ build_tlist_to_deparse(RelOptInfo *foreignrel)
void void
deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel, deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel,
List *tlist, List *remote_conds, List *pathkeys, List *tlist, List *remote_conds, List *pathkeys,
bool has_final_sort, bool is_subquery, bool has_final_sort, bool has_limit, bool is_subquery,
List **retrieved_attrs, List **params_list) List **retrieved_attrs, List **params_list)
{ {
deparse_expr_cxt context; deparse_expr_cxt context;
...@@ -988,6 +989,10 @@ deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel, ...@@ -988,6 +989,10 @@ deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel,
if (pathkeys) if (pathkeys)
appendOrderByClause(pathkeys, has_final_sort, &context); appendOrderByClause(pathkeys, has_final_sort, &context);
/* Add LIMIT clause if necessary */
if (has_limit)
appendLimitClause(&context);
/* Add any necessary FOR UPDATE/SHARE. */ /* Add any necessary FOR UPDATE/SHARE. */
deparseLockingClause(&context); deparseLockingClause(&context);
} }
...@@ -1591,7 +1596,8 @@ deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, ...@@ -1591,7 +1596,8 @@ deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel,
/* Deparse the subquery representing the relation. */ /* Deparse the subquery representing the relation. */
appendStringInfoChar(buf, '('); appendStringInfoChar(buf, '(');
deparseSelectStmtForRel(buf, root, foreignrel, NIL, deparseSelectStmtForRel(buf, root, foreignrel, NIL,
fpinfo->remote_conds, NIL, false, true, fpinfo->remote_conds, NIL,
false, false, true,
&retrieved_attrs, params_list); &retrieved_attrs, params_list);
appendStringInfoChar(buf, ')'); appendStringInfoChar(buf, ')');
...@@ -3160,6 +3166,33 @@ appendOrderByClause(List *pathkeys, bool has_final_sort, ...@@ -3160,6 +3166,33 @@ appendOrderByClause(List *pathkeys, bool has_final_sort,
reset_transmission_modes(nestlevel); reset_transmission_modes(nestlevel);
} }
/*
* Deparse LIMIT/OFFSET clause.
*/
static void
appendLimitClause(deparse_expr_cxt *context)
{
PlannerInfo *root = context->root;
StringInfo buf = context->buf;
int nestlevel;
/* Make sure any constants in the exprs are printed portably */
nestlevel = set_transmission_modes();
if (root->parse->limitCount)
{
appendStringInfoString(buf, " LIMIT ");
deparseExpr((Expr *) root->parse->limitCount, context);
}
if (root->parse->limitOffset)
{
appendStringInfoString(buf, " OFFSET ");
deparseExpr((Expr *) root->parse->limitOffset, context);
}
reset_transmission_modes(nestlevel);
}
/* /*
* appendFunctionName * appendFunctionName
* Deparses function name from given function oid. * Deparses function name from given function oid.
......
This diff is collapsed.
...@@ -188,7 +188,8 @@ extern List *build_tlist_to_deparse(RelOptInfo *foreignrel); ...@@ -188,7 +188,8 @@ extern List *build_tlist_to_deparse(RelOptInfo *foreignrel);
extern void deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, extern void deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root,
RelOptInfo *foreignrel, List *tlist, RelOptInfo *foreignrel, List *tlist,
List *remote_conds, List *pathkeys, List *remote_conds, List *pathkeys,
bool has_final_sort, bool is_subquery, bool has_final_sort, bool has_limit,
bool is_subquery,
List **retrieved_attrs, List **params_list); List **retrieved_attrs, List **params_list);
extern const char *get_jointype_name(JoinType jointype); extern const char *get_jointype_name(JoinType jointype);
......
...@@ -349,6 +349,11 @@ EXPLAIN (VERBOSE, COSTS OFF) ...@@ -349,6 +349,11 @@ EXPLAIN (VERBOSE, COSTS OFF)
SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2;
SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2;
-- ORDER BY can be shipped, though
EXPLAIN (VERBOSE, COSTS OFF)
SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1;
SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1;
-- but let's put them in an extension ... -- but let's put them in an extension ...
ALTER EXTENSION postgres_fdw ADD FUNCTION postgres_fdw_abs(int); ALTER EXTENSION postgres_fdw ADD FUNCTION postgres_fdw_abs(int);
ALTER EXTENSION postgres_fdw ADD OPERATOR === (int, int); ALTER EXTENSION postgres_fdw ADD OPERATOR === (int, int);
...@@ -362,6 +367,11 @@ EXPLAIN (VERBOSE, COSTS OFF) ...@@ -362,6 +367,11 @@ EXPLAIN (VERBOSE, COSTS OFF)
SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2;
SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2;
-- and both ORDER BY and LIMIT can be shipped
EXPLAIN (VERBOSE, COSTS OFF)
SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1;
SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1;
-- =================================================================== -- ===================================================================
-- JOIN queries -- JOIN queries
-- =================================================================== -- ===================================================================
...@@ -506,7 +516,7 @@ SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ...@@ -506,7 +516,7 @@ SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1)
EXPLAIN (VERBOSE, COSTS OFF) EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10;
SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10;
-- CROSS JOIN, not pushed down -- CROSS JOIN can be pushed down
EXPLAIN (VERBOSE, COSTS OFF) EXPLAIN (VERBOSE, COSTS OFF)
SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10;
SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10;
...@@ -613,6 +623,10 @@ explain (verbose, costs off) ...@@ -613,6 +623,10 @@ explain (verbose, costs off)
select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2;
select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2;
explain (verbose, costs off)
select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1;
select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1;
-- Aggregate is not pushed down as aggregation contains random() -- Aggregate is not pushed down as aggregation contains random()
explain (verbose, costs off) explain (verbose, costs off)
select sum(c1 * (random() <= 1)::int) as sum, avg(c1) from ft1; select sum(c1 * (random() <= 1)::int) as sum, avg(c1) from ft1;
......
...@@ -1830,6 +1830,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, ...@@ -1830,6 +1830,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
bool final_target_parallel_safe; bool final_target_parallel_safe;
RelOptInfo *current_rel; RelOptInfo *current_rel;
RelOptInfo *final_rel; RelOptInfo *final_rel;
FinalPathExtraData extra;
ListCell *lc; ListCell *lc;
/* Tweak caller-supplied tuple_fraction if have LIMIT/OFFSET */ /* Tweak caller-supplied tuple_fraction if have LIMIT/OFFSET */
...@@ -2389,6 +2390,11 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, ...@@ -2389,6 +2390,11 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
} }
} }
extra.limit_needed = limit_needed(parse);
extra.limit_tuples = limit_tuples;
extra.count_est = count_est;
extra.offset_est = offset_est;
/* /*
* If there is an FDW that's responsible for all baserels of the query, * If there is an FDW that's responsible for all baserels of the query,
* let it consider adding ForeignPaths. * let it consider adding ForeignPaths.
...@@ -2397,12 +2403,12 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, ...@@ -2397,12 +2403,12 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
final_rel->fdwroutine->GetForeignUpperPaths) final_rel->fdwroutine->GetForeignUpperPaths)
final_rel->fdwroutine->GetForeignUpperPaths(root, UPPERREL_FINAL, final_rel->fdwroutine->GetForeignUpperPaths(root, UPPERREL_FINAL,
current_rel, final_rel, current_rel, final_rel,
NULL); &extra);
/* Let extensions possibly add some more paths */ /* Let extensions possibly add some more paths */
if (create_upper_paths_hook) if (create_upper_paths_hook)
(*create_upper_paths_hook) (root, UPPERREL_FINAL, (*create_upper_paths_hook) (root, UPPERREL_FINAL,
current_rel, final_rel, NULL); current_rel, final_rel, &extra);
/* Note: currently, we leave it to callers to do set_cheapest() */ /* Note: currently, we leave it to callers to do set_cheapest() */
} }
......
...@@ -2439,6 +2439,24 @@ typedef struct ...@@ -2439,6 +2439,24 @@ typedef struct
PartitionwiseAggregateType patype; PartitionwiseAggregateType patype;
} GroupPathExtraData; } GroupPathExtraData;
/*
* Struct for extra information passed to subroutines of grouping_planner
*
* limit_needed is true if we actually need a Limit plan node
* limit_tuples is an estimated bound on the number of output tuples,
* or -1 if no LIMIT or couldn't estimate
* count_est and offset_est are the estimated values of the LIMIT and OFFSET
* expressions computed by preprocess_limit() (see comments for
* preprocess_limit() for more information).
*/
typedef struct
{
bool limit_needed;
double limit_tuples;
int64 count_est;
int64 offset_est;
} FinalPathExtraData;
/* /*
* For speed reasons, cost estimation for join paths is performed in two * For speed reasons, cost estimation for join paths is performed in two
* phases: the first phase tries to quickly derive a lower bound for the * phases: the first phase tries to quickly derive a lower bound for the
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment