diff options
-rw-r--r-- | src/backend/optimizer/path/equivclass.c | 10 | ||||
-rw-r--r-- | src/test/regress/expected/equivclass.out | 385 | ||||
-rw-r--r-- | src/test/regress/parallel_schedule | 2 | ||||
-rw-r--r-- | src/test/regress/serial_schedule | 1 | ||||
-rw-r--r-- | src/test/regress/sql/equivclass.sql | 224 |
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; |