Teach bitmap path generation about transforming OR-clauses to SAOP's
authorAlexander Korotkov <akorotkov@postgresql.org>
Sat, 23 Nov 2024 23:41:45 +0000 (01:41 +0200)
committerAlexander Korotkov <akorotkov@postgresql.org>
Sat, 23 Nov 2024 23:41:45 +0000 (01:41 +0200)
When optimizer generates bitmap paths, it considers breaking OR-clause
arguments one-by-one.  But now, a group of similar OR-clauses can be
transformed into SAOP during index matching.  So, bitmap paths should
keep up.

This commit teaches bitmap paths generation machinery to group similar
OR-clauses into dedicated RestrictInfos.  Those RestrictInfos are considered
both to match index as a whole (as SAOP), or to match as a set of individual
OR-clause argument one-by-one (the old way).

Therefore, bitmap path generation will takes advantage of OR-clauses to SAOP's
transformation.  The old way of handling them is also considered.  So, there
shouldn't be planning regression.

Discussion: https://postgr.es/m/CAPpHfdu5iQOjF93vGbjidsQkhHvY2NSm29duENYH_cbhC6x%2BMg%40mail.gmail.com
Author: Alexander Korotkov, Andrey Lepikhov
Reviewed-by: Alena Rybakina, Andrei Lepikhov, Jian he, Robert Haas
Reviewed-by: Peter Geoghegan
src/backend/optimizer/path/indxpath.c
src/backend/optimizer/util/restrictinfo.c
src/include/optimizer/restrictinfo.h
src/test/regress/expected/create_index.out
src/test/regress/expected/join.out
src/test/regress/sql/create_index.sql
src/tools/pgindent/typedefs.list

index 61b1848241049e29f0a7a50ced43896b8435f1e1..5d8d0c389c909ce1c40879d6e86d3556fac15402 100644 (file)
@@ -1173,6 +1173,383 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
        return result;
 }
 
+/*
+ * Utility structure used to group similar OR-clause arguments in
+ * group_similar_or_args().  It represents information about the OR-clause
+ * argument and its matching index key.
+ */
+typedef struct
+{
+       int                     indexnum;               /* index of the matching index, or -1 if no
+                                                                * matching index */
+       int                     colnum;                 /* index of the matching column, or -1 if no
+                                                                * matching index */
+       Oid                     opno;                   /* OID of the OpClause operator, or InvalidOid
+                                                                * if not an OpExpr */
+       Oid                     inputcollid;    /* OID of the OpClause input collation */
+       int                     argindex;               /* index of the clause in the list of
+                                                                * arguments */
+} OrArgIndexMatch;
+
+/*
+ * Comparison function for OrArgIndexMatch which provides sort order placing
+ * similar OR-clause arguments together.
+ */
+static int
+or_arg_index_match_cmp(const void *a, const void *b)
+{
+       const OrArgIndexMatch *match_a = (const OrArgIndexMatch *) a;
+       const OrArgIndexMatch *match_b = (const OrArgIndexMatch *) b;
+
+       if (match_a->indexnum < match_b->indexnum)
+               return -1;
+       else if (match_a->indexnum > match_b->indexnum)
+               return 1;
+
+       if (match_a->colnum < match_b->colnum)
+               return -1;
+       else if (match_a->colnum > match_b->colnum)
+               return 1;
+
+       if (match_a->opno < match_b->opno)
+               return -1;
+       else if (match_a->opno > match_b->opno)
+               return 1;
+
+       if (match_a->inputcollid < match_b->inputcollid)
+               return -1;
+       else if (match_a->inputcollid > match_b->inputcollid)
+               return 1;
+
+       if (match_a->argindex < match_b->argindex)
+               return -1;
+       else if (match_a->argindex > match_b->argindex)
+               return 1;
+
+       return 0;
+}
+
+/*
+ * group_similar_or_args
+ *             Transform incoming OR-restrictinfo into a list of sub-restrictinfos,
+ *             each of them containing a subset of similar OR-clause arguments from
+ *             the source rinfo.
+ *
+ * Similar OR-clause arguments are of the form "indexkey op constant" having
+ * the same indexkey, operator, and collation.  Constant may comprise either
+ * Const or Param.  It may be employed later, during the
+ * match_clause_to_indexcol() to transform the whole OR-sub-rinfo to an SAOP
+ * clause.
+ *
+ * Returns the processed list of OR-clause arguments.
+ */
+static List *
+group_similar_or_args(PlannerInfo *root, RelOptInfo *rel, RestrictInfo *rinfo)
+{
+       int                     n;
+       int                     i;
+       int                     group_start;
+       OrArgIndexMatch *matches;
+       bool            matched = false;
+       ListCell   *lc;
+       ListCell   *lc2;
+       List       *orargs;
+       List       *result = NIL;
+
+       Assert(IsA(rinfo->orclause, BoolExpr));
+       orargs = ((BoolExpr *) rinfo->orclause)->args;
+       n = list_length(orargs);
+
+       /*
+        * To avoid N^2 behavior, take utility pass along the list of OR-clause
+        * arguments.  For each argument, fill the OrArgIndexMatch structure,
+        * which will be used to sort these arguments at the next step.
+        */
+       i = -1;
+       matches = (OrArgIndexMatch *) palloc(sizeof(OrArgIndexMatch) * n);
+       foreach(lc, orargs)
+       {
+               Node       *arg = lfirst(lc);
+               RestrictInfo *argrinfo;
+               OpExpr     *clause;
+               Oid                     opno;
+               Node       *leftop,
+                                  *rightop;
+               Node       *nonConstExpr;
+               int                     indexnum;
+               int                     colnum;
+
+               i++;
+               matches[i].argindex = i;
+               matches[i].indexnum = -1;
+               matches[i].colnum = -1;
+               matches[i].opno = InvalidOid;
+               matches[i].inputcollid = InvalidOid;
+
+               if (!IsA(arg, RestrictInfo))
+                       continue;
+
+               argrinfo = castNode(RestrictInfo, arg);
+
+               /* Only operator clauses can match  */
+               if (!IsA(argrinfo->clause, OpExpr))
+                       continue;
+
+               clause = (OpExpr *) argrinfo->clause;
+               opno = clause->opno;
+
+               /* Only binary operators can match  */
+               if (list_length(clause->args) != 2)
+                       continue;
+
+               /*
+                * Ignore any RelabelType node above the operands.  This is needed to
+                * be able to apply indexscanning in binary-compatible-operator cases.
+                * Note: we can assume there is at most one RelabelType node;
+                * eval_const_expressions() will have simplified if more than one.
+                */
+               leftop = get_leftop(clause);
+               if (IsA(leftop, RelabelType))
+                       leftop = (Node *) ((RelabelType *) leftop)->arg;
+
+               rightop = get_rightop(clause);
+               if (IsA(rightop, RelabelType))
+                       rightop = (Node *) ((RelabelType *) rightop)->arg;
+
+               /*
+                * Check for clauses of the form: (indexkey operator constant) or
+                * (constant operator indexkey).  But we don't know a particular index
+                * yet.  First check for a constant, which must be Const or Param.
+                * That's cheaper than search for an index key among all indexes.
+                */
+               if (IsA(leftop, Const) || IsA(leftop, Param))
+               {
+                       opno = get_commutator(opno);
+
+                       if (!OidIsValid(opno))
+                       {
+                               /* commutator doesn't exist, we can't reverse the order */
+                               continue;
+                       }
+                       nonConstExpr = rightop;
+               }
+               else if (IsA(rightop, Const) || IsA(rightop, Param))
+               {
+                       nonConstExpr = leftop;
+               }
+               else
+               {
+                       continue;
+               }
+
+               /*
+                * Match non-constant part to the index key.  It's possible that a
+                * single non-constant part matches multiple index keys.  It's OK, we
+                * just stop with first matching index key.  Given that this choice is
+                * determined the same for every clause, we will group similar clauses
+                * together anyway.
+                */
+               indexnum = 0;
+               foreach(lc2, rel->indexlist)
+               {
+                       IndexOptInfo *index = (IndexOptInfo *) lfirst(lc2);
+
+                       /* Ignore index if it doesn't support bitmap scans */
+                       if (!index->amhasgetbitmap)
+                               continue;
+
+                       for (colnum = 0; colnum < index->nkeycolumns; colnum++)
+                       {
+                               if (match_index_to_operand(nonConstExpr, colnum, index))
+                               {
+                                       matches[i].indexnum = indexnum;
+                                       matches[i].colnum = colnum;
+                                       matches[i].opno = opno;
+                                       matches[i].inputcollid = clause->inputcollid;
+                                       matched = true;
+                                       break;
+                               }
+                       }
+
+                       /*
+                        * Stop looping through the indexes, if we managed to match
+                        * nonConstExpr to any index column.
+                        */
+                       if (matches[i].indexnum >= 0)
+                               break;
+                       indexnum++;
+               }
+       }
+
+       /*
+        * Fast-path check: if no clause is matching to the index column, we can
+        * just give up at this stage and return the clause list as-is.
+        */
+       if (!matched)
+       {
+               pfree(matches);
+               return orargs;
+       }
+
+       /* Sort clauses to make similar clauses go together */
+       qsort(matches, n, sizeof(OrArgIndexMatch), or_arg_index_match_cmp);
+
+       /*
+        * Group similar clauses into single sub-restrictinfo. Side effect: the
+        * resulting list of restrictions will be sorted by indexnum and colnum.
+        */
+       group_start = 0;
+       for (i = 1; i <= n; i++)
+       {
+               /* Check if it's a group boundary */
+               if (group_start >= 0 &&
+                       (i == n ||
+                        matches[i].indexnum != matches[group_start].indexnum ||
+                        matches[i].colnum != matches[group_start].colnum ||
+                        matches[i].opno != matches[group_start].opno ||
+                        matches[i].inputcollid != matches[group_start].inputcollid ||
+                        matches[i].indexnum == -1))
+               {
+                       /*
+                        * One clause in group: add it "as is" to the upper-level OR.
+                        */
+                       if (i - group_start == 1)
+                       {
+                               result = lappend(result,
+                                                                list_nth(orargs,
+                                                                                 matches[group_start].argindex));
+                       }
+                       else
+                       {
+                               /*
+                                * Two or more clauses in a group: create a nested OR.
+                                */
+                               List       *args = NIL;
+                               List       *rargs = NIL;
+                               RestrictInfo *subrinfo;
+                               int                     j;
+
+                               Assert(i - group_start >= 2);
+
+                               /* Construct the list of nested OR arguments */
+                               for (j = group_start; j < i; j++)
+                               {
+                                       Node       *arg = list_nth(orargs, matches[j].argindex);
+
+                                       rargs = lappend(rargs, arg);
+                                       if (IsA(arg, RestrictInfo))
+                                               args = lappend(args, ((RestrictInfo *) arg)->clause);
+                                       else
+                                               args = lappend(args, arg);
+                               }
+
+                               /* Construct the nested OR and wrap it with RestrictInfo */
+                               subrinfo = make_plain_restrictinfo(root,
+                                                                                                  make_orclause(args),
+                                                                                                  make_orclause(rargs),
+                                                                                                  rinfo->is_pushed_down,
+                                                                                                  rinfo->has_clone,
+                                                                                                  rinfo->is_clone,
+                                                                                                  rinfo->pseudoconstant,
+                                                                                                  rinfo->security_level,
+                                                                                                  rinfo->required_relids,
+                                                                                                  rinfo->incompatible_relids,
+                                                                                                  rinfo->outer_relids);
+                               result = lappend(result, subrinfo);
+                       }
+
+                       group_start = i;
+               }
+       }
+       pfree(matches);
+       return result;
+}
+
+/*
+ * make_bitmap_paths_for_or_group
+ *             Generate bitmap paths for a group of similar OR-clause arguments
+ *             produced by group_similar_or_args().
+ *
+ * This function considers two cases: (1) matching a group of clauses to
+ * the index as a whole, and (2) matching the individual clauses one-by-one.
+ * (1) typically comprises an optimal solution.  If not, (2) typically
+ * comprises fair alternative.
+ *
+ * Ideally, we could consider all arbitrary splits of arguments into
+ * subgroups, but that could lead to unacceptable computational complexity.
+ * This is why we only consider two cases of above.
+ */
+static List *
+make_bitmap_paths_for_or_group(PlannerInfo *root, RelOptInfo *rel,
+                                                          RestrictInfo *ri, List *other_clauses)
+{
+       List       *jointlist = NIL;
+       List       *splitlist = NIL;
+       ListCell   *lc;
+       List       *orargs;
+       List       *args = ((BoolExpr *) ri->orclause)->args;
+       Cost            jointcost = 0.0,
+                               splitcost = 0.0;
+       Path       *bitmapqual;
+       List       *indlist;
+
+       /*
+        * First, try to match the whole group to the one index.
+        */
+       orargs = list_make1(ri);
+       indlist = build_paths_for_OR(root, rel,
+                                                                orargs,
+                                                                other_clauses);
+       if (indlist != NIL)
+       {
+               bitmapqual = choose_bitmap_and(root, rel, indlist);
+               jointcost = bitmapqual->total_cost;
+               jointlist = list_make1(bitmapqual);
+       }
+
+       /*
+        * If we manage to find a bitmap scan, which uses the group of OR-clause
+        * arguments as a whole, we can skip matching OR-clause arguments
+        * one-by-one as long as there are no other clauses, which can bring more
+        * efficiency to one-by-one case.
+        */
+       if (jointlist != NIL && other_clauses == NIL)
+               return jointlist;
+
+       /*
+        * Also try to match all containing clauses one-by-one.
+        */
+       foreach(lc, args)
+       {
+               orargs = list_make1(lfirst(lc));
+
+               indlist = build_paths_for_OR(root, rel,
+                                                                        orargs,
+                                                                        other_clauses);
+
+               if (indlist == NIL)
+               {
+                       splitlist = NIL;
+                       break;
+               }
+
+               bitmapqual = choose_bitmap_and(root, rel, indlist);
+               splitcost += bitmapqual->total_cost;
+               splitlist = lappend(splitlist, bitmapqual);
+       }
+
+       /*
+        * Pick the best option.
+        */
+       if (splitlist == NIL)
+               return jointlist;
+       else if (jointlist == NIL)
+               return splitlist;
+       else
+               return (jointcost < splitcost) ? jointlist : splitlist;
+}
+
+
 /*
  * generate_bitmap_or_paths
  *             Look through the list of clauses to find OR clauses, and generate
@@ -1203,6 +1580,8 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
                List       *pathlist;
                Path       *bitmapqual;
                ListCell   *j;
+               List       *groupedArgs;
+               List       *inner_other_clauses = NIL;
 
                /* Ignore RestrictInfos that aren't ORs */
                if (!restriction_is_or_clause(rinfo))
@@ -1213,7 +1592,29 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
                 * the OR, else we can't use it.
                 */
                pathlist = NIL;
-               foreach(j, ((BoolExpr *) rinfo->orclause)->args)
+
+               /*
+                * Group the similar OR-clause arguments into dedicated RestrictInfos,
+                * because each of those RestrictInfos has a chance to match the index
+                * as a whole.
+                */
+               groupedArgs = group_similar_or_args(root, rel, rinfo);
+
+               if (groupedArgs != ((BoolExpr *) rinfo->orclause)->args)
+               {
+                       /*
+                        * Some parts of the rinfo were probably grouped.  In this case,
+                        * we have a set of sub-rinfos that together are an exact
+                        * duplicate of rinfo.  Thus, we need to remove the rinfo from
+                        * other clauses. match_clauses_to_index detects duplicated
+                        * iclauses by comparing pointers to original rinfos that would be
+                        * different.  So, we must delete rinfo to avoid de-facto
+                        * duplicated clauses in the index clauses list.
+                        */
+                       inner_other_clauses = list_delete(list_copy(all_clauses), rinfo);
+               }
+
+               foreach(j, groupedArgs)
                {
                        Node       *orarg = (Node *) lfirst(j);
                        List       *indlist;
@@ -1233,12 +1634,34 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
                                                                                                                           andargs,
                                                                                                                           all_clauses));
                        }
+                       else if (restriction_is_or_clause(castNode(RestrictInfo, orarg)))
+                       {
+                               RestrictInfo *ri = castNode(RestrictInfo, orarg);
+
+                               /*
+                                * Generate bitmap paths for the group of similar OR-clause
+                                * arguments.
+                                */
+                               indlist = make_bitmap_paths_for_or_group(root,
+                                                                                                                rel, ri,
+                                                                                                                inner_other_clauses);
+
+                               if (indlist == NIL)
+                               {
+                                       pathlist = NIL;
+                                       break;
+                               }
+                               else
+                               {
+                                       pathlist = list_concat(pathlist, indlist);
+                                       continue;
+                               }
+                       }
                        else
                        {
                                RestrictInfo *ri = castNode(RestrictInfo, orarg);
                                List       *orargs;
 
-                               Assert(!restriction_is_or_clause(ri));
                                orargs = list_make1(ri);
 
                                indlist = build_paths_for_OR(root, rel,
@@ -1264,6 +1687,9 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
                        pathlist = lappend(pathlist, bitmapqual);
                }
 
+               if (inner_other_clauses != NIL)
+                       list_free(inner_other_clauses);
+
                /*
                 * If we have a match for every arm, then turn them into a
                 * BitmapOrPath, and add to result list.
index 0b406e93342d3db1480dbdab768118f88c459e16..9e1458401c26db72e637d49cf59deb83c2d395d4 100644 (file)
 #include "optimizer/restrictinfo.h"
 
 
-static RestrictInfo *make_restrictinfo_internal(PlannerInfo *root,
-                                                                                               Expr *clause,
-                                                                                               Expr *orclause,
-                                                                                               bool is_pushed_down,
-                                                                                               bool has_clone,
-                                                                                               bool is_clone,
-                                                                                               bool pseudoconstant,
-                                                                                               Index security_level,
-                                                                                               Relids required_relids,
-                                                                                               Relids incompatible_relids,
-                                                                                               Relids outer_relids);
 static Expr *make_sub_restrictinfos(PlannerInfo *root,
                                                                        Expr *clause,
                                                                        bool is_pushed_down,
@@ -90,36 +79,38 @@ make_restrictinfo(PlannerInfo *root,
        /* Shouldn't be an AND clause, else AND/OR flattening messed up */
        Assert(!is_andclause(clause));
 
-       return make_restrictinfo_internal(root,
-                                                                         clause,
-                                                                         NULL,
-                                                                         is_pushed_down,
-                                                                         has_clone,
-                                                                         is_clone,
-                                                                         pseudoconstant,
-                                                                         security_level,
-                                                                         required_relids,
-                                                                         incompatible_relids,
-                                                                         outer_relids);
+       return make_plain_restrictinfo(root,
+                                                                  clause,
+                                                                  NULL,
+                                                                  is_pushed_down,
+                                                                  has_clone,
+                                                                  is_clone,
+                                                                  pseudoconstant,
+                                                                  security_level,
+                                                                  required_relids,
+                                                                  incompatible_relids,
+                                                                  outer_relids);
 }
 
 /*
- * make_restrictinfo_internal
+ * make_plain_restrictinfo
  *
- * Common code for the main entry points and the recursive cases.
+ * Common code for the main entry points and the recursive cases.  Also,
+ * useful while contrucitng RestrictInfos above OR clause, which already has
+ * RestrictInfos above its subclauses.
  */
-static RestrictInfo *
-make_restrictinfo_internal(PlannerInfo *root,
-                                                  Expr *clause,
-                                                  Expr *orclause,
-                                                  bool is_pushed_down,
-                                                  bool has_clone,
-                                                  bool is_clone,
-                                                  bool pseudoconstant,
-                                                  Index security_level,
-                                                  Relids required_relids,
-                                                  Relids incompatible_relids,
-                                                  Relids outer_relids)
+RestrictInfo *
+make_plain_restrictinfo(PlannerInfo *root,
+                                               Expr *clause,
+                                               Expr *orclause,
+                                               bool is_pushed_down,
+                                               bool has_clone,
+                                               bool is_clone,
+                                               bool pseudoconstant,
+                                               Index security_level,
+                                               Relids required_relids,
+                                               Relids incompatible_relids,
+                                               Relids outer_relids)
 {
        RestrictInfo *restrictinfo = makeNode(RestrictInfo);
        Relids          baserels;
@@ -296,17 +287,17 @@ make_sub_restrictinfos(PlannerInfo *root,
                                                                                                        NULL,
                                                                                                        incompatible_relids,
                                                                                                        outer_relids));
-               return (Expr *) make_restrictinfo_internal(root,
-                                                                                                  clause,
-                                                                                                  make_orclause(orlist),
-                                                                                                  is_pushed_down,
-                                                                                                  has_clone,
-                                                                                                  is_clone,
-                                                                                                  pseudoconstant,
-                                                                                                  security_level,
-                                                                                                  required_relids,
-                                                                                                  incompatible_relids,
-                                                                                                  outer_relids);
+               return (Expr *) make_plain_restrictinfo(root,
+                                                                                               clause,
+                                                                                               make_orclause(orlist),
+                                                                                               is_pushed_down,
+                                                                                               has_clone,
+                                                                                               is_clone,
+                                                                                               pseudoconstant,
+                                                                                               security_level,
+                                                                                               required_relids,
+                                                                                               incompatible_relids,
+                                                                                               outer_relids);
        }
        else if (is_andclause(clause))
        {
@@ -328,17 +319,17 @@ make_sub_restrictinfos(PlannerInfo *root,
                return make_andclause(andlist);
        }
        else
-               return (Expr *) make_restrictinfo_internal(root,
-                                                                                                  clause,
-                                                                                                  NULL,
-                                                                                                  is_pushed_down,
-                                                                                                  has_clone,
-                                                                                                  is_clone,
-                                                                                                  pseudoconstant,
-                                                                                                  security_level,
-                                                                                                  required_relids,
-                                                                                                  incompatible_relids,
-                                                                                                  outer_relids);
+               return (Expr *) make_plain_restrictinfo(root,
+                                                                                               clause,
+                                                                                               NULL,
+                                                                                               is_pushed_down,
+                                                                                               has_clone,
+                                                                                               is_clone,
+                                                                                               pseudoconstant,
+                                                                                               security_level,
+                                                                                               required_relids,
+                                                                                               incompatible_relids,
+                                                                                               outer_relids);
 }
 
 /*
index fe03a8ecd342c40c6899bc140c43dbb6725d4a63..f32dae8620b58b8fb11ead4dc8ea9acf4bf65f66 100644 (file)
        make_restrictinfo(root, clause, true, false, false, false, 0, \
                NULL, NULL, NULL)
 
+extern RestrictInfo *make_plain_restrictinfo(PlannerInfo *root,
+                                                                                        Expr *clause,
+                                                                                        Expr *orclause,
+                                                                                        bool is_pushed_down,
+                                                                                        bool has_clone,
+                                                                                        bool is_clone,
+                                                                                        bool pseudoconstant,
+                                                                                        Index security_level,
+                                                                                        Relids required_relids,
+                                                                                        Relids incompatible_relids,
+                                                                                        Relids outer_relids);
 extern RestrictInfo *make_restrictinfo(PlannerInfo *root,
                                                                           Expr *clause,
                                                                           bool is_pushed_down,
index e4d117e47ae1335dddc3d5a19d13eb1ae2930455..b003492c5c88c9df6ddeed041beb10997a6c48d9 100644 (file)
@@ -1875,6 +1875,60 @@ SELECT * FROM tenk1
       42 |    5530 |   0 |    2 |   2 |      2 |      42 |       42 |          42 |        42 |       42 |  84 |   85 | QBAAAA   | SEIAAA   | OOOOxx
 (1 row)
 
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42 OR tenthous IS NULL);
+                                                                QUERY PLAN                                                                 
+-------------------------------------------------------------------------------------------------------------------------------------------
+ Bitmap Heap Scan on tenk1
+   Recheck Cond: (((thousand = 42) AND (tenthous IS NULL)) OR ((thousand = 42) AND ((tenthous = 1) OR (tenthous = 3) OR (tenthous = 42))))
+   Filter: ((tenthous = 1) OR (tenthous = 3) OR (tenthous = 42) OR (tenthous IS NULL))
+   ->  BitmapOr
+         ->  Bitmap Index Scan on tenk1_thous_tenthous
+               Index Cond: ((thousand = 42) AND (tenthous IS NULL))
+         ->  Bitmap Index Scan on tenk1_thous_tenthous
+               Index Cond: ((thousand = 42) AND (tenthous = ANY ('{1,3,42}'::integer[])))
+(8 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1::int2 OR tenthous::int2 = 3::int8 OR tenthous = 42::int8);
+                                                 QUERY PLAN                                                  
+-------------------------------------------------------------------------------------------------------------
+ Bitmap Heap Scan on tenk1
+   Recheck Cond: (thousand = 42)
+   Filter: ((tenthous = '1'::smallint) OR ((tenthous)::smallint = '3'::bigint) OR (tenthous = '42'::bigint))
+   ->  Bitmap Index Scan on tenk1_thous_tenthous
+         Index Cond: (thousand = 42)
+(5 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1::int2 OR tenthous::int2 = 3::int8 OR tenthous::int2 = 42::int8);
+                                                       QUERY PLAN                                                        
+-------------------------------------------------------------------------------------------------------------------------
+ Bitmap Heap Scan on tenk1
+   Recheck Cond: (thousand = 42)
+   Filter: ((tenthous = '1'::smallint) OR ((tenthous)::smallint = '3'::bigint) OR ((tenthous)::smallint = '42'::bigint))
+   ->  Bitmap Index Scan on tenk1_thous_tenthous
+         Index Cond: (thousand = 42)
+(5 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1::int2 OR tenthous = 3::int8 OR tenthous = 42::int8);
+                                                                     QUERY PLAN                                                                      
+-----------------------------------------------------------------------------------------------------------------------------------------------------
+ Bitmap Heap Scan on tenk1
+   Recheck Cond: (((thousand = 42) AND ((tenthous = '3'::bigint) OR (tenthous = '42'::bigint))) OR ((thousand = 42) AND (tenthous = '1'::smallint)))
+   Filter: ((tenthous = '1'::smallint) OR (tenthous = '3'::bigint) OR (tenthous = '42'::bigint))
+   ->  BitmapOr
+         ->  Bitmap Index Scan on tenk1_thous_tenthous
+               Index Cond: ((thousand = 42) AND (tenthous = ANY ('{3,42}'::bigint[])))
+         ->  Bitmap Index Scan on tenk1_thous_tenthous
+               Index Cond: ((thousand = 42) AND (tenthous = '1'::smallint))
+(8 rows)
+
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM tenk1
   WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
@@ -2003,25 +2057,24 @@ SELECT count(*) FROM tenk1
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM tenk1
   WHERE hundred = 42 AND (thousand = 42 OR thousand = 99 OR tenthous < 2) OR thousand = 41;
-                                                       QUERY PLAN                                                       
-------------------------------------------------------------------------------------------------------------------------
+                                                        QUERY PLAN                                                        
+--------------------------------------------------------------------------------------------------------------------------
  Aggregate
    ->  Bitmap Heap Scan on tenk1
-         Recheck Cond: (((hundred = 42) AND ((thousand = 42) OR (thousand = 99) OR (tenthous < 2))) OR (thousand = 41))
+         Recheck Cond: (((hundred = 42) AND (((thousand = 42) OR (thousand = 99)) OR (tenthous < 2))) OR (thousand = 41))
+         Filter: (((hundred = 42) AND ((thousand = 42) OR (thousand = 99) OR (tenthous < 2))) OR (thousand = 41))
          ->  BitmapOr
                ->  BitmapAnd
                      ->  Bitmap Index Scan on tenk1_hundred
                            Index Cond: (hundred = 42)
                      ->  BitmapOr
                            ->  Bitmap Index Scan on tenk1_thous_tenthous
-                                 Index Cond: (thousand = 42)
-                           ->  Bitmap Index Scan on tenk1_thous_tenthous
-                                 Index Cond: (thousand = 99)
+                                 Index Cond: (thousand = ANY ('{42,99}'::integer[]))
                            ->  Bitmap Index Scan on tenk1_thous_tenthous
                                  Index Cond: (tenthous < 2)
                ->  Bitmap Index Scan on tenk1_thous_tenthous
                      Index Cond: (thousand = 41)
-(16 rows)
+(15 rows)
 
 SELECT count(*) FROM tenk1
   WHERE hundred = 42 AND (thousand = 42 OR thousand = 99 OR tenthous < 2) OR thousand = 41;
@@ -2033,22 +2086,21 @@ SELECT count(*) FROM tenk1
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM tenk1
   WHERE hundred = 42 AND (thousand = 42 OR thousand = 41 OR thousand = 99 AND tenthous = 2);
-                                                       QUERY PLAN                                                        
--------------------------------------------------------------------------------------------------------------------------
+                                                        QUERY PLAN                                                         
+---------------------------------------------------------------------------------------------------------------------------
  Aggregate
    ->  Bitmap Heap Scan on tenk1
-         Recheck Cond: ((hundred = 42) AND ((thousand = 42) OR (thousand = 41) OR ((thousand = 99) AND (tenthous = 2))))
+         Recheck Cond: ((hundred = 42) AND (((thousand = 99) AND (tenthous = 2)) OR ((thousand = 42) OR (thousand = 41))))
+         Filter: ((thousand = 42) OR (thousand = 41) OR ((thousand = 99) AND (tenthous = 2)))
          ->  BitmapAnd
                ->  Bitmap Index Scan on tenk1_hundred
                      Index Cond: (hundred = 42)
                ->  BitmapOr
-                     ->  Bitmap Index Scan on tenk1_thous_tenthous
-                           Index Cond: (thousand = 42)
-                     ->  Bitmap Index Scan on tenk1_thous_tenthous
-                           Index Cond: (thousand = 41)
                      ->  Bitmap Index Scan on tenk1_thous_tenthous
                            Index Cond: ((thousand = 99) AND (tenthous = 2))
-(13 rows)
+                     ->  Bitmap Index Scan on tenk1_thous_tenthous
+                           Index Cond: (thousand = ANY ('{42,41}'::integer[]))
+(12 rows)
 
 SELECT count(*) FROM tenk1
   WHERE hundred = 42 AND (thousand = 42 OR thousand = 41 OR thousand = 99 AND tenthous = 2);
@@ -3144,6 +3196,49 @@ SELECT  b.relname,
 (2 rows)
 
 DROP TABLE concur_temp_tab_1, concur_temp_tab_2, reindex_temp_before;
+-- Check bitmap scan can consider similar OR arguments separately without
+-- grouping them into SAOP.
+CREATE TABLE bitmap_split_or (a int NOT NULL, b int NOT NULL, c int NOT NULL);
+INSERT INTO bitmap_split_or (SELECT 1, 1, i FROM generate_series(1, 1000) i);
+INSERT INTO bitmap_split_or (select i, 2, 2 FROM generate_series(1, 1000) i);
+VACUUM ANALYZE bitmap_split_or;
+CREATE INDEX t_b_partial_1_idx ON bitmap_split_or (b) WHERE a = 1;
+CREATE INDEX t_b_partial_2_idx ON bitmap_split_or (b) WHERE a = 2;
+EXPLAIN (COSTS OFF)
+SELECT * FROM bitmap_split_or WHERE (a = 1 OR a = 2) AND b = 2;
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Bitmap Heap Scan on bitmap_split_or
+   Recheck Cond: (((b = 2) AND (a = 1)) OR ((b = 2) AND (a = 2)))
+   ->  BitmapOr
+         ->  Bitmap Index Scan on t_b_partial_1_idx
+               Index Cond: (b = 2)
+         ->  Bitmap Index Scan on t_b_partial_2_idx
+               Index Cond: (b = 2)
+(7 rows)
+
+DROP INDEX t_b_partial_1_idx;
+DROP INDEX t_b_partial_2_idx;
+CREATE INDEX t_a_b_idx ON bitmap_split_or (a, b);
+CREATE INDEX t_b_c_idx ON bitmap_split_or (b, c);
+CREATE STATISTICS t_a_b_stat (mcv) ON a, b FROM bitmap_split_or;
+CREATE STATISTICS t_b_c_stat (mcv) ON b, c FROM bitmap_split_or;
+ANALYZE bitmap_split_or;
+EXPLAIN (COSTS OFF)
+SELECT * FROM bitmap_split_or WHERE a = 1 AND (b = 1 OR b = 2) AND c = 2;
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Bitmap Heap Scan on bitmap_split_or
+   Recheck Cond: (((b = 1) AND (c = 2)) OR ((a = 1) AND (b = 2)))
+   Filter: ((a = 1) AND (c = 2))
+   ->  BitmapOr
+         ->  Bitmap Index Scan on t_b_c_idx
+               Index Cond: ((b = 1) AND (c = 2))
+         ->  Bitmap Index Scan on t_a_b_idx
+               Index Cond: ((a = 1) AND (b = 2))
+(8 rows)
+
+DROP TABLE bitmap_split_or;
 --
 -- REINDEX SCHEMA
 --
index 270a7191e680261f13dbdee77bc5dd7391425753..ebf2e3f851a1f2d83cf29d5e794f1f632bffc5f8 100644 (file)
@@ -4296,20 +4296,20 @@ select * from tenk1 a join tenk1 b on
  Nested Loop
    Join Filter: (((a.unique1 = 1) AND (b.unique1 = 2)) OR ((a.unique2 = 3) AND (b.hundred = 4)))
    ->  Bitmap Heap Scan on tenk1 b
-         Recheck Cond: ((unique1 = 2) OR (hundred = 4))
+         Recheck Cond: ((hundred = 4) OR (unique1 = 2))
          ->  BitmapOr
-               ->  Bitmap Index Scan on tenk1_unique1
-                     Index Cond: (unique1 = 2)
                ->  Bitmap Index Scan on tenk1_hundred
                      Index Cond: (hundred = 4)
+               ->  Bitmap Index Scan on tenk1_unique1
+                     Index Cond: (unique1 = 2)
    ->  Materialize
          ->  Bitmap Heap Scan on tenk1 a
-               Recheck Cond: ((unique1 = 1) OR (unique2 = 3))
+               Recheck Cond: ((unique2 = 3) OR (unique1 = 1))
                ->  BitmapOr
-                     ->  Bitmap Index Scan on tenk1_unique1
-                           Index Cond: (unique1 = 1)
                      ->  Bitmap Index Scan on tenk1_unique2
                            Index Cond: (unique2 = 3)
+                     ->  Bitmap Index Scan on tenk1_unique1
+                           Index Cond: (unique1 = 1)
 (17 rows)
 
 explain (costs off)
@@ -4323,12 +4323,12 @@ select * from tenk1 a join tenk1 b on
          Filter: ((unique1 = 2) OR (ten = 4))
    ->  Materialize
          ->  Bitmap Heap Scan on tenk1 a
-               Recheck Cond: ((unique1 = 1) OR (unique2 = 3))
+               Recheck Cond: ((unique2 = 3) OR (unique1 = 1))
                ->  BitmapOr
-                     ->  Bitmap Index Scan on tenk1_unique1
-                           Index Cond: (unique1 = 1)
                      ->  Bitmap Index Scan on tenk1_unique2
                            Index Cond: (unique2 = 3)
+                     ->  Bitmap Index Scan on tenk1_unique1
+                           Index Cond: (unique1 = 1)
 (12 rows)
 
 explain (costs off)
@@ -4340,21 +4340,21 @@ select * from tenk1 a join tenk1 b on
  Nested Loop
    Join Filter: (((a.unique1 = 1) AND (b.unique1 = 2)) OR (((a.unique2 = 3) OR (a.unique2 = 7)) AND (b.hundred = 4)))
    ->  Bitmap Heap Scan on tenk1 b
-         Recheck Cond: ((unique1 = 2) OR (hundred = 4))
+         Recheck Cond: ((hundred = 4) OR (unique1 = 2))
          ->  BitmapOr
-               ->  Bitmap Index Scan on tenk1_unique1
-                     Index Cond: (unique1 = 2)
                ->  Bitmap Index Scan on tenk1_hundred
                      Index Cond: (hundred = 4)
+               ->  Bitmap Index Scan on tenk1_unique1
+                     Index Cond: (unique1 = 2)
    ->  Materialize
          ->  Bitmap Heap Scan on tenk1 a
-               Recheck Cond: ((unique1 = 1) OR ((unique2 = 3) OR (unique2 = 7)))
+               Recheck Cond: (((unique2 = 3) OR (unique2 = 7)) OR (unique1 = 1))
                Filter: ((unique1 = 1) OR (unique2 = 3) OR (unique2 = 7))
                ->  BitmapOr
-                     ->  Bitmap Index Scan on tenk1_unique1
-                           Index Cond: (unique1 = 1)
                      ->  Bitmap Index Scan on tenk1_unique2
                            Index Cond: (unique2 = ANY ('{3,7}'::integer[]))
+                     ->  Bitmap Index Scan on tenk1_unique1
+                           Index Cond: (unique1 = 1)
 (18 rows)
 
 explain (costs off)
@@ -4366,21 +4366,21 @@ select * from tenk1 a join tenk1 b on
  Nested Loop
    Join Filter: (((a.unique1 = 1) AND (b.unique1 = 2)) OR (((a.unique2 = 3) OR (a.unique2 = 7)) AND (b.hundred = 4)))
    ->  Bitmap Heap Scan on tenk1 b
-         Recheck Cond: ((unique1 = 2) OR (hundred = 4))
+         Recheck Cond: ((hundred = 4) OR (unique1 = 2))
          ->  BitmapOr
-               ->  Bitmap Index Scan on tenk1_unique1
-                     Index Cond: (unique1 = 2)
                ->  Bitmap Index Scan on tenk1_hundred
                      Index Cond: (hundred = 4)
+               ->  Bitmap Index Scan on tenk1_unique1
+                     Index Cond: (unique1 = 2)
    ->  Materialize
          ->  Bitmap Heap Scan on tenk1 a
-               Recheck Cond: ((unique1 = 1) OR ((unique2 = 3) OR (unique2 = 7)))
+               Recheck Cond: (((unique2 = 3) OR (unique2 = 7)) OR (unique1 = 1))
                Filter: ((unique1 = 1) OR (unique2 = 3) OR (unique2 = 7))
                ->  BitmapOr
-                     ->  Bitmap Index Scan on tenk1_unique1
-                           Index Cond: (unique1 = 1)
                      ->  Bitmap Index Scan on tenk1_unique2
                            Index Cond: (unique2 = ANY ('{3,7}'::integer[]))
+                     ->  Bitmap Index Scan on tenk1_unique1
+                           Index Cond: (unique1 = 1)
 (18 rows)
 
 explain (costs off)
@@ -4394,18 +4394,16 @@ select * from tenk1 a join tenk1 b on
    ->  Seq Scan on tenk1 b
    ->  Materialize
          ->  Bitmap Heap Scan on tenk1 a
-               Recheck Cond: ((unique1 < 20) OR (unique1 = 3) OR (unique1 = 1) OR ((unique2 = 3) OR (unique2 = 7)))
+               Recheck Cond: (((unique2 = 3) OR (unique2 = 7)) OR ((unique1 = 3) OR (unique1 = 1)) OR (unique1 < 20))
                Filter: ((unique1 < 20) OR (unique1 = 3) OR (unique1 = 1) OR (unique2 = 3) OR (unique2 = 7))
                ->  BitmapOr
-                     ->  Bitmap Index Scan on tenk1_unique1
-                           Index Cond: (unique1 < 20)
-                     ->  Bitmap Index Scan on tenk1_unique1
-                           Index Cond: (unique1 = 3)
-                     ->  Bitmap Index Scan on tenk1_unique1
-                           Index Cond: (unique1 = 1)
                      ->  Bitmap Index Scan on tenk1_unique2
                            Index Cond: (unique2 = ANY ('{3,7}'::integer[]))
-(16 rows)
+                     ->  Bitmap Index Scan on tenk1_unique1
+                           Index Cond: (unique1 = ANY ('{3,1}'::integer[]))
+                     ->  Bitmap Index Scan on tenk1_unique1
+                           Index Cond: (unique1 < 20)
+(14 rows)
 
 --
 -- test placement of movable quals in a parameterized join tree
index 71a7115067eea4236582cdd8e9128e5014848b8c..216bd9660c38e790c0f74d6ec2a3bb0484c6ee91 100644 (file)
@@ -738,6 +738,23 @@ SELECT * FROM tenk1
 SELECT * FROM tenk1
   WHERE thousand = 42 AND (tenthous = 1 OR tenthous = (SELECT 1 + 2) OR tenthous = 42);
 
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42 OR tenthous IS NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1::int2 OR tenthous::int2 = 3::int8 OR tenthous = 42::int8);
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1::int2 OR tenthous::int2 = 3::int8 OR tenthous::int2 = 42::int8);
+
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1::int2 OR tenthous = 3::int8 OR tenthous = 42::int8);
+
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM tenk1
   WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
@@ -1321,6 +1338,27 @@ SELECT  b.relname,
   ORDER BY 1;
 DROP TABLE concur_temp_tab_1, concur_temp_tab_2, reindex_temp_before;
 
+-- Check bitmap scan can consider similar OR arguments separately without
+-- grouping them into SAOP.
+CREATE TABLE bitmap_split_or (a int NOT NULL, b int NOT NULL, c int NOT NULL);
+INSERT INTO bitmap_split_or (SELECT 1, 1, i FROM generate_series(1, 1000) i);
+INSERT INTO bitmap_split_or (select i, 2, 2 FROM generate_series(1, 1000) i);
+VACUUM ANALYZE bitmap_split_or;
+CREATE INDEX t_b_partial_1_idx ON bitmap_split_or (b) WHERE a = 1;
+CREATE INDEX t_b_partial_2_idx ON bitmap_split_or (b) WHERE a = 2;
+EXPLAIN (COSTS OFF)
+SELECT * FROM bitmap_split_or WHERE (a = 1 OR a = 2) AND b = 2;
+DROP INDEX t_b_partial_1_idx;
+DROP INDEX t_b_partial_2_idx;
+CREATE INDEX t_a_b_idx ON bitmap_split_or (a, b);
+CREATE INDEX t_b_c_idx ON bitmap_split_or (b, c);
+CREATE STATISTICS t_a_b_stat (mcv) ON a, b FROM bitmap_split_or;
+CREATE STATISTICS t_b_c_stat (mcv) ON b, c FROM bitmap_split_or;
+ANALYZE bitmap_split_or;
+EXPLAIN (COSTS OFF)
+SELECT * FROM bitmap_split_or WHERE a = 1 AND (b = 1 OR b = 2) AND c = 2;
+DROP TABLE bitmap_split_or;
+
 --
 -- REINDEX SCHEMA
 --
index 08521d51a9bd5857015ab6a3084f25a816fce43d..b54428b38cd60a1acac9fc4c9c9ecd44b5b08af1 100644 (file)
@@ -1767,6 +1767,7 @@ OprCacheKey
 OprInfo
 OprProofCacheEntry
 OprProofCacheKey
+OrArgIndexMatch
 OuterJoinClauseInfo
 OutputPluginCallbacks
 OutputPluginOptions