1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-09 06:21:09 +03:00

Use MINVALUE/MAXVALUE instead of UNBOUNDED for range partition bounds.

Previously, UNBOUNDED meant no lower bound when used in the FROM list,
and no upper bound when used in the TO list, which was OK for
single-column range partitioning, but problematic with multiple
columns. For example, an upper bound of (10.0, UNBOUNDED) would not be
collocated with a lower bound of (10.0, UNBOUNDED), thus making it
difficult or impossible to define contiguous multi-column range
partitions in some cases.

Fix this by using MINVALUE and MAXVALUE instead of UNBOUNDED to
represent a partition column that is unbounded below or above
respectively. This syntax removes any ambiguity, and ensures that if
one partition's lower bound equals another partition's upper bound,
then the partitions are contiguous.

Also drop the constraint prohibiting finite values after an unbounded
column, and just document the fact that any values after MINVALUE or
MAXVALUE are ignored. Previously it was necessary to repeat UNBOUNDED
multiple times, which was needlessly verbose.

Note: Forces a post-PG 10 beta2 initdb.

Report by Amul Sul, original patch by Amit Langote with some
additional hacking by me.

Discussion: https://postgr.es/m/CAAJ_b947mowpLdxL3jo3YLKngRjrq9+Ej4ymduQTfYR+8=YAYQ@mail.gmail.com
This commit is contained in:
Dean Rasheed
2017-07-21 09:20:47 +01:00
parent 866f4a7c21
commit d363d42bb9
18 changed files with 378 additions and 238 deletions

View File

@@ -67,23 +67,14 @@
* is an upper bound.
*/
/* Ternary value to represent what's contained in a range bound datum */
typedef enum RangeDatumContent
{
RANGE_DATUM_FINITE = 0, /* actual datum stored elsewhere */
RANGE_DATUM_NEG_INF, /* negative infinity */
RANGE_DATUM_POS_INF /* positive infinity */
} RangeDatumContent;
typedef struct PartitionBoundInfoData
{
char strategy; /* list or range bounds? */
int ndatums; /* Length of the datums following array */
Datum **datums; /* Array of datum-tuples with key->partnatts
* datums each */
RangeDatumContent **content; /* what's contained in each range bound
* datum? (see the above enum); NULL for
* list partitioned tables */
PartitionRangeDatumKind **kind; /* The kind of each range bound datum;
* NULL for list partitioned tables */
int *indexes; /* Partition indexes; one entry per member of
* the datums array (plus one if range
* partitioned table) */
@@ -110,7 +101,7 @@ typedef struct PartitionRangeBound
{
int index;
Datum *datums; /* range bound datums */
RangeDatumContent *content; /* what's contained in each datum? */
PartitionRangeDatumKind *kind; /* the kind of each datum */
bool lower; /* this is the lower (vs upper) bound */
} PartitionRangeBound;
@@ -136,10 +127,10 @@ static List *generate_partition_qual(Relation rel);
static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index,
List *datums, bool lower);
static int32 partition_rbound_cmp(PartitionKey key,
Datum *datums1, RangeDatumContent *content1, bool lower1,
PartitionRangeBound *b2);
Datum *datums1, PartitionRangeDatumKind *kind1,
bool lower1, PartitionRangeBound *b2);
static int32 partition_rbound_datum_cmp(PartitionKey key,
Datum *rb_datums, RangeDatumContent *rb_content,
Datum *rb_datums, PartitionRangeDatumKind *rb_kind,
Datum *tuple_datums);
static int32 partition_bound_cmp(PartitionKey key,
@@ -366,29 +357,25 @@ RelationBuildPartitionDesc(Relation rel)
bool is_distinct = false;
int j;
/* Is current bound is distinct from the previous? */
/* Is the current bound distinct from the previous one? */
for (j = 0; j < key->partnatts; j++)
{
Datum cmpval;
if (prev == NULL)
if (prev == NULL || cur->kind[j] != prev->kind[j])
{
is_distinct = true;
break;
}
/*
* 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 the bounds are both MINVALUE or MAXVALUE, stop now
* and treat them as equal, since any values after this
* point must be ignored.
*/
if (cur->content[j] != RANGE_DATUM_FINITE ||
prev->content[j] != RANGE_DATUM_FINITE)
{
is_distinct = true;
if (cur->kind[j] != PARTITION_RANGE_DATUM_VALUE)
break;
}
cmpval = FunctionCall2Coll(&key->partsupfunc[j],
key->partcollation[j],
cur->datums[j],
@@ -513,8 +500,9 @@ RelationBuildPartitionDesc(Relation rel)
case PARTITION_STRATEGY_RANGE:
{
boundinfo->content = (RangeDatumContent **) palloc(ndatums *
sizeof(RangeDatumContent *));
boundinfo->kind = (PartitionRangeDatumKind **)
palloc(ndatums *
sizeof(PartitionRangeDatumKind *));
boundinfo->indexes = (int *) palloc((ndatums + 1) *
sizeof(int));
@@ -524,18 +512,17 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo->datums[i] = (Datum *) palloc(key->partnatts *
sizeof(Datum));
boundinfo->content[i] = (RangeDatumContent *)
boundinfo->kind[i] = (PartitionRangeDatumKind *)
palloc(key->partnatts *
sizeof(RangeDatumContent));
sizeof(PartitionRangeDatumKind));
for (j = 0; j < key->partnatts; j++)
{
if (rbounds[i]->content[j] == RANGE_DATUM_FINITE)
if (rbounds[i]->kind[j] == PARTITION_RANGE_DATUM_VALUE)
boundinfo->datums[i][j] =
datumCopy(rbounds[i]->datums[j],
key->parttypbyval[j],
key->parttyplen[j]);
/* Remember, we are storing the tri-state value. */
boundinfo->content[i][j] = rbounds[i]->content[j];
boundinfo->kind[i][j] = rbounds[i]->kind[j];
}
/*
@@ -617,17 +604,14 @@ partition_bounds_equal(PartitionKey key,
for (j = 0; j < key->partnatts; j++)
{
/* For range partitions, the bounds might not be finite. */
if (b1->content != NULL)
if (b1->kind != NULL)
{
/*
* A finite bound always differs from an infinite bound, and
* different kinds of infinities differ from each other.
*/
if (b1->content[i][j] != b2->content[i][j])
/* The different kinds of bound all differ from each other */
if (b1->kind[i][j] != b2->kind[i][j])
return false;
/* Non-finite bounds are equal without further examination. */
if (b1->content[i][j] != RANGE_DATUM_FINITE)
if (b1->kind[i][j] != PARTITION_RANGE_DATUM_VALUE)
continue;
}
@@ -736,7 +720,7 @@ check_new_partition_bound(char *relname, Relation parent,
* First check if the resulting range would be empty with
* specified lower and upper bounds
*/
if (partition_rbound_cmp(key, lower->datums, lower->content, true,
if (partition_rbound_cmp(key, lower->datums, lower->kind, true,
upper) >= 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -754,18 +738,18 @@ check_new_partition_bound(char *relname, Relation parent,
/*
* 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.
* inclusively as part of the new partition) lies inside
* an existing partition, or in a gap.
*
* If it's inside an existing partition, the bound at
* offset + 1 will be the upper bound of that partition,
* and its index will be >= 0.
*
* If it's in a gap, the bound at offset + 1 will be the
* lower bound of the next partition, and its index will be
* -1. This is also true if there is no next partition,
* since the index array is initialised with an extra -1 at
* the end.
* lower bound of the next partition, and its index will
* be -1. This is also true if there is no next partition,
* since the index array is initialised with an extra -1
* at the end.
*/
offset = partition_bound_bsearch(key, boundinfo, lower,
true, &equal);
@@ -774,9 +758,9 @@ check_new_partition_bound(char *relname, Relation parent,
{
/*
* 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.
* 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 (offset + 1 < boundinfo->ndatums)
{
@@ -788,8 +772,9 @@ check_new_partition_bound(char *relname, Relation parent,
if (cmpval < 0)
{
/*
* The new partition overlaps with the existing
* partition between offset + 1 and offset + 2.
* The new partition overlaps with the
* existing partition between offset + 1 and
* offset + 2.
*/
overlap = true;
with = boundinfo->indexes[offset + 2];
@@ -1399,8 +1384,8 @@ 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.
* All of these structures are freshly palloc'd.
* *upper_val). For MINVALUE/MAXVALUE 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
* the key->partexprs list, or NULL. It may be advanced upon return.
@@ -1432,12 +1417,12 @@ get_range_key_properties(PartitionKey key, int keynum,
}
/* Get appropriate Const nodes for the bounds */
if (!ldatum->infinite)
if (ldatum->kind == PARTITION_RANGE_DATUM_VALUE)
*lower_val = castNode(Const, copyObject(ldatum->value));
else
*lower_val = NULL;
if (!udatum->infinite)
if (udatum->kind == PARTITION_RANGE_DATUM_VALUE)
*upper_val = castNode(Const, copyObject(udatum->value));
else
*upper_val = NULL;
@@ -1471,18 +1456,16 @@ 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 a bound datum is either MINVALUE or MAXVALUE, these expressions are
* simplified using the fact that any value is greater than MINVALUE and less
* than MAXVALUE. So, for example, if cu = MAXVALUE, c < cu is automatically
* true, and we need not emit any expression for it, and the last line becomes
*
* (b < bu) OR (b = bu), which is simplified to (b <= bu)
*
* In most common cases with only one partition column, say a, the following
* expression tree will be generated: a IS NOT NULL AND a >= al AND a < au
*
* If all values of both lower and upper bounds are UNBOUNDED, the partition
* does not really have a constraint, except the IS NOT NULL constraint for
* partition keys.
*
* If 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.
*/
@@ -1585,9 +1568,10 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
&lower_val, &upper_val);
/*
* If either or both of lower_val and upper_val is NULL, they are
* unequal, because being NULL means the column is unbounded in the
* respective direction.
* If either value is NULL, the corresponding partition bound is
* either MINVALUE or MAXVALUE, and we treat them as unequal, because
* even if they're the same, there is no common value to equate the
* key column with.
*/
if (!lower_val || !upper_val)
break;
@@ -1668,12 +1652,15 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
/*
* For the non-last columns of this arm, use the EQ operator.
* For the last or the last finite-valued column, use GE.
* For the last column of this arm, use GT, unless this is the
* last column of the whole bound check, or the next bound
* datum is MINVALUE, in which case use GE.
*/
if (j - i < current_or_arm)
strategy = BTEqualStrategyNumber;
else if ((ldatum_next && ldatum_next->infinite) ||
j == key->partnatts - 1)
else if (j == key->partnatts - 1 ||
(ldatum_next &&
ldatum_next->kind == PARTITION_RANGE_DATUM_MINVALUE))
strategy = BTGreaterEqualStrategyNumber;
else
strategy = BTGreaterStrategyNumber;
@@ -1691,11 +1678,13 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
/*
* For the non-last columns of this arm, use the EQ operator.
* For the last finite-valued column, use LE.
* For the last column of this arm, use LT, unless the next
* bound datum is MAXVALUE, in which case use LE.
*/
if (j - i < current_or_arm)
strategy = BTEqualStrategyNumber;
else if (udatum_next && udatum_next->infinite)
else if (udatum_next &&
udatum_next->kind == PARTITION_RANGE_DATUM_MAXVALUE)
strategy = BTLessEqualStrategyNumber;
else
strategy = BTLessStrategyNumber;
@@ -1716,11 +1705,15 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
if (j - i > current_or_arm)
{
/*
* We need not emit the next arm if the new column that will
* be considered is unbounded.
* We must not emit any more arms if the new column that will
* be considered is unbounded, or this one was.
*/
need_next_lower_arm = ldatum_next && !ldatum_next->infinite;
need_next_upper_arm = udatum_next && !udatum_next->infinite;
if (!lower_val || !ldatum_next ||
ldatum_next->kind != PARTITION_RANGE_DATUM_VALUE)
need_next_lower_arm = false;
if (!upper_val || !udatum_next ||
udatum_next->kind != PARTITION_RANGE_DATUM_VALUE)
need_next_upper_arm = false;
break;
}
}
@@ -2092,8 +2085,8 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
bound->index = index;
bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
bound->content = (RangeDatumContent *) palloc0(key->partnatts *
sizeof(RangeDatumContent));
bound->kind = (PartitionRangeDatumKind *) palloc0(key->partnatts *
sizeof(PartitionRangeDatumKind));
bound->lower = lower;
i = 0;
@@ -2102,12 +2095,9 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
PartitionRangeDatum *datum = castNode(PartitionRangeDatum, lfirst(lc));
/* What's contained in this range datum? */
bound->content[i] = !datum->infinite
? RANGE_DATUM_FINITE
: (lower ? RANGE_DATUM_NEG_INF
: RANGE_DATUM_POS_INF);
bound->kind[i] = datum->kind;
if (bound->content[i] == RANGE_DATUM_FINITE)
if (datum->kind == PARTITION_RANGE_DATUM_VALUE)
{
Const *val = castNode(Const, datum->value);
@@ -2130,7 +2120,7 @@ qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
PartitionRangeBound *b2 = (*(PartitionRangeBound *const *) b);
PartitionKey key = (PartitionKey) arg;
return partition_rbound_cmp(key, b1->datums, b1->content, b1->lower, b2);
return partition_rbound_cmp(key, b1->datums, b1->kind, b1->lower, b2);
}
/*
@@ -2148,13 +2138,13 @@ qsort_partition_rbound_cmp(const void *a, const void *b, void *arg)
*/
static int32
partition_rbound_cmp(PartitionKey key,
Datum *datums1, RangeDatumContent *content1, bool lower1,
PartitionRangeBound *b2)
Datum *datums1, PartitionRangeDatumKind *kind1,
bool lower1, PartitionRangeBound *b2)
{
int32 cmpval = 0; /* placate compiler */
int i;
Datum *datums2 = b2->datums;
RangeDatumContent *content2 = b2->content;
PartitionRangeDatumKind *kind2 = b2->kind;
bool lower2 = b2->lower;
for (i = 0; i < key->partnatts; i++)
@@ -2162,28 +2152,21 @@ partition_rbound_cmp(PartitionKey key,
/*
* First, handle cases where the column is unbounded, which should not
* invoke the comparison procedure, and should not consider any later
* columns.
* columns. Note that the PartitionRangeDatumKind enum elements
* compare the same way as the values they represent.
*/
if (content1[i] != RANGE_DATUM_FINITE ||
content2[i] != RANGE_DATUM_FINITE)
{
/*
* If the bound values are equal, fall through and compare whether
* they are upper or lower bounds.
*/
if (content1[i] == content2[i])
break;
if (kind1[i] < kind2[i])
return -1;
else if (kind1[i] > kind2[i])
return 1;
else if (kind1[i] != PARTITION_RANGE_DATUM_VALUE)
/* Otherwise, one bound is definitely larger than the other */
if (content1[i] == RANGE_DATUM_NEG_INF)
return -1;
else if (content1[i] == RANGE_DATUM_POS_INF)
return 1;
else if (content2[i] == RANGE_DATUM_NEG_INF)
return 1;
else if (content2[i] == RANGE_DATUM_POS_INF)
return -1;
}
/*
* The column bounds are both MINVALUE or both MAXVALUE. No later
* columns should be considered, but we still need to compare
* whether they are upper or lower bounds.
*/
break;
cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
key->partcollation[i],
@@ -2208,12 +2191,12 @@ partition_rbound_cmp(PartitionKey key,
/*
* partition_rbound_datum_cmp
*
* Return whether range bound (specified in rb_datums, rb_content, and
* rb_lower) <=, =, >= partition key of tuple (tuple_datums)
* Return whether range bound (specified in rb_datums, rb_kind, and rb_lower)
* is <, =, or > partition key of tuple (tuple_datums)
*/
static int32
partition_rbound_datum_cmp(PartitionKey key,
Datum *rb_datums, RangeDatumContent *rb_content,
Datum *rb_datums, PartitionRangeDatumKind *rb_kind,
Datum *tuple_datums)
{
int i;
@@ -2221,8 +2204,10 @@ partition_rbound_datum_cmp(PartitionKey key,
for (i = 0; i < key->partnatts; i++)
{
if (rb_content[i] != RANGE_DATUM_FINITE)
return rb_content[i] == RANGE_DATUM_NEG_INF ? -1 : 1;
if (rb_kind[i] == PARTITION_RANGE_DATUM_MINVALUE)
return -1;
else if (rb_kind[i] == PARTITION_RANGE_DATUM_MAXVALUE)
return 1;
cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
key->partcollation[i],
@@ -2238,7 +2223,7 @@ partition_rbound_datum_cmp(PartitionKey key,
/*
* partition_bound_cmp
*
* Return whether the bound at offset in boundinfo is <=, =, >= the argument
* Return whether the bound at offset in boundinfo is <, =, or > the argument
* specified in *probe.
*/
static int32
@@ -2259,7 +2244,7 @@ partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
case PARTITION_STRATEGY_RANGE:
{
RangeDatumContent *content = boundinfo->content[offset];
PartitionRangeDatumKind *kind = boundinfo->kind[offset];
if (probe_is_bound)
{
@@ -2271,12 +2256,12 @@ partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo,
bool lower = boundinfo->indexes[offset] < 0;
cmpval = partition_rbound_cmp(key,
bound_datums, content, lower,
bound_datums, kind, lower,
(PartitionRangeBound *) probe);
}
else
cmpval = partition_rbound_datum_cmp(key,
bound_datums, content,
bound_datums, kind,
(Datum *) probe);
break;
}

View File

@@ -4458,7 +4458,7 @@ _copyPartitionRangeDatum(const PartitionRangeDatum *from)
{
PartitionRangeDatum *newnode = makeNode(PartitionRangeDatum);
COPY_SCALAR_FIELD(infinite);
COPY_SCALAR_FIELD(kind);
COPY_NODE_FIELD(value);
COPY_LOCATION_FIELD(location);

View File

@@ -2849,7 +2849,7 @@ _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *
static bool
_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
{
COMPARE_SCALAR_FIELD(infinite);
COMPARE_SCALAR_FIELD(kind);
COMPARE_NODE_FIELD(value);
COMPARE_LOCATION_FIELD(location);

View File

@@ -3582,7 +3582,7 @@ _outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
{
WRITE_NODE_TYPE("PARTITIONRANGEDATUM");
WRITE_BOOL_FIELD(infinite);
WRITE_ENUM_FIELD(kind, PartitionRangeDatumKind);
WRITE_NODE_FIELD(value);
WRITE_LOCATION_FIELD(location);
}

View File

@@ -2404,7 +2404,7 @@ _readPartitionRangeDatum(void)
{
READ_LOCALS(PartitionRangeDatum);
READ_BOOL_FIELD(infinite);
READ_ENUM_FIELD(kind, PartitionRangeDatumKind);
READ_NODE_FIELD(value);
READ_LOCATION_FIELD(location);

View File

@@ -2696,11 +2696,21 @@ range_datum_list:
;
PartitionRangeDatum:
UNBOUNDED
MINVALUE
{
PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
n->infinite = true;
n->kind = PARTITION_RANGE_DATUM_MINVALUE;
n->value = NULL;
n->location = @1;
$$ = (Node *) n;
}
| MAXVALUE
{
PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
n->kind = PARTITION_RANGE_DATUM_MAXVALUE;
n->value = NULL;
n->location = @1;
@@ -2710,7 +2720,7 @@ PartitionRangeDatum:
{
PartitionRangeDatum *n = makeNode(PartitionRangeDatum);
n->infinite = false;
n->kind = PARTITION_RANGE_DATUM_VALUE;
n->value = $1;
n->location = @1;

View File

@@ -3365,7 +3365,6 @@ transformPartitionBound(ParseState *pstate, Relation parent,
*cell2;
int i,
j;
bool seen_unbounded;
if (spec->strategy != PARTITION_STRATEGY_RANGE)
ereport(ERROR,
@@ -3382,39 +3381,6 @@ transformPartitionBound(ParseState *pstate, Relation parent,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
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.
*/
seen_unbounded = false;
foreach(cell1, spec->lowerdatums)
{
PartitionRangeDatum *ldatum = castNode(PartitionRangeDatum,
lfirst(cell1));
if (ldatum->infinite)
seen_unbounded = true;
else if (seen_unbounded)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot specify finite value after UNBOUNDED"),
parser_errposition(pstate, exprLocation((Node *) ldatum))));
}
seen_unbounded = false;
foreach(cell1, spec->upperdatums)
{
PartitionRangeDatum *rdatum = castNode(PartitionRangeDatum,
lfirst(cell1));
if (rdatum->infinite)
seen_unbounded = true;
else if (seen_unbounded)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot specify finite value after UNBOUNDED"),
parser_errposition(pstate, exprLocation((Node *) rdatum))));
}
/* Transform all the constants */
i = j = 0;
result_spec->lowerdatums = result_spec->upperdatums = NIL;

View File

@@ -8715,8 +8715,10 @@ get_rule_expr(Node *node, deparse_context *context,
castNode(PartitionRangeDatum, lfirst(cell));
appendStringInfoString(buf, sep);
if (datum->infinite)
appendStringInfoString(buf, "UNBOUNDED");
if (datum->kind == PARTITION_RANGE_DATUM_MINVALUE)
appendStringInfoString(buf, "MINVALUE");
else if (datum->kind == PARTITION_RANGE_DATUM_MAXVALUE)
appendStringInfoString(buf, "MAXVALUE");
else
{
Const *val = castNode(Const, datum->value);
@@ -8733,8 +8735,10 @@ get_rule_expr(Node *node, deparse_context *context,
castNode(PartitionRangeDatum, lfirst(cell));
appendStringInfoString(buf, sep);
if (datum->infinite)
appendStringInfoString(buf, "UNBOUNDED");
if (datum->kind == PARTITION_RANGE_DATUM_MINVALUE)
appendStringInfoString(buf, "MINVALUE");
else if (datum->kind == PARTITION_RANGE_DATUM_MAXVALUE)
appendStringInfoString(buf, "MAXVALUE");
else
{
Const *val = castNode(Const, datum->value);