Produce more-optimal plans for bitmap scans on boolean columns.

The planner simplifies boolean comparisons such as "x = true" and
"x = false" down to "x" and "NOT x" respectively, to have a canonical
form to ease comparisons.  However, if we want to use an index on x,
the index AM APIs require us to reconstitute the comparison-operator
form of the indexqual.  While that works, in bitmap indexscans the
canonical form of the qual was emitted as a "filter" condition
although it really only needs to be a "recheck" condition, because
create_bitmap_scan_plan didn't recognize the equivalence of that
form with the generated indexqual.  booleq() is pretty cheap so that
likely doesn't make very much difference, but it's unsightly so
let's clean it up.

To fix, add a case to predicate_implied_by() to recognize the
equivalence of such clauses.  This is a relatively low-cost place to
add a check, and perhaps it will have additional use cases in future.

Richard Guo and Tom Lane, per discussion of bug #17618 from Sindy
Senorita.

Discussion: https://postgr.es/m/17618-7a2240bfaa7e84ae@postgresql.org
This commit is contained in:
Tom Lane 2022-11-08 10:36:04 -05:00
parent 05a7be9355
commit 042c9091f0
3 changed files with 60 additions and 2 deletions

View File

@ -92,7 +92,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM test_bool WHERE i=true ORDER BY i;
Sort
Sort Key: i
-> Bitmap Heap Scan on test_bool
Filter: i
Recheck Cond: i
-> Bitmap Index Scan on idx_bool
Index Cond: (i = true)
(6 rows)
@ -119,3 +119,15 @@ EXPLAIN (COSTS OFF) SELECT * FROM test_bool WHERE i>true ORDER BY i;
Index Cond: (i > true)
(6 rows)
-- probably sufficient to check just this one:
EXPLAIN (COSTS OFF) SELECT * FROM test_bool WHERE i=false ORDER BY i;
QUERY PLAN
-------------------------------------------
Sort
Sort Key: i
-> Bitmap Heap Scan on test_bool
Recheck Cond: (NOT i)
-> Bitmap Index Scan on idx_bool
Index Cond: (i = false)
(6 rows)

View File

@ -25,3 +25,5 @@ EXPLAIN (COSTS OFF) SELECT * FROM test_bool WHERE i<=true ORDER BY i;
EXPLAIN (COSTS OFF) SELECT * FROM test_bool WHERE i=true ORDER BY i;
EXPLAIN (COSTS OFF) SELECT * FROM test_bool WHERE i>=true ORDER BY i;
EXPLAIN (COSTS OFF) SELECT * FROM test_bool WHERE i>true ORDER BY i;
-- probably sufficient to check just this one:
EXPLAIN (COSTS OFF) SELECT * FROM test_bool WHERE i=false ORDER BY i;

View File

@ -15,6 +15,7 @@
*/
#include "postgres.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
@ -1093,7 +1094,7 @@ arrayexpr_cleanup_fn(PredIterInfo info)
*
* We return true if able to prove the implication, false if not.
*
* We have three strategies for determining whether one simple clause
* We have several strategies for determining whether one simple clause
* implies another:
*
* A simple and general way is to see if they are equal(); this works for any
@ -1101,6 +1102,12 @@ arrayexpr_cleanup_fn(PredIterInfo info)
* there is an implied assumption that the functions in the expression are
* immutable --- but this was checked for the predicate by the caller.)
*
* Another way that always works is that for boolean x, "x = TRUE" is
* equivalent to "x", likewise "x = FALSE" is equivalent to "NOT x".
* These can be worth checking because, while we preferentially simplify
* boolean comparisons down to "x" and "NOT x", the other form has to be
* dealt with anyway in the context of index conditions.
*
* If the predicate is of the form "foo IS NOT NULL", and we are considering
* strong implication, we can conclude that the predicate is implied if the
* clause is strict for "foo", i.e., it must yield false or NULL when "foo"
@ -1124,6 +1131,43 @@ predicate_implied_by_simple_clause(Expr *predicate, Node *clause,
if (equal((Node *) predicate, clause))
return true;
/* Next see if clause is boolean equality to a constant */
if (is_opclause(clause) &&
((OpExpr *) clause)->opno == BooleanEqualOperator)
{
OpExpr *op = (OpExpr *) clause;
Node *rightop;
Assert(list_length(op->args) == 2);
rightop = lsecond(op->args);
/* We might never see a null Const here, but better check anyway */
if (rightop && IsA(rightop, Const) &&
!((Const *) rightop)->constisnull)
{
Node *leftop = linitial(op->args);
if (DatumGetBool(((Const *) rightop)->constvalue))
{
/* X = true implies X */
if (equal(predicate, leftop))
return true;
}
else
{
/* X = false implies NOT X */
if (is_notclause(predicate) &&
equal(get_notclausearg(predicate), leftop))
return true;
}
}
}
/*
* We could likewise check whether the predicate is boolean equality to a
* constant; but there are no known use-cases for that at the moment,
* assuming that the predicate has been through constant-folding.
*/
/* Next try the IS NOT NULL case */
if (!weak &&
predicate && IsA(predicate, NullTest))