aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/catalog/heap.c41
-rw-r--r--src/backend/catalog/partition.c644
-rw-r--r--src/backend/commands/tablecmds.c187
-rw-r--r--src/backend/nodes/copyfuncs.c1
-rw-r--r--src/backend/nodes/equalfuncs.c1
-rw-r--r--src/backend/nodes/outfuncs.c1
-rw-r--r--src/backend/nodes/readfuncs.c1
-rw-r--r--src/backend/parser/gram.y27
-rw-r--r--src/backend/parser/parse_utilcmd.c12
-rw-r--r--src/backend/utils/adt/ruleutils.c8
10 files changed, 820 insertions, 103 deletions
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 45ee9ac8b9c..05e70818e77 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1759,7 +1759,8 @@ heap_drop_with_catalog(Oid relid)
{
Relation rel;
HeapTuple tuple;
- Oid parentOid = InvalidOid;
+ Oid parentOid = InvalidOid,
+ defaultPartOid = InvalidOid;
/*
* To drop a partition safely, we must grab exclusive lock on its parent,
@@ -1775,6 +1776,14 @@ heap_drop_with_catalog(Oid relid)
{
parentOid = get_partition_parent(relid);
LockRelationOid(parentOid, AccessExclusiveLock);
+
+ /*
+ * If this is not the default partition, dropping it will change the
+ * default partition's partition constraint, so we must lock it.
+ */
+ defaultPartOid = get_default_partition_oid(parentOid);
+ if (OidIsValid(defaultPartOid) && relid != defaultPartOid)
+ LockRelationOid(defaultPartOid, AccessExclusiveLock);
}
ReleaseSysCache(tuple);
@@ -1826,6 +1835,13 @@ heap_drop_with_catalog(Oid relid)
RemovePartitionKeyByRelId(relid);
/*
+ * If the relation being dropped is the default partition itself,
+ * invalidate its entry in pg_partitioned_table.
+ */
+ if (relid == defaultPartOid)
+ update_default_partition_oid(parentOid, InvalidOid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
@@ -1885,6 +1901,14 @@ heap_drop_with_catalog(Oid relid)
if (OidIsValid(parentOid))
{
/*
+ * If this is not the default partition, the partition constraint of
+ * the default partition has changed to include the portion of the key
+ * space previously covered by the dropped partition.
+ */
+ if (OidIsValid(defaultPartOid) && relid != defaultPartOid)
+ CacheInvalidateRelcacheByRelid(defaultPartOid);
+
+ /*
* Invalidate the parent's relcache so that the partition is no longer
* included in its partition descriptor.
*/
@@ -3138,6 +3162,7 @@ StorePartitionKey(Relation rel,
values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy);
values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_table_partdefid - 1] = ObjectIdGetDatum(InvalidOid);
values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec);
values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec);
values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec);
@@ -3223,7 +3248,8 @@ RemovePartitionKeyByRelId(Oid relid)
* relispartition to true
*
* Also, invalidate the parent's relcache, so that the next rebuild will load
- * the new partition's info into its partition descriptor.
+ * the new partition's info into its partition descriptor.  If there is a
+ * default partition, we must invalidate its relcache entry as well.
*/
void
StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
@@ -3234,6 +3260,7 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
Datum new_val[Natts_pg_class];
bool new_null[Natts_pg_class],
new_repl[Natts_pg_class];
+ Oid defaultPartOid;
/* Update pg_class tuple */
classRel = heap_open(RelationRelationId, RowExclusiveLock);
@@ -3271,5 +3298,15 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
heap_freetuple(newtuple);
heap_close(classRel, RowExclusiveLock);
+ /*
+ * The partition constraint for the default partition depends on the
+ * partition bounds of every other partition, so we must invalidate the
+ * relcache entry for that partition every time a partition is added or
+ * removed.
+ */
+ defaultPartOid = get_default_oid_from_partdesc(RelationGetPartitionDesc(parent));
+ if (OidIsValid(defaultPartOid))
+ CacheInvalidateRelcacheByRelid(defaultPartOid);
+
CacheInvalidateRelcache(parent);
}
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6bd02f77dc..7e426ba9c88 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -27,7 +27,9 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_type.h"
+#include "commands/tablecmds.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -35,6 +37,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"
@@ -80,9 +83,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; -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
@@ -120,8 +126,10 @@ static void get_range_key_properties(PartitionKey key, int keynum,
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_range(PartitionKey key, PartitionBoundSpec *spec);
+static List *get_qual_for_list(Relation parent, 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,
@@ -162,6 +170,7 @@ RelationBuildPartitionDesc(Relation rel)
MemoryContext oldcxt;
int ndatums = 0;
+ int default_index = -1;
/* List partitioning specific */
PartitionListValue **all_values = NULL;
@@ -213,6 +222,22 @@ RelationBuildPartitionDesc(Relation rel)
&isnull);
Assert(!isnull);
boundspec = (Node *) stringToNode(TextDatumGetCString(datum));
+
+ /*
+ * Sanity check: If the PartitionBoundSpec says this is the default
+ * partition, its OID should correspond to whatever's stored in
+ * pg_partitioned_table.partdefid; if not, the catalog is corrupt.
+ */
+ if (castNode(PartitionBoundSpec, boundspec)->is_default)
+ {
+ Oid partdefid;
+
+ partdefid = get_default_partition_oid(RelationGetRelid(rel));
+ if (partdefid != inhrelid)
+ elog(ERROR, "expected partdefid %u, but got %u",
+ inhrelid, partdefid);
+ }
+
boundspecs = lappend(boundspecs, boundspec);
partoids = lappend_oid(partoids, inhrelid);
ReleaseSysCache(tuple);
@@ -246,6 +271,18 @@ RelationBuildPartitionDesc(Relation rel)
if (spec->strategy != PARTITION_STRATEGY_LIST)
elog(ERROR, "invalid strategy in partition bound spec");
+ /*
+ * Note the index of the partition bound spec for the default
+ * partition. There's no datum to add to the list of non-null
+ * datums for this partition.
+ */
+ if (spec->is_default)
+ {
+ default_index = i;
+ i++;
+ continue;
+ }
+
foreach(c, spec->listdatums)
{
Const *val = castNode(Const, lfirst(c));
@@ -325,6 +362,17 @@ RelationBuildPartitionDesc(Relation rel)
if (spec->strategy != PARTITION_STRATEGY_RANGE)
elog(ERROR, "invalid strategy in partition bound spec");
+ /*
+ * Note the index of the partition bound spec for the default
+ * partition. There's no datum to add to the allbounds array
+ * for this partition.
+ */
+ if (spec->is_default)
+ {
+ default_index = i++;
+ continue;
+ }
+
lower = make_one_range_bound(key, i, spec->lowerdatums,
true);
upper = make_one_range_bound(key, i, spec->upperdatums,
@@ -334,10 +382,11 @@ RelationBuildPartitionDesc(Relation rel)
i++;
}
- Assert(ndatums == nparts * 2);
+ Assert(ndatums == nparts * 2 ||
+ (default_index != -1 && ndatums == (nparts - 1) * 2));
/* Sort all the bounds in ascending order */
- qsort_arg(all_bounds, 2 * nparts,
+ qsort_arg(all_bounds, ndatums,
sizeof(PartitionRangeBound *),
qsort_partition_rbound_cmp,
(void *) key);
@@ -421,6 +470,7 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo = (PartitionBoundInfoData *)
palloc0(sizeof(PartitionBoundInfoData));
boundinfo->strategy = key->strategy;
+ boundinfo->default_index = -1;
boundinfo->ndatums = ndatums;
boundinfo->null_index = -1;
boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
@@ -473,6 +523,21 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo->null_index = mapping[null_index];
}
+ /* Assign mapped index for the default partition. */
+ if (default_index != -1)
+ {
+ /*
+ * The default partition accepts any value not
+ * specified in the lists of other partitions, hence
+ * it should not get mapped index while assigning
+ * those for non-null datums.
+ */
+ Assert(default_index >= 0 &&
+ mapping[default_index] == -1);
+ mapping[default_index] = next_index++;
+ boundinfo->default_index = mapping[default_index];
+ }
+
/* All partition must now have a valid mapping */
Assert(next_index == nparts);
break;
@@ -527,6 +592,14 @@ RelationBuildPartitionDesc(Relation rel)
boundinfo->indexes[i] = mapping[orig_index];
}
}
+
+ /* Assign mapped index for the 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];
+ }
boundinfo->indexes[i] = -1;
break;
}
@@ -577,6 +650,9 @@ partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval,
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;
@@ -635,10 +711,24 @@ check_new_partition_bound(char *relname, Relation parent,
{
PartitionKey key = RelationGetPartitionKey(parent);
PartitionDesc partdesc = RelationGetPartitionDesc(parent);
+ PartitionBoundInfo boundinfo = partdesc->boundinfo;
ParseState *pstate = make_parsestate(NULL);
int with = -1;
bool overlap = false;
+ if (spec->is_default)
+ {
+ if (boundinfo == NULL || !partition_bound_has_default(boundinfo))
+ return;
+
+ /* Default partition already exists, error out. */
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
+ relname, get_rel_name(partdesc->oids[boundinfo->default_index])),
+ parser_errposition(pstate, spec->location)));
+ }
+
switch (key->strategy)
{
case PARTITION_STRATEGY_LIST:
@@ -647,13 +737,13 @@ check_new_partition_bound(char *relname, Relation parent,
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)));
foreach(cell, spec->listdatums)
{
@@ -718,8 +808,10 @@ check_new_partition_bound(char *relname, Relation parent,
int offset;
bool equal;
- Assert(boundinfo && boundinfo->ndatums > 0 &&
- boundinfo->strategy == PARTITION_STRATEGY_RANGE);
+ Assert(boundinfo &&
+ boundinfo->strategy == PARTITION_STRATEGY_RANGE &&
+ (boundinfo->ndatums > 0 ||
+ partition_bound_has_default(boundinfo)));
/*
* Test whether the new lower bound (which is treated
@@ -797,6 +889,139 @@ check_new_partition_bound(char *relname, Relation parent,
}
/*
+ * check_default_allows_bound
+ *
+ * This function checks if there exists a row in the default partition that
+ * would properly belong to the new partition being added. If it finds one,
+ * it throws an error.
+ */
+void
+check_default_allows_bound(Relation parent, Relation default_rel,
+ PartitionBoundSpec *new_spec)
+{
+ List *new_part_constraints;
+ List *def_part_constraints;
+ List *all_parts;
+ ListCell *lc;
+
+ new_part_constraints = (new_spec->strategy == PARTITION_STRATEGY_LIST)
+ ? get_qual_for_list(parent, new_spec)
+ : get_qual_for_range(parent, new_spec, false);
+ def_part_constraints =
+ get_proposed_default_constraint(new_part_constraints);
+
+ /*
+ * If the existing constraints on the default partition imply that it will
+ * not contain any row that would belong to the new partition, we can
+ * avoid scanning the default partition.
+ */
+ if (PartConstraintImpliedByRelConstraint(default_rel, def_part_constraints))
+ {
+ ereport(INFO,
+ (errmsg("partition constraint for table \"%s\" is implied by existing constraints",
+ RelationGetRelationName(default_rel))));
+ return;
+ }
+
+ /*
+ * Scan the default partition and its subpartitions, and check for rows
+ * that do not satisfy the revised partition constraints.
+ */
+ 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;
+
+ /*
+ * Only RELKIND_RELATION relations (i.e. leaf partitions) need to be
+ * scanned.
+ */
+ if (part_rel->rd_rel->relkind != RELKIND_RELATION)
+ {
+ if (part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ ereport(WARNING,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("skipped scanning foreign table \"%s\" which is a partition of default partition \"%s\"",
+ RelationGetRelationName(part_rel),
+ RelationGetRelationName(default_rel))));
+
+ if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+ heap_close(part_rel, NoLock);
+
+ continue;
+ }
+
+ tupdesc = CreateTupleDescCopy(RelationGetDescr(part_rel));
+ constr = linitial(def_part_constraints);
+ partition_constraint = (Expr *)
+ map_partition_varattnos((List *) constr,
+ 1, part_rel, parent, NULL);
+ 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)
+ {
+ ExecStoreTuple(tuple, tupslot, InvalidBuffer, false);
+ econtext->ecxt_scantuple = tupslot;
+
+ if (!ExecCheck(partqualstate, econtext))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
+ RelationGetRelationName(default_rel))));
+
+ ResetExprContext(econtext);
+ CHECK_FOR_INTERRUPTS();
+ }
+
+ MemoryContextSwitchTo(oldCxt);
+ heap_endscan(scan);
+ UnregisterSnapshot(snapshot);
+ ExecDropSingleTupleTableSlot(tupslot);
+ FreeExecutorState(estate);
+
+ if (RelationGetRelid(default_rel) != RelationGetRelid(part_rel))
+ heap_close(part_rel, NoLock); /* keep the lock until commit */
+ }
+}
+
+/*
* get_partition_parent
*
* Returns inheritance parent of a partition by scanning pg_inherits
@@ -860,12 +1085,12 @@ get_qual_from_partbound(Relation rel, Relation parent,
{
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:
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:
@@ -935,7 +1160,8 @@ RelationGetPartitionQual(Relation rel)
* get_partition_qual_relid
*
* Returns an expression tree describing the passed-in relation's partition
- * constraint.
+ * constraint. If there is no partition constraint returns NULL; this can
+ * happen if the default partition is the only partition.
*/
Expr *
get_partition_qual_relid(Oid relid)
@@ -948,7 +1174,10 @@ get_partition_qual_relid(Oid relid)
if (rel->rd_rel->relispartition)
{
and_args = generate_partition_qual(rel);
- if (list_length(and_args) > 1)
+
+ if (and_args == NIL)
+ result = NULL;
+ else if (list_length(and_args) > 1)
result = makeBoolExpr(AND_EXPR, and_args, -1);
else
result = linitial(and_args);
@@ -1263,10 +1492,14 @@ make_partition_op_expr(PartitionKey key, int keynum,
*
* Returns an implicit-AND list of expressions to use as a list partition's
* constraint, given the partition key and bound structures.
+ *
+ * The function returns NIL for a default partition when it's the only
+ * partition since in that case there is no 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;
Expr *keyCol;
ArrayExpr *arr;
@@ -1293,15 +1526,63 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
else
keyCol = (Expr *) copyObject(linitial(key->partexprs));
- /* Create list of Consts for the allowed values, excluding any nulls */
- foreach(cell, spec->listdatums)
+ /*
+ * For default list partition, collect datums for all the partitions. The
+ * default partition constraint should check that the partition key is
+ * equal to none of those.
+ */
+ if (spec->is_default)
{
- Const *val = castNode(Const, lfirst(cell));
+ int i;
+ int ndatums = 0;
+ PartitionDesc pdesc = RelationGetPartitionDesc(parent);
+ PartitionBoundInfo boundinfo = pdesc->boundinfo;
- if (val->constisnull)
- list_has_null = true;
- else
- arrelems = lappend(arrelems, copyObject(val));
+ if (boundinfo)
+ {
+ ndatums = boundinfo->ndatums;
+
+ if (partition_bound_accepts_nulls(boundinfo))
+ list_has_null = true;
+ }
+
+ /*
+ * If default is the only partition, there need not be any partition
+ * constraint on it.
+ */
+ if (ndatums == 0 && !list_has_null)
+ return 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 */
+ key->parttypbyval[0]);
+
+ arrelems = lappend(arrelems, val);
+ }
+ }
+ else
+ {
+ /*
+ * Create list of Consts for the allowed values, excluding any nulls.
+ */
+ foreach(cell, spec->listdatums)
+ {
+ Const *val = castNode(Const, lfirst(cell));
+
+ if (val->constisnull)
+ list_has_null = true;
+ else
+ arrelems = lappend(arrelems, copyObject(val));
+ }
}
if (arrelems)
@@ -1365,6 +1646,18 @@ get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec)
result = list_make1(nulltest);
}
+ /*
+ * Note that, in general, applying NOT to a constraint expression doesn't
+ * necessarily invert the set of rows it accepts, because NOT (NULL) is
+ * NULL. However, the partition constraints we construct here never
+ * evaluate to NULL, so applying NOT works as intended.
+ */
+ if (spec->is_default)
+ {
+ result = list_make1(make_ands_explicit(result));
+ result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
+ }
+
return result;
}
@@ -1421,6 +1714,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
*
@@ -1459,11 +1799,15 @@ get_range_key_properties(PartitionKey key, int keynum,
* In most common cases with only one partition column, say a, the following
* expression tree will be generated: a IS NOT NULL AND a >= al AND a < au
*
- * If 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.
+ * For default partition, it returns the negation of the constraints of all
+ * the other partitions.
+ *
+ * External callers should pass for_default as false; we set it to true only
+ * when recursing.
*/
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,
@@ -1474,10 +1818,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,
@@ -1487,44 +1831,77 @@ 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 (!IsA(bspec, PartitionBoundSpec))
+ elog(ERROR, "expected PartitionBoundSpec");
+
+ if (!bspec->is_default)
+ {
+ List *part_qual;
+
+ 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));
}
- 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 it is the recursive call for default, we skip the get_range_nulltest
+ * to avoid accumulating the NullTest on the same keys for each partition.
+ */
+ 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
@@ -1746,9 +2123,16 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
? makeBoolExpr(OR_EXPR, upper_or_arms, -1)
: linitial(upper_or_arms));
- /* As noted above, caller expects the list to be non-empty. */
+ /*
+ * As noted above, for non-default, we return list with constant TRUE. If
+ * the result is NIL during the recursive call for default, it implies
+ * this is the only other partition which can hold every value of the key
+ * except NULL. Hence we return the NullTest result skipped earlier.
+ */
if (result == NIL)
- result = list_make1(makeBoolConst(true, false));
+ result = for_default
+ ? get_range_nulltest(key)
+ : list_make1(makeBoolConst(true, false));
return result;
}
@@ -1756,7 +2140,8 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec)
/*
* generate_partition_qual
*
- * Generate partition predicate from rel's partition bound expression
+ * Generate partition predicate from rel's partition bound expression. The
+ * function returns a NIL list if there is no predicate.
*
* Result expression tree is stored CacheMemoryContext to ensure it survives
* as long as the relcache entry. But we should be running in a less long-lived
@@ -1932,7 +2317,7 @@ get_partition_for_tuple(PartitionDispatch *pd,
PartitionDesc partdesc = parent->partdesc;
TupleTableSlot *myslot = parent->tupslot;
TupleConversionMap *map = parent->tupmap;
- int cur_index = -1;
+ int cur_index = -1;
if (myslot != NULL && map != NULL)
{
@@ -1991,14 +2376,25 @@ get_partition_for_tuple(PartitionDispatch *pd,
case PARTITION_STRATEGY_RANGE:
{
- bool equal = false;
+ bool equal = false,
+ range_partkey_has_null = false;
int cur_offset;
int i;
- /* No range includes NULL. */
+ /*
+ * No range includes NULL, so this will be accepted by the
+ * default partition if there is one, and otherwise
+ * rejected.
+ */
for (i = 0; i < key->partnatts; i++)
{
- if (isnull[i])
+ if (isnull[i] &&
+ partition_bound_has_default(partdesc->boundinfo))
+ {
+ range_partkey_has_null = true;
+ break;
+ }
+ else if (isnull[i])
{
*failed_at = parent;
*failed_slot = slot;
@@ -2007,6 +2403,13 @@ get_partition_for_tuple(PartitionDispatch *pd,
}
}
+ /*
+ * No need to search for partition, as the null key will
+ * be routed to the default partition.
+ */
+ if (range_partkey_has_null)
+ break;
+
cur_offset = partition_bound_bsearch(key,
partdesc->boundinfo,
values,
@@ -2014,9 +2417,9 @@ get_partition_for_tuple(PartitionDispatch *pd,
&equal);
/*
- * The offset returned is such that the bound at cur_offset
- * is less than or equal to the tuple value, so the bound
- * at offset+1 is the upper bound.
+ * The offset returned is such that the bound at
+ * cur_offset is less than or equal to the tuple value, so
+ * the bound at offset+1 is the upper bound.
*/
cur_index = partdesc->boundinfo->indexes[cur_offset + 1];
}
@@ -2029,8 +2432,16 @@ get_partition_for_tuple(PartitionDispatch *pd,
/*
* cur_index < 0 means we failed to find a partition of this parent.
- * cur_index >= 0 means we either found the leaf partition, or the
- * next parent to find a partition of.
+ * Use the default partition, if there is one.
+ */
+ if (cur_index < 0)
+ cur_index = partdesc->boundinfo->default_index;
+
+ /*
+ * If cur_index is still less than 0 at this point, there's no
+ * partition for this tuple. Otherwise, we either found the leaf
+ * partition, or a child partitioned table through which we have to
+ * route the tuple.
*/
if (cur_index < 0)
{
@@ -2084,6 +2495,8 @@ make_one_range_bound(PartitionKey key, int index, List *datums, bool lower)
ListCell *lc;
int i;
+ Assert(datums != NIL);
+
bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
bound->index = index;
bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
@@ -2320,3 +2733,104 @@ partition_bound_bsearch(PartitionKey key, PartitionBoundInfo boundinfo,
return lo;
}
+
+/*
+ * get_default_oid_from_partdesc
+ *
+ * Given a partition descriptor, return the OID of the default partition, if
+ * one exists; else, return InvalidOid.
+ */
+Oid
+get_default_oid_from_partdesc(PartitionDesc partdesc)
+{
+ if (partdesc && partdesc->boundinfo &&
+ partition_bound_has_default(partdesc->boundinfo))
+ return partdesc->oids[partdesc->boundinfo->default_index];
+
+ return InvalidOid;
+}
+
+/*
+ * get_default_partition_oid
+ *
+ * Given a relation OID, return the OID of the default partition, if one
+ * exists. Use get_default_oid_from_partdesc where possible, for
+ * efficiency.
+ */
+Oid
+get_default_partition_oid(Oid parentId)
+{
+ HeapTuple tuple;
+ Oid defaultPartId = InvalidOid;
+
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(parentId));
+
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_partitioned_table part_table_form;
+
+ part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+ defaultPartId = part_table_form->partdefid;
+ }
+
+ ReleaseSysCache(tuple);
+ return defaultPartId;
+}
+
+/*
+ * update_default_partition_oid
+ *
+ * Update pg_partition_table.partdefid with a new default partition OID.
+ */
+void
+update_default_partition_oid(Oid parentId, Oid defaultPartId)
+{
+ HeapTuple tuple;
+ Relation pg_partitioned_table;
+ Form_pg_partitioned_table part_table_form;
+
+ pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(parentId));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u",
+ parentId);
+
+ part_table_form = (Form_pg_partitioned_table) GETSTRUCT(tuple);
+ part_table_form->partdefid = defaultPartId;
+ CatalogTupleUpdate(pg_partitioned_table, &tuple->t_self, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(pg_partitioned_table, RowExclusiveLock);
+}
+
+/*
+ * get_proposed_default_constraint
+ *
+ * This function returns the negation of new_part_constraints, which
+ * would be an integral part of the default partition constraints after
+ * addition of the partition to which the new_part_constraints belongs.
+ */
+List *
+get_proposed_default_constraint(List *new_part_constraints)
+{
+ Expr *defPartConstraint;
+
+ defPartConstraint = make_ands_explicit(new_part_constraints);
+
+ /*
+ * Derive the partition constraints of default partition by negating the
+ * given partition constraints. The partition constraint never evaluates
+ * to NULL, so negating it like this is safe.
+ */
+ defPartConstraint = makeBoolExpr(NOT_EXPR,
+ list_make1(defPartConstraint),
+ -1);
+ defPartConstraint =
+ (Expr *) eval_const_expressions(NULL,
+ (Node *) defPartConstraint);
+ defPartConstraint = canonicalize_qual(defPartConstraint);
+
+ return list_make1(defPartConstraint);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c8fc9cb7fe7..d2167eda239 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -168,6 +168,8 @@ typedef struct AlteredTableInfo
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
Expr *partition_constraint; /* for attach partition validation */
+ /* true, if validating default due to some other attach/detach */
+ bool validate_default;
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -473,11 +475,10 @@ static void CreateInheritance(Relation child_rel, Relation parent_rel);
static void RemoveInheritance(Relation child_rel, Relation parent_rel);
static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
PartitionCmd *cmd);
-static bool PartConstraintImpliedByRelConstraint(Relation scanrel,
- List *partConstraint);
static void ValidatePartitionConstraints(List **wqueue, Relation scanrel,
List *scanrel_children,
- List *partConstraint);
+ List *partConstraint,
+ bool validate_default);
static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
@@ -774,8 +775,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
{
PartitionBoundSpec *bound;
ParseState *pstate;
- Oid parentId = linitial_oid(inheritOids);
- Relation parent;
+ Oid parentId = linitial_oid(inheritOids),
+ defaultPartOid;
+ Relation parent,
+ defaultRel = NULL;
/* Already have strong enough lock on the parent */
parent = heap_open(parentId, NoLock);
@@ -790,6 +793,30 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
errmsg("\"%s\" is not partitioned",
RelationGetRelationName(parent))));
+ /*
+ * The partition constraint of the default partition depends on the
+ * partition bounds of every other partition. It is possible that
+ * another backend might be about to execute a query on the default
+ * partition table, and that the query relies on previously cached
+ * default partition constraints. We must therefore take a table lock
+ * strong enough to prevent all queries on the default partition from
+ * proceeding until we commit and send out a shared-cache-inval notice
+ * that will make them update their index lists.
+ *
+ * Order of locking: The relation being added won't be visible to
+ * other backends until it is committed, hence here in
+ * DefineRelation() the order of locking the default partition and the
+ * relation being added does not matter. But at all other places we
+ * need to lock the default relation before we lock the relation being
+ * added or removed i.e. we should take the lock in same order at all
+ * the places such that lock parent, lock default partition and then
+ * lock the partition so as to avoid a deadlock.
+ */
+ defaultPartOid =
+ get_default_oid_from_partdesc(RelationGetPartitionDesc(parent));
+ if (OidIsValid(defaultPartOid))
+ defaultRel = heap_open(defaultPartOid, AccessExclusiveLock);
+
/* Tranform the bound values */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -798,14 +825,31 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Check first that the new partition's bound is valid and does not
- * overlap with any of existing partitions of the parent - note that
- * it does not return on error.
+ * overlap with any of existing partitions of the parent.
*/
check_new_partition_bound(relname, parent, bound);
+ /*
+ * If the default partition exists, its partition constraints 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 that
+ * the existing data in the default partition satisfies the constraint
+ * as it will exist after adding this partition.
+ */
+ if (OidIsValid(defaultPartOid))
+ {
+ check_default_allows_bound(parent, defaultRel, bound);
+ /* Keep the lock until commit. */
+ heap_close(defaultRel, NoLock);
+ }
+
/* Update the pg_class entry. */
StorePartitionBound(rel, parent, bound);
+ /* Update the default partition oid */
+ if (bound->is_default)
+ update_default_partition_oid(RelationGetRelid(parent), relationId);
+
heap_close(parent, NoLock);
/*
@@ -4595,9 +4639,16 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
if (partqualstate && !ExecCheck(partqualstate, econtext))
- ereport(ERROR,
- (errcode(ERRCODE_CHECK_VIOLATION),
- errmsg("partition constraint is violated by some row")));
+ {
+ if (tab->validate_default)
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("updated partition constraint for default partition would be violated by some row")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("partition constraint is violated by some row")));
+ }
/* Write the tuple out to the new relation */
if (newrel)
@@ -13482,7 +13533,7 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs,
* Existing constraints includes its check constraints and column-level
* NOT NULL constraints and partConstraint describes the partition constraint.
*/
-static bool
+bool
PartConstraintImpliedByRelConstraint(Relation scanrel,
List *partConstraint)
{
@@ -13569,7 +13620,8 @@ PartConstraintImpliedByRelConstraint(Relation scanrel,
static void
ValidatePartitionConstraints(List **wqueue, Relation scanrel,
List *scanrel_children,
- List *partConstraint)
+ List *partConstraint,
+ bool validate_default)
{
bool found_whole_row;
ListCell *lc;
@@ -13631,6 +13683,7 @@ ValidatePartitionConstraints(List **wqueue, Relation scanrel,
/* Grab a work queue entry. */
tab = ATGetQueueEntry(wqueue, part_rel);
tab->partition_constraint = (Expr *) linitial(my_partconstr);
+ tab->validate_default = validate_default;
/* keep our lock until commit */
if (part_rel != scanrel)
@@ -13658,6 +13711,17 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
ObjectAddress address;
const char *trigger_name;
bool found_whole_row;
+ Oid defaultPartOid;
+ List *partBoundConstraint;
+
+ /*
+ * We must lock the default partition, because attaching a new partition
+ * will change its partition constraint.
+ */
+ defaultPartOid =
+ get_default_oid_from_partdesc(RelationGetPartitionDesc(rel));
+ if (OidIsValid(defaultPartOid))
+ LockRelationOid(defaultPartOid, AccessExclusiveLock);
attachrel = heap_openrv(cmd->name, AccessExclusiveLock);
@@ -13814,6 +13878,11 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
/* OK to create inheritance. Rest of the checks performed there */
CreateInheritance(attachrel, rel);
+ /* Update the default partition oid */
+ if (cmd->bound->is_default)
+ update_default_partition_oid(RelationGetRelid(rel),
+ RelationGetRelid(attachrel));
+
/*
* Check that the new partition's bound is valid and does not overlap any
* of existing partitions of the parent - note that it does not return on
@@ -13830,27 +13899,61 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
* If the parent itself is a partition, make sure to include its
* constraint as well.
*/
- partConstraint = list_concat(get_qual_from_partbound(attachrel, rel,
- cmd->bound),
+ partBoundConstraint = get_qual_from_partbound(attachrel, rel, cmd->bound);
+ partConstraint = list_concat(partBoundConstraint,
RelationGetPartitionQual(rel));
- partConstraint = (List *) eval_const_expressions(NULL,
- (Node *) partConstraint);
- partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
- partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+ /* Skip validation if there are no constraints to validate. */
+ if (partConstraint)
+ {
+ partConstraint =
+ (List *) eval_const_expressions(NULL,
+ (Node *) partConstraint);
+ partConstraint = (List *) canonicalize_qual((Expr *) partConstraint);
+ partConstraint = list_make1(make_ands_explicit(partConstraint));
+
+ /*
+ * Adjust the generated constraint to match this partition's attribute
+ * numbers.
+ */
+ partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
+ rel, &found_whole_row);
+ /* There can never be a whole-row reference here */
+ if (found_whole_row)
+ elog(ERROR,
+ "unexpected whole-row reference found in partition key");
+
+ /* Validate partition constraints against the table being attached. */
+ ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
+ partConstraint, false);
+ }
/*
- * Adjust the generated constraint to match this partition's attribute
- * numbers.
+ * Check whether default partition has a row that would fit the partition
+ * being attached.
*/
- partConstraint = map_partition_varattnos(partConstraint, 1, attachrel,
- rel, &found_whole_row);
- /* There can never be a whole-row reference here */
- if (found_whole_row)
- elog(ERROR, "unexpected whole-row reference found in partition key");
+ defaultPartOid =
+ get_default_oid_from_partdesc(RelationGetPartitionDesc(rel));
+ if (OidIsValid(defaultPartOid))
+ {
+ Relation defaultrel;
+ List *defaultrel_children;
+ List *defPartConstraint;
+
+ /* We already have taken a lock on default partition. */
+ defaultrel = heap_open(defaultPartOid, NoLock);
+ defPartConstraint =
+ get_proposed_default_constraint(partBoundConstraint);
+ defaultrel_children =
+ find_all_inheritors(defaultPartOid,
+ AccessExclusiveLock, NULL);
+ ValidatePartitionConstraints(wqueue, defaultrel,
+ defaultrel_children,
+ defPartConstraint, true);
- /* Validate partition constraints against the table being attached. */
- ValidatePartitionConstraints(wqueue, attachrel, attachrel_children,
- partConstraint);
+ /* keep our lock until commit. */
+ heap_close(defaultrel, NoLock);
+ }
ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachrel));
@@ -13877,6 +13980,16 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
new_null[Natts_pg_class],
new_repl[Natts_pg_class];
ObjectAddress address;
+ Oid defaultPartOid;
+
+ /*
+ * We must lock the default partition, because detaching this partition
+ * will changing its partition constrant.
+ */
+ defaultPartOid =
+ get_default_oid_from_partdesc(RelationGetPartitionDesc(rel));
+ if (OidIsValid(defaultPartOid))
+ LockRelationOid(defaultPartOid, AccessExclusiveLock);
partRel = heap_openrv(name, AccessShareLock);
@@ -13908,6 +14021,24 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
heap_freetuple(newtuple);
heap_close(classRel, RowExclusiveLock);
+ if (OidIsValid(defaultPartOid))
+ {
+ /*
+ * If the detach relation is the default partition itself, invalidate
+ * its entry in pg_partitioned_table.
+ */
+ if (RelationGetRelid(partRel) == defaultPartOid)
+ update_default_partition_oid(RelationGetRelid(rel), InvalidOid);
+ else
+ {
+ /*
+ * We must invalidate default partition's relcache, for the same
+ * reasons explained in StorePartitionBound().
+ */
+ CacheInvalidateRelcacheByRelid(defaultPartOid);
+ }
+ }
+
/*
* Invalidate the parent's relcache so that the partition is no longer
* included in its partition descriptor.
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 9bae2647fd1..f1bed14e2bb 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4450,6 +4450,7 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
PartitionBoundSpec *newnode = makeNode(PartitionBoundSpec);
COPY_SCALAR_FIELD(strategy);
+ COPY_SCALAR_FIELD(is_default);
COPY_NODE_FIELD(listdatums);
COPY_NODE_FIELD(lowerdatums);
COPY_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 11731da80ab..8b56b9146a1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2839,6 +2839,7 @@ static bool
_equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *b)
{
COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_SCALAR_FIELD(is_default);
COMPARE_NODE_FIELD(listdatums);
COMPARE_NODE_FIELD(lowerdatums);
COMPARE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 9ee3e23761c..b83d919e408 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3573,6 +3573,7 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
WRITE_NODE_TYPE("PARTITIONBOUNDSPEC");
WRITE_CHAR_FIELD(strategy);
+ WRITE_BOOL_FIELD(is_default);
WRITE_NODE_FIELD(listdatums);
WRITE_NODE_FIELD(lowerdatums);
WRITE_NODE_FIELD(upperdatums);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 67b9e19d29b..fbf83307358 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2390,6 +2390,7 @@ _readPartitionBoundSpec(void)
READ_LOCALS(PartitionBoundSpec);
READ_CHAR_FIELD(strategy);
+ READ_BOOL_FIELD(is_default);
READ_NODE_FIELD(listdatums);
READ_NODE_FIELD(lowerdatums);
READ_NODE_FIELD(upperdatums);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5eb398118e5..c303818c9b0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -575,7 +575,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> part_strategy
%type <partelem> part_elem
%type <list> part_params
-%type <partboundspec> ForValues
+%type <partboundspec> PartitionBoundSpec
%type <node> partbound_datum PartitionRangeDatum
%type <list> partbound_datum_list range_datum_list
@@ -1980,7 +1980,7 @@ alter_table_cmds:
partition_cmd:
/* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
- ATTACH PARTITION qualified_name ForValues
+ ATTACH PARTITION qualified_name PartitionBoundSpec
{
AlterTableCmd *n = makeNode(AlterTableCmd);
PartitionCmd *cmd = makeNode(PartitionCmd);
@@ -2635,13 +2635,14 @@ alter_identity_column_option:
}
;
-ForValues:
+PartitionBoundSpec:
/* a LIST partition */
FOR VALUES IN_P '(' partbound_datum_list ')'
{
PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
n->strategy = PARTITION_STRATEGY_LIST;
+ n->is_default = false;
n->listdatums = $5;
n->location = @3;
@@ -2654,12 +2655,24 @@ ForValues:
PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
n->strategy = PARTITION_STRATEGY_RANGE;
+ n->is_default = false;
n->lowerdatums = $5;
n->upperdatums = $9;
n->location = @3;
$$ = n;
}
+
+ /* a DEFAULT partition */
+ | DEFAULT
+ {
+ PartitionBoundSpec *n = makeNode(PartitionBoundSpec);
+
+ n->is_default = true;
+ n->location = @1;
+
+ $$ = n;
+ }
;
partbound_datum:
@@ -3130,7 +3143,7 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
- OptTypedTableElementList ForValues OptPartitionSpec OptWith
+ OptTypedTableElementList PartitionBoundSpec OptPartitionSpec OptWith
OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
@@ -3149,7 +3162,7 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
- qualified_name OptTypedTableElementList ForValues OptPartitionSpec
+ qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
@@ -4864,7 +4877,7 @@ CreateForeignTableStmt:
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE qualified_name
- PARTITION OF qualified_name OptTypedTableElementList ForValues
+ PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
@@ -4885,7 +4898,7 @@ CreateForeignTableStmt:
$$ = (Node *) n;
}
| CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
- PARTITION OF qualified_name OptTypedTableElementList ForValues
+ PARTITION OF qualified_name OptTypedTableElementList PartitionBoundSpec
SERVER name create_generic_options
{
CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 20586797cc5..655da02c109 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3307,6 +3307,18 @@ transformPartitionBound(ParseState *pstate, Relation parent,
/* Avoid scribbling on input */
result_spec = copyObject(spec);
+ if (spec->is_default)
+ {
+ /*
+ * In case of the default partition, parser had no way to identify the
+ * partition strategy. Assign the parent's strategy to the default
+ * partition bound spec.
+ */
+ result_spec->strategy = strategy;
+
+ return result_spec;
+ }
+
if (strategy == PARTITION_STRATEGY_LIST)
{
ListCell *cell;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f9ea7ed771d..0ea50782181 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -1750,7 +1750,7 @@ pg_get_partition_constraintdef(PG_FUNCTION_ARGS)
constr_expr = get_partition_qual_relid(relationId);
- /* Quick exit if not a partition */
+ /* Quick exit if no partition constraint */
if (constr_expr == NULL)
PG_RETURN_NULL();
@@ -8699,6 +8699,12 @@ get_rule_expr(Node *node, deparse_context *context,
ListCell *cell;
char *sep;
+ if (spec->is_default)
+ {
+ appendStringInfoString(buf, "DEFAULT");
+ break;
+ }
+
switch (spec->strategy)
{
case PARTITION_STRATEGY_LIST: