diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index 4486989..4915db9 100644 --- a/src/backend/catalog/partition.c +++ b/src/backend/catalog/partition.c @@ -72,7 +72,8 @@ typedef enum RangeDatumContent { RANGE_DATUM_FINITE = 0, /* actual datum stored elsewhere */ RANGE_DATUM_NEG_INF, /* negative infinity */ - RANGE_DATUM_POS_INF /* positive infinity */ + RANGE_DATUM_POS_INF, /* positive infinity */ + RANGE_DATUM_DEFAULT /* default */ } RangeDatumContent; typedef struct PartitionBoundInfoData @@ -137,7 +138,9 @@ static void get_range_key_properties(PartitionKey key, int keynum, Expr **keyCol, Const **lower_val, Const **upper_val); static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec); -static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec); +static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec, + bool for_default); +static List *get_range_nulltest(PartitionKey key); static List *generate_partition_qual(Relation rel); static PartitionRangeBound *make_one_range_bound(PartitionKey key, int index, @@ -180,11 +183,11 @@ RelationBuildPartitionDesc(Relation rel) MemoryContext oldcxt; int ndatums = 0; + int default_index = -1; /* List partitioning specific */ PartitionListValue **all_values = NULL; int null_index = -1; - int default_index = -1; /* Range partitioning specific */ PartitionRangeBound **rbounds = NULL; @@ -359,6 +362,9 @@ RelationBuildPartitionDesc(Relation rel) if (spec->strategy != PARTITION_STRATEGY_RANGE) elog(ERROR, "invalid strategy in partition bound spec"); + if (spec->is_default) + default_index = i; + lower = make_one_range_bound(key, i, spec->lowerdatums, true); upper = make_one_range_bound(key, i, spec->upperdatums, @@ -556,6 +562,7 @@ RelationBuildPartitionDesc(Relation rel) sizeof(RangeDatumContent *)); boundinfo->indexes = (int *) palloc((ndatums + 1) * sizeof(int)); + boundinfo->default_index = -1; for (i = 0; i < ndatums; i++) { @@ -577,6 +584,13 @@ RelationBuildPartitionDesc(Relation rel) boundinfo->content[i][j] = rbounds[i]->content[j]; } + /* Assign mapping index for default partition. */ + if (default_index != -1 && mapping[default_index] == -1) + { + mapping[default_index] = next_index++; + boundinfo->default_index = mapping[default_index]; + } + /* * There is no mapping for invalid indexes. * @@ -599,10 +613,8 @@ RelationBuildPartitionDesc(Relation rel) boundinfo->indexes[i] = mapping[orig_index]; } } - boundinfo->indexes[i] = -1; - /* As of now, we do not support default range partition. */ - boundinfo->default_index = -1; + boundinfo->indexes[i] = -1; break; } @@ -797,8 +809,9 @@ 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, - upper) >= 0) + if ((lower->content[0] != RANGE_DATUM_DEFAULT) && + partition_rbound_cmp(key, lower->datums, + lower->content, true, upper) >= 0) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("cannot create range partition with empty range"), @@ -815,8 +828,22 @@ check_new_partition_bound(char *relname, Relation parent, boundinfo->strategy == PARTITION_STRATEGY_RANGE); /* - * Firstly, find the greatest range bound that is less - * than or equal to the new lower bound. + * Default partition cannot be added if there already + * exists one. + */ + if ((lower->content[0] == RANGE_DATUM_DEFAULT)) + { + if (partition_bound_has_default(partdesc->boundinfo)) + { + overlap = true; + with = partdesc->boundinfo->default_index; + } + break; + } + + /* + * Find the greatest range bound that is less than or + * equal to the new lower bound. */ off1 = partition_bound_bsearch(key, boundinfo, lower, true, &equal); @@ -934,11 +961,9 @@ check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec) List *all_parts; ListCell *lc; - /* Currently default partition is supported only for LIST partition. */ - if (new_spec->strategy != PARTITION_STRATEGY_LIST) - return; - - new_part_constraints = get_qual_for_list(parent, new_spec); + new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST) + ? get_qual_for_list(parent, new_spec) + : get_qual_for_range(parent, new_spec, false); new_part_constraints = (List *) eval_const_expressions(NULL, (Node *) new_part_constraints); new_part_constraints = @@ -1101,7 +1126,8 @@ get_qual_from_partbound(Relation rel, Relation parent, case PARTITION_STRATEGY_RANGE: Assert(spec->strategy == PARTITION_STRATEGY_RANGE); - my_qual = get_qual_for_range(key, spec); + + my_qual = get_qual_for_range(parent, spec, false); break; default: @@ -1683,6 +1709,53 @@ get_range_key_properties(PartitionKey key, int keynum, *upper_val = NULL; } + /* + * get_range_nulltest + * + * A non-default range partition table does not currently allow partition + * keys to be null, so emit an IS NOT NULL expression for each key column. + */ +static List * +get_range_nulltest(PartitionKey key) +{ + List *result = NIL; + NullTest *nulltest; + ListCell *partexprs_item; + int i; + + partexprs_item = list_head(key->partexprs); + for (i = 0; i < key->partnatts; i++) + { + Expr *keyCol; + + if (key->partattrs[i] != 0) + { + keyCol = (Expr *) makeVar(1, + key->partattrs[i], + key->parttypid[i], + key->parttypmod[i], + key->parttypcoll[i], + 0); + } + else + { + if (partexprs_item == NULL) + elog(ERROR, "wrong number of partition key expressions"); + keyCol = copyObject(lfirst(partexprs_item)); + partexprs_item = lnext(partexprs_item); + } + + nulltest = makeNode(NullTest); + nulltest->arg = keyCol; + nulltest->nulltesttype = IS_NOT_NULL; + nulltest->argisrow = false; + nulltest->location = -1; + result = lappend(result, nulltest); + } + + return result; +} + /* * get_qual_for_range * @@ -1723,11 +1796,15 @@ get_range_key_properties(PartitionKey key, int keynum, * does not really have a constraint, except the IS NOT NULL constraint for * partition keys. * + * For default partition, it returns the negation of the constraints of all + * the other partitions. + * * 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. */ static List * -get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec) +get_qual_for_range(Relation parent, PartitionBoundSpec *spec, + bool for_default) { List *result = NIL; ListCell *cell1, @@ -1738,10 +1815,10 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec) j; PartitionRangeDatum *ldatum, *udatum; + PartitionKey key = RelationGetPartitionKey(parent); Expr *keyCol; Const *lower_val, *upper_val; - NullTest *nulltest; List *lower_or_arms, *upper_or_arms; int num_or_arms, @@ -1751,44 +1828,70 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec) bool need_next_lower_arm, need_next_upper_arm; - lower_or_start_datum = list_head(spec->lowerdatums); - upper_or_start_datum = list_head(spec->upperdatums); - num_or_arms = key->partnatts; - - /* - * A range-partitioned table does not currently allow partition keys to be - * null, so emit an IS NOT NULL expression for each key column. - */ - partexprs_item = list_head(key->partexprs); - for (i = 0; i < key->partnatts; i++) + if (spec->is_default) { - Expr *keyCol; + List *or_expr_args = NIL; + PartitionDesc pdesc = RelationGetPartitionDesc(parent); + Oid *inhoids = pdesc->oids; + int nparts = pdesc->nparts, + i; - if (key->partattrs[i] != 0) + for (i = 0; i < nparts; i++) { - keyCol = (Expr *) makeVar(1, - key->partattrs[i], - key->parttypid[i], - key->parttypmod[i], - key->parttypcoll[i], - 0); + Oid inhrelid = inhoids[i]; + HeapTuple tuple; + Datum datum; + bool isnull; + PartitionBoundSpec *bspec; + + tuple = SearchSysCache1(RELOID, inhrelid); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", inhrelid); + + datum = SysCacheGetAttr(RELOID, tuple, + Anum_pg_class_relpartbound, + &isnull); + + Assert(!isnull); + bspec = (PartitionBoundSpec *) stringToNode(TextDatumGetCString(datum)); + + if (!bspec->is_default) + { + List *part_qual = get_qual_for_range(parent, bspec, true); + + /* + * AND the constraints of the partition and add to + * or_expr_args + */ + or_expr_args = lappend(or_expr_args, list_length(part_qual) > 1 + ? makeBoolExpr(AND_EXPR, part_qual, -1) + : linitial(part_qual)); + } + ReleaseSysCache(tuple); } - else + + if (or_expr_args != NIL) { - if (partexprs_item == NULL) - elog(ERROR, "wrong number of partition key expressions"); - keyCol = copyObject(lfirst(partexprs_item)); - partexprs_item = lnext(partexprs_item); + /* OR all the non-default partition constraints; then negate it */ + result = lappend(result, + list_length(or_expr_args) > 1 + ? makeBoolExpr(OR_EXPR, or_expr_args, -1) + : linitial(or_expr_args)); + result = list_make1(makeBoolExpr(NOT_EXPR, result, -1)); } + else /* default is the only partition */ + result = list_make1(makeBoolConst(true, false)); - nulltest = makeNode(NullTest); - nulltest->arg = keyCol; - nulltest->nulltesttype = IS_NOT_NULL; - nulltest->argisrow = false; - nulltest->location = -1; - result = lappend(result, nulltest); + return result; } + lower_or_start_datum = list_head(spec->lowerdatums); + upper_or_start_datum = list_head(spec->upperdatums); + num_or_arms = key->partnatts; + + if (!for_default) + result = get_range_nulltest(key); + /* * Iterate over the key columns and check if the corresponding lower and * upper datums are equal using the btree equality operator for the @@ -2005,7 +2108,9 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec) /* As noted above, caller expects the list to be non-empty. */ if (result == NIL) - result = list_make1(makeBoolConst(true, false)); + result = for_default + ? get_range_nulltest(key) + : list_make1(makeBoolConst(true, false)); return result; } @@ -2173,8 +2278,7 @@ get_partition_for_tuple(PartitionDispatch *pd, Datum values[PARTITION_MAX_KEYS]; bool isnull[PARTITION_MAX_KEYS]; int cur_offset, - cur_index; - int i, + cur_index = -1, result; ExprContext *ecxt = GetPerTupleExprContext(estate); TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple; @@ -2218,24 +2322,6 @@ get_partition_for_tuple(PartitionDispatch *pd, ecxt->ecxt_scantuple = slot; FormPartitionKeyDatum(parent, slot, estate, values, isnull); - if (key->strategy == PARTITION_STRATEGY_RANGE) - { - /* - * Since we cannot route tuples with NULL partition keys through a - * range-partitioned table, simply return that no partition exists - */ - for (i = 0; i < key->partnatts; i++) - { - if (isnull[i]) - { - *failed_at = parent; - *failed_slot = slot; - result = -1; - goto error_exit; - } - } - } - /* * Handle NULL partition key here if there's a null-accepting list * partition. Else it will be routed to the default partition if one @@ -2347,15 +2433,24 @@ 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->datums = datums ? (Datum *) palloc0(key->partnatts * sizeof(Datum)) + : NULL; + bound->content = (RangeDatumContent *) palloc0( + (datums ? key->partnatts : 1) * sizeof(RangeDatumContent)); bound->lower = lower; i = 0; + + /* If default, datums are NULL */ + if (!datums) + bound->content[i] = RANGE_DATUM_DEFAULT; + foreach(lc, datums) { - PartitionRangeDatum *datum = castNode(PartitionRangeDatum, lfirst(lc)); + Node *node = lfirst(lc); + PartitionRangeDatum *datum; + + datum = castNode(PartitionRangeDatum, node); /* What's contained in this range datum? */ bound->content[i] = !datum->infinite @@ -2409,9 +2504,19 @@ partition_rbound_cmp(PartitionKey key, for (i = 0; i < key->partnatts; i++) { /* - * First, handle cases involving infinity, which don't require - * invoking the comparison proc. + * First, handle cases involving infinity and default, which don't + * require invoking the comparison proc. */ + if (content1[i] == RANGE_DATUM_DEFAULT || + content2[i] == RANGE_DATUM_DEFAULT) + { + if (content1[i] == content2[i]) + return 0; + else if (content1[i] == RANGE_DATUM_DEFAULT) + return -1; + else + return 1; + } if (content1[i] != RANGE_DATUM_FINITE && content2[i] != RANGE_DATUM_FINITE) @@ -2462,6 +2567,9 @@ partition_rbound_datum_cmp(PartitionKey key, for (i = 0; i < key->partnatts; i++) { + if (rb_content[i] == RANGE_DATUM_DEFAULT) + continue; + if (rb_content[i] != RANGE_DATUM_FINITE) return rb_content[i] == RANGE_DATUM_NEG_INF ? -1 : 1; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index ac99859..3b86218 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -2681,7 +2681,6 @@ ForValues: /* * A default partition, that can be partition of either LIST or * RANGE partitioned table. - * Currently this is supported only for LIST partition. */ | DEFAULT { diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index e861195..a92d77b 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3286,11 +3286,6 @@ transformPartitionBound(ParseState *pstate, Relation parent, if (spec->is_default) { - if (strategy != PARTITION_STRATEGY_LIST) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TABLE_DEFINITION), - errmsg("default partition is supported only for list partitioned table"))); - /* * In case of the default partition, parser had no way to identify the * partition strategy. Assign the parent strategy to the default diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 7b6bae7..43eab21 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -8680,6 +8680,12 @@ get_rule_expr(Node *node, deparse_context *context, list_length(spec->lowerdatums) == list_length(spec->upperdatums)); + if (spec->is_default) + { + appendStringInfoString(buf, "DEFAULT"); + break; + } + appendStringInfoString(buf, "FOR VALUES FROM ("); sep = ""; foreach(cell, spec->lowerdatums) diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 75f3617..eb483ba 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -3340,6 +3340,19 @@ CREATE TABLE part2 ( ); ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20); INFO: partition constraint for table "part2" is implied by existing constraints +-- Create default partition +CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT; +-- Only one default partition is allowed, hence, following should give error +CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS); +ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT; +ERROR: a default partition "partr_def1" already exists +-- Overlapping partitions cannot be attached, hence, following should give error +INSERT INTO partr_def1 VALUES (2, 10); +CREATE TABLE part3 (LIKE range_parted); +ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20); +ERROR: default partition contains row(s) that would overlap with partition being created +-- Attaching partitions should be successful when there are no overlapping rows +ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20); -- check that leaf partitions are scanned when attaching a partitioned -- table CREATE TABLE part_5 ( diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out index 3bd663e..f352662 100644 --- a/src/test/regress/expected/create_table.out +++ b/src/test/regress/expected/create_table.out @@ -517,9 +517,6 @@ 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 --- range partition cannot have default partition -CREATE TABLE fail_part PARTITION OF range_parted DEFAULT; -ERROR: default partition is supported only for list partitioned table -- 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); @@ -601,6 +598,16 @@ 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" +-- Create a default partition for range partitioned table +CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT; +-- More than one default partition is not allowed, so this should give error +CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT; +ERROR: a default partition "range2_default" already exists +-- Check if the range for default partitions overlap +INSERT INTO range_parted2 VALUES (85); +CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90); +ERROR: default partition contains row(s) that would overlap with partition being created +CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100); -- now check for multi-column range partition key CREATE TABLE range_parted3 ( a int, @@ -614,6 +621,7 @@ 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 fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20); ERROR: partition "fail_part" would overlap partition "part12" +CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT; -- 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 diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out index f9eef3f..9e5307a 100644 --- a/src/test/regress/expected/insert.out +++ b/src/test/regress/expected/insert.out @@ -271,6 +271,15 @@ insert into range_parted values ('b', 10); insert into range_parted values ('a'); ERROR: no partition of relation "range_parted" found for row DETAIL: Partition key of the failing row contains (a, (b + 0)) = (a, null). +-- Check default partition +create table part_def partition of range_parted default; +-- fail +insert into part_def values ('b', 10); +ERROR: new row for relation "part_def" violates partition constraint +DETAIL: Failing row contains (b, 10). +-- ok +insert into part_def values ('c', 10); +insert into range_parted values (null, null); select tableoid::regclass, * from range_parted; tableoid | a | b ----------+---+---- @@ -280,7 +289,9 @@ select tableoid::regclass, * from range_parted; part3 | b | 1 part4 | b | 10 part4 | b | 10 -(6 rows) + part_def | c | 10 + part_def | | +(8 rows) -- ok insert into list_parted values (null, 1); @@ -435,6 +446,35 @@ with ins (a, b, c) as mlparted4 | 1 | 30 | 39 (5 rows) +-- Check multi-level default partition +create table mlparted_def partition of mlparted default partition by range(a); +create table mlparted_def1 partition of mlparted_def for values from (40) to (50); +create table mlparted_def2 partition of mlparted_def for values from (50) to (60); +insert into mlparted values (40, 100); +insert into mlparted_def1 values (42, 100); +insert into mlparted_def2 values (54, 50); +-- fail +insert into mlparted values (70, 100); +ERROR: no partition of relation "mlparted_def" found for row +DETAIL: Partition key of the failing row contains (a) = (70). +insert into mlparted_def1 values (52, 50); +ERROR: new row for relation "mlparted_def1" violates partition constraint +DETAIL: Failing row contains (52, 50). +insert into mlparted_def2 values (34, 50); +ERROR: new row for relation "mlparted_def2" violates partition constraint +DETAIL: Failing row contains (34, 50). +-- ok +create table mlparted_defd partition of mlparted_def default; +insert into mlparted values (70, 100); +select tableoid::regclass, * from mlparted_def; + tableoid | a | b +---------------+----+----- + mlparted_def1 | 40 | 100 + mlparted_def1 | 42 | 100 + mlparted_def2 | 54 | 50 + mlparted_defd | 70 | 100 +(4 rows) + -- check that message shown after failure to find a partition shows the -- appropriate key description (or none) in various situations create table key_desc (a int, b int) partition by list ((a+0)); diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index 6750152..e996640 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -77,6 +77,10 @@ mlparted12|f mlparted2|f mlparted3|f mlparted4|f +mlparted_def|f +mlparted_def1|f +mlparted_def2|f +mlparted_defd|f money_data|f num_data|f num_exp_add|t diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out index 9912ef2..cfb6aa8 100644 --- a/src/test/regress/expected/update.out +++ b/src/test/regress/expected/update.out @@ -218,6 +218,15 @@ ERROR: new row for relation "part_b_10_b_20" violates partition constraint DETAIL: Failing row contains (b, 9). -- ok update range_parted set b = b + 1 where b = 10; +-- Creating default partition for range +create table part_def partition of range_parted default; +insert into range_parted values ('c', 9); +-- ok +update part_def set a = 'd' where a = 'c'; +-- fail +update part_def set a = 'a' where a = 'd'; +ERROR: new row for relation "part_def" violates partition constraint +DETAIL: Failing row contains (a, 9). create table list_parted ( a text, b int diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index 36c56aa..f319fbb 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -2169,6 +2169,21 @@ CREATE TABLE part2 ( ); ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20); +-- Create default partition +CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT; + +-- Only one default partition is allowed, hence, following should give error +CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS); +ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT; + +-- Overlapping partitions cannot be attached, hence, following should give error +INSERT INTO partr_def1 VALUES (2, 10); +CREATE TABLE part3 (LIKE range_parted); +ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20); + +-- Attaching partitions should be successful when there are no overlapping rows +ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20); + -- check that leaf partitions are scanned when attaching a partitioned -- table CREATE TABLE part_5 ( diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql index f44c0e0..2011cbf 100644 --- a/src/test/regress/sql/create_table.sql +++ b/src/test/regress/sql/create_table.sql @@ -486,8 +486,6 @@ 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); --- range partition cannot have default partition -CREATE TABLE fail_part PARTITION OF range_parted DEFAULT; -- 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); @@ -559,6 +557,17 @@ 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); CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50); +-- Create a default partition for range partitioned table +CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT; + +-- More than one default partition is not allowed, so this should give error +CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT; + +-- Check if the range for default partitions overlap +INSERT INTO range_parted2 VALUES (85); +CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90); +CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100); + -- now check for multi-column range partition key CREATE TABLE range_parted3 ( a int, @@ -572,6 +581,7 @@ CREATE TABLE part10 PARTITION OF range_parted3 FOR VALUES FROM (1, unbounded) TO 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 fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20); +CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT; -- cannot create a partition that says column b is allowed to range -- from -infinity to +infinity, while there exist partitions that have diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql index c120713..e72891f 100644 --- a/src/test/regress/sql/insert.sql +++ b/src/test/regress/sql/insert.sql @@ -168,8 +168,16 @@ insert into range_parted values ('b', 1); insert into range_parted values ('b', 10); -- fail (partition key (b+0) is null) insert into range_parted values ('a'); -select tableoid::regclass, * from range_parted; +-- Check default partition +create table part_def partition of range_parted default; +-- fail +insert into part_def values ('b', 10); +-- ok +insert into part_def values ('c', 10); +insert into range_parted values (null, null); + +select tableoid::regclass, * from range_parted; -- ok insert into list_parted values (null, 1); insert into list_parted (a) values ('aA'); @@ -277,6 +285,23 @@ with ins (a, b, c) as (insert into mlparted (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *) select a, b, min(c), max(c) from ins group by a, b order by 1; +-- Check multi-level default partition +create table mlparted_def partition of mlparted default partition by range(a); +create table mlparted_def1 partition of mlparted_def for values from (40) to (50); +create table mlparted_def2 partition of mlparted_def for values from (50) to (60); +insert into mlparted values (40, 100); +insert into mlparted_def1 values (42, 100); +insert into mlparted_def2 values (54, 50); +-- fail +insert into mlparted values (70, 100); +insert into mlparted_def1 values (52, 50); +insert into mlparted_def2 values (34, 50); +-- ok +create table mlparted_defd partition of mlparted_def default; +insert into mlparted values (70, 100); + +select tableoid::regclass, * from mlparted_def; + -- check that message shown after failure to find a partition shows the -- appropriate key description (or none) in various situations create table key_desc (a int, b int) partition by list ((a+0)); diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql index 44fb0dc..1ce7a94 100644 --- a/src/test/regress/sql/update.sql +++ b/src/test/regress/sql/update.sql @@ -125,6 +125,14 @@ update range_parted set b = b - 1 where b = 10; -- ok update range_parted set b = b + 1 where b = 10; +-- Creating default partition for range +create table part_def partition of range_parted default; +insert into range_parted values ('c', 9); +-- ok +update part_def set a = 'd' where a = 'c'; +-- fail +update part_def set a = 'a' where a = 'd'; + create table list_parted ( a text, b int