From: Tomas Vondra <tomas.vondra@2ndquadrant.com>
To: James Coleman <jtc331@gmail.com>
Cc: Jaime Casanova <jaime.casanova@2ndquadrant.com>
Cc: Pg Hackers <pgsql-hackers@postgresql.org>
Subject: Re: enable_incremental_sort changes query behavior
Date: Tue, 3 Nov 2020 03:37:43 +0100
Message-ID: <20201103023743.sswkl6h74dkbcfmp@development> (raw)
In-Reply-To: <CAAaqYe_izhFTq1FLgUxa+J+_bY8FGBNiHjTj=EzrSA72tvacEw@mail.gmail.com>
References: <CAAaqYe-X57F0Y=BL_wm0OjPGsO-Ls0h_xAJKgoybx8LGnrK1-Q@mail.gmail.com>
<CAAaqYe8_Y7Ydfwdr_q0NWCpr+E_Hc_e=ScEhi9MUmuLT45cNDA@mail.gmail.com>
<CAAaqYe9C3f6A_tZCRfr9Dm7hPpgGwpp4i-K_=NS9GWXuNiFANg@mail.gmail.com>
<CAAaqYe80-V8q7PozVh22EbBhMDU40W286na3RahxJiZ-Pz8S2w@mail.gmail.com>
<CAJGNTeNjGZYFBjFv055aFgkeAurAOVVe2xp7XB1V4Qd3de8Fuw@mail.gmail.com>
<CAAaqYe8e9A9SC1BgWPgzyL+v8uC-=y2+yQ0z6CTXBTy1gxjvyg@mail.gmail.com>
<20201029220652.glf4xk2p7wkfisb6@development>
<CAAaqYe9P261NfjqhAkDbPGEjqf_8ZyayxTYUEdEnPOqmLRb6EA@mail.gmail.com>
<20201030210316.cbwv4arwp4lcfgx2@development>
<CAAaqYe_izhFTq1FLgUxa+J+_bY8FGBNiHjTj=EzrSA72tvacEw@mail.gmail.com>
On Fri, Oct 30, 2020 at 07:37:33PM -0400, James Coleman wrote:
>
> ...
>
>I was looking at this some more, and I'm still convinced that this is
>correct, but I don't think a comment about it being an optimization
>(though I suspect that that its major benefit), since it came from,
>and must parallel, find_ec_member_for_tle, and there is no such
>explanation there. I don't want to backfill reasoning onto that
>decision, but I do think it's important to parallel it since it's
>ultimately what is going to cause errors if we're out of sync with it.
>
>As to why find_em_expr_for_rel is different, I think the answer is
>that it's used by the FDW code, and it seems to me to make sense that
>in that case we'd only send sort conditions to the foreign server if
>the em actually contains rels.
>
>The new series attached includes the primary fix for this issue, the
>additional comments that could either go in at the same time or not,
>and my patch with semi-related questions (which isn't intended to be
>committed, but to keep track of the questions).
>
Thanks. Attached are two patches that I plan to get committed
0001 is what you sent, with slightly reworded commit message. This is
the actual fix.
I'm still thinking about Robert's comment that perhaps we should be
considering only expressions already in the relation's target, which
would imply checking for volatile expressions is not enough. But I've
been unable to convince myself it's correct or incorrect. In any case
0001 is a clear improvement - in the worst case we'll have to make it
stricter in the future.
0002 partially reverts ba3e76cc571 by moving find_em_expr_for_rel back
to postgres_fdw.c. We've moved it to equivclass.c so that it could be
called from both the FDW and allpaths.c, but now that the functions
diverged it's only called from the FDW again. So maybe we should move
it back, but I guess that's not a good thing in a minor release.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
From b2c8085256aa6d6e34f6c9b8394738ee1bef3997 Mon Sep 17 00:00:00 2001
From: James Coleman <jtc331@gmail.com>
Date: Sat, 3 Oct 2020 10:35:47 -0400
Subject: [PATCH 1/3] Fix get_useful_pathkeys_for_relation for volatile
expressions
When considering Incremental Sort below a Gather Merge, we need to be
a bit more careful when matching pathkeys to EC members. It's not enough
to find a member whose Vars are all in the current relation's target;
volatile expressions in particular need to be contained in the target,
otherwise it's too early to use the pathkey.
Reported-by: Jaime Casanova
Author: James Coleman
Reviewed-by: Tomas Vondra
Backpatch-through: 13
Discussion: https://postgr.es/m/CAJGNTeNaxpXgBVcRhJX%2B2vSbq%2BF2kJqGBcvompmpvXb7pq%2BoFA%40mail.gmail.com
---
src/backend/optimizer/path/allpaths.c | 13 +--
src/backend/optimizer/path/equivclass.c | 70 +++++++++++++
src/include/optimizer/paths.h | 1 +
.../regress/expected/incremental_sort.out | 98 +++++++++++++++++++
src/test/regress/sql/incremental_sort.sql | 31 ++++++
5 files changed, 207 insertions(+), 6 deletions(-)
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.cindex 8ad6384c6a..84a69b064a 100644--- a/src/backend/optimizer/path/allpaths.c+++ b/src/backend/optimizer/path/allpaths.c@@ -2816,7 +2816,8 @@ get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel)
/*
* Considering query_pathkeys is always worth it, because it might allow
* us to avoid a total sort when we have a partially presorted path
- * available.+ * available or to push the total sort into the parallel portion of the+ * query.
*/
if (root->query_pathkeys)
{
@@ -2829,17 +2830,17 @@ get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel)
EquivalenceClass *pathkey_ec = pathkey->pk_eclass;
/*
- * We can only build an Incremental Sort for pathkeys which- * contain an EC member in the current relation, so ignore any- * suffix of the list as soon as we find a pathkey without an EC- * member the relation.+ * We can only build a sort for pathkeys which contain an EC+ * member in the current relation's target, so ignore any suffix+ * of the list as soon as we find a pathkey without an EC member+ * in the relation.
*
* By still returning the prefix of the pathkeys list that does
* meet criteria of EC membership in the current relation, we
* enable not just an incremental sort on the entirety of
* query_pathkeys but also incremental sort below a JOIN.
*/
- if (!find_em_expr_for_rel(pathkey_ec, rel))+ if (!find_em_expr_usable_for_sorting_rel(pathkey_ec, rel))
break;
npathkeys++;
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.cindex a21b3b4756..f98fd7b3eb 100644--- a/src/backend/optimizer/path/equivclass.c+++ b/src/backend/optimizer/path/equivclass.c@@ -797,6 +797,76 @@ find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel)
return NULL;
}
+/*+ * Find an equivalence class member expression that can be safely used by a+ * sort node on top of the provided relation. The rules here must match those+ * applied in prepare_sort_from_pathkeys.+ */+Expr *+find_em_expr_usable_for_sorting_rel(EquivalenceClass *ec, RelOptInfo *rel)+{+ ListCell *lc_em;++ /*+ * If there is more than one equivalence member matching these+ * requirements we'll be content to choose any one of them.+ */+ foreach(lc_em, ec->ec_members)+ {+ EquivalenceMember *em = lfirst(lc_em);+ Expr *em_expr = em->em_expr;+ PathTarget *target = rel->reltarget;+ ListCell *lc_target_expr;++ /*+ * We shouldn't be trying to sort by an equivalence class that+ * contains a constant, so no need to consider such cases any further.+ */+ if (em->em_is_const)+ continue;++ /*+ * Any Vars in the equivalence class member need to come from this+ * relation. This is a superset of prepare_sort_from_pathkeys ignoring+ * child members unless they belong to the rel being sorted.+ */+ if (!bms_is_subset(em->em_relids, rel->relids))+ continue;++ /*+ * As long as the expression isn't volatile then+ * prepare_sort_from_pathkeys is able to generate a new target entry,+ * so there's no need to verify that one already exists.+ */+ if (!ec->ec_has_volatile)+ return em->em_expr;++ /*+ * If, however, it's volatile, we have to verify that the+ * equivalence member's expr is already generated in the+ * relation's target (we do strip relabels first from both+ * expressions, which is cheap and might allow us to match+ * more expressions).+ */+ while (em_expr && IsA(em_expr, RelabelType))+ em_expr = ((RelabelType *) em_expr)->arg;++ foreach(lc_target_expr, target->exprs)+ {+ Expr *target_expr = lfirst(lc_target_expr);++ while (target_expr && IsA(target_expr, RelabelType))+ target_expr = ((RelabelType *) target_expr)->arg;++ if (equal(target_expr, em_expr))+ return em->em_expr;+ }+ }++ /* We didn't find any suitable equivalence class expression */+ return NULL;+}+
/*
* generate_base_implied_equalities
* Generate any restriction clauses that we can deduce from equivalence
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.hindex 2134227ebc..8a4c6f8b59 100644--- a/src/include/optimizer/paths.h+++ b/src/include/optimizer/paths.h@@ -135,6 +135,7 @@ extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root,
Relids rel,
bool create_it);
extern Expr *find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel);
+extern Expr *find_em_expr_usable_for_sorting_rel(EquivalenceClass *ec, RelOptInfo *rel);
extern void generate_base_implied_equalities(PlannerInfo *root);
extern List *generate_join_implied_equalities(PlannerInfo *root,
Relids join_relids,
diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.outindex e376ea1276..7cf2eee7c1 100644--- a/src/test/regress/expected/incremental_sort.out+++ b/src/test/regress/expected/incremental_sort.out@@ -1469,3 +1469,101 @@ explain (costs off) select * from t union select * from t order by 1,3;
(13 rows)
drop table t;
+-- Sort pushdown can't go below where expressions are part of the rel target.+-- In particular this is interesting for volatile expressions which have to+-- go above joins since otherwise we'll incorrectly use expression evaluations+-- across multiple rows.+set enable_hashagg=off;+set enable_seqscan=off;+set enable_incremental_sort = off;+set parallel_tuple_cost=0;+set parallel_setup_cost=0;+set min_parallel_table_scan_size = 0;+set min_parallel_index_scan_size = 0;+-- Parallel sort below join.+explain (costs off) select distinct sub.unique1, stringu1+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;+ QUERY PLAN +--------------------------------------------------------------------------+ Unique+ -> Nested Loop+ -> Gather Merge+ Workers Planned: 2+ -> Sort+ Sort Key: tenk1.unique1, tenk1.stringu1+ -> Parallel Index Scan using tenk1_unique1 on tenk1+ -> Function Scan on generate_series+(8 rows)++explain (costs off) select sub.unique1, stringu1+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub+order by 1, 2;+ QUERY PLAN +--------------------------------------------------------------------+ Nested Loop+ -> Gather Merge+ Workers Planned: 2+ -> Sort+ Sort Key: tenk1.unique1, tenk1.stringu1+ -> Parallel Index Scan using tenk1_unique1 on tenk1+ -> Function Scan on generate_series+(7 rows)++-- Parallel sort but with expression that can be safely generated at the base rel.+explain (costs off) select distinct sub.unique1, md5(stringu1)+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;+ QUERY PLAN +----------------------------------------------------------------------------------------+ Unique+ -> Nested Loop+ -> Gather Merge+ Workers Planned: 2+ -> Sort+ Sort Key: tenk1.unique1, (md5((tenk1.stringu1)::text)) COLLATE "C"+ -> Parallel Index Scan using tenk1_unique1 on tenk1+ -> Function Scan on generate_series+(8 rows)++explain (costs off) select sub.unique1, md5(stringu1)+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub+order by 1, 2;+ QUERY PLAN +----------------------------------------------------------------------------------+ Nested Loop+ -> Gather Merge+ Workers Planned: 2+ -> Sort+ Sort Key: tenk1.unique1, (md5((tenk1.stringu1)::text)) COLLATE "C"+ -> Parallel Index Scan using tenk1_unique1 on tenk1+ -> Function Scan on generate_series+(7 rows)++-- Parallel sort but with expression not available until the upper rel.+explain (costs off) select distinct sub.unique1, stringu1 || random()::text+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;+ QUERY PLAN +---------------------------------------------------------------------------------------------+ Unique+ -> Sort+ Sort Key: tenk1.unique1, (((tenk1.stringu1)::text || (random())::text)) COLLATE "C"+ -> Gather+ Workers Planned: 2+ -> Nested Loop+ -> Parallel Index Scan using tenk1_unique1 on tenk1+ -> Function Scan on generate_series+(8 rows)++explain (costs off) select sub.unique1, stringu1 || random()::text+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub+order by 1, 2;+ QUERY PLAN +---------------------------------------------------------------------------------------+ Sort+ Sort Key: tenk1.unique1, (((tenk1.stringu1)::text || (random())::text)) COLLATE "C"+ -> Gather+ Workers Planned: 2+ -> Nested Loop+ -> Parallel Index Scan using tenk1_unique1 on tenk1+ -> Function Scan on generate_series+(7 rows)+diff --git a/src/test/regress/sql/incremental_sort.sql b/src/test/regress/sql/incremental_sort.sqlindex 9c040c90e6..3ee11c394b 100644--- a/src/test/regress/sql/incremental_sort.sql+++ b/src/test/regress/sql/incremental_sort.sql@@ -221,3 +221,34 @@ set enable_hashagg to off;
explain (costs off) select * from t union select * from t order by 1,3;
drop table t;
++-- Sort pushdown can't go below where expressions are part of the rel target.+-- In particular this is interesting for volatile expressions which have to+-- go above joins since otherwise we'll incorrectly use expression evaluations+-- across multiple rows.+set enable_hashagg=off;+set enable_seqscan=off;+set enable_incremental_sort = off;+set parallel_tuple_cost=0;+set parallel_setup_cost=0;+set min_parallel_table_scan_size = 0;+set min_parallel_index_scan_size = 0;++-- Parallel sort below join.+explain (costs off) select distinct sub.unique1, stringu1+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;+explain (costs off) select sub.unique1, stringu1+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub+order by 1, 2;+-- Parallel sort but with expression that can be safely generated at the base rel.+explain (costs off) select distinct sub.unique1, md5(stringu1)+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;+explain (costs off) select sub.unique1, md5(stringu1)+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub+order by 1, 2;+-- Parallel sort but with expression not available until the upper rel.+explain (costs off) select distinct sub.unique1, stringu1 || random()::text+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;+explain (costs off) select sub.unique1, stringu1 || random()::text+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub+order by 1, 2;
--
2.25.4
From aefe619b1cecb4ca70f2035b2b62ff270a4e80e7 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas@2ndquadrant.com>
Date: Tue, 3 Nov 2020 03:22:50 +0100
Subject: [PATCH 2/3] partially revert ba3e76cc571eba3dea19c9465ff15ac3ac186576
---
contrib/postgres_fdw/postgres_fdw.c | 29 +++++++++++++++++++++++++
src/backend/optimizer/path/equivclass.c | 29 -------------------------
src/include/optimizer/paths.h | 1 -
3 files changed, 29 insertions(+), 30 deletions(-)
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.cindex 9c5aaacc51..dcf533d700 100644--- a/contrib/postgres_fdw/postgres_fdw.c+++ b/contrib/postgres_fdw/postgres_fdw.c@@ -6524,6 +6524,35 @@ conversion_error_callback(void *arg)
}
}
+/*+ * Find an equivalence class member expression, all of whose Vars, come from+ * the indicated relation.+ */+Expr *+find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel)+{+ ListCell *lc_em;++ foreach(lc_em, ec->ec_members)+ {+ EquivalenceMember *em = lfirst(lc_em);++ if (bms_is_subset(em->em_relids, rel->relids) &&+ !bms_is_empty(em->em_relids))+ {+ /*+ * If there is more than one equivalence member whose Vars are+ * taken entirely from this relation, we'll be content to choose+ * any one of those.+ */+ return em->em_expr;+ }+ }++ /* We didn't find any suitable equivalence class expression */+ return NULL;+}+
/*
* Find an equivalence class member expression to be computed as a sort column
* in the given target.
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.cindex f98fd7b3eb..a507a76bcf 100644--- a/src/backend/optimizer/path/equivclass.c+++ b/src/backend/optimizer/path/equivclass.c@@ -768,35 +768,6 @@ get_eclass_for_sort_expr(PlannerInfo *root,
return newec;
}
-/*- * Find an equivalence class member expression, all of whose Vars, come from- * the indicated relation.- */-Expr *-find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel)-{- ListCell *lc_em;-- foreach(lc_em, ec->ec_members)- {- EquivalenceMember *em = lfirst(lc_em);-- if (bms_is_subset(em->em_relids, rel->relids) &&- !bms_is_empty(em->em_relids))- {- /*- * If there is more than one equivalence member whose Vars are- * taken entirely from this relation, we'll be content to choose- * any one of those.- */- return em->em_expr;- }- }-- /* We didn't find any suitable equivalence class expression */- return NULL;-}-
/*
* Find an equivalence class member expression that can be safely used by a
* sort node on top of the provided relation. The rules here must match those
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.hindex 8a4c6f8b59..5f6ffbd860 100644--- a/src/include/optimizer/paths.h+++ b/src/include/optimizer/paths.h@@ -134,7 +134,6 @@ extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root,
Index sortref,
Relids rel,
bool create_it);
-extern Expr *find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel);
extern Expr *find_em_expr_usable_for_sorting_rel(EquivalenceClass *ec, RelOptInfo *rel);
extern void generate_base_implied_equalities(PlannerInfo *root);
extern List *generate_join_implied_equalities(PlannerInfo *root,
--
2.25.4
Attachments:
[text/plain] v7-0001-Fix-get_useful_pathkeys_for_relation-for-volatile-ex.patch (12.5K, ../20201103023743.sswkl6h74dkbcfmp@development/2-v7-0001-Fix-get_useful_pathkeys_for_relation-for-volatile-ex.patch)
download | inline diff:
From b2c8085256aa6d6e34f6c9b8394738ee1bef3997 Mon Sep 17 00:00:00 2001
From: James Coleman <jtc331@gmail.com>
Date: Sat, 3 Oct 2020 10:35:47 -0400
Subject: [PATCH 1/3] Fix get_useful_pathkeys_for_relation for volatile
expressions
When considering Incremental Sort below a Gather Merge, we need to be
a bit more careful when matching pathkeys to EC members. It's not enough
to find a member whose Vars are all in the current relation's target;
volatile expressions in particular need to be contained in the target,
otherwise it's too early to use the pathkey.
Reported-by: Jaime Casanova
Author: James Coleman
Reviewed-by: Tomas Vondra
Backpatch-through: 13
Discussion: https://postgr.es/m/CAJGNTeNaxpXgBVcRhJX%2B2vSbq%2BF2kJqGBcvompmpvXb7pq%2BoFA%40mail.gmail.com
---
src/backend/optimizer/path/allpaths.c | 13 +--
src/backend/optimizer/path/equivclass.c | 70 +++++++++++++
src/include/optimizer/paths.h | 1 +
.../regress/expected/incremental_sort.out | 98 +++++++++++++++++++
src/test/regress/sql/incremental_sort.sql | 31 ++++++
5 files changed, 207 insertions(+), 6 deletions(-)
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.cindex 8ad6384c6a..84a69b064a 100644--- a/src/backend/optimizer/path/allpaths.c+++ b/src/backend/optimizer/path/allpaths.c@@ -2816,7 +2816,8 @@ get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel)
/*
* Considering query_pathkeys is always worth it, because it might allow
* us to avoid a total sort when we have a partially presorted path
- * available.+ * available or to push the total sort into the parallel portion of the+ * query.
*/
if (root->query_pathkeys)
{
@@ -2829,17 +2830,17 @@ get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel)
EquivalenceClass *pathkey_ec = pathkey->pk_eclass;
/*
- * We can only build an Incremental Sort for pathkeys which- * contain an EC member in the current relation, so ignore any- * suffix of the list as soon as we find a pathkey without an EC- * member the relation.+ * We can only build a sort for pathkeys which contain an EC+ * member in the current relation's target, so ignore any suffix+ * of the list as soon as we find a pathkey without an EC member+ * in the relation.
*
* By still returning the prefix of the pathkeys list that does
* meet criteria of EC membership in the current relation, we
* enable not just an incremental sort on the entirety of
* query_pathkeys but also incremental sort below a JOIN.
*/
- if (!find_em_expr_for_rel(pathkey_ec, rel))+ if (!find_em_expr_usable_for_sorting_rel(pathkey_ec, rel))
break;
npathkeys++;
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.cindex a21b3b4756..f98fd7b3eb 100644--- a/src/backend/optimizer/path/equivclass.c+++ b/src/backend/optimizer/path/equivclass.c@@ -797,6 +797,76 @@ find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel)
return NULL;
}
+/*+ * Find an equivalence class member expression that can be safely used by a+ * sort node on top of the provided relation. The rules here must match those+ * applied in prepare_sort_from_pathkeys.+ */+Expr *+find_em_expr_usable_for_sorting_rel(EquivalenceClass *ec, RelOptInfo *rel)+{+ ListCell *lc_em;++ /*+ * If there is more than one equivalence member matching these+ * requirements we'll be content to choose any one of them.+ */+ foreach(lc_em, ec->ec_members)+ {+ EquivalenceMember *em = lfirst(lc_em);+ Expr *em_expr = em->em_expr;+ PathTarget *target = rel->reltarget;+ ListCell *lc_target_expr;++ /*+ * We shouldn't be trying to sort by an equivalence class that+ * contains a constant, so no need to consider such cases any further.+ */+ if (em->em_is_const)+ continue;++ /*+ * Any Vars in the equivalence class member need to come from this+ * relation. This is a superset of prepare_sort_from_pathkeys ignoring+ * child members unless they belong to the rel being sorted.+ */+ if (!bms_is_subset(em->em_relids, rel->relids))+ continue;++ /*+ * As long as the expression isn't volatile then+ * prepare_sort_from_pathkeys is able to generate a new target entry,+ * so there's no need to verify that one already exists.+ */+ if (!ec->ec_has_volatile)+ return em->em_expr;++ /*+ * If, however, it's volatile, we have to verify that the+ * equivalence member's expr is already generated in the+ * relation's target (we do strip relabels first from both+ * expressions, which is cheap and might allow us to match+ * more expressions).+ */+ while (em_expr && IsA(em_expr, RelabelType))+ em_expr = ((RelabelType *) em_expr)->arg;++ foreach(lc_target_expr, target->exprs)+ {+ Expr *target_expr = lfirst(lc_target_expr);++ while (target_expr && IsA(target_expr, RelabelType))+ target_expr = ((RelabelType *) target_expr)->arg;++ if (equal(target_expr, em_expr))+ return em->em_expr;+ }+ }++ /* We didn't find any suitable equivalence class expression */+ return NULL;+}+
/*
* generate_base_implied_equalities
* Generate any restriction clauses that we can deduce from equivalence
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.hindex 2134227ebc..8a4c6f8b59 100644--- a/src/include/optimizer/paths.h+++ b/src/include/optimizer/paths.h@@ -135,6 +135,7 @@ extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root,
Relids rel,
bool create_it);
extern Expr *find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel);
+extern Expr *find_em_expr_usable_for_sorting_rel(EquivalenceClass *ec, RelOptInfo *rel);
extern void generate_base_implied_equalities(PlannerInfo *root);
extern List *generate_join_implied_equalities(PlannerInfo *root,
Relids join_relids,
diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.outindex e376ea1276..7cf2eee7c1 100644--- a/src/test/regress/expected/incremental_sort.out+++ b/src/test/regress/expected/incremental_sort.out@@ -1469,3 +1469,101 @@ explain (costs off) select * from t union select * from t order by 1,3;
(13 rows)
drop table t;
+-- Sort pushdown can't go below where expressions are part of the rel target.+-- In particular this is interesting for volatile expressions which have to+-- go above joins since otherwise we'll incorrectly use expression evaluations+-- across multiple rows.+set enable_hashagg=off;+set enable_seqscan=off;+set enable_incremental_sort = off;+set parallel_tuple_cost=0;+set parallel_setup_cost=0;+set min_parallel_table_scan_size = 0;+set min_parallel_index_scan_size = 0;+-- Parallel sort below join.+explain (costs off) select distinct sub.unique1, stringu1+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;+ QUERY PLAN +--------------------------------------------------------------------------+ Unique+ -> Nested Loop+ -> Gather Merge+ Workers Planned: 2+ -> Sort+ Sort Key: tenk1.unique1, tenk1.stringu1+ -> Parallel Index Scan using tenk1_unique1 on tenk1+ -> Function Scan on generate_series+(8 rows)++explain (costs off) select sub.unique1, stringu1+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub+order by 1, 2;+ QUERY PLAN +--------------------------------------------------------------------+ Nested Loop+ -> Gather Merge+ Workers Planned: 2+ -> Sort+ Sort Key: tenk1.unique1, tenk1.stringu1+ -> Parallel Index Scan using tenk1_unique1 on tenk1+ -> Function Scan on generate_series+(7 rows)++-- Parallel sort but with expression that can be safely generated at the base rel.+explain (costs off) select distinct sub.unique1, md5(stringu1)+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;+ QUERY PLAN +----------------------------------------------------------------------------------------+ Unique+ -> Nested Loop+ -> Gather Merge+ Workers Planned: 2+ -> Sort+ Sort Key: tenk1.unique1, (md5((tenk1.stringu1)::text)) COLLATE "C"+ -> Parallel Index Scan using tenk1_unique1 on tenk1+ -> Function Scan on generate_series+(8 rows)++explain (costs off) select sub.unique1, md5(stringu1)+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub+order by 1, 2;+ QUERY PLAN +----------------------------------------------------------------------------------+ Nested Loop+ -> Gather Merge+ Workers Planned: 2+ -> Sort+ Sort Key: tenk1.unique1, (md5((tenk1.stringu1)::text)) COLLATE "C"+ -> Parallel Index Scan using tenk1_unique1 on tenk1+ -> Function Scan on generate_series+(7 rows)++-- Parallel sort but with expression not available until the upper rel.+explain (costs off) select distinct sub.unique1, stringu1 || random()::text+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;+ QUERY PLAN +---------------------------------------------------------------------------------------------+ Unique+ -> Sort+ Sort Key: tenk1.unique1, (((tenk1.stringu1)::text || (random())::text)) COLLATE "C"+ -> Gather+ Workers Planned: 2+ -> Nested Loop+ -> Parallel Index Scan using tenk1_unique1 on tenk1+ -> Function Scan on generate_series+(8 rows)++explain (costs off) select sub.unique1, stringu1 || random()::text+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub+order by 1, 2;+ QUERY PLAN +---------------------------------------------------------------------------------------+ Sort+ Sort Key: tenk1.unique1, (((tenk1.stringu1)::text || (random())::text)) COLLATE "C"+ -> Gather+ Workers Planned: 2+ -> Nested Loop+ -> Parallel Index Scan using tenk1_unique1 on tenk1+ -> Function Scan on generate_series+(7 rows)+diff --git a/src/test/regress/sql/incremental_sort.sql b/src/test/regress/sql/incremental_sort.sqlindex 9c040c90e6..3ee11c394b 100644--- a/src/test/regress/sql/incremental_sort.sql+++ b/src/test/regress/sql/incremental_sort.sql@@ -221,3 +221,34 @@ set enable_hashagg to off;
explain (costs off) select * from t union select * from t order by 1,3;
drop table t;
++-- Sort pushdown can't go below where expressions are part of the rel target.+-- In particular this is interesting for volatile expressions which have to+-- go above joins since otherwise we'll incorrectly use expression evaluations+-- across multiple rows.+set enable_hashagg=off;+set enable_seqscan=off;+set enable_incremental_sort = off;+set parallel_tuple_cost=0;+set parallel_setup_cost=0;+set min_parallel_table_scan_size = 0;+set min_parallel_index_scan_size = 0;++-- Parallel sort below join.+explain (costs off) select distinct sub.unique1, stringu1+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;+explain (costs off) select sub.unique1, stringu1+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub+order by 1, 2;+-- Parallel sort but with expression that can be safely generated at the base rel.+explain (costs off) select distinct sub.unique1, md5(stringu1)+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;+explain (costs off) select sub.unique1, md5(stringu1)+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub+order by 1, 2;+-- Parallel sort but with expression not available until the upper rel.+explain (costs off) select distinct sub.unique1, stringu1 || random()::text+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub;+explain (costs off) select sub.unique1, stringu1 || random()::text+from tenk1, lateral (select tenk1.unique1 from generate_series(1, 1000)) as sub+order by 1, 2;--
2.25.4
[text/plain] v7-0002-partially-revert-ba3e76cc571eba3dea19c9465ff15ac3ac1.patch (3.3K, ../20201103023743.sswkl6h74dkbcfmp@development/3-v7-0002-partially-revert-ba3e76cc571eba3dea19c9465ff15ac3ac1.patch)
download | inline diff:
From aefe619b1cecb4ca70f2035b2b62ff270a4e80e7 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas@2ndquadrant.com>
Date: Tue, 3 Nov 2020 03:22:50 +0100
Subject: [PATCH 2/3] partially revert ba3e76cc571eba3dea19c9465ff15ac3ac186576
---
contrib/postgres_fdw/postgres_fdw.c | 29 +++++++++++++++++++++++++
src/backend/optimizer/path/equivclass.c | 29 -------------------------
src/include/optimizer/paths.h | 1 -
3 files changed, 29 insertions(+), 30 deletions(-)
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.cindex 9c5aaacc51..dcf533d700 100644--- a/contrib/postgres_fdw/postgres_fdw.c+++ b/contrib/postgres_fdw/postgres_fdw.c@@ -6524,6 +6524,35 @@ conversion_error_callback(void *arg)
}
}
+/*+ * Find an equivalence class member expression, all of whose Vars, come from+ * the indicated relation.+ */+Expr *+find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel)+{+ ListCell *lc_em;++ foreach(lc_em, ec->ec_members)+ {+ EquivalenceMember *em = lfirst(lc_em);++ if (bms_is_subset(em->em_relids, rel->relids) &&+ !bms_is_empty(em->em_relids))+ {+ /*+ * If there is more than one equivalence member whose Vars are+ * taken entirely from this relation, we'll be content to choose+ * any one of those.+ */+ return em->em_expr;+ }+ }++ /* We didn't find any suitable equivalence class expression */+ return NULL;+}+
/*
* Find an equivalence class member expression to be computed as a sort column
* in the given target.
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.cindex f98fd7b3eb..a507a76bcf 100644--- a/src/backend/optimizer/path/equivclass.c+++ b/src/backend/optimizer/path/equivclass.c@@ -768,35 +768,6 @@ get_eclass_for_sort_expr(PlannerInfo *root,
return newec;
}
-/*- * Find an equivalence class member expression, all of whose Vars, come from- * the indicated relation.- */-Expr *-find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel)-{- ListCell *lc_em;-- foreach(lc_em, ec->ec_members)- {- EquivalenceMember *em = lfirst(lc_em);-- if (bms_is_subset(em->em_relids, rel->relids) &&- !bms_is_empty(em->em_relids))- {- /*- * If there is more than one equivalence member whose Vars are- * taken entirely from this relation, we'll be content to choose- * any one of those.- */- return em->em_expr;- }- }-- /* We didn't find any suitable equivalence class expression */- return NULL;-}-
/*
* Find an equivalence class member expression that can be safely used by a
* sort node on top of the provided relation. The rules here must match those
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.hindex 8a4c6f8b59..5f6ffbd860 100644--- a/src/include/optimizer/paths.h+++ b/src/include/optimizer/paths.h@@ -134,7 +134,6 @@ extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root,
Index sortref,
Relids rel,
bool create_it);
-extern Expr *find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel);
extern Expr *find_em_expr_usable_for_sorting_rel(EquivalenceClass *ec, RelOptInfo *rel);
extern void generate_base_implied_equalities(PlannerInfo *root);
extern List *generate_join_implied_equalities(PlannerInfo *root,
--
2.25.4
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: tomas.vondra@2ndquadrant.com, jtc331@gmail.com, jaime.casanova@2ndquadrant.com
Subject: Re: enable_incremental_sort changes query behavior
In-Reply-To: <20201103023743.sswkl6h74dkbcfmp@development>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox