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