On 2017/06/23 17:00, Dean Rasheed wrote:
> On 23 June 2017 at 08:01, Ashutosh Bapat
> <ashutosh.ba...@enterprisedb.com> wrote:
>> The way we have designed our syntax, we don't have a way to tell that
>> p3 comes after p2 and they have no gap between those. But I don't
>> think that's your question. What you are struggling with is a way to
>> specify a lower bound (10, +infinity) so that anything with i1 > 10
>> would go to partition 3.
> 
> I think actually there is a fundamental problem here, which arises
> because UNBOUNDED has 2 different meanings depending on context, and
> thus it is not possible in general to specify the start of one range
> to be equal to the end of the previous range, as is necessary to get
> contiguous non-overlapping ranges.

Okay, I thought about this a bit more and I think I realize that this
arbitrary-sounding restriction of allowing only -infinity in FROM and
+infinity in TO limits the usefulness of the feature to specify infinite
bounds at all.

> Note that this isn't just a problem for floating point datatypes
> either, it also applies to other types such as strings. For example,
> given a partition over (text, int) types defined with the following
> values:
> 
>   FROM ('a', UNBOUNDED) TO ('b', UNBOUNDED)
> 
> which is equivalent to
> 
>   FROM ('a', -INFINITY) TO ('b', +INFINITY)
> 
> where should the next range start?
> 
> Even if you were to find a way to specify "the next string after 'b'",
> it wouldn't exactly be pretty. The problem is that the above partition
> corresponds to "all the strings starting with 'a', plus the string
> 'b', which is pretty ugly. A neater way to define the pair of ranges
> in this case would be:
> 
>   FROM ('a', -INFINITY) TO ('b', -INFINITY)
>   FROM ('b', -INFINITY) TO ('c', -INFINITY)
> 
> since then all strings starting with 'a' would fall into the first
> partition and all the strings starting with 'b' would fall into the
> second one.

I agree that a valid use case like the one above is awkward to express
currently.

> Currently, when there are 2 partition columns, the partition
> constraint is defined as
> 
>   (a is not null) and (b is not null)
>   and
>   (a > al or (a = al and b >= bl))
>   and
>   (a < au or (a = au and b < bu))
> 
> if the upper bound bu were allowed to be -INFINITY (something that
> should probably be forbidden unless the previous column's upper bound
> were finite), then this would simplify to
> 
>   (a is not null) and (b is not null)
>   and
>   (a > al or (a = al and b >= bl))
>   and
>   (a < au)
> 
> and in the example above, where al is -INFINITY, it would further simplify to
> 
>   (a is not null) and (b is not null)
>   and
>   (a >= al)
>   and
>   (a < au)
> 
> There would also be a similar simplification possible if the lower
> bound of a partition column were allowed to be +INFINITY.

Yep.

> So, I think that having UNBOUNDED represent both -INFINITY and
> +INFINITY depending on context is a design flaw, and that we need to
> allow both -INFINITY and +INFINITY as upper and lower bounds (provided
> they are preceded by a column with a finite bound). I think that, in
> general, that's the only way to allow contiguous non-overlapping
> partitions to be defined on multiple columns.

Alright, I spent some time implementing a patch to allow specifying
-infinity and +infinity in arbitrary ways.  Of course, it prevents
nonsensical inputs with appropriate error messages.

When implementing the same, I initially thought that the only grammar
modification required is to allow specifying a sign before the unbounded
keyword, but thought it sounded strange to call the actual bound values
-unbounded and +unbounded.  While the keyword "unbounded" describes the
property of being unbounded, actual values are really -infinity and
+infinity.  So, I decided to instead modify the grammar to accept
-infinity and +infinity in the FROM and TO lists.  The sign is optional
and in its absence, infinity in FROM means -infinity and vice versa.  This
decision may be seen as controversial, now that we are actually in beta,
if we decide to go with this patch at all.

Some adjustments were required in the logic in partition.c that depended
on the old assumption that all infinite values in the lower bound meant
-infinity and vice versa.  That includes get_qual_for_range() being able
to simplify the partition constraint as Dean mentioned in his email.

When testing the patch, I realized that the current code in
check_new_partition_bound() that checks for range partition overlap had a
latent bug that resulted in false positives for the new cases that the new
less restrictive syntax allowed.  I spent some time simplifying that code
while also fixing the aforementioned bug.  It's implemented in the
attached patch 0001.

0002 is the patch that implements the new syntax.

It's possible that this won't be considered a PG 10 open item but a new
feature and so PG 11 material, as Ashutosh also wondered.

Thanks,
Amit
From 3487d63069a2ba7cb205ded31be235bcf08fc0fa Mon Sep 17 00:00:00 2001
From: amit <amitlangot...@gmail.com>
Date: Thu, 29 Jun 2017 16:39:07 +0900
Subject: [PATCH 1/2] Simplify code that checks range partition overlap

While making the logic a sightly easier to reason about, a latent
bug in the logic was fixed in this simplification process, whereby
false positives would occur (partitions that don't actually overlap
would be concluded to overlap).
---
 src/backend/catalog/partition.c            | 64 ++++++++++--------------------
 src/test/regress/expected/create_table.out |  2 +-
 2 files changed, 23 insertions(+), 43 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index f8c55b1fe7..457b2fef66 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -753,68 +753,48 @@ check_new_partition_bound(char *relname, Relation parent,
                                                   boundinfo->strategy == 
PARTITION_STRATEGY_RANGE);
 
                                        /*
-                                        * Firstly, find the greatest range 
bound that is less
-                                        * than or equal to the new lower bound.
+                                        * Find the greatest range bound among 
those of the
+                                        * existing partitions that is less 
than or equal to the
+                                        * new lower bound.
                                         */
                                        off1 = 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 there is a "gap" in the range 
immediately on the
+                                        * right of the returned bound (one at 
off1, which the new
+                                        * lower bound is greater than or equal 
to), then the
+                                        * new partition could occupy the same. 
 Only if its upper
+                                        * bound is also confined within the 
gap.
                                         */
-                                       if (!equal && boundinfo->indexes[off1 + 
1] < 0)
+                                       if (boundinfo->indexes[off1 + 1] < 0)
                                        {
+                                               equal = false;
                                                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.
+                                                * If the new upper bound turns 
out to be crossing
+                                                * over the available gap, then 
the new partition
+                                                * overlaps with the 
partition(s) on the right.
+                                                * However, because a 
partition's upper bound can be
+                                                * equal to the lower bound of 
the partition
+                                                * immediately on the the 
right, discount that case.
                                                 */
-                                               if (equal || off1 != off2)
+                                               if (off2 > off1 + 1 || ((off2 
== off1 + 1) && !equal))
                                                {
                                                        overlap = true;
 
                                                        /*
-                                                        * 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.
+                                                        * Although the new 
upper bound might have crossed
+                                                        * over multiple 
partitions on the right, we only
+                                                        * ever report the 
immediately adjacent one.
                                                         */
-                                                       if 
(boundinfo->indexes[off2] < 0)
-                                                               with = 
boundinfo->indexes[off2 + 1];
-                                                       else
-                                                               with = 
boundinfo->indexes[off2];
+                                                       with = 
boundinfo->indexes[off1 + 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).
-                                                */
+                                               /* There is no gap. */
                                                overlap = true;
                                                with = boundinfo->indexes[off1 
+ 1];
                                        }
diff --git a/src/test/regress/expected/create_table.out 
b/src/test/regress/expected/create_table.out
index fb8745be04..b6f794e1c2 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -589,7 +589,7 @@ 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,
-- 
2.11.0

From 52574b20e53e5da94ffe256ae30b0bb19503659a Mon Sep 17 00:00:00 2001
From: amit <amitlangot...@gmail.com>
Date: Thu, 29 Jun 2017 13:50:27 +0900
Subject: [PATCH 2/2] Relax some rules about unbounded range partition bounds

Currently, unbounded means - or + infinity depending on whether
it appears in FROM or TO, respectively.  That rule limits the
usefulness of being able to specify unboundedness for range partition
columns at all.  For example, in case of multi-column partition key,
one cannot specify the same bound value as both a partition's upper
bound and the next partition's lower bound to establish them as
contiguous non-overlapping partitions, if there is unbounded value
in the suffix of the bound tuple, because such a specification
would be considered to be overlapping with the current rule.

Adjust syntax to allow an explicit sign to be specified for unbounded
values.  Also, replace the 'unbounded' keyword with 'infinity', because
that's what seems to make sense with a sign.

Adjust the logic in get_qual_for_range() to consider the possibility
that an infinity in a lower bound could really be +infinity and one
in an upper bound could be -infinity.

Add tests for the new syntax, tuple-routing, and partition constraint
checking for the new use cases allowed by the relaxed syntax.
---
 doc/src/sgml/ref/create_table.sgml         | 16 +++---
 src/backend/catalog/partition.c            | 74 +++++++++++++++++++---------
 src/backend/parser/gram.y                  | 36 ++++++++------
 src/backend/parser/parse_utilcmd.c         | 42 +++++++++++-----
 src/backend/utils/adt/ruleutils.c          | 22 ++++++++-
 src/include/nodes/parsenodes.h             |  8 +--
 src/include/parser/kwlist.h                |  1 +
 src/test/regress/expected/create_table.out | 79 +++++++++++++++++++++---------
 src/test/regress/expected/inherit.out      |  6 +--
 src/test/regress/expected/insert.out       | 61 ++++++++++++++++++++---
 src/test/regress/sql/create_table.sql      | 50 +++++++++++++------
 src/test/regress/sql/inherit.sql           |  6 +--
 src/test/regress/sql/insert.sql            | 34 ++++++++++---
 13 files changed, 315 insertions(+), 120 deletions(-)

diff --git a/doc/src/sgml/ref/create_table.sgml 
b/doc/src/sgml/ref/create_table.sgml
index b15c19d3d0..d146291d9a 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -87,8 +87,8 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] 
TABLE [ IF NOT EXI
 <phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> 
is:</phrase>
 
 IN ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | 
<replaceable class="PARAMETER">string_literal</replaceable> | NULL } [, ...] ) |
-FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | 
<replaceable class="PARAMETER">string_literal</replaceable> | UNBOUNDED } [, 
...] )
-  TO ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | 
<replaceable class="PARAMETER">string_literal</replaceable> | UNBOUNDED } [, 
...] )
+FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | 
<replaceable class="PARAMETER">string_literal</replaceable> | [ + | - ] 
infinity } [, ...] )
+  TO ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | 
<replaceable class="PARAMETER">string_literal</replaceable> | [ + | - ] 
infinity } [, ...] )
 
 <phrase><replaceable class="PARAMETER">index_parameters</replaceable> in 
<literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and 
<literal>EXCLUDE</literal> constraints are:</phrase>
 
@@ -300,13 +300,11 @@ FROM ( { <replaceable 
class="PARAMETER">numeric_literal</replaceable> | <replace
      </para>
 
      <para>
-      Writing <literal>UNBOUNDED</literal> in <literal>FROM</literal>
-      signifies <literal>-infinity</literal> as the lower bound of the
-      corresponding column, whereas when written in <literal>TO</literal>,
-      it signifies <literal>+infinity</literal> as the upper bound.
-      All items following an <literal>UNBOUNDED</literal> item within
-      a <literal>FROM</literal> or <literal>TO</literal> list must also
-      be <literal>UNBOUNDED</literal>.
+      In the absence of an explicit sign, infinity specified in the
+      <literal>FROM</literal> list signifies -infinity, whereas +infinity
+      if specified in the <literal>TO</literal>.  Note that one cannot
+      specify a finite value after specifying (+/-) infinity in either
+      the <literal>FROM</literal> or the <literal>TO</literal> list.
      </para>
 
      <para>
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 457b2fef66..0e2b60e5a8 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -377,15 +377,13 @@ RelationBuildPartitionDesc(Relation rel)
                                        }
 
                                        /*
-                                        * 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 either of them has infinite 
element, we can't invoke
+                                        * the comparison procedure.
                                         */
                                        if (cur->content[j] != 
RANGE_DATUM_FINITE ||
                                                prev->content[j] != 
RANGE_DATUM_FINITE)
                                        {
-                                               is_distinct = true;
+                                               is_distinct = (cur->content[j] 
!= prev->content[j]);
                                                break;
                                        }
                                        cmpval = 
FunctionCall2Coll(&key->partsupfunc[j],
@@ -1392,7 +1390,7 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec 
*spec)
  *
  * 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.
+ * *upper_val).  For infinite limits, NULL is returned instead of a Const.
  * All of these structures are freshly palloc'd.
  *
  * *partexprs_item points to the cell containing the next expression in
@@ -1464,17 +1462,21 @@ get_range_key_properties(PartitionKey key, int keynum,
  *             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:
+ * If cl happens to be -infinity, we need not emit any expression for it, so
+ * the AND sub-expression corresponding to the lower bound would be:
  *
- *     (b < bu) OR (b = bu), which is simplified to (b <= bu)
+ *     (b > bl) OR (b = bl), which is simplified to (b >= bl)
  *
- * 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
+ * But if cl were to be +infinity (because the range partition bound syntax
+ * allows that), no row with b == bl would qualify for this partition, because
+ * c >= +infinity will never be true.  So, the expression would simply be:
+ * b > bl.  Similarly, if cu happens to be -infinity, the AND sub-expression
+ * for the upper bound would be b < bu, because no row with b = bu would
+ * qualify for this partition.
  *
- * 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.
+ * 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,
+ * provided both al and au are finite values.
  *
  * 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.
@@ -1658,15 +1660,24 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec 
*spec)
                        if (need_next_lower_arm && lower_val)
                        {
                                uint16          strategy;
+                               DefElem    *inf;
+                               bool            next_is_neg_inf = false;
+
+                               if (ldatum_next && ldatum_next->infinite)
+                               {
+                                       inf = castNode(DefElem, 
ldatum_next->value);
+                                       if (strcmp(inf->defname, 
"negative_infinity") == 0)
+                                               next_is_neg_inf = true;
+                               }
 
                                /*
                                 * 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 or the last finite-valued 
column (provided the
+                                * next infinite column is actually -infinity), 
use GE.
                                 */
                                if (j - i < current_or_arm)
                                        strategy = BTEqualStrategyNumber;
-                               else if ((ldatum_next && ldatum_next->infinite) 
||
-                                                j == key->partnatts - 1)
+                               else if (next_is_neg_inf || j == key->partnatts 
- 1)
                                        strategy = BTGreaterEqualStrategyNumber;
                                else
                                        strategy = BTGreaterStrategyNumber;
@@ -1681,14 +1692,24 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec 
*spec)
                        if (need_next_upper_arm && upper_val)
                        {
                                uint16          strategy;
+                               DefElem    *inf;
+                               bool            next_is_pos_inf = false;
+
+                               if (udatum_next && udatum_next->infinite)
+                               {
+                                       inf = castNode(DefElem, 
udatum_next->value);
+                                       if (strcmp(inf->defname, 
"positive_infinity") == 0)
+                                               next_is_pos_inf = true;
+                               }
 
                                /*
                                 * For the non-last columns of this arm, use 
the EQ operator.
-                                * For the last finite-valued column, use LE.
+                                * For the last finite-valued column (provided 
the next
+                                * infinite column is actually +infinity), use 
LE.
                                 */
                                if (j - i < current_or_arm)
                                        strategy = BTEqualStrategyNumber;
-                               else if (udatum_next && udatum_next->infinite)
+                               else if (next_is_pos_inf)
                                        strategy = BTLessEqualStrategyNumber;
                                else
                                        strategy = BTLessStrategyNumber;
@@ -2093,12 +2114,19 @@ make_one_range_bound(PartitionKey key, int index, List 
*datums, bool lower)
        foreach(lc, datums)
        {
                PartitionRangeDatum *datum = castNode(PartitionRangeDatum, 
lfirst(lc));
+               DefElem *inf;
 
                /* What's contained in this range datum? */
-               bound->content[i] = !datum->infinite
-                       ? RANGE_DATUM_FINITE
-                       : (lower ? RANGE_DATUM_NEG_INF
-                          : RANGE_DATUM_POS_INF);
+               if (!datum->infinite)
+                       bound->content[i] = RANGE_DATUM_FINITE;
+               else
+               {
+                       inf = castNode(DefElem, datum->value);
+                       if (strcmp(inf->defname, "negative_infinity") == 0)
+                               bound->content[i] = RANGE_DATUM_NEG_INF;
+                       else
+                               bound->content[i] = RANGE_DATUM_POS_INF;
+               }
 
                if (bound->content[i] == RANGE_DATUM_FINITE)
                {
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0f3998ff89..111f35ce8b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -634,8 +634,8 @@ static Node *makeRecursiveViewSelect(char *relname, List 
*aliases, Node *query);
        HANDLER HAVING HEADER_P HOLD HOUR_P
 
        IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
-       INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
-       INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
+       INCLUDING INCREMENT INDEX INDEXES INFINITY INHERIT INHERITS INITIALLY
+       INLINE_P INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
        INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
        JOIN
@@ -2681,6 +2681,23 @@ partbound_datum:
                        Sconst                  { $$ = makeStringConst($1, @1); 
}
                        | NumericOnly   { $$ = makeAConst($1, @1); }
                        | NULL_P                { $$ = makeNullAConst(@1); }
+                       | '-' INFINITY
+                               {
+                                       $$ = (Node *) 
makeDefElem("negative_infinity", NULL, @1);
+                               }
+                       | '+' INFINITY
+                               {
+                                       $$ = (Node *) 
makeDefElem("positive_infinity", NULL, @1);
+                               }
+                       /*
+                        * If a user skips the sign, whether it's the negative 
or the
+                        * positive infinity is determined during the parse 
analysis
+                        * by seeing which of FROM and TO lists the bound is in.
+                        */
+                       | INFINITY
+                               {
+                                       $$ = (Node *) makeDefElem("infinity", 
NULL, @1);
+                               }
                ;
 
 partbound_datum_list:
@@ -2696,21 +2713,11 @@ range_datum_list:
                ;
 
 PartitionRangeDatum:
-                       UNBOUNDED
-                               {
-                                       PartitionRangeDatum *n = 
makeNode(PartitionRangeDatum);
-
-                                       n->infinite = true;
-                                       n->value = NULL;
-                                       n->location = @1;
-
-                                       $$ = (Node *) n;
-                               }
-                       | partbound_datum
+                       partbound_datum
                                {
                                        PartitionRangeDatum *n = 
makeNode(PartitionRangeDatum);
 
-                                       n->infinite = false;
+                                       n->infinite = IsA($1, DefElem);
                                        n->value = $1;
                                        n->location = @1;
 
@@ -14714,6 +14721,7 @@ unreserved_keyword:
                        | INCREMENT
                        | INDEX
                        | INDEXES
+                       | INFINITY
                        | INHERIT
                        | INHERITS
                        | INLINE_P
diff --git a/src/backend/parser/parse_utilcmd.c 
b/src/backend/parser/parse_utilcmd.c
index ee5f3a3a52..2734677855 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3365,7 +3365,7 @@ transformPartitionBound(ParseState *pstate, Relation 
parent,
                                   *cell2;
                int                     i,
                                        j;
-               bool            seen_unbounded;
+               bool            seen_infinity;
 
                if (spec->strategy != PARTITION_STRATEGY_RANGE)
                        ereport(ERROR,
@@ -3383,35 +3383,51 @@ transformPartitionBound(ParseState *pstate, Relation 
parent,
                                         errmsg("TO must specify exactly one 
value per partitioning column")));
 
                /*
-                * Check that no finite value follows an UNBOUNDED item in 
either of
-                * lower and upper bound lists.
+                * Check that no finite value follows an infinity value in 
either of
+                * lower and upper bound lists.  While at it, convert the 
"infinite"
+                * elements in the lowerdatums list into "negative_infinity" 
and those
+                * in the upperdatums list into "positive_infinity".
                 */
-               seen_unbounded = false;
+               seen_infinity = false;
                foreach(cell1, spec->lowerdatums)
                {
                        PartitionRangeDatum *ldatum = 
castNode(PartitionRangeDatum,
                                                                                
                   lfirst(cell1));
 
                        if (ldatum->infinite)
-                               seen_unbounded = true;
-                       else if (seen_unbounded)
+                       {
+                               DefElem *inf;
+
+                               seen_infinity = true;
+                               inf = castNode(DefElem, ldatum->value);
+                               if (strcmp(inf->defname, "infinity") == 0)
+                                       inf->defname = 
pstrdup("negative_infinity");
+                       }
+                       else if (seen_infinity)
                                ereport(ERROR,
                                                
(errcode(ERRCODE_DATATYPE_MISMATCH),
-                                                errmsg("cannot specify finite 
value after UNBOUNDED"),
+                                                errmsg("cannot specify finite 
value after infinity"),
                                                 parser_errposition(pstate, 
exprLocation((Node *) ldatum))));
                }
-               seen_unbounded = false;
+               seen_infinity = false;
                foreach(cell1, spec->upperdatums)
                {
                        PartitionRangeDatum *rdatum = 
castNode(PartitionRangeDatum,
                                                                                
                   lfirst(cell1));
 
                        if (rdatum->infinite)
-                               seen_unbounded = true;
-                       else if (seen_unbounded)
+                       {
+                               DefElem *inf;
+
+                               seen_infinity = true;
+                               inf = castNode(DefElem, rdatum->value);
+                               if (strcmp(inf->defname, "infinity") == 0)
+                                       inf->defname = 
pstrdup("positive_infinity");
+                       }
+                       else if (seen_infinity)
                                ereport(ERROR,
                                                
(errcode(ERRCODE_DATATYPE_MISMATCH),
-                                                errmsg("cannot specify finite 
value after UNBOUNDED"),
+                                                errmsg("cannot specify finite 
value after infinity"),
                                                 parser_errposition(pstate, 
exprLocation((Node *) rdatum))));
                }
 
@@ -3444,7 +3460,7 @@ transformPartitionBound(ParseState *pstate, Relation 
parent,
                        coltype = get_partition_col_typid(key, i);
                        coltypmod = get_partition_col_typmod(key, i);
 
-                       if (ldatum->value)
+                       if (ldatum->value && !IsA(ldatum->value, DefElem))
                        {
                                con = castNode(A_Const, ldatum->value);
                                value = transformPartitionBoundValue(pstate, 
con,
@@ -3458,7 +3474,7 @@ transformPartitionBound(ParseState *pstate, Relation 
parent,
                                ldatum->value = (Node *) value;
                        }
 
-                       if (rdatum->value)
+                       if (rdatum->value && !IsA(rdatum->value, DefElem))
                        {
                                con = castNode(A_Const, rdatum->value);
                                value = transformPartitionBoundValue(pstate, 
con,
diff --git a/src/backend/utils/adt/ruleutils.c 
b/src/backend/utils/adt/ruleutils.c
index 18d9e27d1e..e093e503b3 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8681,7 +8681,16 @@ get_rule_expr(Node *node, deparse_context *context,
 
                                                        
appendStringInfoString(buf, sep);
                                                        if (datum->infinite)
-                                                               
appendStringInfoString(buf, "UNBOUNDED");
+                                                       {
+                                                               DefElem         
*inf;
+
+                                                               inf = 
castNode(DefElem, datum->value);
+                                                               if 
(strcmp(inf->defname,
+                                                                               
   "negative_infinity") == 0)
+                                                                       
appendStringInfoString(buf, "-infinity");
+                                                               else
+                                                                       
appendStringInfoString(buf, "+infinity");
+                                                       }
                                                        else
                                                        {
                                                                Const      *val 
= castNode(Const, datum->value);
@@ -8699,7 +8708,16 @@ get_rule_expr(Node *node, deparse_context *context,
 
                                                        
appendStringInfoString(buf, sep);
                                                        if (datum->infinite)
-                                                               
appendStringInfoString(buf, "UNBOUNDED");
+                                                       {
+                                                               DefElem         
*inf;
+
+                                                               inf = 
castNode(DefElem, datum->value);
+                                                               if 
(strcmp(inf->defname,
+                                                                               
   "negative_infinity") == 0)
+                                                                       
appendStringInfoString(buf, "-infinity");
+                                                               else
+                                                                       
appendStringInfoString(buf, "+infinity");
+                                                       }
                                                        else
                                                        {
                                                                Const      *val 
= castNode(Const, datum->value);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1d96169d34..5fea805717 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -811,14 +811,16 @@ typedef struct PartitionBoundSpec
 /*
  * PartitionRangeDatum - can be either a value or UNBOUNDED
  *
- * "value" is an A_Const in raw grammar output, a Const after analysis
+ * "value" is an A_Const in raw grammar output, a Const after analysis, if
+ * it represents a finite value.  For (-/+) infinity, it contains a suitably
+ * decorated DefElem instead, both before and after analysis.
  */
 typedef struct PartitionRangeDatum
 {
        NodeTag         type;
 
-       bool            infinite;               /* true if UNBOUNDED */
-       Node       *value;                      /* null if UNBOUNDED */
+       bool            infinite;               /* true if (-/+) infinity */
+       Node       *value;
 
        int                     location;               /* token location, or 
-1 if unknown */
 } PartitionRangeDatum;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f50e45e886..dff0af69bd 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -198,6 +198,7 @@ PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
+PG_KEYWORD("infinity", INFINITY, UNRESERVED_KEYWORD)
 PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD)
 PG_KEYWORD("inherits", INHERITS, UNRESERVED_KEYWORD)
 PG_KEYWORD("initially", INITIALLY, RESERVED_KEYWORD)
diff --git a/src/test/regress/expected/create_table.out 
b/src/test/regress/expected/create_table.out
index b6f794e1c2..ab5665b0c8 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -512,13 +512,13 @@ ERROR:  FROM must specify exactly one value per 
partitioning column
 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
 -- 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 
(infinity);
 ERROR:  cannot specify NULL in range bound
--- cannot specify finite values after UNBOUNDED has been specified
+-- cannot specify finite values after infinity 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...
+CREATE TABLE fail_part PARTITION OF range_parted_multicol FOR VALUES FROM (1, 
infinity, 1) TO (infinity, 1, 1);
+ERROR:  cannot specify finite value after infinity
+LINE 1: ...nge_parted_multicol FOR VALUES FROM (1, infinity, 1) TO (inf...
                                                              ^
 DROP TABLE range_parted_multicol;
 -- check if compatible with the specified parent
@@ -578,11 +578,11 @@ ERROR:  cannot create range partition with empty range
 -- 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 (infinity) TO 
(1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (infinity) 
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 
(infinity);
 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);
@@ -595,19 +595,54 @@ 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, infinity) 
TO (0, infinity);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, 
infinity) 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, infinity) 
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 (1, 
infinity);
 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, 
infinity) TO (1, infinity);
 ERROR:  partition "fail_part" would overlap partition "part10"
+-- check that range partition bound syntax allows -/+ to be specified for
+-- infinity values in arbitrary ways.
+create table multicol_range (a text, b numeric) partition by range (a, b);
+create table multicol_range_1 partition of multicol_range for values from 
('a', infinity) to ('b', -infinity);
+create table multicol_range_2 partition of multicol_range for values from 
('b', infinity) to ('c', -infinity);
+-- Accepts 'd', but not strings lexically greater than 'd'
+create table multicol_range_3 partition of multicol_range for values from 
('c', infinity) to ('d', infinity);
+\d+ multicol_range_3
+                              Table "public.multicol_range_3"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | 
Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a      | text    |           |          |         | extended |              | 
+ b      | numeric |           |          |         | main     |              | 
+Partition of: multicol_range FOR VALUES FROM ('c', -infinity) TO ('d', 
+infinity)
+Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 
'c'::text) AND (a <= 'd'::text))
+
+-- Does not accept 'd'; only strings lexically greater than 'd'
+create table multicol_range_4 partition of multicol_range for values from 
('d', +infinity) to (infinity, infinity);
+\d+ multicol_range_4
+                              Table "public.multicol_range_4"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | 
Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a      | text    |           |          |         | extended |              | 
+ b      | numeric |           |          |         | main     |              | 
+Partition of: multicol_range FOR VALUES FROM ('d', +infinity) TO (+infinity, 
+infinity)
+Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'd'::text))
+
+-- Some combinations would make the resulting range effectivly empty, which
+-- are promptly rejected
+create table multicol_range_err partition of multicol_range for values from 
('e', +infinity) to (-infinity, infinity);
+ERROR:  cannot create range partition with empty range
+create table multicol_range_err partition of multicol_range for values from 
('e', infinity) to ('e', -infinity);
+ERROR:  cannot create range partition with empty range
+create table multicol_range_err partition of multicol_range for values from 
(+infinity, infinity) to (-infinity, infinity);
+ERROR:  cannot create range partition with empty range
 -- check schema propagation from parent
 CREATE TABLE parted (
        a text,
@@ -708,7 +743,7 @@ Number of partitions: 3 (Use \d+ to list them.)
 
 -- 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 
(infinity, infinity, infinity) TO (infinity, infinity, infinity);
 \d+ unbounded_range_part
                            Table "public.unbounded_range_part"
  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | 
Description 
@@ -716,11 +751,11 @@ CREATE TABLE unbounded_range_part PARTITION OF 
range_parted4 FOR VALUES FROM (UN
  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 (-infinity, -infinity, -infinity) 
TO (+infinity, +infinity, +infinity)
 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 
(infinity, infinity, infinity) TO (1, infinity, infinity);
 \d+ range_parted4_1
                               Table "public.range_parted4_1"
  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | 
Description 
@@ -728,10 +763,10 @@ CREATE TABLE range_parted4_1 PARTITION OF range_parted4 
FOR VALUES FROM (UNBOUND
  a      | integer |           |          |         | plain   |              | 
  b      | integer |           |          |         | plain   |              | 
  c      | integer |           |          |         | plain   |              | 
-Partition of: range_parted4 FOR VALUES FROM (UNBOUNDED, UNBOUNDED, UNBOUNDED) 
TO (1, UNBOUNDED, UNBOUNDED)
+Partition of: range_parted4 FOR VALUES FROM (-infinity, -infinity, -infinity) 
TO (1, +infinity, +infinity)
 Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS 
NOT NULL) AND (abs(a) <= 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, 7, infinity);
 \d+ range_parted4_2
                               Table "public.range_parted4_2"
  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | 
Description 
@@ -739,10 +774,10 @@ CREATE TABLE range_parted4_2 PARTITION OF range_parted4 
FOR VALUES FROM (3, 4, 5
  a      | integer |           |          |         | plain   |              | 
  b      | integer |           |          |         | plain   |              | 
  c      | integer |           |          |         | plain   |              | 
-Partition of: range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, UNBOUNDED)
+Partition of: range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, +infinity)
 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))))
 
-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, 
infinity) TO (9, infinity, infinity);
 \d+ range_parted4_3
                               Table "public.range_parted4_3"
  Column |  Type   | Collation | Nullable | Default | Storage | Stats target | 
Description 
@@ -750,12 +785,12 @@ CREATE TABLE range_parted4_3 PARTITION OF range_parted4 
FOR VALUES FROM (6, 8, U
  a      | integer |           |          |         | plain   |              | 
  b      | integer |           |          |         | plain   |              | 
  c      | integer |           |          |         | plain   |              | 
-Partition of: range_parted4 FOR VALUES FROM (6, 8, UNBOUNDED) TO (9, 
UNBOUNDED, UNBOUNDED)
+Partition of: range_parted4 FOR VALUES FROM (6, 8, -infinity) TO (9, 
+infinity, +infinity)
 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))
 
 DROP TABLE range_parted4;
 -- cleanup
-DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, 
range_parted3;
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, 
range_parted3, multicol_range;
 -- comments on partitioned tables columns
 CREATE TABLE parted_col_comment (a int, b text) PARTITION BY LIST (a);
 COMMENT ON TABLE parted_col_comment IS 'Am partitioned table';
diff --git a/src/test/regress/expected/inherit.out 
b/src/test/regress/expected/inherit.out
index 35d182d599..924a24f97b 100644
--- 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 part_10_20 for 
values in ('cd');
 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 (+infinity) 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 (-infinity, 
-infinity, -infinity) 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 
(+infinity, +infinity, +infinity);
 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
index d1153f410b..6047c5b0a6 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -288,7 +288,7 @@ select tableoid::regclass, * from list_parted;
 
 -- 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 (infinity) 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 (infinity, 
infinity, infinity) to (1, infinity, infinity);
+create table mcrparted1 partition of mcrparted for values from (2, 1, 
infinity) to (10, 5, 10);
+create table mcrparted2 partition of mcrparted for values from (10, 6, 
infinity) to (10, infinity, infinity);
 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, infinity, 
infinity) to (30, 20, infinity);
+create table mcrparted5 partition of mcrparted for values from (30, 21, 20) to 
(infinity, infinity, infinity);
 -- routed to mcrparted0
 insert into mcrparted values (0, 1, 1);
 insert into mcrparted0 values (0, 1, 1);
@@ -508,3 +508,52 @@ ERROR:  new row for relation "brtrigpartcon1" violates 
partition constraint
 DETAIL:  Failing row contains (2, hi there).
 drop table brtrigpartcon;
 drop function brtrigpartcon1trigf();
+-- check that tuple-routing works and partition constraint are enforced
+-- properly when infinity values are specified with arbitrary signs in
+-- FROM and TO of range partition bounds.
+create table blogs (a text, b numeric) partition by range (a, b);
+create table blogs_a partition of blogs for values from ('a', infinity) to 
('b', -infinity);
+create table blogs_b partition of blogs for values from ('b', infinity) to 
('c', -infinity);
+-- Note that this one admits just the letter 'd'
+create table blogs_c_and_d partition of blogs for values from ('c', infinity) 
to ('d', infinity);
+-- This one allows strings lexically greater than letter 'd'
+create table blogs_d_to_z partition of blogs for values from ('d', +infinity) 
to (infinity, infinity);
+\d+ blogs
+                                   Table "public.blogs"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | 
Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a      | text    |           |          |         | extended |              | 
+ b      | numeric |           |          |         | main     |              | 
+Partition key: RANGE (a, b)
+Partitions: blogs_a FOR VALUES FROM ('a', -infinity) TO ('b', -infinity),
+            blogs_b FOR VALUES FROM ('b', -infinity) TO ('c', -infinity),
+            blogs_c_and_d FOR VALUES FROM ('c', -infinity) TO ('d', +infinity),
+            blogs_d_to_z FOR VALUES FROM ('d', +infinity) TO (+infinity, 
+infinity)
+
+insert into blogs values ('and there it was', 0), ('because thats why', -1), 
('come on', 12345), ('d', 132), ('drag', 908.0), ('zappa the legend', 1);
+select tableoid::regclass::text, * from blogs order by 1;
+   tableoid    |         a         |   b   
+---------------+-------------------+-------
+ blogs_a       | and there it was  |     0
+ blogs_b       | because thats why |    -1
+ blogs_c_and_d | come on           | 12345
+ blogs_c_and_d | d                 |   132
+ blogs_d_to_z  | drag              | 908.0
+ blogs_d_to_z  | zappa the legend  |     1
+(6 rows)
+
+-- indmissible
+insert into blogs_a values ('b', 1);
+ERROR:  new row for relation "blogs_a" violates partition constraint
+DETAIL:  Failing row contains (b, 1).
+insert into blogs_b values ('c', 1);
+ERROR:  new row for relation "blogs_b" violates partition constraint
+DETAIL:  Failing row contains (c, 1).
+insert into blogs_c_and_d values ('dr', 1);
+ERROR:  new row for relation "blogs_c_and_d" violates partition constraint
+DETAIL:  Failing row contains (dr, 1).
+insert into blogs_d_to_z values ('d', 1);
+ERROR:  new row for relation "blogs_d_to_z" violates partition constraint
+DETAIL:  Failing row contains (d, 1).
+-- cleanup
+drop table blogs;
diff --git a/src/test/regress/sql/create_table.sql 
b/src/test/regress/sql/create_table.sql
index cb7aa5bbc6..f0bdc7116c 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -483,11 +483,11 @@ 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 
(infinity);
 
--- cannot specify finite values after UNBOUNDED has been specified
+-- cannot specify finite values after infinity 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);
+CREATE TABLE fail_part PARTITION OF range_parted_multicol FOR VALUES FROM (1, 
infinity, 1) TO (infinity, 1, 1);
 DROP TABLE range_parted_multicol;
 
 -- check if compatible with the specified parent
@@ -542,10 +542,10 @@ CREATE TABLE fail_part PARTITION OF range_parted2 FOR 
VALUES FROM (1) TO (0);
 -- 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 (infinity) TO 
(1);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (infinity) 
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 
(infinity);
 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 +557,36 @@ 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, infinity) 
TO (0, infinity);
+CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (0, 
infinity) 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, infinity) 
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 (1, 
infinity);
 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, 
infinity) TO (1, infinity);
+
+-- check that range partition bound syntax allows -/+ to be specified for
+-- infinity values in arbitrary ways.
+create table multicol_range (a text, b numeric) partition by range (a, b);
+create table multicol_range_1 partition of multicol_range for values from 
('a', infinity) to ('b', -infinity);
+create table multicol_range_2 partition of multicol_range for values from 
('b', infinity) to ('c', -infinity);
+-- Accepts 'd', but not strings lexically greater than 'd'
+create table multicol_range_3 partition of multicol_range for values from 
('c', infinity) to ('d', infinity);
+\d+ multicol_range_3
+-- Does not accept 'd'; only strings lexically greater than 'd'
+create table multicol_range_4 partition of multicol_range for values from 
('d', +infinity) to (infinity, infinity);
+\d+ multicol_range_4
+
+-- Some combinations would make the resulting range effectivly empty, which
+-- are promptly rejected
+create table multicol_range_err partition of multicol_range for values from 
('e', +infinity) to (-infinity, infinity);
+create table multicol_range_err partition of multicol_range for values from 
('e', infinity) to ('e', -infinity);
+create table multicol_range_err partition of multicol_range for values from 
(+infinity, infinity) to (-infinity, infinity);
 
 -- check schema propagation from parent
 
@@ -626,19 +644,19 @@ CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES 
FROM (1) TO (10);
 
 -- 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 
(infinity, infinity, infinity) TO (infinity, infinity, infinity);
 \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 
(infinity, infinity, infinity) TO (1, infinity, infinity);
 \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, 7, infinity);
 \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, 
infinity) TO (9, infinity, infinity);
 \d+ range_parted4_3
 DROP TABLE range_parted4;
 
 -- cleanup
-DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, 
range_parted3;
+DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, 
range_parted3, multicol_range;
 
 -- comments on partitioned tables columns
 CREATE TABLE parted_col_comment (a int, b text) PARTITION BY LIST (a);
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index 70fe971d51..663ecc8190 100644
--- 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 part_10_20 for 
values in ('cd');
 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 (+infinity) 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 (-infinity, 
-infinity, -infinity) 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 
(+infinity, +infinity, +infinity);
 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
index 83c3ad8f53..dd3baab93a 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -169,7 +169,7 @@ select tableoid::regclass, * from list_parted;
 
 -- 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 (infinity) 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 (infinity, 
infinity, infinity) to (1, infinity, infinity);
+create table mcrparted1 partition of mcrparted for values from (2, 1, 
infinity) to (10, 5, 10);
+create table mcrparted2 partition of mcrparted for values from (10, 6, 
infinity) to (10, infinity, infinity);
 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, infinity, 
infinity) to (30, 20, infinity);
+create table mcrparted5 partition of mcrparted for values from (30, 21, 20) to 
(infinity, infinity, infinity);
 
 -- routed to mcrparted0
 insert into mcrparted values (0, 1, 1);
@@ -342,3 +342,25 @@ insert into brtrigpartcon values (1, 'hi there');
 insert into brtrigpartcon1 values (1, 'hi there');
 drop table brtrigpartcon;
 drop function brtrigpartcon1trigf();
+
+-- check that tuple-routing works and partition constraint are enforced
+-- properly when infinity values are specified with arbitrary signs in
+-- FROM and TO of range partition bounds.
+create table blogs (a text, b numeric) partition by range (a, b);
+create table blogs_a partition of blogs for values from ('a', infinity) to 
('b', -infinity);
+create table blogs_b partition of blogs for values from ('b', infinity) to 
('c', -infinity);
+-- Note that this one admits just the letter 'd'
+create table blogs_c_and_d partition of blogs for values from ('c', infinity) 
to ('d', infinity);
+-- This one allows strings lexically greater than letter 'd'
+create table blogs_d_to_z partition of blogs for values from ('d', +infinity) 
to (infinity, infinity);
+\d+ blogs
+insert into blogs values ('and there it was', 0), ('because thats why', -1), 
('come on', 12345), ('d', 132), ('drag', 908.0), ('zappa the legend', 1);
+select tableoid::regclass::text, * from blogs order by 1;
+-- indmissible
+insert into blogs_a values ('b', 1);
+insert into blogs_b values ('c', 1);
+insert into blogs_c_and_d values ('dr', 1);
+insert into blogs_d_to_z values ('d', 1);
+
+-- cleanup
+drop table blogs;
-- 
2.11.0

-- 
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