mirror of
https://github.com/postgres/postgres.git
synced 2025-05-30 00:02:11 -04:00
Revert: Remove useless self-joins
This commit reverts d3d55ce5713 and subsequent fixes 2b26a694554, 93c85db3b5b, b44a1708abe, b7f315c9d7d, 8a8ed916f73, b5fb6736ed3, 0a93f803f45, e0477837ce4, a7928a57b9f, 5ef34a8fc38, 30b4955a466, 8c441c08279, 028b15405b4, fe093994db4, 489072ab7a9, and 466979ef031. 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:
parent
81b2252e60
commit
d1d286d83c
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user