Produce more-optimal plans for bitmap scans on boolean columns.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 8 Nov 2022 15:36:04 +0000 (10:36 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 8 Nov 2022 15:36:04 +0000 (10:36 -0500)
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

contrib/btree_gin/expected/bool.out
contrib/btree_gin/sql/bool.sql
src/backend/optimizer/util/predtest.c

index 207a3f232810731c61a2ad32254d0b1d820002e1..b379622baf790a5bac830d77d7c9c2beb75c256d 100644 (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)
+
index dad2ff32b825538195aeb5f2e3ca1563dfb621a2..08f2986e8c6d849cc661fd843d7aaa9de9944c52 100644 (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;
index 182d5b15234b77c79cfa52d677c72e5097dcd8e3..cd50cffb9e37c8a45646cc3459dbd975ee0da447 100644 (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))