Hi, all!

These days I was porting a patch for converting or expressions to ANY to the choose_bitmap_and function. Unfortunately, it is not possible to transfer the conversion there, since expressions are processed one by one, as far as I saw. Therefore, I tried to make the conversion earlier in the generate_bitmap_or_paths function, there is just a loop bypass. The patch turns out to be quite complicated, in my opinion, and to be honest, it does not work fully yet. Also, due to the fact that the index for the ScalarOpExpr expression is created earlier (approximately 344 lines in the src/backend/optimizer/path/indxpath.c file), we had to call the generate_bitmap_or_paths function earlier. I haven't seen yet what problems this could potentially lead to. Patch in the attached diff file.

In the last letter, I had an incorrect numbering for the original patch, corrected, respectively, it is unclear whether the tests in CI were normal. Corrected it.
From 3062a2408af258b9e327219020221b75501e8530 Mon Sep 17 00:00:00 2001
From: Alena Rybakina <a.rybak...@postgrespro.ru>
Date: Mon, 6 Nov 2023 16:48:00 +0300
Subject: [PATCH] Replace OR clause to ANY expressions. Replace (X=N1) OR
 (X=N2) ... with X = ANY(N1, N2) on the stage of the optimiser when we are
 still working with a tree expression. Firstly, we do not try to make a
 transformation for "non-or" expressions or inequalities and the creation of a
 relation with "or" expressions occurs according to the same scenario.
 Secondly, we do not make transformations if there are less than set
 or_transform_limit. Thirdly, it is worth considering that we consider "or"
 expressions only at the current level.

Authors: Alena Rybakina <lena.riback...@yandex.ru>, Andrey Lepikhov <a.lepik...@postgrespro.ru>
Reviewed-by: Peter Geoghegan <p...@bowt.ie>, Ranier Vilela <ranier...@gmail.com>
Reviewed-by: Alexander Korotkov <aekorot...@gmail.com>, Robert Haas <robertmh...@gmail.com>
---
 src/backend/parser/parse_expr.c               | 299 +++++++++++++++++-
 src/backend/utils/misc/guc_tables.c           |  10 +
 src/include/parser/parse_expr.h               |   1 +
 src/test/regress/expected/create_index.out    | 115 +++++++
 src/test/regress/expected/guc.out             |   3 +-
 src/test/regress/expected/join.out            |  50 +++
 src/test/regress/expected/partition_prune.out | 179 +++++++++++
 src/test/regress/expected/tidscan.out         |  17 +
 src/test/regress/sql/create_index.sql         |  32 ++
 src/test/regress/sql/join.sql                 |  10 +
 src/test/regress/sql/partition_prune.sql      |  22 ++
 src/test/regress/sql/tidscan.sql              |   6 +
 src/tools/pgindent/typedefs.list              |   1 +
 13 files changed, 743 insertions(+), 2 deletions(-)

diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 64c582c344c..25a4235dbd9 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -18,6 +18,7 @@
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "common/hashfn.h"
 #include "commands/dbcommands.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -43,6 +44,7 @@
 
 /* GUC parameters */
 bool		Transform_null_equals = false;
+bool		or_transform_limit = false;
 
 
 static Node *transformExprRecurse(ParseState *pstate, Node *expr);
@@ -98,7 +100,302 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname,
 							  Node *ltree, Node *rtree, int location);
 static Node *make_nulltest_from_distinct(ParseState *pstate,
 										 A_Expr *distincta, Node *arg);
+typedef struct OrClauseGroupEntry
+{
+	char		   *hash_leftvar_key;
+
+	Node		   *node;
+	List		   *consts;
+	Oid				scalar_type;
+	Oid				opno;
+	Expr 		   *expr;
+} OrClauseGroupEntry;
+
+static int
+or_name_match(const void *key1, const void *key2, Size keysize)
+{
+	const char *name1 = *(const char *const *) key1;
+	const char *name2 = *(const char *const *) key2;
+
+	return strcmp(name1, name2);
+}
+
+static uint32
+or_name_hash(const void *key, Size keysize)
+{
+	const char *name = *(const char *const *) key;
+
+	return DatumGetInt32(hash_any((unsigned char *)name, strlen(name)));
+}
+
+static char *
+get_key_nconst_node(Node *nconst_node)
+{
+	if (IsA(nconst_node, OpExpr))
+	{
+		OpExpr 	   *clause = (OpExpr*) nconst_node;
+		OpExpr	   *temp = makeNode(OpExpr);
+
+		temp->opno = clause->opno;
+		temp->opfuncid = InvalidOid;
+		temp->opresulttype = clause->opresulttype;
+		temp->opretset = clause->opretset;
+		temp->opcollid = clause->opcollid;
+		temp->inputcollid = clause->inputcollid;
+		temp->location = -1;
+
+		temp->args = list_copy(clause->args);
+		return nodeToString(temp);
+	}
+	else if (IsA(nconst_node, Var))
+	{
+		Var	   *clause = (Var*) nconst_node;
+		Var	   *var = makeNode(Var);
+
+		var->varno = clause->varno;
+		var->varattno = clause->varattno;
+		var->vartype = clause->vartype;
+		var->vartypmod = clause->vartypmod;
+		var->varcollid = clause->varcollid;
+		var->varlevelsup = clause->varlevelsup;
+		var->varattnosyn = clause->varattno;
+		var->location = -1;
+
+		return nodeToString(var);
+	}
+	else
+	{
+		return NULL;
+	}
+ }
+
+static Node *
+transformBoolExprOr(ParseState *pstate, BoolExpr *expr_orig)
+{
+	List		   *or_list = NIL;
+	ListCell	   *lc;
+	HASHCTL			info;
+	HTAB 		   *or_group_htab = NULL;
+	int 			len_ors = list_length(expr_orig->args);
+
+	MemSet(&info, 0, sizeof(info));
+	info.keysize = sizeof(char *);
+	info.entrysize = sizeof(OrClauseGroupEntry);
+	info.hash = or_name_hash;
+	info.match = or_name_match;
+	or_group_htab = hash_create("OR Groups",
+									  len_ors,
+									  &info,
+									  HASH_ELEM | HASH_FUNCTION | HASH_COMPARE);
+
+	/* If this is not an 'OR' expression, skip the transformation */
+	if (expr_orig->boolop != OR_EXPR || !or_transform_limit || len_ors == 1 || !or_group_htab)
+		return transformBoolExpr(pstate, (BoolExpr *) expr_orig);
+
+	foreach(lc, expr_orig->args)
+	{
+		Node			   *arg = lfirst(lc);
+		Node			   *orqual;
+		Node			   *const_expr;
+		Node			   *nconst_expr;
+		OrClauseGroupEntry *gentry;
+		bool				found;
+		char		   	   *str;
+
+		/* At first, transform the arg and evaluate constant expressions. */
+		orqual = transformExprRecurse(pstate, (Node *) arg);
+		orqual = coerce_to_boolean(pstate, orqual, "OR");
+		orqual = eval_const_expressions(NULL, orqual);
+
+		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;
+		}
+
+		str = get_key_nconst_node(nconst_expr);
+
+		if (!op_mergejoinable(((OpExpr *) orqual)->opno, exprType(nconst_expr)) || str == NULL)
+		{
+			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.
+		*/
+		gentry = hash_search(or_group_htab, &str, HASH_FIND, &found);
+
+		if (found)
+		{
+			gentry->consts = lappend(gentry->consts, const_expr);
+			/*
+				* The clause classified successfully and added into existed
+				* clause group.
+				*/
+			continue;
+		}
+
+		/* New clause group needed */
+		gentry = hash_search(or_group_htab, &str, HASH_ENTER, &found);
+		gentry->node = nconst_expr;
+		gentry->consts = list_make1(const_expr);
+		gentry->expr = (Expr *) orqual;
+		gentry->hash_leftvar_key = str;
+	}
+
+	if (or_group_htab && (hash_get_num_entries(or_group_htab) < 1 ||
+	                         hash_get_num_entries(or_group_htab) == 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);
+		list_free(or_list);
+		return (Node *) makeBoolExpr(OR_EXPR, or_list, expr_orig->location);
+	}
+	else
+	{
+		HASH_SEQ_STATUS		hash_seq;
+		OrClauseGroupEntry *gentry;
+
+		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)
+		{
+			List			   *allexprs;
+			Oid				    scalar_type;
+			Oid					array_type;
+
+			Assert(list_length(gentry->consts) > 0);
+
+			if (list_length(gentry->consts) == 1)
+			{
+				/*
+				 * Only one element in the class. Return rinfo into the BoolExpr
+				 * args list unchanged.
+				 */
+				list_free(gentry->consts);
+				or_list = lappend(or_list, gentry->expr);
+				continue;
+			}
+
+			/*
+			 * Do the transformation.
+			 *
+			 * First of all, try to select a common type for the array elements.
+			 * Note that since the LHS' type is first in the list, it will be
+			 * preferred when there is doubt (eg, when all the RHS items are
+			 * unknown literals).
+			 *
+			 * Note: use list_concat here not lcons, to avoid damaging rnonvars.
+			 *
+			 * As a source of insides, use make_scalar_array_op()
+			 */
+			allexprs = list_concat(list_make1(gentry->node), gentry->consts);
+			scalar_type = select_common_type(NULL, allexprs, NULL, NULL);
+
+			if (scalar_type != RECORDOID && OidIsValid(scalar_type))
+				array_type = get_array_type(scalar_type);
+			else
+				array_type = InvalidOid;
+
+			if (array_type != InvalidOid)
+			{
+				/*
+				 * OK: coerce all the right-hand non-Var inputs to the common
+				 * type and build an ArrayExpr for them.
+				 */
+				List	   *aexprs;
+				ArrayExpr  *newa;
+				ScalarArrayOpExpr *saopexpr;
+				ListCell *l;
+
+				aexprs = NIL;
+
+				foreach(l, gentry->consts)
+				{
+					Node	   *rexpr = (Node *) lfirst(l);
+
+					rexpr = coerce_to_common_type(pstate, rexpr,
+												scalar_type,
+												"IN");
+					aexprs = lappend(aexprs, rexpr);
+				}
+
+				newa = makeNode(ArrayExpr);
+				/* array_collid will be set by parse_collate.c */
+				newa->element_typeid = scalar_type;
+				newa->array_typeid = array_type;
+				newa->multidims = false;
+				newa->elements = aexprs;
+				newa->location = -1;
+
+				saopexpr =
+					(ScalarArrayOpExpr *)
+						make_scalar_array_op(pstate,
+											 list_make1(makeString((char *) "=")),
+											 true,
+											 gentry->node,
+											 (Node *) newa,
+											 -1);
+
+				or_list = lappend(or_list, (void *) saopexpr);
+			}
+			else
+			{
+				list_free(gentry->consts);
+				or_list = lappend(or_list, gentry->expr);
+			}
+			hash_search(or_group_htab, &gentry->hash_leftvar_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_orig->location) :
+						linitial(or_list));
+}
 
 /*
  * transformExpr -
@@ -212,7 +509,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 			}
 
 		case T_BoolExpr:
-			result = transformBoolExpr(pstate, (BoolExpr *) expr);
+			result = (Node *)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 7605eff9b9d..a8e8054e7e6 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
 	},
+	{
+		{"or_transform_limit", 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,...)'")
+		},
+		&or_transform_limit,
+		false,
+		NULL, NULL, NULL
+	},
 	{
 		/*
 		 * Not for general use --- used by SET SESSION AUTHORIZATION and SET
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index 7d38ca75f7b..7a6943c116c 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 or_transform_limit;
 
 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..29c2bc6a2b2 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -1883,6 +1883,121 @@ SELECT count(*) FROM tenk1
     10
 (1 row)
 
+SET or_transform_limit = 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);
+ 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)
+
+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 = 99) AND (tenthous = 2))
+                     ->  Bitmap Index Scan on tenk1_thous_tenthous
+                           Index Cond: (thousand = ANY ('{42,41}'::integer[]))
+(11 rows)
+
+SELECT count(*) FROM tenk1
+  WHERE hundred = 42 AND (thousand = 42 OR thousand = 41 OR thousand = 99 AND tenthous = 2);
+ count 
+-------
+    10
+(1 row)
+
+RESET or_transform_limit;
 --
 -- 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..c052b113eea 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)
+ or_transform_limit
+(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/join.out b/src/test/regress/expected/join.out
index 446959e3c5f..d8684e959e4 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -4207,6 +4207,56 @@ select * from tenk1 a join tenk1 b on
                            Index Cond: (unique2 = 7)
 (19 rows)
 
+SET or_transform_limit = 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 = 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 or_transform_limit;
 --
 -- test placement of movable quals in a parameterized join tree
 --
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index 9a4c48c0556..1789d3c1fd7 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -101,6 +101,28 @@ explain (costs off) select * from lp where a is not null and (a = 'a' or a = 'c'
          Filter: ((a IS NOT NULL) AND ((a = 'a'::bpchar) OR (a = 'c'::bpchar)))
 (5 rows)
 
+SET or_transform_limit = 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 or_transform_limit;
 explain (costs off) select * from lp where a <> 'g';
              QUERY PLAN             
 ------------------------------------
@@ -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 or_transform_limit = 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 or_transform_limit;
 -- 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/expected/tidscan.out b/src/test/regress/expected/tidscan.out
index f133b5a4ac7..8a31e2e670d 100644
--- a/src/test/regress/expected/tidscan.out
+++ b/src/test/regress/expected/tidscan.out
@@ -56,6 +56,23 @@ SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
  (0,2) |  2
 (2 rows)
 
+SET or_transform_limit = 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 = 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)
+
+RESET or_transform_limit;
 -- 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..a709b2c1abc 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 or_transform_limit = 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 or_transform_limit;
+
 --
 -- Check behavior with duplicate index column contents
 --
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index 66dd03dd88b..f5dcf8b90a7 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -1396,6 +1396,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 or_transform_limit = 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 or_transform_limit;
 
 --
 -- 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..88709910592 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 or_transform_limit = 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 or_transform_limit;
+
 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 or_transform_limit = 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 or_transform_limit;
+
 -- 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..c735e219589 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 or_transform_limit = 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 or_transform_limit;
+
 -- 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 87c1aee379f..8cea6a6b851 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1638,6 +1638,7 @@ NumericVar
 OM_uint32
 OP
 OSAPerGroupState
+OrClauseGroupEntry
 OSAPerQueryState
 OSInfo
 OSSLCipher
-- 
2.34.1

diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 03a5fbdc6dc..754c83816f7 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -34,6 +34,11 @@
 #include "optimizer/restrictinfo.h"
 #include "utils/lsyscache.h"
 #include "utils/selfuncs.h"
+#include "common/hashfn.h"
+#include "utils/lsyscache.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_oper.h"
 
 
 /* XXX see PartCollMatchesExprColl */
@@ -73,6 +78,36 @@ typedef struct
 	int			indexcol;		/* index column we want to match to */
 } ec_member_matches_arg;
 
+typedef struct OrClauseGroupEntry
+{
+	char		   *hash_leftvar_key;
+
+	Node		   *node;
+	List		   *consts;
+	Oid				scalar_type;
+	Oid				opno;
+	Node 		   *expr;
+	RestrictInfo	*ri;
+} OrClauseGroupEntry;
+
+static int
+or_name_match(const void *key1, const void *key2, Size keysize)
+{
+	const char *name1 = *(const char *const *) key1;
+	const char *name2 = *(const char *const *) key2;
+
+	return strcmp(name1, name2);
+}
+
+static uint32
+or_name_hash(const void *key, Size keysize)
+{
+	const char *name = *(const char *const *) key;
+
+	return DatumGetInt32(hash_any((unsigned char *)name, strlen(name)));
+}
+
+bool		enable_or_transformation = true;
 
 static void consider_index_join_clauses(PlannerInfo *root, RelOptInfo *rel,
 										IndexOptInfo *index,
@@ -193,6 +228,55 @@ static bool ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel,
 									   EquivalenceClass *ec, EquivalenceMember *em,
 									   void *arg);
 
+static char *
+get_key_nconst_node(Node *nconst_node)
+{
+	if (IsA(nconst_node, OpExpr))
+	{
+		OpExpr 	   *clause = (OpExpr*) nconst_node;
+		OpExpr	   *temp = makeNode(OpExpr);
+
+		temp->opno = clause->opno;
+		temp->opfuncid = InvalidOid;
+		temp->opresulttype = clause->opresulttype;
+		temp->opretset = clause->opretset;
+		temp->opcollid = clause->opcollid;
+		temp->inputcollid = clause->inputcollid;
+		temp->location = -1;
+
+		temp->args = list_copy(clause->args);
+		return nodeToString(temp);
+	}
+	else if (IsA(nconst_node, Var))
+	{
+		Var	   *clause = (Var*) nconst_node;
+		Var	   *var = makeNode(Var);
+
+		var->varno = clause->varno;
+		var->varattno = clause->varattno;
+		var->vartype = clause->vartype;
+		var->vartypmod = clause->vartypmod;
+		var->varcollid = clause->varcollid;
+		var->varlevelsup = clause->varlevelsup;
+		var->varattnosyn = clause->varattno;
+	var->location = -1;
+
+		return nodeToString(var);
+	}
+	else
+	{
+		return NULL;
+	}
+}
+
+/*
+ * 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.
+ */
+
+
 
 /*
  * create_index_paths()
@@ -250,6 +334,13 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel)
 	/* Bitmap paths are collected and then dealt with at the end */
 	bitindexpaths = bitjoinpaths = joinorclauses = NIL;
 
+	/*
+	 * Generate BitmapOrPaths for any suitable OR-clauses present in the
+	 * restriction list.  Add these to bitindexpaths.
+	 */
+	indexpaths = generate_bitmap_or_paths(root, rel,
+										  rel->baserestrictinfo, NIL);
+
 	/* Examine each index in turn */
 	foreach(lc, rel->indexlist)
 	{
@@ -309,13 +400,7 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel)
 										&eclauseset,
 										&bitjoinpaths);
 	}
-
-	/*
-	 * Generate BitmapOrPaths for any suitable OR-clauses present in the
-	 * restriction list.  Add these to bitindexpaths.
-	 */
-	indexpaths = generate_bitmap_or_paths(root, rel,
-										  rel->baserestrictinfo, NIL);
+	
 	bitindexpaths = list_concat(bitindexpaths, indexpaths);
 
 	/*
@@ -1237,6 +1322,7 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
 	List	   *result = NIL;
 	List	   *all_clauses;
 	ListCell   *lc;
+	List	   *modified_rinfo = NIL;
 
 	/*
 	 * We can use both the current and other clauses as context for
@@ -1247,23 +1333,45 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
 	foreach(lc, clauses)
 	{
 		RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
-		List	   *pathlist;
+		List	   *pathlist = NIL;
 		Path	   *bitmapqual;
-		ListCell   *j;
+		ListCell   *lc_rargs, *lc_eargs;
+		HASHCTL		info;
+		HTAB 	   *or_group_htab = NULL;
+		int 		len_ors = 0;
+		List	   *or_list = NIL;
 
 		/* Ignore RestrictInfos that aren't ORs */
 		if (!restriction_is_or_clause(rinfo))
+		{
+			modified_rinfo = lappend(modified_rinfo, rinfo);
 			continue;
+		}
+
+		len_ors = list_length(((BoolExpr *) rinfo->orclause)->args);
+
+		MemSet(&info, 0, sizeof(info));
+		info.keysize = sizeof(char *);
+		info.entrysize = sizeof(OrClauseGroupEntry);
+		info.hash = or_name_hash;
+		info.match = or_name_match;
+		or_group_htab = hash_create("OR Groups",
+										len_ors,
+									  &info,
+									  HASH_ELEM | HASH_FUNCTION | HASH_COMPARE);
 
 		/*
 		 * We must be able to match at least one index to each of the arms of
 		 * the OR, else we can't use it.
 		 */
 		pathlist = NIL;
-		foreach(j, ((BoolExpr *) rinfo->orclause)->args)
+		forboth(lc_eargs, ((BoolExpr *) rinfo->clause)->args,
+				lc_rargs, ((BoolExpr *) rinfo->orclause)->args)
 		{
-			Node	   *orarg = (Node *) lfirst(j);
+			Node	   *orarg = (Node *) lfirst(lc_rargs);
 			List	   *indlist;
+			OrClauseGroupEntry *gentry;
+			Node	   *orqual = (Node *) lfirst(lc_eargs);
 
 			/* OR arguments should be ANDs or sub-RestrictInfos */
 			if (is_andclause(orarg))
@@ -1279,36 +1387,309 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
 									  generate_bitmap_or_paths(root, rel,
 															   andargs,
 															   all_clauses));
+				or_list = lappend(or_list, orqual);
 			}
 			else
 			{
 				RestrictInfo *ri = castNode(RestrictInfo, orarg);
-				List	   *orargs;
+				RestrictInfo *sub_rinfo = lfirst_node(RestrictInfo, lc_rargs);
+				Node *nconst_expr = NULL;
+				Node *const_expr = NULL;
+				char *str = NULL;
+				bool found;
 
 				Assert(!restriction_is_or_clause(ri));
-				orargs = list_make1(ri);
 
-				indlist = build_paths_for_OR(root, rel,
-											 orargs,
-											 all_clauses);
+				if (!IsA(lfirst(lc_rargs), RestrictInfo) || !enable_or_transformation ||
+				/* Check: it is an expr of the form 'F(x) oper ConstExpr' */
+					!IsA(sub_rinfo->clause, OpExpr) ||
+					!(bms_is_empty(sub_rinfo->left_relids) ^
+					bms_is_empty(sub_rinfo->right_relids)) ||
+					contain_volatile_functions((Node *) orarg))
+				{
+					List	   *orargs;
+					orargs = list_make1(ri);
+					indlist = build_paths_for_OR(root, rel,
+												orargs,
+												all_clauses);
+					/*
+					* If nothing matched this arm, we can't do anything with this OR
+					* clause.
+					*/
+					if (indlist == NIL)
+					{
+						pathlist = NIL;
+						break;
+					}
+
+					/*
+					* OK, pick the most promising AND combination, and add it to
+					* pathlist.
+					*/
+					bitmapqual = choose_bitmap_and(root, rel, indlist);
+					pathlist = lappend(pathlist, bitmapqual);
+					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.
+				*/
+				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);
+
+				str = get_key_nconst_node(nconst_expr);
+
+				if (!op_mergejoinable(((OpExpr*)sub_rinfo->clause)->opno, exprType(nconst_expr)) || str == NULL)
+				{
+					List	   *orargs;
+					orargs = list_make1(ri);
+					indlist = build_paths_for_OR(root, rel,
+												orargs,
+												all_clauses);
+					/*
+					* If nothing matched this arm, we can't do anything with this OR
+					* clause.
+					*/
+					if (indlist == NIL)
+					{
+						pathlist = NIL;
+						break;
+					}
+
+					/*
+					* OK, pick the most promising AND combination, and add it to
+					* pathlist.
+					*/
+					bitmapqual = choose_bitmap_and(root, rel, indlist);
+					pathlist = lappend(pathlist, bitmapqual);
+					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.
+				*/
+				gentry = hash_search(or_group_htab, &str, HASH_FIND, &found);
+
+				if (found)
+				{
+					gentry->consts = lappend(gentry->consts, const_expr);
+					/*
+						* The clause classified successfully and added into existed
+						* clause group.
+						*/
+					continue;
+				}
+
+				/* New clause group needed */
+				gentry = hash_search(or_group_htab, &str, HASH_ENTER, &found);
+				gentry->node = nconst_expr;
+				gentry->consts = list_make1(const_expr);
+				gentry->expr = orqual;
+				gentry->hash_leftvar_key = str;
+				gentry->ri = ri;
 			}
+		}
+
+		if (!(or_group_htab && (hash_get_num_entries(or_group_htab) < 1 ||
+	                         hash_get_num_entries(or_group_htab) == len_ors)))
+		{
+			HASH_SEQ_STATUS		hash_seq;
+			OrClauseGroupEntry *gentry;
+			bool success = false;
+
+			hash_seq_init(&hash_seq, or_group_htab);
+
+			/* Let's convert each group of clauses to an IN operation. */
 
 			/*
-			 * If nothing matched this arm, we can't do anything with this OR
-			 * clause.
-			 */
-			if (indlist == NIL)
+			* 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)
 			{
-				pathlist = NIL;
-				break;
+				List			   *allexprs;
+				Oid				    scalar_type;
+				Oid					array_type;
+				List *orargs;
+				List *indlist;
+
+				Assert(list_length(gentry->consts) > 0);
+
+				if (list_length(gentry->consts) == 1)
+				{
+					/*
+					* Only one element in the class. Return rinfo into the BoolExpr
+					* args list unchanged.
+					*/
+					list_free(gentry->consts);
+
+					orargs = list_make1(gentry->ri);
+					indlist = build_paths_for_OR(root, rel,
+												orargs,
+												all_clauses);
+					/*
+					* If nothing matched this arm, we can't do anything with this OR
+					* clause.
+					*/
+					if (indlist == NIL)
+					{
+						pathlist = NIL;
+						break;
+					}
+
+					/*
+					* OK, pick the most promising AND combination, and add it to
+					* pathlist.
+					*/
+					bitmapqual = choose_bitmap_and(root, rel, indlist);
+					pathlist = lappend(pathlist, bitmapqual);
+					or_list = lappend(or_list, gentry->expr);
+					continue;
+				}
+
+				/*
+				* Do the transformation.
+				*
+				* First of all, try to select a common type for the array elements.
+				* Note that since the LHS' type is first in the list, it will be
+				* preferred when there is doubt (eg, when all the RHS items are
+				* unknown literals).
+				*
+				* Note: use list_concat here not lcons, to avoid damaging rnonvars.
+				*
+				* As a source of insides, use make_scalar_array_op()
+				*/
+				allexprs = list_concat(list_make1(gentry->node), gentry->consts);
+				scalar_type = select_common_type(NULL, allexprs, NULL, NULL);
+
+				if (scalar_type != RECORDOID && OidIsValid(scalar_type))
+					array_type = get_array_type(scalar_type);
+				else
+					array_type = InvalidOid;
+
+				if (array_type != InvalidOid)
+				{
+					/*
+					* OK: coerce all the right-hand non-Var inputs to the common
+					* type and build an ArrayExpr for them.
+					*/
+					List	   *aexprs = NIL;
+					ArrayExpr  *newa = NULL;
+					ScalarArrayOpExpr *saopexpr = NULL;
+					ListCell *l;
+
+					aexprs = NIL;
+
+					foreach(l, gentry->consts)
+					{
+						Node	   *rexpr = (Node *) lfirst(l);
+
+						rexpr = coerce_to_common_type(NULL, rexpr,
+													scalar_type,
+													"IN");
+						aexprs = lappend(aexprs, rexpr);
+					}
+
+					newa = makeNode(ArrayExpr);
+					/* array_collid will be set by parse_collate.c */
+					newa->element_typeid = scalar_type;
+					newa->array_typeid = array_type;
+					newa->multidims = false;
+					newa->elements = aexprs;
+					newa->location = -1;
+
+					saopexpr =
+						(ScalarArrayOpExpr *)
+							make_scalar_array_op(NULL,
+												list_make1(makeString((char *) "=")),
+												true,
+												gentry->node,
+												(Node *) newa,
+												-1);
+					or_list = lappend(or_list, (void *) saopexpr);
+					success = true;
+				}
+				else
+				{
+					orargs = list_make1(gentry->ri);
+					indlist = build_paths_for_OR(root, rel,
+												orargs,
+												all_clauses);
+					list_free(gentry->consts);
+					/*
+					* If nothing matched this arm, we can't do anything with this OR
+					* clause.
+					*/
+					if (indlist == NIL)
+					{
+						pathlist = NIL;
+						break;
+					}
+
+					/*
+					* OK, pick the most promising AND combination, and add it to
+					* pathlist.
+					*/
+					bitmapqual = choose_bitmap_and(root, rel, indlist);
+					pathlist = lappend(pathlist, bitmapqual);
+					or_list = lappend(or_list, gentry->expr);
+				}
+				hash_search(or_group_htab, &gentry->hash_leftvar_key, HASH_REMOVE, NULL);
 			}
+			hash_destroy(or_group_htab);
 
-			/*
-			 * OK, pick the most promising AND combination, and add it to
-			 * pathlist.
-			 */
-			bitmapqual = choose_bitmap_and(root, rel, indlist);
-			pathlist = lappend(pathlist, bitmapqual);
+			if (!success)
+			{
+				/*
+				* Each group contains only one element - use rinfo as is.
+				*/
+				modified_rinfo = lappend(modified_rinfo, rinfo);
+				list_free(or_list);
+			}
+			else
+			{
+				RestrictInfo *ri;
+				List *orargs;
+				List *indlist;
+				ri = make_restrictinfo(root,
+				  list_length(or_list) > 1 ? make_orclause(or_list) :
+											 (Expr *) linitial(or_list),
+				  ri->is_pushed_down,
+				  ri->has_clone,
+				  ri->is_clone,
+				  ri->pseudoconstant,
+				  ri->security_level,
+				  ri->required_relids,
+				  ri->incompatible_relids,
+				  ri->outer_relids);
+				
+				ri->eval_cost=rinfo->eval_cost;
+				ri->norm_selec=rinfo->norm_selec;
+				ri->outer_selec=rinfo->outer_selec;
+				ri->left_bucketsize=rinfo->left_bucketsize;
+				ri->right_bucketsize=rinfo->right_bucketsize;
+				ri->left_mcvfreq=rinfo->left_mcvfreq;
+				ri->right_mcvfreq=rinfo->right_mcvfreq;
+				modified_rinfo = lappend(modified_rinfo, ri);
+			}
 		}
 
 		/*
@@ -1322,10 +1703,10 @@ generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
 		}
 	}
 
+	rel->baserestrictinfo = modified_rinfo;
 	return result;
 }
 
-
 /*
  * choose_bitmap_and
  *		Given a nonempty list of bitmap paths, AND them into one path.

Reply via email to