default_partition_v14.patch
application/octet-stream
Filename: default_partition_v14.patch
Type: application/octet-stream
Part: 0
Patch
Format: unified
Series: patch v14
| File | + | − |
|---|---|---|
| src/backend/catalog/partition.c | 286 | 22 |
| src/backend/executor/execMain.c | 1 | 6 |
| src/backend/parser/gram.y | 17 | 0 |
| src/backend/parser/parse_utilcmd.c | 24 | 4 |
| src/backend/utils/adt/ruleutils.c | 12 | 0 |
| src/bin/psql/tab-complete.c | 2 | 2 |
| src/include/catalog/partition.h | 1 | 0 |
| src/include/executor/executor.h | 5 | 0 |
| src/test/regress/expected/alter_table.out | 30 | 2 |
| src/test/regress/expected/create_table.out | 14 | 0 |
| src/test/regress/expected/insert.out | 23 | 0 |
| src/test/regress/expected/update.out | 19 | 0 |
| src/test/regress/sql/alter_table.sql | 27 | 3 |
| src/test/regress/sql/create_table.sql | 8 | 0 |
| src/test/regress/sql/insert.sql | 15 | 0 |
| src/test/regress/sql/update.sql | 17 | 0 |
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 7f2fd58..b8217b7 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,9 @@ 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 +1526,54 @@ 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;
+ int ndatums = 0;
+ MemoryContext oldcxt;
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ PartitionBoundInfo boundinfo = pdesc->boundinfo;
+
+ ndatums = (pdesc->nparts > 0) ? boundinfo->ndatums : 0;
+
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+ listdatums = NIL;
+ for (i = 0; i < 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 (boundinfo && 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 +1582,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 +1619,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 +1839,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 +1863,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 +1921,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 +1944,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 +2281,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;