From 9e0a0200525e7e72f1a91f658b4674fbf78ea18d Mon Sep 17 00:00:00 2001 From: Alena Rybakina Date: Thu, 21 Sep 2023 19:15:42 +0300 Subject: [PATCH] Replace OR clause to ANY expressions. Replace (X=N1) OR (X=N2) ... with X = ANY(N1, N2) on the stage of the optimiser when we are still working with a tree expression. Firstly, we do not try to make a transformation for "non-or" expressions or inequalities and the creation of a relation with "or" expressions occurs according to the same scenario. Secondly, we do not make transformations if there are less than set or_transform_limit. Thirdly, it is worth considering that we consider "or" expressions only at the current level. Authors: Alena Rybakina , Andrey Lepikhov Reviewed-by: Peter Geoghegan , Ranier Vilela --- src/backend/optimizer/plan/planner.c | 3 +- src/backend/optimizer/util/orclauses.c | 232 +++++++++++++++++++++++++ src/include/optimizer/orclauses.h | 2 +- 3 files changed, 235 insertions(+), 2 deletions(-) diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 44efb1f4ebc..80935cec7aa 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -67,6 +67,7 @@ #include "utils/rel.h" #include "utils/selfuncs.h" #include "utils/syscache.h" +#include "optimizer/orclauses.h" /* GUC parameters */ double cursor_tuple_fraction = DEFAULT_CURSOR_TUPLE_FRACTION; @@ -1169,7 +1170,7 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind) if (kind == EXPRKIND_QUAL) { expr = (Node *) canonicalize_qual((Expr *) expr, false); - +expr = transform_ors(root, (Expr *) expr); #ifdef OPTIMIZER_DEBUG printf("After canonicalize_qual()\n"); pprint(expr); diff --git a/src/backend/optimizer/util/orclauses.c b/src/backend/optimizer/util/orclauses.c index 6ef9d14b902..805f4b7294a 100644 --- a/src/backend/optimizer/util/orclauses.c +++ b/src/backend/optimizer/util/orclauses.c @@ -22,6 +22,10 @@ #include "optimizer/optimizer.h" #include "optimizer/orclauses.h" #include "optimizer/restrictinfo.h" +#include "utils/lsyscache.h" +#include "parser/parse_expr.h" +#include "parser/parse_coerce.h" +#include "parser/parse_oper.h" static bool is_safe_restriction_clause_for(RestrictInfo *rinfo, RelOptInfo *rel); @@ -29,7 +33,235 @@ static Expr *extract_or_clause(RestrictInfo *or_rinfo, RelOptInfo *rel); static void consider_new_or_clause(PlannerInfo *root, RelOptInfo *rel, Expr *orclause, RestrictInfo *join_or_rinfo); +typedef struct OrClauseGroupEntry +{ + Node *node; + List *consts; + Oid scalar_type; + Oid opno; + Expr *expr; +} OrClauseGroupEntry; + +static Node * +transform_ors_for_rel(BoolExpr *expr_orig) +{ + List *or_list = NIL; + List *groups_list = NIL; + ListCell *lc; + + /* If this is not an 'OR' expression, skip the transformation */ + if (expr_orig->boolop != OR_EXPR || + list_length(expr_orig->args) < 1) + return (Node*) expr_orig; + + foreach(lc, expr_orig->args) + { + Node *orqual = lfirst(lc); + Node *const_expr; + Node *nconst_expr; + ListCell *lc_groups; + OrClauseGroupEntry *gentry; + + if (!IsA(orqual, OpExpr)) + { + or_list = lappend(or_list, 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. + */ + if (IsA(get_leftop(orqual), Const)) + { + nconst_expr = get_rightop(orqual); + const_expr = get_leftop(orqual); + } + else if (IsA(get_rightop(orqual), Const)) + { + const_expr = get_rightop(orqual); + nconst_expr = get_leftop(orqual); + } + else + { + or_list = lappend(or_list, orqual); + continue; + } + + if (!op_mergejoinable(((OpExpr *) orqual)->opno, exprType(nconst_expr))) + { + or_list = lappend(or_list, orqual); + continue; + } + + /* + * At this point we definitely have a transformable clause. + * Classify it and add into specific group of clauses, or create new + * group. + * TODO: to manage complexity in the case of many different clauses + * (X1=C1) OR (X2=C2 OR) ... (XN = CN) we could invent something + * like a hash table. But also we believe, that the case of many + * different variable sides is very rare. + */ + foreach(lc_groups, groups_list) + { + OrClauseGroupEntry *v = (OrClauseGroupEntry *) lfirst(lc_groups); + + Assert(v->node != NULL); + + if (equal(v->node, nconst_expr)) + { + v->consts = lappend(v->consts, const_expr); + nconst_expr = NULL; + break; + } + } + + if (nconst_expr == NULL) + /* + * The clause classified successfully and added into existed + * clause group. + */ + continue; + + /* New clause group needed */ + gentry = palloc(sizeof(OrClauseGroupEntry)); + gentry->node = nconst_expr; + gentry->consts = list_make1(const_expr); + gentry->expr = (Expr *) orqual; + groups_list = lappend(groups_list, (void *) gentry); + } + if (groups_list == NIL) + { + /* + * No any transformations possible with this list of arguments. Here we + * already made all underlying transformations. Thus, just return the + * transformed bool expression. + */ + return (Node *) expr_orig; + } + else + { + ListCell *lc_args; + + /* Let's convert each group of clauses to an IN operation. */ + + /* + * Go through the list of groups and convert each, where number of + * consts more than 1. trivial groups move to OR-list again + */ + + foreach(lc_args, groups_list) + { + OrClauseGroupEntry *gentry = (OrClauseGroupEntry *) lfirst(lc_args); + List *allexprs; + Oid scalar_type; + Oid array_type; + + Assert(list_length(gentry->consts) > 0); + + if (list_length(gentry->consts) == 1) + { + /* + * Only one element in the class. Return rinfo into the BoolExpr + * args list unchanged. + */ + list_free(gentry->consts); + or_list = lappend(or_list, gentry->expr); + continue; + } + + /* + * Do the transformation. + * + * First of all, try to select a common type for the array elements. + * Note that since the LHS' type is first in the list, it will be + * preferred when there is doubt (eg, when all the RHS items are + * unknown literals). + * + * Note: use list_concat here not lcons, to avoid damaging rnonvars. + * + * As a source of insides, use make_scalar_array_op() + */ + allexprs = list_concat(list_make1(gentry->node), gentry->consts); + scalar_type = select_common_type(NULL, allexprs, NULL, NULL); + + if (scalar_type != RECORDOID && OidIsValid(scalar_type)) + array_type = get_array_type(scalar_type); + else + array_type = InvalidOid; + + if (array_type != InvalidOid) + { + /* + * OK: coerce all the right-hand non-Var inputs to the common + * type and build an ArrayExpr for them. + */ + List *aexprs; + ArrayExpr *newa; + ScalarArrayOpExpr *saopexpr; + ListCell *l; + + aexprs = NIL; + + foreach(l, gentry->consts) + { + Node *rexpr = (Node *) lfirst(l); + + rexpr = coerce_to_common_type(NULL, rexpr, + scalar_type, + "IN"); + aexprs = lappend(aexprs, rexpr); + } + + 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; + + saopexpr = + (ScalarArrayOpExpr *) + make_scalar_array_op(NULL, + list_make1(makeString((char *) "=")), + true, + gentry->node, + (Node *) newa, + -1); + saopexpr->inputcollid = exprInputCollation((Node *)gentry->expr);; + + or_list = lappend(or_list, (void *) saopexpr); + } + else + { + list_free(gentry->consts); + or_list = lappend(or_list, gentry->expr); + continue; + } + } + + list_free_deep(groups_list); + } + + /* One more trick: assemble correct clause */ + return (Node *) ((list_length(or_list) > 1) ? + makeBoolExpr(OR_EXPR, or_list, expr_orig->location) : + linitial(or_list)); +} +Node * +transform_ors(PlannerInfo *root, Expr *jtnode) +{ + if (IsA(jtnode, BoolExpr)) + return transform_ors_for_rel((BoolExpr *) jtnode); + return (Node *) jtnode; +} /* * extract_restriction_or_clauses * Examine join OR-of-AND clauses to see if any useful restriction OR diff --git a/src/include/optimizer/orclauses.h b/src/include/optimizer/orclauses.h index f9dbe6a2972..6a232aeb3ed 100644 --- a/src/include/optimizer/orclauses.h +++ b/src/include/optimizer/orclauses.h @@ -17,5 +17,5 @@ #include "nodes/pathnodes.h" extern void extract_restriction_or_clauses(PlannerInfo *root); - +extern Node * transform_ors(PlannerInfo *root, Expr *jtnode); #endif /* ORCLAUSES_H */ -- 2.34.1