or_patch_bitmapindex_create.diff

text/x-patch

Filename: or_patch_bitmapindex_create.diff
Type: text/x-patch
Part: 1
Message: Re: POC, WIP: OR-clause support for indexes

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
File+
src/backend/optimizer/path/indxpath.c 410 29
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.