On 2017/07/03 14:00, Amit Langote wrote:
> On 2017/07/03 2:15, Dean Rasheed wrote:
>> On 30 June 2017 at 10:04, Ashutosh Bapat
>> <ashutosh.ba...@enterprisedb.com> wrote:
>>> On Fri, Jun 30, 2017 at 1:36 PM, Amit Langote
>>> <langote_amit...@lab.ntt.co.jp> wrote:
>>>>
>>>> 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.
>>>
>>> I don't think -infinity and +infinity are the right terms. For a
>>> string or character data type there is no -infinity and +infinity.
>>> Similarly for enums. We need to extend UNBOUNDED somehow to indicate
>>> the end of a given type in the given direction. I thought about
>>> UNBOUNDED LEFT/RIGHT but then whether LEFT indicates -ve side or +side
>>> would cause confusion. Also LEFT/RIGHT may work for a single
>>> dimensional datatype but not for multi-dimensional spaces. How about
>>> MINIMUM/MAXIMUM or UNBOUNDED MIN/MAX to indicate the extremities.
>>>
>>
>> Yes, I think you're right. Also, some datatypes include values that
>> are equal to +/-infinity, which would then behave differently from
>> unbounded as range bounds, so it wouldn't be a good idea to overload
>> that term.
> 
> Agree with you both that using (+/-) infinity may not be a good idea after
> all.
> 
>> My first thought was UNBOUNDED ABOVE/BELOW, because that matches the
>> terminology already in use of upper and lower bounds.
> 
> I was starting to like the Ashutosh's suggested UNBOUNDED MIN/MAX syntax,
> but could you clarify your comment that ABOVE/BELOW is the terminology
> already in use of upper and lower bounds?  I couldn't find ABOVE/BELOW in
> our existing syntax anywhere that uses the upper/lower bound notion, so
> was confused a little bit.
> 
> Also, I assume UNBOUNDED ABOVE signifies positive infinity and vice versa.

Anyway, here's the revised version of the syntax patch that implements
ABOVE/BELOW extension to UNBOUNDED specification.

0001 is the patch that Dean posted [1] as a replacement for what I earlier
posted for simplifying range partition overlap check.

0002 is the UNBOUNDED syntax extension patch.

Thanks,
Amit

[1]
https://www.postgresql.org/message-id/CAEZATCVcBCBZsMcHj37TF%2BdcsjCtKZdZ_FAaJjaFMvfoXRqZMg%40mail.gmail.com
From e99ee071125b0026e69c5a49cee1865bf380883b Mon Sep 17 00:00:00 2001
From: amit <amitlangot...@gmail.com>
Date: Mon, 3 Jul 2017 10:52:45 +0900
Subject: [PATCH 1/2] Dean's patch to simply range partition overlap check

---
 src/backend/catalog/partition.c            | 90 ++++++++++++------------------
 src/test/regress/expected/create_table.out |  2 +-
 2 files changed, 38 insertions(+), 54 deletions(-)

diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 7da2058f15..96760a0f05 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -745,78 +745,62 @@ check_new_partition_bound(char *relname, Relation parent,
                                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];
                                        }
                                }
 
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 8ea7585d7619f80f171b4314f741ab1ca561a5af 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 per the current rule.

Adjust syntax to allow an explicit direction to be specified for
unbounded values --- "unbounded below" signifies -infinity, whereas
"unbounded above" signifies +infinity.

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 new syntax.
---
 doc/src/sgml/ref/create_table.sgml         | 16 +++----
 src/backend/catalog/partition.c            | 74 ++++++++++++++++++++----------
 src/backend/parser/gram.y                  | 37 +++++++++------
 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                |  2 +
 src/test/regress/expected/create_table.out | 65 ++++++++++++++++++++------
 src/test/regress/expected/insert.out       | 49 ++++++++++++++++++++
 src/test/regress/sql/create_table.sql      | 36 +++++++++++----
 src/test/regress/sql/insert.sql            | 22 +++++++++
 11 files changed, 285 insertions(+), 88 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 96760a0f05..9f74bc9ce7 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],
@@ -1396,7 +1394,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
@@ -1468,17 +1466,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.
@@ -1662,15 +1664,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;
@@ -1685,14 +1696,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;
@@ -2097,12 +2118,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..989fd8099b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -601,11 +601,11 @@ static Node *makeRecursiveViewSelect(char *relname, List 
*aliases, Node *query);
  */
 
 /* ordinary key words in alphabetical order */
-%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
+%token <keyword> ABORT_P ABSOLUTE_P ABOVE ACCESS ACTION ADD_P ADMIN AFTER
        AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
        ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
 
-       BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
+       BACKWARD BEFORE BEGIN_P BELOW BETWEEN BIGINT BINARY BIT
        BOOLEAN_P BOTH BY
 
        CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
@@ -2681,6 +2681,23 @@ partbound_datum:
                        Sconst                  { $$ = makeStringConst($1, @1); 
}
                        | NumericOnly   { $$ = makeAConst($1, @1); }
                        | NULL_P                { $$ = makeNullAConst(@1); }
+                       | UNBOUNDED BELOW
+                               {
+                                       $$ = (Node *) 
makeDefElem("negative_infinity", NULL, @1);
+                               }
+                       | UNBOUNDED ABOVE
+                               {
+                                       $$ = (Node *) 
makeDefElem("positive_infinity", NULL, @1);
+                               }
+                       /*
+                        * If a user does not specify the direction, 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.
+                        */
+                       | UNBOUNDED
+                               {
+                                       $$ = (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;
 
@@ -14606,6 +14613,7 @@ ColLabel:       IDENT                                   
                                { $$ = $1; }
  */
 unreserved_keyword:
                          ABORT_P
+                       | ABOVE
                        | ABSOLUTE_P
                        | ACCESS
                        | ACTION
@@ -14624,6 +14632,7 @@ unreserved_keyword:
                        | BACKWARD
                        | BEFORE
                        | BEGIN_P
+                       | BELOW
                        | BY
                        | CACHE
                        | CALLED
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..b445991bf7 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, "unbounded below");
+                                                               else
+                                                                       
appendStringInfoString(buf, "unbounded above");
+                                                       }
                                                        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, "unbounded below");
+                                                               else
+                                                                       
appendStringInfoString(buf, "unbounded above");
+                                                       }
                                                        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..7b71bb39e1 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -27,6 +27,7 @@
 
 /* name, value, category */
 PG_KEYWORD("abort", ABORT_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("above", ABOVE, UNRESERVED_KEYWORD)
 PG_KEYWORD("absolute", ABSOLUTE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("access", ACCESS, UNRESERVED_KEYWORD)
 PG_KEYWORD("action", ACTION, UNRESERVED_KEYWORD)
@@ -55,6 +56,7 @@ PG_KEYWORD("authorization", AUTHORIZATION, 
TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
 PG_KEYWORD("before", BEFORE, UNRESERVED_KEYWORD)
 PG_KEYWORD("begin", BEGIN_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("below", BELOW, UNRESERVED_KEYWORD)
 PG_KEYWORD("between", BETWEEN, COL_NAME_KEYWORD)
 PG_KEYWORD("bigint", BIGINT, COL_NAME_KEYWORD)
 PG_KEYWORD("binary", BINARY, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/test/regress/expected/create_table.out 
b/src/test/regress/expected/create_table.out
index b6f794e1c2..39a68b454b 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -514,11 +514,11 @@ 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);
 ERROR:  cannot specify NULL in range bound
--- cannot specify finite values after UNBOUNDED has been specified
+-- 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...
+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 infinity
+LINE 1: ...ge_parted_multicol FOR VALUES FROM (1, unbounded, 1) TO (unb...
                                                              ^
 DROP TABLE range_parted_multicol;
 -- check if compatible with the specified parent
@@ -604,10 +604,45 @@ CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES 
FROM (1, 10) TO (1, un
 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
+-- from unbounded below to unbounded above, 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);
 ERROR:  partition "fail_part" would overlap partition "part10"
+-- check that range partition bound syntax allows unbounded values to be
+-- specified with direction 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', unbounded) to ('b', unbounded below);
+create table multicol_range_2 partition of multicol_range for values from 
('b', unbounded) to ('c', unbounded below);
+-- Accepts 'd', but not strings lexically greater than 'd'
+create table multicol_range_3 partition of multicol_range for values from 
('c', unbounded) to ('d', unbounded);
+\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', unbounded below) TO ('d', 
unbounded above)
+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', unbounded above) to (unbounded, unbounded);
+\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', unbounded above) TO 
(unbounded above, unbounded above)
+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', unbounded above) to (unbounded below, unbounded);
+ERROR:  cannot create range partition with empty range
+create table multicol_range_err partition of multicol_range for values from 
('e', unbounded) to ('e', unbounded below);
+ERROR:  cannot create range partition with empty range
+create table multicol_range_err partition of multicol_range for values from 
(unbounded above, unbounded) to (unbounded below, unbounded);
+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 
(unbounded, unbounded, unbounded) TO (unbounded, unbounded, unbounded);
 \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 (unbounded below, unbounded below, 
unbounded below) TO (unbounded above, unbounded above, unbounded above)
 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, unbounded, unbounded) TO (1, unbounded, unbounded);
 \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 (unbounded below, unbounded below, 
unbounded below) TO (1, unbounded above, unbounded above)
 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, unbounded);
 \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, unbounded 
above)
 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, 
unbounded) TO (9, unbounded, unbounded);
 \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, unbounded below) TO (9, 
unbounded above, unbounded above)
 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/insert.out 
b/src/test/regress/expected/insert.out
index d1153f410b..77e71df121 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -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 unbounded 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', unbounded) to 
('b', unbounded below);
+create table blogs_b partition of blogs for values from ('b', unbounded) to 
('c', unbounded below);
+-- Note that this one admits just the letter 'd'
+create table blogs_c_and_d partition of blogs for values from ('c', unbounded) 
to ('d', unbounded);
+-- This one allows strings lexically greater than letter 'd'
+create table blogs_d_to_z partition of blogs for values from ('d', unbounded 
above) to (unbounded, unbounded);
+\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', unbounded below) TO ('b', unbounded 
below),
+            blogs_b FOR VALUES FROM ('b', unbounded below) TO ('c', unbounded 
below),
+            blogs_c_and_d FOR VALUES FROM ('c', unbounded below) TO ('d', 
unbounded above),
+            blogs_d_to_z FOR VALUES FROM ('d', unbounded above) TO (unbounded 
above, unbounded above)
+
+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..a25ba23320 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -485,9 +485,9 @@ CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES 
FROM ('a') TO ('z',
 -- cannot specify null values in range bounds
 CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO 
(unbounded);
 
--- cannot specify finite values after UNBOUNDED has been specified
+-- 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);
+CREATE TABLE fail_part PARTITION OF range_parted_multicol FOR VALUES FROM (1, 
unbounded, 1) TO (unbounded, 1, 1);
 DROP TABLE range_parted_multicol;
 
 -- check if compatible with the specified parent
@@ -566,10 +566,28 @@ CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES 
FROM (1, 10) TO (1, un
 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
+-- from unbounded below to unbounded above, 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);
 
+-- check that range partition bound syntax allows unbounded values to be
+-- specified with direction 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', unbounded) to ('b', unbounded below);
+create table multicol_range_2 partition of multicol_range for values from 
('b', unbounded) to ('c', unbounded below);
+-- Accepts 'd', but not strings lexically greater than 'd'
+create table multicol_range_3 partition of multicol_range for values from 
('c', unbounded) to ('d', unbounded);
+\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', unbounded above) to (unbounded, unbounded);
+\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', unbounded above) to (unbounded below, unbounded);
+create table multicol_range_err partition of multicol_range for values from 
('e', unbounded) to ('e', unbounded below);
+create table multicol_range_err partition of multicol_range for values from 
(unbounded above, unbounded) to (unbounded below, unbounded);
+
 -- check schema propagation from parent
 
 CREATE TABLE parted (
@@ -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 
(unbounded, unbounded, unbounded) TO (unbounded, unbounded, 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, unbounded, unbounded) TO (1, unbounded, unbounded);
 \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, unbounded);
 \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, 
unbounded) TO (9, unbounded, unbounded);
 \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/insert.sql b/src/test/regress/sql/insert.sql
index 83c3ad8f53..047a35e87b 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -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 unbounded 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', unbounded) to 
('b', unbounded below);
+create table blogs_b partition of blogs for values from ('b', unbounded) to 
('c', unbounded below);
+-- Note that this one admits just the letter 'd'
+create table blogs_c_and_d partition of blogs for values from ('c', unbounded) 
to ('d', unbounded);
+-- This one allows strings lexically greater than letter 'd'
+create table blogs_d_to_z partition of blogs for values from ('d', unbounded 
above) to (unbounded, unbounded);
+\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