0003-WIP-Implement-get_partitions_for_keys.patch

text/plain

Filename: 0003-WIP-Implement-get_partitions_for_keys.patch
Type: text/plain
Part: 2
Message: Re: path toward faster partition pruning

Patch

Format: format-patch
Series: patch 0003
Subject: WIP: Implement get_partitions_for_keys()
File+
src/backend/catalog/partition.c 205 5
src/backend/optimizer/util/plancat.c 4 0
From b39fe19609c4cdec4961455540cef50beebce33a Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 22 Aug 2017 13:48:13 +0900
Subject: [PATCH 3/4] WIP: Implement get_partitions_for_keys()

Disable constraint exclusion that occurs using internal partition
constraints, so that it's apparent what the new partition-pruning
code still needs to do to able to create a plan matching the plain
the the traditional constraint exclusion based partition-pruning
would result in.
---
 src/backend/catalog/partition.c      | 210 ++++++++++++++++++++++++++++++++++-
 src/backend/optimizer/util/plancat.c |   4 +
 2 files changed, 209 insertions(+), 5 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 0133748234..9d4b7c1a7f 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1391,8 +1391,6 @@ get_partition_dispatch_recurse(Relation rel, Relation parent,
  *	datum that the query's bounding keys allow to be returned for the query.
  *	Similarly, *max_datum_index.  *null_partition_chosen returns whether
  *	the null partition will be scanned.
- *
- * TODO: Implement.
  */
 List *
 get_partitions_for_keys(Relation rel,
@@ -1403,12 +1401,214 @@ get_partitions_for_keys(Relation rel,
 						bool *null_partition_chosen,
 						bool *default_partition_chosen)
 {
+	int		i,
+			minoff,
+			maxoff;
 	List   *result = NIL;
-	int		i;
+	PartitionKey	partkey = RelationGetPartitionKey(rel);
 	PartitionDesc	partdesc = RelationGetPartitionDesc(rel);
+	PartitionBoundCmpArg	arg;
+	bool	is_equal,
+			scan_default = false;
+	int		null_partition_idx = partdesc->boundinfo->null_index;
 
-	for (i = 0; i < partdesc->nparts; i++)
-		result = lappend_int(result, i);
+	*null_partition_chosen = false;
+
+	/*
+	 * Check if any of the scan keys are null.  If so, return the only
+	 * null-accepting partition if partdesc->boundinfo says there is one.
+	 */
+	for (i = 0; i < partkey->partnatts; i++)
+	{
+		if (keynullness[i] == IS_NULL)
+		{
+			if (null_partition_idx >= 0)
+			{
+				*null_partition_chosen = true;
+				result = list_make1_int(null_partition_idx);
+			}
+			else
+				result = NIL;
+
+			return result;
+		}
+	}
+
+	/*
+	 * If query provides no quals, don't forget to scan the default partition.
+	 */
+	if (n_minkeys == 0 && n_maxkeys == 0)
+		scan_default = true;
+
+	if (n_minkeys > 0 && partdesc->nparts > 0)
+	{
+		memset(&arg, 0, sizeof(PartitionBoundCmpArg));
+		arg.datums = minkeys;
+		arg.ndatums = n_minkeys;
+		minoff = partition_bound_bsearch(partkey, partdesc->boundinfo,
+											&arg, &is_equal);
+
+		if (is_equal && arg.ndatums < partkey->partnatts)
+		{
+			int32	cmpval;
+
+			is_equal = false;
+
+			do
+			{
+				if (min_inclusive)
+					minoff -= 1;
+				else
+					minoff += 1;
+				if (minoff < 0 ||
+					minoff >= partdesc->boundinfo->ndatums)
+					break;
+				cmpval = partition_bound_cmp(partkey, partdesc->boundinfo,
+												 minoff, &arg);
+			} while (cmpval == 0);
+		}
+
+		/* Interpret the result per partition strategy. */
+		switch (partkey->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+				/*
+				 * Found, but if the query may have asked us to exclude it.
+				 */
+				if (is_equal && !min_inclusive)
+					minoff++;
+				break;
+
+			case PARTITION_STRATEGY_RANGE:
+				/*
+				 * Records returned by the query will be > bounds[minoff],
+				 * because min_scankey is >= bounds[minoff], that is, no
+				 * records of the partition at minoff will be returned.  Go
+				 * to the next bound.
+				 */
+				if (minoff < partdesc->boundinfo->ndatums - 1)
+					minoff += 1;
+
+				/*
+				 * Make sure to skip a gap.
+				 * Note: There are ndatums + 1 lots in the indexes array.
+				 */
+				if (partdesc->boundinfo->indexes[minoff] < 0 &&
+					partdesc->boundinfo->indexes[minoff + 1] >= 0)
+					minoff += 1;
+				break;
+		}
+	}
+	else
+		minoff = 0;
+
+	if (n_maxkeys > 0 && partdesc->nparts > 0)
+	{
+		memset(&arg, 0, sizeof(PartitionBoundCmpArg));
+		arg.datums = maxkeys;
+		arg.ndatums = n_maxkeys;
+		maxoff = partition_bound_bsearch(partkey, partdesc->boundinfo,
+											&arg, &is_equal);
+		if (is_equal && arg.ndatums < partkey->partnatts)
+		{
+			int32	cmpval;
+
+			is_equal = false;
+
+			do
+			{
+				if (max_inclusive)
+					maxoff += 1;
+				else
+					maxoff -= 1;
+				if (maxoff < 0 ||
+					maxoff >= partdesc->boundinfo->ndatums)
+					break;
+				cmpval = partition_bound_cmp(partkey, partdesc->boundinfo,
+											 maxoff, &arg);
+			} while (cmpval == 0);
+
+			/* Back up if went too far. */
+			if (max_inclusive)
+				maxoff -= 1;
+		}
+
+		/* Interpret the result per partition strategy. */
+		switch (partkey->strategy)
+		{
+			case PARTITION_STRATEGY_LIST:
+				/*
+				 * Found, but if the query may have asked us to exclude it.
+				 */
+				if (is_equal && !max_inclusive)
+					maxoff--;
+				break;
+
+			case PARTITION_STRATEGY_RANGE:
+				/*
+				 * Because bounds[maxoff] <= max_scankey, we may need to
+				 * to consider the next partition as well, in addition to
+				 * the partition at maxoff and earlier.
+				 */
+				if (!is_equal || max_inclusive)
+					maxoff += 1;
+
+				/* Make sure to skip a gap. */
+				if (partdesc->boundinfo->indexes[maxoff] < 0 && maxoff >= 1)
+					maxoff -= 1;
+				break;
+		}
+	}
+	else
+		maxoff = partdesc->boundinfo->ndatums - 1;
+
+	*min_datum_index = minoff;
+	*max_datum_index = maxoff;
+
+	switch (partkey->strategy)
+	{
+		case PARTITION_STRATEGY_LIST:
+			for (i = minoff; i <= maxoff; i++)
+			{
+				int		partition_idx = partdesc->boundinfo->indexes[i];
+
+				/*
+				 * Multiple values may belong to the same partition, so make
+				 * sure we don't add the same partition index again.
+				 */
+				result = list_append_unique_int(result, partition_idx);
+			}
+
+			/* If no bounding keys exist, include the null partition too. */
+			if (null_partition_idx >= 0 &&
+				(keynullness[0] == -1 || keynullness[0] != IS_NOT_NULL))
+			{
+				*null_partition_chosen = true;
+				result = list_append_unique_int(result, null_partition_idx);
+			}
+
+			break;
+
+		case PARTITION_STRATEGY_RANGE:
+			for (i = minoff; i <= maxoff; i++)
+			{
+				int		partition_idx = partdesc->boundinfo->indexes[i];
+
+				/*
+				 * If a valid partition exists for this range, add its
+				 * index, if not, the default partition (if any) would be
+				 * covering that range, so request to include the same.
+				 */
+				if (partition_idx >= 0)
+					result = lappend_int(result, partition_idx);
+				else
+					scan_default = true;
+			}
+			break;
+	}
+
+	if (scan_default && partdesc->boundinfo->default_index >= 0)
+		result = lappend_int(result, partdesc->boundinfo->default_index);
 
 	return result;
 }
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 49578c0684..ef84dac7f2 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1160,7 +1160,9 @@ get_relation_constraints(PlannerInfo *root,
 	Index		varno = rel->relid;
 	Relation	relation;
 	TupleConstr *constr;
+#ifdef USE_PARTITION_CONSTRAINT_FOR_PRUNING
 	List	   *pcqual;
+#endif
 
 	/*
 	 * We assume the relation has already been safely locked.
@@ -1246,6 +1248,7 @@ get_relation_constraints(PlannerInfo *root,
 		}
 	}
 
+#ifdef USE_PARTITION_CONSTRAINT_FOR_PRUNING
 	/* Append partition predicates, if any */
 	pcqual = RelationGetPartitionQual(relation);
 	if (pcqual)
@@ -1263,6 +1266,7 @@ get_relation_constraints(PlannerInfo *root,
 
 		result = list_concat(result, pcqual);
 	}
+#endif
 
 	heap_close(relation, NoLock);
 
-- 
2.11.0