aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/backend/optimizer/path/equivclass.c10
-rw-r--r--src/test/regress/expected/equivclass.out385
-rw-r--r--src/test/regress/parallel_schedule2
-rw-r--r--src/test/regress/serial_schedule1
-rw-r--r--src/test/regress/sql/equivclass.sql224
5 files changed, 619 insertions, 3 deletions
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index d89feb34b32..e0bdbe87a7f 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -902,7 +902,12 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
* of the EC back into the main restrictinfo datastructures. Multi-relation
* clauses will be regurgitated later by generate_join_implied_equalities().
* (We do it this way to maintain continuity with the case that ec_broken
- * becomes set only after we've gone up a join level or two.)
+ * becomes set only after we've gone up a join level or two.) However, for
+ * an EC that contains constants, we can adopt a simpler strategy and just
+ * throw back all the source RestrictInfos immediately; that works because
+ * we know that such an EC can't become broken later. (This rule justifies
+ * ignoring ec_has_const ECs in generate_join_implied_equalities, even when
+ * they are broken.)
*/
static void
generate_base_implied_equalities_broken(PlannerInfo *root,
@@ -914,7 +919,8 @@ generate_base_implied_equalities_broken(PlannerInfo *root,
{
RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(lc);
- if (bms_membership(restrictinfo->required_relids) != BMS_MULTIPLE)
+ if (ec->ec_has_const ||
+ bms_membership(restrictinfo->required_relids) != BMS_MULTIPLE)
distribute_restrictinfo_to_rels(root, restrictinfo);
}
}
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
new file mode 100644
index 00000000000..0a9b048e494
--- /dev/null
+++ b/src/test/regress/expected/equivclass.out
@@ -0,0 +1,385 @@
+--
+-- Tests for the planner's "equivalence class" mechanism
+--
+-- One thing that's not tested well during normal querying is the logic
+-- for handling "broken" ECs. This is because an EC can only become broken
+-- if its underlying btree operator family doesn't include a complete set
+-- of cross-type equality operators. There are not (and should not be)
+-- any such families built into Postgres; so we have to hack things up
+-- to create one. We do this by making two alias types that are really
+-- int8 (so we need no new C code) and adding only some operators for them
+-- into the standard integer_ops opfamily.
+create type int8alias1;
+create function int8alias1in(cstring) returns int8alias1
+ strict immutable language internal as 'int8in';
+NOTICE: return type int8alias1 is only a shell
+create function int8alias1out(int8alias1) returns cstring
+ strict immutable language internal as 'int8out';
+NOTICE: argument type int8alias1 is only a shell
+create type int8alias1 (
+ input = int8alias1in,
+ output = int8alias1out,
+ like = int8
+);
+create type int8alias2;
+create function int8alias2in(cstring) returns int8alias2
+ strict immutable language internal as 'int8in';
+NOTICE: return type int8alias2 is only a shell
+create function int8alias2out(int8alias2) returns cstring
+ strict immutable language internal as 'int8out';
+NOTICE: argument type int8alias2 is only a shell
+create type int8alias2 (
+ input = int8alias2in,
+ output = int8alias2out,
+ like = int8
+);
+create cast (int8 as int8alias1) without function;
+create cast (int8 as int8alias2) without function;
+create cast (int8alias1 as int8) without function;
+create cast (int8alias2 as int8) without function;
+create function int8alias1eq(int8alias1, int8alias1) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8alias1, rightarg = int8alias1,
+ commutator = =,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias1, int8alias1);
+create function int8alias2eq(int8alias2, int8alias2) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias2eq,
+ leftarg = int8alias2, rightarg = int8alias2,
+ commutator = =,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias2, int8alias2);
+create function int8alias1eq(int8, int8alias1) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8, rightarg = int8alias1,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8, int8alias1);
+create function int8alias1eq(int8alias1, int8alias2) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8alias1, rightarg = int8alias2,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias1, int8alias2);
+create function int8alias1lt(int8alias1, int8alias1) returns bool
+ strict immutable language internal as 'int8lt';
+create operator < (
+ procedure = int8alias1lt,
+ leftarg = int8alias1, rightarg = int8alias1
+);
+alter operator family integer_ops using btree add
+ operator 1 < (int8alias1, int8alias1);
+create function int8alias1cmp(int8, int8alias1) returns int
+ strict immutable language internal as 'btint8cmp';
+alter operator family integer_ops using btree add
+ function 1 int8alias1cmp (int8, int8alias1);
+create table ec0 (ff int8 primary key, f1 int8, f2 int8);
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "ec0_pkey" for table "ec0"
+create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "ec1_pkey" for table "ec1"
+create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
+NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "ec2_pkey" for table "ec2"
+-- for the moment we only want to look at nestloop plans
+set enable_hashjoin = off;
+set enable_mergejoin = off;
+--
+-- Note that for cases where there's a missing operator, we don't care so
+-- much whether the plan is ideal as that we don't fail or generate an
+-- outright incorrect plan.
+--
+explain (costs off)
+ select * from ec0 where ff = f1 and f1 = '42'::int8;
+ QUERY PLAN
+----------------------------------
+ Index Scan using ec0_pkey on ec0
+ Index Cond: (ff = 42::bigint)
+ Filter: (f1 = 42::bigint)
+(3 rows)
+
+explain (costs off)
+ select * from ec0 where ff = f1 and f1 = '42'::int8alias1;
+ QUERY PLAN
+---------------------------------------
+ Index Scan using ec0_pkey on ec0
+ Index Cond: (ff = '42'::int8alias1)
+ Filter: (f1 = '42'::int8alias1)
+(3 rows)
+
+explain (costs off)
+ select * from ec1 where ff = f1 and f1 = '42'::int8alias1;
+ QUERY PLAN
+---------------------------------------
+ Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = '42'::int8alias1)
+ Filter: (f1 = '42'::int8alias1)
+(3 rows)
+
+explain (costs off)
+ select * from ec1 where ff = f1 and f1 = '42'::int8alias2;
+ QUERY PLAN
+---------------------------------------------------
+ Seq Scan on ec1
+ Filter: ((ff = f1) AND (f1 = '42'::int8alias2))
+(2 rows)
+
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and ff = '42'::int8;
+ QUERY PLAN
+---------------------------------------------------------------
+ Nested Loop
+ Join Filter: (ec1.ff = ec2.x1)
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: ((ff = 42::bigint) AND (ff = 42::bigint))
+ -> Seq Scan on ec2
+(5 rows)
+
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and ff = '42'::int8alias1;
+ QUERY PLAN
+---------------------------------------------
+ Nested Loop
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = '42'::int8alias1)
+ -> Seq Scan on ec2
+ Filter: (x1 = '42'::int8alias1)
+(5 rows)
+
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and '42'::int8 = x1;
+ QUERY PLAN
+----------------------------------------
+ Nested Loop
+ Join Filter: (ec1.ff = ec2.x1)
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = 42::bigint)
+ -> Seq Scan on ec2
+ Filter: (42::bigint = x1)
+(6 rows)
+
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and x1 = '42'::int8alias1;
+ QUERY PLAN
+---------------------------------------------
+ Nested Loop
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = '42'::int8alias1)
+ -> Seq Scan on ec2
+ Filter: (x1 = '42'::int8alias1)
+(5 rows)
+
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and x1 = '42'::int8alias2;
+ QUERY PLAN
+-----------------------------------------
+ Nested Loop
+ -> Seq Scan on ec2
+ Filter: (x1 = '42'::int8alias2)
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = ec2.x1)
+(5 rows)
+
+create unique index ec1_expr1 on ec1((ff + 1));
+create unique index ec1_expr2 on ec1((ff + 2 + 1));
+create unique index ec1_expr3 on ec1((ff + 3 + 1));
+create unique index ec1_expr4 on ec1((ff + 4));
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+ QUERY PLAN
+--------------------------------------------------------------
+ Nested Loop
+ Join Filter: ((((public.ec1.ff + 2) + 1)) = public.ec1.f1)
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = 42::bigint)
+ -> Append
+ -> Append
+ -> Seq Scan on ec1
+ -> Seq Scan on ec1
+ -> Index Scan using ec1_expr4 on ec1
+ Index Cond: ((ff + 4) = public.ec1.f1)
+(10 rows)
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8 and ec1.ff = ec1.f1;
+ QUERY PLAN
+---------------------------------------------------------------
+ Nested Loop
+ Join Filter: ((((public.ec1.ff + 2) + 1)) = public.ec1.f1)
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: ((ff = 42::bigint) AND (ff = 42::bigint))
+ Filter: (ff = f1)
+ -> Append
+ -> Index Scan using ec1_expr2 on ec1
+ Index Cond: (((ff + 2) + 1) = 42::bigint)
+ -> Index Scan using ec1_expr3 on ec1
+ Index Cond: (((ff + 3) + 1) = 42::bigint)
+ -> Index Scan using ec1_expr4 on ec1
+ Index Cond: ((ff + 4) = 42::bigint)
+(12 rows)
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss2
+ where ss1.x = ec1.f1 and ss1.x = ss2.x and ec1.ff = 42::int8;
+ QUERY PLAN
+----------------------------------------------------------------------------
+ Nested Loop
+ Join Filter: ((((public.ec1.ff + 2) + 1)) = (((public.ec1.ff + 2) + 1)))
+ -> Append
+ -> Seq Scan on ec1
+ -> Seq Scan on ec1
+ -> Seq Scan on ec1
+ -> Materialize
+ -> Nested Loop
+ Join Filter: ((((public.ec1.ff + 2) + 1)) = public.ec1.f1)
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = 42::bigint)
+ -> Append
+ -> Append
+ -> Seq Scan on ec1
+ -> Seq Scan on ec1
+ -> Index Scan using ec1_expr4 on ec1
+ Index Cond: ((ff + 4) = public.ec1.f1)
+(17 rows)
+
+-- let's try that as a mergejoin
+set enable_mergejoin = on;
+set enable_nestloop = off;
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss2
+ where ss1.x = ec1.f1 and ss1.x = ss2.x and ec1.ff = 42::int8;
+ QUERY PLAN
+---------------------------------------------------------------------------
+ Merge Join
+ Merge Cond: ((((public.ec1.ff + 2) + 1)) = (((public.ec1.ff + 2) + 1)))
+ -> Merge Append
+ Sort Key: (((public.ec1.ff + 2) + 1))
+ -> Index Scan using ec1_expr2 on ec1
+ -> Index Scan using ec1_expr3 on ec1
+ -> Index Scan using ec1_expr4 on ec1
+ -> Materialize
+ -> Merge Join
+ Merge Cond: ((((public.ec1.ff + 2) + 1)) = public.ec1.f1)
+ -> Merge Append
+ Sort Key: (((public.ec1.ff + 2) + 1))
+ -> Index Scan using ec1_expr2 on ec1
+ -> Index Scan using ec1_expr3 on ec1
+ -> Index Scan using ec1_expr4 on ec1
+ -> Materialize
+ -> Sort
+ Sort Key: public.ec1.f1
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = 42::bigint)
+(20 rows)
+
+-- check partially indexed scan
+set enable_nestloop = on;
+set enable_mergejoin = off;
+drop index ec1_expr3;
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+ QUERY PLAN
+--------------------------------------------------------------
+ Nested Loop
+ Join Filter: ((((public.ec1.ff + 2) + 1)) = public.ec1.f1)
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = 42::bigint)
+ -> Append
+ -> Append
+ -> Seq Scan on ec1
+ -> Seq Scan on ec1
+ -> Index Scan using ec1_expr4 on ec1
+ Index Cond: ((ff + 4) = public.ec1.f1)
+(10 rows)
+
+-- let's try that as a mergejoin
+set enable_mergejoin = on;
+set enable_nestloop = off;
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+ QUERY PLAN
+-------------------------------------------------------------
+ Merge Join
+ Merge Cond: ((((public.ec1.ff + 2) + 1)) = public.ec1.f1)
+ -> Merge Append
+ Sort Key: (((public.ec1.ff + 2) + 1))
+ -> Index Scan using ec1_expr2 on ec1
+ -> Sort
+ Sort Key: (((public.ec1.ff + 3) + 1))
+ -> Seq Scan on ec1
+ -> Index Scan using ec1_expr4 on ec1
+ -> Materialize
+ -> Sort
+ Sort Key: public.ec1.f1
+ -> Index Scan using ec1_pkey on ec1
+ Index Cond: (ff = 42::bigint)
+(14 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index b1d68e90555..0fcefa10c1b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -92,7 +92,7 @@ test: rules
# ----------
# Another group of parallel tests
# ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock
+test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock equivclass
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index ac0c2071d29..1dd4d5087cf 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -107,6 +107,7 @@ test: window
test: xmlmap
test: functional_deps
test: advisory_lock
+test: equivclass
test: plancache
test: limit
test: plpgsql
diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql
new file mode 100644
index 00000000000..17fad673e92
--- /dev/null
+++ b/src/test/regress/sql/equivclass.sql
@@ -0,0 +1,224 @@
+--
+-- Tests for the planner's "equivalence class" mechanism
+--
+
+-- One thing that's not tested well during normal querying is the logic
+-- for handling "broken" ECs. This is because an EC can only become broken
+-- if its underlying btree operator family doesn't include a complete set
+-- of cross-type equality operators. There are not (and should not be)
+-- any such families built into Postgres; so we have to hack things up
+-- to create one. We do this by making two alias types that are really
+-- int8 (so we need no new C code) and adding only some operators for them
+-- into the standard integer_ops opfamily.
+
+create type int8alias1;
+create function int8alias1in(cstring) returns int8alias1
+ strict immutable language internal as 'int8in';
+create function int8alias1out(int8alias1) returns cstring
+ strict immutable language internal as 'int8out';
+create type int8alias1 (
+ input = int8alias1in,
+ output = int8alias1out,
+ like = int8
+);
+
+create type int8alias2;
+create function int8alias2in(cstring) returns int8alias2
+ strict immutable language internal as 'int8in';
+create function int8alias2out(int8alias2) returns cstring
+ strict immutable language internal as 'int8out';
+create type int8alias2 (
+ input = int8alias2in,
+ output = int8alias2out,
+ like = int8
+);
+
+create cast (int8 as int8alias1) without function;
+create cast (int8 as int8alias2) without function;
+create cast (int8alias1 as int8) without function;
+create cast (int8alias2 as int8) without function;
+
+create function int8alias1eq(int8alias1, int8alias1) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8alias1, rightarg = int8alias1,
+ commutator = =,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias1, int8alias1);
+
+create function int8alias2eq(int8alias2, int8alias2) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias2eq,
+ leftarg = int8alias2, rightarg = int8alias2,
+ commutator = =,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias2, int8alias2);
+
+create function int8alias1eq(int8, int8alias1) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8, rightarg = int8alias1,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8, int8alias1);
+
+create function int8alias1eq(int8alias1, int8alias2) returns bool
+ strict immutable language internal as 'int8eq';
+create operator = (
+ procedure = int8alias1eq,
+ leftarg = int8alias1, rightarg = int8alias2,
+ restrict = eqsel, join = eqjoinsel,
+ merges
+);
+alter operator family integer_ops using btree add
+ operator 3 = (int8alias1, int8alias2);
+
+create function int8alias1lt(int8alias1, int8alias1) returns bool
+ strict immutable language internal as 'int8lt';
+create operator < (
+ procedure = int8alias1lt,
+ leftarg = int8alias1, rightarg = int8alias1
+);
+alter operator family integer_ops using btree add
+ operator 1 < (int8alias1, int8alias1);
+
+create function int8alias1cmp(int8, int8alias1) returns int
+ strict immutable language internal as 'btint8cmp';
+alter operator family integer_ops using btree add
+ function 1 int8alias1cmp (int8, int8alias1);
+
+create table ec0 (ff int8 primary key, f1 int8, f2 int8);
+create table ec1 (ff int8 primary key, f1 int8alias1, f2 int8alias2);
+create table ec2 (xf int8 primary key, x1 int8alias1, x2 int8alias2);
+
+-- for the moment we only want to look at nestloop plans
+set enable_hashjoin = off;
+set enable_mergejoin = off;
+
+--
+-- Note that for cases where there's a missing operator, we don't care so
+-- much whether the plan is ideal as that we don't fail or generate an
+-- outright incorrect plan.
+--
+
+explain (costs off)
+ select * from ec0 where ff = f1 and f1 = '42'::int8;
+explain (costs off)
+ select * from ec0 where ff = f1 and f1 = '42'::int8alias1;
+explain (costs off)
+ select * from ec1 where ff = f1 and f1 = '42'::int8alias1;
+explain (costs off)
+ select * from ec1 where ff = f1 and f1 = '42'::int8alias2;
+
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and ff = '42'::int8;
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and ff = '42'::int8alias1;
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and '42'::int8 = x1;
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and x1 = '42'::int8alias1;
+explain (costs off)
+ select * from ec1, ec2 where ff = x1 and x1 = '42'::int8alias2;
+
+create unique index ec1_expr1 on ec1((ff + 1));
+create unique index ec1_expr2 on ec1((ff + 2 + 1));
+create unique index ec1_expr3 on ec1((ff + 3 + 1));
+create unique index ec1_expr4 on ec1((ff + 4));
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8 and ec1.ff = ec1.f1;
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss2
+ where ss1.x = ec1.f1 and ss1.x = ss2.x and ec1.ff = 42::int8;
+
+-- let's try that as a mergejoin
+set enable_mergejoin = on;
+set enable_nestloop = off;
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss2
+ where ss1.x = ec1.f1 and ss1.x = ss2.x and ec1.ff = 42::int8;
+
+-- check partially indexed scan
+set enable_nestloop = on;
+set enable_mergejoin = off;
+
+drop index ec1_expr3;
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+
+-- let's try that as a mergejoin
+set enable_mergejoin = on;
+set enable_nestloop = off;
+
+explain (costs off)
+ select * from ec1,
+ (select ff + 1 as x from
+ (select ff + 2 as ff from ec1
+ union all
+ select ff + 3 as ff from ec1) ss0
+ union all
+ select ff + 4 as x from ec1) as ss1
+ where ss1.x = ec1.f1 and ec1.ff = 42::int8;