Transform OR-clauses to SAOP's during index matching
authorAlexander Korotkov <akorotkov@postgresql.org>
Sat, 23 Nov 2024 23:40:20 +0000 (01:40 +0200)
committerAlexander Korotkov <akorotkov@postgresql.org>
Sat, 23 Nov 2024 23:40:20 +0000 (01:40 +0200)
This commit makes match_clause_to_indexcol() match
"(indexkey op C1) OR (indexkey op C2) ... (indexkey op CN)" expression
to the index while transforming it into "indexkey op ANY(ARRAY[C1, C2, ...])"
(ScalarArrayOpExpr node).

This transformation allows handling long OR-clauses with single IndexScan
avoiding diving them into a slower BitmapOr.

We currently restrict Ci to be either Const or Param to apply this
transformation only when it's clearly beneficial.  However, in the future,
we might switch to a liberal understanding of constants, as it is in other
cases.

Discussion: https://postgr.es/m/567ED6CA.2040504%40sigaev.ru
Author: Alena Rybakina, Andrey Lepikhov, Alexander Korotkov
Reviewed-by: Peter Geoghegan, Ranier Vilela, Alexander Korotkov, Robert Haas
Reviewed-by: Jian He, Tom Lane, Nikolay Shaplov
src/backend/optimizer/path/indxpath.c
src/test/regress/expected/create_index.out
src/test/regress/expected/join.out
src/test/regress/expected/rowsecurity.out
src/test/regress/expected/stats_ext.out
src/test/regress/expected/uuid.out
src/test/regress/sql/create_index.sql
src/test/regress/sql/join.sql
src/test/regress/sql/rowsecurity.sql
src/test/regress/sql/stats_ext.sql
src/test/regress/sql/uuid.sql

index c0fcc7d78dfca1875415987ff90d55f3b6d818b8..61b1848241049e29f0a7a50ced43896b8435f1e1 100644 (file)
@@ -20,6 +20,7 @@
 #include "access/stratnum.h"
 #include "access/sysattr.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_amop.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_type.h"
 #include "optimizer/paths.h"
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
+#include "utils/array.h"
 #include "utils/lsyscache.h"
 #include "utils/selfuncs.h"
+#include "utils/syscache.h"
 
 
 /* XXX see PartCollMatchesExprColl */
@@ -177,6 +180,10 @@ static IndexClause *match_rowcompare_to_indexcol(PlannerInfo *root,
                                                                                                 RestrictInfo *rinfo,
                                                                                                 int indexcol,
                                                                                                 IndexOptInfo *index);
+static IndexClause *match_orclause_to_indexcol(PlannerInfo *root,
+                                                                                          RestrictInfo *rinfo,
+                                                                                          int indexcol,
+                                                                                          IndexOptInfo *index);
 static IndexClause *expand_indexqual_rowcompare(PlannerInfo *root,
                                                                                                RestrictInfo *rinfo,
                                                                                                int indexcol,
@@ -2149,7 +2156,10 @@ match_clause_to_index(PlannerInfo *root,
  *       (3)  must match the collation of the index, if collation is relevant.
  *
  *       Our definition of "const" is exceedingly liberal: we allow anything that
- *       doesn't involve a volatile function or a Var of the index's relation.
+ *       doesn't involve a volatile function or a Var of the index's relation
+ *       except for a boolean OR expression input: due to a trade-off between the
+ *       expected execution speedup and planning complexity, we limit or->saop
+ *       transformation by obvious cases when an index scan can get a profit.
  *       In particular, Vars belonging to other relations of the query are
  *       accepted here, since a clause of that form can be used in a
  *       parameterized indexscan.  It's the responsibility of higher code levels
@@ -2179,6 +2189,10 @@ match_clause_to_index(PlannerInfo *root,
  *       It is also possible to match ScalarArrayOpExpr clauses to indexes, when
  *       the clause is of the form "indexkey op ANY (arrayconst)".
  *
+ *       It is also possible to match a list of OR clauses if it might be
+ *       transformed into a single ScalarArrayOpExpr clause.  On success,
+ *       the returning index clause will contain a trasformed clause.
+ *
  *       For boolean indexes, it is also possible to match the clause directly
  *       to the indexkey; or perhaps the clause is (NOT indexkey).
  *
@@ -2228,9 +2242,9 @@ match_clause_to_indexcol(PlannerInfo *root,
        }
 
        /*
-        * Clause must be an opclause, funcclause, ScalarArrayOpExpr, or
-        * RowCompareExpr.  Or, if the index supports it, we can handle IS
-        * NULL/NOT NULL clauses.
+        * Clause must be an opclause, funcclause, ScalarArrayOpExpr,
+        * RowCompareExpr, or OR-clause that could be converted to SAOP.  Or, if
+        * the index supports it, we can handle IS NULL/NOT NULL clauses.
         */
        if (IsA(clause, OpExpr))
        {
@@ -2248,6 +2262,10 @@ match_clause_to_indexcol(PlannerInfo *root,
        {
                return match_rowcompare_to_indexcol(root, rinfo, indexcol, index);
        }
+       else if (restriction_is_or_clause(rinfo))
+       {
+               return match_orclause_to_indexcol(root, rinfo, indexcol, index);
+       }
        else if (index->amsearchnulls && IsA(clause, NullTest))
        {
                NullTest   *nt = (NullTest *) clause;
@@ -2771,6 +2789,264 @@ match_rowcompare_to_indexcol(PlannerInfo *root,
        return NULL;
 }
 
+/*
+ * match_orclause_to_indexcol()
+ *       Handles the OR-expr case for match_clause_to_indexcol() in the case
+ *       when it could be transformed to ScalarArrayOpExpr.
+ *
+ * In this routine, we attempt to transform a list of OR-clause args into a
+ * single SAOP expression matching the target index column.  On success,
+ * return an IndexClause, containing the transformed expression or NULL,
+ * if failed.
+ */
+static IndexClause *
+match_orclause_to_indexcol(PlannerInfo *root,
+                                                  RestrictInfo *rinfo,
+                                                  int indexcol,
+                                                  IndexOptInfo *index)
+{
+       ListCell   *lc;
+       BoolExpr   *orclause = (BoolExpr *) rinfo->orclause;
+       Node       *indexExpr = NULL;
+       List       *consts = NIL;
+       Node       *arrayNode = NULL;
+       ScalarArrayOpExpr *saopexpr = NULL;
+       Oid                     matchOpno = InvalidOid;
+       IndexClause *iclause;
+       Oid                     consttype = InvalidOid;
+       Oid                     arraytype = InvalidOid;
+       Oid                     inputcollid = InvalidOid;
+       bool            firstTime = true;
+       bool            haveParam = false;
+
+       Assert(IsA(orclause, BoolExpr));
+       Assert(orclause->boolop == OR_EXPR);
+
+       /*
+        * Try to convert a list of OR-clauses to a single SAOP expression. Each
+        * OR entry must be in the form: (indexkey operator constant) or (constant
+        * operator indexkey).  Operators of all the entries must match.  Constant
+        * might be either Const or Param.  To be effective, give up on the first
+        * non-matching entry.  Exit is implemented as a break from the loop,
+        * which is catched afterwards.
+        */
+       foreach(lc, orclause->args)
+       {
+               RestrictInfo *subRinfo;
+               OpExpr     *subClause;
+               Oid                     opno;
+               Node       *leftop,
+                                  *rightop;
+               Node       *constExpr;
+
+               if (!IsA(lfirst(lc), RestrictInfo))
+                       break;
+
+               subRinfo = (RestrictInfo *) lfirst(lc);
+
+               /* Only operator clauses can match  */
+               if (!IsA(subRinfo->clause, OpExpr))
+                       break;
+
+               subClause = (OpExpr *) subRinfo->clause;
+               opno = subClause->opno;
+
+               /* Only binary operators can match  */
+               if (list_length(subClause->args) != 2)
+                       break;
+
+               /*
+                * The parameters below must match between sub-rinfo and its parent as
+                * make_restrictinfo() fills them with the same values, and further
+                * modifications are also the same for the whole subtree.  However,
+                * still make a sanity check.
+                */
+               Assert(subRinfo->is_pushed_down == rinfo->is_pushed_down);
+               Assert(subRinfo->is_clone == rinfo->is_clone);
+               Assert(subRinfo->security_level == rinfo->security_level);
+               Assert(bms_equal(subRinfo->incompatible_relids, rinfo->incompatible_relids));
+               Assert(bms_equal(subRinfo->outer_relids, rinfo->outer_relids));
+
+               /*
+                * Also, check that required_relids in sub-rinfo is subset of parent's
+                * required_relids.
+                */
+               Assert(bms_is_subset(subRinfo->required_relids, rinfo->required_relids));
+
+               /* Only the operator returning a boolean suit the transformation. */
+               if (get_op_rettype(opno) != BOOLOID)
+                       break;
+
+               /*
+                * Check for clauses of the form: (indexkey operator constant) or
+                * (constant operator indexkey).  Determine indexkey side first, check
+                * the constant later.
+                */
+               leftop = (Node *) linitial(subClause->args);
+               rightop = (Node *) lsecond(subClause->args);
+               if (match_index_to_operand(leftop, indexcol, index))
+               {
+                       indexExpr = leftop;
+                       constExpr = rightop;
+               }
+               else if (match_index_to_operand(rightop, indexcol, index))
+               {
+                       opno = get_commutator(opno);
+                       if (!OidIsValid(opno))
+                       {
+                               /* commutator doesn't exist, we can't reverse the order */
+                               break;
+                       }
+                       indexExpr = rightop;
+                       constExpr = leftop;
+               }
+               else
+               {
+                       break;
+               }
+
+               /*
+                * Ignore any RelabelType node above the operands.  This is needed to
+                * be able to apply indexscanning in binary-compatible-operator cases.
+                * Note: we can assume there is at most one RelabelType node;
+                * eval_const_expressions() will have simplified if more than one.
+                */
+               if (IsA(constExpr, RelabelType))
+                       constExpr = (Node *) ((RelabelType *) constExpr)->arg;
+               if (IsA(indexExpr, RelabelType))
+                       indexExpr = (Node *) ((RelabelType *) indexExpr)->arg;
+
+               /* We allow constant to be Const or Param */
+               if (!IsA(constExpr, Const) && !IsA(constExpr, Param))
+                       break;
+
+               /* Forbid transformation for composite types, records. */
+               if (type_is_rowtype(exprType(constExpr)) ||
+                       type_is_rowtype(exprType(indexExpr)))
+                       break;
+
+               /*
+                * Save information about the operator, type, and collation for the
+                * first matching qual.  Then, check that subsequent quals match the
+                * first.
+                */
+               if (firstTime)
+               {
+                       matchOpno = opno;
+                       consttype = exprType(constExpr);
+                       arraytype = get_array_type(consttype);
+                       inputcollid = subClause->inputcollid;
+
+                       /*
+                        * Check that the operator is presented in the opfamily and that
+                        * the expression collation matches the index collation.  Also,
+                        * there must be an array type to construct an array later.
+                        */
+                       if (!IndexCollMatchesExprColl(index->indexcollations[indexcol], inputcollid) ||
+                               !op_in_opfamily(matchOpno, index->opfamily[indexcol]) ||
+                               !OidIsValid(arraytype))
+                               break;
+                       firstTime = false;
+               }
+               else
+               {
+                       if (opno != matchOpno ||
+                               inputcollid != subClause->inputcollid ||
+                               consttype != exprType(constExpr))
+                               break;
+               }
+
+               if (IsA(constExpr, Param))
+                       haveParam = true;
+               consts = lappend(consts, constExpr);
+       }
+
+       /*
+        * Catch the break from the loop above.  Normally, a foreach() loop ends
+        * up with a NULL list cell.  A non-NULL list cell indicates a break from
+        * the foreach() loop.  Free the consts list and return NULL then.
+        */
+       if (lc != NULL)
+       {
+               list_free(consts);
+               return NULL;
+       }
+
+       /*
+        * Assemble an array from the list of constants.  It seems more profitable
+        * to build a const array.  But in the presence of parameters, we don't
+        * have a specific value here and must employ an ArrayExpr instead.
+        */
+       if (haveParam)
+       {
+               ArrayExpr  *arrayExpr = makeNode(ArrayExpr);
+
+               /* array_collid will be set by parse_collate.c */
+               arrayExpr->element_typeid = consttype;
+               arrayExpr->array_typeid = arraytype;
+               arrayExpr->multidims = false;
+               arrayExpr->elements = consts;
+               arrayExpr->location = -1;
+
+               arrayNode = (Node *) arrayExpr;
+       }
+       else
+       {
+               int16           typlen;
+               bool            typbyval;
+               char            typalign;
+               Datum      *elems;
+               int                     i = 0;
+               ArrayType  *arrayConst;
+
+               get_typlenbyvalalign(consttype, &typlen, &typbyval, &typalign);
+
+               elems = (Datum *) palloc(sizeof(Datum) * list_length(consts));
+               foreach_node(Const, value, consts)
+               {
+                       Assert(!value->constisnull && value->constvalue);
+
+                       elems[i++] = value->constvalue;
+               }
+
+               arrayConst = construct_array(elems, i, consttype,
+                                                                        typlen, typbyval, typalign);
+               arrayNode = (Node *) makeConst(arraytype, -1, inputcollid,
+                                                                          -1, PointerGetDatum(arrayConst),
+                                                                          false, false);
+
+               pfree(elems);
+               list_free(consts);
+       }
+
+       /* Build the SAOP expression node */
+       saopexpr = makeNode(ScalarArrayOpExpr);
+       saopexpr->opno = matchOpno;
+       saopexpr->opfuncid = get_opcode(matchOpno);
+       saopexpr->hashfuncid = InvalidOid;
+       saopexpr->negfuncid = InvalidOid;
+       saopexpr->useOr = true;
+       saopexpr->inputcollid = inputcollid;
+       saopexpr->args = list_make2(indexExpr, arrayNode);
+       saopexpr->location = -1;
+
+       /*
+        * Finally, build an IndexClause based on the SAOP node.  Use
+        * make_simple_restrictinfo() to get RestrictInfo with clean selectivity
+        * estimations, because they may differ from the estimation made for an OR
+        * clause.  Although it is not a lossy expression, keep the original rinfo
+        * in iclause->rinfo as prescribed.
+        */
+       iclause = makeNode(IndexClause);
+       iclause->rinfo = rinfo;
+       iclause->indexquals = list_make1(make_simple_restrictinfo(root,
+                                                                                                                         &saopexpr->xpr));
+       iclause->lossy = false;
+       iclause->indexcol = indexcol;
+       iclause->indexcols = NIL;
+       return iclause;
+}
+
 /*
  * expand_indexqual_rowcompare --- expand a single indexqual condition
  *             that is a RowCompareExpr
index d3358dfc3942d1d8ef508765110a11d8504278c6..e4d117e47ae1335dddc3d5a19d13eb1ae2930455 100644 (file)
@@ -1844,18 +1844,67 @@ DROP TABLE onek_with_null;
 EXPLAIN (COSTS OFF)
 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)
+                                  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 * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = (SELECT 1 + 2) OR tenthous = 42);
+                                       QUERY PLAN                                       
+----------------------------------------------------------------------------------------
+ Index Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: ((thousand = 42) AND (tenthous = ANY (ARRAY[1, (InitPlan 1).col1, 42])))
+   InitPlan 1
+     ->  Result
+(4 rows)
+
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = (SELECT 1 + 2) 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 = 42) OR (thousand = 99)))
+         ->  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 * 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);
@@ -1864,6 +1913,27 @@ SELECT * FROM tenk1
       42 |    5530 |   0 |    2 |   2 |      2 |      42 |       42 |          42 |        42 |       42 |  84 |   85 | QBAAAA   | SEIAAA   | OOOOxx
 (1 row)
 
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1::numeric OR tenthous = 3::int4 OR tenthous = 42::numeric);
+                                                 QUERY PLAN                                                  
+-------------------------------------------------------------------------------------------------------------
+ Bitmap Heap Scan on tenk1
+   Recheck Cond: (thousand = 42)
+   Filter: (((tenthous)::numeric = '1'::numeric) OR (tenthous = 3) OR ((tenthous)::numeric = '42'::numeric))
+   ->  Bitmap Index Scan on tenk1_thous_tenthous
+         Index Cond: (thousand = 42)
+(5 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1
+  WHERE tenthous = 1::numeric OR tenthous = 3::int4 OR tenthous = 42::numeric;
+                                                 QUERY PLAN                                                  
+-------------------------------------------------------------------------------------------------------------
+ Seq Scan on tenk1
+   Filter: (((tenthous)::numeric = '1'::numeric) OR (tenthous = 3) OR ((tenthous)::numeric = '42'::numeric))
+(2 rows)
+
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM tenk1
   WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
@@ -1872,6 +1942,102 @@ SELECT count(*) FROM tenk1
  Aggregate
    ->  Bitmap Heap Scan on tenk1
          Recheck Cond: ((hundred = 42) AND ((thousand = 42) OR (thousand = 99)))
+         ->  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 < 42) OR (thousand < 99) OR (43 > thousand) OR (42 > thousand)))
+         ->  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)
+
+SELECT count(*) FROM tenk1
+  WHERE hundred = 42 AND (thousand < 42 OR thousand < 99 OR 43 > thousand OR 42 > thousand);
+ count 
+-------
+    10
+(1 row)
+
+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 = 1) OR (tenthous = 3))) 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 = 42) OR (thousand = 99) OR (tenthous < 2))) OR (thousand = 41))
+         ->  BitmapOr
+               ->  BitmapAnd
+                     ->  Bitmap Index Scan on tenk1_hundred
+                           Index Cond: (hundred = 42)
+                     ->  BitmapOr
+                           ->  Bitmap Index Scan on tenk1_thous_tenthous
+                                 Index Cond: (thousand = 42)
+                           ->  Bitmap Index Scan on tenk1_thous_tenthous
+                                 Index Cond: (thousand = 99)
+                           ->  Bitmap Index Scan on tenk1_thous_tenthous
+                                 Index Cond: (tenthous < 2)
+               ->  Bitmap Index Scan on tenk1_thous_tenthous
+                     Index Cond: (thousand = 41)
+(16 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 = 42) OR (thousand = 41) OR ((thousand = 99) AND (tenthous = 2))))
          ->  BitmapAnd
                ->  Bitmap Index Scan on tenk1_hundred
                      Index Cond: (hundred = 42)
@@ -1879,16 +2045,90 @@ SELECT count(*) FROM tenk1
                      ->  Bitmap Index Scan on tenk1_thous_tenthous
                            Index Cond: (thousand = 42)
                      ->  Bitmap Index Scan on tenk1_thous_tenthous
-                           Index Cond: (thousand = 99)
-(11 rows)
+                           Index Cond: (thousand = 41)
+                     ->  Bitmap Index Scan on tenk1_thous_tenthous
+                           Index Cond: ((thousand = 99) AND (tenthous = 2))
+(13 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)
 
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1, tenk2
+  WHERE tenk1.hundred = 42 AND (tenk2.thousand = 42 OR tenk1.thousand = 41 OR tenk2.tenthous = 2) AND
+  tenk2.hundred = tenk1.hundred;
+                                          QUERY PLAN                                           
+-----------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Nested Loop
+         Join Filter: ((tenk2.thousand = 42) OR (tenk1.thousand = 41) OR (tenk2.tenthous = 2))
+         ->  Bitmap Heap Scan on tenk1
+               Recheck Cond: (hundred = 42)
+               ->  Bitmap Index Scan on tenk1_hundred
+                     Index Cond: (hundred = 42)
+         ->  Materialize
+               ->  Bitmap Heap Scan on tenk2
+                     Recheck Cond: (hundred = 42)
+                     ->  Bitmap Index Scan on tenk2_hundred
+                           Index Cond: (hundred = 42)
+(12 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1, tenk2
+  WHERE tenk1.hundred = 42 AND (tenk2.thousand = 42 OR tenk2.thousand = 41 OR tenk2.tenthous = 2) AND
+  tenk2.hundred = tenk1.hundred;
+                                  QUERY PLAN                                  
+------------------------------------------------------------------------------
+ Aggregate
+   ->  Nested Loop
+         ->  Bitmap Heap Scan on tenk2
+               Recheck Cond: (hundred = 42)
+               Filter: ((thousand = 42) OR (thousand = 41) OR (tenthous = 2))
+               ->  Bitmap Index Scan on tenk2_hundred
+                     Index Cond: (hundred = 42)
+         ->  Index Only Scan using tenk1_hundred on tenk1
+               Index Cond: (hundred = 42)
+(9 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1 JOIN tenk2 ON
+  tenk1.hundred = 42 AND (tenk2.thousand = 42 OR tenk2.thousand = 41 OR tenk2.tenthous = 2) AND
+  tenk2.hundred = tenk1.hundred;
+                                  QUERY PLAN                                  
+------------------------------------------------------------------------------
+ Aggregate
+   ->  Nested Loop
+         ->  Bitmap Heap Scan on tenk2
+               Recheck Cond: (hundred = 42)
+               Filter: ((thousand = 42) OR (thousand = 41) OR (tenthous = 2))
+               ->  Bitmap Index Scan on tenk2_hundred
+                     Index Cond: (hundred = 42)
+         ->  Index Only Scan using tenk1_hundred on tenk1
+               Index Cond: (hundred = 42)
+(9 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1 LEFT JOIN tenk2 ON
+  tenk1.hundred = 42 AND (tenk2.thousand = 42 OR tenk2.thousand = 41 OR tenk2.tenthous = 2) AND
+  tenk2.hundred = tenk1.hundred;
+                                     QUERY PLAN                                     
+------------------------------------------------------------------------------------
+ Aggregate
+   ->  Nested Loop Left Join
+         Join Filter: (tenk1.hundred = 42)
+         ->  Index Only Scan using tenk1_hundred on tenk1
+         ->  Memoize
+               Cache Key: tenk1.hundred
+               Cache Mode: logical
+               ->  Index Scan using tenk2_hundred on tenk2
+                     Index Cond: (hundred = tenk1.hundred)
+                     Filter: ((thousand = 42) OR (thousand = 41) OR (tenthous = 2))
+(10 rows)
+
 --
 -- Check behavior with duplicate index column contents
 --
index 9b2973694ffb285a7f5df7433d4dec0ba792f9d6..270a7191e680261f13dbdee77bc5dd7391425753 100644 (file)
@@ -4348,15 +4348,64 @@ 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 = 3) OR (unique2 = 7)))
+               Filter: ((unique1 = 1) OR (unique2 = 3) OR (unique2 = 7))
                ->  BitmapOr
                      ->  Bitmap Index Scan on tenk1_unique1
                            Index Cond: (unique1 = 1)
                      ->  Bitmap Index Scan on tenk1_unique2
-                           Index Cond: (unique2 = 3)
+                           Index Cond: (unique2 = ANY ('{3,7}'::integer[]))
+(18 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 = 3) OR (a.unique2 = 7)) 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 = 3) OR (unique2 = 7)))
+               Filter: ((unique1 = 1) OR (unique2 = 3) OR (unique2 = 7))
+               ->  BitmapOr
+                     ->  Bitmap Index Scan on tenk1_unique1
+                           Index Cond: (unique1 = 1)
                      ->  Bitmap Index Scan on tenk1_unique2
-                           Index Cond: (unique2 = 7)
-(19 rows)
+                           Index Cond: (unique2 = ANY ('{3,7}'::integer[]))
+(18 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 = 3) OR (a.unique2 = 7)) 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 = 3) OR (unique2 = 7)))
+               Filter: ((unique1 < 20) OR (unique1 = 3) OR (unique1 = 1) OR (unique2 = 3) OR (unique2 = 7))
+               ->  BitmapOr
+                     ->  Bitmap Index Scan on tenk1_unique1
+                           Index Cond: (unique1 < 20)
+                     ->  Bitmap Index Scan on tenk1_unique1
+                           Index Cond: (unique1 = 3)
+                     ->  Bitmap Index Scan on tenk1_unique1
+                           Index Cond: (unique1 = 1)
+                     ->  Bitmap Index Scan on tenk1_unique2
+                           Index Cond: (unique2 = ANY ('{3,7}'::integer[]))
+(16 rows)
 
 --
 -- test placement of movable quals in a parameterized join tree
index 6d127c19f47da6dbd2f8d486fbf823a6db3238e5..fd5654df35ebb5f7e43b8021bb6d11df02797691 100644 (file)
@@ -4494,6 +4494,13 @@ SELECT * FROM rls_tbl WHERE a <<< 1000;
 ---
 (0 rows)
 
+EXPLAIN (COSTS OFF) SELECT * FROM rls_tbl WHERE a <<< 1000 or a <<< 900;
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
+
 DROP OPERATOR <<< (int, int);
 DROP FUNCTION op_leak(int, int);
 RESET SESSION AUTHORIZATION;
index 8c4da95508408758baaf6148915520e3559cf757..a4c7be487ef1865079e589ab2ec1ff9e677c317c 100644 (file)
@@ -3254,6 +3254,8 @@ CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int,
                      restrict = scalarltsel);
 SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
 ERROR:  permission denied for table priv_test_tbl
+SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0;
+ERROR:  permission denied for table priv_test_tbl
 DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
 ERROR:  permission denied for table priv_test_tbl
 -- Grant access via a security barrier view, but hide all data
@@ -3268,6 +3270,11 @@ SELECT * FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not l
 ---+---
 (0 rows)
 
+SELECT * FROM tststats.priv_test_view WHERE a <<< 0 OR b <<< 0; -- Should not leak
+ a | b 
+---+---
+(0 rows)
+
 DELETE FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak
 -- Grant table access, but hide all data with RLS
 RESET SESSION AUTHORIZATION;
@@ -3280,6 +3287,11 @@ SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not le
 ---+---
 (0 rows)
 
+SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0;
+ a | b 
+---+---
+(0 rows)
+
 DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
 -- privilege checks for pg_stats_ext and pg_stats_ext_exprs
 RESET SESSION AUTHORIZATION;
index 6026e15ed31977a44b8296a69455463dcc46cad2..8f4ef0d7a6a888092bc1668f0706b99a7272da54 100644 (file)
@@ -129,6 +129,37 @@ CREATE INDEX guid1_btree ON guid1 USING BTREE (guid_field);
 CREATE INDEX guid1_hash  ON guid1 USING HASH  (guid_field);
 -- unique index test
 CREATE UNIQUE INDEX guid1_unique_BTREE ON guid1 USING BTREE (guid_field);
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM guid1 WHERE guid_field <> '11111111111111111111111111111111' OR
+                                                       guid_field <> '3f3e3c3b-3a30-3938-3736-353433a2313e';
+                                                                   QUERY PLAN                                                                   
+------------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Seq Scan on guid1
+         Filter: ((guid_field <> '11111111-1111-1111-1111-111111111111'::uuid) OR (guid_field <> '3f3e3c3b-3a30-3938-3736-353433a2313e'::uuid))
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM guid1 WHERE guid_field <= '22222222-2222-2222-2222-222222222222' OR
+                                                                       guid_field <= '11111111111111111111111111111111' OR
+                                                                       guid_field <= '3f3e3c3b-3a30-3938-3736-353433a2313e';
+                                                                                                   QUERY PLAN                                                                                                   
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Seq Scan on guid1
+         Filter: ((guid_field <= '22222222-2222-2222-2222-222222222222'::uuid) OR (guid_field <= '11111111-1111-1111-1111-111111111111'::uuid) OR (guid_field <= '3f3e3c3b-3a30-3938-3736-353433a2313e'::uuid))
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM guid1 WHERE guid_field = '3f3e3c3b-3a30-3938-3736-353433a2313e' OR
+                                                       guid_field = '11111111111111111111111111111111';
+                                                                  QUERY PLAN                                                                  
+----------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Seq Scan on guid1
+         Filter: ((guid_field = '3f3e3c3b-3a30-3938-3736-353433a2313e'::uuid) OR (guid_field = '11111111-1111-1111-1111-111111111111'::uuid))
+(3 rows)
+
 -- should fail
 INSERT INTO guid1(guid_field) VALUES('11111111-1111-1111-1111-111111111111');
 ERROR:  duplicate key value violates unique constraint "guid1_unique_btree"
index fe162cc7c304e61e6c7bb7d79a3ce68114a818d2..71a7115067eea4236582cdd8e9128e5014848b8c 100644 (file)
@@ -732,12 +732,81 @@ SELECT * FROM tenk1
 SELECT * FROM tenk1
   WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
 
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = (SELECT 1 + 2) OR tenthous = 42);
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1 OR tenthous = (SELECT 1 + 2) OR tenthous = 42);
+
 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 * 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);
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1
+  WHERE thousand = 42 AND (tenthous = 1::numeric OR tenthous = 3::int4 OR tenthous = 42::numeric);
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM tenk1
+  WHERE tenthous = 1::numeric OR tenthous = 3::int4 OR tenthous = 42::numeric;
+
+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);
+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);
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1, tenk2
+  WHERE tenk1.hundred = 42 AND (tenk2.thousand = 42 OR tenk1.thousand = 41 OR tenk2.tenthous = 2) AND
+  tenk2.hundred = tenk1.hundred;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1, tenk2
+  WHERE tenk1.hundred = 42 AND (tenk2.thousand = 42 OR tenk2.thousand = 41 OR tenk2.tenthous = 2) AND
+  tenk2.hundred = tenk1.hundred;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1 JOIN tenk2 ON
+  tenk1.hundred = 42 AND (tenk2.thousand = 42 OR tenk2.thousand = 41 OR tenk2.tenthous = 2) AND
+  tenk2.hundred = tenk1.hundred;
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM tenk1 LEFT JOIN tenk2 ON
+  tenk1.hundred = 42 AND (tenk2.thousand = 42 OR tenk2.thousand = 41 OR tenk2.tenthous = 2) AND
+  tenk2.hundred = tenk1.hundred;
 --
 -- Check behavior with duplicate index column contents
 --
index 4c9c3e9f49be1bb2bf073adc402d9eb04b50c915..1004fc03551a737a8a352c43bd37a759381ac962 100644 (file)
@@ -1462,6 +1462,15 @@ 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
 --
index eab7d99003eec335614e2a18477b9a189e3582da..cf09f62eabacd840c14583576021175997cfe9e0 100644 (file)
@@ -2177,6 +2177,7 @@ CREATE FUNCTION op_leak(int, int) RETURNS bool
 CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int,
                      restrict = scalarltsel);
 SELECT * FROM rls_tbl WHERE a <<< 1000;
+EXPLAIN (COSTS OFF) SELECT * FROM rls_tbl WHERE a <<< 1000 or a <<< 900;
 DROP OPERATOR <<< (int, int);
 DROP FUNCTION op_leak(int, int);
 RESET SESSION AUTHORIZATION;
index 0c08a6cc42e89eea7d957875bef058bb6b6ade70..5c786b16c6f91c70821a6263791ed748e0a45a1c 100644 (file)
@@ -1634,6 +1634,7 @@ CREATE FUNCTION op_leak(int, int) RETURNS bool
 CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int,
                      restrict = scalarltsel);
 SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
+SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0;
 DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
 
 -- Grant access via a security barrier view, but hide all data
@@ -1645,6 +1646,7 @@ GRANT SELECT, DELETE ON tststats.priv_test_view TO regress_stats_user1;
 -- Should now have access via the view, but see nothing and leak nothing
 SET SESSION AUTHORIZATION regress_stats_user1;
 SELECT * FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak
+SELECT * FROM tststats.priv_test_view WHERE a <<< 0 OR b <<< 0; -- Should not leak
 DELETE FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak
 
 -- Grant table access, but hide all data with RLS
@@ -1655,6 +1657,7 @@ GRANT SELECT, DELETE ON tststats.priv_test_tbl TO regress_stats_user1;
 -- Should now have direct table access, but see nothing and leak nothing
 SET SESSION AUTHORIZATION regress_stats_user1;
 SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
+SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0;
 DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
 
 -- privilege checks for pg_stats_ext and pg_stats_ext_exprs
index c88f6d087a716189d621b0313be2ded63ea94744..75ee966ded08e8c60ee1945a673528d91253edcf 100644 (file)
@@ -63,6 +63,18 @@ CREATE INDEX guid1_hash  ON guid1 USING HASH  (guid_field);
 
 -- unique index test
 CREATE UNIQUE INDEX guid1_unique_BTREE ON guid1 USING BTREE (guid_field);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM guid1 WHERE guid_field <> '11111111111111111111111111111111' OR
+                                                       guid_field <> '3f3e3c3b-3a30-3938-3736-353433a2313e';
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM guid1 WHERE guid_field <= '22222222-2222-2222-2222-222222222222' OR
+                                                                       guid_field <= '11111111111111111111111111111111' OR
+                                                                       guid_field <= '3f3e3c3b-3a30-3938-3736-353433a2313e';
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM guid1 WHERE guid_field = '3f3e3c3b-3a30-3938-3736-353433a2313e' OR
+                                                       guid_field = '11111111111111111111111111111111';
+
 -- should fail
 INSERT INTO guid1(guid_field) VALUES('11111111-1111-1111-1111-111111111111');