Sorry, I forgot to apply my patches. For the first experiment was
0001-OR-to-ANY-in-parser-and-ANY-to-OR-in-index.diff and for the second
experiment was 0002-OR-to-ANY-in-index.diff.
On 30.11.2023 11:00, Alena Rybakina wrote:
Hi!
Honestly, it seems very hard to avoid the conclusion that this
transformation is being done at too early a stage. Parse analysis is
not the time to try to do query optimization. I can't really believe
that there's a way to produce a committable patch along these lines.
Ideally, a transformation like this should be done after we know what
plan shape we're using (or considering using), so that we can make
cost-based decisions about whether to transform or not. But at the
very least it should happen somewhere in the planner. There's really
no justification for parse analysis rewriting the SQL that the user
entered.
Here, we assume that array operation is generally better than many ORs.
As a result, it should be more effective to make OR->ANY
transformation in the parser (it is a relatively lightweight
operation here) and, as a second phase, decompose that in the optimizer.
We implemented earlier prototypes in different places of the
optimizer, and I'm convinced that only this approach resolves the
issues we found.
Does this approach look weird? Maybe. We can debate it in this thread.
I think this is incorrect, and the example of A. Korotkov confirms
this. If we perform the conversion at the parsing stage, we will skip
the more important conversion using OR expressions. I'll show you in
the example below.
First of all, I will describe my idea to combine two approaches to
obtaining plans with OR to ANY transformation and ANY to OR
transformation. I think they are both good, and we can't work with
just one of them, we should consider both the option of OR
expressions, and with ANY.
I did this by creating a RelOptInfo with which has references from the
original RelOptInfo, for which conversion is possible either from
ANY->OR, or vice versa. After obtaining the necessary transformation,
I started the procedure for obtaining the seq and index paths for both
relations and then calculated their cost. The relation with the lowest
cost is considered the best.
I'm not sure if this is the best approach, but it's less complicated.
I noticed that I got a lower cost for not the best plan, but I think
this corresponds to another topic related to the wrong estimate
calculation.
1. The first patch is a mixture of the original patch (when we perform
the conversion of OR to ANY at the parsing stage), and when we perform
the conversion at the index creation stage with the conversion to an
OR expression. We can see that the query proposed by A.Korotkov did
not have the best plan with ANY expression at all, and even despite
receiving a query with OR expressions, we cannot get anything better
than SeqScan, due to the lack of effective logical transformations
that would have been performed if we had left the OR expressions.
So, I got query plans using enable_or_transformation if it is enabled:
postgres=# create table test as (select (random()*10)::int x,
(random()*1000) y
from generate_series(1,1000000) i);
create index test_x_1_y on test (y) where x = 1;
create index test_x_2_y on test (y) where x = 2;
vacuum analyze test;
SELECT 1000000
CREATE INDEX
CREATE INDEX
VACUUM
postgres=# explain select * from test where (x = 1 or x = 2) and y = 100;
WARNING: cost with original approach: - 20440.000000
WARNING: cost with OR to ANY applied transfomation: - 15440.000000
QUERY PLAN
--------------------------------------------------------------------------
Gather (cost=1000.00..12690.10 rows=1 width=12)
Workers Planned: 2
-> Parallel Seq Scan on test (cost=0.00..11690.00 rows=1 width=12)
Filter: (((x = 1) OR (x = 2)) AND (y = '100'::double precision))
(4 rows)
and if it is off:
postgres=# set enable_or_transformation =off;
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)
2. The second patch is my patch version when I moved the OR
transformation in the s index formation stage:
So, I got the best query plan despite the possible OR to ANY
transformation:
postgres=# create table test as (select (random()*10)::int x,
(random()*1000) y
from generate_series(1,1000000) i);
create index test_x_1_y on test (y) where x = 1;
create index test_x_2_y on test (y) where x = 2;
vacuum analyze test;
SELECT 1000000
CREATE INDEX
CREATE INDEX
VACUUM
postgres=# explain select * from test where (x = 1 or x = 2) and y = 100;
WARNING: cost with original approach: - 12.618000
WARNING: cost with OR to ANY applied transfomation: - 15440.000000
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)
--
Regards,
Alena Rybakina
Postgres Professional
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 0a5bdf8bcc0..9368ae1d052 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -8537,18 +8537,18 @@ insert into utrtest values (2, 'qux');
-- Check case where the foreign partition is a subplan target rel
explain (verbose, costs off)
update utrtest set a = 1 where a = 1 or a = 2 returning *;
- QUERY PLAN
-----------------------------------------------------------------------------------------------------
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------
Update on public.utrtest
Output: utrtest_1.a, utrtest_1.b
Foreign Update on public.remp utrtest_1
Update on public.locp utrtest_2
-> Append
-> Foreign Update on public.remp utrtest_1
- Remote SQL: UPDATE public.loct SET a = 1 WHERE (((a = 1) OR (a = 2))) RETURNING a, b
+ Remote SQL: UPDATE public.loct SET a = 1 WHERE ((a = ANY ('{1,2}'::integer[]))) RETURNING a, b
-> Seq Scan on public.locp utrtest_2
Output: 1, utrtest_2.tableoid, utrtest_2.ctid, NULL::record
- Filter: ((utrtest_2.a = 1) OR (utrtest_2.a = 2))
+ Filter: (utrtest_2.a = ANY ('{1,2}'::integer[]))
(10 rows)
-- The new values are concatenated with ' triggered !'
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 281907a4d83..99207a8670f 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -135,6 +135,36 @@ JumbleQuery(Query *query)
return jstate;
}
+JumbleState *
+JumbleExpr(Expr *expr, uint64 *queryId)
+{
+ JumbleState *jstate = NULL;
+
+ Assert(queryId != NULL);
+
+ jstate = (JumbleState *) palloc(sizeof(JumbleState));
+
+ /* Set up workspace for query jumbling */
+ jstate->jumble = (unsigned char *) palloc(JUMBLE_SIZE);
+ jstate->jumble_len = 0;
+ jstate->clocations_buf_size = 32;
+ jstate->clocations = (LocationLen *)
+ palloc(jstate->clocations_buf_size * sizeof(LocationLen));
+ jstate->clocations_count = 0;
+ jstate->highest_extern_param_id = 0;
+
+ /* Compute query ID */
+ _jumbleNode(jstate, (Node *) expr);
+ *queryId = DatumGetUInt64(hash_any_extended(jstate->jumble,
+ jstate->jumble_len,
+ 0));
+
+ if (*queryId == UINT64CONST(0))
+ *queryId = UINT64CONST(1);
+
+ return jstate;
+}
+
/*
* Enables query identifier computation.
*
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 67921a08262..2e97ff96036 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,12 @@ static void
set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
Relids required_outer;
+ RelOptInfo *reL_alternative = makeNode(RelOptInfo);
+ //reL_alternative = copyObject(rel);
+ reL_alternative = copy_simple_rel(root, rel->relid, rel, reL_alternative);
+ reL_alternative->indexlist = NIL;
+ reL_alternative->baserestrictinfo = rel->baserestrictinfo;
+ reL_alternative->eclass_indexes = NULL;
/*
* We don't support pushing join clauses into the quals of a seqscan, but
@@ -787,6 +968,39 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
/* Consider TID scans */
create_tidscan_paths(root, rel);
+
+ set_cheapest(rel);
+
+ if (rel->reloptkind == RELOPT_BASEREL && enable_or_transformation)
+ {
+ bool applied_transfomation=true;
+ //applied_transfomation = (bool *) palloc(sizeof(bool));
+ reL_alternative->baserestrictinfo = get_baserestrictinfo(root, rel->baserestrictinfo);
+if (applied_transfomation)
+{
+ add_path(reL_alternative, create_seqscan_path(root, reL_alternative, required_outer, 0));
+
+ if (reL_alternative->consider_parallel && required_outer == NULL)
+ create_plain_partial_paths(root, reL_alternative);
+
+ create_index_paths(root, reL_alternative);
+
+ create_tidscan_paths(root, reL_alternative);
+
+ set_cheapest(reL_alternative);
+
+ elog(WARNING, "ors: - %f", rel->cheapest_total_path->total_cost);
+ elog(WARNING, "applied_transfomation: - %f", reL_alternative->cheapest_total_path->total_cost);
+
+ if (reL_alternative->cheapest_total_path->total_cost <= rel->cheapest_total_path->total_cost)
+ {
+ //copy_simple_rel(root, rel->relid, reL_alternative, rel);
+ rel->indexlist = reL_alternative->indexlist;
+ rel->baserestrictinfo = reL_alternative->baserestrictinfo;
+ rel->eclass_indexes = reL_alternative->eclass_indexes;
+ }
+ }
+ }
}
/*
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 5d83f60eb9a..189cee449bf 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -183,6 +183,97 @@ expand_planner_arrays(PlannerInfo *root, int add_size)
root->simple_rel_array_size = new_size;
}
+RelOptInfo *
+copy_simple_rel(PlannerInfo *root, int relid, RelOptInfo *base_rel, RelOptInfo *rel)
+{
+ rel->reloptkind = base_rel->parent ? RELOPT_OTHER_MEMBER_REL : RELOPT_BASEREL;
+ rel->relids = base_rel->relids;
+ rel->rows = base_rel->rows;
+ /* cheap startup cost is interesting iff not all tuples to be retrieved */
+ rel->consider_startup = (root->tuple_fraction > 0);
+ rel->consider_param_startup = base_rel->consider_param_startup; /* might get changed later */
+ rel->consider_parallel = base_rel->consider_parallel; /* might get changed later */
+ rel->reltarget = base_rel->reltarget;
+ rel->pathlist = base_rel->pathlist;
+ rel->ppilist = base_rel->ppilist;
+ rel->partial_pathlist = base_rel->partial_pathlist;
+ rel->cheapest_startup_path = base_rel->cheapest_startup_path;
+ rel->cheapest_total_path = base_rel->cheapest_total_path;
+ rel->cheapest_unique_path = base_rel->cheapest_unique_path;
+ rel->cheapest_parameterized_paths = base_rel->cheapest_parameterized_paths;
+ rel->relid = base_rel->relid;
+ /* min_attr, max_attr, attr_needed, attr_widths are set below */
+ rel->lateral_vars = base_rel->lateral_vars;
+ rel->statlist = base_rel->statlist;
+ rel->pages = base_rel->pages;
+ rel->tuples = base_rel->tuples;
+ rel->allvisfrac = base_rel->allvisfrac;
+ rel->subroot = base_rel->subroot;
+ rel->subplan_params = base_rel->subplan_params;
+ rel->rel_parallel_workers = base_rel->rel_parallel_workers; /* set up in get_relation_info */
+ rel->amflags = base_rel->amflags;
+ rel->parent = base_rel->parent;
+ rel->serverid = base_rel->serverid;
+ rel->userid = base_rel->userid;
+ rel->useridiscurrent = base_rel->useridiscurrent;
+ rel->fdwroutine = base_rel->fdwroutine;
+ rel->fdw_private = base_rel->fdw_private;
+ rel->unique_for_rels = base_rel->unique_for_rels;
+ rel->non_unique_for_rels = base_rel->non_unique_for_rels;
+ rel->baserestrictcost.startup = 0;
+ rel->baserestrictcost.per_tuple = 0;
+ rel->baserestrict_min_security = UINT_MAX;
+ rel->joininfo = base_rel->joininfo;
+ rel->has_eclass_joins = base_rel->has_eclass_joins;
+
+ /*
+ * Pass assorted information down the inheritance hierarchy.
+ */
+ if (base_rel->parent)
+ {
+ /* We keep back-links to immediate parent and topmost parent. */
+ rel->parent = base_rel->parent;
+ rel->top_parent = base_rel->parent->top_parent ? base_rel->parent->top_parent : base_rel->parent;
+ rel->top_parent_relids = base_rel->top_parent->relids;
+
+ /*
+ * A child rel is below the same outer joins as its parent. (We
+ * presume this info was already calculated for the parent.)
+ */
+ rel->nulling_relids = base_rel->parent->nulling_relids;
+
+ /*
+ * Also propagate lateral-reference information from appendrel parent
+ * rels to their child rels. We intentionally give each child rel the
+ * same minimum parameterization, even though it's quite possible that
+ * some don't reference all the lateral rels. This is because any
+ * append path for the parent will have to have the same
+ * parameterization for every child anyway, and there's no value in
+ * forcing extra reparameterize_path() calls. Similarly, a lateral
+ * reference to the parent prevents use of otherwise-movable join rels
+ * for each child.
+ *
+ * It's possible for child rels to have their own children, in which
+ * case the topmost parent's lateral info propagates all the way down.
+ */
+ rel->direct_lateral_relids = base_rel->parent->direct_lateral_relids;
+ rel->lateral_relids = base_rel->parent->lateral_relids;
+ rel->lateral_referencers = base_rel->parent->lateral_referencers;
+ }
+ else
+ {
+ rel->parent = base_rel->parent;
+ rel->top_parent = base_rel->top_parent;
+ rel->top_parent_relids = base_rel->top_parent_relids;
+ rel->nulling_relids = base_rel->nulling_relids;
+ rel->direct_lateral_relids = base_rel->direct_lateral_relids;
+ rel->lateral_relids = base_rel->lateral_relids;
+ rel->lateral_referencers = base_rel->lateral_referencers;
+ }
+
+ return rel;
+}
+
/*
* build_simple_rel
* Construct a new RelOptInfo for a base relation or 'other' relation.
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 64c582c344c..d8c6096144c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -16,12 +16,14 @@
#include "postgres.h"
#include "catalog/pg_aggregate.h"
+#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "nodes/queryjumble.h"
#include "optimizer/optimizer.h"
#include "parser/analyze.h"
#include "parser/parse_agg.h"
@@ -38,11 +40,13 @@
#include "utils/date.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
+#include "utils/syscache.h"
#include "utils/timestamp.h"
#include "utils/xml.h"
/* GUC parameters */
bool Transform_null_equals = false;
+bool enable_or_transformation = true;
static Node *transformExprRecurse(ParseState *pstate, Node *expr);
@@ -99,6 +103,289 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname,
static Node *make_nulltest_from_distinct(ParseState *pstate,
A_Expr *distincta, Node *arg);
+typedef struct OrClauseGroupKey
+{
+ Expr *expr; /* Pointer to the expression tree which has been a source for
+ the hashkey value */
+ Oid opno;
+ Oid exprtype;
+} OrClauseGroupKey;
+
+typedef struct OrClauseGroupEntry
+{
+ OrClauseGroupKey key;
+
+ Node *node;
+ List *consts;
+ Oid scalar_type;
+ List *exprs;
+} OrClauseGroupEntry;
+
+/*
+ * Hash function that's compatible with guc_name_compare
+ */
+static uint32
+orclause_hash(const void *data, Size keysize)
+{
+ OrClauseGroupKey *key = (OrClauseGroupKey *) data;
+ uint64 hash;
+
+ (void) JumbleExpr(key->expr, &hash);
+ hash += ((uint64) key->opno + (uint64) key->exprtype) % UINT64_MAX;
+ return hash;
+}
+
+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);
+
+ dst_key->expr = src_key->expr;
+ dst_key->opno = src_key->opno;
+ dst_key->exprtype = src_key->exprtype;
+ return dst_key;
+}
+
+/*
+ * Dynahash match function to use in guc_hashtab
+ */
+static int
+orclause_match(const void *data1, const void *data2, Size keysize)
+{
+ OrClauseGroupKey *key1 = (OrClauseGroupKey *) data1;
+ OrClauseGroupKey *key2 = (OrClauseGroupKey *) data2;
+
+ Assert(sizeof(OrClauseGroupKey) == keysize);
+
+ if (key1->opno == key2->opno && key1->exprtype == key2->exprtype &&
+ equal(key1->expr, key2->expr))
+ return 0;
+
+ return 1;
+}
+
+static Node *
+transformBoolExprOr(ParseState *pstate, BoolExpr *expr)
+{
+ List *or_list = NIL;
+ ListCell *lc;
+ HASHCTL info;
+ HTAB *or_group_htab = NULL;
+ int len_ors = list_length(expr->args);
+ HASH_SEQ_STATUS hash_seq;
+ OrClauseGroupEntry *entry = NULL;
+
+ /* If this is not an 'OR' expression, skip the transformation */
+ if (!enable_or_transformation || expr->boolop != OR_EXPR || len_ors < 2)
+ return transformBoolExpr(pstate, (BoolExpr *) expr);
+
+ 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, expr->args)
+ {
+ Node *arg = lfirst(lc);
+ Node *orqual;
+ Node *const_expr;
+ Node *nconst_expr;
+ OrClauseGroupKey hashkey;
+ bool found;
+
+ /* At first, transform the arg and evaluate constant expressions. */
+ orqual = transformExprRecurse(pstate, (Node *) arg);
+ orqual = coerce_to_boolean(pstate, orqual, "OR");
+
+ 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.
+ */
+ hashkey.expr = (Expr *) nconst_expr;
+ hashkey.opno = ((OpExpr *) orqual)->opno;
+ hashkey.exprtype = exprType(nconst_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->node = nconst_expr;
+ entry->consts = list_make1(const_expr);
+ entry->exprs = list_make1(orqual);
+ }
+ }
+
+ if (list_length(or_list) == len_ors)
+ {
+ /*
+ * No any transformations possible with this list of arguments. Here we
+ * already made all underlying transformations. Thus, just return the
+ * transformed bool expression.
+ */
+ hash_destroy(or_group_htab);
+ return (Node *) makeBoolExpr(OR_EXPR, or_list, expr->location);
+ }
+
+ hash_seq_init(&hash_seq, or_group_htab);
+
+ /* 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
+ */
+
+ while ((entry = (OrClauseGroupEntry *) hash_seq_search(&hash_seq)) != NULL)
+ {
+ List *allexprs;
+ Oid scalar_type;
+ Oid array_type;
+
+ Assert(list_length(entry->consts) > 0);
+ Assert(list_length(entry->exprs) == list_length(entry->consts));
+
+ if (list_length(entry->consts) == 1)
+ {
+ /*
+ * Only one element in the class. Return rinfo into the BoolExpr
+ * args list unchanged.
+ */
+ list_free(entry->consts);
+ or_list = list_concat(or_list, entry->exprs);
+ continue;
+ }
+
+ /*
+ * Do the transformation.
+ */
+ allexprs = list_concat(list_make1(entry->node), entry->consts);
+ scalar_type = entry->key.exprtype;
+
+ 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 = NIL;
+ ArrayExpr *newa = NULL;
+ ScalarArrayOpExpr *saopexpr = NULL;
+ ListCell *l;
+ HeapTuple opertup;
+ Form_pg_operator operform;
+ List *namelist = NIL;
+
+ aexprs = entry->consts;
+
+ 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;
+
+ 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(pstate,
+ namelist,
+ true,
+ entry->node,
+ (Node *) newa,
+ -1);
+
+ or_list = lappend(or_list, (void *) saopexpr);
+ }
+ else
+ {
+ /*
+ * This part works on intarray test (OR there is made on
+ * elements of a custom type)
+ */
+ list_free(entry->consts);
+ or_list = list_concat(or_list, entry->exprs);
+ }
+ hash_search(or_group_htab, &entry->key, HASH_REMOVE, NULL);
+ }
+ hash_destroy(or_group_htab);
+
+ /* One more trick: assemble correct clause */
+ return (Node *) ((list_length(or_list) > 1) ?
+ makeBoolExpr(OR_EXPR, or_list, expr->location) :
+ linitial(or_list));
+}
/*
* transformExpr -
@@ -212,7 +499,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
}
case T_BoolExpr:
- result = transformBoolExpr(pstate, (BoolExpr *) expr);
+ result = transformBoolExprOr(pstate, (BoolExpr *) expr);
break;
case T_FuncCall:
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 6474e35ec04..c054b3e1658 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -1048,6 +1048,16 @@ struct config_bool ConfigureNamesBool[] =
true,
NULL, NULL, NULL
},
+ {
+ {"enable_or_transformation", PGC_USERSET, QUERY_TUNING_OTHER,
+ gettext_noop("Transform a sequence of OR clauses to an IN expression."),
+ gettext_noop("The planner will replace clauses like 'x=c1 OR x=c2 .."
+ "to the clause 'x IN (c1,c2,...)'")
+ },
+ &enable_or_transformation,
+ true,
+ NULL, NULL, NULL
+ },
{
/*
* Not for general use --- used by SET SESSION AUTHORIZATION and SET
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cf9f283cfee..3d5b5fbc580 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -374,6 +374,7 @@
# - Planner Method Configuration -
#enable_async_append = on
+#enable_or_transformation = on
#enable_bitmapscan = on
#enable_gathermerge = on
#enable_hashagg = on
diff --git a/src/include/nodes/queryjumble.h b/src/include/nodes/queryjumble.h
index 0769081c7ab..4aaa31aa80e 100644
--- a/src/include/nodes/queryjumble.h
+++ b/src/include/nodes/queryjumble.h
@@ -65,6 +65,7 @@ extern PGDLLIMPORT int compute_query_id;
extern const char *CleanQuerytext(const char *query, int *location, int *len);
extern JumbleState *JumbleQuery(Query *query);
+extern JumbleState *JumbleExpr(Expr *expr, uint64 *queryId);
extern void EnableQueryId(void);
extern PGDLLIMPORT bool query_id_enabled;
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 6e557bebc44..f50a0df5dd2 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -306,6 +306,8 @@ extern void setup_simple_rel_arrays(PlannerInfo *root);
extern void expand_planner_arrays(PlannerInfo *root, int add_size);
extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid,
RelOptInfo *parent);
+extern RelOptInfo *
+copy_simple_rel(PlannerInfo *root, int relid, RelOptInfo *base_rel, RelOptInfo *rel);
extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid);
extern RelOptInfo *find_base_rel_ignore_join(PlannerInfo *root, int relid);
extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids);
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index 7d38ca75f7b..3a87de02859 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -17,6 +17,7 @@
/* GUC parameters */
extern PGDLLIMPORT bool Transform_null_equals;
+extern PGDLLIMPORT bool enable_or_transformation;
extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKind);
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index acfd9d1f4f7..3bb4bbca481 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -1838,18 +1838,50 @@ 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 count(*) FROM tenk1
+ WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
+ QUERY PLAN
+------------------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on tenk1
+ Recheck Cond: ((hundred = 42) AND (thousand = ANY ('{42,99}'::integer[])))
+ -> BitmapAnd
+ -> Bitmap Index Scan on tenk1_hundred
+ Index Cond: (hundred = 42)
+ -> Bitmap Index Scan on tenk1_thous_tenthous
+ Index Cond: (thousand = ANY ('{42,99}'::integer[]))
+(8 rows)
+
+SELECT count(*) FROM tenk1
+ WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
+ count
+-------
+ 10
+(1 row)
+
+SET enable_or_transformation = on;
+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);
@@ -1861,28 +1893,101 @@ SELECT * FROM tenk1
EXPLAIN (COSTS OFF)
SELECT count(*) FROM tenk1
WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
- QUERY PLAN
----------------------------------------------------------------------------------
+ QUERY PLAN
+------------------------------------------------------------------------------------
Aggregate
-> Bitmap Heap Scan on tenk1
- Recheck Cond: ((hundred = 42) AND ((thousand = 42) OR (thousand = 99)))
+ 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 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 ((tenthous < 2) OR (thousand = ANY ('{42,99}'::integer[])))) 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: (tenthous < 2)
+ -> Bitmap Index Scan on tenk1_thous_tenthous
+ Index Cond: (thousand = ANY ('{42,99}'::integer[]))
+ -> 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 = 99) AND (tenthous = 2)) OR (thousand = ANY ('{42,41}'::integer[]))))
-> BitmapAnd
-> Bitmap Index Scan on tenk1_hundred
Index Cond: (hundred = 42)
-> BitmapOr
-> Bitmap Index Scan on tenk1_thous_tenthous
- Index Cond: (thousand = 42)
+ Index Cond: ((thousand = 99) AND (tenthous = 2))
-> Bitmap Index Scan on tenk1_thous_tenthous
- Index Cond: (thousand = 99)
+ Index Cond: (thousand = ANY ('{42,41}'::integer[]))
(11 rows)
SELECT count(*) FROM tenk1
- WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
+ WHERE hundred = 42 AND (thousand = 42 OR thousand = 41 OR thousand = 99 AND tenthous = 2);
count
-------
10
(1 row)
+RESET enable_or_transformation;
--
-- Check behavior with duplicate index column contents
--
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 127c9532976..0f2b1b16200 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -861,7 +861,8 @@ SELECT name FROM tab_settings_flags
name
---------------------------
default_statistics_target
-(1 row)
+ enable_or_transformation
+(2 rows)
-- Runtime-computed GUCs should be part of the preset category.
SELECT name FROM tab_settings_flags
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 0f1aa831f64..17812501227 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -2560,7 +2560,7 @@ explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd'
QUERY PLAN
---------------------------------------------------------------------------------
Seq Scan on part_ab_cd list_parted
- Filter: (((a)::text = 'ab'::text) OR ((a)::text = ANY ('{NULL,cd}'::text[])))
+ Filter: (((a)::text = ANY ('{NULL,cd}'::text[])) OR ((a)::text = 'ab'::text))
(2 rows)
explain (costs off) select * from list_parted where a = 'ab';
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 2c73270143b..e952e5401f5 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -4210,10 +4210,10 @@ explain (costs off)
select * from tenk1 a join tenk1 b on
(a.unique1 = 1 and b.unique1 = 2) or
((a.unique2 = 3 or a.unique2 = 7) and b.hundred = 4);
- QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+------------------------------------------------------------------------------------------------------------------------
Nested Loop
- Join Filter: (((a.unique1 = 1) AND (b.unique1 = 2)) OR (((a.unique2 = 3) OR (a.unique2 = 7)) AND (b.hundred = 4)))
+ Join Filter: (((a.unique1 = 1) AND (b.unique1 = 2)) OR ((a.unique2 = ANY ('{3,7}'::integer[])) AND (b.hundred = 4)))
-> Bitmap Heap Scan on tenk1 b
Recheck Cond: ((unique1 = 2) OR (hundred = 4))
-> BitmapOr
@@ -4223,16 +4223,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 = ANY ('{3,7}'::integer[])))
-> BitmapOr
-> Bitmap Index Scan on tenk1_unique1
Index Cond: (unique1 = 1)
-> Bitmap Index Scan on tenk1_unique2
- Index Cond: (unique2 = 3)
+ Index Cond: (unique2 = ANY ('{3,7}'::integer[]))
+(17 rows)
+
+SET enable_or_transformation = on;
+explain (costs off)
+select * from tenk1 a join tenk1 b on
+ (a.unique1 = 1 and b.unique1 = 2) or
+ ((a.unique2 = 3 or a.unique2 = 7) and b.hundred = 4);
+ QUERY PLAN
+------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Join Filter: (((a.unique1 = 1) AND (b.unique1 = 2)) OR ((a.unique2 = ANY ('{3,7}'::integer[])) AND (b.hundred = 4)))
+ -> Bitmap Heap Scan on tenk1 b
+ Recheck Cond: ((unique1 = 2) OR (hundred = 4))
+ -> BitmapOr
+ -> Bitmap Index Scan on tenk1_unique1
+ Index Cond: (unique1 = 2)
+ -> Bitmap Index Scan on tenk1_hundred
+ Index Cond: (hundred = 4)
+ -> Materialize
+ -> Bitmap Heap Scan on tenk1 a
+ Recheck Cond: ((unique1 = 1) OR (unique2 = ANY ('{3,7}'::integer[])))
+ -> BitmapOr
+ -> Bitmap Index Scan on tenk1_unique1
+ Index Cond: (unique1 = 1)
-> Bitmap Index Scan on tenk1_unique2
- Index Cond: (unique2 = 7)
-(19 rows)
+ Index Cond: (unique2 = ANY ('{3,7}'::integer[]))
+(17 rows)
+
+explain (costs off)
+select * from tenk1 a join tenk1 b on
+ (a.unique1 < 20 or a.unique1 = 3 or a.unique1 = 1 and b.unique1 = 2) or
+ ((a.unique2 = 3 or a.unique2 = 7) and b.hundred = 4);
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Nested Loop
+ Join Filter: ((a.unique1 < 20) OR ((a.unique1 = 1) AND (b.unique1 = 2)) OR ((a.unique2 = ANY ('{3,7}'::integer[])) AND (b.hundred = 4)) OR (a.unique1 = 3))
+ -> Seq Scan on tenk1 b
+ -> Materialize
+ -> Bitmap Heap Scan on tenk1 a
+ Recheck Cond: ((unique1 < 20) OR (unique1 = 1) OR (unique2 = ANY ('{3,7}'::integer[])) OR (unique1 = 3))
+ -> BitmapOr
+ -> Bitmap Index Scan on tenk1_unique1
+ Index Cond: (unique1 < 20)
+ -> Bitmap Index Scan on tenk1_unique1
+ Index Cond: (unique1 = 1)
+ -> Bitmap Index Scan on tenk1_unique2
+ Index Cond: (unique2 = ANY ('{3,7}'::integer[]))
+ -> Bitmap Index Scan on tenk1_unique1
+ Index Cond: (unique1 = 3)
+(15 rows)
+RESET enable_or_transformation;
--
-- test placement of movable quals in a parameterized join tree
--
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index 6560fe2416f..6e8d64655b2 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -296,7 +296,7 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1 WHERE a < 450) t1 FULL JO
Sort Key: prt1.a, prt2.b
-> Hash Full Join
Hash Cond: (prt1.a = prt2.b)
- Filter: ((prt1.b = 0) OR (prt2.a = 0))
+ Filter: ((prt2.a = 0) OR (prt1.b = 0))
-> Append
-> Seq Scan on prt1_p1 prt1_1
Filter: (a < 450)
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index 9a4c48c0556..14a254fba7b 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -82,25 +82,47 @@ 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)
+SET enable_or_transformation = on;
+explain (costs off) select * from lp where a = 'a' or a = 'c';
+ QUERY PLAN
+-----------------------------------------------
+ Append
+ -> Seq Scan on lp_ad lp_1
+ Filter: (a = ANY ('{a,c}'::bpchar[]))
+ -> Seq Scan on lp_bc lp_2
+ Filter: (a = ANY ('{a,c}'::bpchar[]))
+(5 rows)
+
+explain (costs off) select * from lp where a is not null and (a = 'a' or a = 'c');
+ QUERY PLAN
+---------------------------------------------------------------------
+ Append
+ -> Seq Scan on lp_ad lp_1
+ Filter: ((a IS NOT NULL) AND (a = ANY ('{a,c}'::bpchar[])))
+ -> Seq Scan on lp_bc lp_2
+ Filter: ((a IS NOT NULL) AND (a = ANY ('{a,c}'::bpchar[])))
+(5 rows)
+
+RESET enable_or_transformation;
explain (costs off) select * from lp where a <> 'g';
QUERY PLAN
------------------------------------
@@ -515,10 +537,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 +618,13 @@ explain (costs off) select * from rlp where a < 1 or (a > 20 and a < 25);
-- where clause contradicts sub-partition's constraint
explain (costs off) select * from rlp where a = 20 or a = 40;
- QUERY PLAN
-----------------------------------------
+ QUERY PLAN
+--------------------------------------------------
Append
-> Seq Scan on rlp4_1 rlp_1
- Filter: ((a = 20) OR (a = 40))
+ Filter: (a = ANY ('{20,40}'::integer[]))
-> Seq Scan on rlp5_default rlp_2
- Filter: ((a = 20) OR (a = 40))
+ Filter: (a = ANY ('{20,40}'::integer[]))
(5 rows)
explain (costs off) select * from rlp3 where a = 20; /* empty */
@@ -671,6 +693,163 @@ explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a =
Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
(11 rows)
+SET enable_or_transformation = on;
+explain (costs off) select * from rlp where a = 1 or a = 7;
+ QUERY PLAN
+------------------------------------------
+ Seq Scan on rlp2 rlp
+ Filter: (a = ANY ('{1,7}'::integer[]))
+(2 rows)
+
+explain (costs off) select * from rlp where a = 1 or b = 'ab';
+ QUERY PLAN
+-------------------------------------------------------
+ Append
+ -> Seq Scan on rlp1 rlp_1
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp2 rlp_2
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp3abcd rlp_3
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp4_1 rlp_4
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp4_2 rlp_5
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp4_default rlp_6
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp5_1 rlp_7
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp5_default rlp_8
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp_default_10 rlp_9
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp_default_30 rlp_10
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp_default_null rlp_11
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+ -> Seq Scan on rlp_default_default rlp_12
+ Filter: ((a = 1) OR ((b)::text = 'ab'::text))
+(25 rows)
+
+explain (costs off) select * from rlp where a > 20 and a < 27;
+ QUERY PLAN
+-----------------------------------------
+ Append
+ -> Seq Scan on rlp4_1 rlp_1
+ Filter: ((a > 20) AND (a < 27))
+ -> Seq Scan on rlp4_2 rlp_2
+ Filter: ((a > 20) AND (a < 27))
+(5 rows)
+
+explain (costs off) select * from rlp where a = 29;
+ QUERY PLAN
+------------------------------
+ Seq Scan on rlp4_default rlp
+ Filter: (a = 29)
+(2 rows)
+
+explain (costs off) select * from rlp where a >= 29;
+ QUERY PLAN
+---------------------------------------------
+ Append
+ -> Seq Scan on rlp4_default rlp_1
+ Filter: (a >= 29)
+ -> Seq Scan on rlp5_1 rlp_2
+ Filter: (a >= 29)
+ -> Seq Scan on rlp5_default rlp_3
+ Filter: (a >= 29)
+ -> Seq Scan on rlp_default_30 rlp_4
+ Filter: (a >= 29)
+ -> Seq Scan on rlp_default_default rlp_5
+ Filter: (a >= 29)
+(11 rows)
+
+explain (costs off) select * from rlp where a < 1 or (a > 20 and a < 25);
+ QUERY PLAN
+------------------------------------------------------
+ Append
+ -> Seq Scan on rlp1 rlp_1
+ Filter: ((a < 1) OR ((a > 20) AND (a < 25)))
+ -> Seq Scan on rlp4_1 rlp_2
+ Filter: ((a < 1) OR ((a > 20) AND (a < 25)))
+(5 rows)
+
+explain (costs off) select * from rlp where a = 20 or a = 40;
+ QUERY PLAN
+--------------------------------------------------
+ Append
+ -> Seq Scan on rlp4_1 rlp_1
+ Filter: (a = ANY ('{20,40}'::integer[]))
+ -> Seq Scan on rlp5_default rlp_2
+ Filter: (a = ANY ('{20,40}'::integer[]))
+(5 rows)
+
+explain (costs off) select * from rlp3 where a = 20; /* empty */
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+explain (costs off) select * from rlp where a > 1 and a = 10; /* only default */
+ QUERY PLAN
+----------------------------------
+ Seq Scan on rlp_default_10 rlp
+ Filter: ((a > 1) AND (a = 10))
+(2 rows)
+
+explain (costs off) select * from rlp where a > 1 and a >=15; /* rlp3 onwards, including default */
+ QUERY PLAN
+----------------------------------------------
+ Append
+ -> Seq Scan on rlp3abcd rlp_1
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp3efgh rlp_2
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp3nullxy rlp_3
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp3_default rlp_4
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp4_1 rlp_5
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp4_2 rlp_6
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp4_default rlp_7
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp5_1 rlp_8
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp5_default rlp_9
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp_default_30 rlp_10
+ Filter: ((a > 1) AND (a >= 15))
+ -> Seq Scan on rlp_default_default rlp_11
+ Filter: ((a > 1) AND (a >= 15))
+(23 rows)
+
+explain (costs off) select * from rlp where a = 1 and a = 3; /* empty */
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a = 15);
+ QUERY PLAN
+-------------------------------------------------------------------
+ Append
+ -> Seq Scan on rlp2 rlp_1
+ Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
+ -> Seq Scan on rlp3abcd rlp_2
+ Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
+ -> Seq Scan on rlp3efgh rlp_3
+ Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
+ -> Seq Scan on rlp3nullxy rlp_4
+ Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
+ -> Seq Scan on rlp3_default rlp_5
+ Filter: (((a = 1) AND (a = 3)) OR ((a > 1) AND (a = 15)))
+(11 rows)
+
+RESET enable_or_transformation;
-- multi-column keys
create table mc3p (a int, b int, c int) partition by range (a, abs(b), c);
create table mc3p_default partition of mc3p default;
@@ -1933,10 +2112,10 @@ explain (costs off) select * from hp where a = 1 and b = 'abcde';
explain (costs off) select * from hp where a = 1 and b = 'abcde' and
(c = 2 or c = 3);
- QUERY PLAN
-----------------------------------------------------------------------
+ QUERY PLAN
+--------------------------------------------------------------------------------
Seq Scan on hp2 hp
- Filter: ((a = 1) AND (b = 'abcde'::text) AND ((c = 2) OR (c = 3)))
+ Filter: ((c = ANY ('{2,3}'::integer[])) AND (a = 1) AND (b = 'abcde'::text))
(2 rows)
drop table hp2;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 05070393b99..c927c21dd4d 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2495,7 +2495,7 @@ pg_stats| SELECT n.nspname AS schemaname,
JOIN pg_class c ON ((c.oid = s.starelid)))
JOIN pg_attribute a ON (((c.oid = a.attrelid) AND (a.attnum = s.staattnum))))
LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
- WHERE ((NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+ WHERE ((NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((NOT row_security_active(c.oid)) OR (c.relrowsecurity = false)));
pg_stats_ext| SELECT cn.nspname AS schemaname,
c.relname AS tablename,
sn.nspname AS statistics_schemaname,
@@ -2526,7 +2526,7 @@ pg_stats_ext| SELECT cn.nspname AS schemaname,
WHERE ((NOT (EXISTS ( SELECT 1
FROM (unnest(s.stxkeys) k(k)
JOIN pg_attribute a ON (((a.attrelid = s.stxrelid) AND (a.attnum = k.k))))
- WHERE (NOT has_column_privilege(c.oid, a.attnum, 'select'::text))))) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+ WHERE (NOT has_column_privilege(c.oid, a.attnum, 'select'::text))))) AND ((NOT row_security_active(c.oid)) OR (c.relrowsecurity = false)));
pg_stats_ext_exprs| SELECT cn.nspname AS schemaname,
c.relname AS tablename,
sn.nspname AS statistics_schemaname,
diff --git a/src/test/regress/expected/select.out b/src/test/regress/expected/select.out
index 33a6dceb0e3..7a829c44c7b 100644
--- a/src/test/regress/expected/select.out
+++ b/src/test/regress/expected/select.out
@@ -868,13 +868,13 @@ select unique1, unique2 from onek2
QUERY PLAN
--------------------------------------------------------------------------------
Bitmap Heap Scan on onek2
- Recheck Cond: (((unique2 = 11) AND (stringu1 < 'B'::name)) OR (unique1 = 0))
+ Recheck Cond: ((unique1 = 0) OR ((unique2 = 11) AND (stringu1 < 'B'::name)))
Filter: (stringu1 < 'B'::name)
-> BitmapOr
- -> Bitmap Index Scan on onek2_u2_prtl
- Index Cond: (unique2 = 11)
-> Bitmap Index Scan on onek2_u1_prtl
Index Cond: (unique1 = 0)
+ -> Bitmap Index Scan on onek2_u2_prtl
+ Index Cond: (unique2 = 11)
(8 rows)
select unique1, unique2 from onek2
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index a430153b225..659d712f75e 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -1322,19 +1322,19 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE
SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND b = ''1''');
estimated | actual
-----------+--------
- 99 | 100
+ 100 | 100
(1 row)
SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND (b = ''1'' OR b = ''2'')');
estimated | actual
-----------+--------
- 99 | 100
+ 100 | 100
(1 row)
SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 2 OR a = 51 OR a = 52) AND (b = ''1'' OR b = ''2'')');
estimated | actual
-----------+--------
- 197 | 200
+ 200 | 200
(1 row)
-- OR clauses referencing different attributes are incompatible
@@ -1664,19 +1664,19 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE
SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE ((a * 2) = 2 OR (a * 2) = 102) AND upper(b) = ''1''');
estimated | actual
-----------+--------
- 99 | 100
+ 100 | 100
(1 row)
SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE ((a * 2) = 2 OR (a * 2) = 102) AND (upper(b) = ''1'' OR upper(b) = ''2'')');
estimated | actual
-----------+--------
- 99 | 100
+ 100 | 100
(1 row)
SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE ((a * 2) = 2 OR (a * 2) = 4 OR (a * 2) = 102 OR (a * 2) = 104) AND (upper(b) = ''1'' OR upper(b) = ''2'')');
estimated | actual
-----------+--------
- 197 | 200
+ 200 | 200
(1 row)
-- OR clauses referencing different attributes
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 271313ebf86..c6c6b9fb8d9 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -123,6 +123,7 @@ select name, setting from pg_settings where name like 'enable%';
enable_memoize | on
enable_mergejoin | on
enable_nestloop | on
+ enable_or_transformation | on
enable_parallel_append | on
enable_parallel_hash | on
enable_partition_pruning | on
@@ -133,7 +134,7 @@ select name, setting from pg_settings where name like 'enable%';
enable_seqscan | on
enable_sort | on
enable_tidscan | on
-(22 rows)
+(23 rows)
-- There are always wait event descriptions for various types.
select type, count(*) > 0 as ok FROM pg_wait_events
diff --git a/src/test/regress/expected/tidscan.out b/src/test/regress/expected/tidscan.out
index f133b5a4ac7..2a079e996b2 100644
--- a/src/test/regress/expected/tidscan.out
+++ b/src/test/regress/expected/tidscan.out
@@ -43,10 +43,26 @@ SELECT ctid, * FROM tidscan WHERE '(0,1)' = ctid;
-- OR'd clauses
EXPLAIN (COSTS OFF)
SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
- QUERY PLAN
---------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------
+ Tid Scan on tidscan
+ TID Cond: (ctid = ANY ('{"(0,2)","(0,1)"}'::tid[]))
+(2 rows)
+
+SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
+ ctid | id
+-------+----
+ (0,1) | 1
+ (0,2) | 2
+(2 rows)
+
+SET enable_or_transformation = on;
+EXPLAIN (COSTS OFF)
+SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
+ QUERY PLAN
+-------------------------------------------------------
Tid Scan on tidscan
- TID Cond: ((ctid = '(0,2)'::tid) OR ('(0,1)'::tid = ctid))
+ TID Cond: (ctid = ANY ('{"(0,2)","(0,1)"}'::tid[]))
(2 rows)
SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
@@ -56,6 +72,7 @@ SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
(0,2) | 2
(2 rows)
+RESET enable_or_transformation;
-- ctid = ScalarArrayOp - implemented as tidscan
EXPLAIN (COSTS OFF)
SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,1)', '(0,2)']::tid[]);
diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql
index d49ce9f3007..48bb1bc0a0a 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -737,6 +737,38 @@ SELECT count(*) FROM tenk1
SELECT count(*) FROM tenk1
WHERE hundred = 42 AND (thousand = 42 OR thousand = 99);
+SET enable_or_transformation = on;
+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 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 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 enable_or_transformation;
+
--
-- Check behavior with duplicate index column contents
--
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index 8a8a63bd2f1..55d7e2ae7d6 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -1408,6 +1408,16 @@ 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);
+SET enable_or_transformation = on;
+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 enable_or_transformation;
--
-- test placement of movable quals in a parameterized join tree
diff --git a/src/test/regress/sql/partition_prune.sql b/src/test/regress/sql/partition_prune.sql
index 7bf3920827f..1e270ae9c06 100644
--- a/src/test/regress/sql/partition_prune.sql
+++ b/src/test/regress/sql/partition_prune.sql
@@ -21,6 +21,12 @@ 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');
+
+SET enable_or_transformation = on;
+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');
+RESET enable_or_transformation;
+
explain (costs off) select * from lp where a <> 'g';
explain (costs off) select * from lp where a <> 'a' and a <> 'd';
explain (costs off) select * from lp where a not in ('a', 'd');
@@ -99,6 +105,22 @@ explain (costs off) select * from rlp where a > 1 and a >=15; /* rlp3 onwards, i
explain (costs off) select * from rlp where a = 1 and a = 3; /* empty */
explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a = 15);
+
+SET enable_or_transformation = on;
+explain (costs off) select * from rlp where a = 1 or a = 7;
+explain (costs off) select * from rlp where a = 1 or b = 'ab';
+explain (costs off) select * from rlp where a > 20 and a < 27;
+explain (costs off) select * from rlp where a = 29;
+explain (costs off) select * from rlp where a >= 29;
+explain (costs off) select * from rlp where a < 1 or (a > 20 and a < 25);
+explain (costs off) select * from rlp where a = 20 or a = 40;
+explain (costs off) select * from rlp3 where a = 20; /* empty */
+explain (costs off) select * from rlp where a > 1 and a = 10; /* only default */
+explain (costs off) select * from rlp where a > 1 and a >=15; /* rlp3 onwards, including default */
+explain (costs off) select * from rlp where a = 1 and a = 3; /* empty */
+explain (costs off) select * from rlp where (a = 1 and a = 3) or (a > 1 and a = 15);
+RESET enable_or_transformation;
+
-- multi-column keys
create table mc3p (a int, b int, c int) partition by range (a, abs(b), c);
create table mc3p_default partition of mc3p default;
diff --git a/src/test/regress/sql/tidscan.sql b/src/test/regress/sql/tidscan.sql
index 313e0fb9b67..0499bedb9eb 100644
--- a/src/test/regress/sql/tidscan.sql
+++ b/src/test/regress/sql/tidscan.sql
@@ -22,6 +22,12 @@ EXPLAIN (COSTS OFF)
SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
+SET enable_or_transformation = on;
+EXPLAIN (COSTS OFF)
+SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
+SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
+RESET enable_or_transformation;
+
-- ctid = ScalarArrayOp - implemented as tidscan
EXPLAIN (COSTS OFF)
SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,1)', '(0,2)']::tid[]);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d659adbfd6c..9591d0d084d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1645,6 +1645,8 @@ NumericVar
OM_uint32
OP
OSAPerGroupState
+OrClauseGroupEntry
+OrClauseGroupKey
OSAPerQueryState
OSInfo
OSSLCipher
--
2.34.1
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 67921a08262..e0d83672756 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -50,6 +50,12 @@
#include "port/pg_bitutils.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
+#include "parser/parse_coerce.h"
+#include "common/hashfn.h"
+#include "catalog/pg_operator.h"
+#include "nodes/queryjumble.h"
+#include "utils/syscache.h"
+#include "catalog/namespace.h"
/* Bitmask flags for pushdown_safety_info.unsafeFlags */
@@ -165,6 +171,390 @@ static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel,
Bitmapset *extra_used_attrs);
+bool enable_or_transformation = true;
+typedef struct OrClauseGroupKey
+{
+ Expr *expr; /* Pointer to the expression tree which has been a source for
+ the hashkey value */
+ Oid opno;
+ Oid exprtype;
+} OrClauseGroupKey;
+
+typedef struct OrClauseGroupEntry
+{
+ OrClauseGroupKey key;
+ Node *node;
+ List *consts;
+ Oid collation;
+ RestrictInfo *rinfo;
+ List *exprs;
+} OrClauseGroupEntry;
+
+/*
+ * Hash function that's compatible with guc_name_compare
+ */
+static uint32
+orclause_hash(const void *data, Size keysize)
+{
+ OrClauseGroupKey *key = (OrClauseGroupKey *) data;
+ uint64 hash;
+
+ (void) JumbleExpr(key->expr, &hash);
+ hash = ((uint64) key->opno + (uint64) key->exprtype) % UINT64_MAX;
+ return hash;
+}
+
+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);
+
+ dst_key->expr = src_key->expr;
+ dst_key->opno = src_key->opno;
+ dst_key->exprtype = src_key->exprtype;
+ return dst_key;
+}
+
+/*
+ * Dynahash match function to use in guc_hashtab
+ */
+static int
+orclause_match(const void *data1, const void *data2, Size keysize)
+{
+ OrClauseGroupKey *key1 = (OrClauseGroupKey *) data1;
+ OrClauseGroupKey *key2 = (OrClauseGroupKey *) data2;
+
+ Assert(sizeof(OrClauseGroupKey) == keysize);
+
+ if (key1->opno == key2->opno && key1->exprtype == key2->exprtype &&
+ equal(key1->expr, key2->expr))
+ return 0;
+
+ return 1;
+}
+
+/*
+ * Pass through baserestrictinfo clauses and try to convert OR clauses into IN
+ * Return a modified clause list or just the same baserestrictinfo, if no
+ * changes have made.
+ * XXX: do not change source list of clauses at all.
+ */
+static List *
+transform_ors(PlannerInfo *root, List *baserestrictinfo, bool *something_changed)
+{
+ ListCell *lc;
+ List *modified_rinfo = NIL;
+
+ /*
+ * Complexity of a clause could be arbitrarily sophisticated. Here, we will
+ * look up only on the top level of clause list.
+ * XXX: It is substantiated? Could we change something here?
+ */
+ foreach (lc, baserestrictinfo)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
+ RestrictInfo *rinfo_base = copyObject(rinfo);
+ List *or_list = NIL;
+ ListCell *lc_eargs,
+ *lc_rargs;
+ bool change_apply = false;
+ HASHCTL info;
+ HTAB *or_group_htab = NULL;
+ int len_ors;
+ HASH_SEQ_STATUS hash_seq;
+ OrClauseGroupEntry *gentry;
+
+ if (!enable_or_transformation || !restriction_is_or_clause(rinfo))
+ {
+ /* Add a clause without changes */
+ modified_rinfo = lappend(modified_rinfo, rinfo);
+ continue;
+ }
+
+ len_ors = ((BoolExpr *) rinfo->clause)->args? list_length(((BoolExpr *) rinfo->clause)->args) : 0;
+
+ if (len_ors < 2)
+ {
+ /* Add a clause without changes */
+ modified_rinfo = lappend(modified_rinfo, rinfo);
+ continue;
+ }
+
+ 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);
+
+ /*
+ * NOTE:
+ * It is an OR-clause. So, rinfo->orclause is a BoolExpr node, contains
+ * a list of sub-restrictinfo args, and rinfo->clause - which is the
+ * same expression, made from bare clauses. To not break selectivity
+ * caches and other optimizations, use both:
+ * - use rinfos from orclause if no transformation needed
+ * - use bare quals from rinfo->clause in the case of transformation,
+ * to create new RestrictInfo: in this case we have no options to avoid
+ * selectivity estimation procedure.
+ */
+ forboth(lc_eargs, ((BoolExpr *) rinfo->clause)->args,
+ lc_rargs, ((BoolExpr *) rinfo->orclause)->args)
+ {
+ Expr *or_qual = (Expr *) lfirst(lc_eargs);
+ RestrictInfo *sub_rinfo;
+ Node *const_expr;
+ Node *nconst_expr;
+ OrClauseGroupKey hashkey;
+ bool found = false;
+
+ /* It may be one more boolean expression, skip it for now */
+ if (!IsA(lfirst(lc_rargs), RestrictInfo))
+ {
+ or_list = lappend(or_list, (void *) or_qual);
+ continue;
+ }
+
+ sub_rinfo = lfirst_node(RestrictInfo, lc_rargs);
+
+ /* Check: it is an expr of the form 'F(x) oper ConstExpr' */
+ if (!IsA(or_qual, OpExpr) ||
+ !(bms_is_empty(sub_rinfo->left_relids) ^
+ bms_is_empty(sub_rinfo->right_relids)) ||
+ contain_volatile_functions((Node *) or_qual))
+ {
+ /* Again, it's not the expr we can transform */
+ or_list = lappend(or_list, (void *) or_qual);
+ continue;
+ }
+
+ /* Get pointers to constant and expression sides of the clause */
+ const_expr =bms_is_empty(sub_rinfo->left_relids) ?
+ get_leftop(sub_rinfo->clause) :
+ get_rightop(sub_rinfo->clause);
+ nconst_expr = bms_is_empty(sub_rinfo->left_relids) ?
+ get_rightop(sub_rinfo->clause) :
+ get_leftop(sub_rinfo->clause);
+
+ if (!op_mergejoinable(((OpExpr *) sub_rinfo->clause)->opno, exprType(nconst_expr)))
+ {
+ /* And again, filter out non-equality operators */
+ or_list = lappend(or_list, (void *) or_qual);
+ continue;
+ }
+
+ /*
+ * At this point we definitely have a transformable clause.
+ * Classify it and add into specific group of clauses, or create new
+ * group.
+ */
+ hashkey.expr = (Expr *) nconst_expr;
+ hashkey.opno = ((OpExpr *) or_qual)->opno;
+ hashkey.exprtype = exprType(nconst_expr);
+ gentry = hash_search(or_group_htab, &hashkey, HASH_ENTER, &found);
+
+ if (unlikely(found))
+ {
+ gentry->consts = lappend(gentry->consts, const_expr);
+ gentry->exprs = lappend(gentry->exprs, or_qual);
+ }
+ else
+ {
+ gentry->node = nconst_expr;
+ gentry->consts = list_make1(const_expr);
+ gentry->exprs = list_make1(or_qual);
+ gentry->rinfo = sub_rinfo;
+ gentry->collation = exprInputCollation((Node *)sub_rinfo->clause);
+ }
+ }
+
+ if (list_length(or_list) == len_ors)
+ {
+ /*
+ * No any transformations possible with this list of arguments. Here we
+ * already made all underlying transformations. Thus, just return the
+ * transformed bool expression.
+ */
+ modified_rinfo = lappend(modified_rinfo, rinfo);
+ continue;
+ }
+
+ if (!or_group_htab && hash_get_num_entries(or_group_htab) < 1)
+ {
+ /*
+ * No any transformations possible with this rinfo, just add itself
+ * to the list and go further.
+ */
+ modified_rinfo = lappend(modified_rinfo, rinfo);
+ continue;
+ }
+
+ hash_seq_init(&hash_seq, or_group_htab);
+
+ /* 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
+ */
+
+ while ((gentry = (OrClauseGroupEntry *) hash_seq_search(&hash_seq)) != NULL)
+ {
+ Oid scalar_type;
+ Oid array_type;
+
+ Assert(list_length(gentry->consts) > 0);
+ Assert(list_length(gentry->exprs) == list_length(gentry->consts));
+
+ 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 = list_concat(or_list, gentry->exprs);
+ 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()
+ */
+ scalar_type = gentry->key.exprtype;
+
+ if (scalar_type != RECORDOID && OidIsValid(scalar_type))
+ array_type = get_array_type(scalar_type);
+ else
+ array_type = InvalidOid;
+
+ if (array_type != InvalidOid && scalar_type != InvalidOid)
+ {
+ /*
+ * OK: coerce all the right-hand non-Var inputs to the common
+ * type and build an ArrayExpr for them.
+ */
+ List *aexprs = NULL;
+ ArrayExpr *newa = NULL;
+ ScalarArrayOpExpr *saopexpr = NULL;
+ HeapTuple opertup;
+ Form_pg_operator operform;
+ List *namelist = NIL;
+
+ aexprs = gentry->consts;
+
+ 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;
+
+ opertup = SearchSysCache1(OPEROID,
+ ObjectIdGetDatum(gentry->key.opno));
+
+ if (!HeapTupleIsValid(opertup))
+ elog(ERROR, "cache lookup failed for operator %u",
+ gentry->key.opno);
+
+ operform = (Form_pg_operator) GETSTRUCT(opertup);
+ if (!OperatorIsVisible(gentry->key.opno))
+ namelist = lappend(namelist, makeString(get_namespace_name(operform->oprnamespace)));
+
+ namelist = lappend(namelist, makeString(pstrdup(NameStr(operform->oprname))));
+ ReleaseSysCache(opertup);
+
+ saopexpr = makeNode(ScalarArrayOpExpr);
+ saopexpr->opno = ((OpExpr *) gentry->rinfo->clause)->opno;
+ saopexpr->useOr = true;
+ saopexpr->inputcollid = gentry->collation;
+ saopexpr->args = list_make2(gentry->node, newa);
+ saopexpr->location = -1;
+
+
+ or_list = lappend(or_list, (void *) saopexpr);
+
+ *something_changed = true;
+ change_apply = true;
+
+ }
+ else
+ {
+ list_free(gentry->consts);
+ or_list = lappend(or_list, (void *) gentry->rinfo->clause);
+ }
+ hash_search(or_group_htab, &gentry->key, HASH_REMOVE, NULL);
+ }
+
+ hash_destroy(or_group_htab);
+
+ if (!change_apply)
+ {
+ /*
+ * Each group contains only one element - use rinfo as is.
+ */
+ modified_rinfo = lappend(modified_rinfo, rinfo);
+ list_free(or_list);
+ continue;
+ }
+
+ /*
+ * Make a new version of the restriction. Remember source restriction
+ * can be used in another path (SeqScan, for example).
+ */
+
+ /* One more trick: assemble correct clause */
+ rinfo = make_restrictinfo(root,
+ list_length(or_list) > 1 ? makeBoolExpr(OR_EXPR, or_list, ((BoolExpr *) rinfo->clause)->location) :
+ linitial(or_list),
+ rinfo->is_pushed_down,
+ rinfo->has_clone,
+ rinfo->is_clone,
+ rinfo->pseudoconstant,
+ rinfo->security_level,
+ rinfo->required_relids,
+ rinfo->incompatible_relids,
+ rinfo->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);
+ *something_changed = true;
+ }
+
+ /*
+ * Check if transformation has made. If nothing changed - return
+ * baserestrictinfo as is.
+ */
+ if (*something_changed)
+ {
+ return modified_rinfo;
+ }
+
+ list_free(modified_rinfo);
+ return baserestrictinfo;
+}
+
/*
* make_one_rel
* Finds all possible access paths for executing a query, returning a
@@ -767,6 +1157,12 @@ static void
set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
Relids required_outer;
+ RelOptInfo *reL_alternative = makeNode(RelOptInfo);
+ //reL_alternative = copyObject(rel);
+ reL_alternative = copy_simple_rel(root, rel->relid, rel, reL_alternative);
+ reL_alternative->indexlist = NIL;
+ reL_alternative->baserestrictinfo = rel->baserestrictinfo;
+ reL_alternative->eclass_indexes = NULL;
/*
* We don't support pushing join clauses into the quals of a seqscan, but
@@ -787,6 +1183,39 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
/* Consider TID scans */
create_tidscan_paths(root, rel);
+
+ set_cheapest(rel);
+
+ if (rel->reloptkind == RELOPT_BASEREL && enable_or_transformation)
+ {
+ bool applied_transfomation;
+ applied_transfomation = (bool *) palloc(sizeof(bool));
+ reL_alternative->baserestrictinfo = transform_ors(root, reL_alternative->baserestrictinfo, &applied_transfomation);
+if (applied_transfomation)
+{
+ add_path(reL_alternative, create_seqscan_path(root, reL_alternative, required_outer, 0));
+
+ if (reL_alternative->consider_parallel && required_outer == NULL)
+ create_plain_partial_paths(root, reL_alternative);
+
+ create_index_paths(root, reL_alternative);
+
+ create_tidscan_paths(root, reL_alternative);
+
+ set_cheapest(reL_alternative);
+
+ elog(WARNING, "ors: - %f", rel->cheapest_total_path->total_cost);
+ elog(WARNING, "applied_transfomation: - %f", reL_alternative->cheapest_total_path->total_cost);
+
+ if (reL_alternative->cheapest_total_path->total_cost <= rel->cheapest_total_path->total_cost)
+ {
+ //copy_simple_rel(root, rel->relid, reL_alternative, rel);
+ rel->indexlist = reL_alternative->indexlist;
+ rel->baserestrictinfo = reL_alternative->baserestrictinfo;
+ rel->eclass_indexes = reL_alternative->eclass_indexes;
+ }
+ }
+ }
}
/*
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index acf4937db01..a9cc9a585df 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -34,12 +34,6 @@
#include "optimizer/restrictinfo.h"
#include "utils/lsyscache.h"
#include "utils/selfuncs.h"
-#include "parser/parse_coerce.h"
-#include "common/hashfn.h"
-#include "catalog/pg_operator.h"
-#include "nodes/queryjumble.h"
-#include "utils/syscache.h"
-#include "catalog/namespace.h"
/* XXX see PartCollMatchesExprColl */
#define IndexCollMatchesExprColl(idxcollation, exprcollation) \
@@ -198,391 +192,6 @@ static bool ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel,
EquivalenceClass *ec, EquivalenceMember *em,
void *arg);
-bool enable_or_transformation = true;
-typedef struct OrClauseGroupKey
-{
- Expr *expr; /* Pointer to the expression tree which has been a source for
- the hashkey value */
- Oid opno;
- Oid exprtype;
-} OrClauseGroupKey;
-
-typedef struct OrClauseGroupEntry
-{
- OrClauseGroupKey key;
- Node *node;
- List *consts;
- Oid collation;
- RestrictInfo *rinfo;
- List *exprs;
-} OrClauseGroupEntry;
-
-/*
- * Hash function that's compatible with guc_name_compare
- */
-static uint32
-orclause_hash(const void *data, Size keysize)
-{
- OrClauseGroupKey *key = (OrClauseGroupKey *) data;
- uint64 hash;
-
- (void) JumbleExpr(key->expr, &hash);
- hash = ((uint64) key->opno + (uint64) key->exprtype) % UINT64_MAX;
- return hash;
-}
-
-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);
-
- dst_key->expr = src_key->expr;
- dst_key->opno = src_key->opno;
- dst_key->exprtype = src_key->exprtype;
- return dst_key;
-}
-
-/*
- * Dynahash match function to use in guc_hashtab
- */
-static int
-orclause_match(const void *data1, const void *data2, Size keysize)
-{
- OrClauseGroupKey *key1 = (OrClauseGroupKey *) data1;
- OrClauseGroupKey *key2 = (OrClauseGroupKey *) data2;
-
- Assert(sizeof(OrClauseGroupKey) == keysize);
-
- if (key1->opno == key2->opno && key1->exprtype == key2->exprtype &&
- equal(key1->expr, key2->expr))
- return 0;
-
- return 1;
-}
-
-/*
- * Pass through baserestrictinfo clauses and try to convert OR clauses into IN
- * Return a modified clause list or just the same baserestrictinfo, if no
- * changes have made.
- * XXX: do not change source list of clauses at all.
- */
-static List *
-transform_ors(PlannerInfo *root, List *baserestrictinfo)
-{
- ListCell *lc;
- List *modified_rinfo = NIL;
- bool something_changed = false;
-
- /*
- * Complexity of a clause could be arbitrarily sophisticated. Here, we will
- * look up only on the top level of clause list.
- * XXX: It is substantiated? Could we change something here?
- */
- foreach (lc, baserestrictinfo)
- {
- RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
- RestrictInfo *rinfo_base = copyObject(rinfo);
- List *or_list = NIL;
- ListCell *lc_eargs,
- *lc_rargs;
- bool change_apply = false;
- HASHCTL info;
- HTAB *or_group_htab = NULL;
- int len_ors;
- HASH_SEQ_STATUS hash_seq;
- OrClauseGroupEntry *gentry;
-
- if (!enable_or_transformation || !restriction_is_or_clause(rinfo))
- {
- /* Add a clause without changes */
- modified_rinfo = lappend(modified_rinfo, rinfo);
- continue;
- }
-
- len_ors = ((BoolExpr *) rinfo->clause)->args? list_length(((BoolExpr *) rinfo->clause)->args) : 0;
-
- if (len_ors < 2)
- {
- /* Add a clause without changes */
- modified_rinfo = lappend(modified_rinfo, rinfo);
- continue;
- }
-
- 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);
-
- /*
- * NOTE:
- * It is an OR-clause. So, rinfo->orclause is a BoolExpr node, contains
- * a list of sub-restrictinfo args, and rinfo->clause - which is the
- * same expression, made from bare clauses. To not break selectivity
- * caches and other optimizations, use both:
- * - use rinfos from orclause if no transformation needed
- * - use bare quals from rinfo->clause in the case of transformation,
- * to create new RestrictInfo: in this case we have no options to avoid
- * selectivity estimation procedure.
- */
- forboth(lc_eargs, ((BoolExpr *) rinfo->clause)->args,
- lc_rargs, ((BoolExpr *) rinfo->orclause)->args)
- {
- Expr *or_qual = (Expr *) lfirst(lc_eargs);
- RestrictInfo *sub_rinfo;
- Node *const_expr;
- Node *nconst_expr;
- OrClauseGroupKey hashkey;
- bool found = false;
-
- /* It may be one more boolean expression, skip it for now */
- if (!IsA(lfirst(lc_rargs), RestrictInfo))
- {
- or_list = lappend(or_list, (void *) or_qual);
- continue;
- }
-
- sub_rinfo = lfirst_node(RestrictInfo, lc_rargs);
-
- /* Check: it is an expr of the form 'F(x) oper ConstExpr' */
- if (!IsA(or_qual, OpExpr) ||
- !(bms_is_empty(sub_rinfo->left_relids) ^
- bms_is_empty(sub_rinfo->right_relids)) ||
- contain_volatile_functions((Node *) or_qual))
- {
- /* Again, it's not the expr we can transform */
- or_list = lappend(or_list, (void *) or_qual);
- continue;
- }
-
- /* Get pointers to constant and expression sides of the clause */
- const_expr =bms_is_empty(sub_rinfo->left_relids) ?
- get_leftop(sub_rinfo->clause) :
- get_rightop(sub_rinfo->clause);
- nconst_expr = bms_is_empty(sub_rinfo->left_relids) ?
- get_rightop(sub_rinfo->clause) :
- get_leftop(sub_rinfo->clause);
-
- if (!op_mergejoinable(((OpExpr *) sub_rinfo->clause)->opno, exprType(nconst_expr)))
- {
- /* And again, filter out non-equality operators */
- or_list = lappend(or_list, (void *) or_qual);
- continue;
- }
-
- /*
- * At this point we definitely have a transformable clause.
- * Classify it and add into specific group of clauses, or create new
- * group.
- */
- hashkey.expr = (Expr *) nconst_expr;
- hashkey.opno = ((OpExpr *) or_qual)->opno;
- hashkey.exprtype = exprType(nconst_expr);
- gentry = hash_search(or_group_htab, &hashkey, HASH_ENTER, &found);
-
- if (unlikely(found))
- {
- gentry->consts = lappend(gentry->consts, const_expr);
- gentry->exprs = lappend(gentry->exprs, or_qual);
- }
- else
- {
- gentry->node = nconst_expr;
- gentry->consts = list_make1(const_expr);
- gentry->exprs = list_make1(or_qual);
- gentry->rinfo = sub_rinfo;
- gentry->collation = exprInputCollation((Node *)sub_rinfo->clause);
- }
- }
-
- if (list_length(or_list) == len_ors)
- {
- /*
- * No any transformations possible with this list of arguments. Here we
- * already made all underlying transformations. Thus, just return the
- * transformed bool expression.
- */
- modified_rinfo = lappend(modified_rinfo, rinfo);
- continue;
- }
-
- if (!or_group_htab && hash_get_num_entries(or_group_htab) < 1)
- {
- /*
- * No any transformations possible with this rinfo, just add itself
- * to the list and go further.
- */
- modified_rinfo = lappend(modified_rinfo, rinfo);
- continue;
- }
-
- hash_seq_init(&hash_seq, or_group_htab);
-
- /* 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
- */
-
- while ((gentry = (OrClauseGroupEntry *) hash_seq_search(&hash_seq)) != NULL)
- {
- Oid scalar_type;
- Oid array_type;
-
- Assert(list_length(gentry->consts) > 0);
- Assert(list_length(gentry->exprs) == list_length(gentry->consts));
-
- 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 = list_concat(or_list, gentry->exprs);
- 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()
- */
- scalar_type = gentry->key.exprtype;
-
- if (scalar_type != RECORDOID && OidIsValid(scalar_type))
- array_type = get_array_type(scalar_type);
- else
- array_type = InvalidOid;
-
- if (array_type != InvalidOid && scalar_type != InvalidOid)
- {
- /*
- * OK: coerce all the right-hand non-Var inputs to the common
- * type and build an ArrayExpr for them.
- */
- List *aexprs = NULL;
- ArrayExpr *newa = NULL;
- ScalarArrayOpExpr *saopexpr = NULL;
- HeapTuple opertup;
- Form_pg_operator operform;
- List *namelist = NIL;
-
- aexprs = gentry->consts;
-
- 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;
-
- opertup = SearchSysCache1(OPEROID,
- ObjectIdGetDatum(gentry->key.opno));
-
- if (!HeapTupleIsValid(opertup))
- elog(ERROR, "cache lookup failed for operator %u",
- gentry->key.opno);
-
- operform = (Form_pg_operator) GETSTRUCT(opertup);
- if (!OperatorIsVisible(gentry->key.opno))
- namelist = lappend(namelist, makeString(get_namespace_name(operform->oprnamespace)));
-
- namelist = lappend(namelist, makeString(pstrdup(NameStr(operform->oprname))));
- ReleaseSysCache(opertup);
-
- saopexpr = makeNode(ScalarArrayOpExpr);
- saopexpr->opno = ((OpExpr *) gentry->rinfo->clause)->opno;
- saopexpr->useOr = true;
- saopexpr->inputcollid = gentry->collation;
- saopexpr->args = list_make2(gentry->node, newa);
- saopexpr->location = -1;
-
-
- or_list = lappend(or_list, (void *) saopexpr);
-
- something_changed = true;
- change_apply = true;
-
- }
- else
- {
- list_free(gentry->consts);
- or_list = lappend(or_list, (void *) gentry->rinfo->clause);
- }
- hash_search(or_group_htab, &gentry->key, HASH_REMOVE, NULL);
- }
-
- hash_destroy(or_group_htab);
-
- if (!change_apply)
- {
- /*
- * Each group contains only one element - use rinfo as is.
- */
- modified_rinfo = lappend(modified_rinfo, rinfo);
- list_free(or_list);
- continue;
- }
-
- /*
- * Make a new version of the restriction. Remember source restriction
- * can be used in another path (SeqScan, for example).
- */
-
- /* One more trick: assemble correct clause */
- rinfo = make_restrictinfo(root,
- list_length(or_list) > 1 ? makeBoolExpr(OR_EXPR, or_list, ((BoolExpr *) rinfo->clause)->location) :
- linitial(or_list),
- rinfo->is_pushed_down,
- rinfo->has_clone,
- rinfo->is_clone,
- rinfo->pseudoconstant,
- rinfo->security_level,
- rinfo->required_relids,
- rinfo->incompatible_relids,
- rinfo->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);
- something_changed = true;
- }
-
- /*
- * Check if transformation has made. If nothing changed - return
- * baserestrictinfo as is.
- */
- if (something_changed)
- {
- return modified_rinfo;
- }
-
- list_free(modified_rinfo);
- return baserestrictinfo;
-}
-
/*
* create_index_paths()
* Generate all interesting index paths for the given relation.
@@ -1108,9 +717,6 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
bool skip_lower_saop = false;
ListCell *lc;
- if (rel->reloptkind == RELOPT_BASEREL)
- rel->baserestrictinfo = transform_ors(root, rel->baserestrictinfo);
-
/*
* Build simple index paths using the clauses. Allow ScalarArrayOpExpr
* clauses only if the index AM supports them natively, and skip any such
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 5d83f60eb9a..189cee449bf 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -183,6 +183,97 @@ expand_planner_arrays(PlannerInfo *root, int add_size)
root->simple_rel_array_size = new_size;
}
+RelOptInfo *
+copy_simple_rel(PlannerInfo *root, int relid, RelOptInfo *base_rel, RelOptInfo *rel)
+{
+ rel->reloptkind = base_rel->parent ? RELOPT_OTHER_MEMBER_REL : RELOPT_BASEREL;
+ rel->relids = base_rel->relids;
+ rel->rows = base_rel->rows;
+ /* cheap startup cost is interesting iff not all tuples to be retrieved */
+ rel->consider_startup = (root->tuple_fraction > 0);
+ rel->consider_param_startup = base_rel->consider_param_startup; /* might get changed later */
+ rel->consider_parallel = base_rel->consider_parallel; /* might get changed later */
+ rel->reltarget = base_rel->reltarget;
+ rel->pathlist = base_rel->pathlist;
+ rel->ppilist = base_rel->ppilist;
+ rel->partial_pathlist = base_rel->partial_pathlist;
+ rel->cheapest_startup_path = base_rel->cheapest_startup_path;
+ rel->cheapest_total_path = base_rel->cheapest_total_path;
+ rel->cheapest_unique_path = base_rel->cheapest_unique_path;
+ rel->cheapest_parameterized_paths = base_rel->cheapest_parameterized_paths;
+ rel->relid = base_rel->relid;
+ /* min_attr, max_attr, attr_needed, attr_widths are set below */
+ rel->lateral_vars = base_rel->lateral_vars;
+ rel->statlist = base_rel->statlist;
+ rel->pages = base_rel->pages;
+ rel->tuples = base_rel->tuples;
+ rel->allvisfrac = base_rel->allvisfrac;
+ rel->subroot = base_rel->subroot;
+ rel->subplan_params = base_rel->subplan_params;
+ rel->rel_parallel_workers = base_rel->rel_parallel_workers; /* set up in get_relation_info */
+ rel->amflags = base_rel->amflags;
+ rel->parent = base_rel->parent;
+ rel->serverid = base_rel->serverid;
+ rel->userid = base_rel->userid;
+ rel->useridiscurrent = base_rel->useridiscurrent;
+ rel->fdwroutine = base_rel->fdwroutine;
+ rel->fdw_private = base_rel->fdw_private;
+ rel->unique_for_rels = base_rel->unique_for_rels;
+ rel->non_unique_for_rels = base_rel->non_unique_for_rels;
+ rel->baserestrictcost.startup = 0;
+ rel->baserestrictcost.per_tuple = 0;
+ rel->baserestrict_min_security = UINT_MAX;
+ rel->joininfo = base_rel->joininfo;
+ rel->has_eclass_joins = base_rel->has_eclass_joins;
+
+ /*
+ * Pass assorted information down the inheritance hierarchy.
+ */
+ if (base_rel->parent)
+ {
+ /* We keep back-links to immediate parent and topmost parent. */
+ rel->parent = base_rel->parent;
+ rel->top_parent = base_rel->parent->top_parent ? base_rel->parent->top_parent : base_rel->parent;
+ rel->top_parent_relids = base_rel->top_parent->relids;
+
+ /*
+ * A child rel is below the same outer joins as its parent. (We
+ * presume this info was already calculated for the parent.)
+ */
+ rel->nulling_relids = base_rel->parent->nulling_relids;
+
+ /*
+ * Also propagate lateral-reference information from appendrel parent
+ * rels to their child rels. We intentionally give each child rel the
+ * same minimum parameterization, even though it's quite possible that
+ * some don't reference all the lateral rels. This is because any
+ * append path for the parent will have to have the same
+ * parameterization for every child anyway, and there's no value in
+ * forcing extra reparameterize_path() calls. Similarly, a lateral
+ * reference to the parent prevents use of otherwise-movable join rels
+ * for each child.
+ *
+ * It's possible for child rels to have their own children, in which
+ * case the topmost parent's lateral info propagates all the way down.
+ */
+ rel->direct_lateral_relids = base_rel->parent->direct_lateral_relids;
+ rel->lateral_relids = base_rel->parent->lateral_relids;
+ rel->lateral_referencers = base_rel->parent->lateral_referencers;
+ }
+ else
+ {
+ rel->parent = base_rel->parent;
+ rel->top_parent = base_rel->top_parent;
+ rel->top_parent_relids = base_rel->top_parent_relids;
+ rel->nulling_relids = base_rel->nulling_relids;
+ rel->direct_lateral_relids = base_rel->direct_lateral_relids;
+ rel->lateral_relids = base_rel->lateral_relids;
+ rel->lateral_referencers = base_rel->lateral_referencers;
+ }
+
+ return rel;
+}
+
/*
* build_simple_rel
* Construct a new RelOptInfo for a base relation or 'other' relation.
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 6e557bebc44..2b3825b6e52 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -304,6 +304,8 @@ extern Path *reparameterize_path_by_child(PlannerInfo *root, Path *path,
*/
extern void setup_simple_rel_arrays(PlannerInfo *root);
extern void expand_planner_arrays(PlannerInfo *root, int add_size);
+extern RelOptInfo *
+copy_simple_rel(PlannerInfo *root, int relid, RelOptInfo *base_rel, RelOptInfo *rel);
extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid,
RelOptInfo *parent);
extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid);
--
2.34.1