On 25.11.2023 19:13, Alexander Korotkov wrote:
On Sat, Nov 25, 2023 at 1:10 PM Alena Rybakina
<a.rybak...@postgrespro.ru> wrote:
On 25.11.2023 04:13, Alexander Korotkov wrote:

It seems to me there is a confusion.  I didn't mean we need to move
conversion of OR-expressions to ANY into choose_bitmap_and() function
or anything like this.  My idea was to avoid degradation of plans,
which I've seen in [1].  Current code for generation of bitmap paths
considers the possibility to split OR-expressions into distinct bitmap
index scans.  But it doesn't consider this possibility for
ANY-expressions.  So, my idea was to enhance our bitmap scan
generation to consider split values of ANY-expressions into distinct
bitmap index scans.  So, in the example [1] and similar queries
conversion of OR-expressions to ANY wouldn't affect the generation of
bitmap paths.

Thanks for the explanation, yes, I did not understand the idea correctly at 
first. I will try to implement something similar.
Alena, great, thank you.  I'm looking forward to the updated patch.

I wrote the patch (any_to_or.diff.txt), although it is still under development (not all regression tests have been successful so far), it is already clear that for a query where a bad plan was chosen before, it is now choosing a more optimal query plan.

postgres=# set enable_or_transformation =on;
SET
postgres=# explain select * from test where (x = 1 or x = 2) and y = 100;
                                                  QUERY PLAN
--------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on test  (cost=8.60..12.62 rows=1 width=12)
   Recheck Cond: (((y = '100'::double precision) AND (x = 1)) OR ((y = '100'::double precision) AND (x = 2)))
   ->  BitmapOr  (cost=8.60..8.60 rows=1 width=0)
         ->  Bitmap Index Scan on test_x_1_y  (cost=0.00..4.30 rows=1 width=0)
               Index Cond: (y = '100'::double precision)
         ->  Bitmap Index Scan on test_x_2_y  (cost=0.00..4.30 rows=1 width=0)
               Index Cond: (y = '100'::double precision)
(7 rows)

While I'm thinking how to combine it now.

BTW, while I was figuring out how create_index_paths works and creating bitmapscan indexes, I think I found a bug with unallocated memory (fix patch is bugfix.diff.txt). Without a fix here, it falls into the crust at the stage of assigning a value to any of the variables, specifically, skip_lower_stop and skip_nonnative_saop. I discovered it when I forced to form a bitmapindex plan for ANY (any_to_or.diff.txt). I'm causing a problem with my OR->ANY conversion patch.

--
Regards,
Alena Rybakina
Postgres Professional
diff --git a/src/backend/optimizer/path/indxpath.c 
b/src/backend/optimizer/path/indxpath.c
index 03a5fbdc6dc..c242eec34d6 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -34,6 +34,7 @@
 #include "optimizer/restrictinfo.h"
 #include "utils/lsyscache.h"
 #include "utils/selfuncs.h"
+#include "parser/parse_expr.h"
 
 
 /* XXX see PartCollMatchesExprColl */
@@ -715,8 +716,8 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
                                List **bitindexpaths)
 {
        List       *indexpaths;
-       bool            skip_nonnative_saop = false;
-       bool            skip_lower_saop = false;
+       bool            skip_nonnative_saop;
+       bool        skip_lower_saop;
        ListCell   *lc;
 
        /*
@@ -737,7 +738,7 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
         * that supports them, then try again including those clauses.  This 
will
         * produce paths with more selectivity but no ordering.
         */
-       if (skip_lower_saop)
+       if (skip_lower_saop || enable_or_transformation)
        {
                indexpaths = list_concat(indexpaths,
                                                                 
build_index_paths(root, rel,
@@ -778,7 +779,7 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
         * natively, generate bitmap scan paths relying on executor-managed
         * ScalarArrayOpExpr.
         */
-       if (skip_nonnative_saop)
+       if (skip_nonnative_saop || enable_or_transformation)
        {
                indexpaths = build_index_paths(root, rel,
                                                                           
index, clauses,
@@ -908,7 +912,7 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
                        {
                                if (!index->amsearcharray)
                                {
-                                       if (skip_nonnative_saop)
+                                       if (skip_nonnative_saop || 
enable_or_transformation)
                                        {
                                                /* Ignore because not supported 
by index */
                                                *skip_nonnative_saop = true;
@@ -919,7 +923,7 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
                                }
                                if (indexcol > 0)
                                {
-                                       if (skip_lower_saop)
+                                       if (skip_lower_saop || 
enable_or_transformation)
                                        {
                                                /* Caller doesn't want to lose 
index ordering */
                                                *skip_lower_saop = true;
diff --git a/src/backend/optimizer/path/indxpath.c 
b/src/backend/optimizer/path/indxpath.c
index 03a5fbdc6dc..21a6d8febf4 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -854,6 +854,9 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
        bool            index_only_scan;
        int                     indexcol;
 
+       skip_lower_saop = (bool *) palloc(sizeof(bool));
+       skip_nonnative_saop = (bool *) palloc(sizeof(bool));
+
        /*
         * Check that index supports the desired scan type(s)
         */
diff --git a/src/backend/optimizer/path/allpaths.c 
b/src/backend/optimizer/path/allpaths.c
index 67921a08262..3cddc0a4082 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -50,6 +50,8 @@
 #include "port/pg_bitutils.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
+#include "parser/parse_expr.h"
+#include "utils/array.h"
 
 
 /* Bitmask flags for pushdown_safety_info.unsafeFlags */
@@ -759,6 +761,179 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo 
*rel,
        rel->consider_parallel = true;
 }
 
+static List*
+research_or_list(Expr *qual, List *or_list, RestrictInfo *sub_rinfo)
+{
+       List *elem_exprs = NIL;
+
+       /* Check: it is an expr of the form 'F(x) oper ConstExpr' */
+       if (!IsA(qual, ScalarArrayOpExpr))
+       {
+               /* Again, it's not the expr we can transform */
+               or_list = lappend(or_list, qual);
+       }
+       else
+       {
+               ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) qual;
+               Expr       *rightop = (Expr *) lsecond(saop->args);
+               ListCell   *lc1;
+
+               if (sub_rinfo && !op_mergejoinable(((OpExpr *) 
sub_rinfo->clause)->opno, exprType(get_leftop(sub_rinfo->clause))))
+                       {
+                               /* And again, filter out non-equality operators 
*/
+                               or_list = lappend(or_list, (void *) qual);
+                       }
+               else if (rightop && IsA(rightop, Const))
+               {
+                       ArrayType  *arrval;
+                       int16           elemlen;
+                       bool            elembyval;
+                       char            elemalign;
+                       Datum      *elem_values;
+                       bool       *elem_nulls;
+                       int                     num_elems,
+                                               i;
+                       Const      *arr = (Const *) rightop;
+
+                       arrval = DatumGetArrayTypeP(arr->constvalue);
+                       get_typlenbyvalalign(ARR_ELEMTYPE(arrval),
+                                                               &elemlen, 
&elembyval, &elemalign);
+                       deconstruct_array(arrval,
+                                                       ARR_ELEMTYPE(arrval),
+                                                       elemlen, elembyval, 
elemalign,
+                                                       &elem_values, 
&elem_nulls,
+                                                       &num_elems);
+
+                       for (i = 0; i < num_elems; i++)
+                       {
+                               Const      *elem_expr;
+
+                               /*
+                               * A null array element must lead to a null 
comparison result,
+                               * since saop_op is known strict.  We can ignore 
it in the
+                               * useOr case, but otherwise it implies 
self-contradiction.
+                               */
+                               if (elem_nulls[i])
+                               {
+                                       or_list = lappend(or_list, (void *) 
qual);
+                                       elem_exprs = NIL;
+                                       break;
+                               }
+
+                               elem_expr = makeConst(ARR_ELEMTYPE(arrval), -1,
+                                                                       
arr->constcollid, elemlen,
+                                                                       
elem_values[i], false, elembyval);
+                               elem_exprs = lappend(elem_exprs, elem_expr);
+                       }
+               }
+               else if (rightop && IsA(rightop, ArrayExpr) && !((ArrayExpr *) 
rightop)->multidims)
+               {
+                       ArrayExpr  *arrexpr = (ArrayExpr  
*)get_rightop(rightop);
+                       elem_exprs = arrexpr->elements;
+               }
+               else
+                       or_list = lappend(or_list, qual);
+
+               if (elem_exprs)
+               foreach(lc1, elem_exprs)
+               {
+                       Expr       *elem_clause;
+
+                       elem_clause = make_opclause(((ScalarArrayOpExpr*) 
qual)->opno, BOOLOID, false,
+                                                                               
(Expr *) linitial(((ScalarArrayOpExpr*)qual)->args), lfirst(lc1),
+                                                                               
InvalidOid, ((ScalarArrayOpExpr*)qual)->inputcollid);
+                       or_list = lappend(or_list, (void*) elem_clause);
+               }
+       }
+       return or_list;
+}
+
+static List *
+get_baserestrictinfo(PlannerInfo *root, List *baserestrictinfo)
+{
+       ListCell   *lc;
+       List       *modified_rinfo = NIL;
+       bool            or_transformation = false;
+
+       if (!enable_or_transformation)
+               return NULL;
+
+       foreach(lc, baserestrictinfo)
+       {
+               RestrictInfo   *rinfo_base = lfirst_node(RestrictInfo, lc);
+               RestrictInfo   *rinfo;
+               List               *or_list = NIL;
+
+               ListCell           *lc_eargs,
+                                          *lc_rargs;
+
+               if (!IsA(rinfo_base->clause, BoolExpr) && 
!IsA(rinfo_base->clause, ScalarArrayOpExpr))
+               {
+                       /* Add a clause without changes */
+                       modified_rinfo = lappend(modified_rinfo, rinfo_base);
+                       continue;
+               }
+
+               if (IsA(rinfo_base->clause, BoolExpr) && 
is_orclause(rinfo_base->clause))
+               forboth(lc_eargs, ((BoolExpr *) rinfo_base->clause)->args,
+                               lc_rargs, ((BoolExpr *) 
rinfo_base->orclause)->args)
+               {
+                       Expr                       *orqual = (Expr *) 
lfirst(lc_eargs);
+                       RestrictInfo       *sub_rinfo = 
lfirst_node(RestrictInfo, lc_rargs);
+
+                       if (!IsA(orqual, OpExpr) ||
+                               !(bms_is_empty(sub_rinfo->left_relids) ^
+                               bms_is_empty(sub_rinfo->right_relids)) ||
+                               contain_volatile_functions((Node *) orqual))
+                       {
+                               /* Again, it's not the expr we can transform */
+                               or_list = lappend(or_list, (void *) orqual);
+                               continue;
+                       }
+
+                       or_list = research_or_list(orqual, or_list, sub_rinfo);
+               }
+               else if (IsA(rinfo_base->clause, ScalarArrayOpExpr))
+               {
+                       Expr                       *orqual = rinfo_base->clause;
+
+                       or_list = research_or_list(orqual, or_list, NULL);
+               }
+               else
+               {
+                       or_list = lappend(or_list, (void*)rinfo_base->clause);
+               }
+
+
+               rinfo = make_restrictinfo(root,
+                                 list_length(or_list) > 1 ? 
makeBoolExpr(OR_EXPR, or_list, -1):
+                                                                               
                                                                                
(Expr *) linitial(or_list),
+                                 rinfo_base->is_pushed_down,
+                                 rinfo_base->has_clone,
+                                 rinfo_base->is_clone,
+                                 rinfo_base->pseudoconstant,
+                                 rinfo_base->security_level,
+                                 rinfo_base->required_relids,
+                                 rinfo_base->incompatible_relids,
+                                 rinfo_base->outer_relids);
+               rinfo->eval_cost=rinfo_base->eval_cost;
+               rinfo->norm_selec=rinfo_base->norm_selec;
+               rinfo->outer_selec=rinfo_base->outer_selec;
+               rinfo->left_bucketsize=rinfo_base->left_bucketsize;
+               rinfo->right_bucketsize=rinfo_base->right_bucketsize;
+               rinfo->left_mcvfreq=rinfo_base->left_mcvfreq;
+               rinfo->right_mcvfreq=rinfo_base->right_mcvfreq;
+               modified_rinfo = lappend(modified_rinfo, rinfo);
+               or_transformation = true;
+       }
+
+       if(or_transformation)
+       return modified_rinfo;
+
+       return baserestrictinfo;
+}
+
+
 /*
  * set_plain_rel_pathlist
  *       Build access paths for a plain relation (no subquery, no inheritance)
@@ -767,6 +942,7 @@ static void
 set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 {
        Relids          required_outer;
+       List *baserestrict_base = copyObject(rel->baserestrictinfo);
 
        /*
         * We don't support pushing join clauses into the quals of a seqscan, 
but
@@ -778,6 +954,8 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, 
RangeTblEntry *rte)
        /* Consider sequential scan */
        add_path(rel, create_seqscan_path(root, rel, required_outer, 0));
 
+       rel->baserestrictinfo = get_baserestrictinfo(root, 
rel->baserestrictinfo);
+
        /* If appropriate, consider parallel sequential scan */
        if (rel->consider_parallel && required_outer == NULL)
                create_plain_partial_paths(root, rel);

Reply via email to