Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 8ec5429

Browse files
committed
Reduce "X = X" to "X IS NOT NULL", if it's easy to do so.
If the operator is a strict btree equality operator, and X isn't volatile, then the clause must yield true for any non-null value of X, or null if X is null. At top level of a WHERE clause, we can ignore the distinction between false and null results, so it's valid to simplify the clause to "X IS NOT NULL". This is a useful improvement mainly because we'll get a far better selectivity estimate in most cases. Because such cases seldom arise in well-written queries, it is unappetizing to expend a lot of planner cycles looking for them ... but it turns out that there's a place we can shoehorn this in practically for free, because equivclass.c already has to detect and reject candidate equivalences of the form X = X. That doesn't catch every place that it would be valid to simplify to X IS NOT NULL, but it catches the typical case. Working harder doesn't seem justified. Patch by me, reviewed by Petr Jelinek Discussion: https://postgr.es/m/CAMjNa7cC4X9YR-vAJS-jSYCajhRDvJQnN7m2sLH1wLh-_Z2bsw@mail.gmail.com
1 parent b11f0d3 commit 8ec5429

File tree

5 files changed

+83
-17
lines changed

5 files changed

+83
-17
lines changed

src/backend/optimizer/path/equivclass.c

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "optimizer/paths.h"
2828
#include "optimizer/planmain.h"
2929
#include "optimizer/prep.h"
30+
#include "optimizer/restrictinfo.h"
3031
#include "optimizer/var.h"
3132
#include "utils/lsyscache.h"
3233

@@ -71,8 +72,14 @@ static bool reconsider_full_join_clause(PlannerInfo *root,
7172
* any delay by an outer join, so its two sides can be considered equal
7273
* anywhere they are both computable; moreover that equality can be
7374
* extended transitively. Record this knowledge in the EquivalenceClass
74-
* data structure. Returns TRUE if successful, FALSE if not (in which
75-
* case caller should treat the clause as ordinary, not an equivalence).
75+
* data structure, if applicable. Returns TRUE if successful, FALSE if not
76+
* (in which case caller should treat the clause as ordinary, not an
77+
* equivalence).
78+
*
79+
* In some cases, although we cannot convert a clause into EquivalenceClass
80+
* knowledge, we can still modify it to a more useful form than the original.
81+
* Then, *p_restrictinfo will be replaced by a new RestrictInfo, which is what
82+
* the caller should use for further processing.
7683
*
7784
* If below_outer_join is true, then the clause was found below the nullable
7885
* side of an outer join, so its sides might validly be both NULL rather than
@@ -104,9 +111,11 @@ static bool reconsider_full_join_clause(PlannerInfo *root,
104111
* memory context.
105112
*/
106113
bool
107-
process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo,
114+
process_equivalence(PlannerInfo *root,
115+
RestrictInfo **p_restrictinfo,
108116
bool below_outer_join)
109117
{
118+
RestrictInfo *restrictinfo = *p_restrictinfo;
110119
Expr *clause = restrictinfo->clause;
111120
Oid opno,
112121
collation,
@@ -154,16 +163,45 @@ process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo,
154163
collation);
155164

156165
/*
157-
* Reject clauses of the form X=X. These are not as redundant as they
158-
* might seem at first glance: assuming the operator is strict, this is
159-
* really an expensive way to write X IS NOT NULL. So we must not risk
160-
* just losing the clause, which would be possible if there is already a
161-
* single-element EquivalenceClass containing X. The case is not common
162-
* enough to be worth contorting the EC machinery for, so just reject the
163-
* clause and let it be processed as a normal restriction clause.
166+
* Clauses of the form X=X cannot be translated into EquivalenceClasses.
167+
* We'd either end up with a single-entry EC, losing the knowledge that
168+
* the clause was present at all, or else make an EC with duplicate
169+
* entries, causing other issues.
164170
*/
165171
if (equal(item1, item2))
166-
return false; /* X=X is not a useful equivalence */
172+
{
173+
/*
174+
* If the operator is strict, then the clause can be treated as just
175+
* "X IS NOT NULL". (Since we know we are considering a top-level
176+
* qual, we can ignore the difference between FALSE and NULL results.)
177+
* It's worth making the conversion because we'll typically get a much
178+
* better selectivity estimate than we would for X=X.
179+
*
180+
* If the operator is not strict, we can't be sure what it will do
181+
* with NULLs, so don't attempt to optimize it.
182+
*/
183+
set_opfuncid((OpExpr *) clause);
184+
if (func_strict(((OpExpr *) clause)->opfuncid))
185+
{
186+
NullTest *ntest = makeNode(NullTest);
187+
188+
ntest->arg = item1;
189+
ntest->nulltesttype = IS_NOT_NULL;
190+
ntest->argisrow = false; /* correct even if composite arg */
191+
ntest->location = -1;
192+
193+
*p_restrictinfo =
194+
make_restrictinfo((Expr *) ntest,
195+
restrictinfo->is_pushed_down,
196+
restrictinfo->outerjoin_delayed,
197+
restrictinfo->pseudoconstant,
198+
restrictinfo->security_level,
199+
NULL,
200+
restrictinfo->outer_relids,
201+
restrictinfo->nullable_relids);
202+
}
203+
return false;
204+
}
167205

168206
/*
169207
* If below outer join, check for strictness, else reject.
@@ -1741,7 +1779,7 @@ reconsider_outer_join_clause(PlannerInfo *root, RestrictInfo *rinfo,
17411779
bms_copy(inner_relids),
17421780
bms_copy(inner_nullable_relids),
17431781
cur_ec->ec_min_security);
1744-
if (process_equivalence(root, newrinfo, true))
1782+
if (process_equivalence(root, &newrinfo, true))
17451783
match = true;
17461784
}
17471785

@@ -1884,7 +1922,7 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo)
18841922
bms_copy(left_relids),
18851923
bms_copy(left_nullable_relids),
18861924
cur_ec->ec_min_security);
1887-
if (process_equivalence(root, newrinfo, true))
1925+
if (process_equivalence(root, &newrinfo, true))
18881926
matchleft = true;
18891927
}
18901928
eq_op = select_equality_operator(cur_ec,
@@ -1899,7 +1937,7 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo)
18991937
bms_copy(right_relids),
19001938
bms_copy(right_nullable_relids),
19011939
cur_ec->ec_min_security);
1902-
if (process_equivalence(root, newrinfo, true))
1940+
if (process_equivalence(root, &newrinfo, true))
19031941
matchright = true;
19041942
}
19051943
}

src/backend/optimizer/plan/initsplan.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,10 +1964,11 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
19641964
if (maybe_equivalence)
19651965
{
19661966
if (check_equivalence_delay(root, restrictinfo) &&
1967-
process_equivalence(root, restrictinfo, below_outer_join))
1967+
process_equivalence(root, &restrictinfo, below_outer_join))
19681968
return;
19691969
/* EC rejected it, so set left_ec/right_ec the hard way ... */
1970-
initialize_mergeclause_eclasses(root, restrictinfo);
1970+
if (restrictinfo->mergeopfamilies) /* EC might have changed this */
1971+
initialize_mergeclause_eclasses(root, restrictinfo);
19711972
/* ... and fall through to distribute_restrictinfo_to_rels */
19721973
}
19731974
else if (maybe_outer_join && restrictinfo->can_join)

src/include/optimizer/paths.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ typedef bool (*ec_matches_callback_type) (PlannerInfo *root,
127127
EquivalenceMember *em,
128128
void *arg);
129129

130-
extern bool process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo,
130+
extern bool process_equivalence(PlannerInfo *root,
131+
RestrictInfo **p_restrictinfo,
131132
bool below_outer_join);
132133
extern Expr *canonicalize_ec_expression(Expr *expr,
133134
Oid req_type, Oid req_collation);

src/test/regress/expected/equivclass.out

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,3 +421,21 @@ reset session authorization;
421421
revoke select on ec0 from regress_user_ectest;
422422
revoke select on ec1 from regress_user_ectest;
423423
drop user regress_user_ectest;
424+
-- check that X=X is converted to X IS NOT NULL when appropriate
425+
explain (costs off)
426+
select * from tenk1 where unique1 = unique1 and unique2 = unique2;
427+
QUERY PLAN
428+
-------------------------------------------------------------
429+
Seq Scan on tenk1
430+
Filter: ((unique1 IS NOT NULL) AND (unique2 IS NOT NULL))
431+
(2 rows)
432+
433+
-- this could be converted, but isn't at present
434+
explain (costs off)
435+
select * from tenk1 where unique1 = unique1 or unique2 = unique2;
436+
QUERY PLAN
437+
--------------------------------------------------------
438+
Seq Scan on tenk1
439+
Filter: ((unique1 = unique1) OR (unique2 = unique2))
440+
(2 rows)
441+

src/test/regress/sql/equivclass.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,11 @@ revoke select on ec0 from regress_user_ectest;
254254
revoke select on ec1 from regress_user_ectest;
255255

256256
drop user regress_user_ectest;
257+
258+
-- check that X=X is converted to X IS NOT NULL when appropriate
259+
explain (costs off)
260+
select * from tenk1 where unique1 = unique1 and unique2 = unique2;
261+
262+
-- this could be converted, but isn't at present
263+
explain (costs off)
264+
select * from tenk1 where unique1 = unique1 or unique2 = unique2;

0 commit comments

Comments
 (0)