On 5 July 2017 at 10:43, Amit Langote <langote_amit...@lab.ntt.co.jp> wrote:
>> So the more I think about this, the more I think that a cleaner design
>> would be as follows:
>>
>> 1). Don't allow UNBOUNDED, except in the first column, where it can
>>     keep it's current meaning.
>>
>> 2). Allow the partition bounds to have fewer columns than the
>>     partition definition, and have that mean the same as it would have
>>     meant if you were partitioning by that many columns. So, for
>>     example, if you were partitioning by (col1,col2), you'd be allowed
>>     to define a partition like so:
>>
>>       FROM (x) TO (y)
>>
>>     and it would mean
>>
>>       x <= col1 < y
>>
>>     Or you'd be able to define a partition like
>>
>>       FROM (x1,x2) TO (y)
>>
>>     which would mean
>>
>>       (col1 > x1) OR (col1 = x1 AND col2 >= x2) AND col1 < y
>>
>> 3). Don't allow any value after UNBOUNDED (i.e., only specify
>>     UNBOUNDED once in a partition bound).
>
> I assume we don't need the ability of specifying ABOVE/BELOW in this design.
>

Yes that's right.


> In retrospect, that sounds like something that was implemented in the
> earlier versions of the patch, whereby there was no ability to specify
> UNBOUNDED on a per-column basis.  So the syntax was:
>
> FROM { (x [, ...]) | UNBOUNDED } TO { (y [, ...]) | UNBOUNDED }
>

Yes, that's where I ended up too.


> But, it was pointed out to me [1] that that doesn't address the use case,
> for example, where part1 goes up to (10, 10) and part2 goes from (10, 10)
> up to (10, unbounded).
>
> The new design will limit the usage of unbounded range partitions at the
> tail ends.
>

True, but I don't think that's really a problem. When the first column
is a discrete type, an upper bound of (10, unbounded) can be rewritten
as (11) in the new design. When it's a continuous type, e.g. floating
point, it can no longer be represented, because (10.0, unbounded)
really means (col1 <= 10.0). But we've already decided not to support
anything other than inclusive lower bounds and exclusive upper bounds,
so allowing this upper bound goes against that design choice.


>> Of course, it's pretty late in the day to be proposing this kind of
>> redesign, but I fear that if we don't tackle it now, it will just be
>> harder to deal with in the future.
>>
>> Actually, a quick, simple hacky implementation might be to just fill
>> in any omitted values in a partition bound with negative infinity
>> internally, and when printing a bound, omit any values after an
>> infinite value. But really, I think we'd want to tidy up the
>> implementation, and I think a number of things would actually get much
>> simpler. For example, get_qual_for_range() could simply stop when it
>> reached the end of the list of values for the bound, and it wouldn't
>> need to worry about an unbounded value following a bounded one.
>>
>> Thoughts?
>
> I cooked up a patch for the "hacky" implementation for now, just as you
> described in the above paragraph.  Will you be willing to give it a look?
> I will also think about the non-hacky way of implementing this.
>

OK, I'll take a look.

Meanwhile, I already had a go at the "non-hacky" implementation (WIP
patch attached). The more I worked on it, the simpler things got,
which I think is a good sign.

Part-way through, I realised that the PartitionRangeDatum Node type is
no longer needed, because each bound value is now necessarily finite,
so the lowerdatums and upperdatums lists in a PartitionBoundSpec can
now be made into lists of Const nodes, making them match the
listdatums field used for LIST partitioning, and then a whole lot of
related code gets simplified.

It needed a little bit more code in partition.c to track individual
bound sizes, but there were a number of other places that could be
simplified, so overall this represents a reduction in the code size
and complexity.

It's not complete (e.g., no doc updates yet), but it passes all the
tests, and so far seems to work as I would expect.

Regards,
Dean
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 7da2058..aade9f5
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -66,23 +66,26 @@
  * is an upper bound.
  */
 
-/* Ternary value to represent what's contained in a range bound datum */
-typedef enum RangeDatumContent
+/* Type of an individual bound of a range partition */
+typedef enum RangeBoundKind
 {
-	RANGE_DATUM_FINITE = 0,		/* actual datum stored elsewhere */
-	RANGE_DATUM_NEG_INF,		/* negative infinity */
-	RANGE_DATUM_POS_INF			/* positive infinity */
-} RangeDatumContent;
+	RANGE_BOUND_FINITE = 0,		/* actual bound stored in the datums array */
+	RANGE_BOUND_NEG_INF,		/* negative infinity; NULL datums array */
+	RANGE_BOUND_POS_INF			/* positive infinity; NULL datums array */
+} RangeBoundKind;
 
 typedef struct PartitionBoundInfoData
 {
 	char		strategy;		/* list or range bounds? */
 	int			ndatums;		/* Length of the datums following array */
-	Datum	  **datums;			/* Array of datum-tuples with key->partnatts
-								 * datums each */
-	RangeDatumContent **content;	/* what's contained in each range bound
-									 * datum? (see the above enum); NULL for
+	Datum	  **datums;			/* Array of datum-tuples with up to
+								 * key->partnatts datums each */
+	RangeBoundKind *rbound_kind;	/* The type of each range bound; one per
+									 * member of the datums array; NULL for
 									 * list partitioned tables */
+	int		   *rbound_ndatums;	/* The number of datums in each range bound;
+								 * one per member of the datums array; NULL
+								 * for list partitioned tables */
 	int		   *indexes;		/* Partition indexes; one entry per member of
 								 * the datums array (plus one if range
 								 * partitioned table) */
@@ -108,8 +111,11 @@ typedef struct PartitionListValue
 typedef struct PartitionRangeBound
 {
 	int			index;
-	Datum	   *datums;			/* range bound datums */
-	RangeDatumContent *content; /* what's contained in each datum? */
+	RangeBoundKind kind;		/* type of range bound */
+	int			ndatums;		/* number of range bound datums; 0 for
+								 * unbounded ranges */
+	Datum	   *datums;			/* range bound datums; NULL for unbounded
+								 * ranges */
 	bool		lower;			/* this is the lower (vs upper) bound */
 } PartitionRangeBound;
 
@@ -123,11 +129,8 @@ static Oid get_partition_operator(Partit
 static Expr *make_partition_op_expr(PartitionKey key, int keynum,
 					   uint16 strategy, Expr *arg1, Expr *arg2);
 static void get_range_key_properties(PartitionKey key, int keynum,
-						 PartitionRangeDatum *ldatum,
-						 PartitionRangeDatum *udatum,
 						 ListCell **partexprs_item,
-						 Expr **keyCol,
-						 Const **lower_val, Const **upper_val);
+						 Expr **keyCol);
 static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec);
 static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec);
 static List *generate_partition_qual(Relation rel);
@@ -135,11 +138,11 @@ static List *generate_partition_qual(Rel
 static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
 					 List *datums, bool lower);
 static int32 partition_rbound_cmp(PartitionKey key,
-					 Datum *datums1, RangeDatumContent *content1, bool lower1,
-					 PartitionRangeBound *b2);
+					 Datum *datums1, int ndatums1, RangeBoundKind kind1,
+					 bool lower1, PartitionRangeBound *b2);
 static int32 partition_rbound_datum_cmp(PartitionKey key,
-						   Datum *rb_datums, RangeDatumContent *rb_content,
-						   Datum *tuple_datums);
+						   Datum *rb_datums, int rb_ndatums,
+						   RangeBoundKind rb_kind, Datum *tuple_datums);
 
 static int32 partition_bound_cmp(PartitionKey key,
 					PartitionBoundInfo boundinfo,
@@ -366,36 +369,27 @@ RelationBuildPartitionDesc(Relation rel)
 				int			j;
 
 				/* Is current bound is distinct from the previous? */
-				for (j = 0; j < key->partnatts; j++)
+				if (prev == NULL ||
+					cur->kind != prev->kind ||
+					cur->ndatums != prev->ndatums)
 				{
-					Datum		cmpval;
-
-					if (prev == NULL)
+					is_distinct = true;
+				}
+				else
+				{
+					for (j = 0; j < cur->ndatums; j++)
 					{
-						is_distinct = true;
-						break;
-					}
+						Datum		cmpval;
 
-					/*
-					 * If either of them has infinite element, we can't equate
-					 * them.  Even when both are infinite, they'd have
-					 * opposite signs, because only one of cur and prev is a
-					 * lower bound).
-					 */
-					if (cur->content[j] != RANGE_DATUM_FINITE ||
-						prev->content[j] != RANGE_DATUM_FINITE)
-					{
-						is_distinct = true;
-						break;
-					}
-					cmpval = FunctionCall2Coll(&key->partsupfunc[j],
-											   key->partcollation[j],
-											   cur->datums[j],
-											   prev->datums[j]);
-					if (DatumGetInt32(cmpval) != 0)
-					{
-						is_distinct = true;
-						break;
+						cmpval = FunctionCall2Coll(&key->partsupfunc[j],
+												   key->partcollation[j],
+												   cur->datums[j],
+												   prev->datums[j]);
+						if (DatumGetInt32(cmpval) != 0)
+						{
+							is_distinct = true;
+							break;
+						}
 					}
 				}
 
@@ -454,8 +448,8 @@ RelationBuildPartitionDesc(Relation rel)
 			palloc0(sizeof(PartitionBoundInfoData));
 		boundinfo->strategy = key->strategy;
 		boundinfo->ndatums = ndatums;
-		boundinfo->null_index = -1;
 		boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
+		boundinfo->null_index = -1;
 
 		/* Initialize mapping array with invalid values */
 		mapping = (int *) palloc(sizeof(int) * nparts);
@@ -512,8 +506,11 @@ RelationBuildPartitionDesc(Relation rel)
 
 			case PARTITION_STRATEGY_RANGE:
 				{
-					boundinfo->content = (RangeDatumContent **) palloc(ndatums *
-																	   sizeof(RangeDatumContent *));
+					boundinfo->rbound_kind = (RangeBoundKind *)
+											 palloc(ndatums *
+													sizeof(RangeBoundKind));
+					boundinfo->rbound_ndatums = (int *) palloc(ndatums *
+															   sizeof(int));
 					boundinfo->indexes = (int *) palloc((ndatums + 1) *
 														sizeof(int));
 
@@ -521,20 +518,20 @@ RelationBuildPartitionDesc(Relation rel)
 					{
 						int			j;
 
-						boundinfo->datums[i] = (Datum *) palloc(key->partnatts *
-																sizeof(Datum));
-						boundinfo->content[i] = (RangeDatumContent *)
-							palloc(key->partnatts *
-								   sizeof(RangeDatumContent));
-						for (j = 0; j < key->partnatts; j++)
+						boundinfo->rbound_kind[i] = rbounds[i]->kind;
+						boundinfo->rbound_ndatums[i] = rbounds[i]->ndatums;
+						if (rbounds[i]->ndatums > 0)
 						{
-							if (rbounds[i]->content[j] == RANGE_DATUM_FINITE)
+							boundinfo->datums[i] = (Datum *) palloc(rbounds[i]->ndatums *
+																	sizeof(Datum));
+
+							for (j = 0; j < rbounds[i]->ndatums; j++)
+							{
 								boundinfo->datums[i][j] =
 									datumCopy(rbounds[i]->datums[j],
 											  key->parttypbyval[j],
 											  key->parttyplen[j]);
-							/* Remember, we are storing the tri-state value. */
-							boundinfo->content[i][j] = rbounds[i]->content[j];
+							}
 						}
 
 						/*
@@ -611,25 +608,30 @@ partition_bounds_equal(PartitionKey key,
 
 	for (i = 0; i < b1->ndatums; i++)
 	{
+		int			ndatums;
 		int			j;
 
-		for (j = 0; j < key->partnatts; j++)
+		if (key->strategy == PARTITION_STRATEGY_RANGE)
 		{
-			/* For range partitions, the bounds might not be finite. */
-			if (b1->content != NULL)
-			{
-				/*
-				 * A finite bound always differs from an infinite bound, and
-				 * different kinds of infinities differ from each other.
-				 */
-				if (b1->content[i][j] != b2->content[i][j])
-					return false;
+			/* Fields only used for range partitions */
+			if (b1->rbound_kind[i] != b2->rbound_kind[i])
+				return false;
 
-				/* Non-finite bounds are equal without further examination. */
-				if (b1->content[i][j] != RANGE_DATUM_FINITE)
-					continue;
-			}
+			if (b1->rbound_ndatums[i] != b2->rbound_ndatums[i])
+				return false;
 
+			/* Number of datums can vary by bound */
+			ndatums = b1->rbound_ndatums[i];
+		}
+		else
+			/* Number of datums is the same for each bound */
+			ndatums = key->partnatts;
+
+		if (b1->indexes[i] != b2->indexes[i])
+			return false;
+
+		for (j = 0; j < ndatums; j++)
+		{
 			/*
 			 * Compare the actual values. Note that it would be both incorrect
 			 * and unsafe to invoke the comparison operator derived from the
@@ -646,9 +648,6 @@ partition_bounds_equal(PartitionKey key,
 							  key->parttyplen[j]))
 				return false;
 		}
-
-		if (b1->indexes[i] != b2->indexes[i])
-			return false;
 	}
 
 	/* There are ndatums+1 indexes in case of range partitions */
@@ -735,8 +734,8 @@ check_new_partition_bound(char *relname,
 				 * First check if the resulting range would be empty with
 				 * specified lower and upper bounds
 				 */
-				if (partition_rbound_cmp(key, lower->datums, lower->content, true,
-										 upper) >= 0)
+				if (partition_rbound_cmp(key, lower->datums, lower->ndatums,
+										 lower->kind, true, upper) >= 0)
 					ereport(ERROR,
 							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 							 errmsg("cannot create range partition with empty range"),
@@ -745,78 +744,62 @@ check_new_partition_bound(char *relname,
 				if (partdesc->nparts > 0)
 				{
 					PartitionBoundInfo boundinfo = partdesc->boundinfo;
-					int			off1,
-								off2;
-					bool		equal = false;
+					int			offset;
+					bool		equal;
 
 					Assert(boundinfo && boundinfo->ndatums > 0 &&
 						   boundinfo->strategy == PARTITION_STRATEGY_RANGE);
 
 					/*
-					 * Firstly, find the greatest range bound that is less
-					 * than or equal to the new lower bound.
+					 * Test whether the new lower bound (which is treated
+					 * inclusively as part of the new partition) lies inside an
+					 * existing partition, or in a gap.
+					 *
+					 * If it's in a gap, the next index value will be -1 (the
+					 * lower bound of the next partition).  This is also true
+					 * if there is no next partition, since the index array is
+					 * initialised with an extra -1 at the end.
+					 *
+					 * Note that this also allows for the possibility that the
+					 * new lower bound equals an existing upper bound.
 					 */
-					off1 = partition_bound_bsearch(key, boundinfo, lower, true,
-												   &equal);
+					offset = partition_bound_bsearch(key, boundinfo, lower,
+													 true, &equal);
 
-					/*
-					 * off1 == -1 means that all existing bounds are greater
-					 * than the new lower bound.  In that case and the case
-					 * where no partition is defined between the bounds at
-					 * off1 and off1 + 1, we have a "gap" in the range that
-					 * could be occupied by the new partition.  We confirm if
-					 * so by checking whether the new upper bound is confined
-					 * within the gap.
-					 */
-					if (!equal && boundinfo->indexes[off1 + 1] < 0)
+					if (boundinfo->indexes[offset + 1] < 0)
 					{
-						off2 = partition_bound_bsearch(key, boundinfo, upper,
-													   true, &equal);
-
 						/*
-						 * If the new upper bound is returned to be equal to
-						 * the bound at off2, the latter must be the upper
-						 * bound of some partition with which the new
-						 * partition clearly overlaps.
-						 *
-						 * Also, if bound at off2 is not same as the one
-						 * returned for the new lower bound (IOW, off1 !=
-						 * off2), then the new partition overlaps at least one
-						 * partition.
+						 * Check that the new partition will fit in the gap.
+						 * For it to fit, the new upper bound must be less than
+						 * or equal to the lower bound of the next partition,
+						 * if there is one.
 						 */
-						if (equal || off1 != off2)
+						if (offset + 1 < boundinfo->ndatums)
 						{
-							overlap = true;
+							int32		cmpval;
 
-							/*
-							 * The bound at off2 could be the lower bound of
-							 * the partition with which the new partition
-							 * overlaps.  In that case, use the upper bound
-							 * (that is, the bound at off2 + 1) to get the
-							 * index of that partition.
-							 */
-							if (boundinfo->indexes[off2] < 0)
-								with = boundinfo->indexes[off2 + 1];
-							else
-								with = boundinfo->indexes[off2];
+							cmpval = partition_bound_cmp(key, boundinfo,
+														 offset + 1, upper,
+														 true);
+							if (cmpval < 0)
+							{
+								/*
+								 * The new partition overlaps with the existing
+								 * partition between offset + 1 and offset + 2.
+								 */
+								overlap = true;
+								with = boundinfo->indexes[offset + 2];
+							}
 						}
 					}
 					else
 					{
 						/*
-						 * Equal has been set to true and there is no "gap"
-						 * between the bound at off1 and that at off1 + 1, so
-						 * the new partition will overlap some partition. In
-						 * the former case, the new lower bound is found to be
-						 * equal to the bound at off1, which could only ever
-						 * be true if the latter is the lower bound of some
-						 * partition.  It's clear in such a case that the new
-						 * partition overlaps that partition, whose index we
-						 * get using its upper bound (that is, using the bound
-						 * at off1 + 1).
+						 * The new partition overlaps with the existing
+						 * partition between offset and offset + 1.
 						 */
 						overlap = true;
-						with = boundinfo->indexes[off1 + 1];
+						with = boundinfo->indexes[offset + 1];
 					}
 				}
 
@@ -1410,21 +1393,15 @@ get_qual_for_list(PartitionKey key, Part
  * This is a subroutine for get_qual_for_range, and its API is pretty
  * specialized to that caller.
  *
- * Constructs an Expr for the key column (returned in *keyCol) and Consts
- * for the lower and upper range limits (returned in *lower_val and
- * *upper_val).  For UNBOUNDED limits, NULL is returned instead of a Const.
- * All of these structures are freshly palloc'd.
+ * Constructs an Expr for the key column (returned in *keyCol).
  *
  * *partexprs_item points to the cell containing the next expression in
  * the key->partexprs list, or NULL.  It may be advanced upon return.
  */
 static void
 get_range_key_properties(PartitionKey key, int keynum,
-						 PartitionRangeDatum *ldatum,
-						 PartitionRangeDatum *udatum,
 						 ListCell **partexprs_item,
-						 Expr **keyCol,
-						 Const **lower_val, Const **upper_val)
+						 Expr **keyCol)
 {
 	/* Get partition key expression for this column */
 	if (key->partattrs[keynum] != 0)
@@ -1443,17 +1420,6 @@ get_range_key_properties(PartitionKey ke
 		*keyCol = copyObject(lfirst(*partexprs_item));
 		*partexprs_item = lnext(*partexprs_item);
 	}
-
-	/* Get appropriate Const nodes for the bounds */
-	if (!ldatum->infinite)
-		*lower_val = castNode(Const, copyObject(ldatum->value));
-	else
-		*lower_val = NULL;
-
-	if (!udatum->infinite)
-		*upper_val = castNode(Const, copyObject(udatum->value));
-	else
-		*upper_val = NULL;
 }
 
 /*
@@ -1484,17 +1450,16 @@ get_range_key_properties(PartitionKey ke
  *		AND
  *	(b < bu) OR (b = bu AND c < cu))
  *
- * If cu happens to be UNBOUNDED, we need not emit any expression for it, so
- * the last line would be:
- *
- *	(b < bu) OR (b = bu), which is simplified to (b <= bu)
+ * The upper and lower bound tuples may also contain fewer values than the
+ * number of columns in the partition key, in which case the relevant
+ * expression is shortened.
  *
  * In most common cases with only one partition column, say a, the following
  * expression tree will be generated: a IS NOT NULL AND a >= al AND a < au
  *
- * If all values of both lower and upper bounds are UNBOUNDED, the partition
- * does not really have a constraint, except the IS NOT NULL constraint for
- * partition keys.
+ * If either bound is UNBOUNDED, the matching bound tuples list is empty and
+ * the partition does not really have a constraint, except the IS NOT NULL
+ * constraint for partition keys.
  *
  * If we end up with an empty result list, we return a single-member list
  * containing a constant TRUE, because callers expect a non-empty list.
@@ -1509,8 +1474,6 @@ get_qual_for_range(PartitionKey key, Par
 			   *partexprs_item_saved;
 	int			i,
 				j;
-	PartitionRangeDatum *ldatum,
-			   *udatum;
 	Expr	   *keyCol;
 	Const	   *lower_val,
 			   *upper_val;
@@ -1582,8 +1545,8 @@ get_qual_for_range(PartitionKey key, Par
 		Datum		test_result;
 		bool		isNull;
 
-		ldatum = castNode(PartitionRangeDatum, lfirst(cell1));
-		udatum = castNode(PartitionRangeDatum, lfirst(cell2));
+		lower_val = castNode(Const, lfirst(cell1));
+		upper_val = castNode(Const, lfirst(cell2));
 
 		/*
 		 * Since get_range_key_properties() modifies partexprs_item, and we
@@ -1592,18 +1555,7 @@ get_qual_for_range(PartitionKey key, Par
 		 */
 		partexprs_item_saved = partexprs_item;
 
-		get_range_key_properties(key, i, ldatum, udatum,
-								 &partexprs_item,
-								 &keyCol,
-								 &lower_val, &upper_val);
-
-		/*
-		 * If either or both of lower_val and upper_val is NULL, they are
-		 * unequal, because being NULL means the column is unbounded in the
-		 * respective direction.
-		 */
-		if (!lower_val || !upper_val)
-			break;
+		get_range_key_properties(key, i, &partexprs_item, &keyCol);
 
 		/* Create the test expression */
 		estate = CreateExecutorState();
@@ -1643,7 +1595,7 @@ get_qual_for_range(PartitionKey key, Par
 	lower_or_start_datum = cell1;
 	upper_or_start_datum = cell2;
 
-	/* OR will have as many arms as there are key columns left. */
+	/* OR will have up to as many arms as there are key columns left. */
 	num_or_arms = key->partnatts - i;
 	current_or_arm = 0;
 	lower_or_arms = upper_or_arms = NIL;
@@ -1653,27 +1605,23 @@ get_qual_for_range(PartitionKey key, Par
 		List	   *lower_or_arm_args = NIL,
 				   *upper_or_arm_args = NIL;
 
-		/* Restart scan of columns from the i'th one */
+		/*
+		 * Restart scan of columns from the i'th one.  We cannot use forboth
+		 * here because the upper and lower bounds may have different numbers
+		 * of values.
+		 */
 		j = i;
 		partexprs_item = partexprs_item_saved;
 
-		for_both_cell(cell1, lower_or_start_datum, cell2, upper_or_start_datum)
+		for (cell1 = lower_or_start_datum, cell2 = upper_or_start_datum;
+			 cell1 != NULL || cell2 != NULL;
+			 cell1 = cell1 ? lnext(cell1) : NULL,
+			 cell2 = cell2 ? lnext(cell2) : NULL)
 		{
-			PartitionRangeDatum *ldatum_next = NULL,
-					   *udatum_next = NULL;
+			lower_val = cell1 ? castNode(Const, lfirst(cell1)) : NULL;
+			upper_val = cell2 ? castNode(Const, lfirst(cell2)) : NULL;
 
-			ldatum = castNode(PartitionRangeDatum, lfirst(cell1));
-			if (lnext(cell1))
-				ldatum_next = castNode(PartitionRangeDatum,
-									   lfirst(lnext(cell1)));
-			udatum = castNode(PartitionRangeDatum, lfirst(cell2));
-			if (lnext(cell2))
-				udatum_next = castNode(PartitionRangeDatum,
-									   lfirst(lnext(cell2)));
-			get_range_key_properties(key, j, ldatum, udatum,
-									 &partexprs_item,
-									 &keyCol,
-									 &lower_val, &upper_val);
+			get_range_key_properties(key, j, &partexprs_item, &keyCol);
 
 			if (need_next_lower_arm && lower_val)
 			{
@@ -1681,12 +1629,11 @@ get_qual_for_range(PartitionKey key, Par
 
 				/*
 				 * For the non-last columns of this arm, use the EQ operator.
-				 * For the last or the last finite-valued column, use GE.
+				 * For the last column of the bound, use GE. Otherwise use GT.
 				 */
 				if (j - i < current_or_arm)
 					strategy = BTEqualStrategyNumber;
-				else if ((ldatum_next && ldatum_next->infinite) ||
-						 j == key->partnatts - 1)
+				else if (lnext(cell1) == NULL)
 					strategy = BTGreaterEqualStrategyNumber;
 				else
 					strategy = BTGreaterStrategyNumber;
@@ -1704,12 +1651,10 @@ get_qual_for_range(PartitionKey key, Par
 
 				/*
 				 * For the non-last columns of this arm, use the EQ operator.
-				 * For the last finite-valued column, use LE.
+				 * Otherwise use LT.
 				 */
 				if (j - i < current_or_arm)
 					strategy = BTEqualStrategyNumber;
-				else if (udatum_next && udatum_next->infinite)
-					strategy = BTLessEqualStrategyNumber;
 				else
 					strategy = BTLessStrategyNumber;
 
@@ -1729,11 +1674,11 @@ get_qual_for_range(PartitionKey key, Par
 			if (j - i > current_or_arm)
 			{
 				/*
-				 * We need not emit the next arm if the new column that will
-				 * be considered is unbounded.
+				 * We need not emit the next arm if there are no bounds on any
+				 * of the remaining columns.
 				 */
-				need_next_lower_arm = ldatum_next && !ldatum_next->infinite;
-				need_next_upper_arm = udatum_next && !udatum_next->infinite;
+				need_next_lower_arm = (cell1 && lnext(cell1));
+				need_next_upper_arm = (cell2 && lnext(cell2));
 				break;
 			}
 		}
@@ -2091,9 +2036,8 @@ qsort_partition_list_value_cmp(const voi
 /*
  * make_one_range_bound
  *
- * Return a PartitionRangeBound given a list of PartitionRangeDatum elements
- * and a flag telling whether the bound is lower or not.  Made into a function
- * because there are multiple sites that want to use this facility.
+ * Return a PartitionRangeBound given a list of Const bound values and a flag
+ * indicating whether the bound is a lower bound or not.
  */
 static PartitionRangeBound *
 make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
@@ -2104,31 +2048,30 @@ make_one_range_bound(PartitionKey key, i
 
 	bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
 	bound->index = index;
-	bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
-	bound->content = (RangeDatumContent *) palloc0(key->partnatts *
-												   sizeof(RangeDatumContent));
 	bound->lower = lower;
 
-	i = 0;
-	foreach(lc, datums)
+	/* Handle unbounded ranges, which have an empty list of bound values */
+	if (!datums)
 	{
-		PartitionRangeDatum *datum = castNode(PartitionRangeDatum, lfirst(lc));
-
-		/* What's contained in this range datum? */
-		bound->content[i] = !datum->infinite
-			? RANGE_DATUM_FINITE
-			: (lower ? RANGE_DATUM_NEG_INF
-			   : RANGE_DATUM_POS_INF);
+		bound->kind = lower ? RANGE_BOUND_NEG_INF : RANGE_BOUND_POS_INF;
+		bound->ndatums = 0;
+		bound->datums = NULL;
+		return bound;
+	}
 
-		if (bound->content[i] == RANGE_DATUM_FINITE)
-		{
-			Const	   *val = castNode(Const, datum->value);
+	/* Otherwise it's a finite bound; store the datum values in an array */
+	bound->kind = RANGE_BOUND_FINITE;
+	bound->ndatums = list_length(datums);
+	bound->datums = (Datum *) palloc0(bound->ndatums * sizeof(Datum));
 
-			if (val->constisnull)
-				elog(ERROR, "invalid range bound datum");
-			bound->datums[i] = val->constvalue;
-		}
+	i = 0;
+	foreach(lc, datums)
+	{
+		Const	   *val = castNode(Const, lfirst(lc));
 
+		if (val->constisnull)
+			elog(ERROR, "invalid range bound datum");
+		bound->datums[i] = val->constvalue;
 		i++;
 	}
 
@@ -2143,100 +2086,113 @@ qsort_partition_rbound_cmp(const void *a
 	PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
 	PartitionKey key = (PartitionKey) arg;
 
-	return partition_rbound_cmp(key, b1->datums, b1->content, b1->lower, b2);
+	return partition_rbound_cmp(key, b1->datums, b1->ndatums, b1->kind,
+								b1->lower, b2);
 }
 
 /*
  * partition_rbound_cmp
  *
  * Return for two range bounds whether the 1st one (specified in datum1,
- * content1, and lower1) is <=, =, >= the bound specified in *b2
+ * ndatums1, kind1, and lower1) is <, =, > the bound specified in *b2
  */
 static int32
 partition_rbound_cmp(PartitionKey key,
-					 Datum *datums1, RangeDatumContent *content1, bool lower1,
-					 PartitionRangeBound *b2)
+					 Datum *datums1, int ndatums1, RangeBoundKind kind1,
+					 bool lower1, PartitionRangeBound *b2)
 {
-	int32		cmpval = 0;		/* placate compiler */
 	int			i;
 	Datum	   *datums2 = b2->datums;
-	RangeDatumContent *content2 = b2->content;
 	bool		lower2 = b2->lower;
 
-	for (i = 0; i < key->partnatts; i++)
-	{
-		/*
-		 * First, handle cases involving infinity, which don't require
-		 * invoking the comparison proc.
-		 */
-		if (content1[i] != RANGE_DATUM_FINITE &&
-			content2[i] != RANGE_DATUM_FINITE)
+	/*
+	 * Handle cases involving infinity, which don't require any values to be
+	 * compared.
+	 */
+	if (kind1 == RANGE_BOUND_NEG_INF)
+		return b2->kind == RANGE_BOUND_NEG_INF ? 0 : -1;
+	else if (kind1 == RANGE_BOUND_POS_INF)
+		return b2->kind == RANGE_BOUND_POS_INF ? 0 : 1;
+	else if (b2->kind != RANGE_BOUND_FINITE)
+		return b2->kind == RANGE_BOUND_NEG_INF ? 1 : -1;
 
-			/*
-			 * Both are infinity, so they are equal unless one is negative
-			 * infinity and other positive (or vice versa)
-			 */
-			return content1[i] == content2[i] ? 0
-				: (content1[i] < content2[i] ? -1 : 1);
-		else if (content1[i] != RANGE_DATUM_FINITE)
-			return content1[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
-		else if (content2[i] != RANGE_DATUM_FINITE)
-			return content2[i] == RANGE_DATUM_NEG_INF ? 1 : -1;
+	/* Compare values from the two range bounds */
+	for (i = 0; i < Min(ndatums1, b2->ndatums); i++)
+	{
+		int32		cmpval;
 
 		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
 												 key->partcollation[i],
 												 datums1[i],
 												 datums2[i]));
 		if (cmpval != 0)
-			break;
+			return cmpval;
 	}
 
 	/*
-	 * If the comparison is anything other than equal, we're done. If they
-	 * compare equal though, we still have to consider whether the boundaries
-	 * are inclusive or exclusive.  Exclusive one is considered smaller of the
-	 * two.
+	 * If one range has more values than the other, treat that range as
+	 * larger, since all the shared values are now known to be equal.
 	 */
-	if (cmpval == 0 && lower1 != lower2)
-		cmpval = lower1 ? 1 : -1;
+	if (ndatums1 > b2->ndatums)
+		return 1;
+	else if (ndatums1 < b2->ndatums)
+		return -1;
 
-	return cmpval;
+	/*
+	 * The two bounds are equal as far as their values are concerned.  If one
+	 * is a lower (inclusive) bound and the other is an upper (exclusive)
+	 * bound, treat the upper bound as smaller since it belongs to a partition
+	 * that holds smaller values.
+	 */
+	if (lower1)
+		return lower2 ? 0 : 1;
+	else
+		return lower2 ? -1 : 0;
 }
 
 /*
  * partition_rbound_datum_cmp
  *
- * Return whether range bound (specified in rb_datums, rb_content, and
- * rb_lower) <=, =, >= partition key of tuple (tuple_datums)
+ * Return whether range bound (specified in rb_datums, rb_ndatums, rb_kind,
+ * and rb_lower) <, =, > partition key of tuple (tuple_datums)
  */
 static int32
 partition_rbound_datum_cmp(PartitionKey key,
-						   Datum *rb_datums, RangeDatumContent *rb_content,
-						   Datum *tuple_datums)
+						   Datum *rb_datums, int rb_ndatums,
+						   RangeBoundKind rb_kind, Datum *tuple_datums)
 {
 	int			i;
-	int32		cmpval = -1;
 
-	for (i = 0; i < key->partnatts; i++)
+	/* Handle UNBOUNDED ranges */
+	if (rb_kind == RANGE_BOUND_NEG_INF)
+		return -1;
+	if (rb_kind == RANGE_BOUND_POS_INF)
+		return 1;
+
+	/* Compare the range bound values with the tuple values */
+	for (i = 0; i < rb_ndatums; i++)
 	{
-		if (rb_content[i] != RANGE_DATUM_FINITE)
-			return rb_content[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
+		int32		cmpval;
 
 		cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
 												 key->partcollation[i],
 												 rb_datums[i],
 												 tuple_datums[i]));
 		if (cmpval != 0)
-			break;
+			return cmpval;
 	}
 
-	return cmpval;
+	/*
+	 * All the tuple values are the same as the range bound values, but there
+	 * may be more values in the tuple, in which case it is larger.
+	 */
+	return key->partnatts > rb_ndatums ? -1 : 0;
 }
 
 /*
  * partition_bound_cmp
  *
- * Return whether the bound at offset in boundinfo is <=, =, >= the argument
+ * Return whether the bound at offset in boundinfo is <, =, > the argument
  * specified in *probe.
  */
 static int32
@@ -2257,7 +2213,8 @@ partition_bound_cmp(PartitionKey key, Pa
 
 		case PARTITION_STRATEGY_RANGE:
 			{
-				RangeDatumContent *content = boundinfo->content[offset];
+				RangeBoundKind kind = boundinfo->rbound_kind[offset];
+				int			ndatums = boundinfo->rbound_ndatums[offset];
 
 				if (probe_is_bound)
 				{
@@ -2268,13 +2225,13 @@ partition_bound_cmp(PartitionKey key, Pa
 					 */
 					bool		lower = boundinfo->indexes[offset] < 0;
 
-					cmpval = partition_rbound_cmp(key,
-												  bound_datums, content, lower,
+					cmpval = partition_rbound_cmp(key, bound_datums, ndatums,
+												  kind, lower,
 												  (PartitionRangeBound *) probe);
 				}
 				else
-					cmpval = partition_rbound_datum_cmp(key,
-														bound_datums, content,
+					cmpval = partition_rbound_datum_cmp(key, bound_datums,
+														ndatums, kind,
 														(Datum *) probe);
 				break;
 			}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index 67ac814..c4036a9
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4453,18 +4453,6 @@ _copyPartitionBoundSpec(const PartitionB
 	return newnode;
 }
 
-static PartitionRangeDatum *
-_copyPartitionRangeDatum(const PartitionRangeDatum *from)
-{
-	PartitionRangeDatum *newnode = makeNode(PartitionRangeDatum);
-
-	COPY_SCALAR_FIELD(infinite);
-	COPY_NODE_FIELD(value);
-	COPY_LOCATION_FIELD(location);
-
-	return newnode;
-}
-
 static PartitionCmd *
 _copyPartitionCmd(const PartitionCmd *from)
 {
@@ -5519,9 +5507,6 @@ copyObjectImpl(const void *from)
 		case T_PartitionBoundSpec:
 			retval = _copyPartitionBoundSpec(from);
 			break;
-		case T_PartitionRangeDatum:
-			retval = _copyPartitionRangeDatum(from);
-			break;
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
new file mode 100644
index 91d64b7..5d0ccc1
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2847,16 +2847,6 @@ _equalPartitionBoundSpec(const Partition
 }
 
 static bool
-_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
-{
-	COMPARE_SCALAR_FIELD(infinite);
-	COMPARE_NODE_FIELD(value);
-	COMPARE_LOCATION_FIELD(location);
-
-	return true;
-}
-
-static bool
 _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 {
 	COMPARE_NODE_FIELD(name);
@@ -3669,9 +3659,6 @@ equal(const void *a, const void *b)
 		case T_PartitionBoundSpec:
 			retval = _equalPartitionBoundSpec(a, b);
 			break;
-		case T_PartitionRangeDatum:
-			retval = _equalPartitionRangeDatum(a, b);
-			break;
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
new file mode 100644
index 97ba25f..12e9d07
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1541,9 +1541,6 @@ exprLocation(const Node *expr)
 		case T_PartitionBoundSpec:
 			loc = ((const PartitionBoundSpec *) expr)->location;
 			break;
-		case T_PartitionRangeDatum:
-			loc = ((const PartitionRangeDatum *) expr)->location;
-			break;
 		default:
 			/* for any other node type it's just unknown... */
 			loc = -1;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index 3a23f0b..52332a7
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3550,16 +3550,6 @@ _outPartitionBoundSpec(StringInfo str, c
 	WRITE_LOCATION_FIELD(location);
 }
 
-static void
-_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
-{
-	WRITE_NODE_TYPE("PARTITIONRANGEDATUM");
-
-	WRITE_BOOL_FIELD(infinite);
-	WRITE_NODE_FIELD(value);
-	WRITE_LOCATION_FIELD(location);
-}
-
 /*
  * outNode -
  *	  converts a Node into ascii string and append it to 'str'
@@ -4196,9 +4186,6 @@ outNode(StringInfo str, const void *obj)
 			case T_PartitionBoundSpec:
 				_outPartitionBoundSpec(str, obj);
 				break;
-			case T_PartitionRangeDatum:
-				_outPartitionRangeDatum(str, obj);
-				break;
 
 			default:
 
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
new file mode 100644
index 2988e8b..005fc6d
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2382,21 +2382,6 @@ _readPartitionBoundSpec(void)
 }
 
 /*
- * _readPartitionRangeDatum
- */
-static PartitionRangeDatum *
-_readPartitionRangeDatum(void)
-{
-	READ_LOCALS(PartitionRangeDatum);
-
-	READ_BOOL_FIELD(infinite);
-	READ_NODE_FIELD(value);
-	READ_LOCATION_FIELD(location);
-
-	READ_DONE();
-}
-
-/*
  * parseNodeString
  *
  * Given a character string representing a node tree, parseNodeString creates
@@ -2638,8 +2623,6 @@ parseNodeString(void)
 		return_value = _readExtensibleNode();
 	else if (MATCH("PARTITIONBOUNDSPEC", 18))
 		return_value = _readPartitionBoundSpec();
-	else if (MATCH("PARTITIONRANGEDATUM", 19))
-		return_value = _readPartitionRangeDatum();
 	else
 	{
 		elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
new file mode 100644
index 0f3998f..0e2e651
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -576,8 +576,8 @@ static Node *makeRecursiveViewSelect(cha
 %type <partelem>	part_elem
 %type <list>		part_params
 %type <partboundspec> ForValues
-%type <node>		partbound_datum PartitionRangeDatum
-%type <list>		partbound_datum_list range_datum_list
+%type <node>		partbound_datum
+%type <list>		partbound_datum_list range_bound
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -2664,13 +2664,13 @@ ForValues:
 				}
 
 			/* a RANGE partition */
-			| FOR VALUES FROM '(' range_datum_list ')' TO '(' range_datum_list ')'
+			| FOR VALUES FROM range_bound TO range_bound
 				{
 					PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
 
 					n->strategy = PARTITION_STRATEGY_RANGE;
-					n->lowerdatums = $5;
-					n->upperdatums = $9;
+					n->lowerdatums = $4;
+					n->upperdatums = $6;
 					n->location = @3;
 
 					$$ = n;
@@ -2689,33 +2689,9 @@ partbound_datum_list:
 												{ $$ = lappend($1, $3); }
 		;
 
-range_datum_list:
-			PartitionRangeDatum					{ $$ = list_make1($1); }
-			| range_datum_list ',' PartitionRangeDatum
-												{ $$ = lappend($1, $3); }
-		;
-
-PartitionRangeDatum:
-			UNBOUNDED
-				{
-					PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
-
-					n->infinite = true;
-					n->value = NULL;
-					n->location = @1;
-
-					$$ = (Node *) n;
-				}
-			| partbound_datum
-				{
-					PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
-
-					n->infinite = false;
-					n->value = $1;
-					n->location = @1;
-
-					$$ = (Node *) n;
-				}
+range_bound:
+			UNBOUNDED							{ $$ = NIL; }
+			| '(' partbound_datum_list ')'		{ $$ = $2; }
 		;
 
 /*****************************************************************************
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
new file mode 100644
index ee5f3a3..252baa6
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3361,11 +3361,9 @@ transformPartitionBound(ParseState *psta
 	}
 	else if (strategy == PARTITION_STRATEGY_RANGE)
 	{
-		ListCell   *cell1,
-				   *cell2;
+		ListCell   *cell;
 		int			i,
 					j;
-		bool		seen_unbounded;
 
 		if (spec->strategy != PARTITION_STRATEGY_RANGE)
 			ereport(ERROR,
@@ -3373,59 +3371,65 @@ transformPartitionBound(ParseState *psta
 					 errmsg("invalid bound specification for a range partition"),
 					 parser_errposition(pstate, exprLocation((Node *) spec))));
 
-		if (list_length(spec->lowerdatums) != partnatts)
+		if (list_length(spec->lowerdatums) > partnatts)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-					 errmsg("FROM must specify exactly one value per partitioning column")));
-		if (list_length(spec->upperdatums) != partnatts)
+					 errmsg("too many values in FROM list")));
+
+		if (list_length(spec->upperdatums) > partnatts)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-					 errmsg("TO must specify exactly one value per partitioning column")));
+					 errmsg("too many values in TO list")));
 
-		/*
-		 * Check that no finite value follows an UNBOUNDED item in either of
-		 * lower and upper bound lists.
-		 */
-		seen_unbounded = false;
-		foreach(cell1, spec->lowerdatums)
+		/* Transform the lower bound values */
+		i = j = 0;
+		result_spec->lowerdatums = NIL;
+		foreach(cell, spec->lowerdatums)
 		{
-			PartitionRangeDatum *ldatum = castNode(PartitionRangeDatum,
-												   lfirst(cell1));
+			A_Const    *con = castNode(A_Const, lfirst(cell));
+			char	   *colname;
+			Oid			coltype;
+			int32		coltypmod;
+			Const	   *value;
 
-			if (ldatum->infinite)
-				seen_unbounded = true;
-			else if (seen_unbounded)
-				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("cannot specify finite value after UNBOUNDED"),
-						 parser_errposition(pstate, exprLocation((Node *) ldatum))));
-		}
-		seen_unbounded = false;
-		foreach(cell1, spec->upperdatums)
-		{
-			PartitionRangeDatum *rdatum = castNode(PartitionRangeDatum,
-												   lfirst(cell1));
+			/* Get the column's name in case we need to output an error */
+			if (key->partattrs[i] != 0)
+				colname = get_relid_attribute_name(RelationGetRelid(parent),
+												   key->partattrs[i]);
+			else
+			{
+				colname = deparse_expression((Node *) list_nth(partexprs, j),
+											 deparse_context_for(RelationGetRelationName(parent),
+																 RelationGetRelid(parent)),
+											 false, false);
+				++j;
+			}
+			/* Need its type data too */
+			coltype = get_partition_col_typid(key, i);
+			coltypmod = get_partition_col_typmod(key, i);
 
-			if (rdatum->infinite)
-				seen_unbounded = true;
-			else if (seen_unbounded)
+			value = transformPartitionBoundValue(pstate, con,
+												 colname,
+												 coltype, coltypmod);
+			if (value->constisnull)
 				ereport(ERROR,
-						(errcode(ERRCODE_DATATYPE_MISMATCH),
-						 errmsg("cannot specify finite value after UNBOUNDED"),
-						 parser_errposition(pstate, exprLocation((Node *) rdatum))));
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("cannot specify NULL in range bound")));
+
+			result_spec->lowerdatums = lappend(result_spec->lowerdatums,
+											   value);
+			++i;
 		}
 
-		/* Transform all the constants */
+		/* Transform the upper bound values */
 		i = j = 0;
-		result_spec->lowerdatums = result_spec->upperdatums = NIL;
-		forboth(cell1, spec->lowerdatums, cell2, spec->upperdatums)
+		result_spec->upperdatums = NIL;
+		foreach(cell, spec->upperdatums)
 		{
-			PartitionRangeDatum *ldatum = (PartitionRangeDatum *) lfirst(cell1);
-			PartitionRangeDatum *rdatum = (PartitionRangeDatum *) lfirst(cell2);
+			A_Const    *con = castNode(A_Const, lfirst(cell));
 			char	   *colname;
 			Oid			coltype;
 			int32		coltypmod;
-			A_Const    *con;
 			Const	   *value;
 
 			/* Get the column's name in case we need to output an error */
@@ -3444,39 +3448,16 @@ transformPartitionBound(ParseState *psta
 			coltype = get_partition_col_typid(key, i);
 			coltypmod = get_partition_col_typmod(key, i);
 
-			if (ldatum->value)
-			{
-				con = castNode(A_Const, ldatum->value);
-				value = transformPartitionBoundValue(pstate, con,
-													 colname,
-													 coltype, coltypmod);
-				if (value->constisnull)
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-							 errmsg("cannot specify NULL in range bound")));
-				ldatum = copyObject(ldatum);	/* don't scribble on input */
-				ldatum->value = (Node *) value;
-			}
-
-			if (rdatum->value)
-			{
-				con = castNode(A_Const, rdatum->value);
-				value = transformPartitionBoundValue(pstate, con,
-													 colname,
-													 coltype, coltypmod);
-				if (value->constisnull)
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-							 errmsg("cannot specify NULL in range bound")));
-				rdatum = copyObject(rdatum);	/* don't scribble on input */
-				rdatum->value = (Node *) value;
-			}
+			value = transformPartitionBoundValue(pstate, con,
+												 colname,
+												 coltype, coltypmod);
+			if (value->constisnull)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+						 errmsg("cannot specify NULL in range bound")));
 
-			result_spec->lowerdatums = lappend(result_spec->lowerdatums,
-											   ldatum);
 			result_spec->upperdatums = lappend(result_spec->upperdatums,
-											   rdatum);
-
+											   value);
 			++i;
 		}
 	}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
new file mode 100644
index 18d9e27..4df1230
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8667,48 +8667,43 @@ get_rule_expr(Node *node, deparse_contex
 						break;
 
 					case PARTITION_STRATEGY_RANGE:
-						Assert(spec->lowerdatums != NIL &&
-							   spec->upperdatums != NIL &&
-							   list_length(spec->lowerdatums) ==
-							   list_length(spec->upperdatums));
-
-						appendStringInfoString(buf, "FOR VALUES FROM (");
-						sep = "";
-						foreach(cell, spec->lowerdatums)
+						/* Bounds may be empty, which means UNBOUNDED */
+						appendStringInfoString(buf, "FOR VALUES FROM ");
+						if (spec->lowerdatums)
 						{
-							PartitionRangeDatum *datum =
-							castNode(PartitionRangeDatum, lfirst(cell));
-
-							appendStringInfoString(buf, sep);
-							if (datum->infinite)
-								appendStringInfoString(buf, "UNBOUNDED");
-							else
+							appendStringInfoString(buf, "(");
+							sep = "";
+							foreach(cell, spec->lowerdatums)
 							{
-								Const	   *val = castNode(Const, datum->value);
+								Const	   *val = castNode(Const, lfirst(cell));
 
+								appendStringInfoString(buf, sep);
 								get_const_expr(val, context, -1);
+								sep = ", ";
 							}
-							sep = ", ";
+							appendStringInfoString(buf, ")");
 						}
-						appendStringInfoString(buf, ") TO (");
-						sep = "";
-						foreach(cell, spec->upperdatums)
-						{
-							PartitionRangeDatum *datum =
-							castNode(PartitionRangeDatum, lfirst(cell));
+						else
+							appendStringInfoString(buf, "UNBOUNDED");
 
-							appendStringInfoString(buf, sep);
-							if (datum->infinite)
-								appendStringInfoString(buf, "UNBOUNDED");
-							else
+						appendStringInfoString(buf, " TO ");
+						if (spec->upperdatums)
+						{
+							appendStringInfoString(buf, "(");
+							sep = "";
+							foreach(cell, spec->upperdatums)
 							{
-								Const	   *val = castNode(Const, datum->value);
+								Const	   *val = castNode(Const, lfirst(cell));
 
+								appendStringInfoString(buf, sep);
 								get_const_expr(val, context, -1);
+								sep = ", ";
 							}
-							sep = ", ";
+							appendStringInfoString(buf, ")");
 						}
-						appendStringInfoString(buf, ")");
+						else
+							appendStringInfoString(buf, "UNBOUNDED");
+
 						break;
 
 					default:
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
new file mode 100644
index 0152739..b800aab
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -466,7 +466,6 @@ typedef enum NodeTag
 	T_PartitionElem,
 	T_PartitionSpec,
 	T_PartitionBoundSpec,
-	T_PartitionRangeDatum,
 	T_PartitionCmd,
 
 	/*
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
new file mode 100644
index 1d96169..30a8ef8
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -802,28 +802,13 @@ typedef struct PartitionBoundSpec
 	List	   *listdatums;		/* List of Consts (or A_Consts in raw tree) */
 
 	/* Partitioning info for RANGE strategy: */
-	List	   *lowerdatums;	/* List of PartitionRangeDatums */
-	List	   *upperdatums;	/* List of PartitionRangeDatums */
+	List	   *lowerdatums;	/* List of Consts (or A_Consts in raw tree) */
+	List	   *upperdatums;	/* List of Consts (or A_Consts in raw tree) */
 
 	int			location;		/* token location, or -1 if unknown */
 } PartitionBoundSpec;
 
 /*
- * PartitionRangeDatum - can be either a value or UNBOUNDED
- *
- * "value" is an A_Const in raw grammar output, a Const after analysis
- */
-typedef struct PartitionRangeDatum
-{
-	NodeTag		type;
-
-	bool		infinite;		/* true if UNBOUNDED */
-	Node	   *value;			/* null if UNBOUNDED */
-
-	int			location;		/* token location, or -1 if unknown */
-} PartitionRangeDatum;
-
-/*
  * PartitionCmd - info for ALTER TABLE ATTACH/DETACH PARTITION commands
  */
 typedef struct PartitionCmd
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
new file mode 100644
index fb8745b..f24fa58
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -505,22 +505,19 @@ CREATE TABLE fail_part PARTITION OF rang
 ERROR:  invalid bound specification for a range partition
 LINE 1: ...BLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
                                                               ^
--- each of start and end bounds must have same number of values as the
--- length of the partition key
+-- start and end bounds cannot have more values than the length of the partition key
 CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
-ERROR:  FROM must specify exactly one value per partitioning column
+ERROR:  too many values in FROM list
 CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
-ERROR:  TO must specify exactly one value per partitioning column
+ERROR:  too many values in TO list
 -- cannot specify null values in range bounds
-CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO ('z');
 ERROR:  cannot specify NULL in range bound
--- cannot specify finite values after UNBOUNDED has been specified
-CREATE TABLE range_parted_multicol (a int, b int, c int) PARTITION BY RANGE (a, b, c);
-CREATE TABLE fail_part PARTITION OF range_parted_multicol FOR VALUES FROM (1, UNBOUNDED, 1) TO (UNBOUNDED, 1, 1);
-ERROR:  cannot specify finite value after UNBOUNDED
-LINE 1: ...ge_parted_multicol FOR VALUES FROM (1, UNBOUNDED, 1) TO (UNB...
+-- cannot specify UNBOUNDED values in range bounds
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (UNBOUNDED) TO ('z');
+ERROR:  syntax error at or near "UNBOUNDED"
+LINE 1: ...l_part PARTITION OF range_parted FOR VALUES FROM (UNBOUNDED)...
                                                              ^
-DROP TABLE range_parted_multicol;
 -- check if compatible with the specified parent
 -- cannot create as partition of a non-partitioned table
 CREATE TABLE unparted (
@@ -578,35 +575,35 @@ ERROR:  cannot create range partition wi
 -- note that the range '[1, 1)' has no elements
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
 ERROR:  cannot create range partition with empty range
-CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
-CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM unbounded TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM unbounded TO (2);
 ERROR:  partition "fail_part" would overlap partition "part0"
 CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
-CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO unbounded;
 ERROR:  partition "fail_part" would overlap partition "part1"
 CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES FROM (20) TO (30);
 CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
 ERROR:  partition "fail_part" would overlap partition "part2"
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
-ERROR:  partition "fail_part" would overlap partition "part3"
+ERROR:  partition "fail_part" would overlap partition "part2"
 -- now check for multi-column range partition key
 CREATE TABLE range_parted3 (
 	a int,
 	b int
 ) PARTITION BY RANGE (a, (b+1));
-CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
-CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0) TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0) TO (0, 1);
 ERROR:  partition "fail_part" would overlap partition "part00"
-CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1) TO (1, 1);
 CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
-CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (2);
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
 ERROR:  partition "fail_part" would overlap partition "part12"
 -- cannot create a partition that says column b is allowed to range
 -- from -infinity to +infinity, while there exist partitions that have
 -- more specific ranges
-CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1) TO (2);
 ERROR:  partition "fail_part" would overlap partition "part10"
 -- check schema propagation from parent
 CREATE TABLE parted (
@@ -708,7 +705,7 @@ Number of partitions: 3 (Use \d+ to list
 
 -- check that we get the expected partition constraints
 CREATE TABLE range_parted4 (a int, b int, c int) PARTITION BY RANGE (abs(a), abs(b), c);
-CREATE TABLE unbounded_range_part PARTITION OF range_parted4 FOR VALUES FROM (UNBOUNDED, UNBOUNDED, UNBOUNDED) TO (UNBOUNDED, UNBOUNDED, UNBOUNDED);
+CREATE TABLE unbounded_range_part PARTITION OF range_parted4 FOR VALUES FROM UNBOUNDED TO UNBOUNDED;
 \d+ unbounded_range_part
                            Table "public.unbounded_range_part"
  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
@@ -716,11 +713,11 @@ CREATE TABLE unbounded_range_part PARTIT
  a      | integer |           |          |         | plain   |              | 
  b      | integer |           |          |         | plain   |              | 
  c      | integer |           |          |         | plain   |              | 
-Partition of: range_parted4 FOR VALUES FROM (UNBOUNDED, UNBOUNDED, UNBOUNDED) TO (UNBOUNDED, UNBOUNDED, UNBOUNDED)
+Partition of: range_parted4 FOR VALUES FROM UNBOUNDED TO UNBOUNDED
 Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL))
 
 DROP TABLE unbounded_range_part;
-CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (UNBOUNDED, UNBOUNDED, UNBOUNDED) TO (1, UNBOUNDED, UNBOUNDED);
+CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM UNBOUNDED TO (2);
 \d+ range_parted4_1
                               Table "public.range_parted4_1"
  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
@@ -728,10 +725,10 @@ CREATE TABLE range_parted4_1 PARTITION O
  a      | integer |           |          |         | plain   |              | 
  b      | integer |           |          |         | plain   |              | 
  c      | integer |           |          |         | plain   |              | 
-Partition of: range_parted4 FOR VALUES FROM (UNBOUNDED, UNBOUNDED, UNBOUNDED) TO (1, UNBOUNDED, UNBOUNDED)
-Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND (abs(a) <= 1))
+Partition of: range_parted4 FOR VALUES FROM UNBOUNDED TO (2)
+Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND (abs(a) < 2))
 
-CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, UNBOUNDED);
+CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 8);
 \d+ range_parted4_2
                               Table "public.range_parted4_2"
  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
@@ -739,10 +736,10 @@ CREATE TABLE range_parted4_2 PARTITION O
  a      | integer |           |          |         | plain   |              | 
  b      | integer |           |          |         | plain   |              | 
  c      | integer |           |          |         | plain   |              | 
-Partition of: range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, UNBOUNDED)
-Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 3) OR ((abs(a) = 3) AND (abs(b) > 4)) OR ((abs(a) = 3) AND (abs(b) = 4) AND (c >= 5))) AND ((abs(a) < 6) OR ((abs(a) = 6) AND (abs(b) <= 7))))
+Partition of: range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 8)
+Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 3) OR ((abs(a) = 3) AND (abs(b) > 4)) OR ((abs(a) = 3) AND (abs(b) = 4) AND (c >= 5))) AND ((abs(a) < 6) OR ((abs(a) = 6) AND (abs(b) < 8))))
 
-CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, UNBOUNDED) TO (9, UNBOUNDED, UNBOUNDED);
+CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8) TO (10);
 \d+ range_parted4_3
                               Table "public.range_parted4_3"
  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
@@ -750,8 +747,8 @@ CREATE TABLE range_parted4_3 PARTITION O
  a      | integer |           |          |         | plain   |              | 
  b      | integer |           |          |         | plain   |              | 
  c      | integer |           |          |         | plain   |              | 
-Partition of: range_parted4 FOR VALUES FROM (6, 8, UNBOUNDED) TO (9, UNBOUNDED, UNBOUNDED)
-Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 6) OR ((abs(a) = 6) AND (abs(b) >= 8))) AND (abs(a) <= 9))
+Partition of: range_parted4 FOR VALUES FROM (6, 8) TO (10)
+Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 6) OR ((abs(a) = 6) AND (abs(b) >= 8))) AND (abs(a) < 10))
 
 DROP TABLE range_parted4;
 -- cleanup
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
new file mode 100644
index 35d182d..b563ab0
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1718,7 +1718,7 @@ create table part_10_20_cd partition of
 create table part_21_30 partition of range_list_parted for values from (21) to (30) partition by list (b);
 create table part_21_30_ab partition of part_21_30 for values in ('ab');
 create table part_21_30_cd partition of part_21_30 for values in ('cd');
-create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf partition of range_list_parted for values from (40) to unbounded partition by list (b);
 create table part_40_inf_ab partition of part_40_inf for values in ('ab');
 create table part_40_inf_cd partition of part_40_inf for values in ('cd');
 create table part_40_inf_null partition of part_40_inf for values in (null);
@@ -1831,12 +1831,12 @@ drop table range_list_parted;
 -- check that constraint exclusion is able to cope with the partition
 -- constraint emitted for multi-column range partitioned tables
 create table mcrparted (a int, b int, c int) partition by range (a, abs(b), c);
-create table mcrparted0 partition of mcrparted for values from (unbounded, unbounded, unbounded) to (1, 1, 1);
+create table mcrparted0 partition of mcrparted for values from unbounded to (1, 1, 1);
 create table mcrparted1 partition of mcrparted for values from (1, 1, 1) to (10, 5, 10);
 create table mcrparted2 partition of mcrparted for values from (10, 5, 10) to (10, 10, 10);
 create table mcrparted3 partition of mcrparted for values from (11, 1, 1) to (20, 10, 10);
 create table mcrparted4 partition of mcrparted for values from (20, 10, 10) to (20, 20, 20);
-create table mcrparted5 partition of mcrparted for values from (20, 20, 20) to (unbounded, unbounded, unbounded);
+create table mcrparted5 partition of mcrparted for values from (20, 20, 20) to unbounded;
 explain (costs off) select * from mcrparted where a = 0;	-- scans mcrparted0
           QUERY PLAN          
 ------------------------------
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
new file mode 100644
index d1153f4..2718213
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -288,7 +288,7 @@ select tableoid::regclass, * from list_p
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
-create table part_gg1 partition of part_gg for values from (unbounded) to (1);
+create table part_gg1 partition of part_gg for values from unbounded to (1);
 create table part_gg2 partition of part_gg for values from (1) to (10) partition by range (b);
 create table part_gg2_1 partition of part_gg2 for values from (1) to (5);
 create table part_gg2_2 partition of part_gg2 for values from (5) to (10);
@@ -439,12 +439,12 @@ drop table key_desc, key_desc_1;
 -- check multi-column range partitioning expression enforces the same
 -- constraint as what tuple-routing would determine it to be
 create table mcrparted (a int, b int, c int) partition by range (a, abs(b), c);
-create table mcrparted0 partition of mcrparted for values from (unbounded, unbounded, unbounded) to (1, unbounded, unbounded);
-create table mcrparted1 partition of mcrparted for values from (2, 1, unbounded) to (10, 5, 10);
-create table mcrparted2 partition of mcrparted for values from (10, 6, unbounded) to (10, unbounded, unbounded);
+create table mcrparted0 partition of mcrparted for values from unbounded to (2);
+create table mcrparted1 partition of mcrparted for values from (2, 1) to (10, 5, 10);
+create table mcrparted2 partition of mcrparted for values from (10, 6) to (11);
 create table mcrparted3 partition of mcrparted for values from (11, 1, 1) to (20, 10, 10);
-create table mcrparted4 partition of mcrparted for values from (21, unbounded, unbounded) to (30, 20, unbounded);
-create table mcrparted5 partition of mcrparted for values from (30, 21, 20) to (unbounded, unbounded, unbounded);
+create table mcrparted4 partition of mcrparted for values from (21) to (30, 21);
+create table mcrparted5 partition of mcrparted for values from (30, 21, 20) to unbounded;
 -- routed to mcrparted0
 insert into mcrparted values (0, 1, 1);
 insert into mcrparted0 values (0, 1, 1);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
new file mode 100644
index cb7aa5b..89ce126
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -477,18 +477,15 @@ CREATE TABLE range_parted (
 
 -- trying to specify list for range partitioned table
 CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES IN ('a');
--- each of start and end bounds must have same number of values as the
--- length of the partition key
+-- start and end bounds cannot have more values than the length of the partition key
 CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('z');
 CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM ('a') TO ('z', 1);
 
 -- cannot specify null values in range bounds
-CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO ('z');
 
--- cannot specify finite values after UNBOUNDED has been specified
-CREATE TABLE range_parted_multicol (a int, b int, c int) PARTITION BY RANGE (a, b, c);
-CREATE TABLE fail_part PARTITION OF range_parted_multicol FOR VALUES FROM (1, UNBOUNDED, 1) TO (UNBOUNDED, 1, 1);
-DROP TABLE range_parted_multicol;
+-- cannot specify UNBOUNDED values in range bounds
+CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (UNBOUNDED) TO ('z');
 
 -- check if compatible with the specified parent
 
@@ -542,10 +539,10 @@ CREATE TABLE fail_part PARTITION OF rang
 -- note that the range '[1, 1)' has no elements
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (1) TO (1);
 
-CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (1);
-CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (unbounded) TO (2);
+CREATE TABLE part0 PARTITION OF range_parted2 FOR VALUES FROM unbounded TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM unbounded TO (2);
 CREATE TABLE part1 PARTITION OF range_parted2 FOR VALUES FROM (1) TO (10);
-CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO (unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (9) TO unbounded;
 CREATE TABLE part2 PARTITION OF range_parted2 FOR VALUES FROM (20) TO (30);
 CREATE TABLE part3 PARTITION OF range_parted2 FOR VALUES FROM (30) TO (40);
 CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
@@ -557,18 +554,18 @@ CREATE TABLE range_parted3 (
 	b int
 ) PARTITION BY RANGE (a, (b+1));
 
-CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, unbounded);
-CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, unbounded) TO (0, 1);
+CREATE TABLE part00 PARTITION OF range_parted3 FOR VALUES FROM (0) TO (1);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0) TO (0, 1);
 
-CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, 1);
+CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1) TO (1, 1);
 CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
-CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, unbounded);
+CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (2);
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
 
 -- cannot create a partition that says column b is allowed to range
 -- from -infinity to +infinity, while there exist partitions that have
 -- more specific ranges
-CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO (1, unbounded);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1) TO (2);
 
 -- check schema propagation from parent
 
@@ -626,14 +623,14 @@ CREATE TABLE part_c_1_10 PARTITION OF pa
 
 -- check that we get the expected partition constraints
 CREATE TABLE range_parted4 (a int, b int, c int) PARTITION BY RANGE (abs(a), abs(b), c);
-CREATE TABLE unbounded_range_part PARTITION OF range_parted4 FOR VALUES FROM (UNBOUNDED, UNBOUNDED, UNBOUNDED) TO (UNBOUNDED, UNBOUNDED, UNBOUNDED);
+CREATE TABLE unbounded_range_part PARTITION OF range_parted4 FOR VALUES FROM UNBOUNDED TO UNBOUNDED;
 \d+ unbounded_range_part
 DROP TABLE unbounded_range_part;
-CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (UNBOUNDED, UNBOUNDED, UNBOUNDED) TO (1, UNBOUNDED, UNBOUNDED);
+CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM UNBOUNDED TO (2);
 \d+ range_parted4_1
-CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, UNBOUNDED);
+CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 8);
 \d+ range_parted4_2
-CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, UNBOUNDED) TO (9, UNBOUNDED, UNBOUNDED);
+CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8) TO (10);
 \d+ range_parted4_3
 DROP TABLE range_parted4;
 
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
new file mode 100644
index 70fe971..9149bda
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -623,7 +623,7 @@ create table part_10_20_cd partition of
 create table part_21_30 partition of range_list_parted for values from (21) to (30) partition by list (b);
 create table part_21_30_ab partition of part_21_30 for values in ('ab');
 create table part_21_30_cd partition of part_21_30 for values in ('cd');
-create table part_40_inf partition of range_list_parted for values from (40) to (unbounded) partition by list (b);
+create table part_40_inf partition of range_list_parted for values from (40) to unbounded partition by list (b);
 create table part_40_inf_ab partition of part_40_inf for values in ('ab');
 create table part_40_inf_cd partition of part_40_inf for values in ('cd');
 create table part_40_inf_null partition of part_40_inf for values in (null);
@@ -647,12 +647,12 @@ drop table range_list_parted;
 -- check that constraint exclusion is able to cope with the partition
 -- constraint emitted for multi-column range partitioned tables
 create table mcrparted (a int, b int, c int) partition by range (a, abs(b), c);
-create table mcrparted0 partition of mcrparted for values from (unbounded, unbounded, unbounded) to (1, 1, 1);
+create table mcrparted0 partition of mcrparted for values from unbounded to (1, 1, 1);
 create table mcrparted1 partition of mcrparted for values from (1, 1, 1) to (10, 5, 10);
 create table mcrparted2 partition of mcrparted for values from (10, 5, 10) to (10, 10, 10);
 create table mcrparted3 partition of mcrparted for values from (11, 1, 1) to (20, 10, 10);
 create table mcrparted4 partition of mcrparted for values from (20, 10, 10) to (20, 20, 20);
-create table mcrparted5 partition of mcrparted for values from (20, 20, 20) to (unbounded, unbounded, unbounded);
+create table mcrparted5 partition of mcrparted for values from (20, 20, 20) to unbounded;
 explain (costs off) select * from mcrparted where a = 0;	-- scans mcrparted0
 explain (costs off) select * from mcrparted where a = 10 and abs(b) < 5;	-- scans mcrparted1
 explain (costs off) select * from mcrparted where a = 10 and abs(b) = 5;	-- scans mcrparted1, mcrparted2
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
new file mode 100644
index 83c3ad8..2b8637d
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -169,7 +169,7 @@ select tableoid::regclass, * from list_p
 
 -- some more tests to exercise tuple-routing with multi-level partitioning
 create table part_gg partition of list_parted for values in ('gg') partition by range (b);
-create table part_gg1 partition of part_gg for values from (unbounded) to (1);
+create table part_gg1 partition of part_gg for values from unbounded to (1);
 create table part_gg2 partition of part_gg for values from (1) to (10) partition by range (b);
 create table part_gg2_1 partition of part_gg2 for values from (1) to (5);
 create table part_gg2_2 partition of part_gg2 for values from (5) to (10);
@@ -293,12 +293,12 @@ drop table key_desc, key_desc_1;
 -- check multi-column range partitioning expression enforces the same
 -- constraint as what tuple-routing would determine it to be
 create table mcrparted (a int, b int, c int) partition by range (a, abs(b), c);
-create table mcrparted0 partition of mcrparted for values from (unbounded, unbounded, unbounded) to (1, unbounded, unbounded);
-create table mcrparted1 partition of mcrparted for values from (2, 1, unbounded) to (10, 5, 10);
-create table mcrparted2 partition of mcrparted for values from (10, 6, unbounded) to (10, unbounded, unbounded);
+create table mcrparted0 partition of mcrparted for values from unbounded to (2);
+create table mcrparted1 partition of mcrparted for values from (2, 1) to (10, 5, 10);
+create table mcrparted2 partition of mcrparted for values from (10, 6) to (11);
 create table mcrparted3 partition of mcrparted for values from (11, 1, 1) to (20, 10, 10);
-create table mcrparted4 partition of mcrparted for values from (21, unbounded, unbounded) to (30, 20, unbounded);
-create table mcrparted5 partition of mcrparted for values from (30, 21, 20) to (unbounded, unbounded, unbounded);
+create table mcrparted4 partition of mcrparted for values from (21) to (30, 21);
+create table mcrparted5 partition of mcrparted for values from (30, 21, 20) to unbounded;
 
 -- routed to mcrparted0
 insert into mcrparted values (0, 1, 1);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
new file mode 100644
index 23a4bbd..1bb6a8a
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1561,7 +1561,6 @@ PartitionElem
 PartitionKey
 PartitionListValue
 PartitionRangeBound
-PartitionRangeDatum
 PartitionSpec
 PartitionedChildRelInfo
 PasswordType
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to