Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

Started by Dmitry Astapovover 4 years ago42 messages
#1Dmitry Astapov
dastapov@gmail.com

Hi!
I am trying to understand the behaviour of the query planner regarding the
push-down of the conditions "through" the join.

Lets say that I have tables a(adate date, aval text) and b(bdate date, bval
text), and I create a view:

create view v as
select a.adate, a.aval, b.bval from a join b on (a.adate = b.bdate);

Now, when I do (explain select * from v where adate='2021-05-12') I can see
that condition (= '2021-05-12') is used by the planned for table access to
both a and b.

However, if I use range-like condition (this is probably not a correct
terminology, but I am not familiar with the correct one) like BETWEEN or
(>='2021-05-21'), I will see that planner will use this condition to access
a, but not b. It seems that the type of join (inner or left) does not
really matter.

DB fiddle that illustrates this;
https://www.db-fiddle.com/f/pT2PwUkhJWuX9skWiBWXoL/0

In my experiments, I was never able to get an execution plan that "pushes
down" any condition apart from (=) through to the right side of the join,
which is rather surprising and leads to suboptimal planner estimates and
execution plans whenever view like the above is a part of a bigger query
with more joins on top.

Equally surprising is that I was unable to find documentation or past
mailing list discussions of this or similar topic, which leads me to
believe that I am just not familiar with the proper terminology and can't
come up with the right search terms.

Can you please tell me what is the proper way to describe this
behaviour/phenomenon (so that I can use it as search terms) and/or provide
me with references to the parts of the source code that determines which
conditions would be "pushed down" and which are not?

PS As far as I can see, this behaviour is consistent between versions 9.5,
10, 11, 12 and 13.

--
D. Astapov

#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dmitry Astapov (#1)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

Dmitry Astapov <dastapov@gmail.com> writes:

I am trying to understand the behaviour of the query planner regarding the
push-down of the conditions "through" the join.

I think your mental model is wrong. What's actually happening here is
that the planner uses equivalence classes to deduce implied conditions.
That is, we have the join condition a.adate = b.bdate and then you've
added the where condition a.adate = '2021-05-12'. Transitivity implies
that b.bdate = '2021-05-12', so we deduce that condition and are able
to apply it at the relation scan of b. Furthermore, having restricted
both a.adate and b.bdate to the same constant value at the scan level,
we no longer need to apply the join condition a.adate = b.bdate at all.
This is important not only to avoid the (probably minor) inefficiency
of rechecking the join condition, but because if we believed that all
three conditions were independently applicable, we'd come out with a
serious underestimate of the size of the join result.

In my experiments, I was never able to get an execution plan that "pushes
down" any condition apart from (=) through to the right side of the join,

None of the argument sketched above works for non-equality conditions.
There are some situations where you could probably figure out how to
use transitivity to deduce some implied condition, but cleaning things
up so that you don't have redundant conditions fouling up the join
size estimates seems like a hard problem.

Another issue is that we could easily expend a lot of cycles on deductions
that lead nowhere, because once you try to open up the mechanism to
consider operators other than equality, there will be a lot of things that
it looks at and then fails to do anything with. The equivalence class
mechanism is tied into the same logic that considers merge and hash joins,
so we are expending lots of cycles anytime we see an equality operator,
and not so much for other operators.

Equally surprising is that I was unable to find documentation or past
mailing list discussions of this or similar topic, which leads me to
believe that I am just not familiar with the proper terminology and can't
come up with the right search terms.

src/backend/optimizer/README has a discussion of equivalence classes.

regards, tom lane

#3Dmitry Astapov
dastapov@gmail.com
In reply to: Tom Lane (#2)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

On Wed, May 12, 2021 at 4:54 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Dmitry Astapov <dastapov@gmail.com> writes:

I am trying to understand the behaviour of the query planner regarding

the

push-down of the conditions "through" the join.

I think your mental model is wrong. What's actually happening here is
that the planner uses equivalence classes to deduce implied conditions.
That is, we have the join condition a.adate = b.bdate and then you've
added the where condition a.adate = '2021-05-12'. Transitivity implies
that b.bdate = '2021-05-12', so we deduce that condition and are able
to apply it at the relation scan of b. Furthermore, having restricted
both a.adate and b.bdate to the same constant value at the scan level,
we no longer need to apply the join condition a.adate = b.bdate at all.
This is important not only to avoid the (probably minor) inefficiency
of rechecking the join condition, but because if we believed that all
three conditions were independently applicable, we'd come out with a
serious underestimate of the size of the join result.

Thank you very much, my mental model was indeed incorrect, and the above is
very helpful.
Am I right in thinking that elimination the join condition is actually
quite important part of the process?
Could it possibly be the main reason for =ANY/(x IN (..)) not to be
optimized the same way?

In my experiments, I was never able to get an execution plan that "pushes
down" any condition apart from (=) through to the right side of the join,

None of the argument sketched above works for non-equality conditions.
There are some situations where you could probably figure out how to
use transitivity to deduce some implied condition, but cleaning things
up so that you don't have redundant conditions fouling up the join
size estimates seems like a hard problem.

I agree about inequality conditions, this problem seems to be rather hard
to tackle in the general case.

Is it still hard when one thinks about =ANY or (column in (val1, val2,
val3, ...)) as well?
I am thinking that =ANY would be a decent workaround for (x BETWEEN a AND
b) in quite a lot of cases, if it was propagated to all the columns in the
equivalence class.

Equally surprising is that I was unable to find documentation or past
mailing list discussions of this or similar topic, which leads me to
believe that I am just not familiar with the proper terminology and can't
come up with the right search terms.

src/backend/optimizer/README has a discussion of equivalence classes.

Thank you, this gives me a plethora of keywords for further searches.

I realize that it is possibly off-topic here, but what about workarounds
for inequality constraints, joins and views? Maybe you could give me some
pointers here as well?

My tables are large to huge (think OLAP, not OLTP). I found out when I have
a view that joins several (2 to 10) tables on the column that is
semantically the same in all of them (let's say it is ID and we join on
ID), I do not have many avenues to efficiently select from such view for a
list of IDs at the same time.

I could:
1) Do lots of fast queries and union them:
select * from vw where id=ID1 union all select * from vw where id=ID2
....., which is only really feasible if the query is generated by the
program

2)expose all ID columns from all the tables used in the view body and do:
select * from vw where id=ANY() and id1=ANY() and id2=ANY() and id3=ANY()
.....
This only works well if the view hierarchy is flat (no views on views). If
there are other views that use this use, re-exports of extra columns
quickly snowballs, you might need column renaming if same view ends up
being used more than once through two different dependency paths. Plus
people not familiar with the problem tend to omit "clearly superfluous"
columns from the new views they build on top.

3)forbid views that join tables larger than a certain size/dismantle views
that become inefficient (this only works if the problem is detected fast
enough and the view did not become popular yet)

So all of the workarounds I see in front of me right now are somewhat sad,
but they are necessary, as not doing them means that queries would take
hours or days instead of minutes.

Is there anything better that I have not considered in terms of workarounds?

--
D. Astapov

#4Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dmitry Astapov (#3)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

Dmitry Astapov <dastapov@gmail.com> writes:

Am I right in thinking that elimination the join condition is actually
quite important part of the process?
Could it possibly be the main reason for =ANY/(x IN (..)) not to be
optimized the same way?

Yup.

Is it still hard when one thinks about =ANY or (column in (val1, val2,
val3, ...)) as well?

Yeah. For instance, if you have
WHERE a = b AND a IN (1,2,3)
then yes, you could deduce "b IN (1,2,3)", but this would not give you
license to drop the "a = b" condition. So now you have to figure out
what the selectivity of that is after the application of the partially
redundant IN clauses.

I recall somebody (David Rowley, maybe? Too lazy to check archives.)
working on this idea awhile ago, but he didn't get to the point of
a committable patch.

regards, tom lane

#5David Rowley
dgrowleyml@gmail.com
In reply to: Tom Lane (#4)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

On Fri, 14 May 2021 at 11:22, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I recall somebody (David Rowley, maybe? Too lazy to check archives.)
working on this idea awhile ago, but he didn't get to the point of
a committable patch.

Yeah. Me. The discussion is in [1]/messages/by-id/CAKJS1f9FK_X_5HKcPcSeimy16Owe3EmPmmGsGWLcKkj_rW9s6A@mail.gmail.com.

David

[1]: /messages/by-id/CAKJS1f9FK_X_5HKcPcSeimy16Owe3EmPmmGsGWLcKkj_rW9s6A@mail.gmail.com

#6Andy Fan
zhihui.fan1213@gmail.com
In reply to: Tom Lane (#4)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

So now you have to figure out
what the selectivity of that is after the application of the partially
redundant IN clauses.

Would marking the new added RestrictInfo.norm_selec > 1 be OK?

clause_selectivity_ext

/*
* If the clause is marked redundant, always return 1.0.
*/
if (rinfo->norm_selec > 1)
return (Selectivity) 1.0;

--
Best Regards
Andy Fan (https://www.aliyun.com/)

#7David Rowley
dgrowleyml@gmail.com
In reply to: Andy Fan (#6)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

On Mon, 17 May 2021 at 14:52, Andy Fan <zhihui.fan1213@gmail.com> wrote:

Would marking the new added RestrictInfo.norm_selec > 1 be OK?

There would be cases you'd want to not count the additional clauses in
the selectivity estimation and there would be cases you would want to.

For example:

SELECT ... FROM t1 INNER JOIN t2 ON t1.dt = t2.dt WHERE t1.dt BETWEEN
'date1' AND 'date2';

If you derived that t2.dt is also BETWEEN 'date1' AND 'date2' then
you'd most likely want to include those quals for scans feeding merge,
hash and non-parameterized nested loop joins, so you'd also want to
count them in your selectivity estimations, else you'd feed junk
values into the join selectivity estimations.

Parameterized nested loop joins might be different as if you were
looping up an index for t1.dt values on some index on t2.dt, then
you'd likely not want to bother also filtering out the between clause
values too. They're redundant in that case.

I imagined we'd have some functions in equivclass.c that allows you to
choose if you wanted the additional filters or not.

Tom's example, WHERE a = b AND a IN (1,2,3), if a and b were in the
same relation then you'd likely never want to include the additional
quals. The only reason I could think that it would be a good idea is
if "b" had an index but "a" didn't. I've not checked the code, but
the index matching code might already allow that to work anyway.

David

#8Andy Fan
zhihui.fan1213@gmail.com
In reply to: David Rowley (#7)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

On Wed, May 19, 2021 at 8:15 PM David Rowley <dgrowleyml@gmail.com> wrote:

On Mon, 17 May 2021 at 14:52, Andy Fan <zhihui.fan1213@gmail.com> wrote:

Would marking the new added RestrictInfo.norm_selec > 1 be OK?

There would be cases you'd want to not count the additional clauses in
the selectivity estimation and there would be cases you would want to.

For example:

SELECT ... FROM t1 INNER JOIN t2 ON t1.dt = t2.dt WHERE t1.dt BETWEEN
'date1' AND 'date2';

If you derived that t2.dt is also BETWEEN 'date1' AND 'date2' then
you'd most likely want to include those quals for scans feeding merge,
hash and non-parameterized nested loop joins, so you'd also want to
count them in your selectivity estimations, else you'd feed junk
values into the join selectivity estimations.

Yes, you are correct.

Parameterized nested loop joins might be different as if you were
looping up an index for t1.dt values on some index on t2.dt, then
you'd likely not want to bother also filtering out the between clause
values too. They're redundant in that case.

I do not truly understand this.

I imagined we'd have some functions in equivclass.c that allows you to
choose if you wanted the additional filters or not.

Sounds like a good idea.

Tom's example, WHERE a = b AND a IN (1,2,3), if a and b were in the
same relation then you'd likely never want to include the additional
quals. The only reason I could think that it would be a good idea is
if "b" had an index but "a" didn't. I've not checked the code, but
the index matching code might already allow that to work anyway.

+1 for this feature overall.

--
Best Regards
Andy Fan (https://www.aliyun.com/)

#9Andy Fan
zhihui.fan1213@gmail.com
In reply to: David Rowley (#5)
6 attachment(s)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

On Fri, May 14, 2021 at 12:22 PM David Rowley <dgrowleyml@gmail.com> wrote:

On Fri, 14 May 2021 at 11:22, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I recall somebody (David Rowley, maybe? Too lazy to check archives.)
working on this idea awhile ago, but he didn't get to the point of
a committable patch.

Yeah. Me. The discussion is in [1].

David

[1]
/messages/by-id/CAKJS1f9FK_X_5HKcPcSeimy16Owe3EmPmmGsGWLcKkj_rW9s6A@mail.gmail.com

Hi:

I read through that thread and summarized the current pending issue as
below IIUC.
a). The most challenging issue is this push down misleads the planner's
rows estimation,
which probably be worse than the lack of such push down. b). The new
generated
qual may increase the qual execution cost. c). Planning time is also
increased but
we can not gain much because of that. I just tried to address these issues
as below
based on the patch David has finished a long time ago.

To address the row estimation issue, The most straightforward way to fix
this is to
ignore the derived clauses when figuring out the RelOptInfo->rows on base
relation.
To note which clause is derived from this patch, I added a new field
"EquivalenceClass *
derived" in RestrictInfo. and then added a included_derived option in
clauselist_selectivity_ext,
during the set_xxx_rel_size function, we can pass the
included_derived=false. This strategy
should be used in get_parameterized_baserel_size. In all the other cases,
include_derived=true
is used. which are finished in commit 2. (Commit 1 is Daivd's patch, I
just rebased it)

set enable_hashjoin to off;
set enable_mergejoin to off;
set enable_seqscan to on;
regression=# explain analyze select * from tenk1 a, tenk1 b where
a.thousand = b.thousand and a.thousand < 100;

QUERY
PLAN

-----------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop (cost=27.14..1090.67 rows=10740 width=488) (actual
time=0.404..15.006 rows=10000 loops=1)
-> Bitmap Heap Scan on tenk1 b (cost=26.84..385.26 rows=10000
width=244) (actual time=0.350..1.419 rows=1000 loops=1)
Recheck Cond: (thousand < 100)\
Heap Blocks: exact=324
-> Bitmap Index Scan on tenk1_thous_tenthous (cost=0.00..24.34
rows=1074 width=0) (actual time=0.238..0.240 rows=1000 loops=1)
Index Cond: (thousand < 100)
-> Memoize (cost=0.30..0.47 rows=1 width=244) (actual
time=0.002..0.006 rows=10 loops=1000)
Cache Key: b.thousand
Cache Mode: logical
Hits: 900 Misses: 100 Evictions: 0 Overflows: 0 Memory Usage:
277kB
-> Index Scan using tenk1_thous_tenthous on tenk1 a
(cost=0.29..0.46 rows=1 width=244) (actual time=0.010..0.032 rows=10
loops=100)
Index Cond: ((thousand = b.thousand) AND (thousand < 100))
Planning Time: 2.459 ms
Execution Time: 15.964 ms
(14 rows)

As shown above, with commit 2 the JoinRel's rows estimation is correct
now. but it will mislead
the DBA to read the plan. See Bitmap Heap Scan on tenk1 b
(...rows=10000..) (... rows=1000 loops=1)
This is because RelOptInfo->rows is not just used to calculate the
joinrel.rows but also be used to
show the set Path.rows at many places. I can't think of a better way than
adding a new filtered_rows
in RelOptInfo which the semantics is used for Path.rows purpose only. That
is what commit 3 does.

After commit 3, we can see:

regression=# explain analyze select * from tenk1 a, tenk1 b where
a.thousand = b.thousand and a.thousand < 100;

QUERY
PLAN

-----------------------------------------------------------------------------------------------------------------------------------------------

Nested Loop (cost=24.90..459.16 rows=10740 width=488) (actual
time=0.440..16.966 rows=10000 loops=1)
-> Bitmap Heap Scan on tenk1 b (cost=24.61..383.03 rows=1074
width=244) (actual time=0.383..1.546 rows=1000 loops=1)
Recheck Cond: (thousand < 100)
Heap Blocks: exact=324
-> Bitmap Index Scan on tenk1_thous_tenthous (cost=0.00..24.34
rows=1074 width=0) (actual time=0.270..0.272 rows=1000 loops=1)
Index Cond: (thousand < 100)
-> Memoize (cost=0.30..0.47 rows=1 width=244) (actual
time=0.002..0.008 rows=10 loops=1000)
Cache Key: b.thousand
Cache Mode: logical
Hits: 900 Misses: 100 Evictions: 0 Overflows: 0 Memory Usage:
277kB
-> Index Scan using tenk1_thous_tenthous on tenk1 a
(cost=0.29..0.46 rows=1 width=244) (actual time=0.012..0.050 rows=10
loops=100)
Index Cond: ((thousand = b.thousand) AND (thousand < 100))
Planning Time: 2.578 ms
Execution Time: 17.929 ms
(14 rows)

"Bitmap Heap Scan on tenk1 b (... rows=1074 ..) (.. rows=1000 loops=1)"
shows the issue fixed. but
There is something wrong below.

Index Scan using tenk1_thous_tenthous on tenk1 a (cost=0.29..0.46 rows=1
width=244) (actual time=0.012..0.050 rows=10 loops=100)
Index Cond: ((thousand = b.thousand) AND (thousand < 100))

Here the " (thousand < 100)" is from the user, not from this patch. and
(thousand = b.thousand) AND (thousand < 100)
has some correlation. I can't think of a solution for this. and fixing
this issue is beyond the scope of this patch.

So at this stage, I think the row estimation issue is gone.

As the new generated equals increase the execution cost opinion, I think
it is hard for planners to distinguish which quals deserves adding or not.
Instead
I just removed the quals execution during create_plan stage to remove the
obviously
duplicated qual executions. I only handled the case that the derived quals
is executed
at the same time with the restrinctInfo who's parent_ec is used to generate
the
derived quals. If I understand the RestrictInfo.parent_ec correctly, The
cost of
finding out the correlated quals in this patch are pretty low, see
is_correlated_derived_clause.
This is what commit 4 does. After we apply it, we can see the last demo
above becomes to:

regression=# explain analyze select * from tenk1 a join d_tenk2 b on
a.thousand = b.thousand and a.thousand < 100;

QUERY
PLAN

--------------------------------------------------------------------------------------------------------------------------------------------------

Nested Loop (cost=10000000000.30..10000002799.78 rows=20020 width=488)
(actual time=0.051..26.080 rows=20000 loops=1)
-> Seq Scan on tenk1 a (cost=10000000000.00..10000000470.00 rows=1001
width=244) (actual time=0.018..3.902 rows=1000 loops=1)
Filter: (thousand < 100)
Rows Removed by Filter: 9000
-> Memoize (cost=0.30..3.18 rows=20 width=244) (actual
time=0.002..0.008 rows=20 loops=1000)
Cache Key: a.thousand
Cache Mode: logical
Hits: 900 Misses: 100 Evictions: 0 Overflows: 0 Memory Usage:
546kB
-> Index Scan using d_tenk2_thousand_idx on d_tenk2 b
(cost=0.29..3.17 rows=20 width=244) (actual time=0.008..0.037 rows=20
loops=100)
Index Cond: (thousand = a.thousand)
Planning Time: 0.596 ms
Execution Time: 27.502 ms
(12 rows)

The "thousand < 100" for b is removed during execution.

Commit 5 reduced the requirements for this path to work. Now it
supports ScalarArrayOpExpr
and any perudoconstant filter to support the user case I meet. Commit 6
added some testcase
and they are just used for review since there are two many runtime
statistics in the output and
I can't think of way to fix it.

I also study David's commit 1, and the semantics of ec_filters is so
accurate and I'm very
excited to see it.

This patch series is still in the PoC stage, so something is not handled at
all. For commit 2, I didn't
handle extended statistics related paths and I just handled plain rel
(subquery, forign table and so
on are missed). I think it is OK for a PoC.

At last, I will share some performance testing for this patch. This is the
real user case I met.

create table p (a int, b int) partition by range(a);
select 'create table p_' || i || ' partition of p for values from (' ||
(i-1) * 100000 || ') to (' || i * 100000 || ');' from generate_series(1,
50)i; \gexec
insert into p select i, i from generate_series(1, 50 * 100000 -1) i;
create index on p(a);

create table q (a int, b int) partition by range(a);
select 'create table q_' || i || ' partition of q for values from (' ||
(i-1) * 100000 || ') to (' || i * 100000 || ');' from generate_series(1,
50)i; \gexec
insert into q select * from p;
create index on q(a);

select * from p, q where p.a = q.a and p.a in (3, 200000);

Run the above query in both prepared and no prepared case, I get the
following results:

| workload | with this feature | w/o this feature |
|--------------+-------------------+------------------|
| Prepared | 0.25 ms | 0.8 ms |
| Non Prepared | 0.890 ms | 4.207 ms |

Any thoughts?

--
Best Regards
Andy Fan

Attachments:

v1-0003-introduce-RelOptInfo.filtered_rows.patchapplication/x-patch; name=v1-0003-introduce-RelOptInfo.filtered_rows.patchDownload
From 62e161902bf5d77089af772e4f458f05df368585 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=80=E6=8C=83?= <yizhi.fzh@alibaba-inc.com>
Date: Tue, 1 Feb 2022 15:20:10 +0800
Subject: [PATCH v1 3/6] introduce RelOptInfo.filtered_rows.

---
 src/backend/optimizer/path/costsize.c | 21 ++++++++++++++++-----
 src/include/nodes/pathnodes.h         |  2 ++
 2 files changed, 18 insertions(+), 5 deletions(-)

diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 9e303877af7..b1117257a38 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -241,7 +241,7 @@ cost_seqscan(Path *path, PlannerInfo *root,
 	if (param_info)
 		path->rows = param_info->ppi_rows;
 	else
-		path->rows = baserel->rows;
+		path->rows = baserel->filtered_rows;
 
 	if (!enable_seqscan)
 		startup_cost += disable_cost;
@@ -539,7 +539,7 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count,
 	}
 	else
 	{
-		path->path.rows = baserel->rows;
+		path->path.rows = baserel->filtered_rows;
 		/* qpquals come from just the rel's restriction clauses */
 		qpquals = extract_nonindex_conditions(path->indexinfo->indrestrictinfo,
 											  path->indexclauses);
@@ -978,7 +978,7 @@ cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel,
 	if (param_info)
 		path->rows = param_info->ppi_rows;
 	else
-		path->rows = baserel->rows;
+		path->rows = baserel->filtered_rows;
 
 	if (!enable_bitmapscan)
 		startup_cost += disable_cost;
@@ -1209,7 +1209,7 @@ cost_tidscan(Path *path, PlannerInfo *root,
 	if (param_info)
 		path->rows = param_info->ppi_rows;
 	else
-		path->rows = baserel->rows;
+		path->rows = baserel->filtered_rows;
 
 	/* Count how many tuples we expect to retrieve */
 	ntuples = 0;
@@ -1320,7 +1320,7 @@ cost_tidrangescan(Path *path, PlannerInfo *root,
 	if (param_info)
 		path->rows = param_info->ppi_rows;
 	else
-		path->rows = baserel->rows;
+		path->rows = baserel->filtered_rows;
 
 	/* Count how many tuples and pages we expect to scan */
 	selectivity = clauselist_selectivity(root, tidrangequals, baserel->relid,
@@ -4938,6 +4938,17 @@ set_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel)
 
 	rel->rows = clamp_row_est(nrows);
 
+	nrows = rel->tuples *
+		clauselist_selectivity_ext(root,
+								   rel->baserestrictinfo,
+								   0,
+								   JOIN_INNER,
+								   NULL,
+								   true,
+								   true /* include_derived, for filtered rows */);
+
+	rel->filtered_rows = clamp_row_est(nrows);
+
 	cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root);
 
 	set_rel_width(root, rel);
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 42368e10b8e..dbe1775f96d 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -683,6 +683,8 @@ typedef struct RelOptInfo
 	/* size estimates generated by planner */
 	Cardinality	rows;			/* estimated number of result tuples */
 
+	Cardinality filtered_rows;  /* filtered rows */
+
 	/* per-relation planner control flags */
 	bool		consider_startup;	/* keep cheap-startup-cost paths? */
 	bool		consider_param_startup; /* ditto, for parameterized paths? */
-- 
2.21.0

v1-0001-Rebaee-David-s-patch-against-the-latest-code.patchapplication/x-patch; name=v1-0001-Rebaee-David-s-patch-against-the-latest-code.patchDownload
From dee63e6e198ecfe66638910b8764dc5ba6e96e7b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=80=E6=8C=83?= <yizhi.fzh@alibaba-inc.com>
Date: Tue, 1 Feb 2022 20:56:40 +0800
Subject: [PATCH v1 1/6] Rebaee David's patch against the latest code.

---
 src/backend/nodes/outfuncs.c             |  14 ++
 src/backend/optimizer/path/equivclass.c  | 182 +++++++++++++++++++++++
 src/backend/optimizer/plan/initsplan.c   |  96 +++++++++---
 src/backend/utils/cache/lsyscache.c      |  28 ++++
 src/include/nodes/nodes.h                |   1 +
 src/include/nodes/pathnodes.h            |  37 +++++
 src/include/optimizer/paths.h            |   1 +
 src/include/utils/lsyscache.h            |   1 +
 src/test/regress/expected/equivclass.out |  45 ++++++
 9 files changed, 388 insertions(+), 17 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2b0236937aa..504b805326f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2504,6 +2504,17 @@ _outEquivalenceMember(StringInfo str, const EquivalenceMember *node)
 	WRITE_OID_FIELD(em_datatype);
 }
 
+static void
+_outEquivalenceFilter(StringInfo str, const EquivalenceFilter *node)
+{
+	WRITE_NODE_TYPE("EQUIVALENCEFILTER");
+
+	WRITE_NODE_FIELD(ef_const);
+	WRITE_OID_FIELD(ef_opno);
+	WRITE_BOOL_FIELD(ef_const_is_left);
+	WRITE_UINT_FIELD(ef_source_rel);
+}
+
 static void
 _outPathKey(StringInfo str, const PathKey *node)
 {
@@ -4304,6 +4315,9 @@ outNode(StringInfo str, const void *obj)
 			case T_EquivalenceMember:
 				_outEquivalenceMember(str, obj);
 				break;
+			case T_EquivalenceFilter:
+				_outEquivalenceFilter(str, obj);
+				break;
 			case T_PathKey:
 				_outPathKey(str, obj);
 				break;
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 8c6770de972..f9ae2785d60 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -19,6 +19,7 @@
 #include <limits.h>
 
 #include "access/stratnum.h"
+#include "catalog/pg_am.h"
 #include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -1250,6 +1251,37 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 	}
 }
 
+/*
+ * finds the opfamily and strategy number for the specified 'opno' and 'method'
+ * access method. Returns True if one is found and sets 'family' and
+ * 'amstrategy', or returns False if none are found.
+ */
+static bool
+find_am_family_and_stategy(Oid opno, Oid method, Oid *family, int *amstrategy)
+{
+	List *opfamilies;
+	ListCell *l;
+	int strategy;
+
+	opfamilies = get_opfamilies(opno, method);
+
+	foreach(l, opfamilies)
+	{
+		Oid opfamily = lfirst_oid(l);
+
+		strategy = get_op_opfamily_strategy(opno, opfamily);
+
+		if (strategy)
+		{
+			*amstrategy = strategy;
+			*family = opfamily;
+			return true;
+		}
+	}
+
+	return false;
+}
+
 /*
  * generate_base_implied_equalities when EC contains no pseudoconstants
  */
@@ -1259,6 +1291,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 {
 	EquivalenceMember **prev_ems;
 	ListCell   *lc;
+	ListCell   *lc2;
 
 	/*
 	 * We scan the EC members once and track the last-seen member for each
@@ -1320,6 +1353,57 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 				rinfo->right_em = cur_em;
 			}
 		}
+
+		/*
+		 * Also push any EquivalenceFilter clauses down into all relations
+		 * other than the one which the filter actually originated from.
+		 */
+		foreach(lc2, ec->ec_filters)
+		{
+			EquivalenceFilter *ef = (EquivalenceFilter *) lfirst(lc2);
+			Expr *leftexpr;
+			Expr *rightexpr;
+			int strategy;
+			Oid opno;
+			Oid family;
+
+			if (ef->ef_source_rel == relid)
+				continue;
+
+			if (!find_am_family_and_stategy(ef->ef_opno, BTREE_AM_OID,
+				&family, &strategy))
+				continue;
+
+			if (ef->ef_const_is_left)
+			{
+				leftexpr = (Expr *) ef->ef_const;
+				rightexpr = cur_em->em_expr;
+			}
+			else
+			{
+				leftexpr = cur_em->em_expr;
+				rightexpr = (Expr *) ef->ef_const;
+			}
+
+			opno = get_opfamily_member(family,
+										exprType((Node *) leftexpr),
+										exprType((Node *) rightexpr),
+										strategy);
+
+			if (opno == InvalidOid)
+				continue;
+
+			process_implied_equality(root, opno,
+										ec->ec_collation,
+										leftexpr,
+										rightexpr,
+										bms_copy(ec->ec_relids),
+										bms_copy(cur_em->em_nullable_relids),
+									 	ec->ec_min_security,
+										ec->ec_below_outer_join,
+										false);
+		}
+
 		prev_ems[relid] = cur_em;
 	}
 
@@ -1901,6 +1985,104 @@ create_join_clause(PlannerInfo *root,
 	return rinfo;
 }
 
+/*
+ * distribute_filter_quals_to_eclass
+ *		For each OpExpr in quallist look for an eclass which has an Expr
+ *		matching the Expr in the OpExpr. If a match is found we add a new
+ *		EquivalenceFilter to the eclass containing the filter details.
+ */
+void
+distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
+{
+	ListCell *l;
+
+	/* fast path for when no eclasses have been generated */
+	if (root->eq_classes == NIL)
+		return;
+
+	/*
+	 * For each qual in quallist try and find an eclass which contains the
+	 * non-Const part of the OpExpr. We'll tag any matches that we find onto
+	 * the correct eclass.
+	 */
+	foreach(l, quallist)
+	{
+		OpExpr	   *opexpr = (OpExpr *) lfirst(l);
+		Expr	   *leftexpr = (Expr *) linitial(opexpr->args);
+		Expr	   *rightexpr = (Expr *) lsecond(opexpr->args);
+		Const	   *constexpr;
+		Expr	   *varexpr;
+		Relids		exprrels;
+		int			relid;
+		bool		const_isleft;
+		ListCell *l2;
+
+		/*
+		 * Determine if the the OpExpr is in the form "expr op const" or
+		 * "const op expr".
+		 */
+		if (IsA(leftexpr, Const))
+		{
+			constexpr = (Const *) leftexpr;
+			varexpr = rightexpr;
+			const_isleft = true;
+		}
+		else
+		{
+			constexpr = (Const *) rightexpr;
+			varexpr = leftexpr;
+			const_isleft = false;
+		}
+
+		exprrels = pull_varnos(root, (Node *) varexpr);
+
+		/* should be filtered out, but we need to determine relid anyway */
+		if (!bms_get_singleton_member(exprrels, &relid))
+			continue;
+
+		/* search for a matching eclass member in all eclasses */
+		foreach(l2, root->eq_classes)
+		{
+			EquivalenceClass *ec = (EquivalenceClass *) lfirst(l2);
+			ListCell *l3;
+
+			if (ec->ec_broken || ec->ec_has_volatile)
+				continue;
+
+			/*
+			 * if the eclass has a const then that const will serve as the
+			 * filter, we needn't add any others.
+			 */
+			if (ec->ec_has_const)
+				continue;
+
+			/* skip this eclass no members exist which belong to this relid */
+			if (!bms_is_member(relid, ec->ec_relids))
+				continue;
+
+			foreach(l3, ec->ec_members)
+			{
+				EquivalenceMember *em = (EquivalenceMember *) lfirst(l3);
+
+				if (!bms_is_member(relid, em->em_relids))
+					continue;
+
+				if (equal(em->em_expr, varexpr))
+				{
+					EquivalenceFilter *efilter;
+					efilter = makeNode(EquivalenceFilter);
+					efilter->ef_const = (Const *) copyObject(constexpr);
+					efilter->ef_const_is_left = const_isleft;
+					efilter->ef_opno = opexpr->opno;
+					efilter->ef_source_rel = relid;
+
+					ec->ec_filters = lappend(ec->ec_filters, efilter);
+					break;		/* Onto the next eclass */
+				}
+			}
+		}
+	}
+}
 
 /*
  * reconsider_outer_join_clauses
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 023efbaf092..b219bee8567 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -53,7 +53,7 @@ static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel,
 static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
 								 bool below_outer_join,
 								 Relids *qualscope, Relids *inner_join_rels,
-								 List **postponed_qual_list);
+								 List **postponed_qual_list, List **filter_qual_list);
 static void process_security_barrier_quals(PlannerInfo *root,
 										   int rti, Relids qualscope,
 										   bool below_outer_join);
@@ -70,7 +70,8 @@ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 									Relids qualscope,
 									Relids ojscope,
 									Relids outerjoin_nonnullable,
-									List **postponed_qual_list);
+									List **postponed_qual_list,
+									List **filter_qual_list);
 static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p,
 								  Relids *nullable_relids_p, bool is_pushed_down);
 static bool check_equivalence_delay(PlannerInfo *root,
@@ -650,6 +651,43 @@ create_lateral_join_info(PlannerInfo *root)
 	}
 }
 
+/*
+ * is_simple_filter_qual
+ *             Analyzes an OpExpr to determine if it may be useful as an
+ *             EquivalenceFilter. Returns true if the OpExpr may be of some use, or
+ *             false if it should not be used.
+ */
+static bool
+is_simple_filter_qual(PlannerInfo *root, OpExpr *expr)
+{
+       Expr *leftexpr;
+       Expr *rightexpr;
+
+       if (!IsA(expr, OpExpr))
+               return false;
+
+       if (list_length(expr->args) != 2)
+               return false;
+
+       leftexpr = (Expr *) linitial(expr->args);
+       rightexpr = (Expr *) lsecond(expr->args);
+
+       /* XXX should we restrict these to simple Var op Const expressions? */
+       if (IsA(leftexpr, Const))
+       {
+		   if (bms_membership(pull_varnos(root, (Node *) rightexpr)) == BMS_SINGLETON &&
+			   !contain_volatile_functions((Node *) rightexpr))
+                       return true;
+       }
+       else if (IsA(rightexpr, Const))
+       {
+		   if (bms_membership(pull_varnos(root, (Node *) leftexpr)) == BMS_SINGLETON &&
+			   !contain_volatile_functions((Node *) leftexpr))
+                       return true;
+       }
+
+       return false;
+}
 
 /*****************************************************************************
  *
@@ -690,6 +728,7 @@ deconstruct_jointree(PlannerInfo *root)
 	Relids		qualscope;
 	Relids		inner_join_rels;
 	List	   *postponed_qual_list = NIL;
+	List	   *filter_qual_list = NIL;
 
 	/* Start recursion at top of jointree */
 	Assert(root->parse->jointree != NULL &&
@@ -700,11 +739,14 @@ deconstruct_jointree(PlannerInfo *root)
 
 	result = deconstruct_recurse(root, (Node *) root->parse->jointree, false,
 								 &qualscope, &inner_join_rels,
-								 &postponed_qual_list);
+								 &postponed_qual_list, &filter_qual_list);
 
 	/* Shouldn't be any leftover quals */
 	Assert(postponed_qual_list == NIL);
 
+	/* try and match each filter_qual_list item up with an eclass. */
+	distribute_filter_quals_to_eclass(root, filter_qual_list);
+
 	return result;
 }
 
@@ -725,6 +767,8 @@ deconstruct_jointree(PlannerInfo *root)
  *		or free this, either)
  *	*postponed_qual_list is a list of PostponedQual structs, which we can
  *		add quals to if they turn out to belong to a higher join level
+ *	*filter_qual_list is appended to with a list of quals which may be useful
+ *		include as EquivalenceFilters.
  *	Return value is the appropriate joinlist for this jointree node
  *
  * In addition, entries will be added to root->join_info_list for outer joins.
@@ -732,7 +776,7 @@ deconstruct_jointree(PlannerInfo *root)
 static List *
 deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 					Relids *qualscope, Relids *inner_join_rels,
-					List **postponed_qual_list)
+					List **postponed_qual_list, List **filter_qual_list)
 {
 	List	   *joinlist;
 
@@ -785,7 +829,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 											   below_outer_join,
 											   &sub_qualscope,
 											   inner_join_rels,
-											   &child_postponed_quals);
+											   &child_postponed_quals,
+											   filter_qual_list);
 			*qualscope = bms_add_members(*qualscope, sub_qualscope);
 			sub_members = list_length(sub_joinlist);
 			remaining--;
@@ -819,7 +864,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 										below_outer_join, JOIN_INNER,
 										root->qual_security_level,
 										*qualscope, NULL, NULL,
-										NULL);
+										NULL,
+										filter_qual_list);
 			else
 				*postponed_qual_list = lappend(*postponed_qual_list, pq);
 		}
@@ -835,7 +881,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 									below_outer_join, JOIN_INNER,
 									root->qual_security_level,
 									*qualscope, NULL, NULL,
-									postponed_qual_list);
+									postponed_qual_list,
+									filter_qual_list);
 		}
 	}
 	else if (IsA(jtnode, JoinExpr))
@@ -873,11 +920,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													below_outer_join,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = *qualscope;
 				/* Inner join adds no restrictions for quals */
@@ -890,11 +939,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													true,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = bms_union(left_inners, right_inners);
 				nonnullable_rels = leftids;
@@ -904,11 +955,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													below_outer_join,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = bms_union(left_inners, right_inners);
 				/* Semi join adds no restrictions for quals */
@@ -925,11 +978,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   true,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													true,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = bms_union(left_inners, right_inners);
 				/* each side is both outer and inner */
@@ -1013,7 +1068,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 									root->qual_security_level,
 									*qualscope,
 									ojscope, nonnullable_rels,
-									postponed_qual_list);
+									postponed_qual_list,
+									filter_qual_list);
 		}
 
 		/* Now we can add the SpecialJoinInfo to join_info_list */
@@ -1117,6 +1173,7 @@ process_security_barrier_quals(PlannerInfo *root,
 									qualscope,
 									qualscope,
 									NULL,
+									NULL,
 									NULL);
 		}
 		security_level++;
@@ -1610,7 +1667,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 						Relids qualscope,
 						Relids ojscope,
 						Relids outerjoin_nonnullable,
-						List **postponed_qual_list)
+						List **postponed_qual_list,
+						List **filter_qual_list)
 {
 	Relids		relids;
 	bool		is_pushed_down;
@@ -1964,6 +2022,10 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 
 	/* No EC special case applies, so push it into the clause lists */
 	distribute_restrictinfo_to_rels(root, restrictinfo);
+
+	/* Check if the qual looks useful to harvest as an EquivalenceFilter */
+	if (filter_qual_list != NULL && is_simple_filter_qual(root, (OpExpr *) clause))
+		*filter_qual_list = lappend(*filter_qual_list, clause);
 }
 
 /*
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index feef9998634..add2e7176ae 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -341,6 +341,34 @@ get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type)
 	return result;
 }
 
+/*
+ * get_opfamilies
+ *		Returns a list of Oids of each opfamily which 'opno' belonging to
+ *		'method' access method.
+ */
+List *
+get_opfamilies(Oid opno, Oid method)
+{
+	List	   *result = NIL;
+	CatCList   *catlist;
+	int			i;
+
+	catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno));
+
+	for (i = 0; i < catlist->n_members; i++)
+	{
+		HeapTuple	tuple = &catlist->members[i]->tuple;
+		Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
+
+		if (aform->amopmethod == method)
+			result = lappend_oid(result, aform->amopfamily);
+	}
+
+	ReleaseSysCacheList(catlist);
+
+	return result;
+}
+
 /*
  * get_mergejoin_opfamilies
  *		Given a putatively mergejoinable operator, return a list of the OIDs
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index da35f2c2722..3a9a235cd0e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
 	/* these aren't subclasses of Path: */
 	T_EquivalenceClass,
 	T_EquivalenceMember,
+	T_EquivalenceFilter,
 	T_PathKey,
 	T_PathTarget,
 	T_RestrictInfo,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 1f3845b3fec..e73fef057a4 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -990,6 +990,7 @@ typedef struct EquivalenceClass
 	List	   *ec_members;		/* list of EquivalenceMembers */
 	List	   *ec_sources;		/* list of generating RestrictInfos */
 	List	   *ec_derives;		/* list of derived RestrictInfos */
+	List	   *ec_filters;
 	Relids		ec_relids;		/* all relids appearing in ec_members, except
 								 * for child members (see below) */
 	bool		ec_has_const;	/* any pseudoconstants in ec_members? */
@@ -1002,6 +1003,42 @@ typedef struct EquivalenceClass
 	struct EquivalenceClass *ec_merged; /* set if merged into another EC */
 } EquivalenceClass;
 
+/*
+ * EquivalenceFilter - List of filters on Consts which belong to the
+ * EquivalenceClass.
+ *
+ * When building the equivalence classes we also collected a list of quals in
+ * the form of; "Expr op Const" and "Const op Expr". These are collected in the
+ * hope that we'll later generate an equivalence class which contains the
+ * "Expr" part. For example, if we parse a query such as;
+ *
+ *		SELECT * FROM t1 INNER JOIN t2 ON t1.id = t2.id WHERE t1.id < 10;
+ *
+ * then since we'll end up with an equivalence class containing {t1.id,t2.id},
+ * we'll tag the "< 10" filter onto the eclass. We are able to do this because
+ * the eclass proves equality between each class member, therefore all members
+ * must be below 10.
+ *
+ * EquivalenceFilters store the details required to allow us to push these
+ * filter clauses down into other relations which share an equivalence class
+ * containing a member which matches the expression of this EquivalenceFilter.
+ *
+ * ef_const is the Const value which this filter should filter against.
+ * ef_opno is the operator to filter on.
+ * ef_const_is_left marks if the OpExpr was in the form "Const op Expr" or
+ * "Expr op Const".
+ * ef_source_rel is the relation id of where this qual originated from.
+ */
+typedef struct EquivalenceFilter
+{
+	NodeTag		type;
+
+	Const	   *ef_const;		/* the constant expression to filter on */
+	Oid			ef_opno;		/* Operator Oid of filter operator */
+	bool		ef_const_is_left; /* Is the Const on the left of the OpExrp? */
+	Index		ef_source_rel;	/* relid of originating relation. */
+} EquivalenceFilter;
+
 /*
  * If an EC contains a const and isn't below-outer-join, any PathKey depending
  * on it must be redundant, since there's only one possible value of the key.
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 0c3a0b90c85..ce2aac7d3aa 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -126,6 +126,7 @@ extern bool process_equivalence(PlannerInfo *root,
 extern Expr *canonicalize_ec_expression(Expr *expr,
 										Oid req_type, Oid req_collation);
 extern void reconsider_outer_join_clauses(PlannerInfo *root);
+extern void distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist);
 extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root,
 												  Expr *expr,
 												  Relids nullable_relids,
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index b8dd27d4a96..188d65faa0d 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -78,6 +78,7 @@ extern bool get_ordering_op_properties(Oid opno,
 									   Oid *opfamily, Oid *opcintype, int16 *strategy);
 extern Oid	get_equality_op_for_ordering_op(Oid opno, bool *reverse);
 extern Oid	get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type);
+extern List *get_opfamilies(Oid opno, Oid method);
 extern List *get_mergejoin_opfamilies(Oid opno);
 extern bool get_compatible_hash_operators(Oid opno,
 										  Oid *lhs_opno, Oid *rhs_opno);
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index 126f7047fed..ce4c9b11748 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -451,3 +451,48 @@ explain (costs off)  -- this should not require a sort
    Filter: (f1 = 'foo'::name)
 (2 rows)
 
+-- test equivalence filters
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec0.ff between 1 and 10;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Merge Join
+   Merge Cond: (ec0.ff = ec1.ff)
+   ->  Sort
+         Sort Key: ec0.ff
+         ->  Bitmap Heap Scan on ec0
+               Recheck Cond: ((ff >= 1) AND (ff <= 10))
+               ->  Bitmap Index Scan on ec0_pkey
+                     Index Cond: ((ff >= 1) AND (ff <= 10))
+   ->  Sort
+         Sort Key: ec1.ff
+         ->  Bitmap Heap Scan on ec1
+               Recheck Cond: ((ff >= 1) AND (ff <= 10))
+               ->  Bitmap Index Scan on ec1_pkey
+                     Index Cond: ((ff >= 1) AND (ff <= 10))
+(14 rows)
+
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec1.ff between 1 and 10;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Merge Join
+   Merge Cond: (ec0.ff = ec1.ff)
+   ->  Sort
+         Sort Key: ec0.ff
+         ->  Bitmap Heap Scan on ec0
+               Recheck Cond: ((ff >= 1) AND (ff <= 10))
+               ->  Bitmap Index Scan on ec0_pkey
+                     Index Cond: ((ff >= 1) AND (ff <= 10))
+   ->  Sort
+         Sort Key: ec1.ff
+         ->  Bitmap Heap Scan on ec1
+               Recheck Cond: ((ff >= 1) AND (ff <= 10))
+               ->  Bitmap Index Scan on ec1_pkey
+                     Index Cond: ((ff >= 1) AND (ff <= 10))
+(14 rows)
+
-- 
2.21.0

v1-0005-Support-ScalarArrayOpExpr-and-perudoconstant-on-e.patchapplication/x-patch; name=v1-0005-Support-ScalarArrayOpExpr-and-perudoconstant-on-e.patchDownload
From f87f52db53b7caf56d0bb04138cb0357d2db2223 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=80=E6=8C=83?= <yizhi.fzh@alibaba-inc.com>
Date: Tue, 1 Feb 2022 20:58:07 +0800
Subject: [PATCH v1 5/6] Support ScalarArrayOpExpr and perudoconstant on
 ef_filter.

---
 src/backend/optimizer/path/equivclass.c | 110 +++++++++++++++++-------
 src/backend/optimizer/plan/initsplan.c  |  59 ++++++++-----
 src/include/nodes/pathnodes.h           |   3 +-
 3 files changed, 120 insertions(+), 52 deletions(-)

diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 9ce0249b10d..4271bacb070 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -1386,23 +1386,55 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 				rightexpr = (Expr *) ef->ef_const;
 			}
 
-			opno = get_opfamily_member(family,
-										exprType((Node *) leftexpr),
-										exprType((Node *) rightexpr),
-										strategy);
+			if (ef->ef_expr_type == T_OpExpr)
+			{
+				opno = get_opfamily_member(family,
+										   exprType((Node *) leftexpr),
+										   exprType((Node *) rightexpr),
+										   strategy);
 
-			if (opno == InvalidOid)
-				continue;
+				if (opno == InvalidOid)
+					continue;
 
-			rinfo = process_implied_equality(root, opno,
-											 ec->ec_collation,
-											 leftexpr,
-											 rightexpr,
-											 bms_copy(ec->ec_relids),
-											 bms_copy(cur_em->em_nullable_relids),
-											 ec->ec_min_security,
-											 ec->ec_below_outer_join,
-											 false);
+				rinfo = process_implied_equality(root, opno,
+												 ec->ec_collation,
+												 leftexpr,
+												 rightexpr,
+												 bms_copy(ec->ec_relids),
+												 bms_copy(cur_em->em_nullable_relids),
+												 ec->ec_min_security,
+												 ec->ec_below_outer_join,
+												 false);
+			}
+			else
+			{
+				ScalarArrayOpExpr *arr_opexpr;
+				Relids relids;
+
+				Assert(ef->ef_expr_type == T_ScalarArrayOpExpr);
+				arr_opexpr = makeNode(ScalarArrayOpExpr);
+				arr_opexpr->opno = ef->ef_opno;
+				arr_opexpr->opfuncid = get_opcode(ef->ef_opno);
+				arr_opexpr->useOr = true; /* See is_simple_filter_qual */
+				arr_opexpr->args = list_make2(leftexpr, ef->ef_const);
+				arr_opexpr->inputcollid = ec->ec_collation;
+
+				relids = pull_varnos(root, (Node *)arr_opexpr);
+
+				rinfo = make_restrictinfo(root,
+										  (Expr *) arr_opexpr,
+										  true,
+										  false,
+										  false, /* perudoconstant */
+										  ec->ec_min_security,
+										  relids,
+										  NULL,
+										  bms_copy(cur_em->em_nullable_relids));
+
+				// check_mergejoinable(rinfo);
+
+				distribute_restrictinfo_to_rels(root, rinfo);
+			}
 			rinfo->derived = ec;
 		}
 
@@ -2009,29 +2041,48 @@ distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
 	 */
 	foreach(l, quallist)
 	{
-		OpExpr	   *opexpr = (OpExpr *) lfirst(l);
-		Expr	   *leftexpr = (Expr *) linitial(opexpr->args);
-		Expr	   *rightexpr = (Expr *) lsecond(opexpr->args);
-		Const	   *constexpr;
-		Expr	   *varexpr;
+		Expr	*expr = lfirst(l);
+
+		List	*args;
+		Node	   *leftexpr;
+		Node	   *rightexpr;
+		Node	   *constexpr;
+		Node	   *varexpr;
+		Oid			opno;
+
 		Relids		exprrels;
 		int			relid;
 		bool		const_isleft;
 		ListCell *l2;
 
-		/*
-		 * Determine if the the OpExpr is in the form "expr op const" or
-		 * "const op expr".
-		 */
-		if (IsA(leftexpr, Const))
+		if (nodeTag(expr) == T_OpExpr)
+		{
+			OpExpr	   *opexpr = (OpExpr *) lfirst(l);
+			args = opexpr->args;
+			opno = opexpr->opno;
+		}
+		else if (nodeTag(expr) == T_ScalarArrayOpExpr)
+		{
+			ScalarArrayOpExpr *arr_expr = (ScalarArrayOpExpr *) lfirst(l);
+			args = arr_expr->args;
+			opno = arr_expr->opno;
+		}
+		else
+		{
+			Assert(false);
+		}
+
+		leftexpr = linitial(args);
+		rightexpr = lsecond(args);
+		if (is_pseudo_constant_clause(leftexpr))
 		{
-			constexpr = (Const *) leftexpr;
+			constexpr = leftexpr;
 			varexpr = rightexpr;
 			const_isleft = true;
 		}
 		else
 		{
-			constexpr = (Const *) rightexpr;
+			constexpr = rightexpr;
 			varexpr = leftexpr;
 			const_isleft = false;
 		}
@@ -2073,10 +2124,11 @@ distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
 				{
 					EquivalenceFilter *efilter;
 					efilter = makeNode(EquivalenceFilter);
-					efilter->ef_const = (Const *) copyObject(constexpr);
+					efilter->ef_const = (Node *)copyObject(constexpr);
 					efilter->ef_const_is_left = const_isleft;
-					efilter->ef_opno = opexpr->opno;
+					efilter->ef_opno = opno;
 					efilter->ef_source_rel = relid;
+					efilter->ef_expr_type = nodeTag(expr);
 
 					ec->ec_filters = lappend(ec->ec_filters, efilter);
 					break;		/* Onto the next eclass */
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index b219bee8567..a0f12198b8c 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -656,33 +656,42 @@ create_lateral_join_info(PlannerInfo *root)
  *             Analyzes an OpExpr to determine if it may be useful as an
  *             EquivalenceFilter. Returns true if the OpExpr may be of some use, or
  *             false if it should not be used.
+ *
  */
 static bool
-is_simple_filter_qual(PlannerInfo *root, OpExpr *expr)
+is_simple_filter_qual(PlannerInfo *root, Expr *expr)
 {
-       Expr *leftexpr;
-       Expr *rightexpr;
-
-       if (!IsA(expr, OpExpr))
-               return false;
-
-       if (list_length(expr->args) != 2)
-               return false;
-
-       leftexpr = (Expr *) linitial(expr->args);
-       rightexpr = (Expr *) lsecond(expr->args);
-
-       /* XXX should we restrict these to simple Var op Const expressions? */
-       if (IsA(leftexpr, Const))
+       Node *leftexpr;
+       Node *rightexpr;
+	   List	*args = NIL;
+
+       if (IsA(expr, OpExpr))
+		   args = castNode(OpExpr, expr)->args;
+	   else if (IsA(expr, ScalarArrayOpExpr))
+	   {
+		   ScalarArrayOpExpr *arr_opexpr = castNode(ScalarArrayOpExpr, expr);
+		   if (!arr_opexpr->useOr)
+			   /* Just support IN for now */
+			   return false;
+		   args = arr_opexpr->args;
+	   }
+
+       if (list_length(args) != 2)
+		   return false;
+
+       leftexpr = linitial(args);
+       rightexpr = lsecond(args);
+
+       if (is_pseudo_constant_clause(leftexpr))
        {
-		   if (bms_membership(pull_varnos(root, (Node *) rightexpr)) == BMS_SINGLETON &&
-			   !contain_volatile_functions((Node *) rightexpr))
+		   if (bms_membership(pull_varnos(root, rightexpr)) == BMS_SINGLETON &&
+			   !contain_volatile_functions(rightexpr))
                        return true;
        }
-       else if (IsA(rightexpr, Const))
+       else if (is_pseudo_constant_clause(rightexpr))
        {
-		   if (bms_membership(pull_varnos(root, (Node *) leftexpr)) == BMS_SINGLETON &&
-			   !contain_volatile_functions((Node *) leftexpr))
+		   if (bms_membership(pull_varnos(root, leftexpr)) == BMS_SINGLETON &&
+			   !contain_volatile_functions(leftexpr))
                        return true;
        }
 
@@ -739,7 +748,13 @@ deconstruct_jointree(PlannerInfo *root)
 
 	result = deconstruct_recurse(root, (Node *) root->parse->jointree, false,
 								 &qualscope, &inner_join_rels,
-								 &postponed_qual_list, &filter_qual_list);
+								 &postponed_qual_list,
+								 /*
+								  * geqo option here is just used for testing
+								  * during review stage, set enable_geqo to
+								  * false to disable this feature.
+								  */
+								 enable_geqo ? &filter_qual_list : NULL);
 
 	/* Shouldn't be any leftover quals */
 	Assert(postponed_qual_list == NIL);
@@ -2024,7 +2039,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	distribute_restrictinfo_to_rels(root, restrictinfo);
 
 	/* Check if the qual looks useful to harvest as an EquivalenceFilter */
-	if (filter_qual_list != NULL && is_simple_filter_qual(root, (OpExpr *) clause))
+	if (filter_qual_list != NULL && is_simple_filter_qual(root, (Expr *) clause))
 		*filter_qual_list = lappend(*filter_qual_list, clause);
 }
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index dbe1775f96d..c1ef0066188 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1035,10 +1035,11 @@ typedef struct EquivalenceFilter
 {
 	NodeTag		type;
 
-	Const	   *ef_const;		/* the constant expression to filter on */
+	Node		*ef_const;		/* pseudo const */
 	Oid			ef_opno;		/* Operator Oid of filter operator */
 	bool		ef_const_is_left; /* Is the Const on the left of the OpExrp? */
 	Index		ef_source_rel;	/* relid of originating relation. */
+	NodeTag		ef_expr_type;
 } EquivalenceFilter;
 
 /*
-- 
2.21.0

v1-0004-remove-duplicated-qual-executing.patchapplication/x-patch; name=v1-0004-remove-duplicated-qual-executing.patchDownload
From 9d8e8e11253e177f24436fbdbc8aff0715b5dd39 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=80=E6=8C=83?= <yizhi.fzh@alibaba-inc.com>
Date: Tue, 1 Feb 2022 17:37:27 +0800
Subject: [PATCH v1 4/6] remove duplicated qual executing.

---
 src/backend/optimizer/path/equivclass.c | 22 +++++++++++++++++++
 src/backend/optimizer/plan/createplan.c | 29 +++++++++++++++++++++++--
 src/include/optimizer/paths.h           |  2 ++
 src/test/regress/parallel_schedule      |  2 ++
 4 files changed, 53 insertions(+), 2 deletions(-)

diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 6ed9e8c9064..9ce0249b10d 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -3350,6 +3350,28 @@ is_redundant_derived_clause(RestrictInfo *rinfo, List *clauselist)
 	return false;
 }
 
+
+bool
+is_correlated_derived_clause(RestrictInfo *rinfo, List *clauselist)
+{
+	EquivalenceClass *derived_ec = rinfo->derived;
+	ListCell   *lc;
+
+	/* Fail if it's not a potentially-derived clause from some EC */
+	if (derived_ec == NULL)
+		return false;
+
+	foreach(lc, clauselist)
+	{
+		RestrictInfo *otherrinfo = (RestrictInfo *) lfirst(lc);
+
+		if (otherrinfo->parent_ec == derived_ec)
+			return true;
+	}
+
+	return false;
+}
+
 /*
  * is_redundant_with_indexclauses
  *		Test whether rinfo is redundant with any clause in the IndexClause
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index cd6d72c7633..03c5207ede0 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -556,11 +556,14 @@ static Plan *
 create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
 {
 	RelOptInfo *rel = best_path->parent;
-	List	   *scan_clauses;
+	List	   *scan_clauses = NIL;
 	List	   *gating_clauses;
 	List	   *tlist;
 	Plan	   *plan;
 
+	List	*ppi_clauses = best_path->param_info ? best_path->param_info->ppi_clauses : NIL;
+	ListCell	*lc;
+
 	/*
 	 * Extract the relevant restriction clauses from the parent relation. The
 	 * executor must apply all these restrictions during the scan, except for
@@ -591,8 +594,18 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
 	 * For paranoia's sake, don't modify the stored baserestrictinfo list.
 	 */
 	if (best_path->param_info)
-		scan_clauses = list_concat_copy(scan_clauses,
+	{
+		List *stripped_quals = NIL;
+		foreach(lc, rel->baserestrictinfo)
+		{
+			RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
+
+			if (!is_correlated_derived_clause(rinfo, ppi_clauses))
+				stripped_quals = lappend(stripped_quals, rinfo);
+		}
+		scan_clauses = list_concat_copy(stripped_quals,
 										best_path->param_info->ppi_clauses);
+	}
 
 	/*
 	 * Detect whether we have any pseudoconstant quals to deal with.  Then, if
@@ -4912,6 +4925,7 @@ fix_indexqual_references(PlannerInfo *root, IndexPath *index_path,
 	List	   *stripped_indexquals;
 	List	   *fixed_indexquals;
 	ListCell   *lc;
+	List	*ppi_clauses = index_path->path.param_info ? index_path->path.param_info->ppi_clauses : NIL;
 
 	stripped_indexquals = fixed_indexquals = NIL;
 
@@ -4921,6 +4935,17 @@ fix_indexqual_references(PlannerInfo *root, IndexPath *index_path,
 		int			indexcol = iclause->indexcol;
 		ListCell   *lc2;
 
+		if (is_correlated_derived_clause(iclause->rinfo, ppi_clauses))
+		{
+			/*
+			 * bitmapscan will read this indexquals as well. so we can't just igrore
+			 * it for now. we can totally delete it.
+			 */
+			index_path->indexclauses = foreach_delete_current(index_path->indexclauses, lc);
+			continue;
+		}
+
+
 		foreach(lc2, iclause->indexquals)
 		{
 			RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc2);
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index ce2aac7d3aa..08d8612cdba 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -188,6 +188,8 @@ extern bool eclass_useful_for_merging(PlannerInfo *root,
 extern bool is_redundant_derived_clause(RestrictInfo *rinfo, List *clauselist);
 extern bool is_redundant_with_indexclauses(RestrictInfo *rinfo,
 										   List *indexclauses);
+extern bool is_correlated_derived_clause(RestrictInfo *rinfo, List *clauselist);
+
 
 /*
  * pathkeys.c
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 861c30a73ae..b071324b961 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -134,3 +134,5 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+test: ec_filter
\ No newline at end of file
-- 
2.21.0

v1-0002-support-set_xxx_size-with-derived_clauses-ignored.patchapplication/x-patch; name=v1-0002-support-set_xxx_size-with-derived_clauses-ignored.patchDownload
From d34e73aad40e4b54ce61dce65da810abfb608b23 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=80=E6=8C=83?= <yizhi.fzh@alibaba-inc.com>
Date: Tue, 1 Feb 2022 14:54:07 +0800
Subject: [PATCH v1 2/6] support set_xxx_size with derived_clauses ignored.

---
 src/backend/optimizer/path/clausesel.c  | 34 +++++++++++++++++--------
 src/backend/optimizer/path/costsize.c   | 25 ++++++++++--------
 src/backend/optimizer/path/equivclass.c | 20 ++++++++-------
 src/backend/optimizer/util/inherit.c    | 31 ++++++++++++----------
 src/backend/statistics/dependencies.c   |  3 ++-
 src/backend/statistics/extended_stats.c |  5 ++--
 src/include/nodes/pathnodes.h           |  1 +
 src/include/optimizer/optimizer.h       |  6 +++--
 8 files changed, 77 insertions(+), 48 deletions(-)

diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index 06f836308d0..8961e66ea4e 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -106,7 +106,7 @@ clauselist_selectivity(PlannerInfo *root,
 					   SpecialJoinInfo *sjinfo)
 {
 	return clauselist_selectivity_ext(root, clauses, varRelid,
-									  jointype, sjinfo, true);
+									  jointype, sjinfo, true, true);
 }
 
 /*
@@ -121,7 +121,8 @@ clauselist_selectivity_ext(PlannerInfo *root,
 						   int varRelid,
 						   JoinType jointype,
 						   SpecialJoinInfo *sjinfo,
-						   bool use_extended_stats)
+						   bool use_extended_stats,
+						   bool include_derived)
 {
 	Selectivity s1 = 1.0;
 	RelOptInfo *rel;
@@ -137,7 +138,8 @@ clauselist_selectivity_ext(PlannerInfo *root,
 	if (list_length(clauses) == 1)
 		return clause_selectivity_ext(root, (Node *) linitial(clauses),
 									  varRelid, jointype, sjinfo,
-									  use_extended_stats);
+									  use_extended_stats,
+									  include_derived);
 
 	/*
 	 * Determine if these clauses reference a single relation.  If so, and if
@@ -183,7 +185,7 @@ clauselist_selectivity_ext(PlannerInfo *root,
 
 		/* Compute the selectivity of this clause in isolation */
 		s2 = clause_selectivity_ext(root, clause, varRelid, jointype, sjinfo,
-									use_extended_stats);
+									use_extended_stats, include_derived);
 
 		/*
 		 * Check for being passed a RestrictInfo.
@@ -412,7 +414,9 @@ clauselist_selectivity_or(PlannerInfo *root,
 			continue;
 
 		s2 = clause_selectivity_ext(root, (Node *) lfirst(lc), varRelid,
-									jointype, sjinfo, use_extended_stats);
+									jointype, sjinfo, use_extended_stats,
+									true /* we never push a derived under or clause */
+			);
 
 		s1 = s1 + s2 - s1 * s2;
 	}
@@ -694,7 +698,7 @@ clause_selectivity(PlannerInfo *root,
 				   SpecialJoinInfo *sjinfo)
 {
 	return clause_selectivity_ext(root, clause, varRelid,
-								  jointype, sjinfo, true);
+								  jointype, sjinfo, true, true);
 }
 
 /*
@@ -709,7 +713,8 @@ clause_selectivity_ext(PlannerInfo *root,
 					   int varRelid,
 					   JoinType jointype,
 					   SpecialJoinInfo *sjinfo,
-					   bool use_extended_stats)
+					   bool use_extended_stats,
+					   bool include_derived)
 {
 	Selectivity s1 = 0.5;		/* default for any unhandled clause type */
 	RestrictInfo *rinfo = NULL;
@@ -742,6 +747,9 @@ clause_selectivity_ext(PlannerInfo *root,
 		if (rinfo->norm_selec > 1)
 			return (Selectivity) 1.0;
 
+		if (rinfo->derived && !include_derived)
+			return (Selectivity) 1.0;
+
 		/*
 		 * If possible, cache the result of the selectivity calculation for
 		 * the clause.  We can cache if varRelid is zero or the clause
@@ -830,7 +838,8 @@ clause_selectivity_ext(PlannerInfo *root,
 										  varRelid,
 										  jointype,
 										  sjinfo,
-										  use_extended_stats);
+										  use_extended_stats,
+										  include_derived);
 	}
 	else if (is_andclause(clause))
 	{
@@ -840,7 +849,8 @@ clause_selectivity_ext(PlannerInfo *root,
 										varRelid,
 										jointype,
 										sjinfo,
-										use_extended_stats);
+										use_extended_stats,
+										include_derived);
 	}
 	else if (is_orclause(clause))
 	{
@@ -959,7 +969,8 @@ clause_selectivity_ext(PlannerInfo *root,
 									varRelid,
 									jointype,
 									sjinfo,
-									use_extended_stats);
+									use_extended_stats,
+									include_derived);
 	}
 	else if (IsA(clause, CoerceToDomain))
 	{
@@ -969,7 +980,8 @@ clause_selectivity_ext(PlannerInfo *root,
 									varRelid,
 									jointype,
 									sjinfo,
-									use_extended_stats);
+									use_extended_stats,
+									include_derived);
 	}
 	else
 	{
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 8dc7dd4ca26..9e303877af7 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -4928,11 +4928,13 @@ set_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel)
 	Assert(rel->relid > 0);
 
 	nrows = rel->tuples *
-		clauselist_selectivity(root,
-							   rel->baserestrictinfo,
-							   0,
-							   JOIN_INNER,
-							   NULL);
+		clauselist_selectivity_ext(root,
+								   rel->baserestrictinfo,
+								   0,
+								   JOIN_INNER,
+								   NULL,
+								   true,
+								   false /* include_derived */);
 
 	rel->rows = clamp_row_est(nrows);
 
@@ -4964,11 +4966,14 @@ get_parameterized_baserel_size(PlannerInfo *root, RelOptInfo *rel,
 	 */
 	allclauses = list_concat_copy(param_clauses, rel->baserestrictinfo);
 	nrows = rel->tuples *
-		clauselist_selectivity(root,
-							   allclauses,
-							   rel->relid,	/* do not use 0! */
-							   JOIN_INNER,
-							   NULL);
+		clauselist_selectivity_ext(root,
+								   allclauses,
+								   rel->relid,	/* do not use 0! */
+								   JOIN_INNER,
+								   NULL,
+								   true,
+								   false /* doesn't include the derived clause */
+			);
 	nrows = clamp_row_est(nrows);
 	/* For safety, make sure result is not more than the base estimate */
 	if (nrows > rel->rows)
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index f9ae2785d60..6ed9e8c9064 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -1366,6 +1366,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 			int strategy;
 			Oid opno;
 			Oid family;
+			RestrictInfo *rinfo;
 
 			if (ef->ef_source_rel == relid)
 				continue;
@@ -1393,15 +1394,16 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 			if (opno == InvalidOid)
 				continue;
 
-			process_implied_equality(root, opno,
-										ec->ec_collation,
-										leftexpr,
-										rightexpr,
-										bms_copy(ec->ec_relids),
-										bms_copy(cur_em->em_nullable_relids),
-									 	ec->ec_min_security,
-										ec->ec_below_outer_join,
-										false);
+			rinfo = process_implied_equality(root, opno,
+											 ec->ec_collation,
+											 leftexpr,
+											 rightexpr,
+											 bms_copy(ec->ec_relids),
+											 bms_copy(cur_em->em_nullable_relids),
+											 ec->ec_min_security,
+											 ec->ec_below_outer_join,
+											 false);
+			rinfo->derived = ec;
 		}
 
 		prev_ems[relid] = cur_em;
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 7e134822f36..8a5863c4da4 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -797,6 +797,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 		{
 			Node	   *onecq = (Node *) lfirst(lc2);
 			bool		pseudoconstant;
+			RestrictInfo		*child_rinfo;
 
 			/* check for pseudoconstant (no Vars or volatile functions) */
 			pseudoconstant =
@@ -807,15 +808,19 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 				/* tell createplan.c to check for gating quals */
 				root->hasPseudoConstantQuals = true;
 			}
+
+			child_rinfo =  make_restrictinfo(root,
+											 (Expr *) onecq,
+											 rinfo->is_pushed_down,
+											 rinfo->outerjoin_delayed,
+											 pseudoconstant,
+											 rinfo->security_level,
+											 NULL, NULL, NULL);
+
+			child_rinfo->derived = rinfo->derived;
 			/* reconstitute RestrictInfo with appropriate properties */
-			childquals = lappend(childquals,
-								 make_restrictinfo(root,
-												   (Expr *) onecq,
-												   rinfo->is_pushed_down,
-												   rinfo->outerjoin_delayed,
-												   pseudoconstant,
-												   rinfo->security_level,
-												   NULL, NULL, NULL));
+			childquals = lappend(childquals, child_rinfo);
+
 			/* track minimum security level among child quals */
 			cq_min_security = Min(cq_min_security, rinfo->security_level);
 		}
@@ -844,13 +849,13 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 			foreach(lc2, qualset)
 			{
 				Expr	   *qual = (Expr *) lfirst(lc2);
+				RestrictInfo *rinfo = make_restrictinfo(root, qual,
+														true, false, false,
+														security_level,
+														NULL, NULL, NULL);
 
 				/* not likely that we'd see constants here, so no check */
-				childquals = lappend(childquals,
-									 make_restrictinfo(root, qual,
-													   true, false, false,
-													   security_level,
-													   NULL, NULL, NULL));
+				childquals = lappend(childquals, rinfo);
 				cq_min_security = Min(cq_min_security, security_level);
 			}
 			security_level++;
diff --git a/src/backend/statistics/dependencies.c b/src/backend/statistics/dependencies.c
index 34326d55619..838446a220d 100644
--- a/src/backend/statistics/dependencies.c
+++ b/src/backend/statistics/dependencies.c
@@ -1076,7 +1076,8 @@ clauselist_apply_dependencies(PlannerInfo *root, List *clauses,
 		}
 
 		simple_sel = clauselist_selectivity_ext(root, attr_clauses, varRelid,
-												jointype, sjinfo, false);
+												jointype, sjinfo, false,
+												true /* probably no reasonable */);
 		attr_sel[attidx++] = simple_sel;
 	}
 
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index ca48395d5c5..38836f58c4e 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -1870,7 +1870,8 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli
 				 * columns/clauses.
 				 */
 				simple_sel = clause_selectivity_ext(root, clause, varRelid,
-													jointype, sjinfo, false);
+													jointype, sjinfo, false,
+													true);
 
 				overlap_simple_sel = simple_or_sel * simple_sel;
 
@@ -1943,7 +1944,7 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli
 			 */
 			simple_sel = clauselist_selectivity_ext(root, stat_clauses,
 													varRelid, jointype,
-													sjinfo, false);
+													sjinfo, false, true);
 
 			/*
 			 * Multi-column estimate using MCV statistics, along with base and
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index e73fef057a4..42368e10b8e 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -2165,6 +2165,7 @@ typedef struct RestrictInfo
 	/* hash equality operators used for memoize nodes, else InvalidOid */
 	Oid			left_hasheqoperator;
 	Oid			right_hasheqoperator;
+	EquivalenceClass *derived;
 } RestrictInfo;
 
 /*
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 6b8ee0c69fa..a3385ae51ff 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -68,7 +68,8 @@ extern Selectivity clause_selectivity_ext(PlannerInfo *root,
 										  int varRelid,
 										  JoinType jointype,
 										  SpecialJoinInfo *sjinfo,
-										  bool use_extended_stats);
+										  bool use_extended_stats,
+										  bool include_derived);
 extern Selectivity clauselist_selectivity(PlannerInfo *root,
 										  List *clauses,
 										  int varRelid,
@@ -79,7 +80,8 @@ extern Selectivity clauselist_selectivity_ext(PlannerInfo *root,
 											  int varRelid,
 											  JoinType jointype,
 											  SpecialJoinInfo *sjinfo,
-											  bool use_extended_stats);
+											  bool use_extended_stats,
+											  bool include_derived);
 
 /* in path/costsize.c: */
 
-- 
2.21.0

v1-0006-adding-some-test-cases-for-this-feature-and-fix-t.patchapplication/x-patch; name=v1-0006-adding-some-test-cases-for-this-feature-and-fix-t.patchDownload
From ce6623ac58687c67e3e7576c212b4e352bc786b9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=80=E6=8C=83?= <yizhi.fzh@alibaba-inc.com>
Date: Tue, 1 Feb 2022 22:57:13 +0800
Subject: [PATCH v1 6/6] adding some test cases for this feature and fix the
 existing case

because of this feature.
---
 src/test/regress/expected/equivclass.out      |  54 ++++-----
 src/test/regress/expected/join.out            |  33 +++---
 src/test/regress/expected/partition_join.out  | 106 ++++++++++--------
 src/test/regress/expected/partition_prune.out |  56 ++-------
 src/test/regress/sql/equivclass.sql           |  12 ++
 5 files changed, 118 insertions(+), 143 deletions(-)

diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index ce4c9b11748..a5042c3cbc3 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -456,43 +456,29 @@ explain (costs off)
   select * from ec0
   inner join ec1 on ec0.ff = ec1.ff
   where ec0.ff between 1 and 10;
-                         QUERY PLAN                         
-------------------------------------------------------------
- Merge Join
-   Merge Cond: (ec0.ff = ec1.ff)
-   ->  Sort
-         Sort Key: ec0.ff
-         ->  Bitmap Heap Scan on ec0
-               Recheck Cond: ((ff >= 1) AND (ff <= 10))
-               ->  Bitmap Index Scan on ec0_pkey
-                     Index Cond: ((ff >= 1) AND (ff <= 10))
-   ->  Sort
-         Sort Key: ec1.ff
-         ->  Bitmap Heap Scan on ec1
-               Recheck Cond: ((ff >= 1) AND (ff <= 10))
-               ->  Bitmap Index Scan on ec1_pkey
-                     Index Cond: ((ff >= 1) AND (ff <= 10))
-(14 rows)
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Nested Loop
+   ->  Bitmap Heap Scan on ec1
+         Recheck Cond: ((ff >= 1) AND (ff <= 10))
+         ->  Bitmap Index Scan on ec1_pkey
+               Index Cond: ((ff >= 1) AND (ff <= 10))
+   ->  Index Scan using ec0_pkey on ec0
+         Index Cond: ((ff = ec1.ff) AND (ff >= 1) AND (ff <= 10))
+(7 rows)
 
 explain (costs off)
   select * from ec0
   inner join ec1 on ec0.ff = ec1.ff
   where ec1.ff between 1 and 10;
-                         QUERY PLAN                         
-------------------------------------------------------------
- Merge Join
-   Merge Cond: (ec0.ff = ec1.ff)
-   ->  Sort
-         Sort Key: ec0.ff
-         ->  Bitmap Heap Scan on ec0
-               Recheck Cond: ((ff >= 1) AND (ff <= 10))
-               ->  Bitmap Index Scan on ec0_pkey
-                     Index Cond: ((ff >= 1) AND (ff <= 10))
-   ->  Sort
-         Sort Key: ec1.ff
-         ->  Bitmap Heap Scan on ec1
-               Recheck Cond: ((ff >= 1) AND (ff <= 10))
-               ->  Bitmap Index Scan on ec1_pkey
-                     Index Cond: ((ff >= 1) AND (ff <= 10))
-(14 rows)
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Nested Loop
+   ->  Bitmap Heap Scan on ec0
+         Recheck Cond: ((ff >= 1) AND (ff <= 10))
+         ->  Bitmap Index Scan on ec0_pkey
+               Index Cond: ((ff >= 1) AND (ff <= 10))
+   ->  Index Scan using ec1_pkey on ec1
+         Index Cond: ((ff = ec0.ff) AND (ff >= 1) AND (ff <= 10))
+(7 rows)
 
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index d5b5b775fdd..7df4f93856e 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -3337,7 +3337,7 @@ where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
    Join Filter: (t1.stringu1 > t2.stringu2)
    ->  Nested Loop
          ->  Seq Scan on int4_tbl i1
-               Filter: (f1 = 0)
+               Filter: ((f1 = 0) AND (11 < 42))
          ->  Index Scan using tenk1_unique2 on tenk1 t1
                Index Cond: ((unique2 = (11)) AND (unique2 < 42))
    ->  Index Scan using tenk1_unique1 on tenk1 t2
@@ -6496,9 +6496,10 @@ where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 = any (array[1]);
    Merge Cond: (j1.id1 = j2.id1)
    Join Filter: (j1.id2 = j2.id2)
    ->  Index Scan using j1_id1_idx on j1
+         Index Cond: (id1 = ANY ('{1}'::integer[]))
    ->  Index Scan using j2_id1_idx on j2
          Index Cond: (id1 = ANY ('{1}'::integer[]))
-(6 rows)
+(7 rows)
 
 select * from j1
 inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
@@ -6513,16 +6514,17 @@ where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 = any (array[1]);
 explain (costs off) select * from j1
 inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
 where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 >= any (array[1,5]);
-                      QUERY PLAN                       
--------------------------------------------------------
+                       QUERY PLAN                        
+---------------------------------------------------------
  Merge Join
-   Merge Cond: (j1.id1 = j2.id1)
-   Join Filter: (j1.id2 = j2.id2)
-   ->  Index Scan using j1_id1_idx on j1
+   Merge Cond: ((j1.id1 = j2.id1) AND (j1.id2 = j2.id2))
+   ->  Index Only Scan using j1_pkey on j1
+         Index Cond: (id1 >= ANY ('{1,5}'::integer[]))
+         Filter: ((id1 % 1000) = 1)
    ->  Index Only Scan using j2_pkey on j2
          Index Cond: (id1 >= ANY ('{1,5}'::integer[]))
          Filter: ((id1 % 1000) = 1)
-(7 rows)
+(8 rows)
 
 select * from j1
 inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
@@ -6558,6 +6560,7 @@ where exists (select 1 from tenk1 t3
                Group Key: t3.thousand, t3.tenthous
                ->  Index Only Scan using tenk1_thous_tenthous on public.tenk1 t3
                      Output: t3.thousand, t3.tenthous
+                     Index Cond: (t3.thousand < 1)
          ->  Hash
                Output: t1.unique1
                ->  Index Only Scan using onek_unique1 on public.onek t1
@@ -6566,7 +6569,7 @@ where exists (select 1 from tenk1 t3
    ->  Index Only Scan using tenk1_hundred on public.tenk1 t2
          Output: t2.hundred
          Index Cond: (t2.hundred = t3.tenthous)
-(18 rows)
+(19 rows)
 
 -- ... unless it actually is unique
 create table j3 as select unique1, tenthous from onek;
@@ -6578,18 +6581,18 @@ from onek t1, tenk1 t2
 where exists (select 1 from j3
               where j3.unique1 = t1.unique1 and j3.tenthous = t2.hundred)
       and t1.unique1 < 1;
-                               QUERY PLAN                               
-------------------------------------------------------------------------
+                                 QUERY PLAN                                 
+----------------------------------------------------------------------------
  Nested Loop
    Output: t1.unique1, t2.hundred
    ->  Nested Loop
          Output: t1.unique1, j3.tenthous
-         ->  Index Only Scan using onek_unique1 on public.onek t1
-               Output: t1.unique1
-               Index Cond: (t1.unique1 < 1)
          ->  Index Only Scan using j3_unique1_tenthous_idx on public.j3
                Output: j3.unique1, j3.tenthous
-               Index Cond: (j3.unique1 = t1.unique1)
+               Index Cond: (j3.unique1 < 1)
+         ->  Index Only Scan using onek_unique1 on public.onek t1
+               Output: t1.unique1
+               Index Cond: ((t1.unique1 = j3.unique1) AND (t1.unique1 < 1))
    ->  Index Only Scan using tenk1_hundred on public.tenk1 t2
          Output: t2.hundred
          Index Cond: (t2.hundred = j3.tenthous)
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index bb5b7c47a45..c12351a3b0a 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -186,17 +186,17 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0)
 -- Join with pruned partitions from joining relations
 EXPLAIN (COSTS OFF)
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
-                     QUERY PLAN                      
------------------------------------------------------
+                            QUERY PLAN                             
+-------------------------------------------------------------------
  Sort
    Sort Key: t1.a
    ->  Hash Join
          Hash Cond: (t2.b = t1.a)
          ->  Seq Scan on prt2_p2 t2
-               Filter: (b > 250)
+               Filter: ((b > 250) AND (b < 450))
          ->  Hash
                ->  Seq Scan on prt1_p2 t1
-                     Filter: ((a < 450) AND (b = 0))
+                     Filter: ((a < 450) AND (a > 250) AND (b = 0))
 (9 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
@@ -3100,16 +3100,18 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
          ->  Hash Join
                Hash Cond: (t2_1.b = t1_1.a)
                ->  Seq Scan on prt2_adv_p1 t2_1
+                     Filter: (b < 300)
                ->  Hash
                      ->  Seq Scan on prt1_adv_p1 t1_1
                            Filter: ((a < 300) AND (b = 0))
          ->  Hash Join
                Hash Cond: (t2_2.b = t1_2.a)
                ->  Seq Scan on prt2_adv_p2 t2_2
+                     Filter: (b < 300)
                ->  Hash
                      ->  Seq Scan on prt1_adv_p2 t1_2
                            Filter: ((a < 300) AND (b = 0))
-(15 rows)
+(17 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b;
   a  |  c   |  b  |  c   
@@ -3139,16 +3141,19 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
          ->  Hash Join
                Hash Cond: (t2_1.b = t1_1.a)
                ->  Seq Scan on prt2_adv_p1 t2_1
+                     Filter: ((b >= 100) AND (b < 300))
                ->  Hash
                      ->  Seq Scan on prt1_adv_p1 t1_1
                            Filter: ((a >= 100) AND (a < 300) AND (b = 0))
-         ->  Hash Join
-               Hash Cond: (t2_2.b = t1_2.a)
-               ->  Seq Scan on prt2_adv_p2 t2_2
-               ->  Hash
+         ->  Merge Join
+               Merge Cond: (t2_2.b = t1_2.a)
+               ->  Index Scan using prt2_adv_p2_b_idx on prt2_adv_p2 t2_2
+                     Index Cond: ((b >= 100) AND (b < 300))
+               ->  Sort
+                     Sort Key: t1_2.a
                      ->  Seq Scan on prt1_adv_p2 t1_2
                            Filter: ((a >= 100) AND (a < 300) AND (b = 0))
-(15 rows)
+(18 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a >= 100 AND t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b;
   a  |  c   |  b  |  c   
@@ -4444,16 +4449,18 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
          ->  Hash Join
                Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
                ->  Seq Scan on plt2_adv_p3 t2_1
+                     Filter: (c = ANY ('{0003,0004,0005}'::text[]))
                ->  Hash
                      ->  Seq Scan on plt1_adv_p3 t1_1
                            Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
          ->  Hash Join
                Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
                ->  Seq Scan on plt2_adv_p4 t2_2
+                     Filter: (c = ANY ('{0003,0004,0005}'::text[]))
                ->  Hash
                      ->  Seq Scan on plt1_adv_p4 t1_2
                            Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-(15 rows)
+(17 rows)
 
 SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a;
  a |  c   | a |  c   
@@ -4497,16 +4504,18 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
          ->  Hash Join
                Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
                ->  Seq Scan on plt2_adv_p3 t2_1
+                     Filter: (c = ANY ('{0003,0004,0005}'::text[]))
                ->  Hash
                      ->  Seq Scan on plt1_adv_p3 t1_1
                            Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
          ->  Hash Join
                Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
                ->  Seq Scan on plt2_adv_p4 t2_2
+                     Filter: (c = ANY ('{0003,0004,0005}'::text[]))
                ->  Hash
                      ->  Seq Scan on plt1_adv_p4 t1_2
                            Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-(15 rows)
+(17 rows)
 
 SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a;
  a |  c   | a |  c   
@@ -4692,27 +4701,32 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
                      Filter: ((b >= 125) AND (b < 225))
                ->  Hash
                      ->  Seq Scan on beta_neg_p1 t2_1
+                           Filter: ((b >= 125) AND (b < 225))
          ->  Hash Join
-               Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.b = t1_2.b))
-               ->  Seq Scan on beta_neg_p2 t2_2
+               Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.b = t2_2.b))
+               ->  Seq Scan on alpha_neg_p2 t1_2
+                     Filter: ((b >= 125) AND (b < 225))
                ->  Hash
-                     ->  Seq Scan on alpha_neg_p2 t1_2
+                     ->  Seq Scan on beta_neg_p2 t2_2
                            Filter: ((b >= 125) AND (b < 225))
          ->  Hash Join
-               Hash Cond: ((t2_4.a = t1_4.a) AND (t2_4.b = t1_4.b))
+               Hash Cond: ((t1_4.a = t2_4.a) AND (t1_4.b = t2_4.b))
                ->  Append
-                     ->  Seq Scan on beta_pos_p1 t2_4
-                     ->  Seq Scan on beta_pos_p2 t2_5
-                     ->  Seq Scan on beta_pos_p3 t2_6
+                     ->  Seq Scan on alpha_pos_p1 t1_4
+                           Filter: ((b >= 125) AND (b < 225))
+                     ->  Seq Scan on alpha_pos_p2 t1_5
+                           Filter: ((b >= 125) AND (b < 225))
+                     ->  Seq Scan on alpha_pos_p3 t1_6
+                           Filter: ((b >= 125) AND (b < 225))
                ->  Hash
                      ->  Append
-                           ->  Seq Scan on alpha_pos_p1 t1_4
+                           ->  Seq Scan on beta_pos_p1 t2_4
                                  Filter: ((b >= 125) AND (b < 225))
-                           ->  Seq Scan on alpha_pos_p2 t1_5
+                           ->  Seq Scan on beta_pos_p2 t2_5
                                  Filter: ((b >= 125) AND (b < 225))
-                           ->  Seq Scan on alpha_pos_p3 t1_6
+                           ->  Seq Scan on beta_pos_p3 t2_6
                                  Filter: ((b >= 125) AND (b < 225))
-(29 rows)
+(34 rows)
 
 SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b) WHERE t1.b >= 125 AND t1.b < 225 ORDER BY t1.a, t1.b;
  a  |  b  |  c   | a  |  b  |  c   
@@ -4761,8 +4775,8 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
 
 EXPLAIN (COSTS OFF)
 SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b, t2.b;
-                                                              QUERY PLAN                                                              
---------------------------------------------------------------------------------------------------------------------------------------
+                                                                 QUERY PLAN                                                                 
+--------------------------------------------------------------------------------------------------------------------------------------------
  Sort
    Sort Key: t1.a, t1.b, t2.b
    ->  Append
@@ -4776,21 +4790,21 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.c = t2
                ->  Hash
                      ->  Append
                            ->  Seq Scan on beta_neg_p1 t2_2
-                                 Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
+                                 Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
                            ->  Seq Scan on beta_neg_p2 t2_3
-                                 Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
+                                 Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
          ->  Nested Loop
                Join Filter: ((t1_4.a = t2_4.a) AND (t1_4.c = t2_4.c))
                ->  Seq Scan on alpha_pos_p2 t1_4
                      Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
                ->  Seq Scan on beta_pos_p2 t2_4
-                     Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
+                     Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
          ->  Nested Loop
                Join Filter: ((t1_5.a = t2_5.a) AND (t1_5.c = t2_5.c))
                ->  Seq Scan on alpha_pos_p3 t1_5
                      Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
                ->  Seq Scan on beta_pos_p3 t2_5
-                     Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
+                     Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
 (28 rows)
 
 SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b, t2.b;
@@ -4816,38 +4830,40 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.c = t2
 
 EXPLAIN (COSTS OFF)
 SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b;
-                                                           QUERY PLAN                                                           
---------------------------------------------------------------------------------------------------------------------------------
+                                                              QUERY PLAN                                                              
+--------------------------------------------------------------------------------------------------------------------------------------
  Sort
    Sort Key: t1.a, t1.b
    ->  Append
-         ->  Hash Join
-               Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c))
-               ->  Seq Scan on alpha_neg_p1 t1_1
-                     Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
-               ->  Hash
+         ->  Merge Join
+               Merge Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c))
+               ->  Sort
+                     Sort Key: t1_1.a, t1_1.b, t1_1.c
+                     ->  Seq Scan on alpha_neg_p1 t1_1
+                           Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
+               ->  Sort
+                     Sort Key: t2_1.a, t2_1.b, t2_1.c
                      ->  Seq Scan on beta_neg_p1 t2_1
-                           Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
-         ->  Hash Join
-               Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c))
+                           Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
+         ->  Nested Loop
+               Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c))
+               ->  Seq Scan on beta_neg_p2 t2_2
+                     Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
                ->  Seq Scan on alpha_neg_p2 t1_2
                      Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
-               ->  Hash
-                     ->  Seq Scan on beta_neg_p2 t2_2
-                           Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
          ->  Nested Loop
                Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c))
                ->  Seq Scan on alpha_pos_p2 t1_3
                      Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
                ->  Seq Scan on beta_pos_p2 t2_3
-                     Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
+                     Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
          ->  Nested Loop
                Join Filter: ((t1_4.a = t2_4.a) AND (t1_4.b = t2_4.b) AND (t1_4.c = t2_4.c))
                ->  Seq Scan on alpha_pos_p3 t1_4
                      Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
                ->  Seq Scan on beta_pos_p3 t2_4
-                     Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
-(29 rows)
+                     Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
+(31 rows)
 
 SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b;
  a  |  b  |  c   | a  |  b  |  c   
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index 7555764c779..3fb515cdd9d 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -2104,19 +2104,7 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on
                                  Index Cond: (a = a.a)
                            ->  Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N)
                                  Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed)
-                                 Index Cond: (a = a.a)
-(27 rows)
+(15 rows)
 
 -- Ensure the same partitions are pruned when we make the nested loop
 -- parameter an Expr rather than a plain Param.
@@ -2171,19 +2159,13 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on
                                  Index Cond: (a = a.a)
                            ->  Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N)
                                  Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (actual rows=N loops=N)
+                           ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_4 (actual rows=N loops=N)
                                  Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (actual rows=N loops=N)
+                           ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_5 (actual rows=N loops=N)
                                  Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (actual rows=N loops=N)
+                           ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_6 (actual rows=N loops=N)
                                  Index Cond: (a = a.a)
-(27 rows)
+(21 rows)
 
 select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 0)');
                                         explain_parallel_append                                         
@@ -2204,19 +2186,7 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on
                                  Index Cond: (a = a.a)
                            ->  Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N)
                                  Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed)
-                                 Index Cond: (a = a.a)
-(28 rows)
+(16 rows)
 
 delete from lprt_a where a = 1;
 select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 0)');
@@ -2238,19 +2208,7 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on
                                  Index Cond: (a = a.a)
                            ->  Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (never executed)
                                  Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed)
-                                 Index Cond: (a = a.a)
-(28 rows)
+(16 rows)
 
 reset enable_hashjoin;
 reset enable_mergejoin;
diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql
index 247b0a31055..dae83c41965 100644
--- a/src/test/regress/sql/equivclass.sql
+++ b/src/test/regress/sql/equivclass.sql
@@ -269,3 +269,15 @@ create temp view overview as
   select f1::information_schema.sql_identifier as sqli, f2 from undername;
 explain (costs off)  -- this should not require a sort
   select * from overview where sqli = 'foo' order by sqli;
+
+
+-- test equivalence filters
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec0.ff between 1 and 10;
+
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec1.ff between 1 and 10;
-- 
2.21.0

#10Justin Pryzby
pryzby@telsasoft.com
In reply to: Andy Fan (#9)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

Subject: [PATCH v1 1/6] Rebaee David's patch against the latest code.

If you use git-am, then the author/commit information is preserved.
It's probably good to include a link to the patch in any case.

Subject: [PATCH v1 4/6] remove duplicated qual executing.

---                                                                                                                                                                                              
 src/backend/optimizer/path/equivclass.c | 22 +++++++++++++++++++                                                                                                                                
 src/backend/optimizer/plan/createplan.c | 29 +++++++++++++++++++++++--                                                                                                                          
 src/include/optimizer/paths.h           |  2 ++                                                                                                                                                 
 src/test/regress/parallel_schedule      |  2 ++                                                                                                                                                 
 4 files changed, 53 insertions(+), 2 deletions(-)                                                                                                                                               

I think the ./ec_filter test is missing from from this patch.

Subject: [PATCH v1 6/6] adding some test cases for this feature and fix the existing case

The tests should be updated with the corresponding patches. It's common for
the patches to be commited separately, like if 0001 is ready but the others are
still evolving.

I'm not sure whether you think this patch is ready to be added to a commitfest,
but do you know about the CI infrastructure ? It allows running all the cfbot
tests for a github branch against 4 OS, which helps catch portability issues,
including memory errors and unstable explain output. See: src/tools/ci/README.
There's an failure in postgres_fdw, probably the output needs to be updated.

--
Justin

#11Andy Fan
zhihui.fan1213@gmail.com
In reply to: Justin Pryzby (#10)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

Hi Justin:

Thanks for your attention.

On Wed, Feb 2, 2022 at 1:13 AM Justin Pryzby <pryzby@telsasoft.com> wrote:

Subject: [PATCH v1 1/6] Rebaee David's patch against the latest code.

If you use git-am, then the author/commit information is preserved.
It's probably good to include a link to the patch in any case.

Thanks for this reminder, I didn't pay enough attention to this. Fixed.

(The original patch looks like a diff file not a commit, I wrote a simple
commit
message for this and link to the origin discussion link.)

Subject: [PATCH v1 4/6] remove duplicated qual executing.

---

src/backend/optimizer/path/equivclass.c | 22 +++++++++++++++++++

src/backend/optimizer/plan/createplan.c | 29 +++++++++++++++++++++++--

src/include/optimizer/paths.h | 2 ++

src/test/regress/parallel_schedule | 2 ++

4 files changed, 53 insertions(+), 2 deletions(-)

I think the ./ec_filter test is missing from from this patch.

Indeed..

Subject: [PATCH v1 6/6] adding some test cases for this feature and fix

the existing case

The tests should be updated with the corresponding patches. It's common
for
the patches to be commited separately, like if 0001 is ready but the
others are
still evolving.

Yes, I agree with this. Just that in this case, the commit split is just
for easy
review/discussion. they are unlikely to be able to commit separately. so I
keep
it as it was and improve each commit message.

I'm not sure whether you think this patch is ready to be added to a
commitfest,
but do you know about the CI infrastructure ? It allows running all the
cfbot
tests for a github branch against 4 OS, which helps catch portability
issues,
including memory errors and unstable explain output. See:
src/tools/ci/README.

Added. https://commitfest.postgresql.org/37/3524/

There's an failure in postgres_fdw, probably the output needs to be
updated.

For the postgres_fdw, I just refreshed the content. with this patch, the
plan changed
from

Foreign Scan
Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2
Relations: (public.ft5) INNER JOIN (public.ft4)
Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1,
r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1
INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND
((r2.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST
(4 rows)

to

Sort (cost=108.02..108.04 rows=7 width=62)
Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2
Sort Key: ft5.c1
-> Foreign Scan (cost=100.00..107.92 rows=7 width=62)
Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2
Relations: (public.ft5) INNER JOIN (public.ft4)
Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN
ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S
1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >=
10)) AND ((r2.c1 <= 30)) AND ((r1.c1 >= 10)) AND ((r1.
c1 <= 30))))

But if I set enable_sort = off, we can still get the previous plan, which
proves that
this patch doesn't make the above path unavailable, it is just not cheaper
than
the new one. Here is the new commit messages:

commit e0a7838a09e73f831eecb23b5e7884cc34d71301
Author: David Rowley <dgrowleyml@gmail.com>
Date: Tue Feb 1 20:56:40 2022 +0800

Introudce ec_filters in EquivalenceClass struct, the semantics is the
quals can

be applied to any EquivalenceMember in this EC. Later this information
is used
to generate new RestrictInfo and was distributed to related RelOptInfo
very
soon.

Author: David Rowley at 2015-12 [1]/messages/by-id/CAKJS1f9FK_X_5HKcPcSeimy16Owe3EmPmmGsGWLcKkj_rW9s6A@mail.gmail.com
Andy Fan rebase this patch to current latest code.

/messages/by-id/CAKJS1f9FK_X_5HKcPcSeimy16Owe3EmPmmGsGWLcKkj_rW9s6A@mail.gmail.com

commit 73f52d0909374446cd689457f0a4ef52addb035e
Author: Andy Fan <yizhi.fzh@alibaba-inc.com>
Date: Tue Feb 1 14:54:07 2022 +0800

After distributing the new derived RestrictInfo into RelOptInfo, then
the rows
estimation is wrong at the joinrel part. The reason is well described
at [1]/messages/by-id/CAKJS1f9FK_X_5HKcPcSeimy16Owe3EmPmmGsGWLcKkj_rW9s6A@mail.gmail.com and
[2]: /messages/by-id/1727507.1620948117@sss.pgh.pa.us
*derived" in
RestrictInfo struct to indicate how this qual is generated. we would
ignore such
qual during estimate of the rows size. All the set_xx_size should be
taken care of, but
for now, just set_plain_rel_size is taken care of for the PoC purpose.

[1]: /messages/by-id/CAKJS1f9FK_X_5HKcPcSeimy16Owe3EmPmmGsGWLcKkj_rW9s6A@mail.gmail.com

/messages/by-id/CAKJS1f9FK_X_5HKcPcSeimy16Owe3EmPmmGsGWLcKkj_rW9s6A@mail.gmail.com
[2]: /messages/by-id/1727507.1620948117@sss.pgh.pa.us

/messages/by-id/1727507.1620948117@sss.pgh.pa.us

commit 8439b4818410d860a4ca4be3458b54c04c6f8648
Author: Andy Fan <yizhi.fzh@alibaba-inc.com>
Date: Tue Feb 1 15:20:10 2022 +0800

Introduce RelOptInfo.filtered_rows.

Previously the Path.rows (shown in the explain output) and
RelOptInfo.rows
which would be used to calculating joinrel's estimated rows are same
at many scan paths, like SeqScan, IndexScan, BitmapHeapScan and so on.
But
they would be different after distributing a new restrictinfo from
ec_filter.
So I developed RelOptInfo.filtered_rows to take some duty out of
RelOptInfo.rows.

commit 11b3395bb5bcc4a2bcff6fed8078dbbf3cda81b1
Author: Andy Fan <yizhi.fzh@alibaba-inc.com>
Date: Tue Feb 1 17:37:27 2022 +0800

Remove duplicated qual executing for executor.

Take the SELECT * FROM t1, t2 WHERE t1.a = t2.a and t2.a > 3 for
example,
we can derive t1.a > 3 with EC filter infrastructure. However if it
generate a
plan like below, the new generated qual does not deserve to execute.

Nest Loop
Seq Scan (t1.a > 3)
Index Scan t2_a
(a = t1.a) (t2.a > 3)

This patch removes the "t2.a > 3" for the above case.

commit 2875a76136293589b6e409cb6be4defab87ade59
Author: Andy Fan <yizhi.fzh@alibaba-inc.com>
Date: Wed Feb 2 11:54:24 2022 +0800

Support ScalarArrayOpExpr and perudoconstant on ef_filter.

commit a4b21ab6fd0fd57902f5471ec962a77b59085158 (HEAD -> cf_v4)
Author: Andy Fan <yizhi.fzh@alibaba-inc.com>
Date: Wed Feb 2 11:59:53 2022 +0800

Added the testcase for this feature and fix the previous test case

as well. The new added test case needs outputting some runtime
statistics, which will probably be different at each run. I can think
of a way to make the test case stable if the patchsets are not wrong
at the first step.

--
Best Regards
Andy Fan

#12Tomas Vondra
tomas.vondra@enterprisedb.com
In reply to: Andy Fan (#11)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

Hi,

there's been an interesting case [1]/messages/by-id/CA+1Wm9U_sP9237f7OH7O=-UTab71DWOO4Qc-vnC78DfsJQBCwQ@mail.gmail.com of a slow query on pgsql-general,
related to the topic discussed in this thread. It causes an order the
query to run slower by multiple orders of magnitude, and I think it's
interesting, so let me present a simple example demonstrating it.

------------------------------------------------------------------------
create table t1 (a int);
create table t2 (a int);

insert into t1
select i from generate_series(1,100000) s(i);

insert into t2
select mod(i,100000) from generate_series(1,10000000) s(i);

create index on t1(a);
create index on t2(a);

vacuum analyze t1, t2;

-- we need to force mergejoin
set enable_nestloop = off;
------------------------------------------------------------------------

Now, let's run a simple query:

SELECT t1.a, t2.a FROM t1 JOIN t2 USING (a)
WHERE (t1.a > 99000) ORDER BY t1.a LIMIT 100;

QUERY PLAN
------------------------------------------------------------------------
Limit (cost=4.63..224.57 rows=100 width=8)
(actual time=8999.487..8999.707 rows=100 loops=1)
-> Merge Join (cost=4.63..209447.97 rows=95226 width=8)
(actual time=8999.485..8999.620 rows=100 loops=1)
Merge Cond: (t1.a = t2.a)
-> Index Only Scan using t1_a_idx on t1
(cost=0.29..29.25 rows=969 width=4)
(actual time=0.010..0.011 rows=1 loops=1)
Index Cond: (a > 99000)
Heap Fetches: 0
-> Index Only Scan using t2_a_idx on t2
(cost=0.43..183464.09 rows=9999977 width=4)
(actual time=0.026..4594.757 rows=9900200 loops=1)
Heap Fetches: 0
Planning Time: 0.338 ms
Execution Time: 8999.768 ms
(10 rows)

Now, let's do a simple trick and add condition on t2.a, "implied" by the
join condition (t1.a = t2.a) and inequality (t1.a > 99000).

SELECT t1.a, t2.a FROM t1 JOIN t2 USING (a)
WHERE (t1.a > 99000) AND (t2.a > 99000) ORDER BY t1.a LIMIT 100;

QUERY PLAN
------------------------------------------------------------------------
Limit (cost=0.77..250.39 rows=100 width=8)
(actual time=0.040..0.294 rows=100 loops=1)
-> Merge Join (cost=0.77..2297.23 rows=920 width=8)
(actual time=0.039..0.172 rows=100 loops=1)
Merge Cond: (t1.a = t2.a)
-> Index Only Scan using t1_a_idx on t1
(cost=0.29..29.25 rows=969 width=4)
(actual time=0.031..0.031 rows=1 loops=1)
Index Cond: (a > 99000)
Heap Fetches: 0
-> Index Only Scan using t2_a_idx on t2
(cost=0.43..2014.87 rows=96596 width=4)
(actual time=0.005..0.052 rows=100 loops=1)
Index Cond: (a > 99000)
Heap Fetches: 0
Planning Time: 0.222 ms
Execution Time: 0.414 ms
(11 rows)

Well, that's quite a difference! From 9000ms to 1ms, pretty good.

What is happening in the first plan is the merge join needs t2 sorted by
t2.a, and the index-only-scan looks like a great way to do that, as it
has low startup cost (because LIMIT likes that). But this completely
misses that (t1.a > 9900) implies (t2.a > 9900) through the equality in
join condition. So we start scanning t2_a_idx, only to throw the first
99% of tuples away.

In the original report this is particularly egregious, because the index
only scan looks like this:

-> Index Only Scan using data_class_pkey on data_class ta
(cost=0.57..4935483.78 rows=216964862 width=8)
(actual time=0.018..35022.908 rows=151321889 loops=1)
Heap Fetches: 151321889

So yeah, 151M heap fetches, that's bound to be expensive :-/

Adding the condition on t2.a allows just skipping the first chunk of the
index, eliminating the expensive part.

Of course, this breaks the estimates in the faster query, because we now
apply the condition twice - once for the index scan, one as the join
clause. So instead of ~100k rows the join is estimated as ~1000 rows.

I'm also not claiming this is 100% worth it - queries with a suitable
combination of clauses (conditions on the join keys) seems rather
uncommon. But it seems like an interesting example, because it may be
seen either as missed execution optimization (failing to skip the
initial chunk of rows), or an costing issue due to not accounting for
having to process the rows (which would likely result in picking a
different plan).

regards

[1]: /messages/by-id/CA+1Wm9U_sP9237f7OH7O=-UTab71DWOO4Qc-vnC78DfsJQBCwQ@mail.gmail.com
/messages/by-id/CA+1Wm9U_sP9237f7OH7O=-UTab71DWOO4Qc-vnC78DfsJQBCwQ@mail.gmail.com

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#13Andy Fan
zhihui.fan1213@gmail.com
In reply to: Tomas Vondra (#12)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

Hi,

On Sat, Feb 5, 2022 at 9:32 PM Tomas Vondra <tomas.vondra@enterprisedb.com>
wrote:

I'm also not claiming this is 100% worth it - queries with a suitable
combination of clauses (conditions on the join keys) seems rather
uncommon.

Thanks for showing interest in this. I want to add some other user cases
which seem not very uncommon. a). When we join the key on a foregin
table, in which case, push down a qual to foregin key would be pretty
good to reduce the data transformed from the network. b). If the people
join many partitioned table on partitioned key, but they want to query
more than 1 partitions (which means the qual on partition key is not a
simple "partitionKey = Const"), then we have to do a run-time partition
prune (lose the chance for initial partition prune). We have big difference
on the performance aspect as well.

I guess some of the people who think we may need this feature are not very
clear about what bad it would be if we add this feature (Of course Including
me). I summarized the discussion before and hacked the solution at [1]/messages/by-id/CAKU4AWpo9z0hMHDWUKuce4Z-NpcybV0J2UVu5+DVwyP-CrHCQg@mail.gmail.com,
the
current state looks reasonable to me. I'm not sure if I missed any point.

Of course, this breaks the estimates in the faster query, because we now
apply the condition twice - once for the index scan, one as the join
clause. So instead of ~100k rows the join is estimated as ~1000 rows.

I think my patch has addressed this. Here is the example:

postgres=# set geqo to off; -- disable this feature, we have an estimation
error.
-- using geqo guc in patch is
just for easy testing.
SET
postgres=# explain analyze SELECT t1.a, t2.a FROM t1 JOIN t2 USING (a)
WHERE (t1.a > 99000) and t2.a > 99000;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
Merge Join (cost=0.73..2408.37 rows=990 width=8)
(actual time=0.032..21.350 rows=99900 loops=1)
Merge Cond: (t1.a = t2.a)
-> Index Only Scan using t1_a_idx on t1 (cost=0.29..29.64 rows=991
width=4)
(actual time=0.014..0.121
rows=1000 loops=1)
Index Cond: (a > 99000)
Heap Fetches: 0
-> Index Only Scan using t2_a_idx on t2 (cost=0.43..2113.20
rows=101301 width=4)
(actual time=0.013..9.854
rows=99900 loops=1)
Index Cond: (a > 99000)
Heap Fetches: 0
Planning Time: 0.282 ms
Execution Time: 24.823 ms
(10 rows)

postgres=# set geqo to on; -- enable this feature and let planner derive
the qual by itself, the estimation
-- is good.
SET
postgres=# explain analyze SELECT t1.a, t2.a FROM t1 JOIN t2 USING (a)
WHERE (t1.a > 99000) ;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------
Merge Join (cost=0.73..2408.37 rows=97680 width=8)
(actual time=0.031..21.296 rows=99900 loops=1)
Merge Cond: (t1.a = t2.a)
-> Index Only Scan using t1_a_idx on t1 (cost=0.29..29.64 rows=991
width=4)
(actual time=0.014..0.116
rows=1000 loops=1)
Index Cond: (a > 99000)
Heap Fetches: 0
-> Index Only Scan using t2_a_idx on t2 (cost=0.43..2113.20
rows=101301 width=4)
(actual time=0.012..9.751
rows=99900 loops=1)
Index Cond: (a > 99000)
Heap Fetches: 0
Planning Time: 0.269 ms
Execution Time: 24.749 ms
(10 rows)

So I think knowing what bad it is to have this feature is the key point to
discussion now.

[1]: /messages/by-id/CAKU4AWpo9z0hMHDWUKuce4Z-NpcybV0J2UVu5+DVwyP-CrHCQg@mail.gmail.com
/messages/by-id/CAKU4AWpo9z0hMHDWUKuce4Z-NpcybV0J2UVu5+DVwyP-CrHCQg@mail.gmail.com

--
Best Regards
Andy Fan

#14Andy Fan
zhihui.fan1213@gmail.com
In reply to: Andy Fan (#13)
6 attachment(s)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

Of course, this breaks the estimates in the faster query, because we now
apply the condition twice - once for the index scan, one as the join
clause. So instead of ~100k rows the join is estimated as ~1000 rows.

I think my patch has addressed this. Here is the example: ...

So I think knowing what bad it is to have this feature is the key point to

discussion now.

[1]
/messages/by-id/CAKU4AWpo9z0hMHDWUKuce4Z-NpcybV0J2UVu5+DVwyP-CrHCQg@mail.gmail.com

I forgot to upload these patches, upload them now.

--
Best Regards
Andy Fan

Attachments:

v2-0005-Support-ScalarArrayOpExpr-and-perudoconstant-on-e.patchapplication/octet-stream; name=v2-0005-Support-ScalarArrayOpExpr-and-perudoconstant-on-e.patchDownload
From 2875a76136293589b6e409cb6be4defab87ade59 Mon Sep 17 00:00:00 2001
From: Andy Fan <yizhi.fzh@alibaba-inc.com>
Date: Wed, 2 Feb 2022 11:54:24 +0800
Subject: [PATCH v2 5/6] Support ScalarArrayOpExpr and perudoconstant on
 ef_filter.

---
 src/backend/optimizer/path/equivclass.c | 110 +++++++++++++++++-------
 src/backend/optimizer/plan/initsplan.c  |  48 +++++++----
 src/include/nodes/pathnodes.h           |   3 +-
 3 files changed, 114 insertions(+), 47 deletions(-)

diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 9ce0249b10d..4271bacb070 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -1386,23 +1386,55 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 				rightexpr = (Expr *) ef->ef_const;
 			}
 
-			opno = get_opfamily_member(family,
-										exprType((Node *) leftexpr),
-										exprType((Node *) rightexpr),
-										strategy);
+			if (ef->ef_expr_type == T_OpExpr)
+			{
+				opno = get_opfamily_member(family,
+										   exprType((Node *) leftexpr),
+										   exprType((Node *) rightexpr),
+										   strategy);
 
-			if (opno == InvalidOid)
-				continue;
+				if (opno == InvalidOid)
+					continue;
 
-			rinfo = process_implied_equality(root, opno,
-											 ec->ec_collation,
-											 leftexpr,
-											 rightexpr,
-											 bms_copy(ec->ec_relids),
-											 bms_copy(cur_em->em_nullable_relids),
-											 ec->ec_min_security,
-											 ec->ec_below_outer_join,
-											 false);
+				rinfo = process_implied_equality(root, opno,
+												 ec->ec_collation,
+												 leftexpr,
+												 rightexpr,
+												 bms_copy(ec->ec_relids),
+												 bms_copy(cur_em->em_nullable_relids),
+												 ec->ec_min_security,
+												 ec->ec_below_outer_join,
+												 false);
+			}
+			else
+			{
+				ScalarArrayOpExpr *arr_opexpr;
+				Relids relids;
+
+				Assert(ef->ef_expr_type == T_ScalarArrayOpExpr);
+				arr_opexpr = makeNode(ScalarArrayOpExpr);
+				arr_opexpr->opno = ef->ef_opno;
+				arr_opexpr->opfuncid = get_opcode(ef->ef_opno);
+				arr_opexpr->useOr = true; /* See is_simple_filter_qual */
+				arr_opexpr->args = list_make2(leftexpr, ef->ef_const);
+				arr_opexpr->inputcollid = ec->ec_collation;
+
+				relids = pull_varnos(root, (Node *)arr_opexpr);
+
+				rinfo = make_restrictinfo(root,
+										  (Expr *) arr_opexpr,
+										  true,
+										  false,
+										  false, /* perudoconstant */
+										  ec->ec_min_security,
+										  relids,
+										  NULL,
+										  bms_copy(cur_em->em_nullable_relids));
+
+				// check_mergejoinable(rinfo);
+
+				distribute_restrictinfo_to_rels(root, rinfo);
+			}
 			rinfo->derived = ec;
 		}
 
@@ -2009,29 +2041,48 @@ distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
 	 */
 	foreach(l, quallist)
 	{
-		OpExpr	   *opexpr = (OpExpr *) lfirst(l);
-		Expr	   *leftexpr = (Expr *) linitial(opexpr->args);
-		Expr	   *rightexpr = (Expr *) lsecond(opexpr->args);
-		Const	   *constexpr;
-		Expr	   *varexpr;
+		Expr	*expr = lfirst(l);
+
+		List	*args;
+		Node	   *leftexpr;
+		Node	   *rightexpr;
+		Node	   *constexpr;
+		Node	   *varexpr;
+		Oid			opno;
+
 		Relids		exprrels;
 		int			relid;
 		bool		const_isleft;
 		ListCell *l2;
 
-		/*
-		 * Determine if the the OpExpr is in the form "expr op const" or
-		 * "const op expr".
-		 */
-		if (IsA(leftexpr, Const))
+		if (nodeTag(expr) == T_OpExpr)
+		{
+			OpExpr	   *opexpr = (OpExpr *) lfirst(l);
+			args = opexpr->args;
+			opno = opexpr->opno;
+		}
+		else if (nodeTag(expr) == T_ScalarArrayOpExpr)
+		{
+			ScalarArrayOpExpr *arr_expr = (ScalarArrayOpExpr *) lfirst(l);
+			args = arr_expr->args;
+			opno = arr_expr->opno;
+		}
+		else
+		{
+			Assert(false);
+		}
+
+		leftexpr = linitial(args);
+		rightexpr = lsecond(args);
+		if (is_pseudo_constant_clause(leftexpr))
 		{
-			constexpr = (Const *) leftexpr;
+			constexpr = leftexpr;
 			varexpr = rightexpr;
 			const_isleft = true;
 		}
 		else
 		{
-			constexpr = (Const *) rightexpr;
+			constexpr = rightexpr;
 			varexpr = leftexpr;
 			const_isleft = false;
 		}
@@ -2073,10 +2124,11 @@ distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
 				{
 					EquivalenceFilter *efilter;
 					efilter = makeNode(EquivalenceFilter);
-					efilter->ef_const = (Const *) copyObject(constexpr);
+					efilter->ef_const = (Node *)copyObject(constexpr);
 					efilter->ef_const_is_left = const_isleft;
-					efilter->ef_opno = opexpr->opno;
+					efilter->ef_opno = opno;
 					efilter->ef_source_rel = relid;
+					efilter->ef_expr_type = nodeTag(expr);
 
 					ec->ec_filters = lappend(ec->ec_filters, efilter);
 					break;		/* Onto the next eclass */
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index b5204b3c4f4..120380ea3ae 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -658,31 +658,39 @@ create_lateral_join_info(PlannerInfo *root)
  *             false if it should not be used.
  */
 static bool
-is_simple_filter_qual(PlannerInfo *root, OpExpr *expr)
+is_simple_filter_qual(PlannerInfo *root, Expr *expr)
 {
-	Expr *leftexpr;
-	Expr *rightexpr;
+	Node *leftexpr;
+	Node *rightexpr;
+	List *args = NIL;
 
-	if (!IsA(expr, OpExpr))
+	if (IsA(expr, OpExpr))
+		args = castNode(OpExpr, expr)->args;
+	else if (IsA(expr, ScalarArrayOpExpr))
+	{
+		ScalarArrayOpExpr *arr_opexpr = castNode(ScalarArrayOpExpr, expr);
+		if (!arr_opexpr->useOr)
+			/* Just support IN for now */
 			return false;
+		args = arr_opexpr->args;
+	}
 
-	if (list_length(expr->args) != 2)
-			return false;
+	if (list_length(args) != 2)
+		return false;
 
-	leftexpr = (Expr *) linitial(expr->args);
-	rightexpr = (Expr *) lsecond(expr->args);
+	leftexpr = linitial(args);
+	rightexpr = lsecond(args);
 
-	/* XXX should we restrict these to simple Var op Const expressions? */
-	if (IsA(leftexpr, Const))
+	if (is_pseudo_constant_clause(leftexpr))
 	{
-		if (bms_membership(pull_varnos(root, (Node *) rightexpr)) == BMS_SINGLETON &&
-			!contain_volatile_functions((Node *) rightexpr))
+		if (bms_membership(pull_varnos(root, rightexpr)) == BMS_SINGLETON &&
+			!contain_volatile_functions(rightexpr))
 			return true;
 	}
-	else if (IsA(rightexpr, Const))
+	else if (is_pseudo_constant_clause(rightexpr))
 	{
-		if (bms_membership(pull_varnos(root, (Node *) leftexpr)) == BMS_SINGLETON &&
-			!contain_volatile_functions((Node *) leftexpr))
+		if (bms_membership(pull_varnos(root, leftexpr)) == BMS_SINGLETON &&
+			!contain_volatile_functions(leftexpr))
 			return true;
 	}
 
@@ -739,7 +747,13 @@ deconstruct_jointree(PlannerInfo *root)
 
 	result = deconstruct_recurse(root, (Node *) root->parse->jointree, false,
 								 &qualscope, &inner_join_rels,
-								 &postponed_qual_list, &filter_qual_list);
+								 &postponed_qual_list,
+								 /*
+								  * geqo option here is just used for testing
+								  * during review stage, set enable_geqo to
+								  * false to disable this feature.
+								  */
+								 enable_geqo ? &filter_qual_list : NULL);
 
 	/* Shouldn't be any leftover quals */
 	Assert(postponed_qual_list == NIL);
@@ -2024,7 +2038,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	distribute_restrictinfo_to_rels(root, restrictinfo);
 
 	/* Check if the qual looks useful to harvest as an EquivalenceFilter */
-	if (filter_qual_list != NULL && is_simple_filter_qual(root, (OpExpr *) clause))
+	if (filter_qual_list != NULL && is_simple_filter_qual(root, (Expr *) clause))
 		*filter_qual_list = lappend(*filter_qual_list, clause);
 }
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index dbe1775f96d..c1ef0066188 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1035,10 +1035,11 @@ typedef struct EquivalenceFilter
 {
 	NodeTag		type;
 
-	Const	   *ef_const;		/* the constant expression to filter on */
+	Node		*ef_const;		/* pseudo const */
 	Oid			ef_opno;		/* Operator Oid of filter operator */
 	bool		ef_const_is_left; /* Is the Const on the left of the OpExrp? */
 	Index		ef_source_rel;	/* relid of originating relation. */
+	NodeTag		ef_expr_type;
 } EquivalenceFilter;
 
 /*
-- 
2.21.0

v2-0003-Introduce-RelOptInfo.filtered_rows.patchapplication/octet-stream; name=v2-0003-Introduce-RelOptInfo.filtered_rows.patchDownload
From 8439b4818410d860a4ca4be3458b54c04c6f8648 Mon Sep 17 00:00:00 2001
From: Andy Fan <yizhi.fzh@alibaba-inc.com>
Date: Tue, 1 Feb 2022 15:20:10 +0800
Subject: [PATCH v2 3/6] Introduce RelOptInfo.filtered_rows.

Previously the Path.rows (shown in the explain output) and RelOptInfo.rows
which would be used to calculating joinrel's estimated rows. They are same
at many scan paths, like SeqScan, IndexScan, BitmapHeapScan and so on. But
they would be different after distributing a new restrictinfo from ec_filter.
So I developed RelOptInfo.filtered_rows to take the duty out of RelOptInfo.rows.
---
 src/backend/optimizer/path/costsize.c | 21 ++++++++++++++++-----
 src/include/nodes/pathnodes.h         |  2 ++
 2 files changed, 18 insertions(+), 5 deletions(-)

diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 9e303877af7..b1117257a38 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -241,7 +241,7 @@ cost_seqscan(Path *path, PlannerInfo *root,
 	if (param_info)
 		path->rows = param_info->ppi_rows;
 	else
-		path->rows = baserel->rows;
+		path->rows = baserel->filtered_rows;
 
 	if (!enable_seqscan)
 		startup_cost += disable_cost;
@@ -539,7 +539,7 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count,
 	}
 	else
 	{
-		path->path.rows = baserel->rows;
+		path->path.rows = baserel->filtered_rows;
 		/* qpquals come from just the rel's restriction clauses */
 		qpquals = extract_nonindex_conditions(path->indexinfo->indrestrictinfo,
 											  path->indexclauses);
@@ -978,7 +978,7 @@ cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel,
 	if (param_info)
 		path->rows = param_info->ppi_rows;
 	else
-		path->rows = baserel->rows;
+		path->rows = baserel->filtered_rows;
 
 	if (!enable_bitmapscan)
 		startup_cost += disable_cost;
@@ -1209,7 +1209,7 @@ cost_tidscan(Path *path, PlannerInfo *root,
 	if (param_info)
 		path->rows = param_info->ppi_rows;
 	else
-		path->rows = baserel->rows;
+		path->rows = baserel->filtered_rows;
 
 	/* Count how many tuples we expect to retrieve */
 	ntuples = 0;
@@ -1320,7 +1320,7 @@ cost_tidrangescan(Path *path, PlannerInfo *root,
 	if (param_info)
 		path->rows = param_info->ppi_rows;
 	else
-		path->rows = baserel->rows;
+		path->rows = baserel->filtered_rows;
 
 	/* Count how many tuples and pages we expect to scan */
 	selectivity = clauselist_selectivity(root, tidrangequals, baserel->relid,
@@ -4938,6 +4938,17 @@ set_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel)
 
 	rel->rows = clamp_row_est(nrows);
 
+	nrows = rel->tuples *
+		clauselist_selectivity_ext(root,
+								   rel->baserestrictinfo,
+								   0,
+								   JOIN_INNER,
+								   NULL,
+								   true,
+								   true /* include_derived, for filtered rows */);
+
+	rel->filtered_rows = clamp_row_est(nrows);
+
 	cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root);
 
 	set_rel_width(root, rel);
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 42368e10b8e..dbe1775f96d 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -683,6 +683,8 @@ typedef struct RelOptInfo
 	/* size estimates generated by planner */
 	Cardinality	rows;			/* estimated number of result tuples */
 
+	Cardinality filtered_rows;  /* filtered rows */
+
 	/* per-relation planner control flags */
 	bool		consider_startup;	/* keep cheap-startup-cost paths? */
 	bool		consider_param_startup; /* ditto, for parameterized paths? */
-- 
2.21.0

v2-0002-After-distributing-the-new-derived-RestrictInfo-i.patchapplication/octet-stream; name=v2-0002-After-distributing-the-new-derived-RestrictInfo-i.patchDownload
From 73f52d0909374446cd689457f0a4ef52addb035e Mon Sep 17 00:00:00 2001
From: Andy Fan <yizhi.fzh@alibaba-inc.com>
Date: Tue, 1 Feb 2022 14:54:07 +0800
Subject: [PATCH v2 2/6] After distributing the new derived RestrictInfo into
 RelOptInfo, then the rows

estimation is wrong at the joinrel part. The reason is well described at [1] and
[2],  To fix this issue, I added a new field "EquivalenceClass *derived" in
RestrictInfo struct to indicate how this qual is generated. we would ignore such
qual during estimate the rows size. All the set_xx_size should be take care, but
for now, just set_plain_rel_size is taken care for the PoC purpose.

[1]
https://www.postgresql.org/message-id/flat/CAKJS1f9FK_X_5HKcPcSeimy16Owe3EmPmmGsGWLcKkj_rW9s6A%40mail.gmail.com
[2]
https://www.postgresql.org/message-id/flat/1727507.1620948117%40sss.pgh.pa.us#52ac3f46cf614acb0bdbddb7128f5bd2
---
 src/backend/optimizer/path/clausesel.c  | 34 +++++++++++++++++--------
 src/backend/optimizer/path/costsize.c   | 25 ++++++++++--------
 src/backend/optimizer/path/equivclass.c | 20 ++++++++-------
 src/backend/optimizer/util/inherit.c    | 31 ++++++++++++----------
 src/backend/statistics/dependencies.c   |  3 ++-
 src/backend/statistics/extended_stats.c |  5 ++--
 src/include/nodes/pathnodes.h           |  1 +
 src/include/optimizer/optimizer.h       |  6 +++--
 8 files changed, 77 insertions(+), 48 deletions(-)

diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index 06f836308d0..8961e66ea4e 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -106,7 +106,7 @@ clauselist_selectivity(PlannerInfo *root,
 					   SpecialJoinInfo *sjinfo)
 {
 	return clauselist_selectivity_ext(root, clauses, varRelid,
-									  jointype, sjinfo, true);
+									  jointype, sjinfo, true, true);
 }
 
 /*
@@ -121,7 +121,8 @@ clauselist_selectivity_ext(PlannerInfo *root,
 						   int varRelid,
 						   JoinType jointype,
 						   SpecialJoinInfo *sjinfo,
-						   bool use_extended_stats)
+						   bool use_extended_stats,
+						   bool include_derived)
 {
 	Selectivity s1 = 1.0;
 	RelOptInfo *rel;
@@ -137,7 +138,8 @@ clauselist_selectivity_ext(PlannerInfo *root,
 	if (list_length(clauses) == 1)
 		return clause_selectivity_ext(root, (Node *) linitial(clauses),
 									  varRelid, jointype, sjinfo,
-									  use_extended_stats);
+									  use_extended_stats,
+									  include_derived);
 
 	/*
 	 * Determine if these clauses reference a single relation.  If so, and if
@@ -183,7 +185,7 @@ clauselist_selectivity_ext(PlannerInfo *root,
 
 		/* Compute the selectivity of this clause in isolation */
 		s2 = clause_selectivity_ext(root, clause, varRelid, jointype, sjinfo,
-									use_extended_stats);
+									use_extended_stats, include_derived);
 
 		/*
 		 * Check for being passed a RestrictInfo.
@@ -412,7 +414,9 @@ clauselist_selectivity_or(PlannerInfo *root,
 			continue;
 
 		s2 = clause_selectivity_ext(root, (Node *) lfirst(lc), varRelid,
-									jointype, sjinfo, use_extended_stats);
+									jointype, sjinfo, use_extended_stats,
+									true /* we never push a derived under or clause */
+			);
 
 		s1 = s1 + s2 - s1 * s2;
 	}
@@ -694,7 +698,7 @@ clause_selectivity(PlannerInfo *root,
 				   SpecialJoinInfo *sjinfo)
 {
 	return clause_selectivity_ext(root, clause, varRelid,
-								  jointype, sjinfo, true);
+								  jointype, sjinfo, true, true);
 }
 
 /*
@@ -709,7 +713,8 @@ clause_selectivity_ext(PlannerInfo *root,
 					   int varRelid,
 					   JoinType jointype,
 					   SpecialJoinInfo *sjinfo,
-					   bool use_extended_stats)
+					   bool use_extended_stats,
+					   bool include_derived)
 {
 	Selectivity s1 = 0.5;		/* default for any unhandled clause type */
 	RestrictInfo *rinfo = NULL;
@@ -742,6 +747,9 @@ clause_selectivity_ext(PlannerInfo *root,
 		if (rinfo->norm_selec > 1)
 			return (Selectivity) 1.0;
 
+		if (rinfo->derived && !include_derived)
+			return (Selectivity) 1.0;
+
 		/*
 		 * If possible, cache the result of the selectivity calculation for
 		 * the clause.  We can cache if varRelid is zero or the clause
@@ -830,7 +838,8 @@ clause_selectivity_ext(PlannerInfo *root,
 										  varRelid,
 										  jointype,
 										  sjinfo,
-										  use_extended_stats);
+										  use_extended_stats,
+										  include_derived);
 	}
 	else if (is_andclause(clause))
 	{
@@ -840,7 +849,8 @@ clause_selectivity_ext(PlannerInfo *root,
 										varRelid,
 										jointype,
 										sjinfo,
-										use_extended_stats);
+										use_extended_stats,
+										include_derived);
 	}
 	else if (is_orclause(clause))
 	{
@@ -959,7 +969,8 @@ clause_selectivity_ext(PlannerInfo *root,
 									varRelid,
 									jointype,
 									sjinfo,
-									use_extended_stats);
+									use_extended_stats,
+									include_derived);
 	}
 	else if (IsA(clause, CoerceToDomain))
 	{
@@ -969,7 +980,8 @@ clause_selectivity_ext(PlannerInfo *root,
 									varRelid,
 									jointype,
 									sjinfo,
-									use_extended_stats);
+									use_extended_stats,
+									include_derived);
 	}
 	else
 	{
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 8dc7dd4ca26..9e303877af7 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -4928,11 +4928,13 @@ set_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel)
 	Assert(rel->relid > 0);
 
 	nrows = rel->tuples *
-		clauselist_selectivity(root,
-							   rel->baserestrictinfo,
-							   0,
-							   JOIN_INNER,
-							   NULL);
+		clauselist_selectivity_ext(root,
+								   rel->baserestrictinfo,
+								   0,
+								   JOIN_INNER,
+								   NULL,
+								   true,
+								   false /* include_derived */);
 
 	rel->rows = clamp_row_est(nrows);
 
@@ -4964,11 +4966,14 @@ get_parameterized_baserel_size(PlannerInfo *root, RelOptInfo *rel,
 	 */
 	allclauses = list_concat_copy(param_clauses, rel->baserestrictinfo);
 	nrows = rel->tuples *
-		clauselist_selectivity(root,
-							   allclauses,
-							   rel->relid,	/* do not use 0! */
-							   JOIN_INNER,
-							   NULL);
+		clauselist_selectivity_ext(root,
+								   allclauses,
+								   rel->relid,	/* do not use 0! */
+								   JOIN_INNER,
+								   NULL,
+								   true,
+								   false /* doesn't include the derived clause */
+			);
 	nrows = clamp_row_est(nrows);
 	/* For safety, make sure result is not more than the base estimate */
 	if (nrows > rel->rows)
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index f9ae2785d60..6ed9e8c9064 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -1366,6 +1366,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 			int strategy;
 			Oid opno;
 			Oid family;
+			RestrictInfo *rinfo;
 
 			if (ef->ef_source_rel == relid)
 				continue;
@@ -1393,15 +1394,16 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 			if (opno == InvalidOid)
 				continue;
 
-			process_implied_equality(root, opno,
-										ec->ec_collation,
-										leftexpr,
-										rightexpr,
-										bms_copy(ec->ec_relids),
-										bms_copy(cur_em->em_nullable_relids),
-									 	ec->ec_min_security,
-										ec->ec_below_outer_join,
-										false);
+			rinfo = process_implied_equality(root, opno,
+											 ec->ec_collation,
+											 leftexpr,
+											 rightexpr,
+											 bms_copy(ec->ec_relids),
+											 bms_copy(cur_em->em_nullable_relids),
+											 ec->ec_min_security,
+											 ec->ec_below_outer_join,
+											 false);
+			rinfo->derived = ec;
 		}
 
 		prev_ems[relid] = cur_em;
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 7e134822f36..8a5863c4da4 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -797,6 +797,7 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 		{
 			Node	   *onecq = (Node *) lfirst(lc2);
 			bool		pseudoconstant;
+			RestrictInfo		*child_rinfo;
 
 			/* check for pseudoconstant (no Vars or volatile functions) */
 			pseudoconstant =
@@ -807,15 +808,19 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 				/* tell createplan.c to check for gating quals */
 				root->hasPseudoConstantQuals = true;
 			}
+
+			child_rinfo =  make_restrictinfo(root,
+											 (Expr *) onecq,
+											 rinfo->is_pushed_down,
+											 rinfo->outerjoin_delayed,
+											 pseudoconstant,
+											 rinfo->security_level,
+											 NULL, NULL, NULL);
+
+			child_rinfo->derived = rinfo->derived;
 			/* reconstitute RestrictInfo with appropriate properties */
-			childquals = lappend(childquals,
-								 make_restrictinfo(root,
-												   (Expr *) onecq,
-												   rinfo->is_pushed_down,
-												   rinfo->outerjoin_delayed,
-												   pseudoconstant,
-												   rinfo->security_level,
-												   NULL, NULL, NULL));
+			childquals = lappend(childquals, child_rinfo);
+
 			/* track minimum security level among child quals */
 			cq_min_security = Min(cq_min_security, rinfo->security_level);
 		}
@@ -844,13 +849,13 @@ apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
 			foreach(lc2, qualset)
 			{
 				Expr	   *qual = (Expr *) lfirst(lc2);
+				RestrictInfo *rinfo = make_restrictinfo(root, qual,
+														true, false, false,
+														security_level,
+														NULL, NULL, NULL);
 
 				/* not likely that we'd see constants here, so no check */
-				childquals = lappend(childquals,
-									 make_restrictinfo(root, qual,
-													   true, false, false,
-													   security_level,
-													   NULL, NULL, NULL));
+				childquals = lappend(childquals, rinfo);
 				cq_min_security = Min(cq_min_security, security_level);
 			}
 			security_level++;
diff --git a/src/backend/statistics/dependencies.c b/src/backend/statistics/dependencies.c
index 34326d55619..838446a220d 100644
--- a/src/backend/statistics/dependencies.c
+++ b/src/backend/statistics/dependencies.c
@@ -1076,7 +1076,8 @@ clauselist_apply_dependencies(PlannerInfo *root, List *clauses,
 		}
 
 		simple_sel = clauselist_selectivity_ext(root, attr_clauses, varRelid,
-												jointype, sjinfo, false);
+												jointype, sjinfo, false,
+												true /* probably no reasonable */);
 		attr_sel[attidx++] = simple_sel;
 	}
 
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index ca48395d5c5..38836f58c4e 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -1870,7 +1870,8 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli
 				 * columns/clauses.
 				 */
 				simple_sel = clause_selectivity_ext(root, clause, varRelid,
-													jointype, sjinfo, false);
+													jointype, sjinfo, false,
+													true);
 
 				overlap_simple_sel = simple_or_sel * simple_sel;
 
@@ -1943,7 +1944,7 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli
 			 */
 			simple_sel = clauselist_selectivity_ext(root, stat_clauses,
 													varRelid, jointype,
-													sjinfo, false);
+													sjinfo, false, true);
 
 			/*
 			 * Multi-column estimate using MCV statistics, along with base and
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index e73fef057a4..42368e10b8e 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -2165,6 +2165,7 @@ typedef struct RestrictInfo
 	/* hash equality operators used for memoize nodes, else InvalidOid */
 	Oid			left_hasheqoperator;
 	Oid			right_hasheqoperator;
+	EquivalenceClass *derived;
 } RestrictInfo;
 
 /*
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 6b8ee0c69fa..a3385ae51ff 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -68,7 +68,8 @@ extern Selectivity clause_selectivity_ext(PlannerInfo *root,
 										  int varRelid,
 										  JoinType jointype,
 										  SpecialJoinInfo *sjinfo,
-										  bool use_extended_stats);
+										  bool use_extended_stats,
+										  bool include_derived);
 extern Selectivity clauselist_selectivity(PlannerInfo *root,
 										  List *clauses,
 										  int varRelid,
@@ -79,7 +80,8 @@ extern Selectivity clauselist_selectivity_ext(PlannerInfo *root,
 											  int varRelid,
 											  JoinType jointype,
 											  SpecialJoinInfo *sjinfo,
-											  bool use_extended_stats);
+											  bool use_extended_stats,
+											  bool include_derived);
 
 /* in path/costsize.c: */
 
-- 
2.21.0

v2-0004-Remove-duplicated-qual-executing-for-executor.patchapplication/octet-stream; name=v2-0004-Remove-duplicated-qual-executing-for-executor.patchDownload
From 11b3395bb5bcc4a2bcff6fed8078dbbf3cda81b1 Mon Sep 17 00:00:00 2001
From: Andy Fan <yizhi.fzh@alibaba-inc.com>
Date: Tue, 1 Feb 2022 17:37:27 +0800
Subject: [PATCH v2 4/6] Remove duplicated qual executing for executor.

Take the SELECT * FROM t1, t2 WHERE t1.a = t2.a and t2.a > 3 for example,
we can derive t1.a > 3 with EC filter infrastructure.  However if it generate a
plan like below, the new generated qual does not deserve to execute.

Nest Loop
   Seq Scan (t1.a > 3)
   Index Scan t2_a
      (a = t1.a) (t2.a > 3)

This patch remove the "t2.a > 3" for the above case.
---
 src/backend/optimizer/path/equivclass.c | 22 +++++++++++++++++++
 src/backend/optimizer/plan/createplan.c | 29 +++++++++++++++++++++++--
 src/include/optimizer/paths.h           |  2 ++
 src/test/regress/parallel_schedule      |  2 ++
 4 files changed, 53 insertions(+), 2 deletions(-)

diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 6ed9e8c9064..9ce0249b10d 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -3350,6 +3350,28 @@ is_redundant_derived_clause(RestrictInfo *rinfo, List *clauselist)
 	return false;
 }
 
+
+bool
+is_correlated_derived_clause(RestrictInfo *rinfo, List *clauselist)
+{
+	EquivalenceClass *derived_ec = rinfo->derived;
+	ListCell   *lc;
+
+	/* Fail if it's not a potentially-derived clause from some EC */
+	if (derived_ec == NULL)
+		return false;
+
+	foreach(lc, clauselist)
+	{
+		RestrictInfo *otherrinfo = (RestrictInfo *) lfirst(lc);
+
+		if (otherrinfo->parent_ec == derived_ec)
+			return true;
+	}
+
+	return false;
+}
+
 /*
  * is_redundant_with_indexclauses
  *		Test whether rinfo is redundant with any clause in the IndexClause
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index cd6d72c7633..03c5207ede0 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -556,11 +556,14 @@ static Plan *
 create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
 {
 	RelOptInfo *rel = best_path->parent;
-	List	   *scan_clauses;
+	List	   *scan_clauses = NIL;
 	List	   *gating_clauses;
 	List	   *tlist;
 	Plan	   *plan;
 
+	List	*ppi_clauses = best_path->param_info ? best_path->param_info->ppi_clauses : NIL;
+	ListCell	*lc;
+
 	/*
 	 * Extract the relevant restriction clauses from the parent relation. The
 	 * executor must apply all these restrictions during the scan, except for
@@ -591,8 +594,18 @@ create_scan_plan(PlannerInfo *root, Path *best_path, int flags)
 	 * For paranoia's sake, don't modify the stored baserestrictinfo list.
 	 */
 	if (best_path->param_info)
-		scan_clauses = list_concat_copy(scan_clauses,
+	{
+		List *stripped_quals = NIL;
+		foreach(lc, rel->baserestrictinfo)
+		{
+			RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
+
+			if (!is_correlated_derived_clause(rinfo, ppi_clauses))
+				stripped_quals = lappend(stripped_quals, rinfo);
+		}
+		scan_clauses = list_concat_copy(stripped_quals,
 										best_path->param_info->ppi_clauses);
+	}
 
 	/*
 	 * Detect whether we have any pseudoconstant quals to deal with.  Then, if
@@ -4912,6 +4925,7 @@ fix_indexqual_references(PlannerInfo *root, IndexPath *index_path,
 	List	   *stripped_indexquals;
 	List	   *fixed_indexquals;
 	ListCell   *lc;
+	List	*ppi_clauses = index_path->path.param_info ? index_path->path.param_info->ppi_clauses : NIL;
 
 	stripped_indexquals = fixed_indexquals = NIL;
 
@@ -4921,6 +4935,17 @@ fix_indexqual_references(PlannerInfo *root, IndexPath *index_path,
 		int			indexcol = iclause->indexcol;
 		ListCell   *lc2;
 
+		if (is_correlated_derived_clause(iclause->rinfo, ppi_clauses))
+		{
+			/*
+			 * bitmapscan will read this indexquals as well. so we can't just igrore
+			 * it for now. we can totally delete it.
+			 */
+			index_path->indexclauses = foreach_delete_current(index_path->indexclauses, lc);
+			continue;
+		}
+
+
 		foreach(lc2, iclause->indexquals)
 		{
 			RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc2);
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index ce2aac7d3aa..08d8612cdba 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -188,6 +188,8 @@ extern bool eclass_useful_for_merging(PlannerInfo *root,
 extern bool is_redundant_derived_clause(RestrictInfo *rinfo, List *clauselist);
 extern bool is_redundant_with_indexclauses(RestrictInfo *rinfo,
 										   List *indexclauses);
+extern bool is_correlated_derived_clause(RestrictInfo *rinfo, List *clauselist);
+
 
 /*
  * pathkeys.c
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 861c30a73ae..b071324b961 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -134,3 +134,5 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+test: ec_filter
\ No newline at end of file
-- 
2.21.0

v2-0001-Introudce-ec_filters-in-EquivalenceClass-struct-t.patchapplication/octet-stream; name=v2-0001-Introudce-ec_filters-in-EquivalenceClass-struct-t.patchDownload
From e0a7838a09e73f831eecb23b5e7884cc34d71301 Mon Sep 17 00:00:00 2001
From: David Rowley <dgrowleyml@gmail.com>
Date: Tue, 1 Feb 2022 20:56:40 +0800
Subject: [PATCH v2 1/6] Introudce ec_filters in EquivalenceClass struct, the
 semantics is the quals can

be applied to any EquivalenceMember in this EC. Later this information is used
to generate new RestrictInfo and was distributed to related RelOptInfo very
soon.

Author: David Rowley at 2015-12 [1]
Andy Fan rebase this patch to current latest code.

https://www.postgresql.org/message-id/CAKJS1f9FK_X_5HKcPcSeimy16Owe3EmPmmGsGWLcKkj_rW9s6A%40mail.gmail.com
---
 .../postgres_fdw/expected/postgres_fdw.out    |  36 ++--
 src/backend/nodes/outfuncs.c                  |  14 ++
 src/backend/optimizer/path/equivclass.c       | 182 ++++++++++++++++++
 src/backend/optimizer/plan/initsplan.c        |  96 +++++++--
 src/backend/utils/cache/lsyscache.c           |  28 +++
 src/include/nodes/nodes.h                     |   1 +
 src/include/nodes/pathnodes.h                 |  37 ++++
 src/include/optimizer/paths.h                 |   1 +
 src/include/utils/lsyscache.h                 |   1 +
 src/test/regress/expected/equivclass.out      |  45 ++++-
 src/test/regress/expected/join.out            |  22 +--
 src/test/regress/expected/partition_join.out  |  52 +++--
 src/test/regress/sql/equivclass.sql           |  12 ++
 13 files changed, 457 insertions(+), 70 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index b2e02caefe4..d89fd4e11ab 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -1545,12 +1545,12 @@ SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNE
 -- full outer join + inner join
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
-                                                                                                                                                 QUERY PLAN                                                                                                                                                 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                                           QUERY PLAN                                                                                                                                                                           
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Foreign Scan
    Output: t1.c1, t2.c1, t3.c1
    Relations: ((public.ft4 t1) INNER JOIN (public.ft5 t2)) FULL JOIN (public.ft4 t3)
-   Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint
+   Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND (((r2.c1 + 1) >= 50)) AND (((r2.c1 + 1) <= 60)) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
@@ -2335,12 +2335,12 @@ SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT
 UPDATE ft5 SET c3 = null where c1 % 9 = 0;
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
-                                                                                                                                QUERY PLAN                                                                                                                                 
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                    QUERY PLAN                                                                                                                                                     
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Foreign Scan
    Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2
    Relations: (public.ft5) INNER JOIN (public.ft4)
-   Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST
+   Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)) AND ((r1.c1 >= 10)) AND ((r1.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST
 (4 rows)
 
 SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
@@ -2362,8 +2362,8 @@ SET enable_hashjoin TO false;
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1
     AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE;
-                                                                                                                                                                                                                                                                                                                                                                                                                                               QUERY PLAN                                                                                                                                                                                                                                                                                                                                                                                                                                               
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                                                                                                                                                                                                                                                                                                                                      QUERY PLAN                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  LockRows
    Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ctid
    ->  Merge Join
@@ -2373,7 +2373,7 @@ SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = f
          ->  Foreign Scan
                Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
                Relations: (((public.ft1) INNER JOIN (public.ft2)) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
-               Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r4.c1, r4.c2, r4.c3, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4.c1, r4.c2, r4.c3) END FROM ((("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 3" r3 ON (((r1.c2 = r3.c1)))) INNER JOIN "S 1"."T 4" r4 ON (((r1.c2 = r4.c1)))) ORDER BY r1.c2 ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3 FOR UPDATE OF r4
+               Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r4.c1, r4.c2, r4.c3, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4.c1, r4.c2, r4.c3) END FROM ((("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" < 100)) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 3" r3 ON (((r1.c2 = r3.c1)))) INNER JOIN "S 1"."T 4" r4 ON (((r1.c2 = r4.c1)))) ORDER BY r1.c2 ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3 FOR UPDATE OF r4
                ->  Merge Join
                      Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
                      Merge Cond: (ft1.c2 = ft5.c1)
@@ -2391,12 +2391,12 @@ SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = f
                                              Sort Key: ft1.c1
                                              ->  Foreign Scan on public.ft1
                                                    Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
-                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE
+                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND (("C 1" < 100)) FOR UPDATE
                                        ->  Materialize
                                              Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
                                              ->  Foreign Scan on public.ft2
                                                    Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
-                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
+                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
                            ->  Sort
                                  Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
                                  Sort Key: ft4.c1
@@ -5685,25 +5685,25 @@ UPDATE ft2 AS target SET (c2) = (
 EXPLAIN (VERBOSE, COSTS OFF)
 UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END
   FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000;
-                                                                                                                                                                                       QUERY PLAN                                                                                                                                                                                        
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                                                                   QUERY PLAN                                                                                                                                                                                                    
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Update on public.ft2 d
    Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1
    ->  Foreign Scan
          Output: CASE WHEN (random() >= '0'::double precision) THEN d.c2 ELSE 0 END, d.ctid, d.*, t.*
          Relations: (public.ft2 d) INNER JOIN (public.ft2 t)
-         Remote SQL: SELECT r1.c2, r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r1."C 1" > 1000)))) FOR UPDATE OF r1
-         ->  Hash Join
+         Remote SQL: SELECT r1.c2, r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" > 1000)) AND ((r1."C 1" > 1000)))) FOR UPDATE OF r1
+         ->  Merge Join
                Output: d.c2, d.ctid, d.*, t.*
-               Hash Cond: (d.c1 = t.c1)
+               Merge Cond: (d.c1 = t.c1)
                ->  Foreign Scan on public.ft2 d
                      Output: d.c2, d.ctid, d.*, d.c1
                      Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
-               ->  Hash
+               ->  Materialize
                      Output: t.*, t.c1
                      ->  Foreign Scan on public.ft2 t
                            Output: t.*, t.c1
-                           Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
+                           Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST
 (17 rows)
 
 UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2b0236937aa..504b805326f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2504,6 +2504,17 @@ _outEquivalenceMember(StringInfo str, const EquivalenceMember *node)
 	WRITE_OID_FIELD(em_datatype);
 }
 
+static void
+_outEquivalenceFilter(StringInfo str, const EquivalenceFilter *node)
+{
+	WRITE_NODE_TYPE("EQUIVALENCEFILTER");
+
+	WRITE_NODE_FIELD(ef_const);
+	WRITE_OID_FIELD(ef_opno);
+	WRITE_BOOL_FIELD(ef_const_is_left);
+	WRITE_UINT_FIELD(ef_source_rel);
+}
+
 static void
 _outPathKey(StringInfo str, const PathKey *node)
 {
@@ -4304,6 +4315,9 @@ outNode(StringInfo str, const void *obj)
 			case T_EquivalenceMember:
 				_outEquivalenceMember(str, obj);
 				break;
+			case T_EquivalenceFilter:
+				_outEquivalenceFilter(str, obj);
+				break;
 			case T_PathKey:
 				_outPathKey(str, obj);
 				break;
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 8c6770de972..f9ae2785d60 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -19,6 +19,7 @@
 #include <limits.h>
 
 #include "access/stratnum.h"
+#include "catalog/pg_am.h"
 #include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -1250,6 +1251,37 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 	}
 }
 
+/*
+ * finds the opfamily and strategy number for the specified 'opno' and 'method'
+ * access method. Returns True if one is found and sets 'family' and
+ * 'amstrategy', or returns False if none are found.
+ */
+static bool
+find_am_family_and_stategy(Oid opno, Oid method, Oid *family, int *amstrategy)
+{
+	List *opfamilies;
+	ListCell *l;
+	int strategy;
+
+	opfamilies = get_opfamilies(opno, method);
+
+	foreach(l, opfamilies)
+	{
+		Oid opfamily = lfirst_oid(l);
+
+		strategy = get_op_opfamily_strategy(opno, opfamily);
+
+		if (strategy)
+		{
+			*amstrategy = strategy;
+			*family = opfamily;
+			return true;
+		}
+	}
+
+	return false;
+}
+
 /*
  * generate_base_implied_equalities when EC contains no pseudoconstants
  */
@@ -1259,6 +1291,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 {
 	EquivalenceMember **prev_ems;
 	ListCell   *lc;
+	ListCell   *lc2;
 
 	/*
 	 * We scan the EC members once and track the last-seen member for each
@@ -1320,6 +1353,57 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 				rinfo->right_em = cur_em;
 			}
 		}
+
+		/*
+		 * Also push any EquivalenceFilter clauses down into all relations
+		 * other than the one which the filter actually originated from.
+		 */
+		foreach(lc2, ec->ec_filters)
+		{
+			EquivalenceFilter *ef = (EquivalenceFilter *) lfirst(lc2);
+			Expr *leftexpr;
+			Expr *rightexpr;
+			int strategy;
+			Oid opno;
+			Oid family;
+
+			if (ef->ef_source_rel == relid)
+				continue;
+
+			if (!find_am_family_and_stategy(ef->ef_opno, BTREE_AM_OID,
+				&family, &strategy))
+				continue;
+
+			if (ef->ef_const_is_left)
+			{
+				leftexpr = (Expr *) ef->ef_const;
+				rightexpr = cur_em->em_expr;
+			}
+			else
+			{
+				leftexpr = cur_em->em_expr;
+				rightexpr = (Expr *) ef->ef_const;
+			}
+
+			opno = get_opfamily_member(family,
+										exprType((Node *) leftexpr),
+										exprType((Node *) rightexpr),
+										strategy);
+
+			if (opno == InvalidOid)
+				continue;
+
+			process_implied_equality(root, opno,
+										ec->ec_collation,
+										leftexpr,
+										rightexpr,
+										bms_copy(ec->ec_relids),
+										bms_copy(cur_em->em_nullable_relids),
+									 	ec->ec_min_security,
+										ec->ec_below_outer_join,
+										false);
+		}
+
 		prev_ems[relid] = cur_em;
 	}
 
@@ -1901,6 +1985,104 @@ create_join_clause(PlannerInfo *root,
 	return rinfo;
 }
 
+/*
+ * distribute_filter_quals_to_eclass
+ *		For each OpExpr in quallist look for an eclass which has an Expr
+ *		matching the Expr in the OpExpr. If a match is found we add a new
+ *		EquivalenceFilter to the eclass containing the filter details.
+ */
+void
+distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
+{
+	ListCell *l;
+
+	/* fast path for when no eclasses have been generated */
+	if (root->eq_classes == NIL)
+		return;
+
+	/*
+	 * For each qual in quallist try and find an eclass which contains the
+	 * non-Const part of the OpExpr. We'll tag any matches that we find onto
+	 * the correct eclass.
+	 */
+	foreach(l, quallist)
+	{
+		OpExpr	   *opexpr = (OpExpr *) lfirst(l);
+		Expr	   *leftexpr = (Expr *) linitial(opexpr->args);
+		Expr	   *rightexpr = (Expr *) lsecond(opexpr->args);
+		Const	   *constexpr;
+		Expr	   *varexpr;
+		Relids		exprrels;
+		int			relid;
+		bool		const_isleft;
+		ListCell *l2;
+
+		/*
+		 * Determine if the the OpExpr is in the form "expr op const" or
+		 * "const op expr".
+		 */
+		if (IsA(leftexpr, Const))
+		{
+			constexpr = (Const *) leftexpr;
+			varexpr = rightexpr;
+			const_isleft = true;
+		}
+		else
+		{
+			constexpr = (Const *) rightexpr;
+			varexpr = leftexpr;
+			const_isleft = false;
+		}
+
+		exprrels = pull_varnos(root, (Node *) varexpr);
+
+		/* should be filtered out, but we need to determine relid anyway */
+		if (!bms_get_singleton_member(exprrels, &relid))
+			continue;
+
+		/* search for a matching eclass member in all eclasses */
+		foreach(l2, root->eq_classes)
+		{
+			EquivalenceClass *ec = (EquivalenceClass *) lfirst(l2);
+			ListCell *l3;
+
+			if (ec->ec_broken || ec->ec_has_volatile)
+				continue;
+
+			/*
+			 * if the eclass has a const then that const will serve as the
+			 * filter, we needn't add any others.
+			 */
+			if (ec->ec_has_const)
+				continue;
+
+			/* skip this eclass no members exist which belong to this relid */
+			if (!bms_is_member(relid, ec->ec_relids))
+				continue;
+
+			foreach(l3, ec->ec_members)
+			{
+				EquivalenceMember *em = (EquivalenceMember *) lfirst(l3);
+
+				if (!bms_is_member(relid, em->em_relids))
+					continue;
+
+				if (equal(em->em_expr, varexpr))
+				{
+					EquivalenceFilter *efilter;
+					efilter = makeNode(EquivalenceFilter);
+					efilter->ef_const = (Const *) copyObject(constexpr);
+					efilter->ef_const_is_left = const_isleft;
+					efilter->ef_opno = opexpr->opno;
+					efilter->ef_source_rel = relid;
+
+					ec->ec_filters = lappend(ec->ec_filters, efilter);
+					break;		/* Onto the next eclass */
+				}
+			}
+		}
+	}
+}
 
 /*
  * reconsider_outer_join_clauses
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 023efbaf092..b5204b3c4f4 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -53,7 +53,7 @@ static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel,
 static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
 								 bool below_outer_join,
 								 Relids *qualscope, Relids *inner_join_rels,
-								 List **postponed_qual_list);
+								 List **postponed_qual_list, List **filter_qual_list);
 static void process_security_barrier_quals(PlannerInfo *root,
 										   int rti, Relids qualscope,
 										   bool below_outer_join);
@@ -70,7 +70,8 @@ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 									Relids qualscope,
 									Relids ojscope,
 									Relids outerjoin_nonnullable,
-									List **postponed_qual_list);
+									List **postponed_qual_list,
+									List **filter_qual_list);
 static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p,
 								  Relids *nullable_relids_p, bool is_pushed_down);
 static bool check_equivalence_delay(PlannerInfo *root,
@@ -650,6 +651,43 @@ create_lateral_join_info(PlannerInfo *root)
 	}
 }
 
+/*
+ * is_simple_filter_qual
+ *             Analyzes an OpExpr to determine if it may be useful as an
+ *             EquivalenceFilter. Returns true if the OpExpr may be of some use, or
+ *             false if it should not be used.
+ */
+static bool
+is_simple_filter_qual(PlannerInfo *root, OpExpr *expr)
+{
+	Expr *leftexpr;
+	Expr *rightexpr;
+
+	if (!IsA(expr, OpExpr))
+			return false;
+
+	if (list_length(expr->args) != 2)
+			return false;
+
+	leftexpr = (Expr *) linitial(expr->args);
+	rightexpr = (Expr *) lsecond(expr->args);
+
+	/* XXX should we restrict these to simple Var op Const expressions? */
+	if (IsA(leftexpr, Const))
+	{
+		if (bms_membership(pull_varnos(root, (Node *) rightexpr)) == BMS_SINGLETON &&
+			!contain_volatile_functions((Node *) rightexpr))
+			return true;
+	}
+	else if (IsA(rightexpr, Const))
+	{
+		if (bms_membership(pull_varnos(root, (Node *) leftexpr)) == BMS_SINGLETON &&
+			!contain_volatile_functions((Node *) leftexpr))
+			return true;
+	}
+
+	return false;
+}
 
 /*****************************************************************************
  *
@@ -690,6 +728,7 @@ deconstruct_jointree(PlannerInfo *root)
 	Relids		qualscope;
 	Relids		inner_join_rels;
 	List	   *postponed_qual_list = NIL;
+	List	   *filter_qual_list = NIL;
 
 	/* Start recursion at top of jointree */
 	Assert(root->parse->jointree != NULL &&
@@ -700,11 +739,14 @@ deconstruct_jointree(PlannerInfo *root)
 
 	result = deconstruct_recurse(root, (Node *) root->parse->jointree, false,
 								 &qualscope, &inner_join_rels,
-								 &postponed_qual_list);
+								 &postponed_qual_list, &filter_qual_list);
 
 	/* Shouldn't be any leftover quals */
 	Assert(postponed_qual_list == NIL);
 
+	/* try and match each filter_qual_list item up with an eclass. */
+	distribute_filter_quals_to_eclass(root, filter_qual_list);
+
 	return result;
 }
 
@@ -725,6 +767,8 @@ deconstruct_jointree(PlannerInfo *root)
  *		or free this, either)
  *	*postponed_qual_list is a list of PostponedQual structs, which we can
  *		add quals to if they turn out to belong to a higher join level
+ *	*filter_qual_list is appended to with a list of quals which may be useful
+ *		include as EquivalenceFilters.
  *	Return value is the appropriate joinlist for this jointree node
  *
  * In addition, entries will be added to root->join_info_list for outer joins.
@@ -732,7 +776,7 @@ deconstruct_jointree(PlannerInfo *root)
 static List *
 deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 					Relids *qualscope, Relids *inner_join_rels,
-					List **postponed_qual_list)
+					List **postponed_qual_list, List **filter_qual_list)
 {
 	List	   *joinlist;
 
@@ -785,7 +829,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 											   below_outer_join,
 											   &sub_qualscope,
 											   inner_join_rels,
-											   &child_postponed_quals);
+											   &child_postponed_quals,
+											   filter_qual_list);
 			*qualscope = bms_add_members(*qualscope, sub_qualscope);
 			sub_members = list_length(sub_joinlist);
 			remaining--;
@@ -819,7 +864,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 										below_outer_join, JOIN_INNER,
 										root->qual_security_level,
 										*qualscope, NULL, NULL,
-										NULL);
+										NULL,
+										filter_qual_list);
 			else
 				*postponed_qual_list = lappend(*postponed_qual_list, pq);
 		}
@@ -835,7 +881,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 									below_outer_join, JOIN_INNER,
 									root->qual_security_level,
 									*qualscope, NULL, NULL,
-									postponed_qual_list);
+									postponed_qual_list,
+									filter_qual_list);
 		}
 	}
 	else if (IsA(jtnode, JoinExpr))
@@ -873,11 +920,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													below_outer_join,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = *qualscope;
 				/* Inner join adds no restrictions for quals */
@@ -890,11 +939,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													true,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = bms_union(left_inners, right_inners);
 				nonnullable_rels = leftids;
@@ -904,11 +955,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													below_outer_join,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = bms_union(left_inners, right_inners);
 				/* Semi join adds no restrictions for quals */
@@ -925,11 +978,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   true,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													true,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = bms_union(left_inners, right_inners);
 				/* each side is both outer and inner */
@@ -1013,7 +1068,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 									root->qual_security_level,
 									*qualscope,
 									ojscope, nonnullable_rels,
-									postponed_qual_list);
+									postponed_qual_list,
+									filter_qual_list);
 		}
 
 		/* Now we can add the SpecialJoinInfo to join_info_list */
@@ -1117,6 +1173,7 @@ process_security_barrier_quals(PlannerInfo *root,
 									qualscope,
 									qualscope,
 									NULL,
+									NULL,
 									NULL);
 		}
 		security_level++;
@@ -1610,7 +1667,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 						Relids qualscope,
 						Relids ojscope,
 						Relids outerjoin_nonnullable,
-						List **postponed_qual_list)
+						List **postponed_qual_list,
+						List **filter_qual_list)
 {
 	Relids		relids;
 	bool		is_pushed_down;
@@ -1964,6 +2022,10 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 
 	/* No EC special case applies, so push it into the clause lists */
 	distribute_restrictinfo_to_rels(root, restrictinfo);
+
+	/* Check if the qual looks useful to harvest as an EquivalenceFilter */
+	if (filter_qual_list != NULL && is_simple_filter_qual(root, (OpExpr *) clause))
+		*filter_qual_list = lappend(*filter_qual_list, clause);
 }
 
 /*
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index feef9998634..add2e7176ae 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -341,6 +341,34 @@ get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type)
 	return result;
 }
 
+/*
+ * get_opfamilies
+ *		Returns a list of Oids of each opfamily which 'opno' belonging to
+ *		'method' access method.
+ */
+List *
+get_opfamilies(Oid opno, Oid method)
+{
+	List	   *result = NIL;
+	CatCList   *catlist;
+	int			i;
+
+	catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno));
+
+	for (i = 0; i < catlist->n_members; i++)
+	{
+		HeapTuple	tuple = &catlist->members[i]->tuple;
+		Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
+
+		if (aform->amopmethod == method)
+			result = lappend_oid(result, aform->amopfamily);
+	}
+
+	ReleaseSysCacheList(catlist);
+
+	return result;
+}
+
 /*
  * get_mergejoin_opfamilies
  *		Given a putatively mergejoinable operator, return a list of the OIDs
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index da35f2c2722..3a9a235cd0e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
 	/* these aren't subclasses of Path: */
 	T_EquivalenceClass,
 	T_EquivalenceMember,
+	T_EquivalenceFilter,
 	T_PathKey,
 	T_PathTarget,
 	T_RestrictInfo,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 1f3845b3fec..e73fef057a4 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -990,6 +990,7 @@ typedef struct EquivalenceClass
 	List	   *ec_members;		/* list of EquivalenceMembers */
 	List	   *ec_sources;		/* list of generating RestrictInfos */
 	List	   *ec_derives;		/* list of derived RestrictInfos */
+	List	   *ec_filters;
 	Relids		ec_relids;		/* all relids appearing in ec_members, except
 								 * for child members (see below) */
 	bool		ec_has_const;	/* any pseudoconstants in ec_members? */
@@ -1002,6 +1003,42 @@ typedef struct EquivalenceClass
 	struct EquivalenceClass *ec_merged; /* set if merged into another EC */
 } EquivalenceClass;
 
+/*
+ * EquivalenceFilter - List of filters on Consts which belong to the
+ * EquivalenceClass.
+ *
+ * When building the equivalence classes we also collected a list of quals in
+ * the form of; "Expr op Const" and "Const op Expr". These are collected in the
+ * hope that we'll later generate an equivalence class which contains the
+ * "Expr" part. For example, if we parse a query such as;
+ *
+ *		SELECT * FROM t1 INNER JOIN t2 ON t1.id = t2.id WHERE t1.id < 10;
+ *
+ * then since we'll end up with an equivalence class containing {t1.id,t2.id},
+ * we'll tag the "< 10" filter onto the eclass. We are able to do this because
+ * the eclass proves equality between each class member, therefore all members
+ * must be below 10.
+ *
+ * EquivalenceFilters store the details required to allow us to push these
+ * filter clauses down into other relations which share an equivalence class
+ * containing a member which matches the expression of this EquivalenceFilter.
+ *
+ * ef_const is the Const value which this filter should filter against.
+ * ef_opno is the operator to filter on.
+ * ef_const_is_left marks if the OpExpr was in the form "Const op Expr" or
+ * "Expr op Const".
+ * ef_source_rel is the relation id of where this qual originated from.
+ */
+typedef struct EquivalenceFilter
+{
+	NodeTag		type;
+
+	Const	   *ef_const;		/* the constant expression to filter on */
+	Oid			ef_opno;		/* Operator Oid of filter operator */
+	bool		ef_const_is_left; /* Is the Const on the left of the OpExrp? */
+	Index		ef_source_rel;	/* relid of originating relation. */
+} EquivalenceFilter;
+
 /*
  * If an EC contains a const and isn't below-outer-join, any PathKey depending
  * on it must be redundant, since there's only one possible value of the key.
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 0c3a0b90c85..ce2aac7d3aa 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -126,6 +126,7 @@ extern bool process_equivalence(PlannerInfo *root,
 extern Expr *canonicalize_ec_expression(Expr *expr,
 										Oid req_type, Oid req_collation);
 extern void reconsider_outer_join_clauses(PlannerInfo *root);
+extern void distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist);
 extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root,
 												  Expr *expr,
 												  Relids nullable_relids,
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index b8dd27d4a96..188d65faa0d 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -78,6 +78,7 @@ extern bool get_ordering_op_properties(Oid opno,
 									   Oid *opfamily, Oid *opcintype, int16 *strategy);
 extern Oid	get_equality_op_for_ordering_op(Oid opno, bool *reverse);
 extern Oid	get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type);
+extern List *get_opfamilies(Oid opno, Oid method);
 extern List *get_mergejoin_opfamilies(Oid opno);
 extern bool get_compatible_hash_operators(Oid opno,
 										  Oid *lhs_opno, Oid *rhs_opno);
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index 126f7047fed..92fcec1158b 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -407,14 +407,14 @@ set session authorization regress_user_ectest;
 explain (costs off)
   select * from ec0 a, ec1 b
   where a.ff = b.ff and a.ff = 43::bigint::int8alias1;
-                 QUERY PLAN                  
----------------------------------------------
+                              QUERY PLAN                              
+----------------------------------------------------------------------
  Nested Loop
    ->  Index Scan using ec0_pkey on ec0 a
          Index Cond: (ff = '43'::int8alias1)
    ->  Index Scan using ec1_pkey on ec1 b
          Index Cond: (ff = a.ff)
-         Filter: (f1 < '5'::int8alias1)
+         Filter: ((f1 < '5'::int8alias1) AND (ff = '43'::int8alias1))
 (6 rows)
 
 reset session authorization;
@@ -451,3 +451,42 @@ explain (costs off)  -- this should not require a sort
    Filter: (f1 = 'foo'::name)
 (2 rows)
 
+-- test equivalence filters
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec0.ff between 1 and 10;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Nested Loop
+   Join Filter: (ec0.ff = ec1.ff)
+   ->  Bitmap Heap Scan on ec0
+         Recheck Cond: ((ff >= 1) AND (ff <= 10))
+         ->  Bitmap Index Scan on ec0_pkey
+               Index Cond: ((ff >= 1) AND (ff <= 10))
+   ->  Materialize
+         ->  Bitmap Heap Scan on ec1
+               Recheck Cond: ((ff >= 1) AND (ff <= 10))
+               ->  Bitmap Index Scan on ec1_pkey
+                     Index Cond: ((ff >= 1) AND (ff <= 10))
+(11 rows)
+
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec1.ff between 1 and 10;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Nested Loop
+   Join Filter: (ec0.ff = ec1.ff)
+   ->  Bitmap Heap Scan on ec0
+         Recheck Cond: ((ff >= 1) AND (ff <= 10))
+         ->  Bitmap Index Scan on ec0_pkey
+               Index Cond: ((ff >= 1) AND (ff <= 10))
+   ->  Materialize
+         ->  Bitmap Heap Scan on ec1
+               Recheck Cond: ((ff >= 1) AND (ff <= 10))
+               ->  Bitmap Index Scan on ec1_pkey
+                     Index Cond: ((ff >= 1) AND (ff <= 10))
+(11 rows)
+
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index d5b5b775fdd..b20de6a505f 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -3337,7 +3337,7 @@ where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
    Join Filter: (t1.stringu1 > t2.stringu2)
    ->  Nested Loop
          ->  Seq Scan on int4_tbl i1
-               Filter: (f1 = 0)
+               Filter: ((f1 = 0) AND (11 < 42))
          ->  Index Scan using tenk1_unique2 on tenk1 t1
                Index Cond: ((unique2 = (11)) AND (unique2 < 42))
    ->  Index Scan using tenk1_unique1 on tenk1 t2
@@ -6550,23 +6550,22 @@ where exists (select 1 from tenk1 t3
 ---------------------------------------------------------------------------------
  Nested Loop
    Output: t1.unique1, t2.hundred
-   ->  Hash Join
+   ->  Nested Loop
          Output: t1.unique1, t3.tenthous
-         Hash Cond: (t3.thousand = t1.unique1)
+         Join Filter: (t1.unique1 = t3.thousand)
+         ->  Index Only Scan using onek_unique1 on public.onek t1
+               Output: t1.unique1
+               Index Cond: (t1.unique1 < 1)
          ->  HashAggregate
                Output: t3.thousand, t3.tenthous
                Group Key: t3.thousand, t3.tenthous
                ->  Index Only Scan using tenk1_thous_tenthous on public.tenk1 t3
                      Output: t3.thousand, t3.tenthous
-         ->  Hash
-               Output: t1.unique1
-               ->  Index Only Scan using onek_unique1 on public.onek t1
-                     Output: t1.unique1
-                     Index Cond: (t1.unique1 < 1)
+                     Index Cond: (t3.thousand < 1)
    ->  Index Only Scan using tenk1_hundred on public.tenk1 t2
          Output: t2.hundred
          Index Cond: (t2.hundred = t3.tenthous)
-(18 rows)
+(17 rows)
 
 -- ... unless it actually is unique
 create table j3 as select unique1, tenthous from onek;
@@ -6584,15 +6583,16 @@ where exists (select 1 from j3
    Output: t1.unique1, t2.hundred
    ->  Nested Loop
          Output: t1.unique1, j3.tenthous
+         Join Filter: (t1.unique1 = j3.unique1)
          ->  Index Only Scan using onek_unique1 on public.onek t1
                Output: t1.unique1
                Index Cond: (t1.unique1 < 1)
          ->  Index Only Scan using j3_unique1_tenthous_idx on public.j3
                Output: j3.unique1, j3.tenthous
-               Index Cond: (j3.unique1 = t1.unique1)
+               Index Cond: (j3.unique1 < 1)
    ->  Index Only Scan using tenk1_hundred on public.tenk1 t2
          Output: t2.hundred
          Index Cond: (t2.hundred = j3.tenthous)
-(13 rows)
+(14 rows)
 
 drop table j3;
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index bb5b7c47a45..5a2923bac6c 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -186,17 +186,17 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0)
 -- Join with pruned partitions from joining relations
 EXPLAIN (COSTS OFF)
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
-                     QUERY PLAN                      
------------------------------------------------------
+                            QUERY PLAN                             
+-------------------------------------------------------------------
  Sort
    Sort Key: t1.a
    ->  Hash Join
          Hash Cond: (t2.b = t1.a)
          ->  Seq Scan on prt2_p2 t2
-               Filter: (b > 250)
+               Filter: ((b > 250) AND (b < 450))
          ->  Hash
                ->  Seq Scan on prt1_p2 t1
-                     Filter: ((a < 450) AND (b = 0))
+                     Filter: ((a < 450) AND (a > 250) AND (b = 0))
 (9 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
@@ -3100,16 +3100,18 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
          ->  Hash Join
                Hash Cond: (t2_1.b = t1_1.a)
                ->  Seq Scan on prt2_adv_p1 t2_1
+                     Filter: (b < 300)
                ->  Hash
                      ->  Seq Scan on prt1_adv_p1 t1_1
                            Filter: ((a < 300) AND (b = 0))
          ->  Hash Join
                Hash Cond: (t2_2.b = t1_2.a)
                ->  Seq Scan on prt2_adv_p2 t2_2
+                     Filter: (b < 300)
                ->  Hash
                      ->  Seq Scan on prt1_adv_p2 t1_2
                            Filter: ((a < 300) AND (b = 0))
-(15 rows)
+(17 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b;
   a  |  c   |  b  |  c   
@@ -3139,16 +3141,19 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
          ->  Hash Join
                Hash Cond: (t2_1.b = t1_1.a)
                ->  Seq Scan on prt2_adv_p1 t2_1
+                     Filter: ((b >= 100) AND (b < 300))
                ->  Hash
                      ->  Seq Scan on prt1_adv_p1 t1_1
                            Filter: ((a >= 100) AND (a < 300) AND (b = 0))
-         ->  Hash Join
-               Hash Cond: (t2_2.b = t1_2.a)
-               ->  Seq Scan on prt2_adv_p2 t2_2
-               ->  Hash
+         ->  Merge Join
+               Merge Cond: (t2_2.b = t1_2.a)
+               ->  Index Scan using prt2_adv_p2_b_idx on prt2_adv_p2 t2_2
+                     Index Cond: ((b >= 100) AND (b < 300))
+               ->  Sort
+                     Sort Key: t1_2.a
                      ->  Seq Scan on prt1_adv_p2 t1_2
                            Filter: ((a >= 100) AND (a < 300) AND (b = 0))
-(15 rows)
+(18 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a >= 100 AND t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b;
   a  |  c   |  b  |  c   
@@ -4692,27 +4697,32 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
                      Filter: ((b >= 125) AND (b < 225))
                ->  Hash
                      ->  Seq Scan on beta_neg_p1 t2_1
+                           Filter: ((b >= 125) AND (b < 225))
          ->  Hash Join
-               Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.b = t1_2.b))
-               ->  Seq Scan on beta_neg_p2 t2_2
+               Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.b = t2_2.b))
+               ->  Seq Scan on alpha_neg_p2 t1_2
+                     Filter: ((b >= 125) AND (b < 225))
                ->  Hash
-                     ->  Seq Scan on alpha_neg_p2 t1_2
+                     ->  Seq Scan on beta_neg_p2 t2_2
                            Filter: ((b >= 125) AND (b < 225))
          ->  Hash Join
-               Hash Cond: ((t2_4.a = t1_4.a) AND (t2_4.b = t1_4.b))
+               Hash Cond: ((t1_4.a = t2_4.a) AND (t1_4.b = t2_4.b))
                ->  Append
-                     ->  Seq Scan on beta_pos_p1 t2_4
-                     ->  Seq Scan on beta_pos_p2 t2_5
-                     ->  Seq Scan on beta_pos_p3 t2_6
+                     ->  Seq Scan on alpha_pos_p1 t1_4
+                           Filter: ((b >= 125) AND (b < 225))
+                     ->  Seq Scan on alpha_pos_p2 t1_5
+                           Filter: ((b >= 125) AND (b < 225))
+                     ->  Seq Scan on alpha_pos_p3 t1_6
+                           Filter: ((b >= 125) AND (b < 225))
                ->  Hash
                      ->  Append
-                           ->  Seq Scan on alpha_pos_p1 t1_4
+                           ->  Seq Scan on beta_pos_p1 t2_4
                                  Filter: ((b >= 125) AND (b < 225))
-                           ->  Seq Scan on alpha_pos_p2 t1_5
+                           ->  Seq Scan on beta_pos_p2 t2_5
                                  Filter: ((b >= 125) AND (b < 225))
-                           ->  Seq Scan on alpha_pos_p3 t1_6
+                           ->  Seq Scan on beta_pos_p3 t2_6
                                  Filter: ((b >= 125) AND (b < 225))
-(29 rows)
+(34 rows)
 
 SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b) WHERE t1.b >= 125 AND t1.b < 225 ORDER BY t1.a, t1.b;
  a  |  b  |  c   | a  |  b  |  c   
diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql
index 247b0a31055..dae83c41965 100644
--- a/src/test/regress/sql/equivclass.sql
+++ b/src/test/regress/sql/equivclass.sql
@@ -269,3 +269,15 @@ create temp view overview as
   select f1::information_schema.sql_identifier as sqli, f2 from undername;
 explain (costs off)  -- this should not require a sort
   select * from overview where sqli = 'foo' order by sqli;
+
+
+-- test equivalence filters
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec0.ff between 1 and 10;
+
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec1.ff between 1 and 10;
-- 
2.21.0

v2-0006-Added-the-testcase-for-this-feature-and-fix-the-p.patchapplication/octet-stream; name=v2-0006-Added-the-testcase-for-this-feature-and-fix-the-p.patchDownload
From a4b21ab6fd0fd57902f5471ec962a77b59085158 Mon Sep 17 00:00:00 2001
From: Andy Fan <yizhi.fzh@alibaba-inc.com>
Date: Wed, 2 Feb 2022 11:59:53 +0800
Subject: [PATCH v2 6/6] Added the testcase for this feature and fix the
 previous test case

as well. The new added test case needs outputting some runtime
statistics, which probably be different at each run. I can think
of a way to make the test case stable if the patchsets is not wrong
at the first step.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  15 +-
 src/test/regress/expected/ec_filter.out       | 137 ++++++++++++++++++
 src/test/regress/expected/equivclass.out      |  38 ++---
 src/test/regress/expected/join.out            |  43 +++---
 src/test/regress/expected/partition_join.out  |  54 ++++---
 src/test/regress/expected/partition_prune.out |  56 +------
 src/test/regress/sql/ec_filter.sql            |  46 ++++++
 7 files changed, 267 insertions(+), 122 deletions(-)
 create mode 100644 src/test/regress/expected/ec_filter.out
 create mode 100644 src/test/regress/sql/ec_filter.sql

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index d89fd4e11ab..41d0cd414cd 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2335,13 +2335,16 @@ SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT
 UPDATE ft5 SET c3 = null where c1 % 9 = 0;
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
-                                                                                                                                                    QUERY PLAN                                                                                                                                                     
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Foreign Scan
+                                                                                                                                        QUERY PLAN                                                                                                                                         
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Sort
    Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2
-   Relations: (public.ft5) INNER JOIN (public.ft4)
-   Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)) AND ((r1.c1 >= 10)) AND ((r1.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST
-(4 rows)
+   Sort Key: ft5.c1
+   ->  Foreign Scan
+         Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2
+         Relations: (public.ft5) INNER JOIN (public.ft4)
+         Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)) AND ((r1.c1 >= 10)) AND ((r1.c1 <= 30))))
+(7 rows)
 
 SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
       ft5       | c1 | c2 |   c3   | c1 | c2 
diff --git a/src/test/regress/expected/ec_filter.out b/src/test/regress/expected/ec_filter.out
new file mode 100644
index 00000000000..2ee670af1ec
--- /dev/null
+++ b/src/test/regress/expected/ec_filter.out
@@ -0,0 +1,137 @@
+-- THIS TEST IS JUST FOW REVIEW PURPOSE, IT CONTAINS TOO MANY RUNTIME STATS.
+set enable_hashjoin to off;
+set enable_mergejoin to off;
+set enable_seqscan to on;
+-- 1. The joinrel.rows should be correct.
+-- 2. The path estimated rows for b should be correct.
+-- 3. the estimated rows from a in wrong, but that is not the fault of this patch.
+explain analyze select * from tenk1 a, tenk1 b where a.thousand = b.thousand and a.thousand < 100;
+                                                                  QUERY PLAN                                                                   
+-----------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop  (cost=24.90..459.16 rows=10740 width=488) (actual time=0.418..15.061 rows=10000 loops=1)
+   ->  Bitmap Heap Scan on tenk1 b  (cost=24.61..383.03 rows=1074 width=244) (actual time=0.368..1.427 rows=1000 loops=1)
+         Recheck Cond: (thousand < 100)
+         Heap Blocks: exact=324
+         ->  Bitmap Index Scan on tenk1_thous_tenthous  (cost=0.00..24.34 rows=1074 width=0) (actual time=0.256..0.257 rows=1000 loops=1)
+               Index Cond: (thousand < 100)
+   ->  Memoize  (cost=0.30..0.47 rows=1 width=244) (actual time=0.002..0.006 rows=10 loops=1000)
+         Cache Key: b.thousand
+         Cache Mode: logical
+         Hits: 900  Misses: 100  Evictions: 0  Overflows: 0  Memory Usage: 277kB
+         ->  Index Scan using tenk1_thous_tenthous on tenk1 a  (cost=0.29..0.46 rows=1 width=244) (actual time=0.010..0.030 rows=10 loops=100)
+               Index Cond: ((thousand = b.thousand) AND (thousand < 100))
+ Planning Time: 2.314 ms
+ Execution Time: 16.022 ms
+(14 rows)
+
+create table d_tenk2 as select * from tenk1 union all select * from tenk1;
+create index on d_tenk2(thousand);
+analyze d_tenk2 ;
+set enable_seqscan to off;
+drop index tenk1_thous_tenthous;
+-- 1. The parameterized path of b shows the correct rows estimation as well.
+-- 2. The new added qual is fully duplicated with the join clause, so it should be removed.
+explain analyze select * from tenk1 a join d_tenk2 b on a.thousand = b.thousand and a.thousand < 100;
+                                                                    QUERY PLAN                                                                    
+--------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop  (cost=10000000000.30..10000002799.78 rows=20020 width=488) (actual time=0.045..26.640 rows=20000 loops=1)
+   ->  Seq Scan on tenk1 a  (cost=10000000000.00..10000000470.00 rows=1001 width=244) (actual time=0.015..3.923 rows=1000 loops=1)
+         Filter: (thousand < 100)
+         Rows Removed by Filter: 9000
+   ->  Memoize  (cost=0.30..3.18 rows=20 width=244) (actual time=0.002..0.008 rows=20 loops=1000)
+         Cache Key: a.thousand
+         Cache Mode: logical
+         Hits: 900  Misses: 100  Evictions: 0  Overflows: 0  Memory Usage: 546kB
+         ->  Index Scan using d_tenk2_thousand_idx on d_tenk2 b  (cost=0.29..3.17 rows=20 width=244) (actual time=0.008..0.038 rows=20 loops=100)
+               Index Cond: (thousand = a.thousand)
+ Planning Time: 0.829 ms
+ Execution Time: 28.033 ms
+(12 rows)
+
+create index tenk1_thous_tenthous on tenk1(thousand, tenthous);
+-- test ef filter with partition prune.
+create table p (a int, b int) partition by range(a);
+select 'create table p_' || i || ' partition of p  for values from (' || (i-1) * 100000 || ') to (' || i * 100000 || ');' from generate_series(1, 5)i;  \gexec
+                                ?column?                                
+------------------------------------------------------------------------
+ create table p_1 partition of p  for values from (0) to (100000);
+ create table p_2 partition of p  for values from (100000) to (200000);
+ create table p_3 partition of p  for values from (200000) to (300000);
+ create table p_4 partition of p  for values from (300000) to (400000);
+ create table p_5 partition of p  for values from (400000) to (500000);
+(5 rows)
+
+create table p_1 partition of p  for values from (0) to (100000);
+create table p_2 partition of p  for values from (100000) to (200000);
+create table p_3 partition of p  for values from (200000) to (300000);
+create table p_4 partition of p  for values from (300000) to (400000);
+create table p_5 partition of p  for values from (400000) to (500000);
+insert into p select i, i from generate_series(1, 5 * 100000 -1) i;
+create table q (a int, b int) partition by range(a);
+select 'create table q_' || i || ' partition of q  for values from (' || (i-1) * 100000 || ') to (' || i * 100000 || ');' from generate_series(1, 5)i;  \gexec
+                                ?column?                                
+------------------------------------------------------------------------
+ create table q_1 partition of q  for values from (0) to (100000);
+ create table q_2 partition of q  for values from (100000) to (200000);
+ create table q_3 partition of q  for values from (200000) to (300000);
+ create table q_4 partition of q  for values from (300000) to (400000);
+ create table q_5 partition of q  for values from (400000) to (500000);
+(5 rows)
+
+create table q_1 partition of q  for values from (0) to (100000);
+create table q_2 partition of q  for values from (100000) to (200000);
+create table q_3 partition of q  for values from (200000) to (300000);
+create table q_4 partition of q  for values from (300000) to (400000);
+create table q_5 partition of q  for values from (400000) to (500000);
+insert into q select * from p;
+create index on q(a);
+select * from p, q where p.a = q.a and p.a > 5 * 100000 - 3;
+   a    |   b    |   a    |   b    
+--------+--------+--------+--------
+ 499998 | 499998 | 499998 | 499998
+ 499999 | 499999 | 499999 | 499999
+(2 rows)
+
+set plan_cache_mode to force_generic_plan ;
+prepare s as select * from p, q where p.a = q.a and p.a > $1;
+explain (costs off) execute s(5 * 100000 - 3);
+                    QUERY PLAN                     
+---------------------------------------------------
+ Nested Loop
+   ->  Append
+         Subplans Removed: 4
+         ->  Seq Scan on p_5 p_1
+               Filter: (a > $1)
+   ->  Append
+         Subplans Removed: 4
+         ->  Index Scan using q_5_a_idx on q_5 q_1
+               Index Cond: (a = p.a)
+(9 rows)
+
+-- support perudo const.
+prepare s2 as select * from p, q where p.a = q.a and p.a > abs($1::integer);
+explain (costs off) execute s2(5 * 100000 - 3);
+                    QUERY PLAN                     
+---------------------------------------------------
+ Nested Loop
+   ->  Append
+         Subplans Removed: 4
+         ->  Seq Scan on p_5 p_1
+               Filter: (a > abs($1))
+   ->  Append
+         Subplans Removed: 4
+         ->  Index Scan using q_5_a_idx on q_5 q_1
+               Index Cond: (a = p.a)
+(9 rows)
+
+-- test the IN Expr.
+explain (costs off) select * from p, q where p.a = q.a and p.a in (30, 5000000 -3);
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Nested Loop
+   ->  Seq Scan on p_1 p
+         Filter: (a = ANY ('{30,4999997}'::integer[]))
+   ->  Index Scan using q_1_a_idx on q_1 q
+         Index Cond: (a = p.a)
+(5 rows)
+
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index 92fcec1158b..a5042c3cbc3 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -407,14 +407,14 @@ set session authorization regress_user_ectest;
 explain (costs off)
   select * from ec0 a, ec1 b
   where a.ff = b.ff and a.ff = 43::bigint::int8alias1;
-                              QUERY PLAN                              
-----------------------------------------------------------------------
+                 QUERY PLAN                  
+---------------------------------------------
  Nested Loop
    ->  Index Scan using ec0_pkey on ec0 a
          Index Cond: (ff = '43'::int8alias1)
    ->  Index Scan using ec1_pkey on ec1 b
          Index Cond: (ff = a.ff)
-         Filter: ((f1 < '5'::int8alias1) AND (ff = '43'::int8alias1))
+         Filter: (f1 < '5'::int8alias1)
 (6 rows)
 
 reset session authorization;
@@ -456,37 +456,29 @@ explain (costs off)
   select * from ec0
   inner join ec1 on ec0.ff = ec1.ff
   where ec0.ff between 1 and 10;
-                         QUERY PLAN                         
-------------------------------------------------------------
+                            QUERY PLAN                            
+------------------------------------------------------------------
  Nested Loop
-   Join Filter: (ec0.ff = ec1.ff)
-   ->  Bitmap Heap Scan on ec0
+   ->  Bitmap Heap Scan on ec1
          Recheck Cond: ((ff >= 1) AND (ff <= 10))
-         ->  Bitmap Index Scan on ec0_pkey
+         ->  Bitmap Index Scan on ec1_pkey
                Index Cond: ((ff >= 1) AND (ff <= 10))
-   ->  Materialize
-         ->  Bitmap Heap Scan on ec1
-               Recheck Cond: ((ff >= 1) AND (ff <= 10))
-               ->  Bitmap Index Scan on ec1_pkey
-                     Index Cond: ((ff >= 1) AND (ff <= 10))
-(11 rows)
+   ->  Index Scan using ec0_pkey on ec0
+         Index Cond: ((ff = ec1.ff) AND (ff >= 1) AND (ff <= 10))
+(7 rows)
 
 explain (costs off)
   select * from ec0
   inner join ec1 on ec0.ff = ec1.ff
   where ec1.ff between 1 and 10;
-                         QUERY PLAN                         
-------------------------------------------------------------
+                            QUERY PLAN                            
+------------------------------------------------------------------
  Nested Loop
-   Join Filter: (ec0.ff = ec1.ff)
    ->  Bitmap Heap Scan on ec0
          Recheck Cond: ((ff >= 1) AND (ff <= 10))
          ->  Bitmap Index Scan on ec0_pkey
                Index Cond: ((ff >= 1) AND (ff <= 10))
-   ->  Materialize
-         ->  Bitmap Heap Scan on ec1
-               Recheck Cond: ((ff >= 1) AND (ff <= 10))
-               ->  Bitmap Index Scan on ec1_pkey
-                     Index Cond: ((ff >= 1) AND (ff <= 10))
-(11 rows)
+   ->  Index Scan using ec1_pkey on ec1
+         Index Cond: ((ff = ec0.ff) AND (ff >= 1) AND (ff <= 10))
+(7 rows)
 
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index b20de6a505f..7df4f93856e 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -6496,9 +6496,10 @@ where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 = any (array[1]);
    Merge Cond: (j1.id1 = j2.id1)
    Join Filter: (j1.id2 = j2.id2)
    ->  Index Scan using j1_id1_idx on j1
+         Index Cond: (id1 = ANY ('{1}'::integer[]))
    ->  Index Scan using j2_id1_idx on j2
          Index Cond: (id1 = ANY ('{1}'::integer[]))
-(6 rows)
+(7 rows)
 
 select * from j1
 inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
@@ -6513,16 +6514,17 @@ where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 = any (array[1]);
 explain (costs off) select * from j1
 inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
 where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 >= any (array[1,5]);
-                      QUERY PLAN                       
--------------------------------------------------------
+                       QUERY PLAN                        
+---------------------------------------------------------
  Merge Join
-   Merge Cond: (j1.id1 = j2.id1)
-   Join Filter: (j1.id2 = j2.id2)
-   ->  Index Scan using j1_id1_idx on j1
+   Merge Cond: ((j1.id1 = j2.id1) AND (j1.id2 = j2.id2))
+   ->  Index Only Scan using j1_pkey on j1
+         Index Cond: (id1 >= ANY ('{1,5}'::integer[]))
+         Filter: ((id1 % 1000) = 1)
    ->  Index Only Scan using j2_pkey on j2
          Index Cond: (id1 >= ANY ('{1,5}'::integer[]))
          Filter: ((id1 % 1000) = 1)
-(7 rows)
+(8 rows)
 
 select * from j1
 inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
@@ -6550,22 +6552,24 @@ where exists (select 1 from tenk1 t3
 ---------------------------------------------------------------------------------
  Nested Loop
    Output: t1.unique1, t2.hundred
-   ->  Nested Loop
+   ->  Hash Join
          Output: t1.unique1, t3.tenthous
-         Join Filter: (t1.unique1 = t3.thousand)
-         ->  Index Only Scan using onek_unique1 on public.onek t1
-               Output: t1.unique1
-               Index Cond: (t1.unique1 < 1)
+         Hash Cond: (t3.thousand = t1.unique1)
          ->  HashAggregate
                Output: t3.thousand, t3.tenthous
                Group Key: t3.thousand, t3.tenthous
                ->  Index Only Scan using tenk1_thous_tenthous on public.tenk1 t3
                      Output: t3.thousand, t3.tenthous
                      Index Cond: (t3.thousand < 1)
+         ->  Hash
+               Output: t1.unique1
+               ->  Index Only Scan using onek_unique1 on public.onek t1
+                     Output: t1.unique1
+                     Index Cond: (t1.unique1 < 1)
    ->  Index Only Scan using tenk1_hundred on public.tenk1 t2
          Output: t2.hundred
          Index Cond: (t2.hundred = t3.tenthous)
-(17 rows)
+(19 rows)
 
 -- ... unless it actually is unique
 create table j3 as select unique1, tenthous from onek;
@@ -6577,22 +6581,21 @@ from onek t1, tenk1 t2
 where exists (select 1 from j3
               where j3.unique1 = t1.unique1 and j3.tenthous = t2.hundred)
       and t1.unique1 < 1;
-                               QUERY PLAN                               
-------------------------------------------------------------------------
+                                 QUERY PLAN                                 
+----------------------------------------------------------------------------
  Nested Loop
    Output: t1.unique1, t2.hundred
    ->  Nested Loop
          Output: t1.unique1, j3.tenthous
-         Join Filter: (t1.unique1 = j3.unique1)
-         ->  Index Only Scan using onek_unique1 on public.onek t1
-               Output: t1.unique1
-               Index Cond: (t1.unique1 < 1)
          ->  Index Only Scan using j3_unique1_tenthous_idx on public.j3
                Output: j3.unique1, j3.tenthous
                Index Cond: (j3.unique1 < 1)
+         ->  Index Only Scan using onek_unique1 on public.onek t1
+               Output: t1.unique1
+               Index Cond: ((t1.unique1 = j3.unique1) AND (t1.unique1 < 1))
    ->  Index Only Scan using tenk1_hundred on public.tenk1 t2
          Output: t2.hundred
          Index Cond: (t2.hundred = j3.tenthous)
-(14 rows)
+(13 rows)
 
 drop table j3;
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index 5a2923bac6c..c12351a3b0a 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -4449,16 +4449,18 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
          ->  Hash Join
                Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
                ->  Seq Scan on plt2_adv_p3 t2_1
+                     Filter: (c = ANY ('{0003,0004,0005}'::text[]))
                ->  Hash
                      ->  Seq Scan on plt1_adv_p3 t1_1
                            Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
          ->  Hash Join
                Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
                ->  Seq Scan on plt2_adv_p4 t2_2
+                     Filter: (c = ANY ('{0003,0004,0005}'::text[]))
                ->  Hash
                      ->  Seq Scan on plt1_adv_p4 t1_2
                            Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-(15 rows)
+(17 rows)
 
 SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a;
  a |  c   | a |  c   
@@ -4502,16 +4504,18 @@ SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a =
          ->  Hash Join
                Hash Cond: ((t2_1.a = t1_1.a) AND (t2_1.c = t1_1.c))
                ->  Seq Scan on plt2_adv_p3 t2_1
+                     Filter: (c = ANY ('{0003,0004,0005}'::text[]))
                ->  Hash
                      ->  Seq Scan on plt1_adv_p3 t1_1
                            Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
          ->  Hash Join
                Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.c = t1_2.c))
                ->  Seq Scan on plt2_adv_p4 t2_2
+                     Filter: (c = ANY ('{0003,0004,0005}'::text[]))
                ->  Hash
                      ->  Seq Scan on plt1_adv_p4 t1_2
                            Filter: ((b < 10) AND (c = ANY ('{0003,0004,0005}'::text[])))
-(15 rows)
+(17 rows)
 
 SELECT t1.a, t1.c, t2.a, t2.c FROM plt1_adv t1 INNER JOIN plt2_adv t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE t1.c IN ('0003', '0004', '0005') AND t1.b < 10 ORDER BY t1.a;
  a |  c   | a |  c   
@@ -4771,8 +4775,8 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
 
 EXPLAIN (COSTS OFF)
 SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b, t2.b;
-                                                              QUERY PLAN                                                              
---------------------------------------------------------------------------------------------------------------------------------------
+                                                                 QUERY PLAN                                                                 
+--------------------------------------------------------------------------------------------------------------------------------------------
  Sort
    Sort Key: t1.a, t1.b, t2.b
    ->  Append
@@ -4786,21 +4790,21 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.c = t2
                ->  Hash
                      ->  Append
                            ->  Seq Scan on beta_neg_p1 t2_2
-                                 Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
+                                 Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
                            ->  Seq Scan on beta_neg_p2 t2_3
-                                 Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
+                                 Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
          ->  Nested Loop
                Join Filter: ((t1_4.a = t2_4.a) AND (t1_4.c = t2_4.c))
                ->  Seq Scan on alpha_pos_p2 t1_4
                      Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
                ->  Seq Scan on beta_pos_p2 t2_4
-                     Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
+                     Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
          ->  Nested Loop
                Join Filter: ((t1_5.a = t2_5.a) AND (t1_5.c = t2_5.c))
                ->  Seq Scan on alpha_pos_p3 t1_5
                      Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
                ->  Seq Scan on beta_pos_p3 t2_5
-                     Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
+                     Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
 (28 rows)
 
 SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b, t2.b;
@@ -4826,38 +4830,40 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.c = t2
 
 EXPLAIN (COSTS OFF)
 SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b;
-                                                           QUERY PLAN                                                           
---------------------------------------------------------------------------------------------------------------------------------
+                                                              QUERY PLAN                                                              
+--------------------------------------------------------------------------------------------------------------------------------------
  Sort
    Sort Key: t1.a, t1.b
    ->  Append
-         ->  Hash Join
-               Hash Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c))
-               ->  Seq Scan on alpha_neg_p1 t1_1
-                     Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
-               ->  Hash
+         ->  Merge Join
+               Merge Cond: ((t1_1.a = t2_1.a) AND (t1_1.b = t2_1.b) AND (t1_1.c = t2_1.c))
+               ->  Sort
+                     Sort Key: t1_1.a, t1_1.b, t1_1.c
+                     ->  Seq Scan on alpha_neg_p1 t1_1
+                           Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
+               ->  Sort
+                     Sort Key: t2_1.a, t2_1.b, t2_1.c
                      ->  Seq Scan on beta_neg_p1 t2_1
-                           Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
-         ->  Hash Join
-               Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c))
+                           Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
+         ->  Nested Loop
+               Join Filter: ((t1_2.a = t2_2.a) AND (t1_2.b = t2_2.b) AND (t1_2.c = t2_2.c))
+               ->  Seq Scan on beta_neg_p2 t2_2
+                     Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
                ->  Seq Scan on alpha_neg_p2 t1_2
                      Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
-               ->  Hash
-                     ->  Seq Scan on beta_neg_p2 t2_2
-                           Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
          ->  Nested Loop
                Join Filter: ((t1_3.a = t2_3.a) AND (t1_3.b = t2_3.b) AND (t1_3.c = t2_3.c))
                ->  Seq Scan on alpha_pos_p2 t1_3
                      Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
                ->  Seq Scan on beta_pos_p2 t2_3
-                     Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
+                     Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
          ->  Nested Loop
                Join Filter: ((t1_4.a = t2_4.a) AND (t1_4.b = t2_4.b) AND (t1_4.c = t2_4.c))
                ->  Seq Scan on alpha_pos_p3 t1_4
                      Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
                ->  Seq Scan on beta_pos_p3 t2_4
-                     Filter: (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210)))
-(29 rows)
+                     Filter: ((c = ANY ('{0004,0009}'::text[])) AND (((b >= 100) AND (b < 110)) OR ((b >= 200) AND (b < 210))))
+(31 rows)
 
 SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c) WHERE ((t1.b >= 100 AND t1.b < 110) OR (t1.b >= 200 AND t1.b < 210)) AND ((t2.b >= 100 AND t2.b < 110) OR (t2.b >= 200 AND t2.b < 210)) AND t1.c IN ('0004', '0009') ORDER BY t1.a, t1.b;
  a  |  b  |  c   | a  |  b  |  c   
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index 7555764c779..3fb515cdd9d 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -2104,19 +2104,7 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on
                                  Index Cond: (a = a.a)
                            ->  Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N)
                                  Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed)
-                                 Index Cond: (a = a.a)
-(27 rows)
+(15 rows)
 
 -- Ensure the same partitions are pruned when we make the nested loop
 -- parameter an Expr rather than a plain Param.
@@ -2171,19 +2159,13 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on
                                  Index Cond: (a = a.a)
                            ->  Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N)
                                  Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (actual rows=N loops=N)
+                           ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_4 (actual rows=N loops=N)
                                  Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (actual rows=N loops=N)
+                           ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_5 (actual rows=N loops=N)
                                  Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (actual rows=N loops=N)
+                           ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_6 (actual rows=N loops=N)
                                  Index Cond: (a = a.a)
-(27 rows)
+(21 rows)
 
 select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 0)');
                                         explain_parallel_append                                         
@@ -2204,19 +2186,7 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on
                                  Index Cond: (a = a.a)
                            ->  Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (actual rows=N loops=N)
                                  Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed)
-                                 Index Cond: (a = a.a)
-(28 rows)
+(16 rows)
 
 delete from lprt_a where a = 1;
 select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on ab.a = a.a where a.a in(1, 0, 0)');
@@ -2238,19 +2208,7 @@ select explain_parallel_append('select avg(ab.a) from ab inner join lprt_a a on
                                  Index Cond: (a = a.a)
                            ->  Index Scan using ab_a1_b3_a_idx on ab_a1_b3 ab_3 (never executed)
                                  Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b1_a_idx on ab_a2_b1 ab_4 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b2_a_idx on ab_a2_b2 ab_5 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a2_b3_a_idx on ab_a2_b3 ab_6 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b1_a_idx on ab_a3_b1 ab_7 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b2_a_idx on ab_a3_b2 ab_8 (never executed)
-                                 Index Cond: (a = a.a)
-                           ->  Index Scan using ab_a3_b3_a_idx on ab_a3_b3 ab_9 (never executed)
-                                 Index Cond: (a = a.a)
-(28 rows)
+(16 rows)
 
 reset enable_hashjoin;
 reset enable_mergejoin;
diff --git a/src/test/regress/sql/ec_filter.sql b/src/test/regress/sql/ec_filter.sql
new file mode 100644
index 00000000000..f79e4206c50
--- /dev/null
+++ b/src/test/regress/sql/ec_filter.sql
@@ -0,0 +1,46 @@
+-- THIS TEST IS JUST FOW REVIEW PURPOSE, IT CONTAINS TOO MANY RUNTIME STATS.
+set enable_hashjoin to off;
+set enable_mergejoin to off;
+set enable_seqscan to on;
+-- 1. The joinrel.rows should be correct.
+-- 2. The path estimated rows for b should be correct.
+-- 3. the estimated rows from a in wrong, but that is not the fault of this patch.
+explain analyze select * from tenk1 a, tenk1 b where a.thousand = b.thousand and a.thousand < 100;
+
+create table d_tenk2 as select * from tenk1 union all select * from tenk1;
+create index on d_tenk2(thousand);
+analyze d_tenk2 ;
+
+set enable_seqscan to off;
+drop index tenk1_thous_tenthous;
+-- 1. The parameterized path of b shows the correct rows estimation as well.
+-- 2. The new added qual is fully duplicated with the join clause, so it should be removed.
+explain analyze select * from tenk1 a join d_tenk2 b on a.thousand = b.thousand and a.thousand < 100;
+
+create index tenk1_thous_tenthous on tenk1(thousand, tenthous);
+
+-- test ef filter with partition prune.
+
+create table p (a int, b int) partition by range(a);
+select 'create table p_' || i || ' partition of p  for values from (' || (i-1) * 100000 || ') to (' || i * 100000 || ');' from generate_series(1, 5)i;  \gexec
+insert into p select i, i from generate_series(1, 5 * 100000 -1) i;
+
+create table q (a int, b int) partition by range(a);
+select 'create table q_' || i || ' partition of q  for values from (' || (i-1) * 100000 || ') to (' || i * 100000 || ');' from generate_series(1, 5)i;  \gexec
+insert into q select * from p;
+create index on q(a);
+
+select * from p, q where p.a = q.a and p.a > 5 * 100000 - 3;
+
+set plan_cache_mode to force_generic_plan ;
+prepare s as select * from p, q where p.a = q.a and p.a > $1;
+explain (costs off) execute s(5 * 100000 - 3);
+
+-- support perudo const.
+prepare s2 as select * from p, q where p.a = q.a and p.a > abs($1::integer);
+explain (costs off) execute s2(5 * 100000 - 3);
+
+-- test the IN Expr.
+explain (costs off) select * from p, q where p.a = q.a and p.a in (30, 5000000 -3);
+
+
-- 
2.21.0

#15Andy Fan
zhihui.fan1213@gmail.com
In reply to: Andy Fan (#14)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

So I think knowing what bad it is to have this feature is the key point to

discussion now.

I re-read the discussion at 2015 [1] and the below topic is added for the
above
question. Here is the summary for easy discussion.

====
From planner aspect:

While I've only read your description of the patch not the patch itself,
the search methodology you propose seems pretty brute-force and
unlikely to solve that issue. It's particularly important to avoid

O(N^2)

behaviors when there are N expressions ...

The patch has 3 steps in general. 1). Gather the filter_qual_list during
the deconstruct_jointree. only unmergeable qual is gathered here.
2). After the root->eq_classes is built, scan each of the above quals to
find out if there is a EC match, if yes, add it to the EC. There are
some fast paths here. like ec->relids, em->em_relids. 3). compose
the qual in ec_filter and members in ec_members, then distribute it to
the relations. This step take the most cycles of this feature, and it is
the most important part for this feature as well.

Fortunately, thousands of partitions of a table would not make it worse
since they are not generated at that stage. So I'd believe the number of
ECs or EMs in an EC would be pretty small in common cases.

time would be spent on searches for matching subexpressions whether
or not anything was learned (and often nothing would be learned).

This is about some cases like "SELECT * FROM t1, t2 WHERE t1.a = t2.a
and t1.b > 3". In this case, we still need to go through steps 1 & 2,
all the fast
paths don't work and the equal() is unavoidable. However step 3 can be
ignored.
If we want to improve this, could we maintain an attr_eq_indexes in
RelOptInfos
which indicates if the given attribute appears in any one of EC members?

=====
From executor aspects:

The reason why the answer isn't automatically "all of them"
is because, first of all, it's possible that enforcing the condition
at a particular table costs more to filter out the rows that we save
in execution time at higher levels of the plan tree. For example,
consider A JOIN B ON A.X = B.X WHERE A.X > 1000000. It might be that
the range of A.X is [0,1000001] but the range of B.X is
[1000000,2000000]; so enforcing the inequality against A is very
selective but enforcing it against B filters out basically nothing.

I think we can classify this as we push down / execute an qual, the
qual takes lots of cycles, but it doesn't filter many rows.

A first cut might be to enforce the inequality against the relation
where it's believed to be most selective, equivalence-class column
mentioned in the inequality provided that the
selectivity is thought to be above some threshold ... but I'm not sure
this is very principled,

I can only input +1 after some deep thoughts.

Furthermore, there are some cases involving parameterized paths where
enforcing the inequality multiple times is definitely bad: for
example, if we've got a nested loop where the outer side is a seq scan
that enforces the condition and the inner side is an index probe, it
is just a waste to retest it on the inner side. We already know that
the outer row passes the inequality, so the inner row will necessarily
pass also. This doesn't apply to merge or hash joins, and it also
doesn't apply to all nested loops: scans that aren't paramaterized by
the equivalence-class column can still benefit from separate
enforcement of the inequality.

I guess that could be fixed by somehow marking these pushed quals as
optional and having parameterised scans ignore optional quals.

This has been done by committing 4.

Now, all that having been said, I think this is a pretty important
optimization. Lots of people have asked for it, and I think it would
be worth expending some brainpower to try to figure out a way to be
smarter than we are now, which is, in a nutshell, as dumb as possible.

+1. I asked custom to add the derivable quals manually for 10+ of table
each query last year and gained great results.

Anyone still have interest in this? Or is a better solution really
possible?
Or is the current method too bad to rescue?

--
Best Regards
Andy Fan

#16Finnerty, Jim
jfinnert@amazon.com
In reply to: Andy Fan (#15)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

So I think knowing what bad it is to have this feature is the key point to discussion now.

While I've only read your description of the patch not the patch itself,

This comment applies to me also.

Is the join selectivity properly calculated in all cases, e.g. in the n:m join case in particular, or in general when you’re not joining to a unique key? (this would be the usual situation here, since it adds a range qual to a join qual)

Furthermore, there are some cases involving parameterized paths where
enforcing the inequality multiple times is definitely bad

* This has been done by committing 4.

What remaining cases are there where the qual is evaluated redundantly?

* Anyone still have interest in this? Or is a better solution really possible?
Or is the current method too bad to rescue?

As you’ve shown, this can potentially be very important, though I don’t think you’ll often see equijoins with an additional range restriction on the join keys. When it happens, though, it could be especially important for joins to partitioned tables with many remote fdw partitions when the join can’t be pushed down to the remote server.

#17Robert Haas
robertmhaas@gmail.com
In reply to: Andy Fan (#9)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

On Tue, Feb 1, 2022 at 10:08 AM Andy Fan <zhihui.fan1213@gmail.com> wrote:

To address the row estimation issue, The most straightforward way to fix this is to
ignore the derived clauses when figuring out the RelOptInfo->rows on base relation.
To note which clause is derived from this patch, I added a new field "EquivalenceClass *
derived" in RestrictInfo. and then added a included_derived option in clauselist_selectivity_ext,
during the set_xxx_rel_size function, we can pass the included_derived=false. This strategy
should be used in get_parameterized_baserel_size. In all the other cases, include_derived=true
is used. which are finished in commit 2. (Commit 1 is Daivd's patch, I just rebased it)

That doesn't sound correct to me.

Suppose that we have A.x = B.x and also A.x < 42. We can choose to
enforce A.x < 42 or we can choose to enforce B.x < 42 or we can do
both. In general, any of those could be right: if either one of those
two is highly selective while the other is not very selective at all,
it's going to be fastest to enforce only the more selective qual. But
if both are selective then it may be best to enforce both, so let's
suppose we do that. If we don't adopt the proposal above and just do
nothing, then our row count estimates for both A and B will include
the effect of checking x < 42, and so they will be correct, but the
row count estimate for join(A, B) will include the effect of checking
x < 42 twice, and so it will be too low, which can mess up the plan at
higher levels.

But discounting the effect of B.x < 42 when estimating the size of B
is also incorrect. Now, the row count estimate for join(A, B) will
include the effect of x < 42 only once, which is good. However, the
row count estimate for B will be too high, because it will not include
the effect of B.x < 42. And that means that the cost estimate for
join(A, B) will be wrong. It will be too high, because it's going to
think that it has more rows coming from the B side of the join than
what is actually the case. And that can also mess up the plan at
higher levels.

I think we could get around this problem by having multiple
RelOptInfos (or something similar that is lighter-weight) for each
relation. Today, we'd create a RelOptInfo for A, one for B, and one
for join(A, B), and the paths for the join are created by joining a
path for A to a path for B. Now imagine that we have instead 5
RelOptInfos, for {A}, {A|x<42}, {B}, {B|x<42}, and join(A, B). The
legal paths for that last one can be created by joining {A} to
{B|x<42} or {A|x<42} to {B} or {A|x<42} to {B|x<42}. Each of those 5
RelOptInfos can have its own cardinality estimate, and it seems pretty
straightforward to see how to get both the scan cardinality and the
join cardinality correct. Now I think this is decidedly non-trivial to
implement, and I also hear the voice of Tom Lane saying that's going
to be expensive in both time and memory, and he's not wrong.

On the other hand, I completely agree with David's comments on the
other thread to the effect that holding our breath is not getting us
anywhere. People don't keep asking for this feature because it's a
stupid thing that nobody really wants, and when Tom alleges that it
will rarely pay off, I think he's pretty far off the mark. The only
time we need to consider doing any extra work is when we have
something like the example discussed here, namely A.x = B.x and A.x <
42. If there is a variable that is part of an equivalence class and
also is used in a scan qual, what are the chances that the implied
inequality is useful? There's no way to estimate that mathematically -
it's all about what you think human beings are typically going to do -
but I'd say it's probably better than 50%. I know that when I was
regularly doing application programming on top of PostgreSQL I was
VERY aware of this limitation of the optimizer and habitually thought
about which table to write the inequality against. That kept me out of
trouble most of the time, but it sure seems like we're punting the
optimizer's job to the end user.

And even then, I still sometimes couldn't stay out of trouble, because
sometimes I knew that the implied inequality really ought to be
enforced against both sides of the join to get a decent plan. In that
case, the only way to get the optimizer to do what I wanted was to
duplicate the qual. But that runs headlong into the exact problem that
we're talking about here: now the join selectivity is going to be
messed up, and then some other part of the plan would get messed up. I
still remember the frustration associated with that scenario more than
10 years later. You can't even fix it by uglifying your query with a
planner hint, because we don't support those either. Which brings me
to another point: it's incoherent to simultaneously argue that we
shouldn't have planner hints but rather focus on improving the
planner, and at the same time refuse to improve the planner because it
would make planning too expensive. I actually think we should do both,
because I neither believe that it's impossible to fix this particular
problem nor that it is possible to create a planner so good that it
always makes the right decisions without any explicit input from a
human being. But the only way you can think this problem is unfixable
and at the same time think we don't need hints is if you think this
problem is fake.

It's not.

--
Robert Haas
EDB: http://www.enterprisedb.com

#18Tomas Vondra
tomas.vondra@enterprisedb.com
In reply to: Robert Haas (#17)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

On 2/17/22 21:15, Robert Haas wrote:

On Tue, Feb 1, 2022 at 10:08 AM Andy Fan <zhihui.fan1213@gmail.com> wrote:

To address the row estimation issue, The most straightforward way to fix this is to
ignore the derived clauses when figuring out the RelOptInfo->rows on base relation.
To note which clause is derived from this patch, I added a new field "EquivalenceClass *
derived" in RestrictInfo. and then added a included_derived option in clauselist_selectivity_ext,
during the set_xxx_rel_size function, we can pass the included_derived=false. This strategy
should be used in get_parameterized_baserel_size. In all the other cases, include_derived=true
is used. which are finished in commit 2. (Commit 1 is Daivd's patch, I just rebased it)

That doesn't sound correct to me.

Suppose that we have A.x = B.x and also A.x < 42. We can choose to
enforce A.x < 42 or we can choose to enforce B.x < 42 or we can do
both. In general, any of those could be right: if either one of those
two is highly selective while the other is not very selective at all,
it's going to be fastest to enforce only the more selective qual. But
if both are selective then it may be best to enforce both, so let's
suppose we do that. If we don't adopt the proposal above and just do
nothing, then our row count estimates for both A and B will include
the effect of checking x < 42, and so they will be correct, but the
row count estimate for join(A, B) will include the effect of checking
x < 42 twice, and so it will be too low, which can mess up the plan at
higher levels.

But discounting the effect of B.x < 42 when estimating the size of B
is also incorrect. Now, the row count estimate for join(A, B) will
include the effect of x < 42 only once, which is good. However, the
row count estimate for B will be too high, because it will not include
the effect of B.x < 42. And that means that the cost estimate for
join(A, B) will be wrong. It will be too high, because it's going to
think that it has more rows coming from the B side of the join than
what is actually the case. And that can also mess up the plan at
higher levels.

I think we could get around this problem by having multiple
RelOptInfos (or something similar that is lighter-weight) for each
relation. Today, we'd create a RelOptInfo for A, one for B, and one
for join(A, B), and the paths for the join are created by joining a
path for A to a path for B. Now imagine that we have instead 5
RelOptInfos, for {A}, {A|x<42}, {B}, {B|x<42}, and join(A, B). The
legal paths for that last one can be created by joining {A} to
{B|x<42} or {A|x<42} to {B} or {A|x<42} to {B|x<42}. Each of those 5
RelOptInfos can have its own cardinality estimate, and it seems pretty
straightforward to see how to get both the scan cardinality and the
join cardinality correct. Now I think this is decidedly non-trivial to
implement, and I also hear the voice of Tom Lane saying that's going
to be expensive in both time and memory, and he's not wrong.

IMHO the whole problem is we're unable to estimate the join clause as a
conditional probability, i.e.

P(A.x = B.x | (A.x < 42) & (B.x < 42))

so maybe instead of trying to generate additional RelOptInfo items we
should think about improving that. The extra RelOptInfos don't really
solve this, because even if you decide to join A|x<42 to B|x<42 it does
nothing to improve the join clause estimate.

With equality clauses we don't have this issue, because if you derive
clauses at the baserel level, the join clause becomes no-op with
selecitivity 1.0. But for inequalities that does not work ...

Interestingly enough, the patch [1]https://commitfest.postgresql.org/36/3055/ tries to do something like this by
applying extended statistics to joins, and using baserestrictinfos as
"conditions" for statistics on both sides.

It actually deals with a more general form of this case, because the
clauses don't need to reference the same attribute - so for example this
would work too, assuming there is extended stats object on the columns
on each side:

P(A.c = B.d | (A.e < 42) & (B.f < 42))

[1]: https://commitfest.postgresql.org/36/3055/

On the other hand, I completely agree with David's comments on the
other thread to the effect that holding our breath is not getting us
anywhere. People don't keep asking for this feature because it's a
stupid thing that nobody really wants, and when Tom alleges that it
will rarely pay off, I think he's pretty far off the mark. The only
time we need to consider doing any extra work is when we have
something like the example discussed here, namely A.x = B.x and A.x <
42. If there is a variable that is part of an equivalence class and
also is used in a scan qual, what are the chances that the implied
inequality is useful? There's no way to estimate that mathematically -
it's all about what you think human beings are typically going to do -
but I'd say it's probably better than 50%. I know that when I was
regularly doing application programming on top of PostgreSQL I was
VERY aware of this limitation of the optimizer and habitually thought
about which table to write the inequality against. That kept me out of
trouble most of the time, but it sure seems like we're punting the
optimizer's job to the end user.

Not sure. In my experience queries with both a join clause and other
clauses referencing the same attribute are pretty rare. But I agree if
we can do the expensive stuff only when actually needed, with no cost in
the 99.999% other cases, I don't see why not. Of course, code complexity
is a cost too.

And even then, I still sometimes couldn't stay out of trouble, because
sometimes I knew that the implied inequality really ought to be
enforced against both sides of the join to get a decent plan. In that
case, the only way to get the optimizer to do what I wanted was to
duplicate the qual. But that runs headlong into the exact problem that
we're talking about here: now the join selectivity is going to be
messed up, and then some other part of the plan would get messed up. I
still remember the frustration associated with that scenario more than
10 years later. You can't even fix it by uglifying your query with a
planner hint, because we don't support those either. Which brings me
to another point: it's incoherent to simultaneously argue that we
shouldn't have planner hints but rather focus on improving the
planner, and at the same time refuse to improve the planner because it
would make planning too expensive. I actually think we should do both,
because I neither believe that it's impossible to fix this particular
problem nor that it is possible to create a planner so good that it
always makes the right decisions without any explicit input from a
human being. But the only way you can think this problem is unfixable
and at the same time think we don't need hints is if you think this
problem is fake.

IMHO to deal with the estimates it'd be enough to allow calculating
conditional probabilities.

No comment regarding hints ...

regards

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#19Robert Haas
robertmhaas@gmail.com
In reply to: Tomas Vondra (#18)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

On Thu, Feb 17, 2022 at 4:17 PM Tomas Vondra
<tomas.vondra@enterprisedb.com> wrote:

IMHO the whole problem is we're unable to estimate the join clause as a
conditional probability, i.e.

P(A.x = B.x | (A.x < 42) & (B.x < 42))

so maybe instead of trying to generate additional RelOptInfo items we
should think about improving that. The extra RelOptInfos don't really
solve this, because even if you decide to join A|x<42 to B|x<42 it does
nothing to improve the join clause estimate.

I guess I hadn't considered that angle. I think the extra RelOptInfos
(or whatever) actually do solve a problem, because enforcing a
high-selectivity join qual against both sides is potentially quite
wasteful, and you need some way to decide whether to do it on one
side, the other, or both. But it's also true that I was wrong to
assume independence ... and if we could avoid assuming that, then the
join selectivity would work itself out without any of the machinery
that I just proposed.

It actually deals with a more general form of this case, because the
clauses don't need to reference the same attribute - so for example this
would work too, assuming there is extended stats object on the columns
on each side:

P(A.c = B.d | (A.e < 42) & (B.f < 42))

That'd be cool.

Not sure. In my experience queries with both a join clause and other
clauses referencing the same attribute are pretty rare. But I agree if
we can do the expensive stuff only when actually needed, with no cost in
the 99.999% other cases, I don't see why not. Of course, code complexity
is a cost too.

Right. I mean, we could have a planner GUC to control whether the
optimization is used even in cases where we see that it's possible.
But Tom keeps arguing that it is possible in many queries and would
benefit few queries, and I'm not seeing why that should be so. I think
it's likely to benefit many of the queries to which it applies.

--
Robert Haas
EDB: http://www.enterprisedb.com

#20Andy Fan
zhihui.fan1213@gmail.com
In reply to: Robert Haas (#17)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

On Fri, Feb 18, 2022 at 4:15 AM Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Feb 1, 2022 at 10:08 AM Andy Fan <zhihui.fan1213@gmail.com> wrote:

To address the row estimation issue, The most straightforward way to fix

this is to

ignore the derived clauses when figuring out the RelOptInfo->rows on

base relation.

To note which clause is derived from this patch, I added a new field

"EquivalenceClass *

derived" in RestrictInfo. and then added a included_derived option in

clauselist_selectivity_ext,

during the set_xxx_rel_size function, we can pass the

included_derived=false. This strategy

should be used in get_parameterized_baserel_size. In all the other

cases, include_derived=true

is used. which are finished in commit 2. (Commit 1 is Daivd's patch, I

just rebased it)

That doesn't sound correct to me.

Suppose that we have A.x = B.x and also A.x < 42. We can choose to
enforce A.x < 42 or we can choose to enforce B.x < 42 or we can do
both. In general, any of those could be right:

This is impressive. To achieve this, we have to treat a.x < 42 and
b.x < 42 equally rather than b.x < 42 is derived from a.x < 42,
and enforce the final plan to execute 1 qual in such a group at least.
This introduces some more complexity at the first glance, but I think
it is a great aspect to think about.

.., which is good. However, the
row count estimate for B will be too high, because it will not include
the effect of B.x < 42. And that means that the cost estimate for
join(A, B) will be wrong. It will be too high, because it's going to
think that it has more rows coming from the B side of the join than
what is actually the case. And that can also mess up the plan at
higher levels.

IIUC, this would not happen if we apply the commit 3.

In commit 3, the real rows for the scan path are adjusted by
RelOptInfo->filter_rows,
which take the effect of B.x < 42. It is used in cost_{ascan}_path lately.
Here is an example:

regression=# explain analyze select * from tenk1 a, tenk1 b where
a.thousand = b.thousand and a.thousand < 100;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop (cost=24.90..459.16 rows=10740 width=488) (actual
time=0.416..17.459 rows=10000 loops=1)
-> Bitmap Heap Scan on tenk1 b (cost=24.61..383.03 rows=1074 width=244)
(actual time=0.369..1.801 rows=1000 loops=1)
Recheck Cond: (thousand < 100)
Heap Blocks: exact=324
-> Bitmap Index Scan on tenk1_thous_tenthous (cost=0.00..24.34 rows=1074
width=0) (actual time=0.251..0.251 rows=1000 loops=1)
Index Cond: (thousand < 100)
-> Memoize (cost=0.30..0.47 rows=1 width=244) (actual time=0.002..0.006
rows=10 loops=1000)
Cache Key: b.thousand
Cache Mode: logical
Hits: 900 Misses: 100 Evictions: 0 Overflows: 0 Memory Usage: 277kB
-> Index Scan using tenk1_thous_tenthous on tenk1 a (cost=0.29..0.46
rows=1 width=244) (actual time=0.012..0.033 rows=10 loops=100)
Index Cond: ((thousand = b.thousand) AND (thousand < 100))
Planning Time: 0.934 ms
Execution Time: 18.496 ms
(14 rows)

b.thousand < 100 is derived from a.thousand < 100; and the final path cost
is:
-> Bitmap Heap Scan on tenk1 b (cost=24.61..383.03 rows=1074 width=244)
(actual time=0.369..1.801 rows=1000 loops=1)
Recheck Cond: (thousand < 100)
Heap Blocks: exact=324
-> Bitmap Index Scan on tenk1_thous_tenthous (cost=0.00..24.34 rows=1074
width=0) (actual time=0.251..0.251 rows=1000 loops=1)
Index Cond: (thousand < 100)

Which is exactly same as select * from tenk1 where thousand < 100;

== Commit 3 [1]/messages/by-id/CAKU4AWrdeQZ8xvf=DVhndUs=RGn8oVoSJvYK3Yj7uWq2=dt=Mg@mail.gmail.com message with some modification ==

Introduce RelOptInfo.filtered_rows.

Previously the Path.rows (shown in the explain output) and
RelOptInfo.rows
(which would be used to calculating joinrel's estimated rows) are same
at many scan paths, like SeqScan, IndexScan, BitmapHeapScan and so on.
But
they would be different after distributing a new restrictinfo from
ec_filter.
So I developed RelOptInfo.filtered_rows to take some duty out of
RelOptInfo.rows.
RelOptInfo.filtered_rows would count the effect of derived qual, and be
used for
cost_xxx function.

On the other hand, I completely agree with David's comments on the
other thread to the effect that holding our breath is not getting us
anywhere.

+1 with this as well. PostgreSQL community has an great reputation
in the world, and mostly because the authority figures in this community
has set up a good example for the following people. and it is not
easy. But if the authority figures are too restrict with code quality,
this
is not good for the community as well, we should encourage more people
to have a try, to some extent.

Taking the current feature for example, the estimation issue absolutely
needs a fix. While I do listen/think carefully how to reduce planner
extra cost or rethink if any important items are missed by me. I also think
David's method is not unacceptable at all.

What do you think about moving on this feature? The items known by me
are: 1). Make sure the estimation error can be fixed or discuss if my
current
solution is workable. b). Just distribute some selectivity restrictinfo
to
RelOptInfo. c). See how hard it is to treat the original / derived qual
equally.
d). Reduce the extra planner cost at much as possible. Any other important
items I missed?

[1]: /messages/by-id/CAKU4AWrdeQZ8xvf=DVhndUs=RGn8oVoSJvYK3Yj7uWq2=dt=Mg@mail.gmail.com
/messages/by-id/CAKU4AWrdeQZ8xvf=DVhndUs=RGn8oVoSJvYK3Yj7uWq2=dt=Mg@mail.gmail.com

--
Best Regards
Andy Fan

#21Robert Haas
robertmhaas@gmail.com
In reply to: Andy Fan (#20)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

On Fri, Feb 18, 2022 at 12:56 AM Andy Fan <zhihui.fan1213@gmail.com> wrote:

What do you think about moving on this feature? The items known by me
are: 1). Make sure the estimation error can be fixed or discuss if my current
solution is workable. b). Just distribute some selectivity restrictinfo to
RelOptInfo. c). See how hard it is to treat the original / derived qual equally.
d). Reduce the extra planner cost at much as possible. Any other important
items I missed?

I think it's not realistic to do anything here for PostgreSQL 15.
Considering that it's almost the end of February and feature freeze
will probably be in perhaps 5-6 weeks, in order to get something
committed at this point, you would need to have (1) sufficient
consensus on the design, (2) a set of reasonably complete patches
implementing that design at an acceptable level of quality, and (3) a
committer interested in putting in the necessary time to help you get
this over the finish line. As far as I can see, you have none of those
things. Tom doesn't think we need this at all, and you and I and
Tomas all have somewhat different ideas on what approach we ought to
be taking, and the patches appear to be at a POC level at this point
rather than something that's close to being ready to ship, and no
committer has expressed interest in trying to get them into this
release.

It seems to me that the thing to do here is see if you can build
consensus on an approach. Just saying that we ought to think the
patches you've already got are good enough is not going to get you
anywhere. I do understand that the political element of this problem
is frustrating to you, as it is to many people. But consider the
alternative: suppose the way things worked around here is that any
committer could commit anything they liked without needing the
approval of any other committer, or even over their objections. Well,
it would be chaos. People would be constantly reverting or rewriting
things that other people had done, and everybody would probably be
pissed off at each other all the time, and the quality would go down
the tubes and nobody would use PostgreSQL any more. I'm not saying the
current system is perfect, not at all. It's frustrating as all get out
at times. But the reason it's frustrating is because the PostgreSQL
community is a community of human beings, and there's nothing more
frustrating in the world than the stuff other human beings do.

However, it's equally true that we get further working together than
we would individually. I think Tom is wrong about the merits of doing
something in this area, but I also think he's incredibly smart and
thoughtful and one of the best technologists I've ever met, and
probably just one of the absolute best technologists on Planet Earth.
And I also have to consider, and this is really important, the
possibility that Tom is right about this issue and I am wrong. So far
Tom hasn't replied to what I wrote, but I hope he does. Maybe he'll
admit that I have some valid points. Maybe he'll tell me why he thinks
I'm wrong. Maybe I'll learn about some problem that I haven't
considered from his response, and maybe that will lead to a refinement
of the idea that will make it better. I don't know, but it's certainly
happened in plenty of other cases. And that's how PostgreSQL gets to
be this pretty amazing database that it is. So, yeah, building
consensus is frustrating and it takes a long time and sometimes it
feels like other people are obstructing you needlessly and sometimes
that's probably true. But there's not a realistic alternative. Nobody
here is smart enough to create software that is as good as what all of
us create together.

--
Robert Haas
EDB: http://www.enterprisedb.com

#22Tomas Vondra
tomas.vondra@enterprisedb.com
In reply to: Robert Haas (#19)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

On 2/17/22 23:16, Robert Haas wrote:

On Thu, Feb 17, 2022 at 4:17 PM Tomas Vondra
<tomas.vondra@enterprisedb.com> wrote:

IMHO the whole problem is we're unable to estimate the join clause as a
conditional probability, i.e.

P(A.x = B.x | (A.x < 42) & (B.x < 42))

so maybe instead of trying to generate additional RelOptInfo items we
should think about improving that. The extra RelOptInfos don't really
solve this, because even if you decide to join A|x<42 to B|x<42 it does
nothing to improve the join clause estimate.

I guess I hadn't considered that angle. I think the extra RelOptInfos
(or whatever) actually do solve a problem, because enforcing a
high-selectivity join qual against both sides is potentially quite
wasteful, and you need some way to decide whether to do it on one
side, the other, or both. But it's also true that I was wrong to
assume independence ... and if we could avoid assuming that, then the
join selectivity would work itself out without any of the machinery
that I just proposed.

True. We kinda already have this issue for the equality clauses, and
having paths with the condition pushed down (or not) seems like a
natural approach.

It actually deals with a more general form of this case, because the
clauses don't need to reference the same attribute - so for example this
would work too, assuming there is extended stats object on the columns
on each side:

P(A.c = B.d | (A.e < 42) & (B.f < 42))

That'd be cool.

Yeah, but the patch implementing this still needs more work.

Not sure. In my experience queries with both a join clause and other
clauses referencing the same attribute are pretty rare. But I agree if
we can do the expensive stuff only when actually needed, with no cost in
the 99.999% other cases, I don't see why not. Of course, code complexity
is a cost too.

Right. I mean, we could have a planner GUC to control whether the
optimization is used even in cases where we see that it's possible.
But Tom keeps arguing that it is possible in many queries and would
benefit few queries, and I'm not seeing why that should be so. I think
it's likely to benefit many of the queries to which it applies.

Maybe. Although the example I linked some time ago shows a pretty
dramatic improvement, due to picking merge join + index scan, and not
realizing we'll have to skip a lot of data. But that's just one
anecdotal example.

Anyway, I think the best way to deal with these (perfectly legitimate)
concerns is to show how expensive it is for queries not not having such
join/restriction clauses, with the cost being close to 0. And then for
queries with such clauses but not benefiting from the change (a bit like
a worst case).

regards

[1]: /messages/by-id/CA+1Wm9U_sP9237f7OH7O=-UTab71DWOO4Qc-vnC78DfsJQBCwQ@mail.gmail.com
/messages/by-id/CA+1Wm9U_sP9237f7OH7O=-UTab71DWOO4Qc-vnC78DfsJQBCwQ@mail.gmail.com

--
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#23Andy Fan
zhihui.fan1213@gmail.com
In reply to: Robert Haas (#21)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

Thanks for the detailed explanation.

On Sat, Feb 19, 2022 at 2:27 AM Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Feb 18, 2022 at 12:56 AM Andy Fan <zhihui.fan1213@gmail.com>
wrote:

What do you think about moving on this feature? The items known by me
are: 1). Make sure the estimation error can be fixed or discuss if my

current

solution is workable. b). Just distribute some selectivity

restrictinfo to

RelOptInfo. c). See how hard it is to treat the original / derived

qual equally.

d). Reduce the extra planner cost at much as possible. Any other

important

items I missed?

I think it's not realistic to do anything here for PostgreSQL 15.
Considering that it's almost the end of February and feature freeze
will probably be in perhaps 5-6 weeks, in order to get something
committed at this point,

I didn't expect that we could commit it very soon;) Actually my
expectation
was that more people would care about the direction of this feature. I care
about it, but that's not enough obviously. So I summarized the direction I
want to go, and let more people see if that's right.

Tom doesn't think we need this at all, and you and I and
Tomas all have somewhat different ideas on what approach we ought to
be taking,

Agreed. IMO, the estimation error looks like a serious issue that we
all agree to find a solution. But currently we have different ways to
handle
that. I'd pretty much hope that we can have a discussion about this stuff.

and the patches appear to be at a POC level at this point rather than

something that's close to being ready to ship,

This is very true since no consensus on an approach so far. PoC would
be enough for now.

It seems to me that the thing to do here is see if you can build
consensus on an approach. Just saying that we ought to think the
patches you've already got are good enough is not going to get you
anywhere.

I truly understand this and no matter which approach I insist on, the
only reason is just because I think it is the best one IMO and not because
it comes from me or not.

I do understand that the political element of this problem
is frustrating to you, as it is to many people. But consider the
alternative: suppose the way things worked around here is that any
committer could commit anything they liked without needing the
approval of any other committer, or even over their objections. Well,
it would be chaos.

This is the fact I think.

People would be constantly reverting or rewriting
things that other people had done, and everybody would probably be
pissed off at each other all the time, and the quality would go down
the tubes and nobody would use PostgreSQL any more.

But the reason it's frustrating is because the PostgreSQL
community is a community of human beings, and there's nothing more
frustrating in the world than the stuff other human beings do.

New knowledge gained from how committers think about other's patch:)
It is reasonable. Committing the patch is not my only goal. Thinking
stuff more completely is also an awesome thing to get during discussion.
Just that sometimes ignorance is frustrating (I also truly understood
that everyone's energy is limited).

However, it's equally true that we get further working together than

we would individually. I think Tom is wrong about the merits of doing
something in this area, but I also think he's incredibly smart and
thoughtful and one of the best technologists I've ever met, and
probably just one of the absolute best technologists on Planet Earth.
And I also have to consider, and this is really important, the
possibility that Tom is right about this issue and I am wrong. So far
Tom hasn't replied to what I wrote, but I hope he does. Maybe he'll
admit that I have some valid points. Maybe he'll tell me why he thinks
I'm wrong. Maybe I'll learn about some problem that I haven't
considered from his response, and maybe that will lead to a refinement
of the idea that will make it better.

+1. Just to be more precise, are you also confused about why this
should not be done at all. IIUC, I get 3 reasons from Tom's reply.
a). Planning cost. b). estimation error. c) extra qual execution is bad.

I don't know, but it's certainly
happened in plenty of other cases. And that's how PostgreSQL gets to
be this pretty amazing database that it is. So, yeah, building
consensus is frustrating and it takes a long time and sometimes it
feels like other people are obstructing you needlessly and sometimes
that's probably true. But there's not a realistic alternative. Nobody
here is smart enough to create software that is as good as what all of
us create together.

+1.

--
Best Regards
Andy Fan

#24Andy Fan
zhihui.fan1213@gmail.com
In reply to: Tomas Vondra (#22)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

It actually deals with a more general form of this case, because the
clauses don't need to reference the same attribute - so for example this
would work too, assuming there is extended stats object on the columns
on each side:

P(A.c = B.d | (A.e < 42) & (B.f < 42))

That'd be cool.

Yeah, but the patch implementing this still needs more work.

Thanks for that patch. That patch has been on my study list for a long
time and it can fix the other real case I met some day ago. I spent one
day studying it again yesterday just that the result does not deserve
sharing at the current stage. As for the purpose here, if we have
extended statistics, I believe it can work well. But requiring extended
statistics for this feature does not look very reasonable to me. Do you
think we can go further in direction for the issue here? and it would
be super great that you can take a look at the commit 3 [1]/messages/by-id/CAKU4AWrdeQZ8xvf=DVhndUs=RGn8oVoSJvYK3Yj7uWq2=dt=Mg@mail.gmail.com. IIUC,
It can solve the issue and is pretty straightforward.

[1]: /messages/by-id/CAKU4AWrdeQZ8xvf=DVhndUs=RGn8oVoSJvYK3Yj7uWq2=dt=Mg@mail.gmail.com
/messages/by-id/CAKU4AWrdeQZ8xvf=DVhndUs=RGn8oVoSJvYK3Yj7uWq2=dt=Mg@mail.gmail.com

--
Best Regards
Andy Fan

#25Robert Haas
robertmhaas@gmail.com
In reply to: Andy Fan (#23)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

On Mon, Feb 21, 2022 at 2:31 AM Andy Fan <zhihui.fan1213@gmail.com> wrote:

+1. Just to be more precise, are you also confused about why this
should not be done at all. IIUC, I get 3 reasons from Tom's reply.
a). Planning cost. b). estimation error. c) extra qual execution is bad.

This topic has been discussed a number of times, and Tom has basically
always said that he thinks this would be expensive to plan (which I
think is true) and that we wouldn't get much benefit (which I think is
false).

--
Robert Haas
EDB: http://www.enterprisedb.com

#26Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#25)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

Robert Haas <robertmhaas@gmail.com> writes:

This topic has been discussed a number of times, and Tom has basically
always said that he thinks this would be expensive to plan (which I
think is true) and that we wouldn't get much benefit (which I think is
false).

I think the trick here, as in so many other places, is to not impose
significant extra planning cost on queries that don't end up getting
any benefit. I'm not in favor of complicating the EquivalenceClass
mechanism for this, because (a) I don't think that such an approach
will lead to success on that metric, and (b) what it definitely will do
is make ECs harder to understand and reason about. If we develop a
separate mechanism that can infer things from inequalities, and it only
kicks in when there are some inequalities, that might work out okay.
But because of that, I don't even like the 0001 patch in this series.
I've not looked at the subsequent ones.

regards, tom lane

#27Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#26)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

On Tue, Mar 1, 2022 at 5:53 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

This topic has been discussed a number of times, and Tom has basically
always said that he thinks this would be expensive to plan (which I
think is true) and that we wouldn't get much benefit (which I think is
false).

I think the trick here, as in so many other places, is to not impose
significant extra planning cost on queries that don't end up getting
any benefit.

I agree. My question is: why shouldn't every case where we can deduce
an implied inequality be reasonably likely to show a benefit? If the
query specifies that a.x = b.x and also that a.x < 42, the only reason
to suppose that evaluating a.x < 42 rather than b.x < 42 or in
addition to b.x < 42 is likely to be better is if we assume the user
knows how the query optimizer works and has employed that knowledge in
crafting the query. And admittedly, sophisticated users are probably
likely to do that, and even unsophisticated users may do it more
likely than chance would dictate. But it still feels like we have a
good chance of landing of coming out ahead pretty often unless the
user really knows what they are doing. And even then, any mechanism we
add here can have an off switch.

I'm not in favor of complicating the EquivalenceClass
mechanism for this, because (a) I don't think that such an approach
will lead to success on that metric, and (b) what it definitely will do
is make ECs harder to understand and reason about. If we develop a
separate mechanism that can infer things from inequalities, and it only
kicks in when there are some inequalities, that might work out okay.
But because of that, I don't even like the 0001 patch in this series.
I've not looked at the subsequent ones.

I don't think 0001 is right either, although maybe for somewhat
different reasons. First, I think it only considers VAR OP CONST style
clauses, but that is leaving money on the table, because given a.x =
b.x AND mumble(a.x), we can decide to instead test mumble(b.x) if the
equality operator in question has is-binary-identical semantics. It
does not seem necessary for a first patch to deal with both that and
the somewhat more pleasing case where we're making deductions based on
operator families ... but we shouldn't commit to a design for the VAR
OP CONST case without understanding how it could be generalized.
Second, it looks to me like the patch takes the rather naive strategy
of enforcing the derived clauses everywhere that they can legally be
put, which seems certain not to be optimal.

I don't know whether attaching something to the equivalence class data
structure is the right idea or not. Presumably, we don't want to make
an extra pass over the query tree to gather the information needed for
this kind of optimization, and it feels like we need to know which
vars are EMs before we try to derive alternate/additional quals. So I
guess we'd want to study clauses for possible use by this kind of
mechanism after we've derived ECs but before we do any costing stuff,
yet without introducing a whole new pass. Once we do derive that
information, where are we going to put it? We have to be able to tell
efficiently when looking at a baserel whether there are any implied
inequalities that we should be thinking about ... and there's nothing
obvious tying all of the relevant places together other than the EM.
But I'm kind of blathering here: I feel like there are a lot of
complexities I haven't thought hard enough about to have an
intelligent opinion.

--
Robert Haas
EDB: http://www.enterprisedb.com

#28Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#27)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

Robert Haas <robertmhaas@gmail.com> writes:

I agree. My question is: why shouldn't every case where we can deduce
an implied inequality be reasonably likely to show a benefit?

Maybe it will be, if we can deal with the issue you already mentioned
about not misestimating the resulting partially-redundant conditions.

Second, it looks to me like the patch takes the rather naive strategy
of enforcing the derived clauses everywhere that they can legally be
put, which seems certain not to be optimal.

I'm not sure about that ... it's basically what we do with derived
equalities. However, there's enough structure in the equivalence-class
case that we don't end up enforcing redundant quals. It's not clear
to me whether the same can be said here.

I don't know whether attaching something to the equivalence class data
structure is the right idea or not. Presumably, we don't want to make
an extra pass over the query tree to gather the information needed for
this kind of optimization, and it feels like we need to know which
vars are EMs before we try to derive alternate/additional quals.

Yeah, we don't want to make an additional pass over the tree, and
we also would rather not add an additional set of per-operator
catalog lookups. We might be able to generalize the code that looks
for equality operators so that it looks for "any btree operator"
with the same number of lookups, and then have it feed the results
down either the EquivalenceClass path or the inequality path
as appropriate. At the end, after we've formed all the ECs, we
could have a go at matching up the inequality structures with the
ECs. But I don't agree that ECs are a necessary prerequisite.
Here are a couple of other patterns that might be worth looking for:

* "a > b AND b > c" allows deducing "a > c", whether or not any
of those values appears in an EC.

* "a > const1 AND a > const2" can be simplified to either "a > const1"
or "a > const2" depending on which constant is larger. (The predicate
proof mechanism already has a form of this, but we don't typically
apply it in a way that would result in dropping the redundant qual.)

It's entirely possible that one or both of these patterns is not
worth looking for. But I would say that it's equally unproven
that deriving "a > c" from "a = b AND b > c" is worth the cycles.
I'll grant that it's most likely going to be a win if we can use
any of these patterns to generate a restriction clause from what
had been join clauses. Beyond that it's much less clear.

regards, tom lane

#29Andy Fan
zhihui.fan1213@gmail.com
In reply to: Tom Lane (#26)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

Thanks Tom for joining.

I'm not in favor of complicating the EquivalenceClass
mechanism for this, because .... (b) what it definitely will do
is make ECs harder to understand and reason about.

I'm not willing to show opposition on purpose, and I'm not insist on current
strategy, but I can't understand the comment here, not sure how others.
So I just point it out. IMO, the semantics of ec_filter is that every EMs
in this
EC can have this filter. I do like this method very much. If we need
something
to improve that, it may be the content in ec_filter is not generic
enough. For example:

select * from t1, t2 where t1.a = t2.a and t2.a > 3;

Then the EC filter is "t2.a > 3". Why is it a "t2.a" rather than a more
generic type to show "any EM" in this EC, I can double check the
patch to see if this can be any helpful.

Maybe I'm focusing on the current situation too much, could you describe
more about the badness of this semantics level?

If we develop a
separate mechanism that can infer things from inequalities, and it

_only_

kicks in when there are some inequalities, that might work out okay.

I will try to make this part clearer. The current mechanism includes 3
steps.
1). Gather the inequalities_qual_list during the deconstruct_jointree. 2).
After the root->eq_classes is built, scan each of the above quals to find
out if there is an EC match, if yes, add it to the EC. There are some
fast paths here.
3). compose the qual in ec_filter and members in ec_members, then
distribute it to the relations.

Step 1 would make sure only inequalities is checked. Are you unhappy with
the
cost of step 2 here? for the case like

SELECT * FROM t1, t2 WHERE t1.a = t2.a AND t1.b > 3;

we have to go step 2 and get nothing finally. As for the case like "FROM
t1, t2, t3
WHERE t1.a = t2.a and t3.c > 3". t3.c > 3 can be discard quickly with
EC->relids checking.

But because of that, I don't even like the 0001 patch in this series.

I've not looked at the subsequent ones.

I agree with 0001 patch should be the first one to reach an agreement .

--
Best Regards
Andy Fan

#30Andy Fan
zhihui.fan1213@gmail.com
In reply to: Robert Haas (#27)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

I don't think 0001 is right either, although maybe for somewhat
different reasons. First, I think it only considers VAR OP CONST style
clauses, but that is leaving money on the table, because given a.x =
b.x AND mumble(a.x), we can decide to instead test mumble(b.x) if the
equality operator in question has is-binary-identical semantics. It
does not seem necessary for a first patch to deal with both that and
the somewhat more pleasing case where we're making deductions based on
operator families ... but we shouldn't commit to a design for the VAR
OP CONST case without understanding how it could be generalized.

I can follow up with this and +1 with the statement.

Second, it looks to me like the patch takes the rather naive strategy
of enforcing the derived clauses everywhere that they can legally be
put, which seems certain not to be optimal.

If we can have some agreement (after more discussion) the EC filter is
acceptable on semantics level, I think we may have some chances to
improve something at execution level.

--
Best Regards
Andy Fan

#31Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#28)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

On Tue, Mar 1, 2022 at 9:05 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

I agree. My question is: why shouldn't every case where we can deduce
an implied inequality be reasonably likely to show a benefit?

Maybe it will be, if we can deal with the issue you already mentioned
about not misestimating the resulting partially-redundant conditions.

OK.

Second, it looks to me like the patch takes the rather naive strategy
of enforcing the derived clauses everywhere that they can legally be
put, which seems certain not to be optimal.

I'm not sure about that ... it's basically what we do with derived
equalities. However, there's enough structure in the equivalence-class
case that we don't end up enforcing redundant quals. It's not clear
to me whether the same can be said here.

I mean, to go back to the example of a.x < 42 and a.x = b.x, there are
three possible choices as to where to enforce the qual (a, b, both).
That's a meaningful choice, independent of any estimation issue. I
think it is reasonably common to have cases where a.x < 42 is very
selective and b.x < 42 hardly filters out anything at all, or the
other way around. Certainly, that kind of situation came up a lot in
PostgreSQL-based applications that I wrote myself back in the day. If
we're just talking about btree operators, *maybe* we can say it's
cheap enough that we don't care, but color me a tad skeptical.

I don't know whether attaching something to the equivalence class data
structure is the right idea or not. Presumably, we don't want to make
an extra pass over the query tree to gather the information needed for
this kind of optimization, and it feels like we need to know which
vars are EMs before we try to derive alternate/additional quals.

Yeah, we don't want to make an additional pass over the tree, and
we also would rather not add an additional set of per-operator
catalog lookups. We might be able to generalize the code that looks
for equality operators so that it looks for "any btree operator"
with the same number of lookups, and then have it feed the results
down either the EquivalenceClass path or the inequality path
as appropriate. At the end, after we've formed all the ECs, we
could have a go at matching up the inequality structures with the
ECs.

Interesting idea.

But I don't agree that ECs are a necessary prerequisite.
Here are a couple of other patterns that might be worth looking for:

* "a > b AND b > c" allows deducing "a > c", whether or not any
of those values appears in an EC.

* "a > const1 AND a > const2" can be simplified to either "a > const1"
or "a > const2" depending on which constant is larger. (The predicate
proof mechanism already has a form of this, but we don't typically
apply it in a way that would result in dropping the redundant qual.)

It's entirely possible that one or both of these patterns is not
worth looking for. But I would say that it's equally unproven
that deriving "a > c" from "a = b AND b > c" is worth the cycles.
I'll grant that it's most likely going to be a win if we can use
any of these patterns to generate a restriction clause from what
had been join clauses. Beyond that it's much less clear.

Pretty much all of the cases that I've run across involve an equijoin
plus an inequality, so if somebody asked me which problem we ought to
put most effort into solving, I'd say that one. Cases like "a>1 and
a>2" or a same-table case like "a=b and b>3" haven't been as common in
my experience, and haven't caused as much trouble when they do happen.
Part of that is because if you have something like "a>1 and a>2" in
your query, it may be easy for you to just tweak the query generation
to avoid it, and if "a=b and b>3" is coming up a lot, you may choose
to adjust your data model (e.g. choose to store NULL in b to indicate
same-as-a), whereas if you have something like
"orders.orderno=order_lines.orderno and order_lines.orderno<10000,"
what are you going to do to avoid that exactly? If you normalize your
order data and then want to find the old orders, this problem arises
ineluctably.

But having said that, I'm not *against* doing something about those
cases if it's cheap or falls out naturally. If we could detect for
free that the user had written a>1 and a>2, it'd certainly be
beneficial to drop the former qual and keep only the latter. If the
user writes a>b and b>c and all those columns are in one table I don't
see how it helps to derive a>c, because we're still going to need to
check the other two quals anyway so we've just created more work. But
if those columns are not all in the same table then I'd say chances
are really pretty good. Like, suppose it's x.a>y.b and y.b>x.c. Well,
like I say, I don't really see people writing queries like that
myself, but if they do, it seems pretty obvious that deriving x.a>x.c
has the potential to save a LOT of trouble. If it's x.a>y.b and
y.b>z.c I don't feel quite so optimistic, but it may be that we would
like to do the x-z join first, and if we do, enforcing x.a>z.c at that
level to shrink the join product seems like a very strong idea. It is
a slight loss if we run that qual on lots of rows and it never fails,
but it is a gigantic win if it filters out a bunch of stuff. I bet a
lot of users would be VERY happy to pay the cost of testing x.a>z.c at
the x-z join level even on queries where the statistics suggest that
it will be entirely useless, because it won't cost that much to check
it, and if by some chance the statistics are misleading, it might
prevent a really bad outcome where the query runs for a super-long
time and they get paged. So the questions in my mind here are all
about whether we can detect this stuff cheaply and whether anybody
wants to do the work to make it happen, not whether we'd get a benefit
in the cases where it kicks in.

--
Robert Haas
EDB: http://www.enterprisedb.com

#32Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#31)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

Robert Haas <robertmhaas@gmail.com> writes:

So the questions in my mind here are all
about whether we can detect this stuff cheaply and whether anybody
wants to do the work to make it happen, not whether we'd get a benefit
in the cases where it kicks in.

Right, my worries are mostly about the first point.

regards, tom lane

#33Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#32)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

On Wed, Mar 2, 2022 at 11:09 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

So the questions in my mind here are all
about whether we can detect this stuff cheaply and whether anybody
wants to do the work to make it happen, not whether we'd get a benefit
in the cases where it kicks in.

Right, my worries are mostly about the first point.

OK, cool.

--
Robert Haas
EDB: http://www.enterprisedb.com

#34Andy Fan
zhihui.fan1213@gmail.com
In reply to: Robert Haas (#33)
5 attachment(s)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

On Thu, Mar 3, 2022 at 1:29 AM Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Mar 2, 2022 at 11:09 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

So the questions in my mind here are all
about whether we can detect this stuff cheaply and whether anybody
wants to do the work to make it happen, not whether we'd get a benefit
in the cases where it kicks in.

Right, my worries are mostly about the first point.

OK, cool.

I have finished the PoC for planning timing improvement and joinrel rows
estimation.
the design considers the requirement we can enforce any corrective quals
during
execution (rather than must execute the RestirctInfo which user provides),
but
nothing is coded for that part so far.

Copy the commit message here for easy discussion.

== Planning timing part ==
Patch 1: expand the duties of check_mergejoinable to check non-equal btree
operators as well to support the EC Filter function. A new field
named btreeineqopfamilies is added in RestictInfo and it is set
with the same round syscache search for check_mergejoinable. Because
of this, check_mergejoinable is renamed to check_btreeable.
The bad part of this is it only works for opclause so far.

Patch 2: Introduce ec_filters in EquivalenceClass struct, the semantics
is that the quals can
be applied to any EquivalenceMember in this EC. Later this information
is used
to generate new RestrictInfo and was distributed to related RelOptInfo
very
soon. There are 3 major steps here:

a). In distribute_qual_to_rels to gather the ineq quallist.
b). After deconstruct_jointree, distribute_filter_quals_to_eclass
distributes
these ineq-quallist to the related EC's ef_filters.
c). generate_base_implied_equalities_no_const scan the ec_filters and
distribute
the restrictinfo to related RelOptInfo.

Patch 3: Reduce some planning cost for deriving qual for EC filter
feature.
Mainly changes include:
1. Check if the qual is simple enough by checking rinfo->right_relids
and
info->right_relids, save the pull_varnos of rinfo->clause calls.
2. check contain_volatile_functions against RestrictInfo, so that
the result can be shared with following calls.
3. By employing the RestictInfo->btreeineqfamility which is calculating.
with the same round of calculating RestrictInfo->mergeopfamilies. In
this
way we save some calls for syscache.
4. Calculating the opfamility and amstrategy at
distribute_filter_quals_to_eclass and cache the results in
EquivalenceFilter.
if no suitable opfamility and amstrategy are found, bypass the qual
immediately
and at last using the cached value
generate_base_implied_equalities_no_const.

After this change, there is a testcase changed unexpectedly in
equivclass.out
(compared with Patch-2 expectation file.)

create user regress_user_ectest;
grant select on ec0 to regress_user_ectest;
grant select on ec1 to regress_user_ectest;

set session authorization regress_user_ectest;

-- with RLS active, the non-leakproof a.ff = 43 clause is not treated
-- as a suitable source for an EquivalenceClass; currently, this is true
-- even though the RLS clause has nothing to do directly with the EC
explain (costs off)
regression-> select * from ec0 a, ec1 b
regression-> where a.ff = b.ff and a.ff = 43::bigint::int8alias1;

The b.ff = 43 has disappeared from ec1 b. But since it even didn't shown
before the EC filter, so I'm not sure my changes here make something
wrong,
maybe fix an issue by accident?

== Join Rel size estimation part ==

I have revist the strategy before, the main reasons are 1). we should
consider every
qual *equally*. 2). In the past, I just wanted to get the same result as
ec filters doesn't
happen, but later I found that even if there is no ec filter, we still
have some estimation error
clearly. for example:

create table ec_t1000 (a int);
insert into ec_t1000 select i from generate_series(1, 1000)i;
create table ec_t110 (a int);
insert into ec_t110 select i from generate_series(1, 110) i;
create table ec_t200 (a int);
insert into ec_t200 select i from generate_series(1, 200) i;
analyze ec_t1000, ec_t110, ec_t200;

query 1: explain select * from ec_t1000, ec_t110 where ec_t1000.a =
ec_t110.a and ec_t1000.a > 100; -- (0.9)

query 2: explain select * from ec_t1000, ec_t110 where ec_t1000.a =
ec_t110.a and ec_t110.a > 100; -- (0.1)

query 3: explain select * from ec_t1000, ec_t110 , ec_t200 where ec_t1000.a
= ec_t110.a and ec_t110.a = ec_t200.a and ec_t1000.a > 100;

query 4: explain select * from ec_t1000, ec_t110 , ec_t200 where ec_t1000.a
= ec_t110.a and ec_t110.a = ec_t200.a and ec_t110.a > 100;

query 5: explain select * from ec_t1000, ec_t110 , ec_t200 where ec_t1000.a
= ec_t110.a and ec_t110.a = ec_t200.a and ec_t200.a > 100;

we can see query 1 is the same as query 2, and query 3/4/5 should be the
same as well. The fact
is not. Here is the result on the current master and patched version.

| Query Id | Real rows | Est. Rows at master | Est. rows with patched |
|----------+-----------+---------------------+------------------------|
| 1 | 10 | 99 | 10 |
| 2 | 10 | 10 | 10 |
| 3 | 10 | 20 | 11 |
| 4 | 10 | 2 | 11 |
| 5 | 10 | 11 | 11 |

Patch 4: Prepare the code for CorrectiveQual structure.
Just refactor the method for 2-level loop in
generate_base_implied_equalities_no_const, no other things are changed.

Patch 5: struct CorrectiveQuals is as simple as a List of RestrictInfo,
the properties
of it are: a). only one restrictinfo on this group should be counted for
any joinrel
size estimation. b). at least 1 restrictinfo in this group should be
executed during
execution. In this commit, only the size estimation issue is tried.

PlannerInfo.correlative_quals is added to manage all the
CorrectiveQuals at
subquery level. RelOptInfo.cqual_indexes is a List * to indicate a which
CorrectiveQuals this relation is related to. This is designed for easy
to check if
the both sides of joinrel correlated to the same CorrectiveQuals. The
reason why
"List *" will be explained later.

The overall design of handing the joinrel size estimation is:
a). At the base relation level, we just count everything with the
correlative
quals. b). During any level joinrel size estimation, we just keep 1
side's
cqual (short for corrective qual) selectivity by eliminating the other
one. so
the size estimation for a mergeable join selectivity becomes to:

rows = R1.rows X r2.rows X 1 / Max (ndistval_of_colA,
ndistinval_of_colB) X 1 /
Selectivity(R1's CorrectiveQual).

r1.rows X 1 / Selectivity(R1's CorrectiveQual) eliminates the impact of
CorrectiveQual on R1. After this, the JoinRel of (R1, R2) still be
impacted by
this CorrectiveQual but "just once" in this level. Later if
JoinRel(R1, R2) needs
to join with R3, and R3 is impacted by this CorectiveQuals as well. We
need to keep one and eliminate the other one as above again.

The algorithm for which Selectivity should be eliminated and which one
should be
kept is:

When we join 2 inner_rel and outer_rel with a mergeable join
restrictinfo, if
both sides is impacted with the same CorrectiveQual, we first choose
which "side"
to eliminating based on which side of the restrictinfo has a higher
distinct
value. The reason for this is more or less because we used
"Max"(ndistinctValT1,
ndistinctValT2). After deciding which "side" to eliminate, the real
eliminating
selectivity is RelOptInfo->cqual_selectivity[n], The left one still
takes effect
and is noted in the joinrel->cqual_selectivitity[n].

Introduction of RelOptInfo->cqual_selectivity:

The number of elements in cqual_selecitity equals
the length of cqual_indexes. The semantics is which
Selectivity in the corresponding CorectiveQuals's qual
list is taking effect. At any time, only 1 Qual
Selectivity is counted for any-level of joinrel size estimation.

In reality, it is possible to have many CorrectiveQuals, but for design
discussion, the current implementation only takes care of the 1
CorrectiveQuals.
This would be helpful for PoC/review/discussion.

Some flow for the key data:

1. root->corrective_quals is initialized at
generate_base_implied_equalities_no_const stage. we create a
CorrectiveQual in
this list for each ec_filter and fill the RestrictInfo part for it. At
the same time, we note that which RelOptInfo (cqual_indexes) is related
to this cqual.

2. RelOptInfo->cqual_selecitity for baserel is set at the end of
set_rel_size,
at this time, the selectivity for every RestrictInfo is calculated, we
can just
fetch the cached value. As for joinrel, it is maintained in
calc_join_cqual_selectivity, this function would return the Selectivity
to
eliminate and set the above value.

Limitation in this PoC:
1. Only support 1 CorrectiveQual in root->correlative_quals
2. Only tested with INNER_JOIN.
3. Inherited tables are not supported.

I find it is hard to explain things clearly without the code. Any feedback
is welcome.

--
Best Regards
Andy Fan

Attachments:

v3-0003-Reduce-some-planning-cost-for-deriving-qual-for-E.patchapplication/x-patch; name=v3-0003-Reduce-some-planning-cost-for-deriving-qual-for-E.patchDownload
From ad2b12e2fb96a950cb97f8251a7825f061da16ef Mon Sep 17 00:00:00 2001
From: Andy Fan <yizhi.fzh@alibaba-inc.com>
Date: Sun, 6 Mar 2022 14:20:55 +0800
Subject: [PATCH v3 3/5] Reduce some planning cost for deriving qual for EC
 filter feature.

Mainly changes includes:
1. Check if the qual is simple enough by checking rinfo->right_relids and
info->right_relids, save the pull_varnos of rinfo->clause calls.
2. check contain_volatile_functions against RestrictInfo, so that
the result can be shared with following calls.
3. By employing the RestictInfo->btreeineqfamility which is calculating.
with same round of calculating  RestrictInfo->mergeopfamilies. In this
way we save the some calls for syscache.
4. Calculating the opfamility and amstrategy at
distribute_filter_quals_to_eclass and cache the results in EquivalenceFilter.
if no suitable opfamility and amstrategy are found, bypass the qual immediately
and at last using the cached value generate_base_implied_equalities_no_const.

After this change, there is an testcase changed unexpectedly in equivclass.out
(compared with David's expectation file.)

create user regress_user_ectest;
grant select on ec0 to regress_user_ectest;
grant select on ec1 to regress_user_ectest;

set session authorization regress_user_ectest;

-- with RLS active, the non-leakproof a.ff = 43 clause is not treated
-- as a suitable source for an EquivalenceClass; currently, this is true
-- even though the RLS clause has nothing to do directly with the EC
explain (costs off)
regression->   select * from ec0 a, ec1 b
regression->   where a.ff = b.ff and a.ff = 43::bigint::int8alias1;

The b.ff = 43 is disappeared from ec1 b. But since it even didn't shown
before the EC filter, so I'm not sure my changes here make something wrong,
maybe fix a issue by accidental?
---
 src/backend/nodes/outfuncs.c             |  2 +
 src/backend/optimizer/path/equivclass.c  | 59 +++++++++++++-----------
 src/backend/optimizer/plan/initsplan.c   | 50 ++++----------------
 src/include/nodes/pathnodes.h            |  2 +
 src/test/regress/expected/equivclass.out |  6 +--
 5 files changed, 48 insertions(+), 71 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 4c384511c39..d29b64eb918 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2513,6 +2513,8 @@ _outEquivalenceFilter(StringInfo str, const EquivalenceFilter *node)
 	WRITE_OID_FIELD(ef_opno);
 	WRITE_BOOL_FIELD(ef_const_is_left);
 	WRITE_UINT_FIELD(ef_source_rel);
+	WRITE_OID_FIELD(opfamily);
+	WRITE_INT_FIELD(amstrategy);
 }
 
 static void
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index f9ae2785d60..915888a8486 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -1252,19 +1252,17 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 }
 
 /*
- * finds the opfamily and strategy number for the specified 'opno' and 'method'
- * access method. Returns True if one is found and sets 'family' and
- * 'amstrategy', or returns False if none are found.
+ * finds the operator id for the specified 'opno' and 'method' and 'opfamilies'
+ * Returns True if one is found and sets 'opfamily_p' and 'amstrategy_p' or returns
+ * False if none are found.
  */
 static bool
-find_am_family_and_stategy(Oid opno, Oid method, Oid *family, int *amstrategy)
+find_am_family_and_stategy(Oid opno, Oid method, List *opfamilies,
+						   Oid *opfamily_p, int *amstrategy_p)
 {
-	List *opfamilies;
 	ListCell *l;
 	int strategy;
 
-	opfamilies = get_opfamilies(opno, method);
-
 	foreach(l, opfamilies)
 	{
 		Oid opfamily = lfirst_oid(l);
@@ -1273,8 +1271,8 @@ find_am_family_and_stategy(Oid opno, Oid method, Oid *family, int *amstrategy)
 
 		if (strategy)
 		{
-			*amstrategy = strategy;
-			*family = opfamily;
+			*opfamily_p = opfamily;
+			*amstrategy_p = strategy;
 			return true;
 		}
 	}
@@ -1363,17 +1361,11 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 			EquivalenceFilter *ef = (EquivalenceFilter *) lfirst(lc2);
 			Expr *leftexpr;
 			Expr *rightexpr;
-			int strategy;
 			Oid opno;
-			Oid family;
 
 			if (ef->ef_source_rel == relid)
 				continue;
 
-			if (!find_am_family_and_stategy(ef->ef_opno, BTREE_AM_OID,
-				&family, &strategy))
-				continue;
-
 			if (ef->ef_const_is_left)
 			{
 				leftexpr = (Expr *) ef->ef_const;
@@ -1385,10 +1377,10 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 				rightexpr = (Expr *) ef->ef_const;
 			}
 
-			opno = get_opfamily_member(family,
+			opno = get_opfamily_member(ef->opfamily,
 										exprType((Node *) leftexpr),
 										exprType((Node *) rightexpr),
-										strategy);
+										ef->amstrategy);
 
 			if (opno == InvalidOid)
 				continue;
@@ -1399,7 +1391,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 										rightexpr,
 										bms_copy(ec->ec_relids),
 										bms_copy(cur_em->em_nullable_relids),
-									 	ec->ec_min_security,
+										ec->ec_min_security,
 										ec->ec_below_outer_join,
 										false);
 		}
@@ -2007,9 +1999,12 @@ distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
 	 */
 	foreach(l, quallist)
 	{
-		OpExpr	   *opexpr = (OpExpr *) lfirst(l);
-		Expr	   *leftexpr = (Expr *) linitial(opexpr->args);
-		Expr	   *rightexpr = (Expr *) lsecond(opexpr->args);
+		RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
+		OpExpr *opexpr = (OpExpr *)(rinfo->clause);
+
+		Oid opfamily;
+		int amstrategy;
+
 		Const	   *constexpr;
 		Expr	   *varexpr;
 		Relids		exprrels;
@@ -2021,25 +2016,31 @@ distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
 		 * Determine if the the OpExpr is in the form "expr op const" or
 		 * "const op expr".
 		 */
-		if (IsA(leftexpr, Const))
+		if (bms_is_empty(rinfo->left_relids))
 		{
-			constexpr = (Const *) leftexpr;
-			varexpr = rightexpr;
+			constexpr = (Const *) get_leftop(rinfo->clause);
+			varexpr = (Expr *) get_rightop(rinfo->clause);
 			const_isleft = true;
+			exprrels = rinfo->right_relids;
 		}
 		else
 		{
-			constexpr = (Const *) rightexpr;
-			varexpr = leftexpr;
+			constexpr = (Const *) get_rightop(rinfo->clause);
+			varexpr = (Expr *) get_leftop(rinfo->clause);
 			const_isleft = false;
+			exprrels = rinfo->left_relids;
 		}
 
-		exprrels = pull_varnos(root, (Node *) varexpr);
-
 		/* should be filtered out, but we need to determine relid anyway */
 		if (!bms_get_singleton_member(exprrels, &relid))
 			continue;
 
+		if (!find_am_family_and_stategy(opexpr->opno, BTREE_AM_OID,
+										rinfo->btreeineqopfamilies,
+										&opfamily,
+										&amstrategy))
+			continue;
+
 		/* search for a matching eclass member in all eclasses */
 		foreach(l2, root->eq_classes)
 		{
@@ -2075,6 +2076,8 @@ distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
 					efilter->ef_const_is_left = const_isleft;
 					efilter->ef_opno = opexpr->opno;
 					efilter->ef_source_rel = relid;
+					efilter->opfamily = opfamily;
+					efilter->amstrategy = amstrategy;
 
 					ec->ec_filters = lappend(ec->ec_filters, efilter);
 					break;		/* Onto the next eclass */
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 7e355c94362..5a32c3987a0 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -651,44 +651,6 @@ create_lateral_join_info(PlannerInfo *root)
 	}
 }
 
-/*
- * is_simple_filter_qual
- *             Analyzes an OpExpr to determine if it may be useful as an
- *             EquivalenceFilter. Returns true if the OpExpr may be of some use, or
- *             false if it should not be used.
- */
-static bool
-is_simple_filter_qual(PlannerInfo *root, OpExpr *expr)
-{
-	Expr *leftexpr;
-	Expr *rightexpr;
-
-	if (!IsA(expr, OpExpr))
-			return false;
-
-	if (list_length(expr->args) != 2)
-			return false;
-
-	leftexpr = (Expr *) linitial(expr->args);
-	rightexpr = (Expr *) lsecond(expr->args);
-
-	/* XXX should we restrict these to simple Var op Const expressions? */
-	if (IsA(leftexpr, Const))
-	{
-		if (bms_membership(pull_varnos(root, (Node *) rightexpr)) == BMS_SINGLETON &&
-			!contain_volatile_functions((Node *) rightexpr))
-			return true;
-	}
-	else if (IsA(rightexpr, Const))
-	{
-		if (bms_membership(pull_varnos(root, (Node *) leftexpr)) == BMS_SINGLETON &&
-			!contain_volatile_functions((Node *) leftexpr))
-			return true;
-	}
-
-	return false;
-}
-
 /*****************************************************************************
  *
  *	  JOIN TREE PROCESSING
@@ -1678,6 +1640,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	bool		maybe_outer_join;
 	Relids		nullable_relids;
 	RestrictInfo *restrictinfo;
+	int	relid;
 
 	/*
 	 * Retrieve all relids mentioned within the clause.
@@ -2027,8 +1990,15 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	distribute_restrictinfo_to_rels(root, restrictinfo);
 
 	/* Check if the qual looks useful to harvest as an EquivalenceFilter */
-	if (filter_qual_list != NULL && is_simple_filter_qual(root, (OpExpr *) clause))
-		*filter_qual_list = lappend(*filter_qual_list, clause);
+	if (filter_qual_list != NULL &&
+		is_opclause(restrictinfo->clause) &&
+		!contain_volatile_functions((Node *)restrictinfo) && // Cachable
+		restrictinfo->btreeineqopfamilies != NIL &&  /* ineq expression */
+		/* simple & common enough filter, one side references one relation and the other one is a constant */
+		((bms_is_empty(restrictinfo->left_relids) && bms_get_singleton_member(restrictinfo->right_relids, &relid)) ||
+		 (bms_is_empty(restrictinfo->right_relids) && bms_get_singleton_member(restrictinfo->left_relids, &relid)))
+		)
+		*filter_qual_list = lappend(*filter_qual_list, restrictinfo);
 }
 
 /*
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index dcae69635ce..e7b04211839 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1037,6 +1037,8 @@ typedef struct EquivalenceFilter
 	Oid			ef_opno;		/* Operator Oid of filter operator */
 	bool		ef_const_is_left; /* Is the Const on the left of the OpExrp? */
 	Index		ef_source_rel;	/* relid of originating relation. */
+	Oid			opfamily;
+	int			amstrategy;
 } EquivalenceFilter;
 
 /*
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index 92fcec1158b..980bd3817d3 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -407,14 +407,14 @@ set session authorization regress_user_ectest;
 explain (costs off)
   select * from ec0 a, ec1 b
   where a.ff = b.ff and a.ff = 43::bigint::int8alias1;
-                              QUERY PLAN                              
-----------------------------------------------------------------------
+                 QUERY PLAN                  
+---------------------------------------------
  Nested Loop
    ->  Index Scan using ec0_pkey on ec0 a
          Index Cond: (ff = '43'::int8alias1)
    ->  Index Scan using ec1_pkey on ec1 b
          Index Cond: (ff = a.ff)
-         Filter: ((f1 < '5'::int8alias1) AND (ff = '43'::int8alias1))
+         Filter: (f1 < '5'::int8alias1)
 (6 rows)
 
 reset session authorization;
-- 
2.21.0

v3-0004-Prepare-the-code-for-CorrectiveQual-structure.patchapplication/x-patch; name=v3-0004-Prepare-the-code-for-CorrectiveQual-structure.patchDownload
From 732f6a9a4b4f702d46866c7441b7addedcc0c00d Mon Sep 17 00:00:00 2001
From: Andy Fan <yizhi.fzh@alibaba-inc.com>
Date: Mon, 7 Mar 2022 20:17:42 +0800
Subject: [PATCH v3 4/5] Prepare the code for CorrectiveQual structure.

Just refactor the method for 2-level loop in
generate_base_implied_equalities_no_const, no other things is changed.
---
 src/backend/optimizer/path/equivclass.c | 61 +++++++++++++++----------
 1 file changed, 37 insertions(+), 24 deletions(-)

diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 915888a8486..6b638d184aa 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -1351,17 +1351,33 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 				rinfo->right_em = cur_em;
 			}
 		}
+		prev_ems[relid] = cur_em;
+	}
 
-		/*
-		 * Also push any EquivalenceFilter clauses down into all relations
-		 * other than the one which the filter actually originated from.
-		 */
-		foreach(lc2, ec->ec_filters)
+	pfree(prev_ems);
+
+
+	/*
+	 * Also push any EquivalenceFilter clauses down into all relations
+	 * other than the one which the filter actually originated from.
+	 */
+	foreach(lc2, ec->ec_filters)
+	{
+		EquivalenceFilter *ef = (EquivalenceFilter *) lfirst(lc2);
+		Expr *leftexpr;
+		Expr *rightexpr;
+		Oid opno;
+		int relid;
+
+		if (ec->ec_broken)
+			break;
+
+		foreach(lc, ec->ec_members)
 		{
-			EquivalenceFilter *ef = (EquivalenceFilter *) lfirst(lc2);
-			Expr *leftexpr;
-			Expr *rightexpr;
-			Oid opno;
+			EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc);
+
+			if (!bms_get_singleton_member(cur_em->em_relids, &relid))
+				continue;
 
 			if (ef->ef_source_rel == relid)
 				continue;
@@ -1378,29 +1394,26 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 			}
 
 			opno = get_opfamily_member(ef->opfamily,
-										exprType((Node *) leftexpr),
-										exprType((Node *) rightexpr),
-										ef->amstrategy);
+									   exprType((Node *) leftexpr),
+									   exprType((Node *) rightexpr),
+									   ef->amstrategy);
 
 			if (opno == InvalidOid)
 				continue;
 
+
 			process_implied_equality(root, opno,
-										ec->ec_collation,
-										leftexpr,
-										rightexpr,
-										bms_copy(ec->ec_relids),
-										bms_copy(cur_em->em_nullable_relids),
-										ec->ec_min_security,
-										ec->ec_below_outer_join,
-										false);
+									 ec->ec_collation,
+									 leftexpr,
+									 rightexpr,
+									 bms_copy(ec->ec_relids),
+									 bms_copy(cur_em->em_nullable_relids),
+									 ec->ec_min_security,
+									 ec->ec_below_outer_join,
+									 false);
 		}
-
-		prev_ems[relid] = cur_em;
 	}
 
-	pfree(prev_ems);
-
 	/*
 	 * We also have to make sure that all the Vars used in the member clauses
 	 * will be available at any join node we might try to reference them at.
-- 
2.21.0

v3-0001-expand-the-duties-of-check_mergejoinable-to-check.patchapplication/x-patch; name=v3-0001-expand-the-duties-of-check_mergejoinable-to-check.patchDownload
From 067a720aeb273372aa536f0620ec53f022d40d85 Mon Sep 17 00:00:00 2001
From: Andy Fan <yizhi.fzh@alibaba-inc.com>
Date: Mon, 7 Mar 2022 10:07:44 +0800
Subject: [PATCH v3 1/5] expand the duties of check_mergejoinable to check
 non-equal btree

operator as well to support the EC Filter function.  A new filed
named btreeineqopfamilies is added in RestictInfo and it is set
with the same round syscache search for check_mergejoinable. because
of this, check_mergejoinable is renamed to check_btreeable.

The bad part of this is it only works for opclause so far.
---
 src/backend/optimizer/plan/initsplan.c    | 33 ++++++++++-----------
 src/backend/optimizer/util/restrictinfo.c |  1 +
 src/backend/utils/cache/lsyscache.c       | 35 +++++++++++++++++++++++
 src/include/nodes/pathnodes.h             |  1 +
 src/include/utils/lsyscache.h             |  1 +
 5 files changed, 55 insertions(+), 16 deletions(-)

diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 023efbaf092..d61419f61a5 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -76,7 +76,7 @@ static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p,
 static bool check_equivalence_delay(PlannerInfo *root,
 									RestrictInfo *restrictinfo);
 static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
-static void check_mergejoinable(RestrictInfo *restrictinfo);
+static void check_btreeable(RestrictInfo *restrictinfo);
 static void check_hashjoinable(RestrictInfo *restrictinfo);
 static void check_memoizable(RestrictInfo *restrictinfo);
 
@@ -1874,8 +1874,11 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	 * We check "mergejoinability" of every clause, not only join clauses,
 	 * because we want to know about equivalences between vars of the same
 	 * relation, or between vars and consts.
+	 *
+	 * We also checked the btree-able properity at the same round of checking
+	 * mergejoinability to support ec filter function.
 	 */
-	check_mergejoinable(restrictinfo);
+	check_btreeable(restrictinfo);
 
 	/*
 	 * If it is a true equivalence clause, send it to the EquivalenceClass
@@ -2389,7 +2392,7 @@ process_implied_equality(PlannerInfo *root,
 	 * from an EquivalenceClass; but we could have reduced the original clause
 	 * to a constant.
 	 */
-	check_mergejoinable(restrictinfo);
+	check_btreeable(restrictinfo);
 
 	/*
 	 * Note we don't do initialize_mergeclause_eclasses(); the caller can
@@ -2456,8 +2459,8 @@ build_implied_join_equality(PlannerInfo *root,
 									 NULL,	/* outer_relids */
 									 nullable_relids);	/* nullable_relids */
 
-	/* Set mergejoinability/hashjoinability flags */
-	check_mergejoinable(restrictinfo);
+	/* Set btreeability/hashjoinability flags */
+	check_btreeable(restrictinfo);
 	check_hashjoinable(restrictinfo);
 	check_memoizable(restrictinfo);
 
@@ -2641,16 +2644,13 @@ match_foreign_keys_to_quals(PlannerInfo *root)
  *****************************************************************************/
 
 /*
- * check_mergejoinable
- *	  If the restrictinfo's clause is mergejoinable, set the mergejoin
- *	  info fields in the restrictinfo.
- *
- *	  Currently, we support mergejoin for binary opclauses where
- *	  the operator is a mergejoinable operator.  The arguments can be
- *	  anything --- as long as there are no volatile functions in them.
+ * check_btreeable
+ *	  If the restrictinfo's clause is btreeable, set the mergejoin
+ *	  info field and btreeineq info field in the restrictinfo. btreeable
+ *	  now is a superset of mergeable.
  */
 static void
-check_mergejoinable(RestrictInfo *restrictinfo)
+check_btreeable(RestrictInfo *restrictinfo)
 {
 	Expr	   *clause = restrictinfo->clause;
 	Oid			opno;
@@ -2666,9 +2666,10 @@ check_mergejoinable(RestrictInfo *restrictinfo)
 	opno = ((OpExpr *) clause)->opno;
 	leftarg = linitial(((OpExpr *) clause)->args);
 
-	if (op_mergejoinable(opno, exprType(leftarg)) &&
-		!contain_volatile_functions((Node *) restrictinfo))
-		restrictinfo->mergeopfamilies = get_mergejoin_opfamilies(opno);
+	if (!contain_volatile_functions((Node *) restrictinfo))
+		get_btree_opfamilies(opno,
+							 &restrictinfo->mergeopfamilies,
+							 &restrictinfo->btreeineqopfamilies);
 
 	/*
 	 * Note: op_mergejoinable is just a hint; if we fail to find the operator
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index ef8df3d098e..e09196d26f6 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -201,6 +201,7 @@ make_restrictinfo_internal(PlannerInfo *root,
 	restrictinfo->outer_selec = -1;
 
 	restrictinfo->mergeopfamilies = NIL;
+	restrictinfo->btreeineqopfamilies = NIL;
 
 	restrictinfo->left_ec = NULL;
 	restrictinfo->right_ec = NULL;
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index feef9998634..92c34ed8a49 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -389,6 +389,41 @@ get_mergejoin_opfamilies(Oid opno)
 	return result;
 }
 
+/*
+ * TODO:  get_mergejoin_opfamilies shoud be replaced with this function.
+ */
+void
+get_btree_opfamilies(Oid opno,
+					 List **mergeable_opfamilies,
+					 List **unmergeable_btree_opfamilies)
+{
+	CatCList   *catlist;
+	int			i;
+
+	/*
+	 * Search pg_amop to see find out all the btree opfamilies.
+	 */
+	catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno));
+
+	for (i = 0; i < catlist->n_members; i++)
+	{
+		HeapTuple	tuple = &catlist->members[i]->tuple;
+		Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
+
+		if (aform->amopmethod == BTREE_AM_OID)
+		{
+			if (aform->amopstrategy == BTEqualStrategyNumber)
+				*mergeable_opfamilies = lappend_oid(*mergeable_opfamilies,
+													aform->amopfamily);
+			else
+				*unmergeable_btree_opfamilies = lappend_oid(*unmergeable_btree_opfamilies,
+															aform->amopfamily);
+		}
+	}
+
+	ReleaseSysCacheList(catlist);
+}
+
 /*
  * get_compatible_hash_operators
  *		Get the OID(s) of hash equality operator(s) compatible with the given
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 1f3845b3fec..3b95e4a8eae 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -2105,6 +2105,7 @@ typedef struct RestrictInfo
 
 	/* valid if clause is mergejoinable, else NIL */
 	List	   *mergeopfamilies;	/* opfamilies containing clause operator */
+	List		*btreeineqopfamilies; /* btree families except the mergeable ones */
 
 	/* cache space for mergeclause processing; NULL if not yet set */
 	EquivalenceClass *left_ec;	/* EquivalenceClass containing lefthand */
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index b8dd27d4a96..5b5fac0397c 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -79,6 +79,7 @@ extern bool get_ordering_op_properties(Oid opno,
 extern Oid	get_equality_op_for_ordering_op(Oid opno, bool *reverse);
 extern Oid	get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type);
 extern List *get_mergejoin_opfamilies(Oid opno);
+extern void get_btree_opfamilies(Oid opno, List **mergeable_opfamilies, List **unmergeable_btree_opfamilies);
 extern bool get_compatible_hash_operators(Oid opno,
 										  Oid *lhs_opno, Oid *rhs_opno);
 extern bool get_op_hash_functions(Oid opno,
-- 
2.21.0

v3-0002-Introudce-ec_filters-in-EquivalenceClass-struct-t.patchapplication/x-patch; name=v3-0002-Introudce-ec_filters-in-EquivalenceClass-struct-t.patchDownload
From 11204bfa8471e50c9ab3f379fe3a28329eeec841 Mon Sep 17 00:00:00 2001
From: David Rowley <dgrowleyml@gmail.com>
Date: Tue, 1 Feb 2022 20:56:40 +0800
Subject: [PATCH v3 2/5] Introudce ec_filters in EquivalenceClass struct, the
 semantics is the quals can

be applied to any EquivalenceMember in this EC. Later this information is used
to generate new RestrictInfo and was distributed to related RelOptInfo very
soon.  There are 3 major steps here:

a). In distribute_qual_to_rels to gather the ineq quallist.
b). After deconstruct_jointree, distribute_filter_quals_to_eclass distribute
these ineq-quallist to the related EC's ef_filters.
c). generate_base_implied_equalities_no_const scan the ec_filters and distriubte
the restrictinfo to related RelOptInfo.

Author: David Rowley at 2015-12 [1]
Andy Fan rebases this patch to current latest code.

https://www.postgresql.org/message-id/CAKJS1f9FK_X_5HKcPcSeimy16Owe3EmPmmGsGWLcKkj_rW9s6A%40mail.gmail.com
---
 .../postgres_fdw/expected/postgres_fdw.out    |  36 ++--
 src/backend/nodes/outfuncs.c                  |  14 ++
 src/backend/optimizer/path/equivclass.c       | 182 ++++++++++++++++++
 src/backend/optimizer/plan/initsplan.c        |  96 +++++++--
 src/backend/utils/cache/lsyscache.c           |  28 +++
 src/include/nodes/nodes.h                     |   1 +
 src/include/nodes/pathnodes.h                 |  37 ++++
 src/include/optimizer/paths.h                 |   1 +
 src/include/utils/lsyscache.h                 |   1 +
 src/test/regress/expected/equivclass.out      |  45 ++++-
 src/test/regress/expected/join.out            |  22 +--
 src/test/regress/expected/partition_join.out  |  52 +++--
 src/test/regress/sql/equivclass.sql           |  12 ++
 13 files changed, 457 insertions(+), 70 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index f210f911880..ce102abe5d5 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -1545,12 +1545,12 @@ SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNE
 -- full outer join + inner join
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
-                                                                                                                                                 QUERY PLAN                                                                                                                                                 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                                           QUERY PLAN                                                                                                                                                                           
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Foreign Scan
    Output: t1.c1, t2.c1, t3.c1
    Relations: ((public.ft4 t1) INNER JOIN (public.ft5 t2)) FULL JOIN (public.ft4 t3)
-   Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint
+   Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND (((r2.c1 + 1) >= 50)) AND (((r2.c1 + 1) <= 60)) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
@@ -2335,12 +2335,12 @@ SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT
 UPDATE ft5 SET c3 = null where c1 % 9 = 0;
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
-                                                                                                                                QUERY PLAN                                                                                                                                 
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                    QUERY PLAN                                                                                                                                                     
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Foreign Scan
    Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2
    Relations: (public.ft5) INNER JOIN (public.ft4)
-   Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST
+   Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)) AND ((r1.c1 >= 10)) AND ((r1.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST
 (4 rows)
 
 SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
@@ -2362,8 +2362,8 @@ SET enable_hashjoin TO false;
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1
     AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE;
-                                                                                                                                                                                                                                                                                                                                                                                                                                               QUERY PLAN                                                                                                                                                                                                                                                                                                                                                                                                                                               
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                                                                                                                                                                                                                                                                                                                                      QUERY PLAN                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  LockRows
    Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ctid
    ->  Merge Join
@@ -2373,7 +2373,7 @@ SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = f
          ->  Foreign Scan
                Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
                Relations: (((public.ft1) INNER JOIN (public.ft2)) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
-               Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r4.c1, r4.c2, r4.c3, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4.c1, r4.c2, r4.c3) END FROM ((("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 3" r3 ON (((r1.c2 = r3.c1)))) INNER JOIN "S 1"."T 4" r4 ON (((r1.c2 = r4.c1)))) ORDER BY r1.c2 ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3 FOR UPDATE OF r4
+               Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r4.c1, r4.c2, r4.c3, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4.c1, r4.c2, r4.c3) END FROM ((("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" < 100)) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 3" r3 ON (((r1.c2 = r3.c1)))) INNER JOIN "S 1"."T 4" r4 ON (((r1.c2 = r4.c1)))) ORDER BY r1.c2 ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3 FOR UPDATE OF r4
                ->  Merge Join
                      Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
                      Merge Cond: (ft1.c2 = ft5.c1)
@@ -2391,12 +2391,12 @@ SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = f
                                              Sort Key: ft1.c1
                                              ->  Foreign Scan on public.ft1
                                                    Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
-                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE
+                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND (("C 1" < 100)) FOR UPDATE
                                        ->  Materialize
                                              Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
                                              ->  Foreign Scan on public.ft2
                                                    Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
-                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
+                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
                            ->  Sort
                                  Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
                                  Sort Key: ft4.c1
@@ -5685,25 +5685,25 @@ UPDATE ft2 AS target SET (c2) = (
 EXPLAIN (VERBOSE, COSTS OFF)
 UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END
   FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000;
-                                                                                                                                                                                       QUERY PLAN                                                                                                                                                                                        
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                                                                   QUERY PLAN                                                                                                                                                                                                    
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Update on public.ft2 d
    Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1
    ->  Foreign Scan
          Output: CASE WHEN (random() >= '0'::double precision) THEN d.c2 ELSE 0 END, d.ctid, d.*, t.*
          Relations: (public.ft2 d) INNER JOIN (public.ft2 t)
-         Remote SQL: SELECT r1.c2, r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r1."C 1" > 1000)))) FOR UPDATE OF r1
-         ->  Hash Join
+         Remote SQL: SELECT r1.c2, r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" > 1000)) AND ((r1."C 1" > 1000)))) FOR UPDATE OF r1
+         ->  Merge Join
                Output: d.c2, d.ctid, d.*, t.*
-               Hash Cond: (d.c1 = t.c1)
+               Merge Cond: (d.c1 = t.c1)
                ->  Foreign Scan on public.ft2 d
                      Output: d.c2, d.ctid, d.*, d.c1
                      Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
-               ->  Hash
+               ->  Materialize
                      Output: t.*, t.c1
                      ->  Foreign Scan on public.ft2 t
                            Output: t.*, t.c1
-                           Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
+                           Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST
 (17 rows)
 
 UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6bdad462c78..4c384511c39 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2504,6 +2504,17 @@ _outEquivalenceMember(StringInfo str, const EquivalenceMember *node)
 	WRITE_OID_FIELD(em_datatype);
 }
 
+static void
+_outEquivalenceFilter(StringInfo str, const EquivalenceFilter *node)
+{
+	WRITE_NODE_TYPE("EQUIVALENCEFILTER");
+
+	WRITE_NODE_FIELD(ef_const);
+	WRITE_OID_FIELD(ef_opno);
+	WRITE_BOOL_FIELD(ef_const_is_left);
+	WRITE_UINT_FIELD(ef_source_rel);
+}
+
 static void
 _outPathKey(StringInfo str, const PathKey *node)
 {
@@ -4306,6 +4317,9 @@ outNode(StringInfo str, const void *obj)
 			case T_EquivalenceMember:
 				_outEquivalenceMember(str, obj);
 				break;
+			case T_EquivalenceFilter:
+				_outEquivalenceFilter(str, obj);
+				break;
 			case T_PathKey:
 				_outPathKey(str, obj);
 				break;
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 8c6770de972..f9ae2785d60 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -19,6 +19,7 @@
 #include <limits.h>
 
 #include "access/stratnum.h"
+#include "catalog/pg_am.h"
 #include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -1250,6 +1251,37 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 	}
 }
 
+/*
+ * finds the opfamily and strategy number for the specified 'opno' and 'method'
+ * access method. Returns True if one is found and sets 'family' and
+ * 'amstrategy', or returns False if none are found.
+ */
+static bool
+find_am_family_and_stategy(Oid opno, Oid method, Oid *family, int *amstrategy)
+{
+	List *opfamilies;
+	ListCell *l;
+	int strategy;
+
+	opfamilies = get_opfamilies(opno, method);
+
+	foreach(l, opfamilies)
+	{
+		Oid opfamily = lfirst_oid(l);
+
+		strategy = get_op_opfamily_strategy(opno, opfamily);
+
+		if (strategy)
+		{
+			*amstrategy = strategy;
+			*family = opfamily;
+			return true;
+		}
+	}
+
+	return false;
+}
+
 /*
  * generate_base_implied_equalities when EC contains no pseudoconstants
  */
@@ -1259,6 +1291,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 {
 	EquivalenceMember **prev_ems;
 	ListCell   *lc;
+	ListCell   *lc2;
 
 	/*
 	 * We scan the EC members once and track the last-seen member for each
@@ -1320,6 +1353,57 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 				rinfo->right_em = cur_em;
 			}
 		}
+
+		/*
+		 * Also push any EquivalenceFilter clauses down into all relations
+		 * other than the one which the filter actually originated from.
+		 */
+		foreach(lc2, ec->ec_filters)
+		{
+			EquivalenceFilter *ef = (EquivalenceFilter *) lfirst(lc2);
+			Expr *leftexpr;
+			Expr *rightexpr;
+			int strategy;
+			Oid opno;
+			Oid family;
+
+			if (ef->ef_source_rel == relid)
+				continue;
+
+			if (!find_am_family_and_stategy(ef->ef_opno, BTREE_AM_OID,
+				&family, &strategy))
+				continue;
+
+			if (ef->ef_const_is_left)
+			{
+				leftexpr = (Expr *) ef->ef_const;
+				rightexpr = cur_em->em_expr;
+			}
+			else
+			{
+				leftexpr = cur_em->em_expr;
+				rightexpr = (Expr *) ef->ef_const;
+			}
+
+			opno = get_opfamily_member(family,
+										exprType((Node *) leftexpr),
+										exprType((Node *) rightexpr),
+										strategy);
+
+			if (opno == InvalidOid)
+				continue;
+
+			process_implied_equality(root, opno,
+										ec->ec_collation,
+										leftexpr,
+										rightexpr,
+										bms_copy(ec->ec_relids),
+										bms_copy(cur_em->em_nullable_relids),
+									 	ec->ec_min_security,
+										ec->ec_below_outer_join,
+										false);
+		}
+
 		prev_ems[relid] = cur_em;
 	}
 
@@ -1901,6 +1985,104 @@ create_join_clause(PlannerInfo *root,
 	return rinfo;
 }
 
+/*
+ * distribute_filter_quals_to_eclass
+ *		For each OpExpr in quallist look for an eclass which has an Expr
+ *		matching the Expr in the OpExpr. If a match is found we add a new
+ *		EquivalenceFilter to the eclass containing the filter details.
+ */
+void
+distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
+{
+	ListCell *l;
+
+	/* fast path for when no eclasses have been generated */
+	if (root->eq_classes == NIL)
+		return;
+
+	/*
+	 * For each qual in quallist try and find an eclass which contains the
+	 * non-Const part of the OpExpr. We'll tag any matches that we find onto
+	 * the correct eclass.
+	 */
+	foreach(l, quallist)
+	{
+		OpExpr	   *opexpr = (OpExpr *) lfirst(l);
+		Expr	   *leftexpr = (Expr *) linitial(opexpr->args);
+		Expr	   *rightexpr = (Expr *) lsecond(opexpr->args);
+		Const	   *constexpr;
+		Expr	   *varexpr;
+		Relids		exprrels;
+		int			relid;
+		bool		const_isleft;
+		ListCell *l2;
+
+		/*
+		 * Determine if the the OpExpr is in the form "expr op const" or
+		 * "const op expr".
+		 */
+		if (IsA(leftexpr, Const))
+		{
+			constexpr = (Const *) leftexpr;
+			varexpr = rightexpr;
+			const_isleft = true;
+		}
+		else
+		{
+			constexpr = (Const *) rightexpr;
+			varexpr = leftexpr;
+			const_isleft = false;
+		}
+
+		exprrels = pull_varnos(root, (Node *) varexpr);
+
+		/* should be filtered out, but we need to determine relid anyway */
+		if (!bms_get_singleton_member(exprrels, &relid))
+			continue;
+
+		/* search for a matching eclass member in all eclasses */
+		foreach(l2, root->eq_classes)
+		{
+			EquivalenceClass *ec = (EquivalenceClass *) lfirst(l2);
+			ListCell *l3;
+
+			if (ec->ec_broken || ec->ec_has_volatile)
+				continue;
+
+			/*
+			 * if the eclass has a const then that const will serve as the
+			 * filter, we needn't add any others.
+			 */
+			if (ec->ec_has_const)
+				continue;
+
+			/* skip this eclass no members exist which belong to this relid */
+			if (!bms_is_member(relid, ec->ec_relids))
+				continue;
+
+			foreach(l3, ec->ec_members)
+			{
+				EquivalenceMember *em = (EquivalenceMember *) lfirst(l3);
+
+				if (!bms_is_member(relid, em->em_relids))
+					continue;
+
+				if (equal(em->em_expr, varexpr))
+				{
+					EquivalenceFilter *efilter;
+					efilter = makeNode(EquivalenceFilter);
+					efilter->ef_const = (Const *) copyObject(constexpr);
+					efilter->ef_const_is_left = const_isleft;
+					efilter->ef_opno = opexpr->opno;
+					efilter->ef_source_rel = relid;
+
+					ec->ec_filters = lappend(ec->ec_filters, efilter);
+					break;		/* Onto the next eclass */
+				}
+			}
+		}
+	}
+}
 
 /*
  * reconsider_outer_join_clauses
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index d61419f61a5..7e355c94362 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -53,7 +53,7 @@ static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel,
 static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
 								 bool below_outer_join,
 								 Relids *qualscope, Relids *inner_join_rels,
-								 List **postponed_qual_list);
+								 List **postponed_qual_list, List **filter_qual_list);
 static void process_security_barrier_quals(PlannerInfo *root,
 										   int rti, Relids qualscope,
 										   bool below_outer_join);
@@ -70,7 +70,8 @@ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 									Relids qualscope,
 									Relids ojscope,
 									Relids outerjoin_nonnullable,
-									List **postponed_qual_list);
+									List **postponed_qual_list,
+									List **filter_qual_list);
 static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p,
 								  Relids *nullable_relids_p, bool is_pushed_down);
 static bool check_equivalence_delay(PlannerInfo *root,
@@ -650,6 +651,43 @@ create_lateral_join_info(PlannerInfo *root)
 	}
 }
 
+/*
+ * is_simple_filter_qual
+ *             Analyzes an OpExpr to determine if it may be useful as an
+ *             EquivalenceFilter. Returns true if the OpExpr may be of some use, or
+ *             false if it should not be used.
+ */
+static bool
+is_simple_filter_qual(PlannerInfo *root, OpExpr *expr)
+{
+	Expr *leftexpr;
+	Expr *rightexpr;
+
+	if (!IsA(expr, OpExpr))
+			return false;
+
+	if (list_length(expr->args) != 2)
+			return false;
+
+	leftexpr = (Expr *) linitial(expr->args);
+	rightexpr = (Expr *) lsecond(expr->args);
+
+	/* XXX should we restrict these to simple Var op Const expressions? */
+	if (IsA(leftexpr, Const))
+	{
+		if (bms_membership(pull_varnos(root, (Node *) rightexpr)) == BMS_SINGLETON &&
+			!contain_volatile_functions((Node *) rightexpr))
+			return true;
+	}
+	else if (IsA(rightexpr, Const))
+	{
+		if (bms_membership(pull_varnos(root, (Node *) leftexpr)) == BMS_SINGLETON &&
+			!contain_volatile_functions((Node *) leftexpr))
+			return true;
+	}
+
+	return false;
+}
 
 /*****************************************************************************
  *
@@ -690,6 +728,7 @@ deconstruct_jointree(PlannerInfo *root)
 	Relids		qualscope;
 	Relids		inner_join_rels;
 	List	   *postponed_qual_list = NIL;
+	List	   *filter_qual_list = NIL;
 
 	/* Start recursion at top of jointree */
 	Assert(root->parse->jointree != NULL &&
@@ -700,11 +739,14 @@ deconstruct_jointree(PlannerInfo *root)
 
 	result = deconstruct_recurse(root, (Node *) root->parse->jointree, false,
 								 &qualscope, &inner_join_rels,
-								 &postponed_qual_list);
+								 &postponed_qual_list, &filter_qual_list);
 
 	/* Shouldn't be any leftover quals */
 	Assert(postponed_qual_list == NIL);
 
+	/* try and match each filter_qual_list item up with an eclass. */
+	distribute_filter_quals_to_eclass(root, filter_qual_list);
+
 	return result;
 }
 
@@ -725,6 +767,8 @@ deconstruct_jointree(PlannerInfo *root)
  *		or free this, either)
  *	*postponed_qual_list is a list of PostponedQual structs, which we can
  *		add quals to if they turn out to belong to a higher join level
+ *	*filter_qual_list is appended to with a list of quals which may be useful
+ *		include as EquivalenceFilters.
  *	Return value is the appropriate joinlist for this jointree node
  *
  * In addition, entries will be added to root->join_info_list for outer joins.
@@ -732,7 +776,7 @@ deconstruct_jointree(PlannerInfo *root)
 static List *
 deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 					Relids *qualscope, Relids *inner_join_rels,
-					List **postponed_qual_list)
+					List **postponed_qual_list, List **filter_qual_list)
 {
 	List	   *joinlist;
 
@@ -785,7 +829,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 											   below_outer_join,
 											   &sub_qualscope,
 											   inner_join_rels,
-											   &child_postponed_quals);
+											   &child_postponed_quals,
+											   filter_qual_list);
 			*qualscope = bms_add_members(*qualscope, sub_qualscope);
 			sub_members = list_length(sub_joinlist);
 			remaining--;
@@ -819,7 +864,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 										below_outer_join, JOIN_INNER,
 										root->qual_security_level,
 										*qualscope, NULL, NULL,
-										NULL);
+										NULL,
+										filter_qual_list);
 			else
 				*postponed_qual_list = lappend(*postponed_qual_list, pq);
 		}
@@ -835,7 +881,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 									below_outer_join, JOIN_INNER,
 									root->qual_security_level,
 									*qualscope, NULL, NULL,
-									postponed_qual_list);
+									postponed_qual_list,
+									filter_qual_list);
 		}
 	}
 	else if (IsA(jtnode, JoinExpr))
@@ -873,11 +920,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													below_outer_join,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = *qualscope;
 				/* Inner join adds no restrictions for quals */
@@ -890,11 +939,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													true,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = bms_union(left_inners, right_inners);
 				nonnullable_rels = leftids;
@@ -904,11 +955,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													below_outer_join,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = bms_union(left_inners, right_inners);
 				/* Semi join adds no restrictions for quals */
@@ -925,11 +978,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   true,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													true,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = bms_union(left_inners, right_inners);
 				/* each side is both outer and inner */
@@ -1013,7 +1068,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 									root->qual_security_level,
 									*qualscope,
 									ojscope, nonnullable_rels,
-									postponed_qual_list);
+									postponed_qual_list,
+									filter_qual_list);
 		}
 
 		/* Now we can add the SpecialJoinInfo to join_info_list */
@@ -1117,6 +1173,7 @@ process_security_barrier_quals(PlannerInfo *root,
 									qualscope,
 									qualscope,
 									NULL,
+									NULL,
 									NULL);
 		}
 		security_level++;
@@ -1610,7 +1667,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 						Relids qualscope,
 						Relids ojscope,
 						Relids outerjoin_nonnullable,
-						List **postponed_qual_list)
+						List **postponed_qual_list,
+						List **filter_qual_list)
 {
 	Relids		relids;
 	bool		is_pushed_down;
@@ -1967,6 +2025,10 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 
 	/* No EC special case applies, so push it into the clause lists */
 	distribute_restrictinfo_to_rels(root, restrictinfo);
+
+	/* Check if the qual looks useful to harvest as an EquivalenceFilter */
+	if (filter_qual_list != NULL && is_simple_filter_qual(root, (OpExpr *) clause))
+		*filter_qual_list = lappend(*filter_qual_list, clause);
 }
 
 /*
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 92c34ed8a49..6e03d40384c 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -341,6 +341,34 @@ get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type)
 	return result;
 }
 
+/*
+ * get_opfamilies
+ *		Returns a list of Oids of each opfamily which 'opno' belonging to
+ *		'method' access method.
+ */
+List *
+get_opfamilies(Oid opno, Oid method)
+{
+	List	   *result = NIL;
+	CatCList   *catlist;
+	int			i;
+
+	catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno));
+
+	for (i = 0; i < catlist->n_members; i++)
+	{
+		HeapTuple	tuple = &catlist->members[i]->tuple;
+		Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
+
+		if (aform->amopmethod == method)
+			result = lappend_oid(result, aform->amopfamily);
+	}
+
+	ReleaseSysCacheList(catlist);
+
+	return result;
+}
+
 /*
  * get_mergejoin_opfamilies
  *		Given a putatively mergejoinable operator, return a list of the OIDs
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 5d075f0c346..7349afe1640 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
 	/* these aren't subclasses of Path: */
 	T_EquivalenceClass,
 	T_EquivalenceMember,
+	T_EquivalenceFilter,
 	T_PathKey,
 	T_PathTarget,
 	T_RestrictInfo,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 3b95e4a8eae..dcae69635ce 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -990,6 +990,7 @@ typedef struct EquivalenceClass
 	List	   *ec_members;		/* list of EquivalenceMembers */
 	List	   *ec_sources;		/* list of generating RestrictInfos */
 	List	   *ec_derives;		/* list of derived RestrictInfos */
+	List	   *ec_filters;
 	Relids		ec_relids;		/* all relids appearing in ec_members, except
 								 * for child members (see below) */
 	bool		ec_has_const;	/* any pseudoconstants in ec_members? */
@@ -1002,6 +1003,42 @@ typedef struct EquivalenceClass
 	struct EquivalenceClass *ec_merged; /* set if merged into another EC */
 } EquivalenceClass;
 
+/*
+ * EquivalenceFilter - List of filters on Consts which belong to the
+ * EquivalenceClass.
+ *
+ * When building the equivalence classes we also collected a list of quals in
+ * the form of; "Expr op Const" and "Const op Expr". These are collected in the
+ * hope that we'll later generate an equivalence class which contains the
+ * "Expr" part. For example, if we parse a query such as;
+ *
+ *		SELECT * FROM t1 INNER JOIN t2 ON t1.id = t2.id WHERE t1.id < 10;
+ *
+ * then since we'll end up with an equivalence class containing {t1.id,t2.id},
+ * we'll tag the "< 10" filter onto the eclass. We are able to do this because
+ * the eclass proves equality between each class member, therefore all members
+ * must be below 10.
+ *
+ * EquivalenceFilters store the details required to allow us to push these
+ * filter clauses down into other relations which share an equivalence class
+ * containing a member which matches the expression of this EquivalenceFilter.
+ *
+ * ef_const is the Const value which this filter should filter against.
+ * ef_opno is the operator to filter on.
+ * ef_const_is_left marks if the OpExpr was in the form "Const op Expr" or
+ * "Expr op Const".
+ * ef_source_rel is the relation id of where this qual originated from.
+ */
+typedef struct EquivalenceFilter
+{
+	NodeTag		type;
+
+	Const	   *ef_const;		/* the constant expression to filter on */
+	Oid			ef_opno;		/* Operator Oid of filter operator */
+	bool		ef_const_is_left; /* Is the Const on the left of the OpExrp? */
+	Index		ef_source_rel;	/* relid of originating relation. */
+} EquivalenceFilter;
+
 /*
  * If an EC contains a const and isn't below-outer-join, any PathKey depending
  * on it must be redundant, since there's only one possible value of the key.
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 0c3a0b90c85..ce2aac7d3aa 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -126,6 +126,7 @@ extern bool process_equivalence(PlannerInfo *root,
 extern Expr *canonicalize_ec_expression(Expr *expr,
 										Oid req_type, Oid req_collation);
 extern void reconsider_outer_join_clauses(PlannerInfo *root);
+extern void distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist);
 extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root,
 												  Expr *expr,
 												  Relids nullable_relids,
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 5b5fac0397c..e0ed28f330b 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -78,6 +78,7 @@ extern bool get_ordering_op_properties(Oid opno,
 									   Oid *opfamily, Oid *opcintype, int16 *strategy);
 extern Oid	get_equality_op_for_ordering_op(Oid opno, bool *reverse);
 extern Oid	get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type);
+extern List *get_opfamilies(Oid opno, Oid method);
 extern List *get_mergejoin_opfamilies(Oid opno);
 extern void get_btree_opfamilies(Oid opno, List **mergeable_opfamilies, List **unmergeable_btree_opfamilies);
 extern bool get_compatible_hash_operators(Oid opno,
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index 126f7047fed..92fcec1158b 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -407,14 +407,14 @@ set session authorization regress_user_ectest;
 explain (costs off)
   select * from ec0 a, ec1 b
   where a.ff = b.ff and a.ff = 43::bigint::int8alias1;
-                 QUERY PLAN                  
----------------------------------------------
+                              QUERY PLAN                              
+----------------------------------------------------------------------
  Nested Loop
    ->  Index Scan using ec0_pkey on ec0 a
          Index Cond: (ff = '43'::int8alias1)
    ->  Index Scan using ec1_pkey on ec1 b
          Index Cond: (ff = a.ff)
-         Filter: (f1 < '5'::int8alias1)
+         Filter: ((f1 < '5'::int8alias1) AND (ff = '43'::int8alias1))
 (6 rows)
 
 reset session authorization;
@@ -451,3 +451,42 @@ explain (costs off)  -- this should not require a sort
    Filter: (f1 = 'foo'::name)
 (2 rows)
 
+-- test equivalence filters
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec0.ff between 1 and 10;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Nested Loop
+   Join Filter: (ec0.ff = ec1.ff)
+   ->  Bitmap Heap Scan on ec0
+         Recheck Cond: ((ff >= 1) AND (ff <= 10))
+         ->  Bitmap Index Scan on ec0_pkey
+               Index Cond: ((ff >= 1) AND (ff <= 10))
+   ->  Materialize
+         ->  Bitmap Heap Scan on ec1
+               Recheck Cond: ((ff >= 1) AND (ff <= 10))
+               ->  Bitmap Index Scan on ec1_pkey
+                     Index Cond: ((ff >= 1) AND (ff <= 10))
+(11 rows)
+
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec1.ff between 1 and 10;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Nested Loop
+   Join Filter: (ec0.ff = ec1.ff)
+   ->  Bitmap Heap Scan on ec0
+         Recheck Cond: ((ff >= 1) AND (ff <= 10))
+         ->  Bitmap Index Scan on ec0_pkey
+               Index Cond: ((ff >= 1) AND (ff <= 10))
+   ->  Materialize
+         ->  Bitmap Heap Scan on ec1
+               Recheck Cond: ((ff >= 1) AND (ff <= 10))
+               ->  Bitmap Index Scan on ec1_pkey
+                     Index Cond: ((ff >= 1) AND (ff <= 10))
+(11 rows)
+
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 19caebabd01..6a548903eec 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -3337,7 +3337,7 @@ where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
    Join Filter: (t1.stringu1 > t2.stringu2)
    ->  Nested Loop
          ->  Seq Scan on int4_tbl i1
-               Filter: (f1 = 0)
+               Filter: ((f1 = 0) AND (11 < 42))
          ->  Index Scan using tenk1_unique2 on tenk1 t1
                Index Cond: ((unique2 = (11)) AND (unique2 < 42))
    ->  Index Scan using tenk1_unique1 on tenk1 t2
@@ -6550,23 +6550,22 @@ where exists (select 1 from tenk1 t3
 ---------------------------------------------------------------------------------
  Nested Loop
    Output: t1.unique1, t2.hundred
-   ->  Hash Join
+   ->  Nested Loop
          Output: t1.unique1, t3.tenthous
-         Hash Cond: (t3.thousand = t1.unique1)
+         Join Filter: (t1.unique1 = t3.thousand)
+         ->  Index Only Scan using onek_unique1 on public.onek t1
+               Output: t1.unique1
+               Index Cond: (t1.unique1 < 1)
          ->  HashAggregate
                Output: t3.thousand, t3.tenthous
                Group Key: t3.thousand, t3.tenthous
                ->  Index Only Scan using tenk1_thous_tenthous on public.tenk1 t3
                      Output: t3.thousand, t3.tenthous
-         ->  Hash
-               Output: t1.unique1
-               ->  Index Only Scan using onek_unique1 on public.onek t1
-                     Output: t1.unique1
-                     Index Cond: (t1.unique1 < 1)
+                     Index Cond: (t3.thousand < 1)
    ->  Index Only Scan using tenk1_hundred on public.tenk1 t2
          Output: t2.hundred
          Index Cond: (t2.hundred = t3.tenthous)
-(18 rows)
+(17 rows)
 
 -- ... unless it actually is unique
 create table j3 as select unique1, tenthous from onek;
@@ -6584,15 +6583,16 @@ where exists (select 1 from j3
    Output: t1.unique1, t2.hundred
    ->  Nested Loop
          Output: t1.unique1, j3.tenthous
+         Join Filter: (t1.unique1 = j3.unique1)
          ->  Index Only Scan using onek_unique1 on public.onek t1
                Output: t1.unique1
                Index Cond: (t1.unique1 < 1)
          ->  Index Only Scan using j3_unique1_tenthous_idx on public.j3
                Output: j3.unique1, j3.tenthous
-               Index Cond: (j3.unique1 = t1.unique1)
+               Index Cond: (j3.unique1 < 1)
    ->  Index Only Scan using tenk1_hundred on public.tenk1 t2
          Output: t2.hundred
          Index Cond: (t2.hundred = j3.tenthous)
-(13 rows)
+(14 rows)
 
 drop table j3;
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index bb5b7c47a45..5a2923bac6c 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -186,17 +186,17 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0)
 -- Join with pruned partitions from joining relations
 EXPLAIN (COSTS OFF)
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
-                     QUERY PLAN                      
------------------------------------------------------
+                            QUERY PLAN                             
+-------------------------------------------------------------------
  Sort
    Sort Key: t1.a
    ->  Hash Join
          Hash Cond: (t2.b = t1.a)
          ->  Seq Scan on prt2_p2 t2
-               Filter: (b > 250)
+               Filter: ((b > 250) AND (b < 450))
          ->  Hash
                ->  Seq Scan on prt1_p2 t1
-                     Filter: ((a < 450) AND (b = 0))
+                     Filter: ((a < 450) AND (a > 250) AND (b = 0))
 (9 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
@@ -3100,16 +3100,18 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
          ->  Hash Join
                Hash Cond: (t2_1.b = t1_1.a)
                ->  Seq Scan on prt2_adv_p1 t2_1
+                     Filter: (b < 300)
                ->  Hash
                      ->  Seq Scan on prt1_adv_p1 t1_1
                            Filter: ((a < 300) AND (b = 0))
          ->  Hash Join
                Hash Cond: (t2_2.b = t1_2.a)
                ->  Seq Scan on prt2_adv_p2 t2_2
+                     Filter: (b < 300)
                ->  Hash
                      ->  Seq Scan on prt1_adv_p2 t1_2
                            Filter: ((a < 300) AND (b = 0))
-(15 rows)
+(17 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b;
   a  |  c   |  b  |  c   
@@ -3139,16 +3141,19 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
          ->  Hash Join
                Hash Cond: (t2_1.b = t1_1.a)
                ->  Seq Scan on prt2_adv_p1 t2_1
+                     Filter: ((b >= 100) AND (b < 300))
                ->  Hash
                      ->  Seq Scan on prt1_adv_p1 t1_1
                            Filter: ((a >= 100) AND (a < 300) AND (b = 0))
-         ->  Hash Join
-               Hash Cond: (t2_2.b = t1_2.a)
-               ->  Seq Scan on prt2_adv_p2 t2_2
-               ->  Hash
+         ->  Merge Join
+               Merge Cond: (t2_2.b = t1_2.a)
+               ->  Index Scan using prt2_adv_p2_b_idx on prt2_adv_p2 t2_2
+                     Index Cond: ((b >= 100) AND (b < 300))
+               ->  Sort
+                     Sort Key: t1_2.a
                      ->  Seq Scan on prt1_adv_p2 t1_2
                            Filter: ((a >= 100) AND (a < 300) AND (b = 0))
-(15 rows)
+(18 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a >= 100 AND t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b;
   a  |  c   |  b  |  c   
@@ -4692,27 +4697,32 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
                      Filter: ((b >= 125) AND (b < 225))
                ->  Hash
                      ->  Seq Scan on beta_neg_p1 t2_1
+                           Filter: ((b >= 125) AND (b < 225))
          ->  Hash Join
-               Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.b = t1_2.b))
-               ->  Seq Scan on beta_neg_p2 t2_2
+               Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.b = t2_2.b))
+               ->  Seq Scan on alpha_neg_p2 t1_2
+                     Filter: ((b >= 125) AND (b < 225))
                ->  Hash
-                     ->  Seq Scan on alpha_neg_p2 t1_2
+                     ->  Seq Scan on beta_neg_p2 t2_2
                            Filter: ((b >= 125) AND (b < 225))
          ->  Hash Join
-               Hash Cond: ((t2_4.a = t1_4.a) AND (t2_4.b = t1_4.b))
+               Hash Cond: ((t1_4.a = t2_4.a) AND (t1_4.b = t2_4.b))
                ->  Append
-                     ->  Seq Scan on beta_pos_p1 t2_4
-                     ->  Seq Scan on beta_pos_p2 t2_5
-                     ->  Seq Scan on beta_pos_p3 t2_6
+                     ->  Seq Scan on alpha_pos_p1 t1_4
+                           Filter: ((b >= 125) AND (b < 225))
+                     ->  Seq Scan on alpha_pos_p2 t1_5
+                           Filter: ((b >= 125) AND (b < 225))
+                     ->  Seq Scan on alpha_pos_p3 t1_6
+                           Filter: ((b >= 125) AND (b < 225))
                ->  Hash
                      ->  Append
-                           ->  Seq Scan on alpha_pos_p1 t1_4
+                           ->  Seq Scan on beta_pos_p1 t2_4
                                  Filter: ((b >= 125) AND (b < 225))
-                           ->  Seq Scan on alpha_pos_p2 t1_5
+                           ->  Seq Scan on beta_pos_p2 t2_5
                                  Filter: ((b >= 125) AND (b < 225))
-                           ->  Seq Scan on alpha_pos_p3 t1_6
+                           ->  Seq Scan on beta_pos_p3 t2_6
                                  Filter: ((b >= 125) AND (b < 225))
-(29 rows)
+(34 rows)
 
 SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b) WHERE t1.b >= 125 AND t1.b < 225 ORDER BY t1.a, t1.b;
  a  |  b  |  c   | a  |  b  |  c   
diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql
index 247b0a31055..dae83c41965 100644
--- a/src/test/regress/sql/equivclass.sql
+++ b/src/test/regress/sql/equivclass.sql
@@ -269,3 +269,15 @@ create temp view overview as
   select f1::information_schema.sql_identifier as sqli, f2 from undername;
 explain (costs off)  -- this should not require a sort
   select * from overview where sqli = 'foo' order by sqli;
+
+
+-- test equivalence filters
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec0.ff between 1 and 10;
+
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec1.ff between 1 and 10;
-- 
2.21.0

v3-0005-CorrectiveQuals-is-as-simple-as-a-List-of-Restric.patchapplication/x-patch; name=v3-0005-CorrectiveQuals-is-as-simple-as-a-List-of-Restric.patchDownload
From d6c0ebc19307af9863bc9b25321ff1106cc6ea76 Mon Sep 17 00:00:00 2001
From: Andy Fan <yizhi.fzh@alibaba-inc.com>
Date: Tue, 8 Mar 2022 17:25:04 +0800
Subject: [PATCH v3 5/5] CorrectiveQuals is as simple as a List of
 RestrictInfo, a). only one

restrictinfo on this group should be counted for any joinrel estimation. b). at
least 1 restrictinfo in this group should be executed during execution. In this
commit, only rows estimation issue is addressed.

PlannerInfo.correlative_quals is added to manage all the CorrectiveQuals at
subquery level. RelOptInfo.cqual_indexes is a List * to indicate a which
CorrectiveQuals this relation related to. This is designed for easy to check if
the both sides of joinrel correlated to the same CorrectiveQuals. Why isn't the
type a Bitmapset * will be explained later.

The overall design of handing the joinrel size estimation is:
a). At the base relation level, we just count everything with the correlative
quals. b). During the any level joinrel size estimation, we just keep 1 side's
cqual (short for corrective qual) selectivity by eliminated the other one. so
the size estimation for a mergeable join selectivity becomes to:

rows = R1.rows X r2.rows X 1 / Max (ndistval_of_colA,  ndistinval_of_colB) X 1 /
Selectivity(R1's CorrectiveQual).

r1.rows X 1 / Selectivity(R1's CorrectiveQual) eliminated the impact of
CorrectiveQual on R1. After this,  the JoinRel of (R1, R2) still be impacted by
this CorrectiveQual but just one in this level.  Later if JoinRel(R1, R2) needs
to join with R3,  and R3 is impacted by this CorectiveQuals as well. This we
need to keep one and eliminating the other one as above again.

The algorithm for which Selectivity should be eliminated and which one should be
kept is:

When we join 2 inner_rel and outer_rel with a mergeable join restrictinfo, if
both sides is impacted with the same CorrectiveQual, we first choose which "side"
to eliminating based on which side of the restrictinfo has a higher distinct
value. The reason for this is more or less because we used "Max"(ndistinctValT1,
ndistinctValT2). After decide which "side" to eliminating, the real eliminating
selecitity is the side of RelOptInfo->cqual_selectivity[n]

Selectivity *RelOptInfo->cqual_selectivity:

 The number of elements in cqual_selecitity equals
 the length of cqual_indexes. The semantics is which
 selectivity in the corresponding CorectiveQuals's qual
 list is taking effect. At only time, only 1 Qual
 Selectivity is counted for any-level of joinrel.

and the other side's RelOptInfo->cqual_selectivty is used to set the upper
joinrel->cqual_selecivity.

In reality, it is possible for to have many CorrectiveQuals, but for design
discussion, the current implementation only take care of the 1 CorrectiveQuals.
this would be helpful for PoC/review/discussion.

Some flow for the key data:

1. root->corrective_quals is initialized at
generate_base_implied_equalities_no_const stage. we create a CorrectiveQual in
this list for each ec_filter and fill the RestrictInfo part for this cqual. At
the same time, we note which RelOptInfo (cqual_indexes) has related to this cqual.

2. RelOptInfo->cqual_selecitity for baserel is set at the end of set_rel_size,
at this time, the selectivity for every RestrictInfo is calcuated, we can just
fetch the cached value.  As for joinrel, it is maintained in
calc_join_cqual_selectivity, this function would return the Selectivity to
eliminate and set the above value.

Limitation in this PoC:
1. Only support 1 CorrectiveQual in root->correlative_quals
2. Only tested with INNER_JOIN.
3. Inherited table is not supported.
---
 src/backend/nodes/outfuncs.c              |   1 +
 src/backend/optimizer/path/allpaths.c     |  27 ++++
 src/backend/optimizer/path/costsize.c     | 182 ++++++++++++++++++++++
 src/backend/optimizer/path/equivclass.c   |  48 ++++--
 src/backend/optimizer/plan/planner.c      |   1 +
 src/backend/optimizer/prep/prepjointree.c |   1 +
 src/include/nodes/nodes.h                 |   1 +
 src/include/nodes/pathnodes.h             |  36 ++++-
 8 files changed, 280 insertions(+), 17 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d29b64eb918..00989fedacb 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2515,6 +2515,7 @@ _outEquivalenceFilter(StringInfo str, const EquivalenceFilter *node)
 	WRITE_UINT_FIELD(ef_source_rel);
 	WRITE_OID_FIELD(opfamily);
 	WRITE_INT_FIELD(amstrategy);
+	WRITE_NODE_FIELD(rinfo);
 }
 
 static void
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 169b1d53fc8..311a5e3837a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -461,6 +461,33 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 	 * We insist that all non-dummy rels have a nonzero rowcount estimate.
 	 */
 	Assert(rel->rows > 0 || IS_DUMMY_REL(rel));
+
+	/* Now calculating the selectivity impacted by Corrective Qual */
+	if (!rte->inh)  /* not supported in this PoC */
+	{
+		ListCell *l;
+		int i = 0;
+		rel->cqual_selecitiy = palloc(sizeof(Selectivity) * list_length(rel->cqual_indexes));
+
+		foreach(l, rel->cqual_indexes)
+		{
+			int cq_index = lfirst_int(l);
+			CorrelativeQuals *cquals = list_nth_node(CorrelativeQuals, root->correlative_quals, cq_index);
+			ListCell *l2;
+			bool found = false;
+			foreach(l2, cquals->corr_restrictinfo)
+			{
+				RestrictInfo *rinfo = lfirst_node(RestrictInfo, l2);
+				if (bms_equal(rinfo->clause_relids, rel->relids))
+				{
+					found = true;
+					rel->cqual_selecitiy[i] = rinfo->norm_selec > 0 ? rinfo->norm_selec : rinfo->outer_selec;
+					Assert(rel->cqual_selecitiy[i] > 0);
+				}
+			}
+			Assert(found);
+		}
+	}
 }
 
 /*
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 8dc7dd4ca26..b4ad4297826 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -5062,6 +5062,138 @@ get_parameterized_joinrel_size(PlannerInfo *root, RelOptInfo *rel,
 	return nrows;
 }
 
+
+/*
+ * Given a mergeable RestrictInfo, find out which relid should be used for
+ * eliminating Corrective Qual Selectivity.
+ */
+static int
+find_relid_to_eliminate(PlannerInfo *root, RestrictInfo *rinfo)
+{
+	int left_relid,  right_relid;
+	RelOptInfo *lrel, *rrel;
+	bool res;
+
+	res = bms_get_singleton_member(rinfo->left_relids, &left_relid);
+	Assert(res);
+	res = bms_get_singleton_member(rinfo->left_relids, &right_relid);
+	Assert(res);
+
+	lrel = root->simple_rel_array[left_relid];
+	rrel = root->simple_rel_array[right_relid];
+
+	/* XXX: Assumed only one CorrectiveQual exists */
+
+	if (lrel->cqual_selecitiy[0] > rrel->cqual_selecitiy[0])
+		return left_relid;
+
+	return right_relid;
+}
+
+/*
+ * calc_join_cqual_selectivity
+ *
+ *	When join two relations, if both sides are impacted by the same CorrectiveQuals,
+ * we need to eliminate one of them and note the other one for future eliminating when join
+ * another corrective relation. or else just note the joinrel still being impacted by the
+ * single sides's CorrectiveQuals.
+ *
+ * Return value is the Selectivity we need to eliminate for estimating the current
+ * joinrel.
+ */
+static double
+calc_join_cqual_selectivity(PlannerInfo *root,
+							RelOptInfo *joinrel,
+							RelOptInfo *outer_rel,
+							RelOptInfo *inner_rel,
+							RestrictInfo *rinfo)
+{
+	double res = 1;
+	ListCell *lc1, *lc2;
+	Selectivity left_sel;  /* The cqual selectivity still impacted on this joinrel. */
+
+	/*
+	 * Find how many CorrectiveQual for this joinrel and allocate space for each left Selectivity
+	 * for each CorrectiveQual here.
+	 */
+	List	*final_cq_list = list_union_int(outer_rel->cqual_indexes, inner_rel->cqual_indexes);
+
+	joinrel->cqual_selecitiy = palloc(sizeof(Selectivity) * list_length(final_cq_list));
+
+	foreach(lc1, outer_rel->cqual_indexes)
+	{
+		int outer_cq_index = lfirst_int(lc1);
+		int inner_cq_pos = -1;
+		int outer_idx = foreach_current_index(lc1);
+		int curr_sel_len;
+
+		/*
+		 * Check if the same corrective quals applied in both sides,
+		 * if yes, we need to decide which one to eliminate and which one
+		 * to keep. or else, we just keep the selectivity for feature use.
+		 */
+		foreach(lc2, inner_rel->cqual_indexes)
+		{
+			if (outer_cq_index == lfirst_int(lc2))
+				inner_cq_pos = foreach_current_index(lc2);
+		}
+
+		if (inner_cq_pos >= 0)
+		{
+			/* Find the CorrectiveQual which impacts both side. */
+			int relid = find_relid_to_eliminate(root, rinfo);
+			if (bms_is_member(relid, outer_rel->relids))
+			{
+				/* XXXX: we assume only 1 CorrectiveQual exist, so [0] directly. */
+				res *= outer_rel->cqual_selecitiy[0];
+				left_sel = inner_rel->cqual_selecitiy[0];
+			}
+			else
+			{
+				/* XXXX: we assume only 1 CorrectiveQual exist */
+				res *= inner_rel->cqual_selecitiy[0];
+				left_sel = outer_rel->cqual_selecitiy[0];
+			}
+		}
+		else
+		{
+			/* Only shown in outer side. */
+			left_sel = outer_rel->cqual_selecitiy[outer_idx];
+		}
+
+		/*
+		 * If any side of join relation is impacted by a cqual, it is impacted for the joinrel
+		 * for sure.
+		 */
+		curr_sel_len = list_length(joinrel->cqual_indexes);
+		joinrel->cqual_indexes = lappend_int(joinrel->cqual_indexes, outer_idx);
+
+		joinrel->cqual_selecitiy[curr_sel_len] = left_sel;
+		// elog(INFO, "left_sel %f", left_sel);
+	}
+
+	/* Push any cqual information which exists in inner_rel only to join rel. */
+	foreach(lc1, inner_rel->cqual_indexes)
+	{
+		int inner_cq_index = lfirst_int(lc1);
+		int curr_sel_len;
+
+		if (list_member_int(outer_rel->cqual_indexes, inner_cq_index))
+			/* have been handled in the previous loop */
+			continue;
+
+		curr_sel_len = list_length(joinrel->cqual_indexes);
+		joinrel->cqual_selecitiy[curr_sel_len] = inner_rel->cqual_selecitiy[foreach_current_index(lc1)];
+	}
+
+	pfree(final_cq_list);
+
+	// elog(INFO, "Final adjust sel (%s): %f", bmsToString(joinrel->relids), res);
+
+	return res;
+}
+
+
 /*
  * calc_joinrel_size_estimate
  *		Workhorse for set_joinrel_size_estimates and
@@ -5205,6 +5337,56 @@ calc_joinrel_size_estimate(PlannerInfo *root,
 			break;
 	}
 
+	{
+		Selectivity m1 = 1;
+		bool should_eliminate = false;
+		RestrictInfo *rinfo;
+
+		// XXX: For hack only, the aim is the "only one" restrictinfo is the one impacted by "the only one"
+		// CorrectiveQuals. for example:
+		// SELECT * FROM t1, t2, t3 WHERE t1.a = t2.a and t2.a = t3.a and t3.a > 2;
+
+		if (list_length(root->correlative_quals) == 1 &&
+			list_length(restrictlist) == 1 &&
+			jointype == JOIN_INNER)
+		{
+			int left_relid, right_relid;
+			rinfo = linitial_node(RestrictInfo, restrictlist);
+			if (rinfo->mergeopfamilies != NIL &&
+				bms_get_singleton_member(rinfo->left_relids, &left_relid) &&
+				bms_get_singleton_member(rinfo->right_relids, &right_relid))
+			{
+				List *interset_cq_indexes = list_intersection_int(
+					root->simple_rel_array[left_relid]->cqual_indexes,
+					root->simple_rel_array[right_relid]->cqual_indexes);
+
+				if (interset_cq_indexes != NIL &&
+					!root->simple_rte_array[left_relid]->inh &&
+					!root->simple_rte_array[right_relid]->inh)
+ 					should_eliminate = true;
+			}
+		}
+
+		// elog(INFO, "joinrel: %s, %d", bmsToString(joinrel->relids), should_eliminate);
+
+		if (should_eliminate)
+			m1 = calc_join_cqual_selectivity(root, joinrel, outer_rel, inner_rel, rinfo);
+
+		/* elog(INFO, */
+		/*	 "joinrelids: %s, outer_rel: %s, inner_rel: %s, join_clauselist: %s outer rows: %f, inner_rows: %f, join rows: %f, jselec: %f, m1 = %f, m2 = %f", */
+		/*	 bmsToString(joinrel->relids), */
+		/*	 bmsToString(outer_rel->relids), */
+		/*	 bmsToString(inner_rel->relids), */
+		/*	 bmsToString(join_list_relids), */
+		/*	 outer_rel->rows, */
+		/*	 inner_rel->rows, */
+		/*	 nrows, */
+		/*	 jselec, */
+		/*	 m1, */
+		/*	 m2); */
+		nrows /= m1;
+	}
+
 	return clamp_row_est(nrows);
 }
 
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 6b638d184aa..6cdff399d0a 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -1290,6 +1290,8 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 	EquivalenceMember **prev_ems;
 	ListCell   *lc;
 	ListCell   *lc2;
+	int	start_cq_index = list_length(root->correlative_quals);
+	int	ef_index = 0;
 
 	/*
 	 * We scan the EC members once and track the last-seen member for each
@@ -1356,9 +1358,11 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 
 	pfree(prev_ems);
 
+	if (ec->ec_broken)
+		goto ec_filter_done;
 
 	/*
-	 * Also push any EquivalenceFilter clauses down into all relations
+	 * Push any EquivalenceFilter clauses down into all relations
 	 * other than the one which the filter actually originated from.
 	 */
 	foreach(lc2, ec->ec_filters)
@@ -1368,19 +1372,25 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 		Expr *rightexpr;
 		Oid opno;
 		int relid;
-
-		if (ec->ec_broken)
-			break;
+		CorrelativeQuals *cquals = makeNode(CorrelativeQuals);
 
 		foreach(lc, ec->ec_members)
 		{
 			EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc);
+			RelOptInfo *rel;
+			RestrictInfo *rinfo;
 
 			if (!bms_get_singleton_member(cur_em->em_relids, &relid))
 				continue;
 
+			rel = root->simple_rel_array[relid];
+
 			if (ef->ef_source_rel == relid)
+			{
+				rel->cqual_indexes = lappend_int(rel->cqual_indexes, start_cq_index + ef_index);
+				cquals->corr_restrictinfo = lappend(cquals->corr_restrictinfo, ef->rinfo);
 				continue;
+			}
 
 			if (ef->ef_const_is_left)
 			{
@@ -1401,19 +1411,28 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 			if (opno == InvalidOid)
 				continue;
 
-
-			process_implied_equality(root, opno,
-									 ec->ec_collation,
-									 leftexpr,
-									 rightexpr,
-									 bms_copy(ec->ec_relids),
-									 bms_copy(cur_em->em_nullable_relids),
-									 ec->ec_min_security,
-									 ec->ec_below_outer_join,
-									 false);
+			rinfo = process_implied_equality(root, opno,
+											 ec->ec_collation,
+											 leftexpr,
+											 rightexpr,
+											 bms_copy(ec->ec_relids),
+											 bms_copy(cur_em->em_nullable_relids),
+											 ec->ec_min_security,
+											 ec->ec_below_outer_join,
+											 false);
+			cquals->corr_restrictinfo = lappend(cquals->corr_restrictinfo, rinfo);
+			rel->cqual_indexes = lappend_int(rel->cqual_indexes, start_cq_index + ef_index);
 		}
+
+		ef_index += 1;
+
+		root->correlative_quals = lappend(root->correlative_quals, cquals);
 	}
 
+ec_filter_done:
+	/*
+	 * XXX this label can be removed after moving ec_filter to the end of this function.
+	 */
 	/*
 	 * We also have to make sure that all the Vars used in the member clauses
 	 * will be available at any join node we might try to reference them at.
@@ -2091,6 +2110,7 @@ distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
 					efilter->ef_source_rel = relid;
 					efilter->opfamily = opfamily;
 					efilter->amstrategy = amstrategy;
+					efilter->rinfo = rinfo;
 
 					ec->ec_filters = lappend(ec->ec_filters, efilter);
 					break;		/* Onto the next eclass */
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bd09f85aea1..5c9f2833f83 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -619,6 +619,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	root->multiexpr_params = NIL;
 	root->eq_classes = NIL;
 	root->ec_merging_done = false;
+	root->correlative_quals = NIL;
 	root->all_result_relids =
 		parse->resultRelation ? bms_make_singleton(parse->resultRelation) : NULL;
 	root->leaf_result_relids = NULL;	/* we'll find out leaf-ness later */
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 282589dec81..51d146e5a71 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -919,6 +919,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	subroot->multiexpr_params = NIL;
 	subroot->eq_classes = NIL;
 	subroot->ec_merging_done = false;
+	subroot->correlative_quals = NIL;
 	subroot->all_result_relids = NULL;
 	subroot->leaf_result_relids = NULL;
 	subroot->append_rel_list = NIL;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7349afe1640..3bf83a4e405 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -266,6 +266,7 @@ typedef enum NodeTag
 	T_EquivalenceClass,
 	T_EquivalenceMember,
 	T_EquivalenceFilter,
+	T_CorrelativeQuals,
 	T_PathKey,
 	T_PathTarget,
 	T_RestrictInfo,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index e7b04211839..ca871fbb07a 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -250,6 +250,8 @@ struct PlannerInfo
 
 	bool		ec_merging_done;	/* set true once ECs are canonical */
 
+	List		*correlative_quals;  /* list of CorrelativeQuals for this subquery */
+
 	List	   *canon_pathkeys; /* list of "canonical" PathKeys */
 
 	List	   *left_join_clauses;	/* list of RestrictInfos for mergejoinable
@@ -722,6 +724,18 @@ typedef struct RelOptInfo
 	double		allvisfrac;
 	Bitmapset  *eclass_indexes; /* Indexes in PlannerInfo's eq_classes list of
 								 * ECs that mention this rel */
+	List		*cqual_indexes; /* Indexes in PlannerInfo's correlative_quals list of
+								 * CorrelativeQuals that this rel has applied. It is valid
+								 * on both baserel and joinrel. Used to quick check is the
+								 * both sides contains the same CorrectiveQuals object.
+								 */
+	Selectivity	*cqual_selecitiy; /*
+								   * The number of elements in cqual_selecitity equals
+								   * the length of cqual_indexes. The semantics is which
+								   * selectivity in the corresponding CorectiveQuals's qual
+								   * list is taking effect. At only time, only 1 Qual
+								   * Selectivity is counted for any-level of joinrel.
+								   */
 	PlannerInfo *subroot;		/* if subquery */
 	List	   *subplan_params; /* if subquery */
 	int			rel_parallel_workers;	/* wanted number of parallel workers */
@@ -1039,8 +1053,24 @@ typedef struct EquivalenceFilter
 	Index		ef_source_rel;	/* relid of originating relation. */
 	Oid			opfamily;
 	int			amstrategy;
+	struct RestrictInfo	*rinfo;		/* source restrictInfo for this EquivalenceFilter */
 } EquivalenceFilter;
 
+
+/*
+ * Currently it is as simple as a List of RestrictInfo, it means a). For any joinrel size
+ * estimation, only one restrictinfo on this group should be counted. b). During execution,
+ * at least 1 restrictinfo in this group should be executed.
+ *
+ * Define it as a Node just for better extendability, we can stripe it to a List *
+ * if we are sure nothing else is needed.
+ */
+typedef struct CorrelativeQuals
+{
+	NodeTag	type;
+	List	*corr_restrictinfo;
+} CorrelativeQuals;
+
 /*
  * If an EC contains a const and isn't below-outer-join, any PathKey depending
  * on it must be redundant, since there's only one possible value of the key.
@@ -2620,7 +2650,7 @@ typedef enum
  *
  * flags indicating what kinds of grouping are possible.
  * partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * 		have been initialized.
+ *		have been initialized.
  * agg_partial_costs gives partial aggregation costs.
  * agg_final_costs gives finalization costs.
  * target_parallel_safe is true if target is parallel safe.
@@ -2650,8 +2680,8 @@ typedef struct
  * 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).
+ *		expressions computed by preprocess_limit() (see comments for
+ *		preprocess_limit() for more information).
  */
 typedef struct
 {
-- 
2.21.0

#35Andy Fan
zhihui.fan1213@gmail.com
In reply to: Andy Fan (#34)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

Hi:

I just tested more cases for the estimation issue for this feature, and we
can
find **we get a more accurate/stable estimation than before**. Here is the
test
cases and result (by comparing the master version and patched version).

create table ec_t110 as select i::int as a from generate_series(1, 110) i;
create table ec_t200 as select i::int as a from generate_series(1, 200) i;
create table ec_t500 as select i::int as a from generate_series(1, 500) i;
create table ec_t800 as select i::int as a from generate_series(1, 800) i;
create table ec_t1000 as select i::int as a from generate_series(1, 1000) i;

analyze;

-- 2 table joins.
explain analyze select * from ec_t1000, ec_t110 where ec_t1000.a =
ec_t110.a and ec_t1000.a > 100; -- (0.9)
explain analyze select * from ec_t1000, ec_t110 where ec_t1000.a =
ec_t110.a and ec_t110.a > 100; -- (0.1)

-- 3 table joins.
explain analyze select * from ec_t1000, ec_t110 , ec_t200 where ec_t1000.a
= ec_t110.a and ec_t110.a = ec_t200.a and ec_t1000.a > 100;
explain analyze select * from ec_t1000, ec_t110 , ec_t200 where ec_t1000.a
= ec_t110.a and ec_t110.a = ec_t200.a and ec_t110.a > 100;
explain analyze select * from ec_t1000, ec_t110 , ec_t200 where ec_t1000.a
= ec_t110.a and ec_t110.a = ec_t200.a and ec_t200.a > 100;

-- 4 table joins.
explain analyze select * from ec_t1000, ec_t110 , ec_t200, ec_t500 where
ec_t1000.a = ec_t110.a and ec_t110.a = ec_t200.a and ec_t500.a = ec_t200.a
and ec_t1000.a > 100;
explain analyze select * from ec_t1000, ec_t110 , ec_t200, ec_t500 where
ec_t1000.a = ec_t110.a and ec_t110.a = ec_t200.a and ec_t500.a = ec_t200.a
and ec_t110.a > 100;
explain analyze select * from ec_t1000, ec_t110 , ec_t200, ec_t500 where
ec_t1000.a = ec_t110.a and ec_t110.a = ec_t200.a and ec_t500.a = ec_t200.a
and ec_t200.a > 100;
explain analyze select * from ec_t1000, ec_t110 , ec_t200, ec_t500 where
ec_t1000.a = ec_t110.a and ec_t110.a = ec_t200.a and ec_t500.a = ec_t200.a
and ec_t500.a > 100;

-- 5 table joins.
explain analyze select * from ec_t1000, ec_t110 , ec_t200, ec_t500, ec_t800
where ec_t1000.a = ec_t110.a and ec_t110.a = ec_t200.a and ec_t500.a =
ec_t200.a and ec_t500.a = ec_t800.a and ec_t1000.a > 100;
explain analyze select * from ec_t1000, ec_t110 , ec_t200, ec_t500, ec_t800
where ec_t1000.a = ec_t110.a and ec_t110.a = ec_t200.a and ec_t500.a =
ec_t200.a and ec_t500.a = ec_t800.a and ec_t110.a > 100;
explain analyze select * from ec_t1000, ec_t110 , ec_t200, ec_t500, ec_t800
where ec_t1000.a = ec_t110.a and ec_t110.a = ec_t200.a and ec_t500.a =
ec_t200.a and ec_t500.a = ec_t800.a and ec_t200.a > 100;
explain analyze select * from ec_t1000, ec_t110 , ec_t200, ec_t500, ec_t800
where ec_t1000.a = ec_t110.a and ec_t110.a = ec_t200.a and ec_t500.a =
ec_t200.a and ec_t500.a = ec_t800.a and ec_t500.a > 100;
explain analyze select * from ec_t1000, ec_t110 , ec_t200, ec_t500, ec_t800
where ec_t1000.a = ec_t110.a and ec_t110.a = ec_t200.a and ec_t500.a =
ec_t200.a and ec_t500.a = ec_t800.a and ec_t800.a > 100;

| Query Id | Real rows | Est. Rows at master | Est. rows with patched |
table # |
|----------+-----------+---------------------+------------------------+---------|
| 1 | 10 | 99 | 10 |
2 |
| 2 | 10 | 10 | 10 |
2 |
| 3 | 10 | 20 | 11 |
3 |
| 4 | 10 | 2 | 11 |
3 |
| 5 | 10 | 11 | 11 |
3 |
| 6 | 10 | 10 | 9 |
4 |
| 7 | 10 | 1 | 9 |
4 |
| 8 | 10 | 6 | 9 |
4 |
| 9 | 10 | 9 | 9 |
4 |
| 10 | 10 | 8 | 8 |
5 |
| 11 | 10 | 1 | 8 |
5 |
| 12 | 10 | 5 | 8 |
5 |
| 13 | 10 | 7 | 8 |
5 |
| 14 | 10 | 8 | 8 |
5 |

In the past, we can just use the qual user provided to do estimation. As for
now, since we introduce the CorrectiveQuals design, we still keep just only
1
qual counted, but we can choose the best one in CorrectiveQuals no matter
which
one is provided by the user. we gain a better and stable estimation because
of this.

I'm happy about the overall design but not pretty confident about the
method to
"choose the best one to keep". So I did some test case as many as I can to
find
something is wrong, so far so good.

I'm also happy with how to keep only one qual in CorrectiveQuals (not
choose the
best one). Assume we just have 1 EC filter in this query for simplicity. At
the
beginning, all the baserel have been impacted by CorrectiveQual. When join 2
relations, we rollback 1 side and keep the other one. when we join this
joinrel
with another rel, we rollback 1 side and keep the other one and so forth.

(rollback is not changing some things which we already computed, it is only
used when estimating size for coming joinrel).

The patchset can be applied cleanly with
9e98583898c347e007958c8a09911be2ea4acfb9.

#36Andres Freund
andres@anarazel.de
In reply to: Andy Fan (#34)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

Hi,

On 2022-03-08 21:44:37 +0800, Andy Fan wrote:

I have finished the PoC for planning timing improvement and joinrel rows
estimation.

This currently crashes on cfbot:
https://api.cirrus-ci.com/v1/task/6158455839916032/logs/cores.log
https://cirrus-ci.com/task/6158455839916032

As this is clearly not 15 material, I've set the target version as 16. But it
might be good to just move the whole entry to the next CF...

Greetings,

Andres Freund

#37Andy Fan
zhihui.fan1213@gmail.com
In reply to: Andres Freund (#36)
6 attachment(s)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

Hi:

Thanks for take care of this.

On Tue, Mar 22, 2022 at 9:41 AM Andres Freund <andres@anarazel.de> wrote:

Hi,

On 2022-03-08 21:44:37 +0800, Andy Fan wrote:

I have finished the PoC for planning timing improvement and joinrel rows
estimation.

This currently crashes on cfbot:
https://api.cirrus-ci.com/v1/task/6158455839916032/logs/cores.log
https://cirrus-ci.com/task/6158455839916032

The crash happens at my own Assert statement. I assume we know the
Selectivity
for a RestrictInfo after set_rel_size, however this is not true for foreign
table with use_remote_estimate=true. Since we are in a design discussion
stage,
I just disable this feature for foreign tables and can fix it later. Would
this be the
right way to go?

As this is clearly not 15 material, I've set the target version as 16. But
it
might be good to just move the whole entry to the next CF...

Thanks for doing that. I tried but didn't find how to move it to the next
CF.

Here is the latest code. I have rebased the code with the latest master
a1bc4d3590b.

--
Best Regards
Andy Fan

Attachments:

v4-0004-Prepare-the-code-for-CorrectiveQual-structure.patchapplication/octet-stream; name=v4-0004-Prepare-the-code-for-CorrectiveQual-structure.patchDownload
From 57e64920b4a2a75ce85822f917cd3f529e9ce57c Mon Sep 17 00:00:00 2001
From: Andy Fan <yizhi.fzh@alibaba-inc.com>
Date: Mon, 7 Mar 2022 20:17:42 +0800
Subject: [PATCH v4 4/6] Prepare the code for CorrectiveQual structure.

Just refactor the method for 2-level loop in
generate_base_implied_equalities_no_const, no other things is changed.
---
 src/backend/optimizer/path/equivclass.c | 61 +++++++++++++++----------
 1 file changed, 37 insertions(+), 24 deletions(-)

diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 915888a8486..6b638d184aa 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -1351,17 +1351,33 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 				rinfo->right_em = cur_em;
 			}
 		}
+		prev_ems[relid] = cur_em;
+	}
 
-		/*
-		 * Also push any EquivalenceFilter clauses down into all relations
-		 * other than the one which the filter actually originated from.
-		 */
-		foreach(lc2, ec->ec_filters)
+	pfree(prev_ems);
+
+
+	/*
+	 * Also push any EquivalenceFilter clauses down into all relations
+	 * other than the one which the filter actually originated from.
+	 */
+	foreach(lc2, ec->ec_filters)
+	{
+		EquivalenceFilter *ef = (EquivalenceFilter *) lfirst(lc2);
+		Expr *leftexpr;
+		Expr *rightexpr;
+		Oid opno;
+		int relid;
+
+		if (ec->ec_broken)
+			break;
+
+		foreach(lc, ec->ec_members)
 		{
-			EquivalenceFilter *ef = (EquivalenceFilter *) lfirst(lc2);
-			Expr *leftexpr;
-			Expr *rightexpr;
-			Oid opno;
+			EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc);
+
+			if (!bms_get_singleton_member(cur_em->em_relids, &relid))
+				continue;
 
 			if (ef->ef_source_rel == relid)
 				continue;
@@ -1378,29 +1394,26 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 			}
 
 			opno = get_opfamily_member(ef->opfamily,
-										exprType((Node *) leftexpr),
-										exprType((Node *) rightexpr),
-										ef->amstrategy);
+									   exprType((Node *) leftexpr),
+									   exprType((Node *) rightexpr),
+									   ef->amstrategy);
 
 			if (opno == InvalidOid)
 				continue;
 
+
 			process_implied_equality(root, opno,
-										ec->ec_collation,
-										leftexpr,
-										rightexpr,
-										bms_copy(ec->ec_relids),
-										bms_copy(cur_em->em_nullable_relids),
-										ec->ec_min_security,
-										ec->ec_below_outer_join,
-										false);
+									 ec->ec_collation,
+									 leftexpr,
+									 rightexpr,
+									 bms_copy(ec->ec_relids),
+									 bms_copy(cur_em->em_nullable_relids),
+									 ec->ec_min_security,
+									 ec->ec_below_outer_join,
+									 false);
 		}
-
-		prev_ems[relid] = cur_em;
 	}
 
-	pfree(prev_ems);
-
 	/*
 	 * We also have to make sure that all the Vars used in the member clauses
 	 * will be available at any join node we might try to reference them at.
-- 
2.21.0

v4-0005-CorrectiveQuals-is-as-simple-as-a-List-of-Restric.patchapplication/octet-stream; name=v4-0005-CorrectiveQuals-is-as-simple-as-a-List-of-Restric.patchDownload
From 360d2f88ac3736cfa271867bf374939cf6e07681 Mon Sep 17 00:00:00 2001
From: Andy Fan <yizhi.fzh@alibaba-inc.com>
Date: Tue, 8 Mar 2022 17:25:04 +0800
Subject: [PATCH v4 5/6] CorrectiveQuals is as simple as a List of
 RestrictInfo, a). only one

restrictinfo on this group should be counted for any joinrel estimation. b). at
least 1 restrictinfo in this group should be executed during execution. In this
commit, only rows estimation issue is addressed.

PlannerInfo.correlative_quals is added to manage all the CorrectiveQuals at
subquery level. RelOptInfo.cqual_indexes is a List * to indicate a which
CorrectiveQuals this relation related to. This is designed for easy to check if
the both sides of joinrel correlated to the same CorrectiveQuals. Why isn't the
type a Bitmapset * will be explained later.

The overall design of handing the joinrel size estimation is:
a). At the base relation level, we just count everything with the correlative
quals. b). During the any level joinrel size estimation, we just keep 1 side's
cqual (short for corrective qual) selectivity by eliminated the other one. so
the size estimation for a mergeable join selectivity becomes to:

rows = R1.rows X r2.rows X 1 / Max (ndistval_of_colA,  ndistinval_of_colB) X 1 /
Selectivity(R1's CorrectiveQual).

r1.rows X 1 / Selectivity(R1's CorrectiveQual) eliminated the impact of
CorrectiveQual on R1. After this,  the JoinRel of (R1, R2) still be impacted by
this CorrectiveQual but just one in this level.  Later if JoinRel(R1, R2) needs
to join with R3,  and R3 is impacted by this CorectiveQuals as well. This we
need to keep one and eliminating the other one as above again.

The algorithm for which Selectivity should be eliminated and which one should be
kept is:

When we join 2 inner_rel and outer_rel with a mergeable join restrictinfo, if
both sides is impacted with the same CorrectiveQual, we first choose which "side"
to eliminating based on which side of the restrictinfo has a higher distinct
value. The reason for this is more or less because we used "Max"(ndistinctValT1,
ndistinctValT2). After decide which "side" to eliminating, the real eliminating
selecitity is the side of RelOptInfo->cqual_selectivity[n]

Selectivity *RelOptInfo->cqual_selectivity:

 The number of elements in cqual_selecitity equals
 the length of cqual_indexes. The semantics is which
 selectivity in the corresponding CorectiveQuals's qual
 list is taking effect. At only time, only 1 Qual
 Selectivity is counted for any-level of joinrel.

and the other side's RelOptInfo->cqual_selectivty is used to set the upper
joinrel->cqual_selecivity.

In reality, it is possible for to have many CorrectiveQuals, but for design
discussion, the current implementation only take care of the 1 CorrectiveQuals.
this would be helpful for PoC/review/discussion.

Some flow for the key data:

1. root->corrective_quals is initialized at
generate_base_implied_equalities_no_const stage. we create a CorrectiveQual in
this list for each ec_filter and fill the RestrictInfo part for this cqual. At
the same time, we note which RelOptInfo (cqual_indexes) has related to this cqual.

2. RelOptInfo->cqual_selecitity for baserel is set at the end of set_rel_size,
at this time, the selectivity for every RestrictInfo is calcuated, we can just
fetch the cached value.  As for joinrel, it is maintained in
calc_join_cqual_selectivity, this function would return the Selectivity to
eliminate and set the above value.

Limitation in this PoC:
1. Only support 1 CorrectiveQual in root->correlative_quals
2. Only tested with INNER_JOIN.
3. Inherited table is not supported.
---
 src/backend/nodes/outfuncs.c              |   1 +
 src/backend/optimizer/path/allpaths.c     |  27 ++++
 src/backend/optimizer/path/costsize.c     | 182 ++++++++++++++++++++++
 src/backend/optimizer/path/equivclass.c   |  48 ++++--
 src/backend/optimizer/plan/planner.c      |   1 +
 src/backend/optimizer/prep/prepjointree.c |   1 +
 src/include/nodes/nodes.h                 |   1 +
 src/include/nodes/pathnodes.h             |  36 ++++-
 8 files changed, 280 insertions(+), 17 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d29b64eb918..00989fedacb 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2515,6 +2515,7 @@ _outEquivalenceFilter(StringInfo str, const EquivalenceFilter *node)
 	WRITE_UINT_FIELD(ef_source_rel);
 	WRITE_OID_FIELD(opfamily);
 	WRITE_INT_FIELD(amstrategy);
+	WRITE_NODE_FIELD(rinfo);
 }
 
 static void
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 169b1d53fc8..311a5e3837a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -461,6 +461,33 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 	 * We insist that all non-dummy rels have a nonzero rowcount estimate.
 	 */
 	Assert(rel->rows > 0 || IS_DUMMY_REL(rel));
+
+	/* Now calculating the selectivity impacted by Corrective Qual */
+	if (!rte->inh)  /* not supported in this PoC */
+	{
+		ListCell *l;
+		int i = 0;
+		rel->cqual_selecitiy = palloc(sizeof(Selectivity) * list_length(rel->cqual_indexes));
+
+		foreach(l, rel->cqual_indexes)
+		{
+			int cq_index = lfirst_int(l);
+			CorrelativeQuals *cquals = list_nth_node(CorrelativeQuals, root->correlative_quals, cq_index);
+			ListCell *l2;
+			bool found = false;
+			foreach(l2, cquals->corr_restrictinfo)
+			{
+				RestrictInfo *rinfo = lfirst_node(RestrictInfo, l2);
+				if (bms_equal(rinfo->clause_relids, rel->relids))
+				{
+					found = true;
+					rel->cqual_selecitiy[i] = rinfo->norm_selec > 0 ? rinfo->norm_selec : rinfo->outer_selec;
+					Assert(rel->cqual_selecitiy[i] > 0);
+				}
+			}
+			Assert(found);
+		}
+	}
 }
 
 /*
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 4d9f3b4bb6b..fe3bc097fe0 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -5068,6 +5068,138 @@ get_parameterized_joinrel_size(PlannerInfo *root, RelOptInfo *rel,
 	return nrows;
 }
 
+
+/*
+ * Given a mergeable RestrictInfo, find out which relid should be used for
+ * eliminating Corrective Qual Selectivity.
+ */
+static int
+find_relid_to_eliminate(PlannerInfo *root, RestrictInfo *rinfo)
+{
+	int left_relid,  right_relid;
+	RelOptInfo *lrel, *rrel;
+	bool res;
+
+	res = bms_get_singleton_member(rinfo->left_relids, &left_relid);
+	Assert(res);
+	res = bms_get_singleton_member(rinfo->left_relids, &right_relid);
+	Assert(res);
+
+	lrel = root->simple_rel_array[left_relid];
+	rrel = root->simple_rel_array[right_relid];
+
+	/* XXX: Assumed only one CorrectiveQual exists */
+
+	if (lrel->cqual_selecitiy[0] > rrel->cqual_selecitiy[0])
+		return left_relid;
+
+	return right_relid;
+}
+
+/*
+ * calc_join_cqual_selectivity
+ *
+ *	When join two relations, if both sides are impacted by the same CorrectiveQuals,
+ * we need to eliminate one of them and note the other one for future eliminating when join
+ * another corrective relation. or else just note the joinrel still being impacted by the
+ * single sides's CorrectiveQuals.
+ *
+ * Return value is the Selectivity we need to eliminate for estimating the current
+ * joinrel.
+ */
+static double
+calc_join_cqual_selectivity(PlannerInfo *root,
+							RelOptInfo *joinrel,
+							RelOptInfo *outer_rel,
+							RelOptInfo *inner_rel,
+							RestrictInfo *rinfo)
+{
+	double res = 1;
+	ListCell *lc1, *lc2;
+	Selectivity left_sel;  /* The cqual selectivity still impacted on this joinrel. */
+
+	/*
+	 * Find how many CorrectiveQual for this joinrel and allocate space for each left Selectivity
+	 * for each CorrectiveQual here.
+	 */
+	List	*final_cq_list = list_union_int(outer_rel->cqual_indexes, inner_rel->cqual_indexes);
+
+	joinrel->cqual_selecitiy = palloc(sizeof(Selectivity) * list_length(final_cq_list));
+
+	foreach(lc1, outer_rel->cqual_indexes)
+	{
+		int outer_cq_index = lfirst_int(lc1);
+		int inner_cq_pos = -1;
+		int outer_idx = foreach_current_index(lc1);
+		int curr_sel_len;
+
+		/*
+		 * Check if the same corrective quals applied in both sides,
+		 * if yes, we need to decide which one to eliminate and which one
+		 * to keep. or else, we just keep the selectivity for feature use.
+		 */
+		foreach(lc2, inner_rel->cqual_indexes)
+		{
+			if (outer_cq_index == lfirst_int(lc2))
+				inner_cq_pos = foreach_current_index(lc2);
+		}
+
+		if (inner_cq_pos >= 0)
+		{
+			/* Find the CorrectiveQual which impacts both side. */
+			int relid = find_relid_to_eliminate(root, rinfo);
+			if (bms_is_member(relid, outer_rel->relids))
+			{
+				/* XXXX: we assume only 1 CorrectiveQual exist, so [0] directly. */
+				res *= outer_rel->cqual_selecitiy[0];
+				left_sel = inner_rel->cqual_selecitiy[0];
+			}
+			else
+			{
+				/* XXXX: we assume only 1 CorrectiveQual exist */
+				res *= inner_rel->cqual_selecitiy[0];
+				left_sel = outer_rel->cqual_selecitiy[0];
+			}
+		}
+		else
+		{
+			/* Only shown in outer side. */
+			left_sel = outer_rel->cqual_selecitiy[outer_idx];
+		}
+
+		/*
+		 * If any side of join relation is impacted by a cqual, it is impacted for the joinrel
+		 * for sure.
+		 */
+		curr_sel_len = list_length(joinrel->cqual_indexes);
+		joinrel->cqual_indexes = lappend_int(joinrel->cqual_indexes, outer_idx);
+
+		joinrel->cqual_selecitiy[curr_sel_len] = left_sel;
+		// elog(INFO, "left_sel %f", left_sel);
+	}
+
+	/* Push any cqual information which exists in inner_rel only to join rel. */
+	foreach(lc1, inner_rel->cqual_indexes)
+	{
+		int inner_cq_index = lfirst_int(lc1);
+		int curr_sel_len;
+
+		if (list_member_int(outer_rel->cqual_indexes, inner_cq_index))
+			/* have been handled in the previous loop */
+			continue;
+
+		curr_sel_len = list_length(joinrel->cqual_indexes);
+		joinrel->cqual_selecitiy[curr_sel_len] = inner_rel->cqual_selecitiy[foreach_current_index(lc1)];
+	}
+
+	pfree(final_cq_list);
+
+	// elog(INFO, "Final adjust sel (%s): %f", bmsToString(joinrel->relids), res);
+
+	return res;
+}
+
+
 /*
  * calc_joinrel_size_estimate
  *		Workhorse for set_joinrel_size_estimates and
@@ -5211,6 +5343,56 @@ calc_joinrel_size_estimate(PlannerInfo *root,
 			break;
 	}
 
+	{
+		Selectivity m1 = 1;
+		bool should_eliminate = false;
+		RestrictInfo *rinfo;
+
+		// XXX: For hack only, the aim is the "only one" restrictinfo is the one impacted by "the only one"
+		// CorrectiveQuals. for example:
+		// SELECT * FROM t1, t2, t3 WHERE t1.a = t2.a and t2.a = t3.a and t3.a > 2;
+
+		if (list_length(root->correlative_quals) == 1 &&
+			list_length(restrictlist) == 1 &&
+			jointype == JOIN_INNER)
+		{
+			int left_relid, right_relid;
+			rinfo = linitial_node(RestrictInfo, restrictlist);
+			if (rinfo->mergeopfamilies != NIL &&
+				bms_get_singleton_member(rinfo->left_relids, &left_relid) &&
+				bms_get_singleton_member(rinfo->right_relids, &right_relid))
+			{
+				List *interset_cq_indexes = list_intersection_int(
+					root->simple_rel_array[left_relid]->cqual_indexes,
+					root->simple_rel_array[right_relid]->cqual_indexes);
+
+				if (interset_cq_indexes != NIL &&
+					!root->simple_rte_array[left_relid]->inh &&
+					!root->simple_rte_array[right_relid]->inh)
+ 					should_eliminate = true;
+			}
+		}
+
+		// elog(INFO, "joinrel: %s, %d", bmsToString(joinrel->relids), should_eliminate);
+
+		if (should_eliminate)
+			m1 = calc_join_cqual_selectivity(root, joinrel, outer_rel, inner_rel, rinfo);
+
+		/* elog(INFO, */
+		/*	 "joinrelids: %s, outer_rel: %s, inner_rel: %s, join_clauselist: %s outer rows: %f, inner_rows: %f, join rows: %f, jselec: %f, m1 = %f, m2 = %f", */
+		/*	 bmsToString(joinrel->relids), */
+		/*	 bmsToString(outer_rel->relids), */
+		/*	 bmsToString(inner_rel->relids), */
+		/*	 bmsToString(join_list_relids), */
+		/*	 outer_rel->rows, */
+		/*	 inner_rel->rows, */
+		/*	 nrows, */
+		/*	 jselec, */
+		/*	 m1, */
+		/*	 m2); */
+		nrows /= m1;
+	}
+
 	return clamp_row_est(nrows);
 }
 
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 6b638d184aa..6cdff399d0a 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -1290,6 +1290,8 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 	EquivalenceMember **prev_ems;
 	ListCell   *lc;
 	ListCell   *lc2;
+	int	start_cq_index = list_length(root->correlative_quals);
+	int	ef_index = 0;
 
 	/*
 	 * We scan the EC members once and track the last-seen member for each
@@ -1356,9 +1358,11 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 
 	pfree(prev_ems);
 
+	if (ec->ec_broken)
+		goto ec_filter_done;
 
 	/*
-	 * Also push any EquivalenceFilter clauses down into all relations
+	 * Push any EquivalenceFilter clauses down into all relations
 	 * other than the one which the filter actually originated from.
 	 */
 	foreach(lc2, ec->ec_filters)
@@ -1368,19 +1372,25 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 		Expr *rightexpr;
 		Oid opno;
 		int relid;
-
-		if (ec->ec_broken)
-			break;
+		CorrelativeQuals *cquals = makeNode(CorrelativeQuals);
 
 		foreach(lc, ec->ec_members)
 		{
 			EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc);
+			RelOptInfo *rel;
+			RestrictInfo *rinfo;
 
 			if (!bms_get_singleton_member(cur_em->em_relids, &relid))
 				continue;
 
+			rel = root->simple_rel_array[relid];
+
 			if (ef->ef_source_rel == relid)
+			{
+				rel->cqual_indexes = lappend_int(rel->cqual_indexes, start_cq_index + ef_index);
+				cquals->corr_restrictinfo = lappend(cquals->corr_restrictinfo, ef->rinfo);
 				continue;
+			}
 
 			if (ef->ef_const_is_left)
 			{
@@ -1401,19 +1411,28 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 			if (opno == InvalidOid)
 				continue;
 
-
-			process_implied_equality(root, opno,
-									 ec->ec_collation,
-									 leftexpr,
-									 rightexpr,
-									 bms_copy(ec->ec_relids),
-									 bms_copy(cur_em->em_nullable_relids),
-									 ec->ec_min_security,
-									 ec->ec_below_outer_join,
-									 false);
+			rinfo = process_implied_equality(root, opno,
+											 ec->ec_collation,
+											 leftexpr,
+											 rightexpr,
+											 bms_copy(ec->ec_relids),
+											 bms_copy(cur_em->em_nullable_relids),
+											 ec->ec_min_security,
+											 ec->ec_below_outer_join,
+											 false);
+			cquals->corr_restrictinfo = lappend(cquals->corr_restrictinfo, rinfo);
+			rel->cqual_indexes = lappend_int(rel->cqual_indexes, start_cq_index + ef_index);
 		}
+
+		ef_index += 1;
+
+		root->correlative_quals = lappend(root->correlative_quals, cquals);
 	}
 
+ec_filter_done:
+	/*
+	 * XXX this label can be removed after moving ec_filter to the end of this function.
+	 */
 	/*
 	 * We also have to make sure that all the Vars used in the member clauses
 	 * will be available at any join node we might try to reference them at.
@@ -2091,6 +2110,7 @@ distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
 					efilter->ef_source_rel = relid;
 					efilter->opfamily = opfamily;
 					efilter->amstrategy = amstrategy;
+					efilter->rinfo = rinfo;
 
 					ec->ec_filters = lappend(ec->ec_filters, efilter);
 					break;		/* Onto the next eclass */
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index bd09f85aea1..5c9f2833f83 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -619,6 +619,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	root->multiexpr_params = NIL;
 	root->eq_classes = NIL;
 	root->ec_merging_done = false;
+	root->correlative_quals = NIL;
 	root->all_result_relids =
 		parse->resultRelation ? bms_make_singleton(parse->resultRelation) : NULL;
 	root->leaf_result_relids = NULL;	/* we'll find out leaf-ness later */
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 74823e8437a..6d3aceaadfe 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -919,6 +919,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	subroot->multiexpr_params = NIL;
 	subroot->eq_classes = NIL;
 	subroot->ec_merging_done = false;
+	subroot->correlative_quals = NIL;
 	subroot->all_result_relids = NULL;
 	subroot->leaf_result_relids = NULL;
 	subroot->append_rel_list = NIL;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7349afe1640..3bf83a4e405 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -266,6 +266,7 @@ typedef enum NodeTag
 	T_EquivalenceClass,
 	T_EquivalenceMember,
 	T_EquivalenceFilter,
+	T_CorrelativeQuals,
 	T_PathKey,
 	T_PathTarget,
 	T_RestrictInfo,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index e7b04211839..ca871fbb07a 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -250,6 +250,8 @@ struct PlannerInfo
 
 	bool		ec_merging_done;	/* set true once ECs are canonical */
 
+	List		*correlative_quals;  /* list of CorrelativeQuals for this subquery */
+
 	List	   *canon_pathkeys; /* list of "canonical" PathKeys */
 
 	List	   *left_join_clauses;	/* list of RestrictInfos for mergejoinable
@@ -722,6 +724,18 @@ typedef struct RelOptInfo
 	double		allvisfrac;
 	Bitmapset  *eclass_indexes; /* Indexes in PlannerInfo's eq_classes list of
 								 * ECs that mention this rel */
+	List		*cqual_indexes; /* Indexes in PlannerInfo's correlative_quals list of
+								 * CorrelativeQuals that this rel has applied. It is valid
+								 * on both baserel and joinrel. Used to quick check is the
+								 * both sides contains the same CorrectiveQuals object.
+								 */
+	Selectivity	*cqual_selecitiy; /*
+								   * The number of elements in cqual_selecitity equals
+								   * the length of cqual_indexes. The semantics is which
+								   * selectivity in the corresponding CorectiveQuals's qual
+								   * list is taking effect. At only time, only 1 Qual
+								   * Selectivity is counted for any-level of joinrel.
+								   */
 	PlannerInfo *subroot;		/* if subquery */
 	List	   *subplan_params; /* if subquery */
 	int			rel_parallel_workers;	/* wanted number of parallel workers */
@@ -1039,8 +1053,24 @@ typedef struct EquivalenceFilter
 	Index		ef_source_rel;	/* relid of originating relation. */
 	Oid			opfamily;
 	int			amstrategy;
+	struct RestrictInfo	*rinfo;		/* source restrictInfo for this EquivalenceFilter */
 } EquivalenceFilter;
 
+
+/*
+ * Currently it is as simple as a List of RestrictInfo, it means a). For any joinrel size
+ * estimation, only one restrictinfo on this group should be counted. b). During execution,
+ * at least 1 restrictinfo in this group should be executed.
+ *
+ * Define it as a Node just for better extendability, we can stripe it to a List *
+ * if we are sure nothing else is needed.
+ */
+typedef struct CorrelativeQuals
+{
+	NodeTag	type;
+	List	*corr_restrictinfo;
+} CorrelativeQuals;
+
 /*
  * If an EC contains a const and isn't below-outer-join, any PathKey depending
  * on it must be redundant, since there's only one possible value of the key.
@@ -2620,7 +2650,7 @@ typedef enum
  *
  * flags indicating what kinds of grouping are possible.
  * partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * 		have been initialized.
+ *		have been initialized.
  * agg_partial_costs gives partial aggregation costs.
  * agg_final_costs gives finalization costs.
  * target_parallel_safe is true if target is parallel safe.
@@ -2650,8 +2680,8 @@ typedef struct
  * 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).
+ *		expressions computed by preprocess_limit() (see comments for
+ *		preprocess_limit() for more information).
  */
 typedef struct
 {
-- 
2.21.0

v4-0003-Reduce-some-planning-cost-for-deriving-qual-for-E.patchapplication/octet-stream; name=v4-0003-Reduce-some-planning-cost-for-deriving-qual-for-E.patchDownload
From 42260ffcfac54f11abbdb57522a4feaa523d0f51 Mon Sep 17 00:00:00 2001
From: Andy Fan <yizhi.fzh@alibaba-inc.com>
Date: Sun, 6 Mar 2022 14:20:55 +0800
Subject: [PATCH v4 3/6] Reduce some planning cost for deriving qual for EC
 filter feature.

Mainly changes includes:
1. Check if the qual is simple enough by checking rinfo->right_relids and
info->right_relids, save the pull_varnos of rinfo->clause calls.
2. check contain_volatile_functions against RestrictInfo, so that
the result can be shared with following calls.
3. By employing the RestictInfo->btreeineqfamility which is calculating.
with same round of calculating  RestrictInfo->mergeopfamilies. In this
way we save the some calls for syscache.
4. Calculating the opfamility and amstrategy at
distribute_filter_quals_to_eclass and cache the results in EquivalenceFilter.
if no suitable opfamility and amstrategy are found, bypass the qual immediately
and at last using the cached value generate_base_implied_equalities_no_const.

After this change, there is an testcase changed unexpectedly in equivclass.out
(compared with David's expectation file.)

create user regress_user_ectest;
grant select on ec0 to regress_user_ectest;
grant select on ec1 to regress_user_ectest;

set session authorization regress_user_ectest;

-- with RLS active, the non-leakproof a.ff = 43 clause is not treated
-- as a suitable source for an EquivalenceClass; currently, this is true
-- even though the RLS clause has nothing to do directly with the EC
explain (costs off)
regression->   select * from ec0 a, ec1 b
regression->   where a.ff = b.ff and a.ff = 43::bigint::int8alias1;

The b.ff = 43 is disappeared from ec1 b. But since it even didn't shown
before the EC filter, so I'm not sure my changes here make something wrong,
maybe fix a issue by accidental?
---
 src/backend/nodes/outfuncs.c             |  2 +
 src/backend/optimizer/path/equivclass.c  | 59 +++++++++++++-----------
 src/backend/optimizer/plan/initsplan.c   | 50 ++++----------------
 src/include/nodes/pathnodes.h            |  2 +
 src/test/regress/expected/equivclass.out |  6 +--
 5 files changed, 48 insertions(+), 71 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 4c384511c39..d29b64eb918 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2513,6 +2513,8 @@ _outEquivalenceFilter(StringInfo str, const EquivalenceFilter *node)
 	WRITE_OID_FIELD(ef_opno);
 	WRITE_BOOL_FIELD(ef_const_is_left);
 	WRITE_UINT_FIELD(ef_source_rel);
+	WRITE_OID_FIELD(opfamily);
+	WRITE_INT_FIELD(amstrategy);
 }
 
 static void
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index f9ae2785d60..915888a8486 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -1252,19 +1252,17 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 }
 
 /*
- * finds the opfamily and strategy number for the specified 'opno' and 'method'
- * access method. Returns True if one is found and sets 'family' and
- * 'amstrategy', or returns False if none are found.
+ * finds the operator id for the specified 'opno' and 'method' and 'opfamilies'
+ * Returns True if one is found and sets 'opfamily_p' and 'amstrategy_p' or returns
+ * False if none are found.
  */
 static bool
-find_am_family_and_stategy(Oid opno, Oid method, Oid *family, int *amstrategy)
+find_am_family_and_stategy(Oid opno, Oid method, List *opfamilies,
+						   Oid *opfamily_p, int *amstrategy_p)
 {
-	List *opfamilies;
 	ListCell *l;
 	int strategy;
 
-	opfamilies = get_opfamilies(opno, method);
-
 	foreach(l, opfamilies)
 	{
 		Oid opfamily = lfirst_oid(l);
@@ -1273,8 +1271,8 @@ find_am_family_and_stategy(Oid opno, Oid method, Oid *family, int *amstrategy)
 
 		if (strategy)
 		{
-			*amstrategy = strategy;
-			*family = opfamily;
+			*opfamily_p = opfamily;
+			*amstrategy_p = strategy;
 			return true;
 		}
 	}
@@ -1363,17 +1361,11 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 			EquivalenceFilter *ef = (EquivalenceFilter *) lfirst(lc2);
 			Expr *leftexpr;
 			Expr *rightexpr;
-			int strategy;
 			Oid opno;
-			Oid family;
 
 			if (ef->ef_source_rel == relid)
 				continue;
 
-			if (!find_am_family_and_stategy(ef->ef_opno, BTREE_AM_OID,
-				&family, &strategy))
-				continue;
-
 			if (ef->ef_const_is_left)
 			{
 				leftexpr = (Expr *) ef->ef_const;
@@ -1385,10 +1377,10 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 				rightexpr = (Expr *) ef->ef_const;
 			}
 
-			opno = get_opfamily_member(family,
+			opno = get_opfamily_member(ef->opfamily,
 										exprType((Node *) leftexpr),
 										exprType((Node *) rightexpr),
-										strategy);
+										ef->amstrategy);
 
 			if (opno == InvalidOid)
 				continue;
@@ -1399,7 +1391,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 										rightexpr,
 										bms_copy(ec->ec_relids),
 										bms_copy(cur_em->em_nullable_relids),
-									 	ec->ec_min_security,
+										ec->ec_min_security,
 										ec->ec_below_outer_join,
 										false);
 		}
@@ -2007,9 +1999,12 @@ distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
 	 */
 	foreach(l, quallist)
 	{
-		OpExpr	   *opexpr = (OpExpr *) lfirst(l);
-		Expr	   *leftexpr = (Expr *) linitial(opexpr->args);
-		Expr	   *rightexpr = (Expr *) lsecond(opexpr->args);
+		RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
+		OpExpr *opexpr = (OpExpr *)(rinfo->clause);
+
+		Oid opfamily;
+		int amstrategy;
+
 		Const	   *constexpr;
 		Expr	   *varexpr;
 		Relids		exprrels;
@@ -2021,25 +2016,31 @@ distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
 		 * Determine if the the OpExpr is in the form "expr op const" or
 		 * "const op expr".
 		 */
-		if (IsA(leftexpr, Const))
+		if (bms_is_empty(rinfo->left_relids))
 		{
-			constexpr = (Const *) leftexpr;
-			varexpr = rightexpr;
+			constexpr = (Const *) get_leftop(rinfo->clause);
+			varexpr = (Expr *) get_rightop(rinfo->clause);
 			const_isleft = true;
+			exprrels = rinfo->right_relids;
 		}
 		else
 		{
-			constexpr = (Const *) rightexpr;
-			varexpr = leftexpr;
+			constexpr = (Const *) get_rightop(rinfo->clause);
+			varexpr = (Expr *) get_leftop(rinfo->clause);
 			const_isleft = false;
+			exprrels = rinfo->left_relids;
 		}
 
-		exprrels = pull_varnos(root, (Node *) varexpr);
-
 		/* should be filtered out, but we need to determine relid anyway */
 		if (!bms_get_singleton_member(exprrels, &relid))
 			continue;
 
+		if (!find_am_family_and_stategy(opexpr->opno, BTREE_AM_OID,
+										rinfo->btreeineqopfamilies,
+										&opfamily,
+										&amstrategy))
+			continue;
+
 		/* search for a matching eclass member in all eclasses */
 		foreach(l2, root->eq_classes)
 		{
@@ -2075,6 +2076,8 @@ distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
 					efilter->ef_const_is_left = const_isleft;
 					efilter->ef_opno = opexpr->opno;
 					efilter->ef_source_rel = relid;
+					efilter->opfamily = opfamily;
+					efilter->amstrategy = amstrategy;
 
 					ec->ec_filters = lappend(ec->ec_filters, efilter);
 					break;		/* Onto the next eclass */
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 7e355c94362..5a32c3987a0 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -651,44 +651,6 @@ create_lateral_join_info(PlannerInfo *root)
 	}
 }
 
-/*
- * is_simple_filter_qual
- *             Analyzes an OpExpr to determine if it may be useful as an
- *             EquivalenceFilter. Returns true if the OpExpr may be of some use, or
- *             false if it should not be used.
- */
-static bool
-is_simple_filter_qual(PlannerInfo *root, OpExpr *expr)
-{
-	Expr *leftexpr;
-	Expr *rightexpr;
-
-	if (!IsA(expr, OpExpr))
-			return false;
-
-	if (list_length(expr->args) != 2)
-			return false;
-
-	leftexpr = (Expr *) linitial(expr->args);
-	rightexpr = (Expr *) lsecond(expr->args);
-
-	/* XXX should we restrict these to simple Var op Const expressions? */
-	if (IsA(leftexpr, Const))
-	{
-		if (bms_membership(pull_varnos(root, (Node *) rightexpr)) == BMS_SINGLETON &&
-			!contain_volatile_functions((Node *) rightexpr))
-			return true;
-	}
-	else if (IsA(rightexpr, Const))
-	{
-		if (bms_membership(pull_varnos(root, (Node *) leftexpr)) == BMS_SINGLETON &&
-			!contain_volatile_functions((Node *) leftexpr))
-			return true;
-	}
-
-	return false;
-}
-
 /*****************************************************************************
  *
  *	  JOIN TREE PROCESSING
@@ -1678,6 +1640,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	bool		maybe_outer_join;
 	Relids		nullable_relids;
 	RestrictInfo *restrictinfo;
+	int	relid;
 
 	/*
 	 * Retrieve all relids mentioned within the clause.
@@ -2027,8 +1990,15 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	distribute_restrictinfo_to_rels(root, restrictinfo);
 
 	/* Check if the qual looks useful to harvest as an EquivalenceFilter */
-	if (filter_qual_list != NULL && is_simple_filter_qual(root, (OpExpr *) clause))
-		*filter_qual_list = lappend(*filter_qual_list, clause);
+	if (filter_qual_list != NULL &&
+		is_opclause(restrictinfo->clause) &&
+		!contain_volatile_functions((Node *)restrictinfo) && // Cachable
+		restrictinfo->btreeineqopfamilies != NIL &&  /* ineq expression */
+		/* simple & common enough filter, one side references one relation and the other one is a constant */
+		((bms_is_empty(restrictinfo->left_relids) && bms_get_singleton_member(restrictinfo->right_relids, &relid)) ||
+		 (bms_is_empty(restrictinfo->right_relids) && bms_get_singleton_member(restrictinfo->left_relids, &relid)))
+		)
+		*filter_qual_list = lappend(*filter_qual_list, restrictinfo);
 }
 
 /*
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index dcae69635ce..e7b04211839 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1037,6 +1037,8 @@ typedef struct EquivalenceFilter
 	Oid			ef_opno;		/* Operator Oid of filter operator */
 	bool		ef_const_is_left; /* Is the Const on the left of the OpExrp? */
 	Index		ef_source_rel;	/* relid of originating relation. */
+	Oid			opfamily;
+	int			amstrategy;
 } EquivalenceFilter;
 
 /*
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index 92fcec1158b..980bd3817d3 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -407,14 +407,14 @@ set session authorization regress_user_ectest;
 explain (costs off)
   select * from ec0 a, ec1 b
   where a.ff = b.ff and a.ff = 43::bigint::int8alias1;
-                              QUERY PLAN                              
-----------------------------------------------------------------------
+                 QUERY PLAN                  
+---------------------------------------------
  Nested Loop
    ->  Index Scan using ec0_pkey on ec0 a
          Index Cond: (ff = '43'::int8alias1)
    ->  Index Scan using ec1_pkey on ec1 b
          Index Cond: (ff = a.ff)
-         Filter: ((f1 < '5'::int8alias1) AND (ff = '43'::int8alias1))
+         Filter: (f1 < '5'::int8alias1)
 (6 rows)
 
 reset session authorization;
-- 
2.21.0

v4-0002-Introudce-ec_filters-in-EquivalenceClass-struct-t.patchapplication/octet-stream; name=v4-0002-Introudce-ec_filters-in-EquivalenceClass-struct-t.patchDownload
From fbe7ad0a8111245d676b2104e4982665044a3982 Mon Sep 17 00:00:00 2001
From: David Rowley <dgrowleyml@gmail.com>
Date: Tue, 1 Feb 2022 20:56:40 +0800
Subject: [PATCH v4 2/6] Introudce ec_filters in EquivalenceClass struct, the
 semantics is the quals can

be applied to any EquivalenceMember in this EC. Later this information is used
to generate new RestrictInfo and was distributed to related RelOptInfo very
soon.  There are 3 major steps here:

a). In distribute_qual_to_rels to gather the ineq quallist.
b). After deconstruct_jointree, distribute_filter_quals_to_eclass distribute
these ineq-quallist to the related EC's ef_filters.
c). generate_base_implied_equalities_no_const scan the ec_filters and distriubte
the restrictinfo to related RelOptInfo.

Author: David Rowley at 2015-12 [1]
Andy Fan rebases this patch to current latest code.

https://www.postgresql.org/message-id/CAKJS1f9FK_X_5HKcPcSeimy16Owe3EmPmmGsGWLcKkj_rW9s6A%40mail.gmail.com
---
 .../postgres_fdw/expected/postgres_fdw.out    |  36 ++--
 src/backend/nodes/outfuncs.c                  |  14 ++
 src/backend/optimizer/path/equivclass.c       | 182 ++++++++++++++++++
 src/backend/optimizer/plan/initsplan.c        |  96 +++++++--
 src/backend/utils/cache/lsyscache.c           |  28 +++
 src/include/nodes/nodes.h                     |   1 +
 src/include/nodes/pathnodes.h                 |  37 ++++
 src/include/optimizer/paths.h                 |   1 +
 src/include/utils/lsyscache.h                 |   1 +
 src/test/regress/expected/equivclass.out      |  45 ++++-
 src/test/regress/expected/join.out            |  22 +--
 src/test/regress/expected/partition_join.out  |  52 +++--
 src/test/regress/sql/equivclass.sql           |  12 ++
 13 files changed, 457 insertions(+), 70 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index f210f911880..ce102abe5d5 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -1545,12 +1545,12 @@ SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNE
 -- full outer join + inner join
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
-                                                                                                                                                 QUERY PLAN                                                                                                                                                 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                                           QUERY PLAN                                                                                                                                                                           
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Foreign Scan
    Output: t1.c1, t2.c1, t3.c1
    Relations: ((public.ft4 t1) INNER JOIN (public.ft5 t2)) FULL JOIN (public.ft4 t3)
-   Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint
+   Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND (((r2.c1 + 1) >= 50)) AND (((r2.c1 + 1) <= 60)) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
@@ -2335,12 +2335,12 @@ SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT
 UPDATE ft5 SET c3 = null where c1 % 9 = 0;
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
-                                                                                                                                QUERY PLAN                                                                                                                                 
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                    QUERY PLAN                                                                                                                                                     
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Foreign Scan
    Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2
    Relations: (public.ft5) INNER JOIN (public.ft4)
-   Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST
+   Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)) AND ((r1.c1 >= 10)) AND ((r1.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST
 (4 rows)
 
 SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
@@ -2362,8 +2362,8 @@ SET enable_hashjoin TO false;
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1
     AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE;
-                                                                                                                                                                                                                                                                                                                                                                                                                                               QUERY PLAN                                                                                                                                                                                                                                                                                                                                                                                                                                               
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                                                                                                                                                                                                                                                                                                                                      QUERY PLAN                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  LockRows
    Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ctid
    ->  Merge Join
@@ -2373,7 +2373,7 @@ SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = f
          ->  Foreign Scan
                Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
                Relations: (((public.ft1) INNER JOIN (public.ft2)) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
-               Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r4.c1, r4.c2, r4.c3, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4.c1, r4.c2, r4.c3) END FROM ((("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 3" r3 ON (((r1.c2 = r3.c1)))) INNER JOIN "S 1"."T 4" r4 ON (((r1.c2 = r4.c1)))) ORDER BY r1.c2 ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3 FOR UPDATE OF r4
+               Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r4.c1, r4.c2, r4.c3, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4.c1, r4.c2, r4.c3) END FROM ((("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" < 100)) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 3" r3 ON (((r1.c2 = r3.c1)))) INNER JOIN "S 1"."T 4" r4 ON (((r1.c2 = r4.c1)))) ORDER BY r1.c2 ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3 FOR UPDATE OF r4
                ->  Merge Join
                      Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
                      Merge Cond: (ft1.c2 = ft5.c1)
@@ -2391,12 +2391,12 @@ SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = f
                                              Sort Key: ft1.c1
                                              ->  Foreign Scan on public.ft1
                                                    Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
-                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE
+                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND (("C 1" < 100)) FOR UPDATE
                                        ->  Materialize
                                              Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
                                              ->  Foreign Scan on public.ft2
                                                    Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
-                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
+                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
                            ->  Sort
                                  Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
                                  Sort Key: ft4.c1
@@ -5685,25 +5685,25 @@ UPDATE ft2 AS target SET (c2) = (
 EXPLAIN (VERBOSE, COSTS OFF)
 UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END
   FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000;
-                                                                                                                                                                                       QUERY PLAN                                                                                                                                                                                        
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                                                                   QUERY PLAN                                                                                                                                                                                                    
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Update on public.ft2 d
    Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1
    ->  Foreign Scan
          Output: CASE WHEN (random() >= '0'::double precision) THEN d.c2 ELSE 0 END, d.ctid, d.*, t.*
          Relations: (public.ft2 d) INNER JOIN (public.ft2 t)
-         Remote SQL: SELECT r1.c2, r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r1."C 1" > 1000)))) FOR UPDATE OF r1
-         ->  Hash Join
+         Remote SQL: SELECT r1.c2, r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" > 1000)) AND ((r1."C 1" > 1000)))) FOR UPDATE OF r1
+         ->  Merge Join
                Output: d.c2, d.ctid, d.*, t.*
-               Hash Cond: (d.c1 = t.c1)
+               Merge Cond: (d.c1 = t.c1)
                ->  Foreign Scan on public.ft2 d
                      Output: d.c2, d.ctid, d.*, d.c1
                      Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
-               ->  Hash
+               ->  Materialize
                      Output: t.*, t.c1
                      ->  Foreign Scan on public.ft2 t
                            Output: t.*, t.c1
-                           Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
+                           Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST
 (17 rows)
 
 UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 6bdad462c78..4c384511c39 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2504,6 +2504,17 @@ _outEquivalenceMember(StringInfo str, const EquivalenceMember *node)
 	WRITE_OID_FIELD(em_datatype);
 }
 
+static void
+_outEquivalenceFilter(StringInfo str, const EquivalenceFilter *node)
+{
+	WRITE_NODE_TYPE("EQUIVALENCEFILTER");
+
+	WRITE_NODE_FIELD(ef_const);
+	WRITE_OID_FIELD(ef_opno);
+	WRITE_BOOL_FIELD(ef_const_is_left);
+	WRITE_UINT_FIELD(ef_source_rel);
+}
+
 static void
 _outPathKey(StringInfo str, const PathKey *node)
 {
@@ -4306,6 +4317,9 @@ outNode(StringInfo str, const void *obj)
 			case T_EquivalenceMember:
 				_outEquivalenceMember(str, obj);
 				break;
+			case T_EquivalenceFilter:
+				_outEquivalenceFilter(str, obj);
+				break;
 			case T_PathKey:
 				_outPathKey(str, obj);
 				break;
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 8c6770de972..f9ae2785d60 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -19,6 +19,7 @@
 #include <limits.h>
 
 #include "access/stratnum.h"
+#include "catalog/pg_am.h"
 #include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -1250,6 +1251,37 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 	}
 }
 
+/*
+ * finds the opfamily and strategy number for the specified 'opno' and 'method'
+ * access method. Returns True if one is found and sets 'family' and
+ * 'amstrategy', or returns False if none are found.
+ */
+static bool
+find_am_family_and_stategy(Oid opno, Oid method, Oid *family, int *amstrategy)
+{
+	List *opfamilies;
+	ListCell *l;
+	int strategy;
+
+	opfamilies = get_opfamilies(opno, method);
+
+	foreach(l, opfamilies)
+	{
+		Oid opfamily = lfirst_oid(l);
+
+		strategy = get_op_opfamily_strategy(opno, opfamily);
+
+		if (strategy)
+		{
+			*amstrategy = strategy;
+			*family = opfamily;
+			return true;
+		}
+	}
+
+	return false;
+}
+
 /*
  * generate_base_implied_equalities when EC contains no pseudoconstants
  */
@@ -1259,6 +1291,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 {
 	EquivalenceMember **prev_ems;
 	ListCell   *lc;
+	ListCell   *lc2;
 
 	/*
 	 * We scan the EC members once and track the last-seen member for each
@@ -1320,6 +1353,57 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 				rinfo->right_em = cur_em;
 			}
 		}
+
+		/*
+		 * Also push any EquivalenceFilter clauses down into all relations
+		 * other than the one which the filter actually originated from.
+		 */
+		foreach(lc2, ec->ec_filters)
+		{
+			EquivalenceFilter *ef = (EquivalenceFilter *) lfirst(lc2);
+			Expr *leftexpr;
+			Expr *rightexpr;
+			int strategy;
+			Oid opno;
+			Oid family;
+
+			if (ef->ef_source_rel == relid)
+				continue;
+
+			if (!find_am_family_and_stategy(ef->ef_opno, BTREE_AM_OID,
+				&family, &strategy))
+				continue;
+
+			if (ef->ef_const_is_left)
+			{
+				leftexpr = (Expr *) ef->ef_const;
+				rightexpr = cur_em->em_expr;
+			}
+			else
+			{
+				leftexpr = cur_em->em_expr;
+				rightexpr = (Expr *) ef->ef_const;
+			}
+
+			opno = get_opfamily_member(family,
+										exprType((Node *) leftexpr),
+										exprType((Node *) rightexpr),
+										strategy);
+
+			if (opno == InvalidOid)
+				continue;
+
+			process_implied_equality(root, opno,
+										ec->ec_collation,
+										leftexpr,
+										rightexpr,
+										bms_copy(ec->ec_relids),
+										bms_copy(cur_em->em_nullable_relids),
+									 	ec->ec_min_security,
+										ec->ec_below_outer_join,
+										false);
+		}
+
 		prev_ems[relid] = cur_em;
 	}
 
@@ -1901,6 +1985,104 @@ create_join_clause(PlannerInfo *root,
 	return rinfo;
 }
 
+/*
+ * distribute_filter_quals_to_eclass
+ *		For each OpExpr in quallist look for an eclass which has an Expr
+ *		matching the Expr in the OpExpr. If a match is found we add a new
+ *		EquivalenceFilter to the eclass containing the filter details.
+ */
+void
+distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
+{
+	ListCell *l;
+
+	/* fast path for when no eclasses have been generated */
+	if (root->eq_classes == NIL)
+		return;
+
+	/*
+	 * For each qual in quallist try and find an eclass which contains the
+	 * non-Const part of the OpExpr. We'll tag any matches that we find onto
+	 * the correct eclass.
+	 */
+	foreach(l, quallist)
+	{
+		OpExpr	   *opexpr = (OpExpr *) lfirst(l);
+		Expr	   *leftexpr = (Expr *) linitial(opexpr->args);
+		Expr	   *rightexpr = (Expr *) lsecond(opexpr->args);
+		Const	   *constexpr;
+		Expr	   *varexpr;
+		Relids		exprrels;
+		int			relid;
+		bool		const_isleft;
+		ListCell *l2;
+
+		/*
+		 * Determine if the the OpExpr is in the form "expr op const" or
+		 * "const op expr".
+		 */
+		if (IsA(leftexpr, Const))
+		{
+			constexpr = (Const *) leftexpr;
+			varexpr = rightexpr;
+			const_isleft = true;
+		}
+		else
+		{
+			constexpr = (Const *) rightexpr;
+			varexpr = leftexpr;
+			const_isleft = false;
+		}
+
+		exprrels = pull_varnos(root, (Node *) varexpr);
+
+		/* should be filtered out, but we need to determine relid anyway */
+		if (!bms_get_singleton_member(exprrels, &relid))
+			continue;
+
+		/* search for a matching eclass member in all eclasses */
+		foreach(l2, root->eq_classes)
+		{
+			EquivalenceClass *ec = (EquivalenceClass *) lfirst(l2);
+			ListCell *l3;
+
+			if (ec->ec_broken || ec->ec_has_volatile)
+				continue;
+
+			/*
+			 * if the eclass has a const then that const will serve as the
+			 * filter, we needn't add any others.
+			 */
+			if (ec->ec_has_const)
+				continue;
+
+			/* skip this eclass no members exist which belong to this relid */
+			if (!bms_is_member(relid, ec->ec_relids))
+				continue;
+
+			foreach(l3, ec->ec_members)
+			{
+				EquivalenceMember *em = (EquivalenceMember *) lfirst(l3);
+
+				if (!bms_is_member(relid, em->em_relids))
+					continue;
+
+				if (equal(em->em_expr, varexpr))
+				{
+					EquivalenceFilter *efilter;
+					efilter = makeNode(EquivalenceFilter);
+					efilter->ef_const = (Const *) copyObject(constexpr);
+					efilter->ef_const_is_left = const_isleft;
+					efilter->ef_opno = opexpr->opno;
+					efilter->ef_source_rel = relid;
+
+					ec->ec_filters = lappend(ec->ec_filters, efilter);
+					break;		/* Onto the next eclass */
+				}
+			}
+		}
+	}
+}
 
 /*
  * reconsider_outer_join_clauses
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index d61419f61a5..7e355c94362 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -53,7 +53,7 @@ static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel,
 static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
 								 bool below_outer_join,
 								 Relids *qualscope, Relids *inner_join_rels,
-								 List **postponed_qual_list);
+								 List **postponed_qual_list, List **filter_qual_list);
 static void process_security_barrier_quals(PlannerInfo *root,
 										   int rti, Relids qualscope,
 										   bool below_outer_join);
@@ -70,7 +70,8 @@ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 									Relids qualscope,
 									Relids ojscope,
 									Relids outerjoin_nonnullable,
-									List **postponed_qual_list);
+									List **postponed_qual_list,
+									List **filter_qual_list);
 static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p,
 								  Relids *nullable_relids_p, bool is_pushed_down);
 static bool check_equivalence_delay(PlannerInfo *root,
@@ -650,6 +651,43 @@ create_lateral_join_info(PlannerInfo *root)
 	}
 }
 
+/*
+ * is_simple_filter_qual
+ *             Analyzes an OpExpr to determine if it may be useful as an
+ *             EquivalenceFilter. Returns true if the OpExpr may be of some use, or
+ *             false if it should not be used.
+ */
+static bool
+is_simple_filter_qual(PlannerInfo *root, OpExpr *expr)
+{
+	Expr *leftexpr;
+	Expr *rightexpr;
+
+	if (!IsA(expr, OpExpr))
+			return false;
+
+	if (list_length(expr->args) != 2)
+			return false;
+
+	leftexpr = (Expr *) linitial(expr->args);
+	rightexpr = (Expr *) lsecond(expr->args);
+
+	/* XXX should we restrict these to simple Var op Const expressions? */
+	if (IsA(leftexpr, Const))
+	{
+		if (bms_membership(pull_varnos(root, (Node *) rightexpr)) == BMS_SINGLETON &&
+			!contain_volatile_functions((Node *) rightexpr))
+			return true;
+	}
+	else if (IsA(rightexpr, Const))
+	{
+		if (bms_membership(pull_varnos(root, (Node *) leftexpr)) == BMS_SINGLETON &&
+			!contain_volatile_functions((Node *) leftexpr))
+			return true;
+	}
+
+	return false;
+}
 
 /*****************************************************************************
  *
@@ -690,6 +728,7 @@ deconstruct_jointree(PlannerInfo *root)
 	Relids		qualscope;
 	Relids		inner_join_rels;
 	List	   *postponed_qual_list = NIL;
+	List	   *filter_qual_list = NIL;
 
 	/* Start recursion at top of jointree */
 	Assert(root->parse->jointree != NULL &&
@@ -700,11 +739,14 @@ deconstruct_jointree(PlannerInfo *root)
 
 	result = deconstruct_recurse(root, (Node *) root->parse->jointree, false,
 								 &qualscope, &inner_join_rels,
-								 &postponed_qual_list);
+								 &postponed_qual_list, &filter_qual_list);
 
 	/* Shouldn't be any leftover quals */
 	Assert(postponed_qual_list == NIL);
 
+	/* try and match each filter_qual_list item up with an eclass. */
+	distribute_filter_quals_to_eclass(root, filter_qual_list);
+
 	return result;
 }
 
@@ -725,6 +767,8 @@ deconstruct_jointree(PlannerInfo *root)
  *		or free this, either)
  *	*postponed_qual_list is a list of PostponedQual structs, which we can
  *		add quals to if they turn out to belong to a higher join level
+ *	*filter_qual_list is appended to with a list of quals which may be useful
+ *		include as EquivalenceFilters.
  *	Return value is the appropriate joinlist for this jointree node
  *
  * In addition, entries will be added to root->join_info_list for outer joins.
@@ -732,7 +776,7 @@ deconstruct_jointree(PlannerInfo *root)
 static List *
 deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 					Relids *qualscope, Relids *inner_join_rels,
-					List **postponed_qual_list)
+					List **postponed_qual_list, List **filter_qual_list)
 {
 	List	   *joinlist;
 
@@ -785,7 +829,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 											   below_outer_join,
 											   &sub_qualscope,
 											   inner_join_rels,
-											   &child_postponed_quals);
+											   &child_postponed_quals,
+											   filter_qual_list);
 			*qualscope = bms_add_members(*qualscope, sub_qualscope);
 			sub_members = list_length(sub_joinlist);
 			remaining--;
@@ -819,7 +864,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 										below_outer_join, JOIN_INNER,
 										root->qual_security_level,
 										*qualscope, NULL, NULL,
-										NULL);
+										NULL,
+										filter_qual_list);
 			else
 				*postponed_qual_list = lappend(*postponed_qual_list, pq);
 		}
@@ -835,7 +881,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 									below_outer_join, JOIN_INNER,
 									root->qual_security_level,
 									*qualscope, NULL, NULL,
-									postponed_qual_list);
+									postponed_qual_list,
+									filter_qual_list);
 		}
 	}
 	else if (IsA(jtnode, JoinExpr))
@@ -873,11 +920,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													below_outer_join,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = *qualscope;
 				/* Inner join adds no restrictions for quals */
@@ -890,11 +939,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													true,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = bms_union(left_inners, right_inners);
 				nonnullable_rels = leftids;
@@ -904,11 +955,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													below_outer_join,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = bms_union(left_inners, right_inners);
 				/* Semi join adds no restrictions for quals */
@@ -925,11 +978,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   true,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													true,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = bms_union(left_inners, right_inners);
 				/* each side is both outer and inner */
@@ -1013,7 +1068,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 									root->qual_security_level,
 									*qualscope,
 									ojscope, nonnullable_rels,
-									postponed_qual_list);
+									postponed_qual_list,
+									filter_qual_list);
 		}
 
 		/* Now we can add the SpecialJoinInfo to join_info_list */
@@ -1117,6 +1173,7 @@ process_security_barrier_quals(PlannerInfo *root,
 									qualscope,
 									qualscope,
 									NULL,
+									NULL,
 									NULL);
 		}
 		security_level++;
@@ -1610,7 +1667,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 						Relids qualscope,
 						Relids ojscope,
 						Relids outerjoin_nonnullable,
-						List **postponed_qual_list)
+						List **postponed_qual_list,
+						List **filter_qual_list)
 {
 	Relids		relids;
 	bool		is_pushed_down;
@@ -1967,6 +2025,10 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 
 	/* No EC special case applies, so push it into the clause lists */
 	distribute_restrictinfo_to_rels(root, restrictinfo);
+
+	/* Check if the qual looks useful to harvest as an EquivalenceFilter */
+	if (filter_qual_list != NULL && is_simple_filter_qual(root, (OpExpr *) clause))
+		*filter_qual_list = lappend(*filter_qual_list, clause);
 }
 
 /*
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 91cd813ce8f..b0243925e43 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -341,6 +341,34 @@ get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type)
 	return result;
 }
 
+/*
+ * get_opfamilies
+ *		Returns a list of Oids of each opfamily which 'opno' belonging to
+ *		'method' access method.
+ */
+List *
+get_opfamilies(Oid opno, Oid method)
+{
+	List	   *result = NIL;
+	CatCList   *catlist;
+	int			i;
+
+	catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno));
+
+	for (i = 0; i < catlist->n_members; i++)
+	{
+		HeapTuple	tuple = &catlist->members[i]->tuple;
+		Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
+
+		if (aform->amopmethod == method)
+			result = lappend_oid(result, aform->amopfamily);
+	}
+
+	ReleaseSysCacheList(catlist);
+
+	return result;
+}
+
 /*
  * get_mergejoin_opfamilies
  *		Given a putatively mergejoinable operator, return a list of the OIDs
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 5d075f0c346..7349afe1640 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -265,6 +265,7 @@ typedef enum NodeTag
 	/* these aren't subclasses of Path: */
 	T_EquivalenceClass,
 	T_EquivalenceMember,
+	T_EquivalenceFilter,
 	T_PathKey,
 	T_PathTarget,
 	T_RestrictInfo,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 3b95e4a8eae..dcae69635ce 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -990,6 +990,7 @@ typedef struct EquivalenceClass
 	List	   *ec_members;		/* list of EquivalenceMembers */
 	List	   *ec_sources;		/* list of generating RestrictInfos */
 	List	   *ec_derives;		/* list of derived RestrictInfos */
+	List	   *ec_filters;
 	Relids		ec_relids;		/* all relids appearing in ec_members, except
 								 * for child members (see below) */
 	bool		ec_has_const;	/* any pseudoconstants in ec_members? */
@@ -1002,6 +1003,42 @@ typedef struct EquivalenceClass
 	struct EquivalenceClass *ec_merged; /* set if merged into another EC */
 } EquivalenceClass;
 
+/*
+ * EquivalenceFilter - List of filters on Consts which belong to the
+ * EquivalenceClass.
+ *
+ * When building the equivalence classes we also collected a list of quals in
+ * the form of; "Expr op Const" and "Const op Expr". These are collected in the
+ * hope that we'll later generate an equivalence class which contains the
+ * "Expr" part. For example, if we parse a query such as;
+ *
+ *		SELECT * FROM t1 INNER JOIN t2 ON t1.id = t2.id WHERE t1.id < 10;
+ *
+ * then since we'll end up with an equivalence class containing {t1.id,t2.id},
+ * we'll tag the "< 10" filter onto the eclass. We are able to do this because
+ * the eclass proves equality between each class member, therefore all members
+ * must be below 10.
+ *
+ * EquivalenceFilters store the details required to allow us to push these
+ * filter clauses down into other relations which share an equivalence class
+ * containing a member which matches the expression of this EquivalenceFilter.
+ *
+ * ef_const is the Const value which this filter should filter against.
+ * ef_opno is the operator to filter on.
+ * ef_const_is_left marks if the OpExpr was in the form "Const op Expr" or
+ * "Expr op Const".
+ * ef_source_rel is the relation id of where this qual originated from.
+ */
+typedef struct EquivalenceFilter
+{
+	NodeTag		type;
+
+	Const	   *ef_const;		/* the constant expression to filter on */
+	Oid			ef_opno;		/* Operator Oid of filter operator */
+	bool		ef_const_is_left; /* Is the Const on the left of the OpExrp? */
+	Index		ef_source_rel;	/* relid of originating relation. */
+} EquivalenceFilter;
+
 /*
  * If an EC contains a const and isn't below-outer-join, any PathKey depending
  * on it must be redundant, since there's only one possible value of the key.
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 0c3a0b90c85..ce2aac7d3aa 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -126,6 +126,7 @@ extern bool process_equivalence(PlannerInfo *root,
 extern Expr *canonicalize_ec_expression(Expr *expr,
 										Oid req_type, Oid req_collation);
 extern void reconsider_outer_join_clauses(PlannerInfo *root);
+extern void distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist);
 extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root,
 												  Expr *expr,
 												  Relids nullable_relids,
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 5b5fac0397c..e0ed28f330b 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -78,6 +78,7 @@ extern bool get_ordering_op_properties(Oid opno,
 									   Oid *opfamily, Oid *opcintype, int16 *strategy);
 extern Oid	get_equality_op_for_ordering_op(Oid opno, bool *reverse);
 extern Oid	get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type);
+extern List *get_opfamilies(Oid opno, Oid method);
 extern List *get_mergejoin_opfamilies(Oid opno);
 extern void get_btree_opfamilies(Oid opno, List **mergeable_opfamilies, List **unmergeable_btree_opfamilies);
 extern bool get_compatible_hash_operators(Oid opno,
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index 126f7047fed..92fcec1158b 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -407,14 +407,14 @@ set session authorization regress_user_ectest;
 explain (costs off)
   select * from ec0 a, ec1 b
   where a.ff = b.ff and a.ff = 43::bigint::int8alias1;
-                 QUERY PLAN                  
----------------------------------------------
+                              QUERY PLAN                              
+----------------------------------------------------------------------
  Nested Loop
    ->  Index Scan using ec0_pkey on ec0 a
          Index Cond: (ff = '43'::int8alias1)
    ->  Index Scan using ec1_pkey on ec1 b
          Index Cond: (ff = a.ff)
-         Filter: (f1 < '5'::int8alias1)
+         Filter: ((f1 < '5'::int8alias1) AND (ff = '43'::int8alias1))
 (6 rows)
 
 reset session authorization;
@@ -451,3 +451,42 @@ explain (costs off)  -- this should not require a sort
    Filter: (f1 = 'foo'::name)
 (2 rows)
 
+-- test equivalence filters
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec0.ff between 1 and 10;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Nested Loop
+   Join Filter: (ec0.ff = ec1.ff)
+   ->  Bitmap Heap Scan on ec0
+         Recheck Cond: ((ff >= 1) AND (ff <= 10))
+         ->  Bitmap Index Scan on ec0_pkey
+               Index Cond: ((ff >= 1) AND (ff <= 10))
+   ->  Materialize
+         ->  Bitmap Heap Scan on ec1
+               Recheck Cond: ((ff >= 1) AND (ff <= 10))
+               ->  Bitmap Index Scan on ec1_pkey
+                     Index Cond: ((ff >= 1) AND (ff <= 10))
+(11 rows)
+
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec1.ff between 1 and 10;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Nested Loop
+   Join Filter: (ec0.ff = ec1.ff)
+   ->  Bitmap Heap Scan on ec0
+         Recheck Cond: ((ff >= 1) AND (ff <= 10))
+         ->  Bitmap Index Scan on ec0_pkey
+               Index Cond: ((ff >= 1) AND (ff <= 10))
+   ->  Materialize
+         ->  Bitmap Heap Scan on ec1
+               Recheck Cond: ((ff >= 1) AND (ff <= 10))
+               ->  Bitmap Index Scan on ec1_pkey
+                     Index Cond: ((ff >= 1) AND (ff <= 10))
+(11 rows)
+
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 19caebabd01..6a548903eec 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -3337,7 +3337,7 @@ where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
    Join Filter: (t1.stringu1 > t2.stringu2)
    ->  Nested Loop
          ->  Seq Scan on int4_tbl i1
-               Filter: (f1 = 0)
+               Filter: ((f1 = 0) AND (11 < 42))
          ->  Index Scan using tenk1_unique2 on tenk1 t1
                Index Cond: ((unique2 = (11)) AND (unique2 < 42))
    ->  Index Scan using tenk1_unique1 on tenk1 t2
@@ -6550,23 +6550,22 @@ where exists (select 1 from tenk1 t3
 ---------------------------------------------------------------------------------
  Nested Loop
    Output: t1.unique1, t2.hundred
-   ->  Hash Join
+   ->  Nested Loop
          Output: t1.unique1, t3.tenthous
-         Hash Cond: (t3.thousand = t1.unique1)
+         Join Filter: (t1.unique1 = t3.thousand)
+         ->  Index Only Scan using onek_unique1 on public.onek t1
+               Output: t1.unique1
+               Index Cond: (t1.unique1 < 1)
          ->  HashAggregate
                Output: t3.thousand, t3.tenthous
                Group Key: t3.thousand, t3.tenthous
                ->  Index Only Scan using tenk1_thous_tenthous on public.tenk1 t3
                      Output: t3.thousand, t3.tenthous
-         ->  Hash
-               Output: t1.unique1
-               ->  Index Only Scan using onek_unique1 on public.onek t1
-                     Output: t1.unique1
-                     Index Cond: (t1.unique1 < 1)
+                     Index Cond: (t3.thousand < 1)
    ->  Index Only Scan using tenk1_hundred on public.tenk1 t2
          Output: t2.hundred
          Index Cond: (t2.hundred = t3.tenthous)
-(18 rows)
+(17 rows)
 
 -- ... unless it actually is unique
 create table j3 as select unique1, tenthous from onek;
@@ -6584,15 +6583,16 @@ where exists (select 1 from j3
    Output: t1.unique1, t2.hundred
    ->  Nested Loop
          Output: t1.unique1, j3.tenthous
+         Join Filter: (t1.unique1 = j3.unique1)
          ->  Index Only Scan using onek_unique1 on public.onek t1
                Output: t1.unique1
                Index Cond: (t1.unique1 < 1)
          ->  Index Only Scan using j3_unique1_tenthous_idx on public.j3
                Output: j3.unique1, j3.tenthous
-               Index Cond: (j3.unique1 = t1.unique1)
+               Index Cond: (j3.unique1 < 1)
    ->  Index Only Scan using tenk1_hundred on public.tenk1 t2
          Output: t2.hundred
          Index Cond: (t2.hundred = j3.tenthous)
-(13 rows)
+(14 rows)
 
 drop table j3;
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index bb5b7c47a45..5a2923bac6c 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -186,17 +186,17 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0)
 -- Join with pruned partitions from joining relations
 EXPLAIN (COSTS OFF)
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
-                     QUERY PLAN                      
------------------------------------------------------
+                            QUERY PLAN                             
+-------------------------------------------------------------------
  Sort
    Sort Key: t1.a
    ->  Hash Join
          Hash Cond: (t2.b = t1.a)
          ->  Seq Scan on prt2_p2 t2
-               Filter: (b > 250)
+               Filter: ((b > 250) AND (b < 450))
          ->  Hash
                ->  Seq Scan on prt1_p2 t1
-                     Filter: ((a < 450) AND (b = 0))
+                     Filter: ((a < 450) AND (a > 250) AND (b = 0))
 (9 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
@@ -3100,16 +3100,18 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
          ->  Hash Join
                Hash Cond: (t2_1.b = t1_1.a)
                ->  Seq Scan on prt2_adv_p1 t2_1
+                     Filter: (b < 300)
                ->  Hash
                      ->  Seq Scan on prt1_adv_p1 t1_1
                            Filter: ((a < 300) AND (b = 0))
          ->  Hash Join
                Hash Cond: (t2_2.b = t1_2.a)
                ->  Seq Scan on prt2_adv_p2 t2_2
+                     Filter: (b < 300)
                ->  Hash
                      ->  Seq Scan on prt1_adv_p2 t1_2
                            Filter: ((a < 300) AND (b = 0))
-(15 rows)
+(17 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b;
   a  |  c   |  b  |  c   
@@ -3139,16 +3141,19 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
          ->  Hash Join
                Hash Cond: (t2_1.b = t1_1.a)
                ->  Seq Scan on prt2_adv_p1 t2_1
+                     Filter: ((b >= 100) AND (b < 300))
                ->  Hash
                      ->  Seq Scan on prt1_adv_p1 t1_1
                            Filter: ((a >= 100) AND (a < 300) AND (b = 0))
-         ->  Hash Join
-               Hash Cond: (t2_2.b = t1_2.a)
-               ->  Seq Scan on prt2_adv_p2 t2_2
-               ->  Hash
+         ->  Merge Join
+               Merge Cond: (t2_2.b = t1_2.a)
+               ->  Index Scan using prt2_adv_p2_b_idx on prt2_adv_p2 t2_2
+                     Index Cond: ((b >= 100) AND (b < 300))
+               ->  Sort
+                     Sort Key: t1_2.a
                      ->  Seq Scan on prt1_adv_p2 t1_2
                            Filter: ((a >= 100) AND (a < 300) AND (b = 0))
-(15 rows)
+(18 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a >= 100 AND t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b;
   a  |  c   |  b  |  c   
@@ -4692,27 +4697,32 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
                      Filter: ((b >= 125) AND (b < 225))
                ->  Hash
                      ->  Seq Scan on beta_neg_p1 t2_1
+                           Filter: ((b >= 125) AND (b < 225))
          ->  Hash Join
-               Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.b = t1_2.b))
-               ->  Seq Scan on beta_neg_p2 t2_2
+               Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.b = t2_2.b))
+               ->  Seq Scan on alpha_neg_p2 t1_2
+                     Filter: ((b >= 125) AND (b < 225))
                ->  Hash
-                     ->  Seq Scan on alpha_neg_p2 t1_2
+                     ->  Seq Scan on beta_neg_p2 t2_2
                            Filter: ((b >= 125) AND (b < 225))
          ->  Hash Join
-               Hash Cond: ((t2_4.a = t1_4.a) AND (t2_4.b = t1_4.b))
+               Hash Cond: ((t1_4.a = t2_4.a) AND (t1_4.b = t2_4.b))
                ->  Append
-                     ->  Seq Scan on beta_pos_p1 t2_4
-                     ->  Seq Scan on beta_pos_p2 t2_5
-                     ->  Seq Scan on beta_pos_p3 t2_6
+                     ->  Seq Scan on alpha_pos_p1 t1_4
+                           Filter: ((b >= 125) AND (b < 225))
+                     ->  Seq Scan on alpha_pos_p2 t1_5
+                           Filter: ((b >= 125) AND (b < 225))
+                     ->  Seq Scan on alpha_pos_p3 t1_6
+                           Filter: ((b >= 125) AND (b < 225))
                ->  Hash
                      ->  Append
-                           ->  Seq Scan on alpha_pos_p1 t1_4
+                           ->  Seq Scan on beta_pos_p1 t2_4
                                  Filter: ((b >= 125) AND (b < 225))
-                           ->  Seq Scan on alpha_pos_p2 t1_5
+                           ->  Seq Scan on beta_pos_p2 t2_5
                                  Filter: ((b >= 125) AND (b < 225))
-                           ->  Seq Scan on alpha_pos_p3 t1_6
+                           ->  Seq Scan on beta_pos_p3 t2_6
                                  Filter: ((b >= 125) AND (b < 225))
-(29 rows)
+(34 rows)
 
 SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b) WHERE t1.b >= 125 AND t1.b < 225 ORDER BY t1.a, t1.b;
  a  |  b  |  c   | a  |  b  |  c   
diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql
index 247b0a31055..dae83c41965 100644
--- a/src/test/regress/sql/equivclass.sql
+++ b/src/test/regress/sql/equivclass.sql
@@ -269,3 +269,15 @@ create temp view overview as
   select f1::information_schema.sql_identifier as sqli, f2 from undername;
 explain (costs off)  -- this should not require a sort
   select * from overview where sqli = 'foo' order by sqli;
+
+
+-- test equivalence filters
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec0.ff between 1 and 10;
+
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec1.ff between 1 and 10;
-- 
2.21.0

v4-0001-expand-the-duties-of-check_mergejoinable-to-check.patchapplication/octet-stream; name=v4-0001-expand-the-duties-of-check_mergejoinable-to-check.patchDownload
From 0efd829e3c411b6733aa308d9e1267e592873005 Mon Sep 17 00:00:00 2001
From: Andy Fan <yizhi.fzh@alibaba-inc.com>
Date: Mon, 7 Mar 2022 10:07:44 +0800
Subject: [PATCH v4 1/6] expand the duties of check_mergejoinable to check
 non-equal btree

operator as well to support the EC Filter function.  A new filed
named btreeineqopfamilies is added in RestictInfo and it is set
with the same round syscache search for check_mergejoinable. because
of this, check_mergejoinable is renamed to check_btreeable.

The bad part of this is it only works for opclause so far.
---
 src/backend/optimizer/plan/initsplan.c    | 33 ++++++++++-----------
 src/backend/optimizer/util/restrictinfo.c |  1 +
 src/backend/utils/cache/lsyscache.c       | 35 +++++++++++++++++++++++
 src/include/nodes/pathnodes.h             |  1 +
 src/include/utils/lsyscache.h             |  1 +
 5 files changed, 55 insertions(+), 16 deletions(-)

diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 023efbaf092..d61419f61a5 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -76,7 +76,7 @@ static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p,
 static bool check_equivalence_delay(PlannerInfo *root,
 									RestrictInfo *restrictinfo);
 static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
-static void check_mergejoinable(RestrictInfo *restrictinfo);
+static void check_btreeable(RestrictInfo *restrictinfo);
 static void check_hashjoinable(RestrictInfo *restrictinfo);
 static void check_memoizable(RestrictInfo *restrictinfo);
 
@@ -1874,8 +1874,11 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	 * We check "mergejoinability" of every clause, not only join clauses,
 	 * because we want to know about equivalences between vars of the same
 	 * relation, or between vars and consts.
+	 *
+	 * We also checked the btree-able properity at the same round of checking
+	 * mergejoinability to support ec filter function.
 	 */
-	check_mergejoinable(restrictinfo);
+	check_btreeable(restrictinfo);
 
 	/*
 	 * If it is a true equivalence clause, send it to the EquivalenceClass
@@ -2389,7 +2392,7 @@ process_implied_equality(PlannerInfo *root,
 	 * from an EquivalenceClass; but we could have reduced the original clause
 	 * to a constant.
 	 */
-	check_mergejoinable(restrictinfo);
+	check_btreeable(restrictinfo);
 
 	/*
 	 * Note we don't do initialize_mergeclause_eclasses(); the caller can
@@ -2456,8 +2459,8 @@ build_implied_join_equality(PlannerInfo *root,
 									 NULL,	/* outer_relids */
 									 nullable_relids);	/* nullable_relids */
 
-	/* Set mergejoinability/hashjoinability flags */
-	check_mergejoinable(restrictinfo);
+	/* Set btreeability/hashjoinability flags */
+	check_btreeable(restrictinfo);
 	check_hashjoinable(restrictinfo);
 	check_memoizable(restrictinfo);
 
@@ -2641,16 +2644,13 @@ match_foreign_keys_to_quals(PlannerInfo *root)
  *****************************************************************************/
 
 /*
- * check_mergejoinable
- *	  If the restrictinfo's clause is mergejoinable, set the mergejoin
- *	  info fields in the restrictinfo.
- *
- *	  Currently, we support mergejoin for binary opclauses where
- *	  the operator is a mergejoinable operator.  The arguments can be
- *	  anything --- as long as there are no volatile functions in them.
+ * check_btreeable
+ *	  If the restrictinfo's clause is btreeable, set the mergejoin
+ *	  info field and btreeineq info field in the restrictinfo. btreeable
+ *	  now is a superset of mergeable.
  */
 static void
-check_mergejoinable(RestrictInfo *restrictinfo)
+check_btreeable(RestrictInfo *restrictinfo)
 {
 	Expr	   *clause = restrictinfo->clause;
 	Oid			opno;
@@ -2666,9 +2666,10 @@ check_mergejoinable(RestrictInfo *restrictinfo)
 	opno = ((OpExpr *) clause)->opno;
 	leftarg = linitial(((OpExpr *) clause)->args);
 
-	if (op_mergejoinable(opno, exprType(leftarg)) &&
-		!contain_volatile_functions((Node *) restrictinfo))
-		restrictinfo->mergeopfamilies = get_mergejoin_opfamilies(opno);
+	if (!contain_volatile_functions((Node *) restrictinfo))
+		get_btree_opfamilies(opno,
+							 &restrictinfo->mergeopfamilies,
+							 &restrictinfo->btreeineqopfamilies);
 
 	/*
 	 * Note: op_mergejoinable is just a hint; if we fail to find the operator
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index ef8df3d098e..e09196d26f6 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -201,6 +201,7 @@ make_restrictinfo_internal(PlannerInfo *root,
 	restrictinfo->outer_selec = -1;
 
 	restrictinfo->mergeopfamilies = NIL;
+	restrictinfo->btreeineqopfamilies = NIL;
 
 	restrictinfo->left_ec = NULL;
 	restrictinfo->right_ec = NULL;
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1b7e11b93e0..91cd813ce8f 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -389,6 +389,41 @@ get_mergejoin_opfamilies(Oid opno)
 	return result;
 }
 
+/*
+ * TODO:  get_mergejoin_opfamilies shoud be replaced with this function.
+ */
+void
+get_btree_opfamilies(Oid opno,
+					 List **mergeable_opfamilies,
+					 List **unmergeable_btree_opfamilies)
+{
+	CatCList   *catlist;
+	int			i;
+
+	/*
+	 * Search pg_amop to see find out all the btree opfamilies.
+	 */
+	catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno));
+
+	for (i = 0; i < catlist->n_members; i++)
+	{
+		HeapTuple	tuple = &catlist->members[i]->tuple;
+		Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
+
+		if (aform->amopmethod == BTREE_AM_OID)
+		{
+			if (aform->amopstrategy == BTEqualStrategyNumber)
+				*mergeable_opfamilies = lappend_oid(*mergeable_opfamilies,
+													aform->amopfamily);
+			else
+				*unmergeable_btree_opfamilies = lappend_oid(*unmergeable_btree_opfamilies,
+															aform->amopfamily);
+		}
+	}
+
+	ReleaseSysCacheList(catlist);
+}
+
 /*
  * get_compatible_hash_operators
  *		Get the OID(s) of hash equality operator(s) compatible with the given
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 1f3845b3fec..3b95e4a8eae 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -2105,6 +2105,7 @@ typedef struct RestrictInfo
 
 	/* valid if clause is mergejoinable, else NIL */
 	List	   *mergeopfamilies;	/* opfamilies containing clause operator */
+	List		*btreeineqopfamilies; /* btree families except the mergeable ones */
 
 	/* cache space for mergeclause processing; NULL if not yet set */
 	EquivalenceClass *left_ec;	/* EquivalenceClass containing lefthand */
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index b8dd27d4a96..5b5fac0397c 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -79,6 +79,7 @@ extern bool get_ordering_op_properties(Oid opno,
 extern Oid	get_equality_op_for_ordering_op(Oid opno, bool *reverse);
 extern Oid	get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type);
 extern List *get_mergejoin_opfamilies(Oid opno);
+extern void get_btree_opfamilies(Oid opno, List **mergeable_opfamilies, List **unmergeable_btree_opfamilies);
 extern bool get_compatible_hash_operators(Oid opno,
 										  Oid *lhs_opno, Oid *rhs_opno);
 extern bool get_op_hash_functions(Oid opno,
-- 
2.21.0

v4-0006-Disable-ec-filter-for-foregin-table-for-now.patchapplication/octet-stream; name=v4-0006-Disable-ec-filter-for-foregin-table-for-now.patchDownload
From 9887557db64ce270f5179877852bf399cec057a3 Mon Sep 17 00:00:00 2001
From: Andy Fan <yizhi.fzh@alibaba-inc.com>
Date: Thu, 24 Mar 2022 10:11:57 +0800
Subject: [PATCH v4 6/6] Disable ec filter for foregin table for now.

we do need support EC filter against for foreign table, but when fixing
the cost model issue, we need to know the selecvitiy of the qual on foregin
table. However it is impossible for now to know that when use_remote_estimate = true.
see for postgresGetForeignRelSize. Since we currently only doing PoC for
this cost-model-fix algorithm, I just disable that for foregin table. At last,
we need improve the use_remote_estimate somehow to get the selectivity.
---
 .../postgres_fdw/expected/postgres_fdw.out    | 36 +++++++++----------
 src/backend/optimizer/path/allpaths.c         |  2 +-
 src/backend/optimizer/path/equivclass.c       | 14 ++++++++
 src/backend/optimizer/plan/initsplan.c        |  7 +++-
 4 files changed, 39 insertions(+), 20 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index ce102abe5d5..f210f911880 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -1545,12 +1545,12 @@ SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNE
 -- full outer join + inner join
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
-                                                                                                                                                                           QUERY PLAN                                                                                                                                                                           
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                 QUERY PLAN                                                                                                                                                 
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Foreign Scan
    Output: t1.c1, t2.c1, t3.c1
    Relations: ((public.ft4 t1) INNER JOIN (public.ft5 t2)) FULL JOIN (public.ft4 t3)
-   Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND (((r2.c1 + 1) >= 50)) AND (((r2.c1 + 1) <= 60)) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint
+   Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
@@ -2335,12 +2335,12 @@ SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT
 UPDATE ft5 SET c3 = null where c1 % 9 = 0;
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
-                                                                                                                                                    QUERY PLAN                                                                                                                                                     
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                QUERY PLAN                                                                                                                                 
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Foreign Scan
    Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2
    Relations: (public.ft5) INNER JOIN (public.ft4)
-   Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)) AND ((r1.c1 >= 10)) AND ((r1.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST
+   Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST
 (4 rows)
 
 SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
@@ -2362,8 +2362,8 @@ SET enable_hashjoin TO false;
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1
     AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE;
-                                                                                                                                                                                                                                                                                                                                                                                                                                                                      QUERY PLAN                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                                                                                                                                                                                                                                                                                                               QUERY PLAN                                                                                                                                                                                                                                                                                                                                                                                                                                               
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  LockRows
    Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ctid
    ->  Merge Join
@@ -2373,7 +2373,7 @@ SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = f
          ->  Foreign Scan
                Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
                Relations: (((public.ft1) INNER JOIN (public.ft2)) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
-               Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r4.c1, r4.c2, r4.c3, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4.c1, r4.c2, r4.c3) END FROM ((("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" < 100)) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 3" r3 ON (((r1.c2 = r3.c1)))) INNER JOIN "S 1"."T 4" r4 ON (((r1.c2 = r4.c1)))) ORDER BY r1.c2 ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3 FOR UPDATE OF r4
+               Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r4.c1, r4.c2, r4.c3, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4.c1, r4.c2, r4.c3) END FROM ((("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 3" r3 ON (((r1.c2 = r3.c1)))) INNER JOIN "S 1"."T 4" r4 ON (((r1.c2 = r4.c1)))) ORDER BY r1.c2 ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3 FOR UPDATE OF r4
                ->  Merge Join
                      Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
                      Merge Cond: (ft1.c2 = ft5.c1)
@@ -2391,12 +2391,12 @@ SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = f
                                              Sort Key: ft1.c1
                                              ->  Foreign Scan on public.ft1
                                                    Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
-                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND (("C 1" < 100)) FOR UPDATE
+                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE
                                        ->  Materialize
                                              Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
                                              ->  Foreign Scan on public.ft2
                                                    Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
-                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
+                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
                            ->  Sort
                                  Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
                                  Sort Key: ft4.c1
@@ -5685,25 +5685,25 @@ UPDATE ft2 AS target SET (c2) = (
 EXPLAIN (VERBOSE, COSTS OFF)
 UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END
   FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000;
-                                                                                                                                                                                                   QUERY PLAN                                                                                                                                                                                                    
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                                                       QUERY PLAN                                                                                                                                                                                        
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Update on public.ft2 d
    Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1
    ->  Foreign Scan
          Output: CASE WHEN (random() >= '0'::double precision) THEN d.c2 ELSE 0 END, d.ctid, d.*, t.*
          Relations: (public.ft2 d) INNER JOIN (public.ft2 t)
-         Remote SQL: SELECT r1.c2, r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" > 1000)) AND ((r1."C 1" > 1000)))) FOR UPDATE OF r1
-         ->  Merge Join
+         Remote SQL: SELECT r1.c2, r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r1."C 1" > 1000)))) FOR UPDATE OF r1
+         ->  Hash Join
                Output: d.c2, d.ctid, d.*, t.*
-               Merge Cond: (d.c1 = t.c1)
+               Hash Cond: (d.c1 = t.c1)
                ->  Foreign Scan on public.ft2 d
                      Output: d.c2, d.ctid, d.*, d.c1
                      Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
-               ->  Materialize
+               ->  Hash
                      Output: t.*, t.c1
                      ->  Foreign Scan on public.ft2 t
                            Output: t.*, t.c1
-                           Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST
+                           Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
 (17 rows)
 
 UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 311a5e3837a..d497639623a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -463,7 +463,7 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 	Assert(rel->rows > 0 || IS_DUMMY_REL(rel));
 
 	/* Now calculating the selectivity impacted by Corrective Qual */
-	if (!rte->inh)  /* not supported in this PoC */
+	if (!rte->inh)  /* Inherited table is not supported in this PoC */
 	{
 		ListCell *l;
 		int i = 0;
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 6cdff399d0a..7dd7f373926 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -21,6 +21,7 @@
 #include "access/stratnum.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_class.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/appendinfo.h"
@@ -1383,6 +1384,19 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 			if (!bms_get_singleton_member(cur_em->em_relids, &relid))
 				continue;
 
+			if (root->simple_rte_array[relid]->relkind == RELKIND_FOREIGN_TABLE)
+			{
+				/*
+				 * We do need support EC filter against for foreign table, but when fixing
+				 * the cost model issue, we need to know the selecvitiy of the qual on foregin
+				 * table. However it is impossible for now to know that when use_remote_estimate = true.
+				 * see for postgresGetForeignRelSize. Since we currently only doing PoC for
+				 * this cost-model-fix algorithm, I just disable that for foregin table. At last,
+				 * we need improve the use_remote_estimate somehow to get the selectivity.
+				 */
+				continue;
+			}
+
 			rel = root->simple_rel_array[relid];
 
 			if (ef->ef_source_rel == relid)
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 5a32c3987a0..9707c35f5c8 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -1991,9 +1991,14 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 
 	/* Check if the qual looks useful to harvest as an EquivalenceFilter */
 	if (filter_qual_list != NULL &&
+
+		// Must be an OpExpr for now.
 		is_opclause(restrictinfo->clause) &&
-		!contain_volatile_functions((Node *)restrictinfo) && // Cachable
+		// Checking volatile against RestrictInfo so that the result can be cached.
+		!contain_volatile_functions((Node *)restrictinfo) && 
+
 		restrictinfo->btreeineqopfamilies != NIL &&  /* ineq expression */
+		
 		/* simple & common enough filter, one side references one relation and the other one is a constant */
 		((bms_is_empty(restrictinfo->left_relids) && bms_get_singleton_member(restrictinfo->right_relids, &relid)) ||
 		 (bms_is_empty(restrictinfo->right_relids) && bms_get_singleton_member(restrictinfo->left_relids, &relid)))
-- 
2.21.0

#38Thomas Munro
thomas.munro@gmail.com
In reply to: Andy Fan (#37)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

On Thu, Mar 24, 2022 at 3:22 PM Andy Fan <zhihui.fan1213@gmail.com> wrote:

Here is the latest code. I have rebased the code with the latest master a1bc4d3590b.

FYI this is failing with an unexpected plan in the partition_join test:

https://api.cirrus-ci.com/v1/artifact/task/6090435050340352/log/src/test/regress/regression.diffs

#39Andy Fan
zhihui.fan1213@gmail.com
In reply to: Thomas Munro (#38)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

On Tue, May 17, 2022 at 6:52 AM Thomas Munro <thomas.munro@gmail.com> wrote:

On Thu, Mar 24, 2022 at 3:22 PM Andy Fan <zhihui.fan1213@gmail.com> wrote:

Here is the latest code. I have rebased the code with the latest master

a1bc4d3590b.

FYI this is failing with an unexpected plan in the partition_join test:

https://api.cirrus-ci.com/v1/artifact/task/6090435050340352/log/src/test/regress/regression.diffs

Thanks. But I will wait to see if anyone will show interest with this. Or
else
Moving alone is not a great experience.

--
Best Regards
Andy Fan

#40Andrey Lepikhov
a.lepikhov@postgrespro.ru
In reply to: Andy Fan (#39)
6 attachment(s)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

On 17/5/2022 05:00, Andy Fan wrote:

Thanks.  But I will wait to see if anyone will show interest with this.
Or else
Moving alone is not a great experience.

To move forward I've rebased your patchset onto new master, removed
annoying tailing backspaces and applied two regression test changes,
caused by second patch: first of changes are legal, second looks normal
but should be checked on optimality.
As I see, a consensus should be found for the questions:
1. Case of redundant clauses (x < 100 and x < 1000)
2. Planning time degradation for trivial OLTP queries

--
regards,
Andrey Lepikhov
Postgres Professional

Attachments:

v5-0001-Expand-the-duties-of-check_mergejoinable-to-check-no.patchtext/plain; charset=UTF-8; name=v5-0001-Expand-the-duties-of-check_mergejoinable-to-check-no.patchDownload
From 9bc8ce0021869cef0ce6029d8aaf2a363434e268 Mon Sep 17 00:00:00 2001
From: "Andrey V. Lepikhov" <a.lepikhov@postgrespro.ru>
Date: Wed, 6 Jul 2022 14:44:56 +0300
Subject: [PATCH 1/6] Expand the duties of check_mergejoinable to check
 non-equal btree operator as well to support the EC Filter function.  A new
 field named btreeineqopfamilies is added into RestictInfo and it is set with
 the same round syscache search for check_mergejoinable. because of this,
 check_mergejoinable is renamed to check_btreeable.

The bad part of this is it only works for opclause so far.
---
 src/backend/optimizer/plan/initsplan.c    | 35 +++++++++++------------
 src/backend/optimizer/util/restrictinfo.c |  1 +
 src/backend/utils/cache/lsyscache.c       | 35 +++++++++++++++++++++++
 src/include/nodes/pathnodes.h             |  1 +
 src/include/utils/lsyscache.h             |  1 +
 5 files changed, 55 insertions(+), 18 deletions(-)

diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 023efbaf09..fa2bfbfb72 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -76,7 +76,7 @@ static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p,
 static bool check_equivalence_delay(PlannerInfo *root,
 									RestrictInfo *restrictinfo);
 static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
-static void check_mergejoinable(RestrictInfo *restrictinfo);
+static void check_btreeable(RestrictInfo *restrictinfo);
 static void check_hashjoinable(RestrictInfo *restrictinfo);
 static void check_memoizable(RestrictInfo *restrictinfo);
 
@@ -1874,8 +1874,11 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	 * We check "mergejoinability" of every clause, not only join clauses,
 	 * because we want to know about equivalences between vars of the same
 	 * relation, or between vars and consts.
+	 *
+	 * We also checked the btree-able properity at the same round of checking
+	 * mergejoinability to support ec filter function.
 	 */
-	check_mergejoinable(restrictinfo);
+	check_btreeable(restrictinfo);
 
 	/*
 	 * If it is a true equivalence clause, send it to the EquivalenceClass
@@ -2389,7 +2392,7 @@ process_implied_equality(PlannerInfo *root,
 	 * from an EquivalenceClass; but we could have reduced the original clause
 	 * to a constant.
 	 */
-	check_mergejoinable(restrictinfo);
+	check_btreeable(restrictinfo);
 
 	/*
 	 * Note we don't do initialize_mergeclause_eclasses(); the caller can
@@ -2456,8 +2459,8 @@ build_implied_join_equality(PlannerInfo *root,
 									 NULL,	/* outer_relids */
 									 nullable_relids);	/* nullable_relids */
 
-	/* Set mergejoinability/hashjoinability flags */
-	check_mergejoinable(restrictinfo);
+	/* Set btreeability/hashjoinability flags */
+	check_btreeable(restrictinfo);
 	check_hashjoinable(restrictinfo);
 	check_memoizable(restrictinfo);
 
@@ -2641,20 +2644,16 @@ match_foreign_keys_to_quals(PlannerInfo *root)
  *****************************************************************************/
 
 /*
- * check_mergejoinable
- *	  If the restrictinfo's clause is mergejoinable, set the mergejoin
- *	  info fields in the restrictinfo.
- *
- *	  Currently, we support mergejoin for binary opclauses where
- *	  the operator is a mergejoinable operator.  The arguments can be
- *	  anything --- as long as there are no volatile functions in them.
+ * check_btreeable
+ *	  If the restrictinfo's clause is btreeable, set the mergejoin
+ *	  info field and btreeineq info field in the restrictinfo. btreeable
+ *	  now is a superset of mergeable.
  */
 static void
-check_mergejoinable(RestrictInfo *restrictinfo)
+check_btreeable(RestrictInfo *restrictinfo)
 {
 	Expr	   *clause = restrictinfo->clause;
 	Oid			opno;
-	Node	   *leftarg;
 
 	if (restrictinfo->pseudoconstant)
 		return;
@@ -2664,11 +2663,11 @@ check_mergejoinable(RestrictInfo *restrictinfo)
 		return;
 
 	opno = ((OpExpr *) clause)->opno;
-	leftarg = linitial(((OpExpr *) clause)->args);
 
-	if (op_mergejoinable(opno, exprType(leftarg)) &&
-		!contain_volatile_functions((Node *) restrictinfo))
-		restrictinfo->mergeopfamilies = get_mergejoin_opfamilies(opno);
+	if (!contain_volatile_functions((Node *) restrictinfo))
+		get_btree_opfamilies(opno,
+							 &restrictinfo->mergeopfamilies,
+							 &restrictinfo->btreeineqopfamilies);
 
 	/*
 	 * Note: op_mergejoinable is just a hint; if we fail to find the operator
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index ef8df3d098..e09196d26f 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -201,6 +201,7 @@ make_restrictinfo_internal(PlannerInfo *root,
 	restrictinfo->outer_selec = -1;
 
 	restrictinfo->mergeopfamilies = NIL;
+	restrictinfo->btreeineqopfamilies = NIL;
 
 	restrictinfo->left_ec = NULL;
 	restrictinfo->right_ec = NULL;
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1b7e11b93e..91cd813ce8 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -389,6 +389,41 @@ get_mergejoin_opfamilies(Oid opno)
 	return result;
 }
 
+/*
+ * TODO:  get_mergejoin_opfamilies shoud be replaced with this function.
+ */
+void
+get_btree_opfamilies(Oid opno,
+					 List **mergeable_opfamilies,
+					 List **unmergeable_btree_opfamilies)
+{
+	CatCList   *catlist;
+	int			i;
+
+	/*
+	 * Search pg_amop to see find out all the btree opfamilies.
+	 */
+	catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno));
+
+	for (i = 0; i < catlist->n_members; i++)
+	{
+		HeapTuple	tuple = &catlist->members[i]->tuple;
+		Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
+
+		if (aform->amopmethod == BTREE_AM_OID)
+		{
+			if (aform->amopstrategy == BTEqualStrategyNumber)
+				*mergeable_opfamilies = lappend_oid(*mergeable_opfamilies,
+													aform->amopfamily);
+			else
+				*unmergeable_btree_opfamilies = lappend_oid(*unmergeable_btree_opfamilies,
+															aform->amopfamily);
+		}
+	}
+
+	ReleaseSysCacheList(catlist);
+}
+
 /*
  * get_compatible_hash_operators
  *		Get the OID(s) of hash equality operator(s) compatible with the given
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index b88cfb8dc0..f407f1852d 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -2305,6 +2305,7 @@ typedef struct RestrictInfo
 	 * mergejoinable, else NIL
 	 */
 	List	   *mergeopfamilies;
+	List		*btreeineqopfamilies; /* btree families except the mergeable ones */
 
 	/*
 	 * cache space for mergeclause processing; NULL if not yet set
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index b8dd27d4a9..5b5fac0397 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -79,6 +79,7 @@ extern bool get_ordering_op_properties(Oid opno,
 extern Oid	get_equality_op_for_ordering_op(Oid opno, bool *reverse);
 extern Oid	get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type);
 extern List *get_mergejoin_opfamilies(Oid opno);
+extern void get_btree_opfamilies(Oid opno, List **mergeable_opfamilies, List **unmergeable_btree_opfamilies);
 extern bool get_compatible_hash_operators(Oid opno,
 										  Oid *lhs_opno, Oid *rhs_opno);
 extern bool get_op_hash_functions(Oid opno,
-- 
2.37.0

v5-0002-Introudce-ec_filters-in-EquivalenceClass-struct-the-.patchtext/plain; charset=UTF-8; name=v5-0002-Introudce-ec_filters-in-EquivalenceClass-struct-the-.patchDownload
From e67d5767ae85606f99d696874d53388bac6543b7 Mon Sep 17 00:00:00 2001
From: "Andrey V. Lepikhov" <a.lepikhov@postgrespro.ru>
Date: Wed, 6 Jul 2022 15:36:19 +0300
Subject: [PATCH 2/6] Introudce ec_filters in EquivalenceClass struct, the
 semantics is the quals can be applied to any EquivalenceMember in this EC.
 Later this information is used to generate new RestrictInfo and was
 distributed to related RelOptInfo very soon.

There are 3 major steps here:
a). In distribute_qual_to_rels to gather the ineq quallist.
b). After deconstruct_jointree, distribute_filter_quals_to_eclass distribute
these ineq-quallist to the related EC's ef_filters.
c). generate_base_implied_equalities_no_const scan the ec_filters and distriubte
the restrictinfo to related RelOptInfo.

Author: David Rowley at 2015-12 [1]
Andy Fan rebases this patch to current latest code.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  36 ++--
 src/backend/nodes/outfuncs.c                  |  14 ++
 src/backend/optimizer/path/equivclass.c       | 182 ++++++++++++++++++
 src/backend/optimizer/plan/initsplan.c        |  96 +++++++--
 src/backend/utils/cache/lsyscache.c           |  28 +++
 src/include/nodes/nodes.h                     |   1 +
 src/include/nodes/pathnodes.h                 |  37 ++++
 src/include/optimizer/paths.h                 |   1 +
 src/include/utils/lsyscache.h                 |   1 +
 src/test/regress/expected/equivclass.out      |  45 ++++-
 src/test/regress/expected/join.out            |  22 +--
 src/test/regress/expected/merge.out           |  16 +-
 src/test/regress/expected/partition_join.out  |  43 +++--
 src/test/regress/sql/equivclass.sql           |  12 ++
 14 files changed, 460 insertions(+), 74 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 44457f930c..2758049f5b 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -1545,12 +1545,12 @@ SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNE
 -- full outer join + inner join
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
-                                                                                                                                                 QUERY PLAN                                                                                                                                                 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                                           QUERY PLAN                                                                                                                                                                           
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Foreign Scan
    Output: t1.c1, t2.c1, t3.c1
    Relations: ((public.ft4 t1) INNER JOIN (public.ft5 t2)) FULL JOIN (public.ft4 t3)
-   Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint
+   Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND (((r2.c1 + 1) >= 50)) AND (((r2.c1 + 1) <= 60)) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
@@ -2335,12 +2335,12 @@ SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT
 UPDATE ft5 SET c3 = null where c1 % 9 = 0;
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
-                                                                                                                                QUERY PLAN                                                                                                                                 
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                    QUERY PLAN                                                                                                                                                     
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Foreign Scan
    Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2
    Relations: (public.ft5) INNER JOIN (public.ft4)
-   Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST
+   Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)) AND ((r1.c1 >= 10)) AND ((r1.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST
 (4 rows)
 
 SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
@@ -2362,8 +2362,8 @@ SET enable_hashjoin TO false;
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1
     AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE;
-                                                                                                                                                                                                                                                                                                                                                                                                                                               QUERY PLAN                                                                                                                                                                                                                                                                                                                                                                                                                                               
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                                                                                                                                                                                                                                                                                                                                      QUERY PLAN                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  LockRows
    Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ctid
    ->  Merge Join
@@ -2373,7 +2373,7 @@ SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = f
          ->  Foreign Scan
                Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
                Relations: (((public.ft1) INNER JOIN (public.ft2)) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
-               Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r4.c1, r4.c2, r4.c3, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4.c1, r4.c2, r4.c3) END FROM ((("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 3" r3 ON (((r1.c2 = r3.c1)))) INNER JOIN "S 1"."T 4" r4 ON (((r1.c2 = r4.c1)))) ORDER BY r1.c2 ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3 FOR UPDATE OF r4
+               Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r4.c1, r4.c2, r4.c3, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4.c1, r4.c2, r4.c3) END FROM ((("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" < 100)) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 3" r3 ON (((r1.c2 = r3.c1)))) INNER JOIN "S 1"."T 4" r4 ON (((r1.c2 = r4.c1)))) ORDER BY r1.c2 ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3 FOR UPDATE OF r4
                ->  Merge Join
                      Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
                      Merge Cond: (ft1.c2 = ft5.c1)
@@ -2391,12 +2391,12 @@ SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = f
                                              Sort Key: ft1.c1
                                              ->  Foreign Scan on public.ft1
                                                    Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
-                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE
+                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND (("C 1" < 100)) FOR UPDATE
                                        ->  Materialize
                                              Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
                                              ->  Foreign Scan on public.ft2
                                                    Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
-                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
+                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
                            ->  Sort
                                  Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
                                  Sort Key: ft4.c1
@@ -5705,25 +5705,25 @@ UPDATE ft2 AS target SET (c2) = (
 EXPLAIN (VERBOSE, COSTS OFF)
 UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END
   FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000;
-                                                                                                                                                                                       QUERY PLAN                                                                                                                                                                                        
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                                                                   QUERY PLAN                                                                                                                                                                                                    
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Update on public.ft2 d
    Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1
    ->  Foreign Scan
          Output: CASE WHEN (random() >= '0'::double precision) THEN d.c2 ELSE 0 END, d.ctid, d.*, t.*
          Relations: (public.ft2 d) INNER JOIN (public.ft2 t)
-         Remote SQL: SELECT r1.c2, r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r1."C 1" > 1000)))) FOR UPDATE OF r1
-         ->  Hash Join
+         Remote SQL: SELECT r1.c2, r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" > 1000)) AND ((r1."C 1" > 1000)))) FOR UPDATE OF r1
+         ->  Merge Join
                Output: d.c2, d.ctid, d.*, t.*
-               Hash Cond: (d.c1 = t.c1)
+               Merge Cond: (d.c1 = t.c1)
                ->  Foreign Scan on public.ft2 d
                      Output: d.c2, d.ctid, d.*, d.c1
                      Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
-               ->  Hash
+               ->  Materialize
                      Output: t.*, t.c1
                      ->  Foreign Scan on public.ft2 t
                            Output: t.*, t.c1
-                           Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
+                           Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST
 (17 rows)
 
 UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 05f27f044b..b19462c758 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2665,6 +2665,17 @@ _outEquivalenceMember(StringInfo str, const EquivalenceMember *node)
 	WRITE_OID_FIELD(em_datatype);
 }
 
+static void
+_outEquivalenceFilter(StringInfo str, const EquivalenceFilter *node)
+{
+	WRITE_NODE_TYPE("EQUIVALENCEFILTER");
+
+	WRITE_NODE_FIELD(ef_const);
+	WRITE_OID_FIELD(ef_opno);
+	WRITE_BOOL_FIELD(ef_const_is_left);
+	WRITE_UINT_FIELD(ef_source_rel);
+}
+
 static void
 _outPathKey(StringInfo str, const PathKey *node)
 {
@@ -4496,6 +4507,9 @@ outNode(StringInfo str, const void *obj)
 			case T_EquivalenceMember:
 				_outEquivalenceMember(str, obj);
 				break;
+			case T_EquivalenceFilter:
+				_outEquivalenceFilter(str, obj);
+				break;
 			case T_PathKey:
 				_outPathKey(str, obj);
 				break;
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 60c0e3f108..89c7f0dc39 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -19,6 +19,7 @@
 #include <limits.h>
 
 #include "access/stratnum.h"
+#include "catalog/pg_am.h"
 #include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -1232,6 +1233,37 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 	}
 }
 
+/*
+ * finds the opfamily and strategy number for the specified 'opno' and 'method'
+ * access method. Returns True if one is found and sets 'family' and
+ * 'amstrategy', or returns False if none are found.
+ */
+static bool
+find_am_family_and_stategy(Oid opno, Oid method, Oid *family, int *amstrategy)
+{
+	List *opfamilies;
+	ListCell *l;
+	int strategy;
+
+	opfamilies = get_opfamilies(opno, method);
+
+	foreach(l, opfamilies)
+	{
+		Oid opfamily = lfirst_oid(l);
+
+		strategy = get_op_opfamily_strategy(opno, opfamily);
+
+		if (strategy)
+		{
+			*amstrategy = strategy;
+			*family = opfamily;
+			return true;
+		}
+	}
+
+	return false;
+}
+
 /*
  * generate_base_implied_equalities when EC contains no pseudoconstants
  */
@@ -1241,6 +1273,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 {
 	EquivalenceMember **prev_ems;
 	ListCell   *lc;
+	ListCell   *lc2;
 
 	/*
 	 * We scan the EC members once and track the last-seen member for each
@@ -1302,6 +1335,57 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 				rinfo->right_em = cur_em;
 			}
 		}
+
+		/*
+		 * Also push any EquivalenceFilter clauses down into all relations
+		 * other than the one which the filter actually originated from.
+		 */
+		foreach(lc2, ec->ec_filters)
+		{
+			EquivalenceFilter *ef = (EquivalenceFilter *) lfirst(lc2);
+			Expr *leftexpr;
+			Expr *rightexpr;
+			int strategy;
+			Oid opno;
+			Oid family;
+
+			if (ef->ef_source_rel == relid)
+				continue;
+
+			if (!find_am_family_and_stategy(ef->ef_opno, BTREE_AM_OID,
+				&family, &strategy))
+				continue;
+
+			if (ef->ef_const_is_left)
+			{
+				leftexpr = (Expr *) ef->ef_const;
+				rightexpr = cur_em->em_expr;
+			}
+			else
+			{
+				leftexpr = cur_em->em_expr;
+				rightexpr = (Expr *) ef->ef_const;
+			}
+
+			opno = get_opfamily_member(family,
+										exprType((Node *) leftexpr),
+										exprType((Node *) rightexpr),
+										strategy);
+
+			if (opno == InvalidOid)
+				continue;
+
+			process_implied_equality(root, opno,
+										ec->ec_collation,
+										leftexpr,
+										rightexpr,
+										bms_copy(ec->ec_relids),
+										bms_copy(cur_em->em_nullable_relids),
+										ec->ec_min_security,
+										ec->ec_below_outer_join,
+										false);
+		}
+
 		prev_ems[relid] = cur_em;
 	}
 
@@ -1883,6 +1967,104 @@ create_join_clause(PlannerInfo *root,
 	return rinfo;
 }
 
+/*
+ * distribute_filter_quals_to_eclass
+ *		For each OpExpr in quallist look for an eclass which has an Expr
+ *		matching the Expr in the OpExpr. If a match is found we add a new
+ *		EquivalenceFilter to the eclass containing the filter details.
+ */
+void
+distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
+{
+	ListCell *l;
+
+	/* fast path for when no eclasses have been generated */
+	if (root->eq_classes == NIL)
+		return;
+
+	/*
+	 * For each qual in quallist try and find an eclass which contains the
+	 * non-Const part of the OpExpr. We'll tag any matches that we find onto
+	 * the correct eclass.
+	 */
+	foreach(l, quallist)
+	{
+		OpExpr	   *opexpr = (OpExpr *) lfirst(l);
+		Expr	   *leftexpr = (Expr *) linitial(opexpr->args);
+		Expr	   *rightexpr = (Expr *) lsecond(opexpr->args);
+		Const	   *constexpr;
+		Expr	   *varexpr;
+		Relids		exprrels;
+		int			relid;
+		bool		const_isleft;
+		ListCell *l2;
+
+		/*
+		 * Determine if the the OpExpr is in the form "expr op const" or
+		 * "const op expr".
+		 */
+		if (IsA(leftexpr, Const))
+		{
+			constexpr = (Const *) leftexpr;
+			varexpr = rightexpr;
+			const_isleft = true;
+		}
+		else
+		{
+			constexpr = (Const *) rightexpr;
+			varexpr = leftexpr;
+			const_isleft = false;
+		}
+
+		exprrels = pull_varnos(root, (Node *) varexpr);
+
+		/* should be filtered out, but we need to determine relid anyway */
+		if (!bms_get_singleton_member(exprrels, &relid))
+			continue;
+
+		/* search for a matching eclass member in all eclasses */
+		foreach(l2, root->eq_classes)
+		{
+			EquivalenceClass *ec = (EquivalenceClass *) lfirst(l2);
+			ListCell *l3;
+
+			if (ec->ec_broken || ec->ec_has_volatile)
+				continue;
+
+			/*
+			 * if the eclass has a const then that const will serve as the
+			 * filter, we needn't add any others.
+			 */
+			if (ec->ec_has_const)
+				continue;
+
+			/* skip this eclass no members exist which belong to this relid */
+			if (!bms_is_member(relid, ec->ec_relids))
+				continue;
+
+			foreach(l3, ec->ec_members)
+			{
+				EquivalenceMember *em = (EquivalenceMember *) lfirst(l3);
+
+				if (!bms_is_member(relid, em->em_relids))
+					continue;
+
+				if (equal(em->em_expr, varexpr))
+				{
+					EquivalenceFilter *efilter;
+					efilter = makeNode(EquivalenceFilter);
+					efilter->ef_const = (Const *) copyObject(constexpr);
+					efilter->ef_const_is_left = const_isleft;
+					efilter->ef_opno = opexpr->opno;
+					efilter->ef_source_rel = relid;
+
+					ec->ec_filters = lappend(ec->ec_filters, efilter);
+					break;		/* Onto the next eclass */
+				}
+			}
+		}
+	}
+}
 
 /*
  * reconsider_outer_join_clauses
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index fa2bfbfb72..f86276b667 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -53,7 +53,7 @@ static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel,
 static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
 								 bool below_outer_join,
 								 Relids *qualscope, Relids *inner_join_rels,
-								 List **postponed_qual_list);
+								 List **postponed_qual_list, List **filter_qual_list);
 static void process_security_barrier_quals(PlannerInfo *root,
 										   int rti, Relids qualscope,
 										   bool below_outer_join);
@@ -70,7 +70,8 @@ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 									Relids qualscope,
 									Relids ojscope,
 									Relids outerjoin_nonnullable,
-									List **postponed_qual_list);
+									List **postponed_qual_list,
+									List **filter_qual_list);
 static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p,
 								  Relids *nullable_relids_p, bool is_pushed_down);
 static bool check_equivalence_delay(PlannerInfo *root,
@@ -650,6 +651,43 @@ create_lateral_join_info(PlannerInfo *root)
 	}
 }
 
+/*
+ * is_simple_filter_qual
+ *             Analyzes an OpExpr to determine if it may be useful as an
+ *             EquivalenceFilter. Returns true if the OpExpr may be of some use, or
+ *             false if it should not be used.
+ */
+static bool
+is_simple_filter_qual(PlannerInfo *root, OpExpr *expr)
+{
+	Expr *leftexpr;
+	Expr *rightexpr;
+
+	if (!IsA(expr, OpExpr))
+			return false;
+
+	if (list_length(expr->args) != 2)
+			return false;
+
+	leftexpr = (Expr *) linitial(expr->args);
+	rightexpr = (Expr *) lsecond(expr->args);
+
+	/* XXX should we restrict these to simple Var op Const expressions? */
+	if (IsA(leftexpr, Const))
+	{
+		if (bms_membership(pull_varnos(root, (Node *) rightexpr)) == BMS_SINGLETON &&
+			!contain_volatile_functions((Node *) rightexpr))
+			return true;
+	}
+	else if (IsA(rightexpr, Const))
+	{
+		if (bms_membership(pull_varnos(root, (Node *) leftexpr)) == BMS_SINGLETON &&
+			!contain_volatile_functions((Node *) leftexpr))
+			return true;
+	}
+
+	return false;
+}
 
 /*****************************************************************************
  *
@@ -690,6 +728,7 @@ deconstruct_jointree(PlannerInfo *root)
 	Relids		qualscope;
 	Relids		inner_join_rels;
 	List	   *postponed_qual_list = NIL;
+	List	   *filter_qual_list = NIL;
 
 	/* Start recursion at top of jointree */
 	Assert(root->parse->jointree != NULL &&
@@ -700,11 +739,14 @@ deconstruct_jointree(PlannerInfo *root)
 
 	result = deconstruct_recurse(root, (Node *) root->parse->jointree, false,
 								 &qualscope, &inner_join_rels,
-								 &postponed_qual_list);
+								 &postponed_qual_list, &filter_qual_list);
 
 	/* Shouldn't be any leftover quals */
 	Assert(postponed_qual_list == NIL);
 
+	/* try and match each filter_qual_list item up with an eclass. */
+	distribute_filter_quals_to_eclass(root, filter_qual_list);
+
 	return result;
 }
 
@@ -725,6 +767,8 @@ deconstruct_jointree(PlannerInfo *root)
  *		or free this, either)
  *	*postponed_qual_list is a list of PostponedQual structs, which we can
  *		add quals to if they turn out to belong to a higher join level
+ *	*filter_qual_list is appended to with a list of quals which may be useful
+ *		include as EquivalenceFilters.
  *	Return value is the appropriate joinlist for this jointree node
  *
  * In addition, entries will be added to root->join_info_list for outer joins.
@@ -732,7 +776,7 @@ deconstruct_jointree(PlannerInfo *root)
 static List *
 deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 					Relids *qualscope, Relids *inner_join_rels,
-					List **postponed_qual_list)
+					List **postponed_qual_list, List **filter_qual_list)
 {
 	List	   *joinlist;
 
@@ -785,7 +829,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 											   below_outer_join,
 											   &sub_qualscope,
 											   inner_join_rels,
-											   &child_postponed_quals);
+											   &child_postponed_quals,
+											   filter_qual_list);
 			*qualscope = bms_add_members(*qualscope, sub_qualscope);
 			sub_members = list_length(sub_joinlist);
 			remaining--;
@@ -819,7 +864,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 										below_outer_join, JOIN_INNER,
 										root->qual_security_level,
 										*qualscope, NULL, NULL,
-										NULL);
+										NULL,
+										filter_qual_list);
 			else
 				*postponed_qual_list = lappend(*postponed_qual_list, pq);
 		}
@@ -835,7 +881,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 									below_outer_join, JOIN_INNER,
 									root->qual_security_level,
 									*qualscope, NULL, NULL,
-									postponed_qual_list);
+									postponed_qual_list,
+									filter_qual_list);
 		}
 	}
 	else if (IsA(jtnode, JoinExpr))
@@ -873,11 +920,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													below_outer_join,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = *qualscope;
 				/* Inner join adds no restrictions for quals */
@@ -890,11 +939,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													true,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = bms_union(left_inners, right_inners);
 				nonnullable_rels = leftids;
@@ -904,11 +955,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   below_outer_join,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													below_outer_join,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = bms_union(left_inners, right_inners);
 				/* Semi join adds no restrictions for quals */
@@ -925,11 +978,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 				leftjoinlist = deconstruct_recurse(root, j->larg,
 												   true,
 												   &leftids, &left_inners,
-												   &child_postponed_quals);
+												   &child_postponed_quals,
+												   filter_qual_list);
 				rightjoinlist = deconstruct_recurse(root, j->rarg,
 													true,
 													&rightids, &right_inners,
-													&child_postponed_quals);
+													&child_postponed_quals,
+													filter_qual_list);
 				*qualscope = bms_union(leftids, rightids);
 				*inner_join_rels = bms_union(left_inners, right_inners);
 				/* each side is both outer and inner */
@@ -1013,7 +1068,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 									root->qual_security_level,
 									*qualscope,
 									ojscope, nonnullable_rels,
-									postponed_qual_list);
+									postponed_qual_list,
+									filter_qual_list);
 		}
 
 		/* Now we can add the SpecialJoinInfo to join_info_list */
@@ -1117,6 +1173,7 @@ process_security_barrier_quals(PlannerInfo *root,
 									qualscope,
 									qualscope,
 									NULL,
+									NULL,
 									NULL);
 		}
 		security_level++;
@@ -1610,7 +1667,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 						Relids qualscope,
 						Relids ojscope,
 						Relids outerjoin_nonnullable,
-						List **postponed_qual_list)
+						List **postponed_qual_list,
+						List **filter_qual_list)
 {
 	Relids		relids;
 	bool		is_pushed_down;
@@ -1967,6 +2025,10 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 
 	/* No EC special case applies, so push it into the clause lists */
 	distribute_restrictinfo_to_rels(root, restrictinfo);
+
+	/* Check if the qual looks useful to harvest as an EquivalenceFilter */
+	if (filter_qual_list != NULL && is_simple_filter_qual(root, (OpExpr *) clause))
+		*filter_qual_list = lappend(*filter_qual_list, clause);
 }
 
 /*
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 91cd813ce8..b0243925e4 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -341,6 +341,34 @@ get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type)
 	return result;
 }
 
+/*
+ * get_opfamilies
+ *		Returns a list of Oids of each opfamily which 'opno' belonging to
+ *		'method' access method.
+ */
+List *
+get_opfamilies(Oid opno, Oid method)
+{
+	List	   *result = NIL;
+	CatCList   *catlist;
+	int			i;
+
+	catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno));
+
+	for (i = 0; i < catlist->n_members; i++)
+	{
+		HeapTuple	tuple = &catlist->members[i]->tuple;
+		Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple);
+
+		if (aform->amopmethod == method)
+			result = lappend_oid(result, aform->amopfamily);
+	}
+
+	ReleaseSysCacheList(catlist);
+
+	return result;
+}
+
 /*
  * get_mergejoin_opfamilies
  *		Given a putatively mergejoinable operator, return a list of the OIDs
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7ce1fc4deb..ba879ab3e9 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -277,6 +277,7 @@ typedef enum NodeTag
 	T_LimitPath,
 	/* these aren't subclasses of Path: */
 	T_EquivalenceClass,
+	T_EquivalenceFilter,
 	T_EquivalenceMember,
 	T_PathKey,
 	T_PathKeyInfo,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index f407f1852d..f80b47ae2c 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1132,6 +1132,7 @@ typedef struct EquivalenceClass
 	List	   *ec_members;		/* list of EquivalenceMembers */
 	List	   *ec_sources;		/* list of generating RestrictInfos */
 	List	   *ec_derives;		/* list of derived RestrictInfos */
+	List	   *ec_filters;
 	Relids		ec_relids;		/* all relids appearing in ec_members, except
 								 * for child members (see below) */
 	bool		ec_has_const;	/* any pseudoconstants in ec_members? */
@@ -1144,6 +1145,42 @@ typedef struct EquivalenceClass
 	struct EquivalenceClass *ec_merged; /* set if merged into another EC */
 } EquivalenceClass;
 
+/*
+ * EquivalenceFilter - List of filters on Consts which belong to the
+ * EquivalenceClass.
+ *
+ * When building the equivalence classes we also collected a list of quals in
+ * the form of; "Expr op Const" and "Const op Expr". These are collected in the
+ * hope that we'll later generate an equivalence class which contains the
+ * "Expr" part. For example, if we parse a query such as;
+ *
+ *		SELECT * FROM t1 INNER JOIN t2 ON t1.id = t2.id WHERE t1.id < 10;
+ *
+ * then since we'll end up with an equivalence class containing {t1.id,t2.id},
+ * we'll tag the "< 10" filter onto the eclass. We are able to do this because
+ * the eclass proves equality between each class member, therefore all members
+ * must be below 10.
+ *
+ * EquivalenceFilters store the details required to allow us to push these
+ * filter clauses down into other relations which share an equivalence class
+ * containing a member which matches the expression of this EquivalenceFilter.
+ *
+ * ef_const is the Const value which this filter should filter against.
+ * ef_opno is the operator to filter on.
+ * ef_const_is_left marks if the OpExpr was in the form "Const op Expr" or
+ * "Expr op Const".
+ * ef_source_rel is the relation id of where this qual originated from.
+ */
+typedef struct EquivalenceFilter
+{
+	NodeTag		type;
+
+	Const	   *ef_const;		/* the constant expression to filter on */
+	Oid			ef_opno;		/* Operator Oid of filter operator */
+	bool		ef_const_is_left; /* Is the Const on the left of the OpExrp? */
+	Index		ef_source_rel;	/* relid of originating relation. */
+} EquivalenceFilter;
+
 /*
  * If an EC contains a const and isn't below-outer-join, any PathKey depending
  * on it must be redundant, since there's only one possible value of the key.
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index b6e137cf83..79553778cd 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -127,6 +127,7 @@ extern bool process_equivalence(PlannerInfo *root,
 extern Expr *canonicalize_ec_expression(Expr *expr,
 										Oid req_type, Oid req_collation);
 extern void reconsider_outer_join_clauses(PlannerInfo *root);
+extern void distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist);
 extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root,
 												  Expr *expr,
 												  Relids nullable_relids,
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 5b5fac0397..e0ed28f330 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -78,6 +78,7 @@ extern bool get_ordering_op_properties(Oid opno,
 									   Oid *opfamily, Oid *opcintype, int16 *strategy);
 extern Oid	get_equality_op_for_ordering_op(Oid opno, bool *reverse);
 extern Oid	get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type);
+extern List *get_opfamilies(Oid opno, Oid method);
 extern List *get_mergejoin_opfamilies(Oid opno);
 extern void get_btree_opfamilies(Oid opno, List **mergeable_opfamilies, List **unmergeable_btree_opfamilies);
 extern bool get_compatible_hash_operators(Oid opno,
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index 126f7047fe..92fcec1158 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -407,14 +407,14 @@ set session authorization regress_user_ectest;
 explain (costs off)
   select * from ec0 a, ec1 b
   where a.ff = b.ff and a.ff = 43::bigint::int8alias1;
-                 QUERY PLAN                  
----------------------------------------------
+                              QUERY PLAN                              
+----------------------------------------------------------------------
  Nested Loop
    ->  Index Scan using ec0_pkey on ec0 a
          Index Cond: (ff = '43'::int8alias1)
    ->  Index Scan using ec1_pkey on ec1 b
          Index Cond: (ff = a.ff)
-         Filter: (f1 < '5'::int8alias1)
+         Filter: ((f1 < '5'::int8alias1) AND (ff = '43'::int8alias1))
 (6 rows)
 
 reset session authorization;
@@ -451,3 +451,42 @@ explain (costs off)  -- this should not require a sort
    Filter: (f1 = 'foo'::name)
 (2 rows)
 
+-- test equivalence filters
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec0.ff between 1 and 10;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Nested Loop
+   Join Filter: (ec0.ff = ec1.ff)
+   ->  Bitmap Heap Scan on ec0
+         Recheck Cond: ((ff >= 1) AND (ff <= 10))
+         ->  Bitmap Index Scan on ec0_pkey
+               Index Cond: ((ff >= 1) AND (ff <= 10))
+   ->  Materialize
+         ->  Bitmap Heap Scan on ec1
+               Recheck Cond: ((ff >= 1) AND (ff <= 10))
+               ->  Bitmap Index Scan on ec1_pkey
+                     Index Cond: ((ff >= 1) AND (ff <= 10))
+(11 rows)
+
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec1.ff between 1 and 10;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Nested Loop
+   Join Filter: (ec0.ff = ec1.ff)
+   ->  Bitmap Heap Scan on ec0
+         Recheck Cond: ((ff >= 1) AND (ff <= 10))
+         ->  Bitmap Index Scan on ec0_pkey
+               Index Cond: ((ff >= 1) AND (ff <= 10))
+   ->  Materialize
+         ->  Bitmap Heap Scan on ec1
+               Recheck Cond: ((ff >= 1) AND (ff <= 10))
+               ->  Bitmap Index Scan on ec1_pkey
+                     Index Cond: ((ff >= 1) AND (ff <= 10))
+(11 rows)
+
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 2538bd6a79..f173074621 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -3337,7 +3337,7 @@ where t1.unique2 < 42 and t1.stringu1 > t2.stringu2;
    Join Filter: (t1.stringu1 > t2.stringu2)
    ->  Nested Loop
          ->  Seq Scan on int4_tbl i1
-               Filter: (f1 = 0)
+               Filter: ((f1 = 0) AND (11 < 42))
          ->  Index Scan using tenk1_unique2 on tenk1 t1
                Index Cond: ((unique2 = (11)) AND (unique2 < 42))
    ->  Index Scan using tenk1_unique1 on tenk1 t2
@@ -6544,23 +6544,22 @@ where exists (select 1 from tenk1 t3
 ---------------------------------------------------------------------------------
  Nested Loop
    Output: t1.unique1, t2.hundred
-   ->  Hash Join
+   ->  Nested Loop
          Output: t1.unique1, t3.tenthous
-         Hash Cond: (t3.thousand = t1.unique1)
+         Join Filter: (t1.unique1 = t3.thousand)
+         ->  Index Only Scan using onek_unique1 on public.onek t1
+               Output: t1.unique1
+               Index Cond: (t1.unique1 < 1)
          ->  HashAggregate
                Output: t3.thousand, t3.tenthous
                Group Key: t3.thousand, t3.tenthous
                ->  Index Only Scan using tenk1_thous_tenthous on public.tenk1 t3
                      Output: t3.thousand, t3.tenthous
-         ->  Hash
-               Output: t1.unique1
-               ->  Index Only Scan using onek_unique1 on public.onek t1
-                     Output: t1.unique1
-                     Index Cond: (t1.unique1 < 1)
+                     Index Cond: (t3.thousand < 1)
    ->  Index Only Scan using tenk1_hundred on public.tenk1 t2
          Output: t2.hundred
          Index Cond: (t2.hundred = t3.tenthous)
-(18 rows)
+(17 rows)
 
 -- ... unless it actually is unique
 create table j3 as select unique1, tenthous from onek;
@@ -6578,15 +6577,16 @@ where exists (select 1 from j3
    Output: t1.unique1, t2.hundred
    ->  Nested Loop
          Output: t1.unique1, j3.tenthous
+         Join Filter: (t1.unique1 = j3.unique1)
          ->  Index Only Scan using onek_unique1 on public.onek t1
                Output: t1.unique1
                Index Cond: (t1.unique1 < 1)
          ->  Index Only Scan using j3_unique1_tenthous_idx on public.j3
                Output: j3.unique1, j3.tenthous
-               Index Cond: (j3.unique1 = t1.unique1)
+               Index Cond: (j3.unique1 < 1)
    ->  Index Only Scan using tenk1_hundred on public.tenk1 t2
          Output: t2.hundred
          Index Cond: (t2.hundred = j3.tenthous)
-(13 rows)
+(14 rows)
 
 drop table j3;
diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out
index af670e28e7..ab3a711d61 100644
--- a/src/test/regress/expected/merge.out
+++ b/src/test/regress/expected/merge.out
@@ -1437,17 +1437,17 @@ SELECT explain_merge('
 MERGE INTO ex_mtarget t USING ex_msource s ON t.a = s.a AND t.a < -1000
 WHEN MATCHED AND t.a < 10 THEN
 	DO NOTHING');
-                           explain_merge                            
---------------------------------------------------------------------
+                        explain_merge                         
+--------------------------------------------------------------
  Merge on ex_mtarget t (actual rows=0 loops=1)
    ->  Hash Join (actual rows=0 loops=1)
-         Hash Cond: (s.a = t.a)
-         ->  Seq Scan on ex_msource s (actual rows=1 loops=1)
-         ->  Hash (actual rows=0 loops=1)
-               Buckets: xxx  Batches: xxx  Memory Usage: xxx
-               ->  Seq Scan on ex_mtarget t (actual rows=0 loops=1)
+         Hash Cond: (t.a = s.a)
+         ->  Seq Scan on ex_mtarget t (actual rows=0 loops=1)
+               Filter: (a < '-1000'::integer)
+               Rows Removed by Filter: 54
+         ->  Hash (never executed)
+               ->  Seq Scan on ex_msource s (never executed)
                      Filter: (a < '-1000'::integer)
-                     Rows Removed by Filter: 54
 (9 rows)
 
 DROP TABLE ex_msource, ex_mtarget;
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index 03926a8413..3be9a2bed5 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -186,17 +186,17 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0)
 -- Join with pruned partitions from joining relations
 EXPLAIN (COSTS OFF)
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
-                     QUERY PLAN                      
------------------------------------------------------
+                            QUERY PLAN                             
+-------------------------------------------------------------------
  Sort
    Sort Key: t1.a
    ->  Hash Join
          Hash Cond: (t2.b = t1.a)
          ->  Seq Scan on prt2_p2 t2
-               Filter: (b > 250)
+               Filter: ((b > 250) AND (b < 450))
          ->  Hash
                ->  Seq Scan on prt1_p2 t1
-                     Filter: ((a < 450) AND (b = 0))
+                     Filter: ((a < 450) AND (a > 250) AND (b = 0))
 (9 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
@@ -3089,16 +3089,18 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
          ->  Hash Join
                Hash Cond: (t2_1.b = t1_1.a)
                ->  Seq Scan on prt2_adv_p1 t2_1
+                     Filter: (b < 300)
                ->  Hash
                      ->  Seq Scan on prt1_adv_p1 t1_1
                            Filter: ((a < 300) AND (b = 0))
          ->  Hash Join
                Hash Cond: (t2_2.b = t1_2.a)
                ->  Seq Scan on prt2_adv_p2 t2_2
+                     Filter: (b < 300)
                ->  Hash
                      ->  Seq Scan on prt1_adv_p2 t1_2
                            Filter: ((a < 300) AND (b = 0))
-(15 rows)
+(17 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b;
   a  |  c   |  b  |  c   
@@ -3128,16 +3130,18 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
          ->  Hash Join
                Hash Cond: (t2_1.b = t1_1.a)
                ->  Seq Scan on prt2_adv_p1 t2_1
+                     Filter: ((b >= 100) AND (b < 300))
                ->  Hash
                      ->  Seq Scan on prt1_adv_p1 t1_1
                            Filter: ((a >= 100) AND (a < 300) AND (b = 0))
          ->  Hash Join
                Hash Cond: (t2_2.b = t1_2.a)
                ->  Seq Scan on prt2_adv_p2 t2_2
+                     Filter: ((b >= 100) AND (b < 300))
                ->  Hash
                      ->  Seq Scan on prt1_adv_p2 t1_2
                            Filter: ((a >= 100) AND (a < 300) AND (b = 0))
-(15 rows)
+(17 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.a >= 100 AND t1.a < 300 AND t1.b = 0 ORDER BY t1.a, t2.b;
   a  |  c   |  b  |  c   
@@ -4681,27 +4685,32 @@ SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2
                      Filter: ((b >= 125) AND (b < 225))
                ->  Hash
                      ->  Seq Scan on beta_neg_p1 t2_1
+                           Filter: ((b >= 125) AND (b < 225))
          ->  Hash Join
-               Hash Cond: ((t2_2.a = t1_2.a) AND (t2_2.b = t1_2.b))
-               ->  Seq Scan on beta_neg_p2 t2_2
+               Hash Cond: ((t1_2.a = t2_2.a) AND (t1_2.b = t2_2.b))
+               ->  Seq Scan on alpha_neg_p2 t1_2
+                     Filter: ((b >= 125) AND (b < 225))
                ->  Hash
-                     ->  Seq Scan on alpha_neg_p2 t1_2
+                     ->  Seq Scan on beta_neg_p2 t2_2
                            Filter: ((b >= 125) AND (b < 225))
          ->  Hash Join
-               Hash Cond: ((t2_4.a = t1_4.a) AND (t2_4.b = t1_4.b))
+               Hash Cond: ((t1_4.a = t2_4.a) AND (t1_4.b = t2_4.b))
                ->  Append
-                     ->  Seq Scan on beta_pos_p1 t2_4
-                     ->  Seq Scan on beta_pos_p2 t2_5
-                     ->  Seq Scan on beta_pos_p3 t2_6
+                     ->  Seq Scan on alpha_pos_p1 t1_4
+                           Filter: ((b >= 125) AND (b < 225))
+                     ->  Seq Scan on alpha_pos_p2 t1_5
+                           Filter: ((b >= 125) AND (b < 225))
+                     ->  Seq Scan on alpha_pos_p3 t1_6
+                           Filter: ((b >= 125) AND (b < 225))
                ->  Hash
                      ->  Append
-                           ->  Seq Scan on alpha_pos_p1 t1_4
+                           ->  Seq Scan on beta_pos_p1 t2_4
                                  Filter: ((b >= 125) AND (b < 225))
-                           ->  Seq Scan on alpha_pos_p2 t1_5
+                           ->  Seq Scan on beta_pos_p2 t2_5
                                  Filter: ((b >= 125) AND (b < 225))
-                           ->  Seq Scan on alpha_pos_p3 t1_6
+                           ->  Seq Scan on beta_pos_p3 t2_6
                                  Filter: ((b >= 125) AND (b < 225))
-(29 rows)
+(34 rows)
 
 SELECT t1.*, t2.* FROM alpha t1 INNER JOIN beta t2 ON (t1.a = t2.a AND t1.b = t2.b) WHERE t1.b >= 125 AND t1.b < 225 ORDER BY t1.a, t1.b;
  a  |  b  |  c   | a  |  b  |  c   
diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql
index 247b0a3105..dae83c4196 100644
--- a/src/test/regress/sql/equivclass.sql
+++ b/src/test/regress/sql/equivclass.sql
@@ -269,3 +269,15 @@ create temp view overview as
   select f1::information_schema.sql_identifier as sqli, f2 from undername;
 explain (costs off)  -- this should not require a sort
   select * from overview where sqli = 'foo' order by sqli;
+
+
+-- test equivalence filters
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec0.ff between 1 and 10;
+
+explain (costs off)
+  select * from ec0
+  inner join ec1 on ec0.ff = ec1.ff
+  where ec1.ff between 1 and 10;
-- 
2.37.0

v5-0003-Reduce-some-planning-cost-for-deriving-qual-for-EC-f.patchtext/plain; charset=UTF-8; name=v5-0003-Reduce-some-planning-cost-for-deriving-qual-for-EC-f.patchDownload
From b19ec90394d5d933028355725754b4bb7a8c9094 Mon Sep 17 00:00:00 2001
From: "Andrey V. Lepikhov" <a.lepikhov@postgrespro.ru>
Date: Wed, 6 Jul 2022 15:47:30 +0300
Subject: [PATCH 3/6] Reduce some planning cost for deriving qual for EC filter
 feature.

Mainly changes includes:
1. Check if the qual is simple enough by checking rinfo->right_relids and
info->right_relids, save the pull_varnos of rinfo->clause calls.
2. check contain_volatile_functions against RestrictInfo, so that
the result can be shared with following calls.
3. By employing the RestictInfo->btreeineqfamility which is calculating.
with same round of calculating  RestrictInfo->mergeopfamilies. In this
way we save the some calls for syscache.
4. Calculating the opfamility and amstrategy at
distribute_filter_quals_to_eclass and cache the results in EquivalenceFilter.
if no suitable opfamility and amstrategy are found, bypass the qual immediately
and at last using the cached value generate_base_implied_equalities_no_const.

After this change, there is an testcase changed unexpectedly in equivclass.out
(compared with David's expectation file.)

create user regress_user_ectest;
grant select on ec0 to regress_user_ectest;
grant select on ec1 to regress_user_ectest;

set session authorization regress_user_ectest;

-- with RLS active, the non-leakproof a.ff = 43 clause is not treated
-- as a suitable source for an EquivalenceClass; currently, this is true
-- even though the RLS clause has nothing to do directly with the EC
explain (costs off)
regression->   select * from ec0 a, ec1 b
regression->   where a.ff = b.ff and a.ff = 43::bigint::int8alias1;

The b.ff = 43 is disappeared from ec1 b. But since it even didn't shown
before the EC filter, so I'm not sure my changes here make something wrong,
maybe fix a issue by accidental?
---
 src/backend/nodes/outfuncs.c             |  2 +
 src/backend/optimizer/path/equivclass.c  | 57 +++++++++++++-----------
 src/backend/optimizer/plan/initsplan.c   | 50 +++++----------------
 src/include/nodes/pathnodes.h            |  2 +
 src/test/regress/expected/equivclass.out |  6 +--
 5 files changed, 47 insertions(+), 70 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b19462c758..f31f1de983 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2674,6 +2674,8 @@ _outEquivalenceFilter(StringInfo str, const EquivalenceFilter *node)
 	WRITE_OID_FIELD(ef_opno);
 	WRITE_BOOL_FIELD(ef_const_is_left);
 	WRITE_UINT_FIELD(ef_source_rel);
+	WRITE_OID_FIELD(opfamily);
+	WRITE_INT_FIELD(amstrategy);
 }
 
 static void
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 89c7f0dc39..38bbc325b0 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -1234,19 +1234,17 @@ generate_base_implied_equalities_const(PlannerInfo *root,
 }
 
 /*
- * finds the opfamily and strategy number for the specified 'opno' and 'method'
- * access method. Returns True if one is found and sets 'family' and
- * 'amstrategy', or returns False if none are found.
+ * finds the operator id for the specified 'opno' and 'method' and 'opfamilies'
+ * Returns True if one is found and sets 'opfamily_p' and 'amstrategy_p' or returns
+ * False if none are found.
  */
 static bool
-find_am_family_and_stategy(Oid opno, Oid method, Oid *family, int *amstrategy)
+find_am_family_and_stategy(Oid opno, Oid method, List *opfamilies,
+						   Oid *opfamily_p, int *amstrategy_p)
 {
-	List *opfamilies;
 	ListCell *l;
 	int strategy;
 
-	opfamilies = get_opfamilies(opno, method);
-
 	foreach(l, opfamilies)
 	{
 		Oid opfamily = lfirst_oid(l);
@@ -1255,8 +1253,8 @@ find_am_family_and_stategy(Oid opno, Oid method, Oid *family, int *amstrategy)
 
 		if (strategy)
 		{
-			*amstrategy = strategy;
-			*family = opfamily;
+			*opfamily_p = opfamily;
+			*amstrategy_p = strategy;
 			return true;
 		}
 	}
@@ -1345,17 +1343,11 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 			EquivalenceFilter *ef = (EquivalenceFilter *) lfirst(lc2);
 			Expr *leftexpr;
 			Expr *rightexpr;
-			int strategy;
 			Oid opno;
-			Oid family;
 
 			if (ef->ef_source_rel == relid)
 				continue;
 
-			if (!find_am_family_and_stategy(ef->ef_opno, BTREE_AM_OID,
-				&family, &strategy))
-				continue;
-
 			if (ef->ef_const_is_left)
 			{
 				leftexpr = (Expr *) ef->ef_const;
@@ -1367,10 +1359,10 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 				rightexpr = (Expr *) ef->ef_const;
 			}
 
-			opno = get_opfamily_member(family,
+			opno = get_opfamily_member(ef->opfamily,
 										exprType((Node *) leftexpr),
 										exprType((Node *) rightexpr),
-										strategy);
+										ef->amstrategy);
 
 			if (opno == InvalidOid)
 				continue;
@@ -1989,9 +1981,12 @@ distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
 	 */
 	foreach(l, quallist)
 	{
-		OpExpr	   *opexpr = (OpExpr *) lfirst(l);
-		Expr	   *leftexpr = (Expr *) linitial(opexpr->args);
-		Expr	   *rightexpr = (Expr *) lsecond(opexpr->args);
+		RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
+		OpExpr *opexpr = (OpExpr *)(rinfo->clause);
+
+		Oid opfamily;
+		int amstrategy;
+
 		Const	   *constexpr;
 		Expr	   *varexpr;
 		Relids		exprrels;
@@ -2003,25 +1998,31 @@ distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
 		 * Determine if the the OpExpr is in the form "expr op const" or
 		 * "const op expr".
 		 */
-		if (IsA(leftexpr, Const))
+		if (bms_is_empty(rinfo->left_relids))
 		{
-			constexpr = (Const *) leftexpr;
-			varexpr = rightexpr;
+			constexpr = (Const *) get_leftop(rinfo->clause);
+			varexpr = (Expr *) get_rightop(rinfo->clause);
 			const_isleft = true;
+			exprrels = rinfo->right_relids;
 		}
 		else
 		{
-			constexpr = (Const *) rightexpr;
-			varexpr = leftexpr;
+			constexpr = (Const *) get_rightop(rinfo->clause);
+			varexpr = (Expr *) get_leftop(rinfo->clause);
 			const_isleft = false;
+			exprrels = rinfo->left_relids;
 		}
 
-		exprrels = pull_varnos(root, (Node *) varexpr);
-
 		/* should be filtered out, but we need to determine relid anyway */
 		if (!bms_get_singleton_member(exprrels, &relid))
 			continue;
 
+		if (!find_am_family_and_stategy(opexpr->opno, BTREE_AM_OID,
+										rinfo->btreeineqopfamilies,
+										&opfamily,
+										&amstrategy))
+			continue;
+
 		/* search for a matching eclass member in all eclasses */
 		foreach(l2, root->eq_classes)
 		{
@@ -2057,6 +2058,8 @@ distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
 					efilter->ef_const_is_left = const_isleft;
 					efilter->ef_opno = opexpr->opno;
 					efilter->ef_source_rel = relid;
+					efilter->opfamily = opfamily;
+					efilter->amstrategy = amstrategy;
 
 					ec->ec_filters = lappend(ec->ec_filters, efilter);
 					break;		/* Onto the next eclass */
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index f86276b667..d7a173b327 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -651,44 +651,6 @@ create_lateral_join_info(PlannerInfo *root)
 	}
 }
 
-/*
- * is_simple_filter_qual
- *             Analyzes an OpExpr to determine if it may be useful as an
- *             EquivalenceFilter. Returns true if the OpExpr may be of some use, or
- *             false if it should not be used.
- */
-static bool
-is_simple_filter_qual(PlannerInfo *root, OpExpr *expr)
-{
-	Expr *leftexpr;
-	Expr *rightexpr;
-
-	if (!IsA(expr, OpExpr))
-			return false;
-
-	if (list_length(expr->args) != 2)
-			return false;
-
-	leftexpr = (Expr *) linitial(expr->args);
-	rightexpr = (Expr *) lsecond(expr->args);
-
-	/* XXX should we restrict these to simple Var op Const expressions? */
-	if (IsA(leftexpr, Const))
-	{
-		if (bms_membership(pull_varnos(root, (Node *) rightexpr)) == BMS_SINGLETON &&
-			!contain_volatile_functions((Node *) rightexpr))
-			return true;
-	}
-	else if (IsA(rightexpr, Const))
-	{
-		if (bms_membership(pull_varnos(root, (Node *) leftexpr)) == BMS_SINGLETON &&
-			!contain_volatile_functions((Node *) leftexpr))
-			return true;
-	}
-
-	return false;
-}
-
 /*****************************************************************************
  *
  *	  JOIN TREE PROCESSING
@@ -1678,6 +1640,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	bool		maybe_outer_join;
 	Relids		nullable_relids;
 	RestrictInfo *restrictinfo;
+	int	relid;
 
 	/*
 	 * Retrieve all relids mentioned within the clause.
@@ -2027,8 +1990,15 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 	distribute_restrictinfo_to_rels(root, restrictinfo);
 
 	/* Check if the qual looks useful to harvest as an EquivalenceFilter */
-	if (filter_qual_list != NULL && is_simple_filter_qual(root, (OpExpr *) clause))
-		*filter_qual_list = lappend(*filter_qual_list, clause);
+	if (filter_qual_list != NULL &&
+		is_opclause(restrictinfo->clause) &&
+		!contain_volatile_functions((Node *)restrictinfo) && // Cachable
+		restrictinfo->btreeineqopfamilies != NIL &&  /* ineq expression */
+		/* simple & common enough filter, one side references one relation and the other one is a constant */
+		((bms_is_empty(restrictinfo->left_relids) && bms_get_singleton_member(restrictinfo->right_relids, &relid)) ||
+		 (bms_is_empty(restrictinfo->right_relids) && bms_get_singleton_member(restrictinfo->left_relids, &relid)))
+		)
+		*filter_qual_list = lappend(*filter_qual_list, restrictinfo);
 }
 
 /*
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index f80b47ae2c..942a52fcac 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1179,6 +1179,8 @@ typedef struct EquivalenceFilter
 	Oid			ef_opno;		/* Operator Oid of filter operator */
 	bool		ef_const_is_left; /* Is the Const on the left of the OpExrp? */
 	Index		ef_source_rel;	/* relid of originating relation. */
+	Oid			opfamily;
+	int			amstrategy;
 } EquivalenceFilter;
 
 /*
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index 92fcec1158..980bd3817d 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -407,14 +407,14 @@ set session authorization regress_user_ectest;
 explain (costs off)
   select * from ec0 a, ec1 b
   where a.ff = b.ff and a.ff = 43::bigint::int8alias1;
-                              QUERY PLAN                              
-----------------------------------------------------------------------
+                 QUERY PLAN                  
+---------------------------------------------
  Nested Loop
    ->  Index Scan using ec0_pkey on ec0 a
          Index Cond: (ff = '43'::int8alias1)
    ->  Index Scan using ec1_pkey on ec1 b
          Index Cond: (ff = a.ff)
-         Filter: ((f1 < '5'::int8alias1) AND (ff = '43'::int8alias1))
+         Filter: (f1 < '5'::int8alias1)
 (6 rows)
 
 reset session authorization;
-- 
2.37.0

v5-0004-Prepare-the-code-for-CorrectiveQual-structure.patchtext/plain; charset=UTF-8; name=v5-0004-Prepare-the-code-for-CorrectiveQual-structure.patchDownload
From e84fa416c5e55fcdb250bfbb0125fa2fc75013df Mon Sep 17 00:00:00 2001
From: "Andrey V. Lepikhov" <a.lepikhov@postgrespro.ru>
Date: Wed, 6 Jul 2022 15:48:00 +0300
Subject: [PATCH 4/6] Prepare the code for CorrectiveQual structure. Just
 refactor the method for 2-level loop in
 generate_base_implied_equalities_no_const, no other things is changed.

---
 src/backend/optimizer/path/equivclass.c | 61 +++++++++++++++----------
 1 file changed, 37 insertions(+), 24 deletions(-)

diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 38bbc325b0..b3e5ebfbb1 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -1333,17 +1333,33 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 				rinfo->right_em = cur_em;
 			}
 		}
+		prev_ems[relid] = cur_em;
+	}
 
-		/*
-		 * Also push any EquivalenceFilter clauses down into all relations
-		 * other than the one which the filter actually originated from.
-		 */
-		foreach(lc2, ec->ec_filters)
+	pfree(prev_ems);
+
+
+	/*
+	 * Also push any EquivalenceFilter clauses down into all relations
+	 * other than the one which the filter actually originated from.
+	 */
+	foreach(lc2, ec->ec_filters)
+	{
+		EquivalenceFilter *ef = (EquivalenceFilter *) lfirst(lc2);
+		Expr *leftexpr;
+		Expr *rightexpr;
+		Oid opno;
+		int relid;
+
+		if (ec->ec_broken)
+			break;
+
+		foreach(lc, ec->ec_members)
 		{
-			EquivalenceFilter *ef = (EquivalenceFilter *) lfirst(lc2);
-			Expr *leftexpr;
-			Expr *rightexpr;
-			Oid opno;
+			EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc);
+
+			if (!bms_get_singleton_member(cur_em->em_relids, &relid))
+				continue;
 
 			if (ef->ef_source_rel == relid)
 				continue;
@@ -1360,29 +1376,26 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 			}
 
 			opno = get_opfamily_member(ef->opfamily,
-										exprType((Node *) leftexpr),
-										exprType((Node *) rightexpr),
-										ef->amstrategy);
+									   exprType((Node *) leftexpr),
+									   exprType((Node *) rightexpr),
+									   ef->amstrategy);
 
 			if (opno == InvalidOid)
 				continue;
 
+
 			process_implied_equality(root, opno,
-										ec->ec_collation,
-										leftexpr,
-										rightexpr,
-										bms_copy(ec->ec_relids),
-										bms_copy(cur_em->em_nullable_relids),
-										ec->ec_min_security,
-										ec->ec_below_outer_join,
-										false);
+									 ec->ec_collation,
+									 leftexpr,
+									 rightexpr,
+									 bms_copy(ec->ec_relids),
+									 bms_copy(cur_em->em_nullable_relids),
+									 ec->ec_min_security,
+									 ec->ec_below_outer_join,
+									 false);
 		}
-
-		prev_ems[relid] = cur_em;
 	}
 
-	pfree(prev_ems);
-
 	/*
 	 * We also have to make sure that all the Vars used in the member clauses
 	 * will be available at any join node we might try to reference them at.
-- 
2.37.0

v5-0005-CorrectiveQuals-is-as-simple-as-a-List-of-RestrictIn.patchtext/plain; charset=UTF-8; name=v5-0005-CorrectiveQuals-is-as-simple-as-a-List-of-RestrictIn.patchDownload
From 03e75cf5db6b607cd62d2fdcd1b44e56fccaf3cf Mon Sep 17 00:00:00 2001
From: "Andrey V. Lepikhov" <a.lepikhov@postgrespro.ru>
Date: Wed, 6 Jul 2022 15:52:36 +0300
Subject: [PATCH 5/6] CorrectiveQuals is as simple as a List of RestrictInfo,
 a). only one restrictinfo on this group should be counted for any joinrel
 estimation. b). at least 1 restrictinfo in this group should be executed
 during execution. In this commit, only rows estimation issue is addressed.

PlannerInfo.correlative_quals is added to manage all the CorrectiveQuals at
subquery level. RelOptInfo.cqual_indexes is a List * to indicate a which
CorrectiveQuals this relation related to. This is designed for easy to check if
the both sides of joinrel correlated to the same CorrectiveQuals. Why isn't the
type a Bitmapset * will be explained later.

The overall design of handing the joinrel size estimation is:
a). At the base relation level, we just count everything with the correlative
quals. b). During the any level joinrel size estimation, we just keep 1 side's
cqual (short for corrective qual) selectivity by eliminated the other one. so
the size estimation for a mergeable join selectivity becomes to:

rows = R1.rows X r2.rows X 1 / Max (ndistval_of_colA,  ndistinval_of_colB) X 1 /
Selectivity(R1's CorrectiveQual).

r1.rows X 1 / Selectivity(R1's CorrectiveQual) eliminated the impact of
CorrectiveQual on R1. After this,  the JoinRel of (R1, R2) still be impacted by
this CorrectiveQual but just one in this level.  Later if JoinRel(R1, R2) needs
to join with R3,  and R3 is impacted by this CorectiveQuals as well. This we
need to keep one and eliminating the other one as above again.

The algorithm for which Selectivity should be eliminated and which one should be
kept is:

When we join 2 inner_rel and outer_rel with a mergeable join restrictinfo, if
both sides is impacted with the same CorrectiveQual, we first choose which "side"
to eliminating based on which side of the restrictinfo has a higher distinct
value. The reason for this is more or less because we used "Max"(ndistinctValT1,
ndistinctValT2). After decide which "side" to eliminating, the real eliminating
selecitity is the side of RelOptInfo->cqual_selectivity[n]

Selectivity *RelOptInfo->cqual_selectivity:

 The number of elements in cqual_selecitity equals
 the length of cqual_indexes. The semantics is which
 selectivity in the corresponding CorectiveQuals's qual
 list is taking effect. At only time, only 1 Qual
 Selectivity is counted for any-level of joinrel.

and the other side's RelOptInfo->cqual_selectivty is used to set the upper
joinrel->cqual_selecivity.

In reality, it is possible for to have many CorrectiveQuals, but for design
discussion, the current implementation only take care of the 1 CorrectiveQuals.
this would be helpful for PoC/review/discussion.

Some flow for the key data:

1. root->corrective_quals is initialized at
generate_base_implied_equalities_no_const stage. we create a CorrectiveQual in
this list for each ec_filter and fill the RestrictInfo part for this cqual. At
the same time, we note which RelOptInfo (cqual_indexes) has related to this cqual.

2. RelOptInfo->cqual_selecitity for baserel is set at the end of set_rel_size,
at this time, the selectivity for every RestrictInfo is calcuated, we can just
fetch the cached value.  As for joinrel, it is maintained in
calc_join_cqual_selectivity, this function would return the Selectivity to
eliminate and set the above value.

Limitation in this PoC:
1. Only support 1 CorrectiveQual in root->correlative_quals
2. Only tested with INNER_JOIN.
3. Inherited table is not supported.
---
 src/backend/nodes/outfuncs.c              |   1 +
 src/backend/optimizer/path/allpaths.c     |  27 ++++
 src/backend/optimizer/path/costsize.c     | 182 ++++++++++++++++++++++
 src/backend/optimizer/path/equivclass.c   |  48 ++++--
 src/backend/optimizer/plan/planner.c      |   1 +
 src/backend/optimizer/prep/prepjointree.c |   1 +
 src/include/nodes/nodes.h                 |   1 +
 src/include/nodes/pathnodes.h             |  36 ++++-
 8 files changed, 280 insertions(+), 17 deletions(-)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index f31f1de983..5e0434df1e 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2676,6 +2676,7 @@ _outEquivalenceFilter(StringInfo str, const EquivalenceFilter *node)
 	WRITE_UINT_FIELD(ef_source_rel);
 	WRITE_OID_FIELD(opfamily);
 	WRITE_INT_FIELD(amstrategy);
+	WRITE_NODE_FIELD(rinfo);
 }
 
 static void
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index e9342097e5..2ee28a94fc 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -463,6 +463,33 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 	 * We insist that all non-dummy rels have a nonzero rowcount estimate.
 	 */
 	Assert(rel->rows > 0 || IS_DUMMY_REL(rel));
+
+	/* Now calculating the selectivity impacted by Corrective Qual */
+	if (!rte->inh)  /* not supported in this PoC */
+	{
+		ListCell *l;
+		int i = 0;
+		rel->cqual_selectivity = palloc(sizeof(Selectivity) * list_length(rel->cqual_indexes));
+
+		foreach(l, rel->cqual_indexes)
+		{
+			int cq_index = lfirst_int(l);
+			CorrelativeQuals *cquals = list_nth_node(CorrelativeQuals, root->correlative_quals, cq_index);
+			ListCell *l2;
+			bool found = false;
+			foreach(l2, cquals->corr_restrictinfo)
+			{
+				RestrictInfo *rinfo = lfirst_node(RestrictInfo, l2);
+				if (bms_equal(rinfo->clause_relids, rel->relids))
+				{
+					found = true;
+					rel->cqual_selectivity[i] = rinfo->norm_selec > 0 ? rinfo->norm_selec : rinfo->outer_selec;
+					Assert(rel->cqual_selectivity[i] > 0);
+				}
+			}
+			Assert(found);
+		}
+	}
 }
 
 /*
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index fcc26b01a4..03b92a2a88 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -5428,6 +5428,138 @@ get_parameterized_joinrel_size(PlannerInfo *root, RelOptInfo *rel,
 	return nrows;
 }
 
+
+/*
+ * Given a mergeable RestrictInfo, find out which relid should be used for
+ * eliminating Corrective Qual Selectivity.
+ */
+static int
+find_relid_to_eliminate(PlannerInfo *root, RestrictInfo *rinfo)
+{
+	int left_relid,  right_relid;
+	RelOptInfo *lrel, *rrel;
+	bool res;
+
+	res = bms_get_singleton_member(rinfo->left_relids, &left_relid);
+	Assert(res);
+	res = bms_get_singleton_member(rinfo->left_relids, &right_relid);
+	Assert(res);
+
+	lrel = root->simple_rel_array[left_relid];
+	rrel = root->simple_rel_array[right_relid];
+
+	/* XXX: Assumed only one CorrectiveQual exists */
+
+	if (lrel->cqual_selectivity[0] > rrel->cqual_selectivity[0])
+		return left_relid;
+
+	return right_relid;
+}
+
+/*
+ * calc_join_cqual_selectivity
+ *
+ *	When join two relations, if both sides are impacted by the same CorrectiveQuals,
+ * we need to eliminate one of them and note the other one for future eliminating when join
+ * another corrective relation. or else just note the joinrel still being impacted by the
+ * single sides's CorrectiveQuals.
+ *
+ * Return value is the Selectivity we need to eliminate for estimating the current
+ * joinrel.
+ */
+static double
+calc_join_cqual_selectivity(PlannerInfo *root,
+							RelOptInfo *joinrel,
+							RelOptInfo *outer_rel,
+							RelOptInfo *inner_rel,
+							RestrictInfo *rinfo)
+{
+	double res = 1;
+	ListCell *lc1, *lc2;
+	Selectivity left_sel;  /* The cqual selectivity still impacted on this joinrel. */
+
+	/*
+	 * Find how many CorrectiveQual for this joinrel and allocate space for each left Selectivity
+	 * for each CorrectiveQual here.
+	 */
+	List	*final_cq_list = list_union_int(outer_rel->cqual_indexes, inner_rel->cqual_indexes);
+
+	joinrel->cqual_selectivity = palloc(sizeof(Selectivity) * list_length(final_cq_list));
+
+	foreach(lc1, outer_rel->cqual_indexes)
+	{
+		int outer_cq_index = lfirst_int(lc1);
+		int inner_cq_pos = -1;
+		int outer_idx = foreach_current_index(lc1);
+		int curr_sel_len;
+
+		/*
+		 * Check if the same corrective quals applied in both sides,
+		 * if yes, we need to decide which one to eliminate and which one
+		 * to keep. or else, we just keep the selectivity for feature use.
+		 */
+		foreach(lc2, inner_rel->cqual_indexes)
+		{
+			if (outer_cq_index == lfirst_int(lc2))
+				inner_cq_pos = foreach_current_index(lc2);
+		}
+
+		if (inner_cq_pos >= 0)
+		{
+			/* Find the CorrectiveQual which impacts both side. */
+			int relid = find_relid_to_eliminate(root, rinfo);
+			if (bms_is_member(relid, outer_rel->relids))
+			{
+				/* XXXX: we assume only 1 CorrectiveQual exist, so [0] directly. */
+				res *= outer_rel->cqual_selectivity[0];
+				left_sel = inner_rel->cqual_selectivity[0];
+			}
+			else
+			{
+				/* XXXX: we assume only 1 CorrectiveQual exist */
+				res *= inner_rel->cqual_selectivity[0];
+				left_sel = outer_rel->cqual_selectivity[0];
+			}
+		}
+		else
+		{
+			/* Only shown in outer side. */
+			left_sel = outer_rel->cqual_selectivity[outer_idx];
+		}
+
+		/*
+		 * If any side of join relation is impacted by a cqual, it is impacted for the joinrel
+		 * for sure.
+		 */
+		curr_sel_len = list_length(joinrel->cqual_indexes);
+		joinrel->cqual_indexes = lappend_int(joinrel->cqual_indexes, outer_idx);
+
+		joinrel->cqual_selectivity[curr_sel_len] = left_sel;
+		// elog(INFO, "left_sel %f", left_sel);
+	}
+
+	/* Push any cqual information which exists in inner_rel only to join rel. */
+	foreach(lc1, inner_rel->cqual_indexes)
+	{
+		int inner_cq_index = lfirst_int(lc1);
+		int curr_sel_len;
+
+		if (list_member_int(outer_rel->cqual_indexes, inner_cq_index))
+			/* have been handled in the previous loop */
+			continue;
+
+		curr_sel_len = list_length(joinrel->cqual_indexes);
+		joinrel->cqual_selectivity[curr_sel_len] = inner_rel->cqual_selectivity[foreach_current_index(lc1)];
+	}
+
+	pfree(final_cq_list);
+
+	// elog(INFO, "Final adjust sel (%s): %f", bmsToString(joinrel->relids), res);
+
+	return res;
+}
+
+
 /*
  * calc_joinrel_size_estimate
  *		Workhorse for set_joinrel_size_estimates and
@@ -5571,6 +5703,56 @@ calc_joinrel_size_estimate(PlannerInfo *root,
 			break;
 	}
 
+	{
+		Selectivity m1 = 1;
+		bool should_eliminate = false;
+		RestrictInfo *rinfo;
+
+		// XXX: For hack only, the aim is the "only one" restrictinfo is the one impacted by "the only one"
+		// CorrectiveQuals. for example:
+		// SELECT * FROM t1, t2, t3 WHERE t1.a = t2.a and t2.a = t3.a and t3.a > 2;
+
+		if (list_length(root->correlative_quals) == 1 &&
+			list_length(restrictlist) == 1 &&
+			jointype == JOIN_INNER)
+		{
+			int left_relid, right_relid;
+			rinfo = linitial_node(RestrictInfo, restrictlist);
+			if (rinfo->mergeopfamilies != NIL &&
+				bms_get_singleton_member(rinfo->left_relids, &left_relid) &&
+				bms_get_singleton_member(rinfo->right_relids, &right_relid))
+			{
+				List *interset_cq_indexes = list_intersection_int(
+					root->simple_rel_array[left_relid]->cqual_indexes,
+					root->simple_rel_array[right_relid]->cqual_indexes);
+
+				if (interset_cq_indexes != NIL &&
+					!root->simple_rte_array[left_relid]->inh &&
+					!root->simple_rte_array[right_relid]->inh)
+					should_eliminate = true;
+			}
+		}
+
+		// elog(INFO, "joinrel: %s, %d", bmsToString(joinrel->relids), should_eliminate);
+
+		if (should_eliminate)
+			m1 = calc_join_cqual_selectivity(root, joinrel, outer_rel, inner_rel, rinfo);
+
+		/* elog(INFO, */
+		/*	 "joinrelids: %s, outer_rel: %s, inner_rel: %s, join_clauselist: %s outer rows: %f, inner_rows: %f, join rows: %f, jselec: %f, m1 = %f, m2 = %f", */
+		/*	 bmsToString(joinrel->relids), */
+		/*	 bmsToString(outer_rel->relids), */
+		/*	 bmsToString(inner_rel->relids), */
+		/*	 bmsToString(join_list_relids), */
+		/*	 outer_rel->rows, */
+		/*	 inner_rel->rows, */
+		/*	 nrows, */
+		/*	 jselec, */
+		/*	 m1, */
+		/*	 m2); */
+		nrows /= m1;
+	}
+
 	return clamp_row_est(nrows);
 }
 
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index b3e5ebfbb1..3efeb1f333 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -1272,6 +1272,8 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 	EquivalenceMember **prev_ems;
 	ListCell   *lc;
 	ListCell   *lc2;
+	int	start_cq_index = list_length(root->correlative_quals);
+	int	ef_index = 0;
 
 	/*
 	 * We scan the EC members once and track the last-seen member for each
@@ -1338,9 +1340,11 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 
 	pfree(prev_ems);
 
+	if (ec->ec_broken)
+		goto ec_filter_done;
 
 	/*
-	 * Also push any EquivalenceFilter clauses down into all relations
+	 * Push any EquivalenceFilter clauses down into all relations
 	 * other than the one which the filter actually originated from.
 	 */
 	foreach(lc2, ec->ec_filters)
@@ -1350,19 +1354,25 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 		Expr *rightexpr;
 		Oid opno;
 		int relid;
-
-		if (ec->ec_broken)
-			break;
+		CorrelativeQuals *cquals = makeNode(CorrelativeQuals);
 
 		foreach(lc, ec->ec_members)
 		{
 			EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc);
+			RelOptInfo *rel;
+			RestrictInfo *rinfo;
 
 			if (!bms_get_singleton_member(cur_em->em_relids, &relid))
 				continue;
 
+			rel = root->simple_rel_array[relid];
+
 			if (ef->ef_source_rel == relid)
+			{
+				rel->cqual_indexes = lappend_int(rel->cqual_indexes, start_cq_index + ef_index);
+				cquals->corr_restrictinfo = lappend(cquals->corr_restrictinfo, ef->rinfo);
 				continue;
+			}
 
 			if (ef->ef_const_is_left)
 			{
@@ -1383,19 +1393,28 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 			if (opno == InvalidOid)
 				continue;
 
-
-			process_implied_equality(root, opno,
-									 ec->ec_collation,
-									 leftexpr,
-									 rightexpr,
-									 bms_copy(ec->ec_relids),
-									 bms_copy(cur_em->em_nullable_relids),
-									 ec->ec_min_security,
-									 ec->ec_below_outer_join,
-									 false);
+			rinfo = process_implied_equality(root, opno,
+											 ec->ec_collation,
+											 leftexpr,
+											 rightexpr,
+											 bms_copy(ec->ec_relids),
+											 bms_copy(cur_em->em_nullable_relids),
+											 ec->ec_min_security,
+											 ec->ec_below_outer_join,
+											 false);
+			cquals->corr_restrictinfo = lappend(cquals->corr_restrictinfo, rinfo);
+			rel->cqual_indexes = lappend_int(rel->cqual_indexes, start_cq_index + ef_index);
 		}
+
+		ef_index += 1;
+
+		root->correlative_quals = lappend(root->correlative_quals, cquals);
 	}
 
+ec_filter_done:
+	/*
+	 * XXX this label can be removed after moving ec_filter to the end of this function.
+	 */
 	/*
 	 * We also have to make sure that all the Vars used in the member clauses
 	 * will be available at any join node we might try to reference them at.
@@ -2073,6 +2092,7 @@ distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist)
 					efilter->ef_source_rel = relid;
 					efilter->opfamily = opfamily;
 					efilter->amstrategy = amstrategy;
+					efilter->rinfo = rinfo;
 
 					ec->ec_filters = lappend(ec->ec_filters, efilter);
 					break;		/* Onto the next eclass */
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 06ad856eac..2be2429454 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -619,6 +619,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	root->multiexpr_params = NIL;
 	root->eq_classes = NIL;
 	root->ec_merging_done = false;
+	root->correlative_quals = NIL;
 	root->all_result_relids =
 		parse->resultRelation ? bms_make_singleton(parse->resultRelation) : NULL;
 	root->leaf_result_relids = NULL;	/* we'll find out leaf-ness later */
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 0bd99acf83..d427de6f85 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -999,6 +999,7 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	subroot->multiexpr_params = NIL;
 	subroot->eq_classes = NIL;
 	subroot->ec_merging_done = false;
+	subroot->correlative_quals = NIL;
 	subroot->all_result_relids = NULL;
 	subroot->leaf_result_relids = NULL;
 	subroot->append_rel_list = NIL;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index ba879ab3e9..8800a05252 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -278,6 +278,7 @@ typedef enum NodeTag
 	/* these aren't subclasses of Path: */
 	T_EquivalenceClass,
 	T_EquivalenceFilter,
+	T_CorrelativeQuals,
 	T_EquivalenceMember,
 	T_PathKey,
 	T_PathKeyInfo,
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 942a52fcac..1e9bb39277 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -251,6 +251,8 @@ struct PlannerInfo
 
 	bool		ec_merging_done;	/* set true once ECs are canonical */
 
+	List		*correlative_quals;  /* list of CorrelativeQuals for this subquery */
+
 	List	   *canon_pathkeys; /* list of "canonical" PathKeys */
 
 	List	   *left_join_clauses;	/* list of RestrictInfos for mergejoinable
@@ -767,6 +769,18 @@ typedef struct RelOptInfo
 	 * Indexes in PlannerInfo's eq_classes list of ECs that mention this rel
 	 */
 	Bitmapset  *eclass_indexes;
+	List		*cqual_indexes; /* Indexes in PlannerInfo's correlative_quals list of
+								 * CorrelativeQuals that this rel has applied. It is valid
+								 * on both baserel and joinrel. Used to quick check is the
+								 * both sides contains the same CorrectiveQuals object.
+								 */
+	Selectivity	*cqual_selectivity; /*
+								   * The number of elements in cqual_selectivity equals
+								   * the length of cqual_indexes. The semantics is which
+								   * selectivity in the corresponding CorectiveQuals's qual
+								   * list is taking effect. At only time, only 1 Qual
+								   * Selectivity is counted for any-level of joinrel.
+								   */
 	PlannerInfo *subroot;		/* if subquery */
 	List	   *subplan_params; /* if subquery */
 	/* wanted number of parallel workers */
@@ -1181,8 +1195,24 @@ typedef struct EquivalenceFilter
 	Index		ef_source_rel;	/* relid of originating relation. */
 	Oid			opfamily;
 	int			amstrategy;
+	struct RestrictInfo	*rinfo;		/* source restrictInfo for this EquivalenceFilter */
 } EquivalenceFilter;
 
+
+/*
+ * Currently it is as simple as a List of RestrictInfo, it means a). For any joinrel size
+ * estimation, only one restrictinfo on this group should be counted. b). During execution,
+ * at least 1 restrictinfo in this group should be executed.
+ *
+ * Define it as a Node just for better extendability, we can stripe it to a List *
+ * if we are sure nothing else is needed.
+ */
+typedef struct CorrelativeQuals
+{
+	NodeTag	type;
+	List	*corr_restrictinfo;
+} CorrelativeQuals;
+
 /*
  * If an EC contains a const and isn't below-outer-join, any PathKey depending
  * on it must be redundant, since there's only one possible value of the key.
@@ -2872,7 +2902,7 @@ typedef enum
  *
  * flags indicating what kinds of grouping are possible.
  * partial_costs_set is true if the agg_partial_costs and agg_final_costs
- * 		have been initialized.
+ *		have been initialized.
  * agg_partial_costs gives partial aggregation costs.
  * agg_final_costs gives finalization costs.
  * target_parallel_safe is true if target is parallel safe.
@@ -2902,8 +2932,8 @@ typedef struct
  * 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).
+ *		expressions computed by preprocess_limit() (see comments for
+ *		preprocess_limit() for more information).
  */
 typedef struct
 {
-- 
2.37.0

v5-0006-Disable-ec-filter-for-foregin-table-for-now.-We-do-n.patchtext/plain; charset=UTF-8; name=v5-0006-Disable-ec-filter-for-foregin-table-for-now.-We-do-n.patchDownload
From b05f3381a5cfde1d3e628b829fe2e7cff3649804 Mon Sep 17 00:00:00 2001
From: "Andrey V. Lepikhov" <a.lepikhov@postgrespro.ru>
Date: Wed, 6 Jul 2022 15:54:24 +0300
Subject: [PATCH 6/6] Disable ec filter for foregin table for now. We do need
 support EC filter against for foreign table, but when fixing the cost model
 issue, we need to know the selecvitiy of the qual on foregin table. However
 it is impossible for now to know that when use_remote_estimate = true. see
 for postgresGetForeignRelSize. Since we currently only doing PoC for this
 cost-model-fix algorithm, I just disable that for foregin table. At last, we
 need improve the use_remote_estimate somehow to get the selectivity.

---
 .../postgres_fdw/expected/postgres_fdw.out    | 36 +++++++++----------
 src/backend/optimizer/path/allpaths.c         |  2 +-
 src/backend/optimizer/path/equivclass.c       | 14 ++++++++
 src/backend/optimizer/plan/initsplan.c        |  9 +++--
 4 files changed, 40 insertions(+), 21 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 2758049f5b..44457f930c 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -1545,12 +1545,12 @@ SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNE
 -- full outer join + inner join
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
-                                                                                                                                                                           QUERY PLAN                                                                                                                                                                           
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                 QUERY PLAN                                                                                                                                                 
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Foreign Scan
    Output: t1.c1, t2.c1, t3.c1
    Relations: ((public.ft4 t1) INNER JOIN (public.ft5 t2)) FULL JOIN (public.ft4 t3)
-   Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND (((r2.c1 + 1) >= 50)) AND (((r2.c1 + 1) <= 60)) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint
+   Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint
 (4 rows)
 
 SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10;
@@ -2335,12 +2335,12 @@ SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT
 UPDATE ft5 SET c3 = null where c1 % 9 = 0;
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
-                                                                                                                                                    QUERY PLAN                                                                                                                                                     
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                QUERY PLAN                                                                                                                                 
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Foreign Scan
    Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2
    Relations: (public.ft5) INNER JOIN (public.ft4)
-   Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)) AND ((r1.c1 >= 10)) AND ((r1.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST
+   Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST
 (4 rows)
 
 SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1;
@@ -2362,8 +2362,8 @@ SET enable_hashjoin TO false;
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1
     AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE;
-                                                                                                                                                                                                                                                                                                                                                                                                                                                                      QUERY PLAN                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                                                                                                                                                                                                                                                                                                               QUERY PLAN                                                                                                                                                                                                                                                                                                                                                                                                                                               
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  LockRows
    Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ctid
    ->  Merge Join
@@ -2373,7 +2373,7 @@ SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = f
          ->  Foreign Scan
                Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
                Relations: (((public.ft1) INNER JOIN (public.ft2)) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
-               Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r4.c1, r4.c2, r4.c3, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4.c1, r4.c2, r4.c3) END FROM ((("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" < 100)) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 3" r3 ON (((r1.c2 = r3.c1)))) INNER JOIN "S 1"."T 4" r4 ON (((r1.c2 = r4.c1)))) ORDER BY r1.c2 ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3 FOR UPDATE OF r4
+               Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r4.c1, r4.c2, r4.c3, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4.c1, r4.c2, r4.c3) END FROM ((("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 3" r3 ON (((r1.c2 = r3.c1)))) INNER JOIN "S 1"."T 4" r4 ON (((r1.c2 = r4.c1)))) ORDER BY r1.c2 ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3 FOR UPDATE OF r4
                ->  Merge Join
                      Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
                      Merge Cond: (ft1.c2 = ft5.c1)
@@ -2391,12 +2391,12 @@ SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = f
                                              Sort Key: ft1.c1
                                              ->  Foreign Scan on public.ft1
                                                    Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
-                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND (("C 1" < 100)) FOR UPDATE
+                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE
                                        ->  Materialize
                                              Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
                                              ->  Foreign Scan on public.ft2
                                                    Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
-                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
+                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
                            ->  Sort
                                  Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
                                  Sort Key: ft4.c1
@@ -5705,25 +5705,25 @@ UPDATE ft2 AS target SET (c2) = (
 EXPLAIN (VERBOSE, COSTS OFF)
 UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END
   FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000;
-                                                                                                                                                                                                   QUERY PLAN                                                                                                                                                                                                    
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                                                                                                                                       QUERY PLAN                                                                                                                                                                                        
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Update on public.ft2 d
    Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1
    ->  Foreign Scan
          Output: CASE WHEN (random() >= '0'::double precision) THEN d.c2 ELSE 0 END, d.ctid, d.*, t.*
          Relations: (public.ft2 d) INNER JOIN (public.ft2 t)
-         Remote SQL: SELECT r1.c2, r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" > 1000)) AND ((r1."C 1" > 1000)))) FOR UPDATE OF r1
-         ->  Merge Join
+         Remote SQL: SELECT r1.c2, r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r1."C 1" > 1000)))) FOR UPDATE OF r1
+         ->  Hash Join
                Output: d.c2, d.ctid, d.*, t.*
-               Merge Cond: (d.c1 = t.c1)
+               Hash Cond: (d.c1 = t.c1)
                ->  Foreign Scan on public.ft2 d
                      Output: d.c2, d.ctid, d.*, d.c1
                      Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
-               ->  Materialize
+               ->  Hash
                      Output: t.*, t.c1
                      ->  Foreign Scan on public.ft2 t
                            Output: t.*, t.c1
-                           Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST
+                           Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
 (17 rows)
 
 UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 2ee28a94fc..eb4ecc8478 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -465,7 +465,7 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 	Assert(rel->rows > 0 || IS_DUMMY_REL(rel));
 
 	/* Now calculating the selectivity impacted by Corrective Qual */
-	if (!rte->inh)  /* not supported in this PoC */
+	if (!rte->inh)  /* Inherited table is not supported in this PoC */
 	{
 		ListCell *l;
 		int i = 0;
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 3efeb1f333..7b8124e1f2 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -21,6 +21,7 @@
 #include "access/stratnum.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_type.h"
+#include "catalog/pg_class.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/appendinfo.h"
@@ -1365,6 +1366,19 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
 			if (!bms_get_singleton_member(cur_em->em_relids, &relid))
 				continue;
 
+			if (root->simple_rte_array[relid]->relkind == RELKIND_FOREIGN_TABLE)
+			{
+				/*
+				 * We do need support EC filter against for foreign table, but when fixing
+				 * the cost model issue, we need to know the selecvitiy of the qual on foregin
+				 * table. However it is impossible for now to know that when use_remote_estimate = true.
+				 * see for postgresGetForeignRelSize. Since we currently only doing PoC for
+				 * this cost-model-fix algorithm, I just disable that for foregin table. At last,
+				 * we need improve the use_remote_estimate somehow to get the selectivity.
+				 */
+				continue;
+			}
+
 			rel = root->simple_rel_array[relid];
 
 			if (ef->ef_source_rel == relid)
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index d7a173b327..9153b8d296 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -1991,9 +1991,14 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
 
 	/* Check if the qual looks useful to harvest as an EquivalenceFilter */
 	if (filter_qual_list != NULL &&
+
+		// Must be an OpExpr for now.
 		is_opclause(restrictinfo->clause) &&
-		!contain_volatile_functions((Node *)restrictinfo) && // Cachable
-		restrictinfo->btreeineqopfamilies != NIL &&  /* ineq expression */
+		// Checking volatile against RestrictInfo so that the result can be cached.
+		!contain_volatile_functions((Node *)restrictinfo) &&
+
+		restrictinfo->btreeineqopfamilies != NIL && /* ineq expression */
+
 		/* simple & common enough filter, one side references one relation and the other one is a constant */
 		((bms_is_empty(restrictinfo->left_relids) && bms_get_singleton_member(restrictinfo->right_relids, &relid)) ||
 		 (bms_is_empty(restrictinfo->right_relids) && bms_get_singleton_member(restrictinfo->left_relids, &relid)))
-- 
2.37.0

#41Ian Lawrence Barwick
barwick@gmail.com
In reply to: Andrey Lepikhov (#40)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

2022年7月7日(木) 20:11 Andrey Lepikhov <a.lepikhov@postgrespro.ru>:

On 17/5/2022 05:00, Andy Fan wrote:

Thanks. But I will wait to see if anyone will show interest with this.
Or else
Moving alone is not a great experience.

To move forward I've rebased your patchset onto new master, removed
annoying tailing backspaces and applied two regression test changes,
caused by second patch: first of changes are legal, second looks normal
but should be checked on optimality.
As I see, a consensus should be found for the questions:
1. Case of redundant clauses (x < 100 and x < 1000)
2. Planning time degradation for trivial OLTP queries

Hi

cfbot reports the patch no longer applies [1]http://cfbot.cputube.org/patch_40_3524.log. As CommitFest 2022-11 is
currently underway, this would be an excellent time to update the patch.

[1]: http://cfbot.cputube.org/patch_40_3524.log

Thanks

Ian Barwick

#42Andy Fan
zhihui.fan1213@gmail.com
In reply to: Ian Lawrence Barwick (#41)
Re: Condition pushdown: why (=) is pushed down into join, but BETWEEN or >= is not?

Hi:

cfbot reports the patch no longer applies [1]. As CommitFest 2022-11 is
currently underway, this would be an excellent time to update the patch.

Thank you Ian & Andrey for taking care of this! I am planning to start
a new thread for this topic in 2 weeks, and will post an update patch
at that time.

--
Best Regards
Andy Fan