public inbox for [email protected]  
help / color / mirror / Atom feed
From: Alena Rybakina <[email protected]>
To: Andrei Lepikhov <[email protected]>
To: Robert Haas <[email protected]>
To: [email protected]
To: Alexander Korotkov <[email protected]>
Cc: Peter Geoghegan <[email protected]>
Cc: Finnerty, Jim <[email protected]>
Cc: Marcos Pegoraro <[email protected]>
Cc: [email protected]
Cc: Ranier Vilela <[email protected]>
Cc: Tomas Vondra <[email protected]>
Cc: Peter Eisentraut <[email protected]>
Subject: Re: POC, WIP: OR-clause support for indexes
Date: Thu, 30 Nov 2023 11:05:31 +0300
Message-ID: <[email protected]> (raw)
In-Reply-To: <[email protected]>
References: <[email protected]>
	<CAH2-Wzkrg_nnh-xvnFZhNKEMJf0hZ0putQaqWhQrezEnb8b+XA@mail.gmail.com>
	<CA+Tgmoa+ywixgrgV2x4QHJgLvre31wJDsx-dC1gXDmtJUgv=Ww@mail.gmail.com>
	<CAPpHfdt58O5jzKMjkScNbGecM5JqfaMfdGLjs4rk5XisfjTNBw@mail.gmail.com>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<[email protected]>
	<CAPpHfdu-_j6=E9CHjSGu0=SidfTqdtj7qk78dCdW-=4RFDHBDw@mail.gmail.com>
	<[email protected]>
	<CA+TgmoZCgP6FrBQEusn4yaWm02XU8OPeoEMk91q7PRBgwaAkFw@mail.gmail.com>
	<[email protected]>
	<[email protected]>

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


Attachments:

  [text/x-patch] 0001-OR-to-ANY-in-parser-and-ANY-to-OR-in-index.diff (64.2K, ../[email protected]/2-0001-OR-to-ANY-in-parser-and-ANY-to-OR-in-index.diff)
  download | inline diff:
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



  [text/x-patch] 0002-OR-to-ANY-in-index.diff (31.4K, ../[email protected]/3-0002-OR-to-ANY-in-index.diff)
  download | inline diff:
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



view thread (24+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected]
  Subject: Re: POC, WIP: OR-clause support for indexes
  In-Reply-To: <[email protected]>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox