Transform OR clauses to ANY expression
authorAlexander Korotkov <[email protected]>
Sun, 7 Apr 2024 22:27:28 +0000 (01:27 +0300)
committerAlexander Korotkov <[email protected]>
Sun, 7 Apr 2024 22:27:52 +0000 (01:27 +0300)
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 Cn is a n-th 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 'Cn op expr').

Sometimes it can lead to not optimal plan.  This is why there is a
or_to_any_transform_limit GUC.  It specifies a threshold value of length of
arguments in an OR expression that triggers the OR-to-ANY transformation.
Generally, more groupable OR arguments mean that transformation will be more
likely to win than to lose.

Discussion: https://postgr.es/m/567ED6CA.2040504%40sigaev.ru
Author: Alena Rybakina <[email protected]>
Author: Andrey Lepikhov <[email protected]>
Reviewed-by: Peter Geoghegan <[email protected]>
Reviewed-by: Ranier Vilela <[email protected]>
Reviewed-by: Alexander Korotkov <[email protected]>
Reviewed-by: Robert Haas <[email protected]>
Reviewed-by: Jian He <[email protected]>
14 files changed:
doc/src/sgml/config.sgml
src/backend/nodes/queryjumblefuncs.c
src/backend/optimizer/prep/prepqual.c
src/backend/utils/misc/guc_tables.c
src/backend/utils/misc/postgresql.conf.sample
src/include/nodes/queryjumble.h
src/include/optimizer/optimizer.h
src/test/regress/expected/create_index.out
src/test/regress/expected/join.out
src/test/regress/expected/partition_prune.out
src/test/regress/sql/create_index.sql
src/test/regress/sql/join.sql
src/test/regress/sql/partition_prune.sql
src/tools/pgindent/typedefs.list

index d8e1282e128d7fefc6acc327a1107f01f5fbda2e..ac945ca4d19d3da9ac9bcb20387f768a978b5277 100644 (file)
@@ -6304,6 +6304,63 @@ SELECT * FROM parent WHERE key = 2400;
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-or-to-any-transform-limit" xreflabel="or_to_any_transform_limit">
+      <term><varname>or_to_any_transform_limit</varname> (<type>boolean</type>)
+       <indexterm>
+        <primary><varname>or_to_any_transform_limit</varname> configuration parameter</primary>
+       </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Sets the minimum length of arguments in an <literal>OR</literal>
+        expression exceeding which planner will try to lookup and group
+        multiple similar <literal>OR</literal> expressions to
+        <literal>ANY</literal> (<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 value is <literal>5</literal>.  The value of
+        <literal>-1</literal> completely disables the transformation.
+       </para>
+       <para>
+        The advantage of this <literal>OR-to-ANY</literal> transformation is
+        faster query planning and execution.  In certain cases, this
+        transformation also leads to more effective plans containing
+        a single index scan instead of multiple bitmap scans.  However, it
+        may also cause a planning regression when distinct
+        <literal>OR</literal> arguments are better to match to distinct indexes.
+        This may happen when they have different matching partial indexes or
+        have different distributions of other columns used in the query.
+        Generally, more groupable <literal>OR</literal> arguments mean that
+        transformation will be more likely to win than to lose.
+       </para>
+       <para>
+        For example, this query has its set of five <literal>OR</literal>
+        expressions transformed to <literal>ANY</literal> with the default
+        value of <varname>or_to_any_transform_limit</varname>.  But not with
+        the increased value.
+<programlisting>
+# EXPLAIN SELECT * FROM tbl WHERE key = 1 OR key = 2 OR key = 3 OR key = 4 OR key = 5;
+                     QUERY PLAN
+-----------------------------------------------------
+ Seq Scan on tbl  (cost=0.00..51.44 rows=64 width=4)
+   Filter: (key = ANY ('{1,2,3,4,5}'::integer[]))
+(2 rows)
+
+# SET or_to_any_transform_limit = 6;
+SET
+
+# EXPLAIN SELECT * FROM tbl WHERE key = 1 OR key = 2 OR key = 3 OR key = 4 OR key = 5;
+                                QUERY PLAN
+---------------------------------------------------------------------------
+ Seq Scan on tbl  (cost=0.00..67.38 rows=63 width=4)
+   Filter: ((key = 1) OR (key = 2) OR (key = 3) OR (key = 4) OR (key = 5))
+(2 rows)
+</programlisting>
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-plan-cache-mode" xreflabel="plan_cache_mode">
       <term><varname>plan_cache_mode</varname> (<type>enum</type>)
       <indexterm>
index be823a7f8fa47ca15c816a9b5d5b1899eec2fa0b..de29d51c631ea211834bbbdabb1c65026cd134ba 100644 (file)
@@ -141,6 +141,33 @@ JumbleQuery(Query *query)
    return jstate;
 }
 
+JumbleState *
+JumbleExpr(Expr *expr, uint64 *exprId)
+{
+   JumbleState *jstate = NULL;
+
+   Assert(exprId != 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);
+   *exprId = DatumGetUInt64(hash_any_extended(jstate->jumble,
+                                              jstate->jumble_len,
+                                              0));
+
+   return jstate;
+}
+
 /*
  * Enables query identifier computation.
  *
index cbcf83f847327789355dbbec0909b517d900e4a3..1514dea8e9b3d815a7a87d14b9a05e659b1c47b8 100644 (file)
 
 #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_to_any_transform_limit = 5;
 
 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 *transform_or_to_any(List *orlist);
 
 
 /*
@@ -266,6 +275,375 @@ negate_clause(Node *node)
    return (Node *) make_notclause((Expr *) node);
 }
 
+/*
+ * The key for grouping similar operator expressions in transform_or_to_any().
+ */
+typedef struct OrClauseGroupKey
+{
+   /* We need this to put this structure into list together with other nodes */
+   NodeTag     type;
+
+   /* The expression of the variable side of operator */
+   Expr       *expr;
+   /* The operator of the operator expression */
+   Oid         opno;
+   /* The collation of the operator expression */
+   Oid         inputcollid;
+   /* The type of constant side of operator */
+   Oid         consttype;
+} OrClauseGroupKey;
+
+/*
+ * The group of similar operator expressions in transform_or_to_any().
+ */
+typedef struct OrClauseGroupEntry
+{
+   OrClauseGroupKey key;
+
+   /* The list of constant sides of operators */
+   List       *consts;
+
+   /*
+    * List of source expressions.  We need this for convenience in case we
+    * will give up on transformation.
+    */
+   List       *exprs;
+} OrClauseGroupEntry;
+
+/*
+ * The hash function for OrClauseGroupKey.
+ */
+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)));
+}
+
+/*
+ * The copy function for OrClauseGroupKey.
+ */
+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;
+}
+
+/*
+ * The equality function for OrClauseGroupKey.
+ */
+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;
+}
+
+/*
+ * transform_or_to_any -
+ *   Discover the args of an OR expression and try to group similar OR
+ *   expressions to SAOP expressions.
+ *
+ * This transformation groups two-sided equality expression.  One side of
+ * such an expression must be a plain constant or constant expression.  The
+ * other side must be a variable expression without volatile functions.
+ * To group quals, opno, inputcollid of variable expression, and type of
+ * constant expression must be equal too.
+ *
+ * The grouping technique is based on the equivalence of variable sides of
+ * the expression: using exprId and equal() routine, it groups constant sides
+ * of similar clauses into an array.  After the grouping procedure, each
+ * couple ('variable expression' and 'constant array') forms a new SAOP
+ * operation, which is added to the args list of the returning expression.
+ */
+static List *
+transform_or_to_any(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_to_any_transform_limit >= 0 &&
+          len_ors >= or_to_any_transform_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;
+       }
+
+       /*
+        * Forbid transformation for composite types, records, and 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 ANY expression. */
+
+   /*
+    * 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's (right side of operator expression) 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
@@ -601,10 +979,22 @@ process_duplicate_ors(List *orlist)
    }
 
    /*
-    * If no winners, we can't transform the OR
+    * If no winners, we can't do OR-to-ANY transformation.
     */
    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_to_any_transform_limit >= 0 &&
+           list_length(orlist) >= or_to_any_transform_limit)
+           orlist = transform_or_to_any(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 +1041,11 @@ process_duplicate_ors(List *orlist)
        }
    }
 
+   /* Make an attempt to group similar OR clauses into ANY operation */
+   if (or_to_any_transform_limit >= 0 &&
+       list_length(neworlist) >= or_to_any_transform_limit)
+       neworlist = transform_or_to_any(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?).
index 7d4e4387cf5f3c476e8f839fef9cd7d3ae517c91..f9bb2b0f9e502cb2682588dbdc134167ee6b01d1 100644 (file)
@@ -3657,6 +3657,18 @@ struct config_int ConfigureNamesInt[] =
        NULL, NULL, NULL
    },
 
+   {
+       {"or_to_any_transform_limit", PGC_USERSET, QUERY_TUNING_OTHER,
+           gettext_noop("Set the minimum length of the list of OR clauses to attempt the OR-to-ANY transformation."),
+           gettext_noop("Once the limit is reached, the planner will try to replace expression like "
+                        "'x=c1 OR x=c2 ..' to the expression 'x = ANY(ARRAY[c1,c2,..])'"),
+           GUC_EXPLAIN
+       },
+       &or_to_any_transform_limit,
+       5, -1, INT_MAX,
+       NULL, NULL, NULL
+   },
+
    /* End-of-list marker */
    {
        {NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL, NULL
index 2166ea4a87aa229e5ba3fbbb00c05aac9550f512..15b14ba8507319eadf73a079bea186a7a78afb6c 100644 (file)
 # - Planner Method Configuration -
 
 #enable_async_append = on
+#or_to_any_transform_limit = 0
 #enable_bitmapscan = on
 #enable_gathermerge = on
 #enable_hashagg = on
index f1c55c8067f406dbd458dbf90a7d4725c14dc1b7..5643ee8f6510e3ff317f1974389d5013eedccfee 100644 (file)
@@ -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 *exprId);
 extern void EnableQueryId(void);
 
 extern PGDLLIMPORT bool query_id_enabled;
index 7b63c5cf718dbe8c5a4cfe020268b396c2896b36..960dfb97a3867ce26e7ee505bfe1a9f1f04cbf12 100644 (file)
@@ -133,6 +133,8 @@ extern void extract_query_dependencies(Node *query,
 
 /* in prep/prepqual.c: */
 
+extern PGDLLIMPORT int or_to_any_transform_limit;
+
 extern Node *negate_clause(Node *node);
 extern Expr *canonicalize_qual(Expr *qual, bool is_check);
 
index cf6eac57349a023f8c2e72144898a3382a8e8cae..b4b42173e5180b77a685316ead77839bcc0ce60e 100644 (file)
@@ -1889,6 +1889,165 @@ SELECT count(*) FROM tenk1
     10
 (1 row)
 
+SET or_to_any_transform_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_to_any_transform_limit = 3;
+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_to_any_transform_limit = 4;
+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                                                                
+-----------------------------------------------------------------------------------------------------------------------------------------
+ Bitmap Heap Scan on tenk1
+   Recheck Cond: (((thousand = 42) AND (tenthous = 1)) OR ((thousand = 42) AND (tenthous = 3)) OR ((thousand = 42) AND (tenthous = 42)))
+   ->  BitmapOr
+         ->  Bitmap Index Scan on tenk1_thous_tenthous
+               Index Cond: ((thousand = 42) AND (tenthous = 1))
+         ->  Bitmap Index Scan on tenk1_thous_tenthous
+               Index Cond: ((thousand = 42) AND (tenthous = 3))
+         ->  Bitmap Index Scan on tenk1_thous_tenthous
+               Index Cond: ((thousand = 42) AND (tenthous = 42))
+(9 rows)
+
+RESET or_to_any_transform_limit;
+SET or_to_any_transform_limit = 0;
+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 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: (((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 = ANY ('{42,41}'::integer[]))
+                     ->  Bitmap Index Scan on tenk1_thous_tenthous
+                           Index Cond: ((thousand = 99) AND (tenthous = 2))
+(11 rows)
+
+SELECT count(*) FROM tenk1
+  WHERE hundred = 42 AND (thousand = 42 OR thousand = 41 OR thousand = 99 AND tenthous = 2);
+ count 
+-------
+    10
+(1 row)
+
+RESET or_to_any_transform_limit;
 --
 -- Check behavior with duplicate index column contents
 --
index 8b640c2fc2fc9186f463bd44aea59f6c528a0588..407d251a508319a38050b49651385fc635e3b23c 100644 (file)
@@ -4233,6 +4233,56 @@ select * from tenk1 a join tenk1 b on
                            Index Cond: (unique2 = 7)
 (19 rows)
 
+SET or_to_any_transform_limit = 0;
+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 = 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)
+
+RESET or_to_any_transform_limit;
 --
 -- test placement of movable quals in a parameterized join tree
 --
index 46b78ba3c4138f85682b10271ab791930bd6231a..db507eff448c389aa3644b8ee772c309833d6276 100644 (file)
@@ -3,6 +3,7 @@
 --
 -- Force generic plans to be used for all prepared statements in this file.
 set plan_cache_mode = force_generic_plan;
+set or_to_any_transform_limit = 0;
 create table lp (a char) partition by list (a);
 create table lp_default partition of lp default;
 create table lp_ef partition of lp for values in ('e', 'f');
@@ -82,23 +83,23 @@ 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 <> 'g';
@@ -515,10 +516,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 +597,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 */
@@ -2072,10 +2073,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;
index e296891cab8064771d46959bc3570da35ceae484..7059b4ea86b8cd92a9469ead4bac106ac3aea441 100644 (file)
@@ -738,6 +738,51 @@ SELECT count(*) FROM tenk1
 SELECT count(*) FROM tenk1
   WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
 
+SET or_to_any_transform_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_to_any_transform_limit = 3;
+EXPLAIN (COSTS OFF) -- or_transformation still works
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
+SET or_to_any_transform_limit = 4;
+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_to_any_transform_limit;
+
+SET or_to_any_transform_limit = 0;
+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_to_any_transform_limit;
+
 --
 -- Check behavior with duplicate index column contents
 --
index c4c6c7b8ba29e6616c7794d5216ea3868b07f642..266461fb5b79080616b1bbe72a56c72de7edda51 100644 (file)
@@ -1409,6 +1409,17 @@ 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);
 
+SET or_to_any_transform_limit = 0;
+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);
+RESET or_to_any_transform_limit;
+
 --
 -- test placement of movable quals in a parameterized join tree
 --
index dc7169386106de56cb5cf2d1dcd02a9ec438adf7..c49153f38adc3ada18288a7b7cefdd9bf82b0b19 100644 (file)
@@ -4,6 +4,7 @@
 
 -- Force generic plans to be used for all prepared statements in this file.
 set plan_cache_mode = force_generic_plan;
+set or_to_any_transform_limit = 0;
 
 create table lp (a char) partition by list (a);
 create table lp_default partition of lp default;
@@ -21,6 +22,7 @@ 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 <> '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');
index 6e0717c8c473995b9d286527301ed2ac6b636cfe..cb48e2f95a896f7a0296852c1baffb00bd00e0b5 100644 (file)
@@ -1698,6 +1698,8 @@ NumericVar
 OM_uint32
 OP
 OSAPerGroupState
+OrClauseGroupEntry
+OrClauseGroupKey
 OSAPerQueryState
 OSInfo
 OSSLCipher