mirror of
https://github.com/postgres/postgres.git
synced 2025-06-10 09:21:54 +03:00
Revert: Remove useless self-joins
This commit revertsd3d55ce571
and subsequent fixes2b26a69455
,93c85db3b5
,b44a1708ab
,b7f315c9d7
,8a8ed916f7
,b5fb6736ed
,0a93f803f4
,e0477837ce
,a7928a57b9
,5ef34a8fc3
,30b4955a46
,8c441c0827
,028b15405b
,fe093994db
,489072ab7a
, and466979ef03
. We are quite late in the release cycle and new bugs continue to appear. Even though we have fixes for all known bugs, there is a risk of throwing many bugs to end users. The plan for self-join elimination would be to do more review and testing, then re-commit in the early v18 cycle. Reported-by: Tom Lane Discussion: https://postgr.es/m/2422119.1714691974%40sss.pgh.pa.us
This commit is contained in:
@ -5586,22 +5586,6 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="guc-enable_self_join_removal" xreflabel="enable_self_join_removal">
|
||||
<term><varname>enable_self_join_removal</varname> (<type>boolean</type>)
|
||||
<indexterm>
|
||||
<primary><varname>enable_self_join_removal</varname> configuration parameter</primary>
|
||||
</indexterm>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Enables or disables the query planner's optimization which analyses
|
||||
the query tree and replaces self joins with semantically equivalent
|
||||
single scans. Takes into consideration only plain tables.
|
||||
The default is <literal>on</literal>.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="guc-enable-seqscan" xreflabel="enable_seqscan">
|
||||
<term><varname>enable_seqscan</varname> (<type>boolean</type>)
|
||||
<indexterm>
|
||||
|
@ -3440,22 +3440,6 @@ bool
|
||||
relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
|
||||
List *restrictlist,
|
||||
List *exprlist, List *oprlist)
|
||||
{
|
||||
return relation_has_unique_index_ext(root, rel, restrictlist,
|
||||
exprlist, oprlist, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* relation_has_unique_index_ext
|
||||
* Same as relation_has_unique_index_for(), but supports extra_clauses
|
||||
* parameter. If extra_clauses isn't NULL, return baserestrictinfo clauses
|
||||
* which were used to derive uniqueness.
|
||||
*/
|
||||
bool
|
||||
relation_has_unique_index_ext(PlannerInfo *root, RelOptInfo *rel,
|
||||
List *restrictlist,
|
||||
List *exprlist, List *oprlist,
|
||||
List **extra_clauses)
|
||||
{
|
||||
ListCell *ic;
|
||||
|
||||
@ -3511,7 +3495,6 @@ relation_has_unique_index_ext(PlannerInfo *root, RelOptInfo *rel,
|
||||
{
|
||||
IndexOptInfo *ind = (IndexOptInfo *) lfirst(ic);
|
||||
int c;
|
||||
List *exprs = NIL;
|
||||
|
||||
/*
|
||||
* If the index is not unique, or not immediately enforced, or if it's
|
||||
@ -3563,24 +3546,6 @@ relation_has_unique_index_ext(PlannerInfo *root, RelOptInfo *rel,
|
||||
if (match_index_to_operand(rexpr, c, ind))
|
||||
{
|
||||
matched = true; /* column is unique */
|
||||
|
||||
if (bms_membership(rinfo->clause_relids) == BMS_SINGLETON)
|
||||
{
|
||||
MemoryContext oldMemCtx =
|
||||
MemoryContextSwitchTo(root->planner_cxt);
|
||||
|
||||
/*
|
||||
* Add filter clause into a list allowing caller to
|
||||
* know if uniqueness have made not only by join
|
||||
* clauses.
|
||||
*/
|
||||
Assert(bms_is_empty(rinfo->left_relids) ||
|
||||
bms_is_empty(rinfo->right_relids));
|
||||
if (extra_clauses)
|
||||
exprs = lappend(exprs, rinfo);
|
||||
MemoryContextSwitchTo(oldMemCtx);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -3623,11 +3588,7 @@ relation_has_unique_index_ext(PlannerInfo *root, RelOptInfo *rel,
|
||||
|
||||
/* Matched all key columns of this index? */
|
||||
if (c == ind->nkeycolumns)
|
||||
{
|
||||
if (extra_clauses)
|
||||
*extra_clauses = exprs;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -230,11 +230,6 @@ query_planner(PlannerInfo *root,
|
||||
*/
|
||||
reduce_unique_semijoins(root);
|
||||
|
||||
/*
|
||||
* Remove self joins on a unique column.
|
||||
*/
|
||||
joinlist = remove_useless_self_joins(root, joinlist);
|
||||
|
||||
/*
|
||||
* Now distribute "placeholders" to base rels as needed. This has to be
|
||||
* done after join removal because removal could change whether a
|
||||
|
@ -986,16 +986,6 @@ struct config_bool ConfigureNamesBool[] =
|
||||
true,
|
||||
NULL, NULL, NULL
|
||||
},
|
||||
{
|
||||
{"enable_self_join_removal", PGC_USERSET, QUERY_TUNING_METHOD,
|
||||
gettext_noop("Enable removal of unique self-joins."),
|
||||
NULL,
|
||||
GUC_EXPLAIN | GUC_NOT_IN_SAMPLE
|
||||
},
|
||||
&enable_self_join_removal,
|
||||
true,
|
||||
NULL, NULL, NULL
|
||||
},
|
||||
{
|
||||
{"enable_group_by_reordering", PGC_USERSET, QUERY_TUNING_METHOD,
|
||||
gettext_noop("Enables reordering of GROUP BY keys."),
|
||||
|
@ -724,7 +724,7 @@ typedef struct PartitionSchemeData *PartitionScheme;
|
||||
* populate these fields, for base rels; but someday they might be used for
|
||||
* join rels too:
|
||||
*
|
||||
* unique_for_rels - list of UniqueRelInfo, each one being a set of other
|
||||
* unique_for_rels - list of Relid sets, each one being a set of other
|
||||
* rels for which this one has been proven unique
|
||||
* non_unique_for_rels - list of Relid sets, each one being a set of
|
||||
* other rels for which we have tried and failed to prove
|
||||
@ -963,7 +963,7 @@ typedef struct RelOptInfo
|
||||
/*
|
||||
* cache space for remembering if we have proven this relation unique
|
||||
*/
|
||||
/* known unique for these other relid set(s) given in UniqueRelInfo(s) */
|
||||
/* known unique for these other relid set(s) */
|
||||
List *unique_for_rels;
|
||||
/* known not unique for these set(s) */
|
||||
List *non_unique_for_rels;
|
||||
@ -3421,35 +3421,4 @@ typedef struct AggTransInfo
|
||||
bool initValueIsNull;
|
||||
} AggTransInfo;
|
||||
|
||||
/*
|
||||
* UniqueRelInfo caches a fact that a relation is unique when being joined
|
||||
* to other relation(s).
|
||||
*/
|
||||
typedef struct UniqueRelInfo
|
||||
{
|
||||
pg_node_attr(no_copy_equal, no_read, no_query_jumble)
|
||||
|
||||
NodeTag type;
|
||||
|
||||
/*
|
||||
* The relation in consideration is unique when being joined with this set
|
||||
* of other relation(s).
|
||||
*/
|
||||
Relids outerrelids;
|
||||
|
||||
/*
|
||||
* The relation in consideration is unique when considering only clauses
|
||||
* suitable for self-join (passed split_selfjoin_quals()).
|
||||
*/
|
||||
bool self_join;
|
||||
|
||||
/*
|
||||
* Additional clauses from a baserestrictinfo list that were used to prove
|
||||
* the uniqueness. We cache it for the self-join checking procedure: a
|
||||
* self-join can be removed if the outer relation contains strictly the
|
||||
* same set of clauses.
|
||||
*/
|
||||
List *extra_clauses;
|
||||
} UniqueRelInfo;
|
||||
|
||||
#endif /* PATHNODES_H */
|
||||
|
@ -72,9 +72,6 @@ extern void create_index_paths(PlannerInfo *root, RelOptInfo *rel);
|
||||
extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
|
||||
List *restrictlist,
|
||||
List *exprlist, List *oprlist);
|
||||
extern bool relation_has_unique_index_ext(PlannerInfo *root, RelOptInfo *rel,
|
||||
List *restrictlist, List *exprlist,
|
||||
List *oprlist, List **extra_clauses);
|
||||
extern bool indexcol_is_bool_constant_for_query(PlannerInfo *root,
|
||||
IndexOptInfo *index,
|
||||
int indexcol);
|
||||
|
@ -20,7 +20,6 @@
|
||||
/* GUC parameters */
|
||||
#define DEFAULT_CURSOR_TUPLE_FRACTION 0.1
|
||||
extern PGDLLIMPORT double cursor_tuple_fraction;
|
||||
extern PGDLLIMPORT bool enable_self_join_removal;
|
||||
|
||||
/* query_planner callback to compute query_pathkeys */
|
||||
typedef void (*query_pathkeys_callback) (PlannerInfo *root, void *extra);
|
||||
@ -109,11 +108,6 @@ extern bool query_is_distinct_for(Query *query, List *colnos, List *opids);
|
||||
extern bool innerrel_is_unique(PlannerInfo *root,
|
||||
Relids joinrelids, Relids outerrelids, RelOptInfo *innerrel,
|
||||
JoinType jointype, List *restrictlist, bool force_cache);
|
||||
extern bool innerrel_is_unique_ext(PlannerInfo *root, Relids joinrelids,
|
||||
Relids outerrelids, RelOptInfo *innerrel,
|
||||
JoinType jointype, List *restrictlist,
|
||||
bool force_cache, List **uclauses);
|
||||
extern List *remove_useless_self_joins(PlannerInfo *root, List *jointree);
|
||||
|
||||
/*
|
||||
* prototypes for plan/setrefs.c
|
||||
|
@ -430,36 +430,6 @@ explain (costs off)
|
||||
Filter: ((unique1 IS NOT NULL) AND (unique2 IS NOT NULL))
|
||||
(2 rows)
|
||||
|
||||
-- Test that broken ECs are processed correctly during self join removal.
|
||||
-- Disable merge joins so that we don't get an error about missing commutator.
|
||||
-- Test both orientations of the join clause, because only one of them breaks
|
||||
-- the EC.
|
||||
set enable_mergejoin to off;
|
||||
explain (costs off)
|
||||
select * from ec0 m join ec0 n on m.ff = n.ff
|
||||
join ec1 p on m.ff + n.ff = p.f1;
|
||||
QUERY PLAN
|
||||
---------------------------------------
|
||||
Nested Loop
|
||||
Join Filter: ((n.ff + n.ff) = p.f1)
|
||||
-> Seq Scan on ec0 n
|
||||
-> Materialize
|
||||
-> Seq Scan on ec1 p
|
||||
(5 rows)
|
||||
|
||||
explain (costs off)
|
||||
select * from ec0 m join ec0 n on m.ff = n.ff
|
||||
join ec1 p on p.f1::int8 = (m.ff + n.ff)::int8alias1;
|
||||
QUERY PLAN
|
||||
---------------------------------------------------------------
|
||||
Nested Loop
|
||||
Join Filter: ((p.f1)::bigint = ((n.ff + n.ff))::int8alias1)
|
||||
-> Seq Scan on ec0 n
|
||||
-> Materialize
|
||||
-> Seq Scan on ec1 p
|
||||
(5 rows)
|
||||
|
||||
reset enable_mergejoin;
|
||||
-- this could be converted, but isn't at present
|
||||
explain (costs off)
|
||||
select * from tenk1 where unique1 = unique1 or unique2 = unique2;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -153,11 +153,10 @@ select name, setting from pg_settings where name like 'enable%';
|
||||
enable_partitionwise_aggregate | off
|
||||
enable_partitionwise_join | off
|
||||
enable_presorted_aggregate | on
|
||||
enable_self_join_removal | on
|
||||
enable_seqscan | on
|
||||
enable_sort | on
|
||||
enable_tidscan | on
|
||||
(23 rows)
|
||||
(22 rows)
|
||||
|
||||
-- There are always wait event descriptions for various types.
|
||||
select type, count(*) > 0 as ok FROM pg_wait_events
|
||||
|
@ -259,22 +259,6 @@ drop user regress_user_ectest;
|
||||
explain (costs off)
|
||||
select * from tenk1 where unique1 = unique1 and unique2 = unique2;
|
||||
|
||||
-- Test that broken ECs are processed correctly during self join removal.
|
||||
-- Disable merge joins so that we don't get an error about missing commutator.
|
||||
-- Test both orientations of the join clause, because only one of them breaks
|
||||
-- the EC.
|
||||
set enable_mergejoin to off;
|
||||
|
||||
explain (costs off)
|
||||
select * from ec0 m join ec0 n on m.ff = n.ff
|
||||
join ec1 p on m.ff + n.ff = p.f1;
|
||||
|
||||
explain (costs off)
|
||||
select * from ec0 m join ec0 n on m.ff = n.ff
|
||||
join ec1 p on p.f1::int8 = (m.ff + n.ff)::int8alias1;
|
||||
|
||||
reset enable_mergejoin;
|
||||
|
||||
-- this could be converted, but isn't at present
|
||||
explain (costs off)
|
||||
select * from tenk1 where unique1 = unique1 or unique2 = unique2;
|
||||
|
@ -2321,476 +2321,6 @@ select * from
|
||||
select * from
|
||||
int8_tbl x join (int4_tbl x cross join int4_tbl y(ff)) j on q1 = f1; -- ok
|
||||
|
||||
--
|
||||
-- test that semi- or inner self-joins on a unique column are removed
|
||||
--
|
||||
|
||||
-- enable only nestloop to get more predictable plans
|
||||
set enable_hashjoin to off;
|
||||
set enable_mergejoin to off;
|
||||
|
||||
create table sj (a int unique, b int, c int unique);
|
||||
insert into sj values (1, null, 2), (null, 2, null), (2, 1, 1);
|
||||
analyze sj;
|
||||
|
||||
-- Trivial self-join case.
|
||||
explain (costs off)
|
||||
select p.* from sj p, sj q where q.a = p.a and q.b = q.a - 1;
|
||||
select p.* from sj p, sj q where q.a = p.a and q.b = q.a - 1;
|
||||
|
||||
-- Self-join removal performs after a subquery pull-up process and could remove
|
||||
-- such kind of self-join too. Check this option.
|
||||
explain (costs off)
|
||||
select * from sj p
|
||||
where exists (select * from sj q
|
||||
where q.a = p.a and q.b < 10);
|
||||
select * from sj p
|
||||
where exists (select * from sj q
|
||||
where q.a = p.a and q.b < 10);
|
||||
|
||||
-- Don't remove self-join for the case of equality of two different unique columns.
|
||||
explain (costs off)
|
||||
select * from sj t1, sj t2 where t1.a = t2.c and t1.b is not null;
|
||||
|
||||
-- Degenerated case.
|
||||
explain (costs off)
|
||||
select * from
|
||||
(select a as x from sj where false) as q1,
|
||||
(select a as y from sj where false) as q2
|
||||
where q1.x = q2.y;
|
||||
|
||||
-- We can't use a cross-EC generated self join qual because of current logic of
|
||||
-- the generate_join_implied_equalities routine.
|
||||
explain (costs off)
|
||||
select * from sj t1, sj t2 where t1.a = t1.b and t1.b = t2.b and t2.b = t2.a;
|
||||
explain (costs off)
|
||||
select * from sj t1, sj t2, sj t3
|
||||
where t1.a = t1.b and t1.b = t2.b and t2.b = t2.a and
|
||||
t1.b = t3.b and t3.b = t3.a;
|
||||
|
||||
-- Double self-join removal.
|
||||
-- Use a condition on "b + 1", not on "b", for the second join, so that
|
||||
-- the equivalence class is different from the first one, and we can
|
||||
-- test the non-ec code path.
|
||||
explain (costs off)
|
||||
select *
|
||||
from sj t1
|
||||
join sj t2 on t1.a = t2.a and t1.b = t2.b
|
||||
join sj t3 on t2.a = t3.a and t2.b + 1 = t3.b + 1;
|
||||
|
||||
-- subselect that references the removed relation
|
||||
explain (costs off)
|
||||
select t1.a, (select a from sj where a = t2.a and a = t1.a)
|
||||
from sj t1, sj t2
|
||||
where t1.a = t2.a;
|
||||
|
||||
-- self-join under outer join
|
||||
explain (costs off)
|
||||
select * from sj x join sj y on x.a = y.a
|
||||
left join int8_tbl z on x.a = z.q1;
|
||||
|
||||
explain (costs off)
|
||||
select * from sj x join sj y on x.a = y.a
|
||||
left join int8_tbl z on y.a = z.q1;
|
||||
|
||||
explain (costs off)
|
||||
select * from (
|
||||
select t1.*, t2.a as ax from sj t1 join sj t2
|
||||
on (t1.a = t2.a and t1.c * t1.c = t2.c + 2 and t2.b is null)
|
||||
) as q1
|
||||
left join
|
||||
(select t3.* from sj t3, sj t4 where t3.c = t4.c) as q2
|
||||
on q1.ax = q2.a;
|
||||
|
||||
-- Test that placeholders are updated correctly after join removal
|
||||
explain (costs off)
|
||||
select * from (values (1)) x
|
||||
left join (select coalesce(y.q1, 1) from int8_tbl y
|
||||
right join sj j1 inner join sj j2 on j1.a = j2.a
|
||||
on true) z
|
||||
on true;
|
||||
|
||||
-- Test that references to the removed rel in lateral subqueries are replaced
|
||||
-- correctly after join removal
|
||||
explain (verbose, costs off)
|
||||
select t3.a from sj t1
|
||||
join sj t2 on t1.a = t2.a
|
||||
join lateral (select t1.a offset 0) t3 on true;
|
||||
|
||||
explain (verbose, costs off)
|
||||
select t3.a from sj t1
|
||||
join sj t2 on t1.a = t2.a
|
||||
join lateral (select * from (select t1.a offset 0) offset 0) t3 on true;
|
||||
|
||||
explain (verbose, costs off)
|
||||
select t4.a from sj t1
|
||||
join sj t2 on t1.a = t2.a
|
||||
join lateral (select t3.a from sj t3, (select t1.a) offset 0) t4 on true;
|
||||
|
||||
-- Check updating of Lateral links from top-level query to the removing relation
|
||||
explain (COSTS OFF)
|
||||
SELECT * FROM pg_am am WHERE am.amname IN (
|
||||
SELECT c1.relname AS relname
|
||||
FROM pg_class c1
|
||||
JOIN pg_class c2
|
||||
ON c1.oid=c2.oid AND c1.oid < 10
|
||||
);
|
||||
|
||||
--
|
||||
-- SJE corner case: uniqueness of an inner is [partially] derived from
|
||||
-- baserestrictinfo clauses.
|
||||
-- XXX: We really should allow SJE for these corner cases?
|
||||
--
|
||||
|
||||
INSERT INTO sj VALUES (3, 1, 3);
|
||||
|
||||
-- Don't remove SJ
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM sj j1, sj j2 WHERE j1.b = j2.b AND j1.a = 2 AND j2.a = 3;
|
||||
-- Return one row
|
||||
SELECT * FROM sj j1, sj j2 WHERE j1.b = j2.b AND j1.a = 2 AND j2.a = 3;
|
||||
|
||||
-- Remove SJ, define uniqueness by a constant
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM sj j1, sj j2 WHERE j1.b = j2.b AND j1.a = 2 AND j2.a = 2;
|
||||
-- Return one row
|
||||
SELECT * FROM sj j1, sj j2 WHERE j1.b = j2.b AND j1.a = 2 AND j2.a = 2;
|
||||
|
||||
-- Remove SJ, define uniqueness by a constant expression
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM sj j1, sj j2
|
||||
WHERE j1.b = j2.b
|
||||
AND j1.a = (EXTRACT(DOW FROM current_timestamp(0))/15 + 3)::int
|
||||
AND (EXTRACT(DOW FROM current_timestamp(0))/15 + 3)::int = j2.a;
|
||||
-- Return one row
|
||||
SELECT * FROM sj j1, sj j2
|
||||
WHERE j1.b = j2.b
|
||||
AND j1.a = (EXTRACT(DOW FROM current_timestamp(0))/15 + 3)::int
|
||||
AND (EXTRACT(DOW FROM current_timestamp(0))/15 + 3)::int = j2.a;
|
||||
|
||||
-- Remove SJ
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM sj j1, sj j2 WHERE j1.b = j2.b AND j1.a = 1 AND j2.a = 1;
|
||||
-- Return no rows
|
||||
SELECT * FROM sj j1, sj j2 WHERE j1.b = j2.b AND j1.a = 1 AND j2.a = 1;
|
||||
|
||||
-- Shuffle a clause. Remove SJ
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM sj j1, sj j2 WHERE j1.b = j2.b AND 1 = j1.a AND j2.a = 1;
|
||||
-- Return no rows
|
||||
SELECT * FROM sj j1, sj j2 WHERE j1.b = j2.b AND 1 = j1.a AND j2.a = 1;
|
||||
|
||||
-- SJE Corner case: a 'a.x=a.x' clause, have replaced with 'a.x IS NOT NULL'
|
||||
-- after SJ elimination it shouldn't be a mergejoinable clause.
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT t4.*
|
||||
FROM (SELECT t1.*, t2.a AS a1 FROM sj t1, sj t2 WHERE t1.b = t2.b) AS t3
|
||||
JOIN sj t4 ON (t4.a = t3.a) WHERE t3.a1 = 42;
|
||||
SELECT t4.*
|
||||
FROM (SELECT t1.*, t2.a AS a1 FROM sj t1, sj t2 WHERE t1.b = t2.b) AS t3
|
||||
JOIN sj t4 ON (t4.a = t3.a) WHERE t3.a1 = 42;
|
||||
|
||||
-- Functional index
|
||||
CREATE UNIQUE INDEX sj_fn_idx ON sj((a * a));
|
||||
|
||||
-- Remove SJ
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM sj j1, sj j2
|
||||
WHERE j1.b = j2.b AND j1.a*j1.a = 1 AND j2.a*j2.a = 1;
|
||||
-- Don't remove SJ
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM sj j1, sj j2
|
||||
WHERE j1.b = j2.b AND j1.a*j1.a = 1 AND j2.a*j2.a = 2;
|
||||
|
||||
-- Restriction contains expressions in both sides, Remove SJ.
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM sj j1, sj j2
|
||||
WHERE j1.b = j2.b
|
||||
AND (j1.a*j1.a) = (EXTRACT(DOW FROM current_timestamp(0))/15 + 3)::int
|
||||
AND (EXTRACT(DOW FROM current_timestamp(0))/15 + 3)::int = (j2.a*j2.a);
|
||||
-- Empty set of rows should be returned
|
||||
SELECT * FROM sj j1, sj j2
|
||||
WHERE j1.b = j2.b
|
||||
AND (j1.a*j1.a) = (EXTRACT(DOW FROM current_timestamp(0))/15 + 3)::int
|
||||
AND (EXTRACT(DOW FROM current_timestamp(0))/15 + 3)::int = (j2.a*j2.a);
|
||||
|
||||
-- Restriction contains volatile function - disable SJE feature.
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM sj j1, sj j2
|
||||
WHERE j1.b = j2.b
|
||||
AND (j1.a*j1.c/3) = (random()/3 + 3)::int
|
||||
AND (random()/3 + 3)::int = (j2.a*j2.c/3);
|
||||
-- Return one row
|
||||
SELECT * FROM sj j1, sj j2
|
||||
WHERE j1.b = j2.b
|
||||
AND (j1.a*j1.c/3) = (random()/3 + 3)::int
|
||||
AND (random()/3 + 3)::int = (j2.a*j2.c/3);
|
||||
|
||||
-- Multiple filters
|
||||
CREATE UNIQUE INDEX sj_temp_idx1 ON sj(a,b,c);
|
||||
|
||||
-- Remove SJ
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM sj j1, sj j2
|
||||
WHERE j1.b = j2.b AND j1.a = 2 AND j1.c = 3 AND j2.a = 2 AND 3 = j2.c;
|
||||
|
||||
-- Don't remove SJ
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM sj j1, sj j2
|
||||
WHERE j1.b = j2.b AND 2 = j1.a AND j1.c = 3 AND j2.a = 1 AND 3 = j2.c;
|
||||
|
||||
CREATE UNIQUE INDEX sj_temp_idx ON sj(a,b);
|
||||
|
||||
-- Don't remove SJ
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM sj j1, sj j2 WHERE j1.b = j2.b AND j1.a = 2;
|
||||
|
||||
-- Don't remove SJ
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM sj j1, sj j2 WHERE j1.b = j2.b AND 2 = j2.a;
|
||||
|
||||
-- Don't remove SJ
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM sj j1, sj j2 WHERE j1.b = j2.b AND (j1.a = 1 OR j2.a = 1);
|
||||
|
||||
DROP INDEX sj_fn_idx, sj_temp_idx1, sj_temp_idx;
|
||||
|
||||
-- Test that OR predicated are updated correctly after join removal
|
||||
CREATE TABLE tab_with_flag ( id INT PRIMARY KEY, is_flag SMALLINT);
|
||||
CREATE INDEX idx_test_is_flag ON tab_with_flag (is_flag);
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT COUNT(*) FROM tab_with_flag
|
||||
WHERE
|
||||
(is_flag IS NULL OR is_flag = 0)
|
||||
AND id IN (SELECT id FROM tab_with_flag WHERE id IN (2, 3));
|
||||
DROP TABLE tab_with_flag;
|
||||
|
||||
-- HAVING clause
|
||||
explain (costs off)
|
||||
select p.b from sj p join sj q on p.a = q.a group by p.b having sum(p.a) = 1;
|
||||
|
||||
-- update lateral references and range table entry reference
|
||||
explain (verbose, costs off)
|
||||
select 1 from (select x.* from sj x, sj y where x.a = y.a) q,
|
||||
lateral generate_series(1, q.a) gs(i);
|
||||
|
||||
explain (verbose, costs off)
|
||||
select 1 from (select y.* from sj x, sj y where x.a = y.a) q,
|
||||
lateral generate_series(1, q.a) gs(i);
|
||||
|
||||
-- Test that a non-EC-derived join clause is processed correctly. Use an
|
||||
-- outer join so that we can't form an EC.
|
||||
explain (costs off) select * from sj p join sj q on p.a = q.a
|
||||
left join sj r on p.a + q.a = r.a;
|
||||
|
||||
-- FIXME this constant false filter doesn't look good. Should we merge
|
||||
-- equivalence classes?
|
||||
explain (costs off)
|
||||
select * from sj p, sj q where p.a = q.a and p.b = 1 and q.b = 2;
|
||||
|
||||
-- Check that attr_needed is updated correctly after self-join removal. In this
|
||||
-- test, the join of j1 with j2 is removed. k1.b is required at either j1 or j2.
|
||||
-- If this info is lost, join targetlist for (k1, k2) will not contain k1.b.
|
||||
-- Use index scan for k1 so that we don't get 'b' from physical tlist used for
|
||||
-- seqscan. Also disable reordering of joins because this test depends on a
|
||||
-- particular join tree.
|
||||
create table sk (a int, b int);
|
||||
create index on sk(a);
|
||||
set join_collapse_limit to 1;
|
||||
set enable_seqscan to off;
|
||||
explain (costs off) select 1 from
|
||||
(sk k1 join sk k2 on k1.a = k2.a)
|
||||
join (sj j1 join sj j2 on j1.a = j2.a) on j1.b = k1.b;
|
||||
explain (costs off) select 1 from
|
||||
(sk k1 join sk k2 on k1.a = k2.a)
|
||||
join (sj j1 join sj j2 on j1.a = j2.a) on j2.b = k1.b;
|
||||
reset join_collapse_limit;
|
||||
reset enable_seqscan;
|
||||
|
||||
-- Check that clauses from the join filter list is not lost on the self-join removal
|
||||
CREATE TABLE emp1 (id SERIAL PRIMARY KEY NOT NULL, code int);
|
||||
EXPLAIN (VERBOSE, COSTS OFF)
|
||||
SELECT * FROM emp1 e1, emp1 e2 WHERE e1.id = e2.id AND e2.code <> e1.code;
|
||||
|
||||
-- Shuffle self-joined relations. Only in the case of iterative deletion
|
||||
-- attempts explains of these queries will be identical.
|
||||
CREATE UNIQUE INDEX ON emp1((id*id));
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT count(*) FROM emp1 c1, emp1 c2, emp1 c3
|
||||
WHERE c1.id=c2.id AND c1.id*c2.id=c3.id*c3.id;
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT count(*) FROM emp1 c1, emp1 c2, emp1 c3
|
||||
WHERE c1.id=c3.id AND c1.id*c3.id=c2.id*c2.id;
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT count(*) FROM emp1 c1, emp1 c2, emp1 c3
|
||||
WHERE c3.id=c2.id AND c3.id*c2.id=c1.id*c1.id;
|
||||
|
||||
-- Check the usage of a parse tree by the set operations (bug #18170)
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT c1.code FROM emp1 c1 LEFT JOIN emp1 c2 ON c1.id = c2.id
|
||||
WHERE c2.id IS NOT NULL
|
||||
EXCEPT ALL
|
||||
SELECT c3.code FROM emp1 c3;
|
||||
|
||||
-- Check that SJE removes references from PHVs correctly
|
||||
explain (costs off)
|
||||
select * from emp1 t1 left join
|
||||
(select coalesce(t3.code, 1) from emp1 t2
|
||||
left join (emp1 t3 join emp1 t4 on t3.id = t4.id)
|
||||
on true)
|
||||
on true;
|
||||
|
||||
-- Check that SJE removes the whole PHVs correctly
|
||||
explain (verbose, costs off)
|
||||
select 1 from emp1 t1 left join
|
||||
((select 1 as x, * from emp1 t2) s1 inner join
|
||||
(select * from emp1 t3) s2 on s1.id = s2.id)
|
||||
on true
|
||||
where s1.x = 1;
|
||||
|
||||
-- Check that PHVs do not impose any constraints on removing self joins
|
||||
explain (verbose, costs off)
|
||||
select * from emp1 t1 join emp1 t2 on t1.id = t2.id left join
|
||||
lateral (select t1.id as t1id, * from generate_series(1,1) t3) s on true;
|
||||
|
||||
explain (verbose, costs off)
|
||||
select * from generate_series(1,10) t1(id) left join
|
||||
lateral (select t1.id as t1id, t2.id from emp1 t2 join emp1 t3 on t2.id = t3.id)
|
||||
on true;
|
||||
|
||||
-- Check that SJE replaces join clauses involving the removed rel correctly
|
||||
explain (costs off)
|
||||
select * from emp1 t1
|
||||
inner join emp1 t2 on t1.id = t2.id
|
||||
left join emp1 t3 on t1.id > 1 and t1.id < 2;
|
||||
|
||||
-- Check that SJE doesn't replace the target relation
|
||||
EXPLAIN (COSTS OFF)
|
||||
WITH t1 AS (SELECT * FROM emp1)
|
||||
UPDATE emp1 SET code = t1.code + 1 FROM t1
|
||||
WHERE t1.id = emp1.id RETURNING emp1.id, emp1.code, t1.code;
|
||||
|
||||
INSERT INTO emp1 VALUES (1, 1), (2, 1);
|
||||
|
||||
WITH t1 AS (SELECT * FROM emp1)
|
||||
UPDATE emp1 SET code = t1.code + 1 FROM t1
|
||||
WHERE t1.id = emp1.id RETURNING emp1.id, emp1.code, t1.code;
|
||||
|
||||
TRUNCATE emp1;
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
UPDATE sj sq SET b = 1 FROM sj as sz WHERE sq.a = sz.a;
|
||||
|
||||
CREATE RULE sj_del_rule AS ON DELETE TO sj
|
||||
DO INSTEAD
|
||||
UPDATE sj SET a = 1 WHERE a = old.a;
|
||||
EXPLAIN (COSTS OFF) DELETE FROM sj;
|
||||
DROP RULE sj_del_rule ON sj CASCADE;
|
||||
|
||||
-- Check that SJE does not mistakenly omit qual clauses (bug #18187)
|
||||
insert into emp1 values (1, 1);
|
||||
explain (costs off)
|
||||
select 1 from emp1 full join
|
||||
(select * from emp1 t1 join
|
||||
emp1 t2 join emp1 t3 on t2.id = t3.id
|
||||
on true
|
||||
where false) s on true
|
||||
where false;
|
||||
select 1 from emp1 full join
|
||||
(select * from emp1 t1 join
|
||||
emp1 t2 join emp1 t3 on t2.id = t3.id
|
||||
on true
|
||||
where false) s on true
|
||||
where false;
|
||||
|
||||
-- Check that SJE does not mistakenly re-use knowledge of relation uniqueness
|
||||
-- made with different set of quals
|
||||
insert into emp1 values (2, 1);
|
||||
explain (costs off)
|
||||
select * from emp1 t1 where exists (select * from emp1 t2
|
||||
where t2.id = t1.code and t2.code > 0);
|
||||
select * from emp1 t1 where exists (select * from emp1 t2
|
||||
where t2.id = t1.code and t2.code > 0);
|
||||
|
||||
-- We can remove the join even if we find the join can't duplicate rows and
|
||||
-- the base quals of each side are different. In the following case we end up
|
||||
-- moving quals over to s1 to make it so it can't match any rows.
|
||||
create table sl(a int, b int, c int);
|
||||
create unique index on sl(a, b);
|
||||
vacuum analyze sl;
|
||||
|
||||
-- Both sides are unique, but base quals are different
|
||||
explain (costs off)
|
||||
select * from sl t1, sl t2 where t1.a = t2.a and t1.b = 1 and t2.b = 2;
|
||||
|
||||
-- Check NullTest in baserestrictinfo list
|
||||
explain (costs off)
|
||||
select * from sl t1, sl t2
|
||||
where t1.a = t2.a and t1.b = 1 and t2.b = 2
|
||||
and t1.c IS NOT NULL and t2.c IS NOT NULL
|
||||
and t2.b IS NOT NULL and t1.b IS NOT NULL
|
||||
and t1.a IS NOT NULL and t2.a IS NOT NULL;
|
||||
explain (verbose, costs off)
|
||||
select * from sl t1, sl t2
|
||||
where t1.b = t2.b and t2.a = 3 and t1.a = 3
|
||||
and t1.c IS NOT NULL and t2.c IS NOT NULL
|
||||
and t2.b IS NOT NULL and t1.b IS NOT NULL
|
||||
and t1.a IS NOT NULL and t2.a IS NOT NULL;
|
||||
|
||||
-- Join qual isn't mergejoinable, but inner is unique.
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT n2.a FROM sj n1, sj n2 WHERE n1.a <> n2.a AND n2.a = 1;
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM
|
||||
(SELECT n2.a FROM sj n1, sj n2 WHERE n1.a <> n2.a) q0, sl
|
||||
WHERE q0.a = 1;
|
||||
|
||||
-- Check optimization disabling if it will violate special join conditions.
|
||||
-- Two identical joined relations satisfies self join removal conditions but
|
||||
-- stay in different special join infos.
|
||||
CREATE TABLE sj_t1 (id serial, a int);
|
||||
CREATE TABLE sj_t2 (id serial, a int);
|
||||
CREATE TABLE sj_t3 (id serial, a int);
|
||||
CREATE TABLE sj_t4 (id serial, a int);
|
||||
|
||||
CREATE UNIQUE INDEX ON sj_t3 USING btree (a,id);
|
||||
CREATE UNIQUE INDEX ON sj_t2 USING btree (id);
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM sj_t1
|
||||
JOIN (
|
||||
SELECT sj_t2.id AS id FROM sj_t2
|
||||
WHERE EXISTS
|
||||
(
|
||||
SELECT TRUE FROM sj_t3,sj_t4 WHERE sj_t3.a = 1 AND sj_t3.id = sj_t2.id
|
||||
)
|
||||
) t2t3t4
|
||||
ON sj_t1.id = t2t3t4.id
|
||||
JOIN (
|
||||
SELECT sj_t2.id AS id FROM sj_t2
|
||||
WHERE EXISTS
|
||||
(
|
||||
SELECT TRUE FROM sj_t3,sj_t4 WHERE sj_t3.a = 1 AND sj_t3.id = sj_t2.id
|
||||
)
|
||||
) _t2t3t4
|
||||
ON sj_t1.id = _t2t3t4.id;
|
||||
|
||||
--
|
||||
-- Test RowMarks-related code
|
||||
--
|
||||
|
||||
-- Both sides have explicit LockRows marks
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT a1.a FROM sj a1,sj a2 WHERE (a1.a=a2.a) FOR UPDATE;
|
||||
|
||||
reset enable_hashjoin;
|
||||
reset enable_mergejoin;
|
||||
|
||||
--
|
||||
-- Test hints given on incorrect column references are useful
|
||||
--
|
||||
|
@ -379,7 +379,6 @@ CatalogId
|
||||
CatalogIdMapEntry
|
||||
CatalogIndexState
|
||||
ChangeVarNodes_context
|
||||
ReplaceVarnoContext
|
||||
CheckPoint
|
||||
CheckPointStmt
|
||||
CheckpointStatsData
|
||||
@ -2548,7 +2547,6 @@ SeenRelsEntry
|
||||
SelectLimit
|
||||
SelectStmt
|
||||
Selectivity
|
||||
SelfJoinCandidate
|
||||
SemTPadded
|
||||
SemiAntiJoinFactors
|
||||
SeqScan
|
||||
@ -3927,7 +3925,6 @@ unicodeStyleColumnFormat
|
||||
unicodeStyleFormat
|
||||
unicodeStyleRowFormat
|
||||
unicode_linestyle
|
||||
UniqueRelInfo
|
||||
unit_conversion
|
||||
unlogged_relation_entry
|
||||
utf_local_conversion_func
|
||||
|
Reference in New Issue
Block a user