diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index 7f2fd58..4972823 100644 --- a/src/backend/catalog/partition.c +++ b/src/backend/catalog/partition.c @@ -35,6 +35,7 @@ #include "nodes/parsenodes.h" #include "optimizer/clauses.h" #include "optimizer/planmain.h" +#include "optimizer/prep.h" #include "optimizer/var.h" #include "rewrite/rewriteManip.h" #include "storage/lmgr.h" @@ -88,9 +89,12 @@ typedef struct PartitionBoundInfoData * partitioned table) */ int null_index; /* Index of the null-accepting partition; -1 * if there isn't one */ + int default_index; /* Index of the default partition if any; -1 + * if there isn't one */ } PartitionBoundInfoData; #define partition_bound_accepts_nulls(bi) ((bi)->null_index != -1) +#define partition_bound_has_default(bi) ((bi)->default_index != -1) /* * When qsort'ing partition bounds after reading from the catalog, each bound @@ -121,14 +125,15 @@ static int32 qsort_partition_rbound_cmp(const void *a, const void *b, static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy, bool *need_relabel); static Expr *make_partition_op_expr(PartitionKey key, int keynum, - uint16 strategy, Expr *arg1, Expr *arg2); + uint16 strategy, Expr *arg1, Expr *arg2, + bool is_default); 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); -static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec); +static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec); static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec); static List *generate_partition_qual(Relation rel); @@ -147,6 +152,8 @@ static int32 partition_bound_cmp(PartitionKey key, static int partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo, void *probe, bool probe_is_bound, bool *is_equal); +static void check_default_allows_bound(Relation parent, + PartitionBoundSpec *new_spec); /* * RelationBuildPartitionDesc @@ -174,6 +181,7 @@ RelationBuildPartitionDesc(Relation rel) /* List partitioning specific */ PartitionListValue **all_values = NULL; int null_index = -1; + int default_index = -1; /* Range partitioning specific */ PartitionRangeBound **rbounds = NULL; @@ -258,6 +266,12 @@ RelationBuildPartitionDesc(Relation rel) Const *val = lfirst(c); PartitionListValue *list_value = NULL; + if (isDefaultPartitionBound((Node *) lfirst(c))) + { + default_index = i; + continue; + } + if (!val->constisnull) { list_value = (PartitionListValue *) @@ -504,6 +518,17 @@ RelationBuildPartitionDesc(Relation rel) else boundinfo->null_index = -1; + /* Assign mapping index for default partition. */ + if (default_index != -1) + { + Assert(default_index >= 0 && + mapping[default_index] == -1); + mapping[default_index] = next_index++; + boundinfo->default_index = mapping[default_index]; + } + else + boundinfo->default_index = -1; + /* All partition must now have a valid mapping */ Assert(next_index == nparts); break; @@ -559,6 +584,12 @@ RelationBuildPartitionDesc(Relation rel) } } boundinfo->indexes[i] = -1; + + /* + * Currently range partition do not have default partition + * support. + */ + boundinfo->default_index = -1; break; } @@ -608,6 +639,9 @@ partition_bounds_equal(PartitionKey key, if (b1->null_index != b2->null_index) return false; + if (b1->default_index != b2->default_index) + return false; + for (i = 0; i < b1->ndatums; i++) { int j; @@ -670,6 +704,7 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound) PartitionBoundSpec *spec = (PartitionBoundSpec *) bound; PartitionKey key = RelationGetPartitionKey(parent); PartitionDesc partdesc = RelationGetPartitionDesc(parent); + PartitionBoundInfo boundinfo = partdesc->boundinfo; ParseState *pstate = make_parsestate(NULL); int with = -1; bool overlap = false; @@ -682,13 +717,29 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound) if (partdesc->nparts > 0) { - PartitionBoundInfo boundinfo = partdesc->boundinfo; ListCell *cell; Assert(boundinfo && boundinfo->strategy == PARTITION_STRATEGY_LIST && (boundinfo->ndatums > 0 || - partition_bound_accepts_nulls(boundinfo))); + partition_bound_accepts_nulls(boundinfo) || + partition_bound_has_default(boundinfo))); + + /* + * Default partition cannot be added if there already + * exists one. + */ + cell = list_head(spec->listdatums); + if (cell && isDefaultPartitionBound((Node *) lfirst(cell))) + { + if (partition_bound_has_default(boundinfo)) + { + overlap = true; + with = boundinfo->default_index; + } + + break; + } foreach(cell, spec->listdatums) { @@ -836,6 +887,139 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound) relname, get_rel_name(partdesc->oids[with])), parser_errposition(pstate, spec->location))); } + + /* + * If a default partition exists, it's partition constraint will change + * after the addition of this new partition such that it won't allow any + * row that qualifies for this new partition. So, check if the existing + * data in the default partition satisfies this *would be* default + * partition constraint. + */ + if (partdesc->nparts > 0 && partition_bound_has_default(boundinfo)) + check_default_allows_bound(parent, spec); +} + +/* + * check_default_allows_bound + * + * Checks if there exists any row in default partition that passes the check + * for constraints of new partition, if any reports an error. + */ +static void +check_default_allows_bound(Relation parent, PartitionBoundSpec *new_spec) +{ + Relation default_rel; + int default_index; + List *new_part_constraints = NIL; + 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 = (List *) eval_const_expressions(NULL, + (Node *) new_part_constraints); + new_part_constraints = + (List *) canonicalize_qual((Expr *) new_part_constraints); + new_part_constraints = list_make1(make_ands_explicit(new_part_constraints)); + + /* Generate the constraint and default execution states. */ + default_index = parent->rd_partdesc->boundinfo->default_index; + default_rel = heap_open(parent->rd_partdesc->oids[default_index], + AccessExclusiveLock); + + if (default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + all_parts = find_all_inheritors(RelationGetRelid(default_rel), + AccessExclusiveLock, NULL); + else + all_parts = list_make1_oid(RelationGetRelid(default_rel)); + + foreach(lc, all_parts) + { + Oid part_relid = lfirst_oid(lc); + Relation part_rel; + Expr *constr; + Expr *partition_constraint; + EState *estate; + HeapTuple tuple; + ExprState *partqualstate = NULL; + Snapshot snapshot; + TupleDesc tupdesc; + ExprContext *econtext; + HeapScanDesc scan; + MemoryContext oldCxt; + TupleTableSlot *tupslot; + + /* Lock already taken above. */ + if (part_relid != RelationGetRelid(default_rel)) + part_rel = heap_open(part_relid, NoLock); + else + part_rel = default_rel; + + /* + * Skip if it's a partitioned table. Only RELKIND_RELATION relations + * (ie, leaf partitions) need to be scanned. + */ + if (part_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + heap_close(part_rel, NoLock); + continue; + } + + tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel)); + constr = linitial(new_part_constraints); + partition_constraint = (Expr *) map_partition_varattnos((List *) constr, + 1, part_rel, parent); + estate = CreateExecutorState(); + + /* Build expression execution states for partition check quals */ + partqualstate = ExecPrepareExpr(partition_constraint, estate); + + econtext = GetPerTupleExprContext(estate); + snapshot = RegisterSnapshot(GetLatestSnapshot()); + scan = heap_beginscan(part_rel, snapshot, 0, NULL); + tupslot = MakeSingleTupleTableSlot(tupdesc); + + /* + * Switch to per-tuple memory context and reset it for each tuple + * produced, so we don't leak memory. + */ + oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); + + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + char *val_desc; + + ExecStoreTuple(tuple, tupslot, InvalidBuffer, false); + econtext->ecxt_scantuple = tupslot; + + if (partqualstate && ExecCheck(partqualstate, econtext)) + { + val_desc = + ExecBuildSlotValueDescription(RelationGetRelid(part_rel), + tupslot, tupdesc, NULL, 64); + + ereport(ERROR, + (errcode(ERRCODE_CHECK_VIOLATION), + errmsg("new default partition constraint is violated by some row"), + val_desc ? + errdetail("Violating row contains %s.", val_desc) : 0)); + } + + ResetExprContext(econtext); + CHECK_FOR_INTERRUPTS(); + } + + CacheInvalidateRelcache(part_rel); + MemoryContextSwitchTo(oldCxt); + heap_endscan(scan); + UnregisterSnapshot(snapshot); + ExecDropSingleTupleTableSlot(tupslot); + FreeExecutorState(estate); + heap_close(part_rel, NoLock); + } } /* @@ -884,6 +1068,16 @@ get_partition_parent(Oid relid) } /* + * Returns true if the partition bound is default. + */ +bool +isDefaultPartitionBound(Node *value) +{ + return (IsA(value, DefElem) && + !strcmp(castNode(DefElem, value)->defname, "default_partition")); +} + +/* * get_qual_from_partbound * Given a parser node for partition bound, return the list of executable * expressions as partition constraint @@ -901,7 +1095,7 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound) { case PARTITION_STRATEGY_LIST: Assert(spec->strategy == PARTITION_STRATEGY_LIST); - my_qual = get_qual_for_list(key, spec); + my_qual = get_qual_for_list(parent, spec); break; case PARTITION_STRATEGY_RANGE: @@ -1231,7 +1425,7 @@ get_partition_operator(PartitionKey key, int col, StrategyNumber strategy, */ static Expr * make_partition_op_expr(PartitionKey key, int keynum, - uint16 strategy, Expr *arg1, Expr *arg2) + uint16 strategy, Expr *arg1, Expr *arg2, bool is_default) { Oid operoid; bool need_relabel = false; @@ -1261,11 +1455,17 @@ make_partition_op_expr(PartitionKey key, int keynum, { ScalarArrayOpExpr *saopexpr; + if (is_default && + ((operoid = get_negator(operoid)) == InvalidOid)) + ereport(ERROR, (errcode(ERRCODE_RESTRICT_VIOLATION), + errmsg("DEFAULT partition cannot be used without negator of operator %s", + get_opname(operoid)))); + /* Build leftop = ANY (rightop) */ saopexpr = makeNode(ScalarArrayOpExpr); saopexpr->opno = operoid; saopexpr->opfuncid = get_opcode(operoid); - saopexpr->useOr = true; + saopexpr->useOr = !is_default; saopexpr->inputcollid = key->partcollation[keynum]; saopexpr->args = list_make2(arg1, arg2); saopexpr->location = -1; @@ -1297,8 +1497,9 @@ make_partition_op_expr(PartitionKey key, int keynum, * Returns a list of expressions to use as a list partition's constraint. */ static List * -get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec) +get_qual_for_list(Relation parent, PartitionBoundSpec *spec) { + PartitionKey key = RelationGetPartitionKey(parent); List *result; ArrayExpr *arr; Expr *opexpr; @@ -1309,6 +1510,8 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec) bool list_has_null = false; NullTest *nulltest1 = NULL, *nulltest2 = NULL; + List *listdatums = spec->listdatums; + bool is_default = isDefaultPartitionBound((Node *) linitial(listdatums)); /* Left operand is either a simple Var or arbitrary expression */ if (key->partattrs[0] != 0) @@ -1322,11 +1525,52 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec) keyCol = (Expr *) copyObject(linitial(key->partexprs)); /* + * In case of default partition for list, the partition constraint is + * basically any value that is not equal to any of the values in + * boundinfo->datums array. So, construct a list of constants from + * boundinfo->datums to pass to function make_partition_op_expr via + * ArrayExpr, which would return an negated expression for default + * partition. + */ + if (is_default) + { + int i; + MemoryContext oldcxt; + + PartitionBoundInfo boundinfo = + RelationGetPartitionDesc(parent)->boundinfo; + + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + + listdatums = NIL; + for (i = 0; i < boundinfo->ndatums; i++) + { + Const *val; + + /* Construct const from datum */ + val = makeConst(key->parttypid[0], + key->parttypmod[0], + key->parttypcoll[0], + key->parttyplen[0], + *boundinfo->datums[i], + false, /* isnull */ + true /* byval */ ); + + listdatums = lappend(listdatums, val); + } + + if (partition_bound_accepts_nulls(boundinfo)) + list_has_null = true; + + MemoryContextSwitchTo(oldcxt); + } + + /* * We must remove any NULL value in the list; we handle it separately * below. */ prev = NULL; - for (cell = list_head(spec->listdatums); cell; cell = next) + for (cell = list_head(listdatums); cell && !is_default; cell = next) { Const *val = (Const *) lfirst(cell); @@ -1335,14 +1579,13 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec) if (val->constisnull) { list_has_null = true; - spec->listdatums = list_delete_cell(spec->listdatums, - cell, prev); + listdatums = list_delete_cell(listdatums, cell, prev); } else prev = cell; } - if (!list_has_null) + if ((!list_has_null && !is_default) || (list_has_null && is_default)) { /* * Gin up a col IS NOT NULL test that will be AND'd with other @@ -1373,13 +1616,13 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec) : key->parttypid[0]; arr->array_collid = key->parttypcoll[0]; arr->element_typeid = key->parttypid[0]; - arr->elements = spec->listdatums; + arr->elements = listdatums; arr->multidims = false; arr->location = -1; /* Generate the main expression, i.e., keyCol = ANY (arr) */ opexpr = make_partition_op_expr(key, 0, BTEqualStrategyNumber, - keyCol, (Expr *) arr); + keyCol, (Expr *) arr, is_default); if (nulltest1) result = list_make2(nulltest1, opexpr); @@ -1593,7 +1836,7 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec) oldcxt = MemoryContextSwitchTo(estate->es_query_cxt); test_expr = make_partition_op_expr(key, i, BTEqualStrategyNumber, (Expr *) lower_val, - (Expr *) upper_val); + (Expr *) upper_val, false); fix_opfuncids((Node *) test_expr); test_exprstate = ExecInitExpr(test_expr, NULL); test_result = ExecEvalExprSwitchContext(test_exprstate, @@ -1617,7 +1860,7 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec) /* Equal, so generate keyCol = lower_val expression */ result = lappend(result, make_partition_op_expr(key, i, BTEqualStrategyNumber, - keyCol, (Expr *) lower_val)); + keyCol, (Expr *) lower_val, false)); i++; } @@ -1675,7 +1918,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec) make_partition_op_expr(key, j, strategy, keyCol, - (Expr *) lower_val)); + (Expr *) lower_val, + false)); } if (need_next_upper_arm && upper_val) @@ -1697,7 +1941,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec) make_partition_op_expr(key, j, strategy, keyCol, - (Expr *) upper_val)); + (Expr *) upper_val, + false)); } @@ -2033,10 +2278,26 @@ get_partition_for_tuple(PartitionDispatch *pd, */ if (cur_index < 0) { - result = -1; - *failed_at = parent; - *failed_slot = slot; - break; + /* + * If partitioned table has a default partition, return its + * sequence number. + */ + if (partition_bound_has_default(partdesc->boundinfo)) + { + result = parent->indexes[partdesc->boundinfo->default_index]; + + if (result >= 0) + break; + else + parent = pd[-result]; + } + else + { + result = -1; + *failed_at = parent; + *failed_slot = slot; + break; + } } else if (parent->indexes[cur_index] >= 0) { diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 4a899f1..dae592a 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -92,11 +92,6 @@ static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid, Bitmapset *modifiedCols, AclMode requiredPerms); static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt); -static char *ExecBuildSlotValueDescription(Oid reloid, - TupleTableSlot *slot, - TupleDesc tupdesc, - Bitmapset *modifiedCols, - int maxfieldlen); static char *ExecBuildSlotPartitionKeyDescription(Relation rel, Datum *values, bool *isnull, @@ -2174,7 +2169,7 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo, * column involved, that subset will be returned with a key identifying which * columns they are. */ -static char * +char * ExecBuildSlotValueDescription(Oid reloid, TupleTableSlot *slot, TupleDesc tupdesc, diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 2822331..bb59bfc 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -2676,6 +2676,23 @@ ForValues: $$ = (Node *) n; } + + /* + * A default partition, that can be partition of either LIST or RANGE + * partitioned table. + * Currently this is supported only for LIST partition. + */ + | DEFAULT + { + PartitionBoundSpec *n = makeNode(PartitionBoundSpec); + + n->listdatums = + list_make1(makeDefElem("default_partition", NULL, @1)); + n->location = @1; + + $$ = (Node *) n; + } + ; partbound_datum: diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index beb0995..bbd95d6 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -33,6 +33,7 @@ #include "catalog/heap.h" #include "catalog/index.h" #include "catalog/namespace.h" +#include "catalog/partition.h" #include "catalog/pg_am.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" @@ -3310,19 +3311,38 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound) false, false); if (spec->strategy != PARTITION_STRATEGY_LIST) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + { + /* + * In case of default partition, parser had no way to identify the + * partition strategy. Assign the parent strategy to default + * partition bound spec. + */ + if ((list_length(spec->listdatums) == 1) && + isDefaultPartitionBound((Node *) linitial(spec->listdatums))) + result_spec->strategy = PARTITION_STRATEGY_LIST; + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("invalid bound specification for a list partition"), - parser_errposition(pstate, exprLocation(bound)))); + parser_errposition(pstate, exprLocation(bound)))); + } result_spec->listdatums = NIL; foreach(cell, spec->listdatums) { A_Const *con = (A_Const *) lfirst(cell); - Node *value; + Node *value = lfirst(cell); ListCell *cell2; bool duplicate; + /* Perform the transformation only for non default partition. */ + if (isDefaultPartitionBound(value)) + { + result_spec->listdatums = lappend(result_spec->listdatums, + value); + continue; + } + value = (Node *) make_const(pstate, &con->val, con->location); value = coerce_to_target_type(pstate, value, exprType(value), diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 9234bc2..fffeb4f 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -8652,6 +8652,18 @@ get_rule_expr(Node *node, deparse_context *context, case PARTITION_STRATEGY_LIST: Assert(spec->listdatums != NIL); + /* + * If the boundspec is of Default partition, it does + * not have list of datums, but has only one node to + * indicate its a default partition. + */ + if (isDefaultPartitionBound( + (Node *) linitial(spec->listdatums))) + { + appendStringInfoString(buf, "DEFAULT"); + break; + } + appendStringInfoString(buf, "FOR VALUES"); appendStringInfoString(buf, " IN ("); sep = ""; diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 2abd087..b01ba07 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -2044,7 +2044,7 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, ""); /* Limited completion support for partition bound specification */ else if (TailMatches3("ATTACH", "PARTITION", MatchAny)) - COMPLETE_WITH_CONST("FOR VALUES"); + COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT"); else if (TailMatches2("FOR", "VALUES")) COMPLETE_WITH_LIST2("FROM (", "IN ("); @@ -2483,7 +2483,7 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, ""); /* Limited completion support for partition bound specification */ else if (TailMatches3("PARTITION", "OF", MatchAny)) - COMPLETE_WITH_CONST("FOR VALUES"); + COMPLETE_WITH_LIST2("FOR VALUES", "DEFAULT"); /* CREATE TABLESPACE */ else if (Matches3("CREATE", "TABLESPACE", MatchAny)) diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h index 25fb0a0..081c1e4 100644 --- a/src/include/catalog/partition.h +++ b/src/include/catalog/partition.h @@ -81,6 +81,7 @@ extern List *map_partition_varattnos(List *expr, int target_varno, Relation partrel, Relation parent); extern List *RelationGetPartitionQual(Relation rel); extern Expr *get_partition_qual_relid(Oid relid); +extern bool isDefaultPartitionBound(Node *value); /* For tuple routing */ extern PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel, diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 8cc5f3a..8bd574c 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -537,5 +537,10 @@ extern void CheckCmdReplicaIdentity(Relation rel, CmdType cmd); extern void CheckSubscriptionRelkind(char relkind, const char *nspname, const char *relname); +char *ExecBuildSlotValueDescription(Oid reloid, + TupleTableSlot *slot, + TupleDesc tupdesc, + Bitmapset *modifiedCols, + int maxfieldlen); #endif /* EXECUTOR_H */ diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index c88fd76..c31db25 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -3224,6 +3224,11 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS); ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); ERROR: partition "fail_part" would overlap partition "part_1" +-- attaching default partition overlaps if a default partition already exists +CREATE TABLE part_def1 PARTITION OF list_parted DEFAULT; +CREATE TABLE part_def2 (LIKE part_1 INCLUDING CONSTRAINTS); +ALTER TABLE list_parted ATTACH PARTITION part_def2 DEFAULT; +ERROR: partition "part_def2" would overlap partition "part_def1" -- check validation when attaching list partitions CREATE TABLE list_parted2 ( a int, @@ -3237,6 +3242,16 @@ ERROR: partition constraint is violated by some row -- should be ok after deleting the bad row DELETE FROM part_2; ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2); +-- check partition cannot be attached if default has some row for its values +CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT; +INSERT INTO list_parted2_def VALUES (11, 'z'); +CREATE TABLE part_3 (LIKE list_parted2); +ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11); +ERROR: new default partition constraint is violated by some row +DETAIL: Violating row contains (11, z). +-- should be ok after deleting the bad row +DELETE FROM list_parted2_def WHERE a = 11; +ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11); -- adding constraints that describe the desired partition constraint -- (or more restrictive) will help skip the validation scan CREATE TABLE part_3_4 ( @@ -3289,9 +3304,22 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5); ERROR: partition constraint is violated by some row -- delete the faulting row and also add a constraint to skip the scan DELETE FROM part_5_a WHERE a NOT IN (3); -ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL; -ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5); +ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5, 55)), ALTER a SET NOT NULL; +ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5, 55); INFO: partition constraint for table "part_5" is implied by existing constraints +-- check that leaf partitons of default partition are scanned when +-- attaching a partitioned table. +ALTER TABLE part_5 DROP CONSTRAINT check_a; +CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a); +CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (44, 55); +INSERT INTO part5_def_p1 VALUES (55, 'y'); +CREATE TABLE part5_p1 (LIKE part_5); +ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y'); +ERROR: new default partition constraint is violated by some row +DETAIL: Violating row contains (55, y). +-- should be ok after deleting the bad row +DELETE FROM part5_def_p1 WHERE b = 'y'; +ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y'); -- check that the table being attached is not already a partition ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2); ERROR: "part_2" is already a partition diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out index 39edf04..2588497 100644 --- a/src/test/regress/expected/create_table.out +++ b/src/test/regress/expected/create_table.out @@ -449,6 +449,7 @@ CREATE TABLE list_parted ( CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1'); CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2); CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null); +CREATE TABLE part_default PARTITION OF list_parted DEFAULT; CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1'); ERROR: syntax error at or near "int" LINE 1: ... fail_part PARTITION OF list_parted FOR VALUES IN (int '1'); @@ -457,6 +458,8 @@ CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int); ERROR: syntax error at or near "::" LINE 1: ...fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int); ^ +CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT; +ERROR: partition "fail_default_part" would overlap partition "part_default" -- syntax does not allow empty list of values for list partitions CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (); ERROR: syntax error at or near ")" @@ -493,6 +496,11 @@ ERROR: TO must specify exactly one value per partitioning column -- cannot specify null values in range bounds CREATE TABLE fail_part PARTITION OF range_parted FOR VALUES FROM (null) TO (unbounded); ERROR: cannot specify NULL in range bound +-- range partition cannot have default partition +CREATE TABLE fail_part PARTITION OF range_parted DEFAULT; +ERROR: invalid bound specification for a range partition +LINE 1: 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); CREATE TABLE fail_part PARTITION OF range_parted_multicol FOR VALUES FROM (1, UNBOUNDED, 1) TO (UNBOUNDED, 1, 1); @@ -544,10 +552,16 @@ CREATE TABLE list_parted2 ( ) PARTITION BY LIST (a); CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z'); CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b'); +CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT; CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null); ERROR: partition "fail_part" would overlap partition "part_null_z" CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c'); ERROR: partition "fail_part" would overlap partition "part_ab" +-- check default partition overlap +INSERT INTO list_parted2 VALUES('X'); +CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y'); +ERROR: new default partition constraint is violated by some row +DETAIL: Violating row contains (X). CREATE TABLE range_parted2 ( a int ) PARTITION BY RANGE (a); diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out index 8b0752a..841f7c3 100644 --- a/src/test/regress/expected/insert.out +++ b/src/test/regress/expected/insert.out @@ -202,6 +202,7 @@ create table list_parted ( create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb'); create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd'); create table part_null partition of list_parted FOR VALUES IN (null); +create table part_default partition of list_parted default; -- fail insert into part_aa_bb values ('cc', 1); ERROR: new row for relation "part_aa_bb" violates partition constraint @@ -212,24 +213,46 @@ DETAIL: Failing row contains (AAa, 1). insert into part_aa_bb values (null); ERROR: new row for relation "part_aa_bb" violates partition constraint DETAIL: Failing row contains (null, null). +insert into part_default values ('aa', 2); +ERROR: new row for relation "part_default" violates partition constraint +DETAIL: Failing row contains (aa, 2). +insert into part_default values (null, 2); +ERROR: new row for relation "part_default" violates partition constraint +DETAIL: Failing row contains (null, 2). -- ok insert into part_cc_dd values ('cC', 1); insert into part_null values (null, 0); +insert into part_default values ('Zz', 2); -- check in case of multi-level partitioned table create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b); create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10); create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20); +-- check in case of multi-level default partitioned table +drop table part_default; +create table part_default partition of list_parted default partition by range(b); +create table part_default_p1 partition of part_default for values from (20) to (30); +create table part_default_p2 partition of part_default for values from (30) to (40); -- fail insert into part_ee_ff1 values ('EE', 11); ERROR: new row for relation "part_ee_ff1" violates partition constraint DETAIL: Failing row contains (EE, 11). +insert into part_default_p2 values ('gg', 43); +ERROR: new row for relation "part_default_p2" violates partition constraint +DETAIL: Failing row contains (gg, 43). -- fail (even the parent's, ie, part_ee_ff's partition constraint applies) insert into part_ee_ff1 values ('cc', 1); ERROR: new row for relation "part_ee_ff1" violates partition constraint DETAIL: Failing row contains (cc, 1). +insert into part_default values ('gg', 43); +ERROR: no partition of relation "part_default" found for row +DETAIL: Partition key of the failing row contains (b) = (43). -- ok insert into part_ee_ff1 values ('ff', 1); insert into part_ee_ff2 values ('ff', 11); +insert into part_default_p1 values ('cd', 25); +insert into part_default_p2 values ('gg', 35); +insert into list_parted values ('ab', 21); +drop table part_default; -- Check tuple routing for partitioned tables -- fail insert into range_parted values ('a', 0); diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out index 9366f04..0f41e7e 100644 --- a/src/test/regress/expected/update.out +++ b/src/test/regress/expected/update.out @@ -218,5 +218,24 @@ 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; +create table list_parted ( + a text, + b int +) partition by list (a); +create table list_part1 partition of list_parted for values in ('a', 'b'); +create table list_default partition of list_parted default; +insert into list_part1 values ('a', 1); +insert into list_default values ('d', 10); +-- fail +update list_part1 set a = 'c' where a = 'a'; +ERROR: new row for relation "list_part1" violates partition constraint +DETAIL: Failing row contains (c, 1). +update list_default set a = 'a' where a = 'd'; +ERROR: new row for relation "list_default" violates partition constraint +DETAIL: Failing row contains (a, 10). +-- ok +update list_part1 set a = 'b' where a = 'a'; +update list_default set a = 'x' where a = 'd'; -- cleanup drop table range_parted; +drop table list_parted; diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index c0e2972..dab3cfa 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -2075,6 +2075,10 @@ SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::reg -- check that the new partition won't overlap with an existing partition CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS); ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +-- attaching default partition overlaps if a default partition already exists +CREATE TABLE part_def1 PARTITION OF list_parted DEFAULT; +CREATE TABLE part_def2 (LIKE part_1 INCLUDING CONSTRAINTS); +ALTER TABLE list_parted ATTACH PARTITION part_def2 DEFAULT; -- check validation when attaching list partitions CREATE TABLE list_parted2 ( @@ -2091,6 +2095,15 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2); DELETE FROM part_2; ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2); +-- check partition cannot be attached if default has some row for its values +CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT; +INSERT INTO list_parted2_def VALUES (11, 'z'); +CREATE TABLE part_3 (LIKE list_parted2); +ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11); +-- should be ok after deleting the bad row +DELETE FROM list_parted2_def WHERE a = 11; +ALTER TABLE list_parted2 ATTACH PARTITION part_3 FOR VALUES IN (11); + -- adding constraints that describe the desired partition constraint -- (or more restrictive) will help skip the validation scan CREATE TABLE part_3_4 ( @@ -2149,9 +2162,20 @@ ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5); -- delete the faulting row and also add a constraint to skip the scan DELETE FROM part_5_a WHERE a NOT IN (3); -ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5)), ALTER a SET NOT NULL; -ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5); - +ALTER TABLE part_5 ADD CONSTRAINT check_a CHECK (a IN (5, 55)), ALTER a SET NOT NULL; +ALTER TABLE list_parted2 ATTACH PARTITION part_5 FOR VALUES IN (5, 55); + +-- check that leaf partitons of default partition are scanned when +-- attaching a partitioned table. +ALTER TABLE part_5 DROP CONSTRAINT check_a; +CREATE TABLE part5_def PARTITION OF part_5 DEFAULT PARTITION BY LIST(a); +CREATE TABLE part5_def_p1 PARTITION OF part5_def FOR VALUES IN (44, 55); +INSERT INTO part5_def_p1 VALUES (55, 'y'); +CREATE TABLE part5_p1 (LIKE part_5); +ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y'); +-- should be ok after deleting the bad row +DELETE FROM part5_def_p1 WHERE b = 'y'; +ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y'); -- check that the table being attached is not already a partition ALTER TABLE list_parted2 ATTACH PARTITION part_2 FOR VALUES IN (2); diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql index 5a27743..31cbab1 100644 --- a/src/test/regress/sql/create_table.sql +++ b/src/test/regress/sql/create_table.sql @@ -439,8 +439,10 @@ CREATE TABLE list_parted ( CREATE TABLE part_1 PARTITION OF list_parted FOR VALUES IN ('1'); CREATE TABLE part_2 PARTITION OF list_parted FOR VALUES IN (2); CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null); +CREATE TABLE part_default PARTITION OF list_parted DEFAULT; CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (int '1'); CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('1'::int); +CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT; -- syntax does not allow empty list of values for list partitions CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN (); @@ -467,6 +469,8 @@ 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); @@ -512,9 +516,13 @@ CREATE TABLE list_parted2 ( ) PARTITION BY LIST (a); CREATE TABLE part_null_z PARTITION OF list_parted2 FOR VALUES IN (null, 'z'); CREATE TABLE part_ab PARTITION OF list_parted2 FOR VALUES IN ('a', 'b'); +CREATE TABLE list_parted2_def PARTITION OF list_parted2 DEFAULT; CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN (null); CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c'); +-- check default partition overlap +INSERT INTO list_parted2 VALUES('X'); +CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y'); CREATE TABLE range_parted2 ( a int diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql index db8967b..a9f2289 100644 --- a/src/test/regress/sql/insert.sql +++ b/src/test/regress/sql/insert.sql @@ -118,27 +118,42 @@ create table list_parted ( create table part_aa_bb partition of list_parted FOR VALUES IN ('aa', 'bb'); create table part_cc_dd partition of list_parted FOR VALUES IN ('cc', 'dd'); create table part_null partition of list_parted FOR VALUES IN (null); +create table part_default partition of list_parted default; -- fail insert into part_aa_bb values ('cc', 1); insert into part_aa_bb values ('AAa', 1); insert into part_aa_bb values (null); +insert into part_default values ('aa', 2); +insert into part_default values (null, 2); -- ok insert into part_cc_dd values ('cC', 1); insert into part_null values (null, 0); +insert into part_default values ('Zz', 2); -- check in case of multi-level partitioned table create table part_ee_ff partition of list_parted for values in ('ee', 'ff') partition by range (b); create table part_ee_ff1 partition of part_ee_ff for values from (1) to (10); create table part_ee_ff2 partition of part_ee_ff for values from (10) to (20); +-- check in case of multi-level default partitioned table +drop table part_default; +create table part_default partition of list_parted default partition by range(b); +create table part_default_p1 partition of part_default for values from (20) to (30); +create table part_default_p2 partition of part_default for values from (30) to (40); -- fail insert into part_ee_ff1 values ('EE', 11); +insert into part_default_p2 values ('gg', 43); -- fail (even the parent's, ie, part_ee_ff's partition constraint applies) insert into part_ee_ff1 values ('cc', 1); +insert into part_default values ('gg', 43); -- ok insert into part_ee_ff1 values ('ff', 1); insert into part_ee_ff2 values ('ff', 11); +insert into part_default_p1 values ('cd', 25); +insert into part_default_p2 values ('gg', 35); +insert into list_parted values ('ab', 21); +drop table part_default; -- Check tuple routing for partitioned tables diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql index 6637119..02840a4 100644 --- a/src/test/regress/sql/update.sql +++ b/src/test/regress/sql/update.sql @@ -125,5 +125,22 @@ update range_parted set b = b - 1 where b = 10; -- ok update range_parted set b = b + 1 where b = 10; +create table list_parted ( + a text, + b int +) partition by list (a); +create table list_part1 partition of list_parted for values in ('a', 'b'); +create table list_default partition of list_parted default; +insert into list_part1 values ('a', 1); +insert into list_default values ('d', 10); + +-- fail +update list_part1 set a = 'c' where a = 'a'; +update list_default set a = 'a' where a = 'd'; +-- ok +update list_part1 set a = 'b' where a = 'a'; +update list_default set a = 'x' where a = 'd'; + -- cleanup drop table range_parted; +drop table list_parted;