On 28/3/2024 16:54, Alexander Korotkov wrote:
The current patch has a boolean guc enable_or_transformation.
However, when we have just a few ORs to be transformated, then we
should get less performance gain from the transformation and higher
chances to lose a good bitmap scan plan from that.  When there is a
huge list of ORs to be transformed, then the performance gain is
greater and it is less likely we could lose a good bitmap scan plan.

What do you think about introducing a GUC threshold value: the minimum
size of list to do OR-to-ANY transformation?
min_list_or_transformation or something.
I labelled it or_transformation_limit (see in attachment). Feel free to rename it. It's important to note that the limiting GUC doesn't operate symmetrically for forward, OR -> SAOP, and backward SAOP -> OR operations. In the forward case, it functions as you've proposed. However, in the backward case, we only check whether the feature is enabled or not. This is due to our existing limitation, MAX_SAOP_ARRAY_SIZE, and the fact that we can't match the length of the original OR list with the sizes of the resulting SAOPs. For instance, a lengthy OR list with 100 elements can be transformed into 3 SAOPs, each with a size of around 30 elements. One aspect that requires attention is the potential inefficiency of our OR -> ANY transformation when we have a number of elements less than MAX_SAOP_ARRAY_SIZE. This is because we perform a reverse transformation ANY -> OR at the stage of generating bitmap scans. If the BitmapScan path dominates, we may have done unnecessary work. Is this an occurrence that we should address? But the concern above may just be a point of improvement later: We can add one more strategy to the optimizer: testing each array element as an OR clause; we can also provide a BitmapOr path, where SAOP is covered with a minimal number of partial indexes (likewise, previous version).

--
regards,
Andrei Lepikhov
Postgres Professional
From e42a7111a12ef82eecdb2e692d65e7ba6e43ad79 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <a.rybak...@postgrespro.ru>
Date: Fri, 2 Feb 2024 22:01:09 +0300
Subject: [PATCH 1/2] Transform OR clauses to ANY expression.

Replace (expr op C1) OR (expr op C2) ... with expr op ANY(ARRAY[C1, C2, ...]) 
on the
preliminary stage of optimization when we are still working with the
expression tree.
Here C<X> is a constant expression, 'expr' is non-constant expression, 'op' is
an operator which returns boolean result and has a commuter (for the case of
reverse order of constant and non-constant parts of the expression,
like 'CX op expr').
Sometimes it can lead to not optimal plan. But we think it is better to have
array of elements instead of a lot of OR clauses. Here is a room for further
optimizations on decomposing that array into more optimal parts.
Authors: Alena Rybakina <lena.riback...@yandex.ru>, Andrey Lepikhov 
<a.lepik...@postgrespro.ru>
Reviewed-by: Peter Geoghegan <p...@bowt.ie>, Ranier Vilela <ranier...@gmail.com>
Reviewed-by: Alexander Korotkov <aekorot...@gmail.com>, Robert Haas 
<robertmh...@gmail.com>
Reviewed-by: jian he <jian.universal...@gmail.com>
---
 .../postgres_fdw/expected/postgres_fdw.out    |   8 +-
 doc/src/sgml/config.sgml                      |  18 +
 src/backend/nodes/queryjumblefuncs.c          |  27 ++
 src/backend/optimizer/prep/prepqual.c         | 379 +++++++++++++++++-
 src/backend/utils/misc/guc_tables.c           |  13 +
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/include/nodes/queryjumble.h               |   1 +
 src/include/optimizer/optimizer.h             |   2 +
 src/test/regress/expected/create_index.out    | 172 +++++++-
 src/test/regress/expected/join.out            |  60 ++-
 src/test/regress/expected/partition_prune.out | 211 +++++++++-
 src/test/regress/expected/stats_ext.out       |  12 +-
 src/test/regress/expected/tidscan.out         |  21 +-
 src/test/regress/sql/create_index.sql         |  44 ++
 src/test/regress/sql/join.sql                 |   8 +
 src/test/regress/sql/partition_prune.sql      |  18 +
 src/test/regress/sql/tidscan.sql              |   4 +
 src/tools/pgindent/typedefs.list              |   2 +
 18 files changed, 950 insertions(+), 51 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out 
b/contrib/postgres_fdw/expected/postgres_fdw.out
index b7af86d351..277ef3f385 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8853,18 +8853,18 @@ insert into utrtest values (2, 'qux');
 -- Check case where the foreign partition is a subplan target rel
 explain (verbose, costs off)
 update utrtest set a = 1 where a = 1 or a = 2 returning *;
-                                             QUERY PLAN                        
                     
-----------------------------------------------------------------------------------------------------
+                                                  QUERY PLAN                   
                               
+--------------------------------------------------------------------------------------------------------------
  Update on public.utrtest
    Output: utrtest_1.a, utrtest_1.b
    Foreign Update on public.remp utrtest_1
    Update on public.locp utrtest_2
    ->  Append
          ->  Foreign Update on public.remp utrtest_1
-               Remote SQL: UPDATE public.loct SET a = 1 WHERE (((a = 1) OR (a 
= 2))) RETURNING a, b
+               Remote SQL: UPDATE public.loct SET a = 1 WHERE ((a = ANY 
('{1,2}'::integer[]))) RETURNING a, b
          ->  Seq Scan on public.locp utrtest_2
                Output: 1, utrtest_2.tableoid, utrtest_2.ctid, NULL::record
-               Filter: ((utrtest_2.a = 1) OR (utrtest_2.a = 2))
+               Filter: (utrtest_2.a = ANY ('{1,2}'::integer[]))
 (10 rows)
 
 -- The new values are concatenated with ' triggered !'
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5468637e2e..f384edde05 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -5472,6 +5472,24 @@ ANY <replaceable 
class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-enable-or-transformation" 
xreflabel="or_transformation_limit">
+      <term><varname>or_transformation_limit</varname> (<type>boolean</type>)
+       <indexterm>
+        <primary><varname>or_transformation_limit</varname> configuration 
parameter</primary>
+       </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Sets the minimum length of arguments in an OR expression exceeding 
which
+        planner will try to lookup and group multiple similar OR expressions to
+        ANY (<xref linkend="functions-comparisons-any-some"/>) expressions.
+        The grouping technique of this transformation is based on the 
equivalence of variable sides.
+        One side of such an expression must be a constant clause, and the 
other must contain a variable clause.
+        The default is <literal>on</literal>.
+        </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-enable-parallel-append" 
xreflabel="enable_parallel_append">
       <term><varname>enable_parallel_append</varname> (<type>boolean</type>)
       <indexterm>
diff --git a/src/backend/nodes/queryjumblefuncs.c 
b/src/backend/nodes/queryjumblefuncs.c
index be823a7f8f..edcb00bd31 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -141,6 +141,33 @@ JumbleQuery(Query *query)
        return jstate;
 }
 
+JumbleState *
+JumbleExpr(Expr *expr, uint64 *queryId)
+{
+       JumbleState *jstate = NULL;
+
+       Assert(queryId != NULL);
+
+       jstate = (JumbleState *) palloc(sizeof(JumbleState));
+
+       /* Set up workspace for query jumbling */
+       jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
+       jstate->jumble_len = 0;
+       jstate->clocations_buf_size = 32;
+       jstate->clocations = (LocationLen *)
+               palloc(jstate->clocations_buf_size * sizeof(LocationLen));
+       jstate->clocations_count = 0;
+       jstate->highest_extern_param_id = 0;
+
+       /* Compute query ID */
+       _jumbleNode(jstate, (Node *) expr);
+       *queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
+                                                                               
                jstate->jumble_len,
+                                                                               
                0));
+
+       return jstate;
+}
+
 /*
  * Enables query identifier computation.
  *
diff --git a/src/backend/optimizer/prep/prepqual.c 
b/src/backend/optimizer/prep/prepqual.c
index cbcf83f847..e27b07e6a9 100644
--- a/src/backend/optimizer/prep/prepqual.c
+++ b/src/backend/optimizer/prep/prepqual.c
@@ -31,16 +31,25 @@
 
 #include "postgres.h"
 
+#include "catalog/namespace.h"
+#include "catalog/pg_operator.h"
+#include "common/hashfn.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "nodes/queryjumble.h"
 #include "optimizer/optimizer.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_oper.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
 
+int            or_transformation_limit = 0;
 
 static List *pull_ands(List *andlist);
 static List *pull_ors(List *orlist);
 static Expr *find_duplicate_ors(Expr *qual, bool is_check);
 static Expr *process_duplicate_ors(List *orlist);
+static List *or_transformation(List *orlist);
 
 
 /*
@@ -266,6 +275,357 @@ negate_clause(Node *node)
        return (Node *) make_notclause((Expr *) node);
 }
 
+typedef struct OrClauseGroupKey
+{
+       NodeTag type;
+
+       Expr   *expr; /* Pointer to the expression tree which has been a source 
for
+                                       the hashkey value */
+       Oid             opno;
+       Oid             consttype;
+       Oid             inputcollid; /* XXX: Could we lookup for common 
collation? */
+} OrClauseGroupKey;
+
+typedef struct OrClauseGroupEntry
+{
+       OrClauseGroupKey        key;
+
+       List                       *consts;
+       List                       *exprs;
+} OrClauseGroupEntry;
+
+/*
+ * Hash function to find candidate clauses.
+ */
+static uint32
+orclause_hash(const void *data, Size keysize)
+{
+       OrClauseGroupKey   *key = (OrClauseGroupKey *) data;
+       uint64                          exprHash;
+
+       Assert(keysize == sizeof(OrClauseGroupKey));
+       Assert(IsA(data, Invalid));
+
+       (void) JumbleExpr(key->expr, &exprHash);
+
+       return hash_combine((uint32) exprHash,
+                                               hash_combine((uint32) key->opno,
+                                                       hash_combine((uint32) 
key->consttype,
+                                                                               
 (uint32) key->inputcollid)));
+}
+
+static void *
+orclause_keycopy(void *dest, const void *src, Size keysize)
+{
+       OrClauseGroupKey *src_key = (OrClauseGroupKey *) src;
+       OrClauseGroupKey *dst_key = (OrClauseGroupKey *) dest;
+
+       Assert(sizeof(OrClauseGroupKey) == keysize);
+       Assert(IsA(src, Invalid));
+
+       dst_key->type = T_Invalid;
+       dst_key->expr = src_key->expr;
+       dst_key->opno = src_key->opno;
+       dst_key->consttype = src_key->consttype;
+       dst_key->inputcollid = src_key->inputcollid;
+
+       return dst_key;
+}
+
+/*
+ * Dynahash match function to use in or_group_htab
+ */
+static int
+orclause_match(const void *data1, const void *data2, Size keysize)
+{
+       OrClauseGroupKey   *key1 = (OrClauseGroupKey *) data1;
+       OrClauseGroupKey   *key2 = (OrClauseGroupKey *) data2;
+
+       Assert(sizeof(OrClauseGroupKey) == keysize);
+       Assert(IsA(key1, Invalid));
+       Assert(IsA(key2, Invalid));
+
+       if (key1->opno == key2->opno &&
+               key1->consttype == key2->consttype &&
+               key1->inputcollid == key2->inputcollid &&
+               equal(key1->expr, key2->expr))
+               return 0;
+
+       return 1;
+}
+
+/*
+ * or_transformation -
+ *       Discover the args of an OR expression and try to group similar OR
+ *       expressions to an SAOP operation.
+ *       Transformation groups two-sided equality operations. One side of such 
an
+ *       operation must be plain constant or constant expression. The other 
side of
+ *       the clause must be a variable expression without volatile functions.
+ *       To group quals, inputcollid, opno and constype of the OR OpExpr quals 
must
+ *       be equal too.
+ *       The grouping technique is based on an equivalence of variable sides 
of the
+ *       expression: using queryId and equal() routine, it groups constant 
sides of
+ *       similar clauses into an array. After the grouping procedure, each 
couple
+ *       ('variable expression' and 'constant array') form a new SAOP 
operation,
+ *       which is added to the args list of the returning expression.
+ */
+static List *
+or_transformation(List *orlist)
+{
+       List                               *neworlist = NIL;
+       List                               *entries = NIL;
+       ListCell                           *lc;
+       HASHCTL                                 info;
+       HTAB                               *or_group_htab = NULL;
+       int                                     len_ors = list_length(orlist);
+       OrClauseGroupEntry         *entry = NULL;
+
+       Assert(or_transformation_limit >= 0 && len_ors > 
or_transformation_limit);
+
+       MemSet(&info, 0, sizeof(info));
+       info.keysize = sizeof(OrClauseGroupKey);
+       info.entrysize = sizeof(OrClauseGroupEntry);
+       info.hash = orclause_hash;
+       info.keycopy = orclause_keycopy;
+       info.match = orclause_match;
+       or_group_htab = hash_create("OR Groups",
+                                                               len_ors,
+                                                               &info,
+                                                               HASH_ELEM | 
HASH_FUNCTION | HASH_COMPARE | HASH_KEYCOPY);
+
+       foreach(lc, orlist)
+       {
+               Node                               *orqual = lfirst(lc);
+               Node                               *const_expr;
+               Node                               *nconst_expr;
+               OrClauseGroupKey                hashkey;
+               bool                                    found;
+               Oid                                             opno;
+               Oid                                             consttype;
+               Node                               *leftop, *rightop;
+
+               if (!IsA(orqual, OpExpr))
+               {
+                       entries = lappend(entries, orqual);
+                       continue;
+               }
+
+               opno = ((OpExpr *) orqual)->opno;
+               if (get_op_rettype(opno) != BOOLOID)
+               {
+                       /* Only operator returning boolean suits OR -> ANY 
transformation */
+                       entries = lappend(entries, orqual);
+                       continue;
+               }
+
+               /*
+                * Detect the constant side of the clause. Recall non-constant
+                * expression can be made not only with Vars, but also with 
Params,
+                * which is not bonded with any relation. Thus, we detect the 
const
+                * side - if another side is constant too, the orqual couldn't 
be
+                * an OpExpr.
+                * Get pointers to constant and expression sides of the qual.
+                */
+               leftop = get_leftop(orqual);
+               if (IsA(leftop, RelabelType))
+                       leftop = (Node *) ((RelabelType *) leftop)->arg;
+               rightop = get_rightop(orqual);
+               if (IsA(rightop, RelabelType))
+                       rightop = (Node *) ((RelabelType *) rightop)->arg;
+
+               if (IsA(leftop, Const))
+               {
+                       opno = get_commutator(opno);
+
+                       if (!OidIsValid(opno))
+                       {
+                               /* commutator doesn't exist, we can't reverse 
the order */
+                               entries = lappend(entries, orqual);
+                               continue;
+                       }
+
+                       nconst_expr = get_rightop(orqual);
+                       const_expr = get_leftop(orqual);
+               }
+               else if (IsA(rightop, Const))
+               {
+                       const_expr = get_rightop(orqual);
+                       nconst_expr = get_leftop(orqual);
+               }
+               else
+               {
+                       entries = lappend(entries, orqual);
+                       continue;
+               }
+
+               /*
+                * Transformation only works with both side type is not
+                * { array | composite | domain | record }.
+                * Also, forbid it for volatile expressions.
+                */
+               consttype = exprType(const_expr);
+               if (type_is_rowtype(exprType(const_expr)) ||
+                       type_is_rowtype(consttype) ||
+                       contain_volatile_functions((Node *) nconst_expr))
+               {
+                       entries = lappend(entries, orqual);
+                       continue;
+               }
+
+               /*
+               * At this point we definitely have a transformable clause.
+               * Classify it and add into specific group of clauses, or create 
new
+               * group.
+               */
+               hashkey.type = T_Invalid;
+               hashkey.expr = (Expr *) nconst_expr;
+               hashkey.opno = opno;
+               hashkey.consttype = consttype;
+               hashkey.inputcollid = exprCollation(const_expr);
+               entry = hash_search(or_group_htab, &hashkey, HASH_ENTER, 
&found);
+
+               if (unlikely(found))
+               {
+                       entry->consts = lappend(entry->consts, const_expr);
+                       entry->exprs = lappend(entry->exprs, orqual);
+               }
+               else
+               {
+                       entry->consts = list_make1(const_expr);
+                       entry->exprs = list_make1(orqual);
+
+                       /*
+                        * Add the entry to the list. It is needed exclusively 
to manage the
+                        * problem with the order of transformed clauses in 
explain.
+                        * Hash value can depend on the platform and version. 
Hence,
+                        * sequental scan of the hash table would prone to 
change the order
+                        * of clauses in lists and, as a result, break 
regression tests
+                        * accidentially.
+                        */
+                       entries = lappend(entries, entry);
+               }
+       }
+
+       /* Let's convert each group of clauses to an IN operation. */
+
+       /*
+        * Go through the list of groups and convert each, where number of
+        * consts more than 1. trivial groups move to OR-list again
+        */
+       foreach (lc, entries)
+       {
+               Oid                                 scalar_type;
+               Oid                                     array_type;
+
+               if (!IsA(lfirst(lc), Invalid))
+               {
+                       neworlist = lappend(neworlist, lfirst(lc));
+                       continue;
+               }
+
+               entry = (OrClauseGroupEntry *) lfirst(lc);
+
+               Assert(list_length(entry->consts) > 0);
+               Assert(list_length(entry->exprs) == list_length(entry->consts));
+
+               if (list_length(entry->consts) == 1)
+               {
+                       /*
+                        * Only one element returns origin expression into the 
BoolExpr args
+                        * list unchanged.
+                        */
+                       list_free(entry->consts);
+                       neworlist = list_concat(neworlist, entry->exprs);
+                       continue;
+               }
+
+               /*
+                * Do the transformation.
+                */
+
+               scalar_type = entry->key.consttype;
+               array_type = OidIsValid(scalar_type) ? 
get_array_type(scalar_type) :
+                                                                               
           InvalidOid;
+
+               if (OidIsValid(array_type))
+               {
+                       /*
+                        * OK: coerce all the right-hand non-Var inputs to the 
common
+                        * type and build an ArrayExpr for them.
+                        */
+                       List                       *aexprs = NIL;
+                       ArrayExpr                  *newa = NULL;
+                       ScalarArrayOpExpr  *saopexpr = NULL;
+                       HeapTuple                       opertup;
+                       Form_pg_operator        operform;
+                       List                       *namelist = NIL;
+
+                       foreach(lc, entry->consts)
+                       {
+                               Node *node = (Node *) lfirst(lc);
+
+                               node = coerce_to_common_type(NULL, node, 
scalar_type,
+                                                                               
         "OR ANY Transformation");
+                               aexprs = lappend(aexprs, node);
+                       }
+
+                       newa = makeNode(ArrayExpr);
+                       /* array_collid will be set by parse_collate.c */
+                       newa->element_typeid = scalar_type;
+                       newa->array_typeid = array_type;
+                       newa->multidims = false;
+                       newa->elements = aexprs;
+                       newa->location = -1;
+
+                       /*
+                        * Try to cast this expression to Const. Due to current 
strict
+                        * transformation rules it should be done [almost] 
every time.
+                        */
+                       newa = (ArrayExpr *) eval_const_expressions(NULL, (Node 
*) newa);
+
+                       opertup = SearchSysCache1(OPEROID,
+                                                                         
ObjectIdGetDatum(entry->key.opno));
+                       if (!HeapTupleIsValid(opertup))
+                               elog(ERROR, "cache lookup failed for operator 
%u",
+                                        entry->key.opno);
+
+                       operform = (Form_pg_operator) GETSTRUCT(opertup);
+                       if (!OperatorIsVisible(entry->key.opno))
+                               namelist = lappend(namelist, 
makeString(get_namespace_name(operform->oprnamespace)));
+
+                       namelist = lappend(namelist, 
makeString(pstrdup(NameStr(operform->oprname))));
+                       ReleaseSysCache(opertup);
+
+                       saopexpr =
+                               (ScalarArrayOpExpr *)
+                                       make_scalar_array_op(NULL,
+                                                                               
 namelist,
+                                                                               
 true,
+                                                                               
 (Node *) entry->key.expr,
+                                                                               
 (Node *) newa,
+                                                                               
 -1);
+                       saopexpr->inputcollid = entry->key.inputcollid;
+
+                       neworlist = lappend(neworlist, (void *) saopexpr);
+               }
+               else
+               {
+                       /*
+                        * If the const node (right side of operator 
expression) 's type
+                        *  don't have “true” array type, then we cannnot do 
the transformation.
+                        * We simply concatenate the expression node.
+                        *
+                        */
+                       list_free(entry->consts);
+                       neworlist = list_concat(neworlist, entry->exprs);
+               }
+       }
+       hash_destroy(or_group_htab);
+       list_free(entries);
+
+       /* One more trick: assemble correct clause */
+       return neworlist;
+}
 
 /*
  * canonicalize_qual
@@ -604,7 +964,19 @@ process_duplicate_ors(List *orlist)
         * If no winners, we can't transform the OR
         */
        if (winners == NIL)
-               return make_orclause(orlist);
+       {
+               /*
+                * Make an attempt to group similar OR clauses into SAOP if the 
list is
+                * lengthy enough.
+                */
+               if (or_transformation_limit >= 0 &&
+                       list_length(orlist) > or_transformation_limit)
+                       orlist = or_transformation(orlist);
+
+               /* Transformation could group all OR clauses to a single SAOP */
+               return (list_length(orlist) == 1) ?
+                                                       (Expr *) 
linitial(orlist) : make_orclause(orlist);
+       }
 
        /*
         * Generate new OR list consisting of the remaining sub-clauses.
@@ -651,6 +1023,11 @@ process_duplicate_ors(List *orlist)
                }
        }
 
+       /* Make an attempt to group similar OR clauses into ANY operation */
+       if (or_transformation_limit >= 0 &&
+               list_length(neworlist) > or_transformation_limit)
+               neworlist = or_transformation(neworlist);
+
        /*
         * Append reduced OR to the winners list, if it's not degenerate, 
handling
         * the special case of one element correctly (can that really happen?).
diff --git a/src/backend/utils/misc/guc_tables.c 
b/src/backend/utils/misc/guc_tables.c
index abd9029451..a8dd701e7c 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -3626,6 +3626,19 @@ struct config_int ConfigureNamesInt[] =
                NULL, NULL, NULL
        },
 
+       {
+               {"or_transformation_limit", PGC_USERSET, QUERY_TUNING_OTHER,
+                       gettext_noop("Set the minimum length of the list of OR 
clauses to "
+                                                "attempt the transformation."),
+                       gettext_noop("The planner will try to replace 
expression like "
+                                                "'x=c1 OR x=c2 ..' to the 
expression 'x = ANY(ARRAY[c1,c2,..])'"),
+                       GUC_EXPLAIN
+               },
+               &or_transformation_limit,
+               0, -1, INT_MAX,
+               NULL, NULL, NULL
+       },
+
        /* End-of-list marker */
        {
                {NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample 
b/src/backend/utils/misc/postgresql.conf.sample
index 2244ee52f7..03745c2630 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -391,6 +391,7 @@
 # - Planner Method Configuration -
 
 #enable_async_append = on
+#or_transformation_limit = 0
 #enable_bitmapscan = on
 #enable_gathermerge = on
 #enable_hashagg = on
diff --git a/src/include/nodes/queryjumble.h b/src/include/nodes/queryjumble.h
index f1c55c8067..a9ae048af5 100644
--- a/src/include/nodes/queryjumble.h
+++ b/src/include/nodes/queryjumble.h
@@ -65,6 +65,7 @@ extern PGDLLIMPORT int compute_query_id;
 
 extern const char *CleanQuerytext(const char *query, int *location, int *len);
 extern JumbleState *JumbleQuery(Query *query);
+extern JumbleState *JumbleExpr(Expr *expr, uint64 *queryId);
 extern void EnableQueryId(void);
 
 extern PGDLLIMPORT bool query_id_enabled;
diff --git a/src/include/optimizer/optimizer.h 
b/src/include/optimizer/optimizer.h
index 7b63c5cf71..4c613401d6 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -133,6 +133,8 @@ extern void extract_query_dependencies(Node *query,
 
 /* in prep/prepqual.c: */
 
+extern PGDLLIMPORT int or_transformation_limit;
+
 extern Node *negate_clause(Node *node);
 extern Expr *canonicalize_qual(Expr *qual, bool is_check);
 
diff --git a/src/test/regress/expected/create_index.out 
b/src/test/regress/expected/create_index.out
index 70ab47a92f..e9fb82b64c 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -1836,6 +1836,72 @@ DROP TABLE onek_with_null;
 -- Check bitmap index path planning
 --
 EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
+                                  QUERY PLAN                                  
+------------------------------------------------------------------------------
+ Index Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: ((thousand = 42) AND (tenthous = ANY ('{1,3,42}'::integer[])))
+(2 rows)
+
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | 
twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+      42 |    5530 |   0 |    2 |   2 |      2 |      42 |       42 |          
42 |        42 |       42 |  84 |   85 | QBAAAA   | SEIAAA   | OOOOxx
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1
+  WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
+                                     QUERY PLAN                                
     
+------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on tenk1
+         Recheck Cond: ((hundred = 42) AND (thousand = ANY 
('{42,99}'::integer[])))
+         ->  BitmapAnd
+               ->  Bitmap Index Scan on tenk1_hundred
+                     Index Cond: (hundred = 42)
+               ->  Bitmap Index Scan on tenk1_thous_tenthous
+                     Index Cond: (thousand = ANY ('{42,99}'::integer[]))
+(8 rows)
+
+SELECT count(*) FROM tenk1
+  WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
+ count 
+-------
+    10
+(1 row)
+
+SET or_transformation_limit = 0;
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
+                                  QUERY PLAN                                  
+------------------------------------------------------------------------------
+ Index Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: ((thousand = 42) AND (tenthous = ANY ('{1,3,42}'::integer[])))
+(2 rows)
+
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | 
twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+      42 |    5530 |   0 |    2 |   2 |      2 |      42 |       42 |          
42 |        42 |       42 |  84 |   85 | QBAAAA   | SEIAAA   | OOOOxx
+(1 row)
+
+SET or_transformation_limit = 2;
+EXPLAIN (COSTS OFF) -- or_transformation still works
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
+                                  QUERY PLAN                                  
+------------------------------------------------------------------------------
+ Index Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: ((thousand = 42) AND (tenthous = ANY ('{1,3,42}'::integer[])))
+(2 rows)
+
+SET or_transformation_limit = 3;
+EXPLAIN (COSTS OFF) -- or_transformation must be disabled
 SELECT * FROM tenk1
   WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
                                                                QUERY PLAN      
                                                          
@@ -1851,38 +1917,120 @@ SELECT * FROM tenk1
                Index Cond: ((thousand = 42) AND (tenthous = 42))
 (9 rows)
 
-SELECT * FROM tenk1
-  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
- unique1 | unique2 | two | four | ten | twenty | hundred | thousand | 
twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 
----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
-      42 |    5530 |   0 |    2 |   2 |      2 |      42 |       42 |          
42 |        42 |       42 |  84 |   85 | QBAAAA   | SEIAAA   | OOOOxx
+RESET or_transformation_limit;
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1
+  WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
+                                     QUERY PLAN                                
     
+------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on tenk1
+         Recheck Cond: ((hundred = 42) AND (thousand = ANY 
('{42,99}'::integer[])))
+         ->  BitmapAnd
+               ->  Bitmap Index Scan on tenk1_hundred
+                     Index Cond: (hundred = 42)
+               ->  Bitmap Index Scan on tenk1_thous_tenthous
+                     Index Cond: (thousand = ANY ('{42,99}'::integer[]))
+(8 rows)
+
+SELECT count(*) FROM tenk1
+  WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
+ count 
+-------
+    10
 (1 row)
 
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM tenk1
-  WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
-                                   QUERY PLAN                                  
  
----------------------------------------------------------------------------------
+  WHERE hundred = 42 AND (thousand < 42 OR thousand < 99 OR 43 > thousand OR 
42 > thousand);
+                                        QUERY PLAN                             
           
+------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on tenk1
+         Recheck Cond: ((hundred = 42) AND (thousand < ANY 
('{42,99,43,42}'::integer[])))
+         ->  BitmapAnd
+               ->  Bitmap Index Scan on tenk1_hundred
+                     Index Cond: (hundred = 42)
+               ->  Bitmap Index Scan on tenk1_thous_tenthous
+                     Index Cond: (thousand < ANY ('{42,99,43,42}'::integer[]))
+(8 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3) OR thousand = 41;
+                                               QUERY PLAN                      
                         
+--------------------------------------------------------------------------------------------------------
  Aggregate
    ->  Bitmap Heap Scan on tenk1
-         Recheck Cond: ((hundred = 42) AND ((thousand = 42) OR (thousand = 
99)))
+         Recheck Cond: (((thousand = 42) AND (tenthous = ANY 
('{1,3}'::integer[]))) OR (thousand = 41))
+         ->  BitmapOr
+               ->  Bitmap Index Scan on tenk1_thous_tenthous
+                     Index Cond: ((thousand = 42) AND (tenthous = ANY 
('{1,3}'::integer[])))
+               ->  Bitmap Index Scan on tenk1_thous_tenthous
+                     Index Cond: (thousand = 41)
+(8 rows)
+
+SELECT count(*) FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3) OR thousand = 41;
+ count 
+-------
+    10
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1
+  WHERE hundred = 42 AND (thousand = 42 OR thousand = 99 OR tenthous < 2) OR 
thousand = 41;
+                                                         QUERY PLAN            
                                              
+-----------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on tenk1
+         Recheck Cond: (((hundred = 42) AND ((thousand = ANY 
('{42,99}'::integer[])) 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 = 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)
+(14 rows)
+
+SELECT count(*) FROM tenk1
+  WHERE hundred = 42 AND (thousand = 42 OR thousand = 99 OR tenthous < 2) OR 
thousand = 41;
+ count 
+-------
+    20
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1
+  WHERE hundred = 42 AND (thousand = 42 OR thousand = 41 OR thousand = 99 AND 
tenthous = 2);
+                                                          QUERY PLAN           
                                               
+------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on tenk1
+         Recheck Cond: ((hundred = 42) AND ((thousand = ANY 
('{42,41}'::integer[])) 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)
+                           Index Cond: (thousand = ANY ('{42,41}'::integer[]))
                      ->  Bitmap Index Scan on tenk1_thous_tenthous
-                           Index Cond: (thousand = 99)
+                           Index Cond: ((thousand = 99) AND (tenthous = 2))
 (11 rows)
 
 SELECT count(*) FROM tenk1
-  WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
+  WHERE hundred = 42 AND (thousand = 42 OR thousand = 41 OR thousand = 99 AND 
tenthous = 2);
  count 
 -------
     10
 (1 row)
 
+RESET or_transformation_limit;
 --
 -- Check behavior with duplicate index column contents
 --
diff --git a/src/test/regress/expected/join.out 
b/src/test/regress/expected/join.out
index 63cddac0d6..41e2a5ed98 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -4210,10 +4210,10 @@ explain (costs off)
 select * from tenk1 a join tenk1 b on
   (a.unique1 = 1 and b.unique1 = 2) or
   ((a.unique2 = 3 or a.unique2 = 7) and b.hundred = 4);
-                                                      QUERY PLAN               
                                       
-----------------------------------------------------------------------------------------------------------------------
+                                                       QUERY PLAN              
                                         
+------------------------------------------------------------------------------------------------------------------------
  Nested Loop
-   Join Filter: (((a.unique1 = 1) AND (b.unique1 = 2)) OR (((a.unique2 = 3) OR 
(a.unique2 = 7)) AND (b.hundred = 4)))
+   Join Filter: (((a.unique1 = 1) AND (b.unique1 = 2)) OR ((a.unique2 = ANY 
('{3,7}'::integer[])) AND (b.hundred = 4)))
    ->  Bitmap Heap Scan on tenk1 b
          Recheck Cond: ((unique1 = 2) OR (hundred = 4))
          ->  BitmapOr
@@ -4223,15 +4223,61 @@ select * from tenk1 a join tenk1 b on
                      Index Cond: (hundred = 4)
    ->  Materialize
          ->  Bitmap Heap Scan on tenk1 a
-               Recheck Cond: ((unique1 = 1) OR (unique2 = 3) OR (unique2 = 7))
+               Recheck Cond: ((unique1 = 1) OR (unique2 = ANY 
('{3,7}'::integer[])))
                ->  BitmapOr
                      ->  Bitmap Index Scan on tenk1_unique1
                            Index Cond: (unique1 = 1)
                      ->  Bitmap Index Scan on tenk1_unique2
-                           Index Cond: (unique2 = 3)
+                           Index Cond: (unique2 = ANY ('{3,7}'::integer[]))
+(17 rows)
+
+explain (costs off)
+select * from tenk1 a join tenk1 b on
+  (a.unique1 = 1 and b.unique1 = 2) or
+  ((a.unique2 = 3 or a.unique2 = 7) and b.hundred = 4);
+                                                       QUERY PLAN              
                                         
+------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Join Filter: (((a.unique1 = 1) AND (b.unique1 = 2)) OR ((a.unique2 = ANY 
('{3,7}'::integer[])) AND (b.hundred = 4)))
+   ->  Bitmap Heap Scan on tenk1 b
+         Recheck Cond: ((unique1 = 2) OR (hundred = 4))
+         ->  BitmapOr
+               ->  Bitmap Index Scan on tenk1_unique1
+                     Index Cond: (unique1 = 2)
+               ->  Bitmap Index Scan on tenk1_hundred
+                     Index Cond: (hundred = 4)
+   ->  Materialize
+         ->  Bitmap Heap Scan on tenk1 a
+               Recheck Cond: ((unique1 = 1) OR (unique2 = ANY 
('{3,7}'::integer[])))
+               ->  BitmapOr
+                     ->  Bitmap Index Scan on tenk1_unique1
+                           Index Cond: (unique1 = 1)
                      ->  Bitmap Index Scan on tenk1_unique2
-                           Index Cond: (unique2 = 7)
-(19 rows)
+                           Index Cond: (unique2 = ANY ('{3,7}'::integer[]))
+(17 rows)
+
+explain (costs off)
+select * from tenk1 a join tenk1 b on
+  (a.unique1 < 20 or a.unique1 = 3 or a.unique1 = 1 and b.unique1 = 2) or
+  ((a.unique2 = 3 or a.unique2 = 7) and b.hundred = 4);
+                                                                          
QUERY PLAN                                                                      
     
+---------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+   Join Filter: ((a.unique1 < 20) OR (a.unique1 = 3) OR ((a.unique1 = 1) AND 
(b.unique1 = 2)) OR ((a.unique2 = ANY ('{3,7}'::integer[])) AND (b.hundred = 
4)))
+   ->  Seq Scan on tenk1 b
+   ->  Materialize
+         ->  Bitmap Heap Scan on tenk1 a
+               Recheck Cond: ((unique1 < 20) OR (unique1 = 3) OR (unique1 = 1) 
OR (unique2 = ANY ('{3,7}'::integer[])))
+               ->  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[]))
+(15 rows)
 
 --
 -- test placement of movable quals in a parameterized join tree
diff --git a/src/test/regress/expected/partition_prune.out 
b/src/test/regress/expected/partition_prune.out
index 46b78ba3c4..388b7f838b 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -82,23 +82,43 @@ explain (costs off) select * from lp where a is null;
 (2 rows)
 
 explain (costs off) select * from lp where a = 'a' or a = 'c';
-                        QUERY PLAN                        
-----------------------------------------------------------
+                  QUERY PLAN                   
+-----------------------------------------------
  Append
    ->  Seq Scan on lp_ad lp_1
-         Filter: ((a = 'a'::bpchar) OR (a = 'c'::bpchar))
+         Filter: (a = ANY ('{a,c}'::bpchar[]))
    ->  Seq Scan on lp_bc lp_2
-         Filter: ((a = 'a'::bpchar) OR (a = 'c'::bpchar))
+         Filter: (a = ANY ('{a,c}'::bpchar[]))
 (5 rows)
 
 explain (costs off) select * from lp where a is not null and (a = 'a' or a = 
'c');
-                                   QUERY PLAN                                  
 
---------------------------------------------------------------------------------
+                             QUERY PLAN                              
+---------------------------------------------------------------------
  Append
    ->  Seq Scan on lp_ad lp_1
-         Filter: ((a IS NOT NULL) AND ((a = 'a'::bpchar) OR (a = 'c'::bpchar)))
+         Filter: ((a IS NOT NULL) AND (a = ANY ('{a,c}'::bpchar[])))
    ->  Seq Scan on lp_bc lp_2
-         Filter: ((a IS NOT NULL) AND ((a = 'a'::bpchar) OR (a = 'c'::bpchar)))
+         Filter: ((a IS NOT NULL) AND (a = ANY ('{a,c}'::bpchar[])))
+(5 rows)
+
+explain (costs off) select * from lp where a = 'a' or a = 'c';
+                  QUERY PLAN                   
+-----------------------------------------------
+ Append
+   ->  Seq Scan on lp_ad lp_1
+         Filter: (a = ANY ('{a,c}'::bpchar[]))
+   ->  Seq Scan on lp_bc lp_2
+         Filter: (a = ANY ('{a,c}'::bpchar[]))
+(5 rows)
+
+explain (costs off) select * from lp where a is not null and (a = 'a' or a = 
'c');
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Append
+   ->  Seq Scan on lp_ad lp_1
+         Filter: ((a IS NOT NULL) AND (a = ANY ('{a,c}'::bpchar[])))
+   ->  Seq Scan on lp_bc lp_2
+         Filter: ((a IS NOT NULL) AND (a = ANY ('{a,c}'::bpchar[])))
 (5 rows)
 
 explain (costs off) select * from lp where a <> 'g';
@@ -515,10 +535,10 @@ explain (costs off) select * from rlp where a <= 31;
 (27 rows)
 
 explain (costs off) select * from rlp where a = 1 or a = 7;
-           QUERY PLAN           
---------------------------------
+                QUERY PLAN                
+------------------------------------------
  Seq Scan on rlp2 rlp
-   Filter: ((a = 1) OR (a = 7))
+   Filter: (a = ANY ('{1,7}'::integer[]))
 (2 rows)
 
 explain (costs off) select * from rlp where a = 1 or b = 'ab';
@@ -596,13 +616,13 @@ explain (costs off) select * from rlp where a < 1 or (a > 
20 and a < 25);
 
 -- where clause contradicts sub-partition's constraint
 explain (costs off) select * from rlp where a = 20 or a = 40;
-               QUERY PLAN               
-----------------------------------------
+                    QUERY PLAN                    
+--------------------------------------------------
  Append
    ->  Seq Scan on rlp4_1 rlp_1
-         Filter: ((a = 20) OR (a = 40))
+         Filter: (a = ANY ('{20,40}'::integer[]))
    ->  Seq Scan on rlp5_default rlp_2
-         Filter: ((a = 20) OR (a = 40))
+         Filter: (a = ANY ('{20,40}'::integer[]))
 (5 rows)
 
 explain (costs off) select * from rlp3 where a = 20;   /* empty */
@@ -671,6 +691,161 @@ explain (costs off) select * from rlp where (a = 1 and a 
= 3) or (a > 1 and a =
          Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
 (11 rows)
 
+explain (costs off) select * from rlp where a = 1 or a = 7;
+                QUERY PLAN                
+------------------------------------------
+ Seq Scan on rlp2 rlp
+   Filter: (a = ANY ('{1,7}'::integer[]))
+(2 rows)
+
+explain (costs off) select * from rlp where a = 1 or b = 'ab';
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Append
+   ->  Seq Scan on rlp1 rlp_1
+         Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+   ->  Seq Scan on rlp2 rlp_2
+         Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+   ->  Seq Scan on rlp3abcd rlp_3
+         Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+   ->  Seq Scan on rlp4_1 rlp_4
+         Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+   ->  Seq Scan on rlp4_2 rlp_5
+         Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+   ->  Seq Scan on rlp4_default rlp_6
+         Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+   ->  Seq Scan on rlp5_1 rlp_7
+         Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+   ->  Seq Scan on rlp5_default rlp_8
+         Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+   ->  Seq Scan on rlp_default_10 rlp_9
+         Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+   ->  Seq Scan on rlp_default_30 rlp_10
+         Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+   ->  Seq Scan on rlp_default_null rlp_11
+         Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+   ->  Seq Scan on rlp_default_default rlp_12
+         Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+(25 rows)
+
+explain (costs off) select * from rlp where a > 20 and a < 27;
+               QUERY PLAN                
+-----------------------------------------
+ Append
+   ->  Seq Scan on rlp4_1 rlp_1
+         Filter: ((a > 20) AND (a < 27))
+   ->  Seq Scan on rlp4_2 rlp_2
+         Filter: ((a > 20) AND (a < 27))
+(5 rows)
+
+explain (costs off) select * from rlp where a = 29;
+          QUERY PLAN          
+------------------------------
+ Seq Scan on rlp4_default rlp
+   Filter: (a = 29)
+(2 rows)
+
+explain (costs off) select * from rlp where a >= 29;
+                 QUERY PLAN                  
+---------------------------------------------
+ Append
+   ->  Seq Scan on rlp4_default rlp_1
+         Filter: (a >= 29)
+   ->  Seq Scan on rlp5_1 rlp_2
+         Filter: (a >= 29)
+   ->  Seq Scan on rlp5_default rlp_3
+         Filter: (a >= 29)
+   ->  Seq Scan on rlp_default_30 rlp_4
+         Filter: (a >= 29)
+   ->  Seq Scan on rlp_default_default rlp_5
+         Filter: (a >= 29)
+(11 rows)
+
+explain (costs off) select * from rlp where a < 1 or (a > 20 and a < 25);
+                      QUERY PLAN                      
+------------------------------------------------------
+ Append
+   ->  Seq Scan on rlp1 rlp_1
+         Filter: ((a < 1) OR ((a > 20) AND (a < 25)))
+   ->  Seq Scan on rlp4_1 rlp_2
+         Filter: ((a < 1) OR ((a > 20) AND (a < 25)))
+(5 rows)
+
+explain (costs off) select * from rlp where a = 20 or a = 40;
+                    QUERY PLAN                    
+--------------------------------------------------
+ Append
+   ->  Seq Scan on rlp4_1 rlp_1
+         Filter: (a = ANY ('{20,40}'::integer[]))
+   ->  Seq Scan on rlp5_default rlp_2
+         Filter: (a = ANY ('{20,40}'::integer[]))
+(5 rows)
+
+explain (costs off) select * from rlp3 where a = 20;   /* empty */
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
+
+explain (costs off) select * from rlp where a > 1 and a = 10;  /* only default 
*/
+            QUERY PLAN            
+----------------------------------
+ Seq Scan on rlp_default_10 rlp
+   Filter: ((a > 1) AND (a = 10))
+(2 rows)
+
+explain (costs off) select * from rlp where a > 1 and a >=15;  /* rlp3 
onwards, including default */
+                  QUERY PLAN                  
+----------------------------------------------
+ Append
+   ->  Seq Scan on rlp3abcd rlp_1
+         Filter: ((a > 1) AND (a >= 15))
+   ->  Seq Scan on rlp3efgh rlp_2
+         Filter: ((a > 1) AND (a >= 15))
+   ->  Seq Scan on rlp3nullxy rlp_3
+         Filter: ((a > 1) AND (a >= 15))
+   ->  Seq Scan on rlp3_default rlp_4
+         Filter: ((a > 1) AND (a >= 15))
+   ->  Seq Scan on rlp4_1 rlp_5
+         Filter: ((a > 1) AND (a >= 15))
+   ->  Seq Scan on rlp4_2 rlp_6
+         Filter: ((a > 1) AND (a >= 15))
+   ->  Seq Scan on rlp4_default rlp_7
+         Filter: ((a > 1) AND (a >= 15))
+   ->  Seq Scan on rlp5_1 rlp_8
+         Filter: ((a > 1) AND (a >= 15))
+   ->  Seq Scan on rlp5_default rlp_9
+         Filter: ((a > 1) AND (a >= 15))
+   ->  Seq Scan on rlp_default_30 rlp_10
+         Filter: ((a > 1) AND (a >= 15))
+   ->  Seq Scan on rlp_default_default rlp_11
+         Filter: ((a > 1) AND (a >= 15))
+(23 rows)
+
+explain (costs off) select * from rlp where a = 1 and a = 3;   /* empty */
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
+
+explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a 
= 15);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Append
+   ->  Seq Scan on rlp2 rlp_1
+         Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
+   ->  Seq Scan on rlp3abcd rlp_2
+         Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
+   ->  Seq Scan on rlp3efgh rlp_3
+         Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
+   ->  Seq Scan on rlp3nullxy rlp_4
+         Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
+   ->  Seq Scan on rlp3_default rlp_5
+         Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
+(11 rows)
+
 -- multi-column keys
 create table mc3p (a int, b int, c int) partition by range (a, abs(b), c);
 create table mc3p_default partition of mc3p default;
@@ -2072,10 +2247,10 @@ explain (costs off) select * from hp where a = 1 and b 
= 'abcde';
 
 explain (costs off) select * from hp where a = 1 and b = 'abcde' and
   (c = 2 or c = 3);
-                              QUERY PLAN                              
-----------------------------------------------------------------------
+                                   QUERY PLAN                                  
 
+--------------------------------------------------------------------------------
  Seq Scan on hp2 hp
-   Filter: ((a = 1) AND (b = 'abcde'::text) AND ((c = 2) OR (c = 3)))
+   Filter: ((c = ANY ('{2,3}'::integer[])) AND (a = 1) AND (b = 'abcde'::text))
 (2 rows)
 
 drop table hp2;
diff --git a/src/test/regress/expected/stats_ext.out 
b/src/test/regress/expected/stats_ext.out
index 10903bdab0..6f55b9e3ec 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -1322,19 +1322,19 @@ SELECT * FROM check_estimated_rows('SELECT * FROM 
functional_dependencies WHERE
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies 
WHERE (a = 1 OR a = 51) AND b = ''1''');
  estimated | actual 
 -----------+--------
-        99 |    100
+       100 |    100
 (1 row)
 
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies 
WHERE (a = 1 OR a = 51) AND (b = ''1'' OR b = ''2'')');
  estimated | actual 
 -----------+--------
-        99 |    100
+       100 |    100
 (1 row)
 
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies 
WHERE (a = 1 OR a = 2 OR a = 51 OR a = 52) AND (b = ''1'' OR b = ''2'')');
  estimated | actual 
 -----------+--------
-       197 |    200
+       200 |    200
 (1 row)
 
 -- OR clauses referencing different attributes are incompatible
@@ -1664,19 +1664,19 @@ SELECT * FROM check_estimated_rows('SELECT * FROM 
functional_dependencies WHERE
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies 
WHERE ((a * 2) = 2 OR (a * 2) = 102) AND upper(b) = ''1''');
  estimated | actual 
 -----------+--------
-        99 |    100
+       100 |    100
 (1 row)
 
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies 
WHERE ((a * 2) = 2 OR (a * 2) = 102) AND (upper(b) = ''1'' OR upper(b) = 
''2'')');
  estimated | actual 
 -----------+--------
-        99 |    100
+       100 |    100
 (1 row)
 
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies 
WHERE ((a * 2) = 2 OR (a * 2) = 4 OR (a * 2) = 102 OR (a * 2) = 104) AND 
(upper(b) = ''1'' OR upper(b) = ''2'')');
  estimated | actual 
 -----------+--------
-       197 |    200
+       200 |    200
 (1 row)
 
 -- OR clauses referencing different attributes
diff --git a/src/test/regress/expected/tidscan.out 
b/src/test/regress/expected/tidscan.out
index f133b5a4ac..f112e64a87 100644
--- a/src/test/regress/expected/tidscan.out
+++ b/src/test/regress/expected/tidscan.out
@@ -43,10 +43,25 @@ SELECT ctid, * FROM tidscan WHERE '(0,1)' = ctid;
 -- OR'd clauses
 EXPLAIN (COSTS OFF)
 SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
-                          QUERY PLAN                          
---------------------------------------------------------------
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Tid Scan on tidscan
+   TID Cond: (ctid = ANY ('{"(0,2)","(0,1)"}'::tid[]))
+(2 rows)
+
+SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
+ ctid  | id 
+-------+----
+ (0,1) |  1
+ (0,2) |  2
+(2 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
+                      QUERY PLAN                       
+-------------------------------------------------------
  Tid Scan on tidscan
-   TID Cond: ((ctid = '(0,2)'::tid) OR ('(0,1)'::tid = ctid))
+   TID Cond: (ctid = ANY ('{"(0,2)","(0,1)"}'::tid[]))
 (2 rows)
 
 SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
diff --git a/src/test/regress/sql/create_index.sql 
b/src/test/regress/sql/create_index.sql
index d49ce9f300..2f691efcc5 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -737,6 +737,50 @@ SELECT count(*) FROM tenk1
 SELECT count(*) FROM tenk1
   WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
 
+SET or_transformation_limit = 0;
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
+SET or_transformation_limit = 2;
+EXPLAIN (COSTS OFF) -- or_transformation still works
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
+SET or_transformation_limit = 3;
+EXPLAIN (COSTS OFF) -- or_transformation must be disabled
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
+RESET or_transformation_limit;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1
+  WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
+SELECT count(*) FROM tenk1
+  WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1
+  WHERE hundred = 42 AND (thousand < 42 OR thousand < 99 OR 43 > thousand OR 
42 > thousand);
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3) OR thousand = 41;
+SELECT count(*) FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3) OR thousand = 41;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1
+  WHERE hundred = 42 AND (thousand = 42 OR thousand = 99 OR tenthous < 2) OR 
thousand = 41;
+SELECT count(*) FROM tenk1
+  WHERE hundred = 42 AND (thousand = 42 OR thousand = 99 OR tenthous < 2) OR 
thousand = 41;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1
+  WHERE hundred = 42 AND (thousand = 42 OR thousand = 41 OR thousand = 99 AND 
tenthous = 2);
+SELECT count(*) FROM tenk1
+  WHERE hundred = 42 AND (thousand = 42 OR thousand = 41 OR thousand = 99 AND 
tenthous = 2);
+RESET or_transformation_limit;
+
 --
 -- Check behavior with duplicate index column contents
 --
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index c4c6c7b8ba..8158a7dd24 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -1408,6 +1408,14 @@ explain (costs off)
 select * from tenk1 a join tenk1 b on
   (a.unique1 = 1 and b.unique1 = 2) or
   ((a.unique2 = 3 or a.unique2 = 7) and b.hundred = 4);
+explain (costs off)
+select * from tenk1 a join tenk1 b on
+  (a.unique1 = 1 and b.unique1 = 2) or
+  ((a.unique2 = 3 or a.unique2 = 7) and b.hundred = 4);
+explain (costs off)
+select * from tenk1 a join tenk1 b on
+  (a.unique1 < 20 or a.unique1 = 3 or a.unique1 = 1 and b.unique1 = 2) or
+  ((a.unique2 = 3 or a.unique2 = 7) and b.hundred = 4);
 
 --
 -- test placement of movable quals in a parameterized join tree
diff --git a/src/test/regress/sql/partition_prune.sql 
b/src/test/regress/sql/partition_prune.sql
index dc71693861..e677ce7250 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -21,6 +21,10 @@ explain (costs off) select * from lp where a is not null;
 explain (costs off) select * from lp where a is null;
 explain (costs off) select * from lp where a = 'a' or a = 'c';
 explain (costs off) select * from lp where a is not null and (a = 'a' or a = 
'c');
+
+explain (costs off) select * from lp where a = 'a' or a = 'c';
+explain (costs off) select * from lp where a is not null and (a = 'a' or a = 
'c');
+
 explain (costs off) select * from lp where a <> 'g';
 explain (costs off) select * from lp where a <> 'a' and a <> 'd';
 explain (costs off) select * from lp where a not in ('a', 'd');
@@ -99,6 +103,20 @@ explain (costs off) select * from rlp where a > 1 and a 
>=15;       /* rlp3 onwards, i
 explain (costs off) select * from rlp where a = 1 and a = 3;   /* empty */
 explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a 
= 15);
 
+
+explain (costs off) select * from rlp where a = 1 or a = 7;
+explain (costs off) select * from rlp where a = 1 or b = 'ab';
+explain (costs off) select * from rlp where a > 20 and a < 27;
+explain (costs off) select * from rlp where a = 29;
+explain (costs off) select * from rlp where a >= 29;
+explain (costs off) select * from rlp where a < 1 or (a > 20 and a < 25);
+explain (costs off) select * from rlp where a = 20 or a = 40;
+explain (costs off) select * from rlp3 where a = 20;   /* empty */
+explain (costs off) select * from rlp where a > 1 and a = 10;  /* only default 
*/
+explain (costs off) select * from rlp where a > 1 and a >=15;  /* rlp3 
onwards, including default */
+explain (costs off) select * from rlp where a = 1 and a = 3;   /* empty */
+explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a 
= 15);
+
 -- multi-column keys
 create table mc3p (a int, b int, c int) partition by range (a, abs(b), c);
 create table mc3p_default partition of mc3p default;
diff --git a/src/test/regress/sql/tidscan.sql b/src/test/regress/sql/tidscan.sql
index 313e0fb9b6..283e026751 100644
--- a/src/test/regress/sql/tidscan.sql
+++ b/src/test/regress/sql/tidscan.sql
@@ -22,6 +22,10 @@ EXPLAIN (COSTS OFF)
 SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
 SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
 
+EXPLAIN (COSTS OFF)
+SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
+SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
+
 -- ctid = ScalarArrayOp - implemented as tidscan
 EXPLAIN (COSTS OFF)
 SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,1)', '(0,2)']::tid[]);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cfa9d5aaea..4f7925d78c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1672,6 +1672,8 @@ NumericVar
 OM_uint32
 OP
 OSAPerGroupState
+OrClauseGroupEntry
+OrClauseGroupKey
 OSAPerQueryState
 OSInfo
 OSSLCipher
-- 
2.44.0

From 37831eb47fe890d41e5f66bedc5d616c0f1ad570 Mon Sep 17 00:00:00 2001
From: "Andrey V. Lepikhov" <a.lepik...@postgrespro.ru>
Date: Wed, 13 Mar 2024 12:26:02 +0700
Subject: [PATCH 2/2] Teach generate_bitmap_or_paths to build BitmapOr paths
 over SAOP clauses.

Likewise OR clauses, discover SAOP array and try to split its elements
between smaller sized arrays to fit a set of partial indexes.
---
 doc/src/sgml/config.sgml                   |   3 +
 src/backend/optimizer/path/indxpath.c      |  82 +++++-
 src/backend/optimizer/util/predtest.c      |  37 +++
 src/backend/optimizer/util/restrictinfo.c  |  13 +
 src/include/optimizer/optimizer.h          |   3 +
 src/include/optimizer/restrictinfo.h       |   1 +
 src/test/regress/expected/create_index.out |  24 +-
 src/test/regress/expected/select.out       | 280 +++++++++++++++++++++
 src/test/regress/sql/select.sql            |  82 ++++++
 9 files changed, 508 insertions(+), 17 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index f384edde05..5772574634 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -5486,6 +5486,9 @@ ANY <replaceable class="parameter">num_sync</replaceable> 
( <replaceable class="
         The grouping technique of this transformation is based on the 
equivalence of variable sides.
         One side of such an expression must be a constant clause, and the 
other must contain a variable clause.
         The default is <literal>on</literal>.
+        Also, during BitmapScan paths generation it enables analysis of 
elements
+        of IN or ANY constant arrays to cover such clause with BitmapOr set of
+        partial index scans.
         </para>
       </listitem>
      </varlistentry>
diff --git a/src/backend/optimizer/path/indxpath.c 
b/src/backend/optimizer/path/indxpath.c
index 32c6a8bbdc..f78dae25ff 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -32,6 +32,7 @@
 #include "optimizer/paths.h"
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
+#include "utils/array.h"
 #include "utils/lsyscache.h"
 #include "utils/selfuncs.h"
 
@@ -1220,11 +1221,73 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
        return result;
 }
 
+/*
+ * Expand SAOP node to use it in bitmapscan path building routine.
+ *
+ * If RestrictInfo is an OR bool expression, extract each SAOP from the list of
+ * arguments, if possible.
+ * Working jointly with the TransformOrExprToANY routine, it provides a user
+ * with some sort of independence of the query plan from the approach to 
writing
+ * alternatives for the same entity in the WHERE section.
+ */
+static List *
+extract_saop_ors(PlannerInfo *root, RestrictInfo *rinfo)
+{
+       List               *orlist = NIL;
+       List               *result = NIL;
+       ListCell           *lc;
+
+       Assert(IsA(rinfo, RestrictInfo));
+
+       if (restriction_is_or_clause(rinfo))
+               orlist = ((BoolExpr *) rinfo->orclause)->args;
+
+       /*
+        * Don't spend cycles here if the transformation is disabled.
+        * We don't behave symmetrically here with the OR -> ANY 
transformation, see
+        * the process_duplicate_ors routine, because origin list of ORs and
+        * resulting SAOP args list lengths can differ significantly. Moreover,
+        * Here we already limited by the MAX_SAOP_ARRAY_SIZE value.
+        */
+       if (or_transformation_limit < 0)
+               return orlist;
+
+       if (restriction_is_saop_clause(rinfo))
+       {
+               result = transform_saop_to_ors(root, rinfo);
+               return result;
+       }
+
+       foreach(lc, orlist)
+       {
+               Expr *expr = (Expr *) lfirst(lc);
+
+               if (IsA(expr, RestrictInfo) && 
restriction_is_saop_clause((RestrictInfo *) expr))
+               {
+                       List *sublist;
+
+                       sublist = extract_saop_ors(root, (RestrictInfo *) 
lfirst(lc));
+                       if (sublist != NIL)
+                       {
+                               result = list_concat(result, sublist);
+                               continue;
+                       }
+
+                       /* Need to return expr to the result list */
+               }
+
+               result = lappend(result, expr);
+       }
+
+       return result;
+}
+
 /*
  * generate_bitmap_or_paths
- *             Look through the list of clauses to find OR clauses, and 
generate
- *             a BitmapOrPath for each one we can handle that way.  Return a 
list
- *             of the generated BitmapOrPaths.
+ *             Look through the list of clauses to find OR and SAOP clauses, 
and
+ *             Each saop clause are splitted to be covered by partial indexes.
+ *             generate a BitmapOrPath for each one we can handle that way.
+ *             Return a list of the generated BitmapOrPaths.
  *
  * other_clauses is a list of additional clauses that can be assumed true
  * for the purpose of generating indexquals, but are not to be searched for
@@ -1247,20 +1310,25 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo 
*rel,
        foreach(lc, clauses)
        {
                RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
-               List       *pathlist;
+               List       *pathlist = NIL;
                Path       *bitmapqual;
                ListCell   *j;
+               List       *orlist = NIL;
 
-               /* Ignore RestrictInfos that aren't ORs */
-               if (!restriction_is_or_clause(rinfo))
+               orlist = extract_saop_ors(root, rinfo);
+               if (orlist == NIL)
+                       /* Ignore RestrictInfo that doesn't provide proper OR 
list */
                        continue;
 
+               /* SAOP have splitted, remove already redundant clause */
+               all_clauses = list_delete(all_clauses, rinfo);
+
                /*
                 * We must be able to match at least one index to each of the 
arms of
                 * the OR, else we can't use it.
                 */
                pathlist = NIL;
-               foreach(j, ((BoolExpr *) rinfo->orclause)->args)
+               foreach(j, orlist)
                {
                        Node       *orarg = (Node *) lfirst(j);
                        List       *indlist;
diff --git a/src/backend/optimizer/util/predtest.c 
b/src/backend/optimizer/util/predtest.c
index 6e3b376f3d..fe8862b0a0 100644
--- a/src/backend/optimizer/util/predtest.c
+++ b/src/backend/optimizer/util/predtest.c
@@ -111,6 +111,43 @@ static bool operator_same_subexprs_lookup(Oid pred_op, Oid 
clause_op,
 static Oid     get_btree_test_op(Oid pred_op, Oid clause_op, bool refute_it);
 static void InvalidateOprProofCacheCallBack(Datum arg, int cacheid, uint32 
hashvalue);
 
+/*
+ * Expand a SAOP operation into the list of OR expressions
+ */
+List *
+transform_saop_to_ors(PlannerInfo *root, RestrictInfo *rinfo)
+{
+       PredIterInfoData        clause_info;
+       List                       *orlist = NIL;
+       Node                       *saop = (Node *) rinfo->clause;
+
+       Assert(IsA(saop, ScalarArrayOpExpr));
+
+       if (predicate_classify(saop, &clause_info) != CLASS_OR)
+               return NIL;
+
+       iterate_begin(pitem, saop, clause_info)
+       {
+               RestrictInfo   *rinfo1;
+
+               /* Predicate is found. Add the elem to the saop clause */
+               Assert(IsA(pitem, OpExpr));
+
+               /* Extract constant from the expression */
+               rinfo1 = make_restrictinfo(root, (Expr *) copyObject(pitem),
+                                                                  
rinfo->is_pushed_down,
+                                                                  
rinfo->has_clone, rinfo->is_clone,
+                                                                  
rinfo->pseudoconstant,
+                                                                  
rinfo->security_level,
+                                                                  
rinfo->required_relids,
+                                                                  
rinfo->incompatible_relids,
+                                                                  
rinfo->outer_relids);
+               orlist = lappend(orlist, rinfo1);
+       }
+       iterate_end(clause_info);
+
+       return orlist;
+}
 
 /*
  * predicate_implied_by
diff --git a/src/backend/optimizer/util/restrictinfo.c 
b/src/backend/optimizer/util/restrictinfo.c
index 0b406e9334..1dad1dc654 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -421,6 +421,19 @@ restriction_is_or_clause(RestrictInfo *restrictinfo)
                return false;
 }
 
+bool
+restriction_is_saop_clause(RestrictInfo *restrictinfo)
+{
+       if (restrictinfo->clause && IsA(restrictinfo->clause, 
ScalarArrayOpExpr))
+       {
+               ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) 
restrictinfo->clause;
+
+               if (saop->useOr)
+                       return true;
+       }
+       return false;
+}
+
 /*
  * restriction_is_securely_promotable
  *
diff --git a/src/include/optimizer/optimizer.h 
b/src/include/optimizer/optimizer.h
index 4c613401d6..4ca26f65d1 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -23,6 +23,7 @@
 #define OPTIMIZER_H
 
 #include "nodes/parsenodes.h"
+#include "optimizer/restrictinfo.h"
 
 /*
  * We don't want to include nodes/pathnodes.h here, because non-planner
@@ -161,6 +162,8 @@ extern List *expand_function_arguments(List *args, bool 
include_out_arguments,
 
 /* in util/predtest.c: */
 
+
+extern List *transform_saop_to_ors(PlannerInfo *root, RestrictInfo *rinfo);
 extern bool predicate_implied_by(List *predicate_list, List *clause_list,
                                                                 bool weak);
 extern bool predicate_refuted_by(List *predicate_list, List *clause_list,
diff --git a/src/include/optimizer/restrictinfo.h 
b/src/include/optimizer/restrictinfo.h
index 1b42c832c5..2cd5fbf943 100644
--- a/src/include/optimizer/restrictinfo.h
+++ b/src/include/optimizer/restrictinfo.h
@@ -34,6 +34,7 @@ extern RestrictInfo *make_restrictinfo(PlannerInfo *root,
                                                                           
Relids outer_relids);
 extern RestrictInfo *commute_restrictinfo(RestrictInfo *rinfo, Oid comm_op);
 extern bool restriction_is_or_clause(RestrictInfo *restrictinfo);
+extern bool restriction_is_saop_clause(RestrictInfo *restrictinfo);
 extern bool restriction_is_securely_promotable(RestrictInfo *restrictinfo,
                                                                                
           RelOptInfo *rel);
 extern List *get_actual_clauses(List *restrictinfo_list);
diff --git a/src/test/regress/expected/create_index.out 
b/src/test/regress/expected/create_index.out
index e9fb82b64c..0d9c9334f9 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -1980,23 +1980,25 @@ 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 = ANY 
('{42,99}'::integer[])) OR (tenthous < 2))) OR (thousand = 41))
+         Recheck Cond: (((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 = ANY 
('{42,99}'::integer[]))
+                                 Index Cond: (thousand = 42)
+                           ->  Bitmap Index Scan on tenk1_thous_tenthous
+                                 Index Cond: (thousand = 99)
                            ->  Bitmap Index Scan on tenk1_thous_tenthous
                                  Index Cond: (tenthous < 2)
                ->  Bitmap Index Scan on tenk1_thous_tenthous
                      Index Cond: (thousand = 41)
-(14 rows)
+(16 rows)
 
 SELECT count(*) FROM tenk1
   WHERE hundred = 42 AND (thousand = 42 OR thousand = 99 OR tenthous < 2) OR 
thousand = 41;
@@ -2008,20 +2010,22 @@ 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 = ANY 
('{42,41}'::integer[])) OR ((thousand = 99) AND (tenthous = 2))))
+         Recheck Cond: ((hundred = 42) AND ((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 = ANY ('{42,41}'::integer[]))
+                           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))
-(11 rows)
+(13 rows)
 
 SELECT count(*) FROM tenk1
   WHERE hundred = 42 AND (thousand = 42 OR thousand = 41 OR thousand = 99 AND 
tenthous = 2);
diff --git a/src/test/regress/expected/select.out 
b/src/test/regress/expected/select.out
index 33a6dceb0e..b5fdde47ac 100644
--- a/src/test/regress/expected/select.out
+++ b/src/test/regress/expected/select.out
@@ -907,6 +907,286 @@ select unique1, unique2 from onek2
        0 |     998
 (2 rows)
 
+SET enable_seqscan TO off;
+SET enable_indexscan TO off; -- Only BitmapScan is a subject matter here
+SET or_transformation_limit = -1;
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM onek2 WHERE stringu1 = 'A' OR stringu1 = 'J';
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on onek2
+         Recheck Cond: ((stringu1 < 'B'::name) OR (stringu1 = 'J'::name))
+         Filter: ((stringu1 = 'A'::name) OR (stringu1 = 'J'::name))
+         ->  BitmapOr
+               ->  Bitmap Index Scan on onek2_u2_prtl
+               ->  Bitmap Index Scan on onek2_stu1_prtl
+                     Index Cond: (stringu1 = 'J'::name)
+(8 rows)
+
+-- Without the transformation only seqscan possible here
+EXPLAIN (COSTS OFF)
+SELECT unique2, stringu1 FROM onek2
+WHERE unique2 < 1 AND stringu1 IN ('A','J') AND stringu1 < 'Z';
+                                         QUERY PLAN                            
              
+---------------------------------------------------------------------------------------------
+ Seq Scan on onek2
+   Filter: ((unique2 < 1) AND (stringu1 = ANY ('{A,J}'::name[])) AND (stringu1 
< 'Z'::name))
+(2 rows)
+
+-- Use partial indexes
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM onek2
+WHERE stringu1 IN ('B','J') AND (stringu1 = 'A' OR unique1 = 1);
+                                             QUERY PLAN                        
                     
+----------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on onek2
+         Recheck Cond: ((stringu1 < 'B'::name) OR (unique1 = 1))
+         Filter: ((stringu1 = ANY ('{B,J}'::name[])) AND ((stringu1 = 
'A'::name) OR (unique1 = 1)))
+         ->  BitmapOr
+               ->  Bitmap Index Scan on onek2_u2_prtl
+               ->  Bitmap Index Scan on onek2_u1_prtl
+                     Index Cond: (unique1 = 1)
+(8 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT unique2, stringu1 FROM onek2
+WHERE unique1 < 1 OR (stringu1 = 'A' OR stringu1 = 'J');
+                                     QUERY PLAN                                
      
+-------------------------------------------------------------------------------------
+ Bitmap Heap Scan on onek2
+   Recheck Cond: ((unique1 < 1) OR (stringu1 < 'B'::name) OR (stringu1 = 
'J'::name))
+   Filter: ((unique1 < 1) OR (stringu1 = 'A'::name) OR (stringu1 = 'J'::name))
+   ->  BitmapOr
+         ->  Bitmap Index Scan on onek2_u1_prtl
+               Index Cond: (unique1 < 1)
+         ->  Bitmap Index Scan on onek2_u2_prtl
+         ->  Bitmap Index Scan on onek2_stu1_prtl
+               Index Cond: (stringu1 = 'J'::name)
+(9 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT unique2, stringu1 FROM onek2
+WHERE unique1 = 1 OR unique2 = PI()::integer;
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on onek2
+   Filter: ((unique1 = 1) OR (unique2 = 3))
+(2 rows)
+
+RESET or_transformation_limit;
+-- OR <-> ANY transformation must find a path with partial indexes scan
+-- regardless the clause representation.
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM onek2 WHERE stringu1 = 'A' OR stringu1 = 'J';
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on onek2
+         Recheck Cond: ((stringu1 < 'B'::name) OR (stringu1 = 'J'::name))
+         Filter: (stringu1 = ANY ('{A,J}'::name[]))
+         ->  BitmapOr
+               ->  Bitmap Index Scan on onek2_u2_prtl
+               ->  Bitmap Index Scan on onek2_stu1_prtl
+                     Index Cond: (stringu1 = 'J'::name)
+(8 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM onek2 WHERE stringu1 IN ('A','J');
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on onek2
+         Recheck Cond: ((stringu1 < 'B'::name) OR (stringu1 = 'J'::name))
+         Filter: (stringu1 = ANY ('{A,J}'::name[]))
+         ->  BitmapOr
+               ->  Bitmap Index Scan on onek2_u2_prtl
+               ->  Bitmap Index Scan on onek2_stu1_prtl
+                     Index Cond: (stringu1 = 'J'::name)
+(8 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM onek2 WHERE stringu1 = ANY ('{A,J}');
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on onek2
+         Recheck Cond: ((stringu1 < 'B'::name) OR (stringu1 = 'J'::name))
+         Filter: (stringu1 = ANY ('{A,J}'::name[]))
+         ->  BitmapOr
+               ->  Bitmap Index Scan on onek2_u2_prtl
+               ->  Bitmap Index Scan on onek2_stu1_prtl
+                     Index Cond: (stringu1 = 'J'::name)
+(8 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM onek2 WHERE stringu1 IN ('A') OR stringu1 IN ('J');
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on onek2
+         Recheck Cond: ((stringu1 < 'B'::name) OR (stringu1 = 'J'::name))
+         Filter: (stringu1 = ANY ('{A,J}'::name[]))
+         ->  BitmapOr
+               ->  Bitmap Index Scan on onek2_u2_prtl
+               ->  Bitmap Index Scan on onek2_stu1_prtl
+                     Index Cond: (stringu1 = 'J'::name)
+(8 rows)
+
+-- Don't scan partial indexes because of extra value.
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM onek2 WHERE stringu1 IN ('A', 'J', 'C');
+                      QUERY PLAN                      
+------------------------------------------------------
+ Aggregate
+   ->  Seq Scan on onek2
+         Filter: (stringu1 = ANY ('{A,J,C}'::name[]))
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT unique2 FROM onek2
+WHERE stringu1 IN ('A', 'A') AND (stringu1 = 'A' OR stringu1 = 'A');
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Bitmap Heap Scan on onek2
+   Recheck Cond: (stringu1 < 'B'::name)
+   Filter: ((stringu1 = ANY ('{A,A}'::name[])) AND (stringu1 = 'A'::name))
+   ->  Bitmap Index Scan on onek2_u2_prtl
+(4 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT unique2, stringu1 FROM onek2
+WHERE unique2 < 1 AND stringu1 IN ('A','J') AND stringu1 < 'Z';
+                                                     QUERY PLAN                
                                      
+---------------------------------------------------------------------------------------------------------------------
+ Bitmap Heap Scan on onek2
+   Recheck Cond: (((unique2 < 1) AND (stringu1 < 'B'::name)) OR ((stringu1 = 
'J'::name) AND (stringu1 < 'Z'::name)))
+   Filter: ((unique2 < 1) AND (stringu1 = ANY ('{A,J}'::name[])))
+   ->  BitmapOr
+         ->  Bitmap Index Scan on onek2_u2_prtl
+               Index Cond: (unique2 < 1)
+         ->  Bitmap Index Scan on onek2_stu1_prtl
+               Index Cond: ((stringu1 = 'J'::name) AND (stringu1 < 'Z'::name))
+(8 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT unique2, stringu1 FROM onek2
+WHERE unique1 = 1 OR unique1 = PI()::integer;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Bitmap Heap Scan on onek2
+   Recheck Cond: (unique1 = ANY ('{1,3}'::integer[]))
+   ->  Bitmap Index Scan on onek2_u1_prtl
+         Index Cond: (unique1 = ANY ('{1,3}'::integer[]))
+(4 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT unique2, stringu1 FROM onek2
+WHERE unique1 IN (1, PI()::integer);
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Bitmap Heap Scan on onek2
+   Recheck Cond: (unique1 = ANY ('{1,3}'::integer[]))
+   ->  Bitmap Index Scan on onek2_u1_prtl
+         Index Cond: (unique1 = ANY ('{1,3}'::integer[]))
+(4 rows)
+
+-- Don't apply the optimization to clauses, containing volatile functions
+EXPLAIN (COSTS OFF)
+SELECT unique2,stringu1 FROM onek2
+WHERE unique1 = (random()*2)::integer OR unique1 = (random()*3)::integer;
+                                                             QUERY PLAN        
                                                     
+------------------------------------------------------------------------------------------------------------------------------------
+ Seq Scan on onek2
+   Filter: ((unique1 = ((random() * '2'::double precision))::integer) OR 
(unique1 = ((random() * '3'::double precision))::integer))
+(2 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT unique2,stringu1 FROM onek2
+WHERE unique1 IN ((random()*2)::integer, (random()*3)::integer);
+                                                           QUERY PLAN          
                                                  
+---------------------------------------------------------------------------------------------------------------------------------
+ Seq Scan on onek2
+   Filter: (unique1 = ANY (ARRAY[((random() * '2'::double 
precision))::integer, ((random() * '3'::double precision))::integer]))
+(2 rows)
+
+-- Combine different saops. Some of them doesnt' fit a set of partial indexes,
+-- but other fits.
+EXPLAIN (COSTS OFF)
+SELECT unique2,stringu1 FROM onek2
+WHERE
+  unique1 IN (1,2,21) AND
+  (stringu1 IN ('A','J') OR unique1 IN (3,4) OR stringu1 = 'J');
+                                                                           
QUERY PLAN                                                                      
     
+----------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Bitmap Heap Scan on onek2
+   Recheck Cond: ((stringu1 < 'B'::name) OR (stringu1 = 'J'::name) OR (unique1 
= 3) OR (unique1 = 4) OR (stringu1 = 'J'::name))
+   Filter: ((unique1 = ANY ('{1,2,21}'::integer[])) AND ((stringu1 = ANY 
('{A,J}'::name[])) OR (unique1 = ANY ('{3,4}'::integer[])) OR (stringu1 = 
'J'::name)))
+   ->  BitmapOr
+         ->  Bitmap Index Scan on onek2_u2_prtl
+         ->  Bitmap Index Scan on onek2_stu1_prtl
+               Index Cond: (stringu1 = 'J'::name)
+         ->  Bitmap Index Scan on onek2_u1_prtl
+               Index Cond: (unique1 = 3)
+         ->  Bitmap Index Scan on onek2_u1_prtl
+               Index Cond: (unique1 = 4)
+         ->  Bitmap Index Scan on onek2_stu1_prtl
+               Index Cond: (stringu1 = 'J'::name)
+(13 rows)
+
+-- Check recursive combination of OR and SAOP expressions
+EXPLAIN (COSTS OFF)
+SELECT unique2, stringu1 FROM onek2
+WHERE unique1 < 1 OR (stringu1 = 'A' OR stringu1 = 'J');
+                                     QUERY PLAN                                
      
+-------------------------------------------------------------------------------------
+ Bitmap Heap Scan on onek2
+   Recheck Cond: ((unique1 < 1) OR (stringu1 < 'B'::name) OR (stringu1 = 
'J'::name))
+   Filter: ((unique1 < 1) OR (stringu1 = ANY ('{A,J}'::name[])))
+   ->  BitmapOr
+         ->  Bitmap Index Scan on onek2_u1_prtl
+               Index Cond: (unique1 < 1)
+         ->  Bitmap Index Scan on onek2_u2_prtl
+         ->  Bitmap Index Scan on onek2_stu1_prtl
+               Index Cond: (stringu1 = 'J'::name)
+(9 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT unique2, stringu1 FROM onek2
+WHERE (unique1 < 1 OR stringu1 IN ('A','J'));
+                                     QUERY PLAN                                
      
+-------------------------------------------------------------------------------------
+ Bitmap Heap Scan on onek2
+   Recheck Cond: ((unique1 < 1) OR (stringu1 < 'B'::name) OR (stringu1 = 
'J'::name))
+   Filter: ((unique1 < 1) OR (stringu1 = ANY ('{A,J}'::name[])))
+   ->  BitmapOr
+         ->  Bitmap Index Scan on onek2_u1_prtl
+               Index Cond: (unique1 < 1)
+         ->  Bitmap Index Scan on onek2_u2_prtl
+         ->  Bitmap Index Scan on onek2_stu1_prtl
+               Index Cond: (stringu1 = 'J'::name)
+(9 rows)
+
+-- Although SAOP doesn't fit partial indexes fully, we can use anded OR clause
+-- to scan another couple of partial indexes.
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM onek2
+WHERE stringu1 IN ('B','J') AND (stringu1 = 'A' OR unique1 = 1);
+                                             QUERY PLAN                        
                     
+----------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on onek2
+         Recheck Cond: ((stringu1 < 'B'::name) OR (unique1 = 1))
+         Filter: ((stringu1 = ANY ('{B,J}'::name[])) AND ((stringu1 = 
'A'::name) OR (unique1 = 1)))
+         ->  BitmapOr
+               ->  Bitmap Index Scan on onek2_u2_prtl
+               ->  Bitmap Index Scan on onek2_u1_prtl
+                     Index Cond: (unique1 = 1)
+(8 rows)
+
+RESET enable_indexscan;
+RESET enable_seqscan;
 --
 -- Test some corner cases that have been known to confuse the planner
 --
diff --git a/src/test/regress/sql/select.sql b/src/test/regress/sql/select.sql
index 019f1e7673..10ed6f0a9c 100644
--- a/src/test/regress/sql/select.sql
+++ b/src/test/regress/sql/select.sql
@@ -234,6 +234,88 @@ select unique1, unique2 from onek2
 select unique1, unique2 from onek2
   where (unique2 = 11 and stringu1 < 'B') or unique1 = 0;
 
+SET enable_seqscan TO off;
+SET enable_indexscan TO off; -- Only BitmapScan is a subject matter here
+SET or_transformation_limit = -1;
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM onek2 WHERE stringu1 = 'A' OR stringu1 = 'J';
+-- Without the transformation only seqscan possible here
+EXPLAIN (COSTS OFF)
+SELECT unique2, stringu1 FROM onek2
+WHERE unique2 < 1 AND stringu1 IN ('A','J') AND stringu1 < 'Z';
+-- Use partial indexes
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM onek2
+WHERE stringu1 IN ('B','J') AND (stringu1 = 'A' OR unique1 = 1);
+EXPLAIN (COSTS OFF)
+SELECT unique2, stringu1 FROM onek2
+WHERE unique1 < 1 OR (stringu1 = 'A' OR stringu1 = 'J');
+EXPLAIN (COSTS OFF)
+SELECT unique2, stringu1 FROM onek2
+WHERE unique1 = 1 OR unique2 = PI()::integer;
+RESET or_transformation_limit;
+
+-- OR <-> ANY transformation must find a path with partial indexes scan
+-- regardless the clause representation.
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM onek2 WHERE stringu1 = 'A' OR stringu1 = 'J';
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM onek2 WHERE stringu1 IN ('A','J');
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM onek2 WHERE stringu1 = ANY ('{A,J}');
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM onek2 WHERE stringu1 IN ('A') OR stringu1 IN ('J');
+
+-- Don't scan partial indexes because of extra value.
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM onek2 WHERE stringu1 IN ('A', 'J', 'C');
+EXPLAIN (COSTS OFF)
+SELECT unique2 FROM onek2
+WHERE stringu1 IN ('A', 'A') AND (stringu1 = 'A' OR stringu1 = 'A');
+
+EXPLAIN (COSTS OFF)
+SELECT unique2, stringu1 FROM onek2
+WHERE unique2 < 1 AND stringu1 IN ('A','J') AND stringu1 < 'Z';
+EXPLAIN (COSTS OFF)
+SELECT unique2, stringu1 FROM onek2
+WHERE unique1 = 1 OR unique1 = PI()::integer;
+EXPLAIN (COSTS OFF)
+SELECT unique2, stringu1 FROM onek2
+WHERE unique1 IN (1, PI()::integer);
+
+-- Don't apply the optimization to clauses, containing volatile functions
+EXPLAIN (COSTS OFF)
+SELECT unique2,stringu1 FROM onek2
+WHERE unique1 = (random()*2)::integer OR unique1 = (random()*3)::integer;
+EXPLAIN (COSTS OFF)
+SELECT unique2,stringu1 FROM onek2
+WHERE unique1 IN ((random()*2)::integer, (random()*3)::integer);
+
+-- Combine different saops. Some of them doesnt' fit a set of partial indexes,
+-- but other fits.
+
+EXPLAIN (COSTS OFF)
+SELECT unique2,stringu1 FROM onek2
+WHERE
+  unique1 IN (1,2,21) AND
+  (stringu1 IN ('A','J') OR unique1 IN (3,4) OR stringu1 = 'J');
+
+-- Check recursive combination of OR and SAOP expressions
+EXPLAIN (COSTS OFF)
+SELECT unique2, stringu1 FROM onek2
+WHERE unique1 < 1 OR (stringu1 = 'A' OR stringu1 = 'J');
+EXPLAIN (COSTS OFF)
+SELECT unique2, stringu1 FROM onek2
+WHERE (unique1 < 1 OR stringu1 IN ('A','J'));
+-- Although SAOP doesn't fit partial indexes fully, we can use anded OR clause
+-- to scan another couple of partial indexes.
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM onek2
+WHERE stringu1 IN ('B','J') AND (stringu1 = 'A' OR unique1 = 1);
+
+RESET enable_indexscan;
+RESET enable_seqscan;
+
 --
 -- Test some corner cases that have been known to confuse the planner
 --
-- 
2.44.0

Reply via email to