<entry>The number of columns in partition key</entry>
</row>
+ <row>
+ <entry><structfield>partdefid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+ <entry>
+ The OID of the <structname>pg_class</> entry for the default partition
+ of this partitioned table, or zero if this partitioned table does not
+ have a default partition.
+ </entry>
+ </row>
+
<row>
<entry><structfield>partattrs</structfield></entry>
<entry><type>int2vector</type></entry>
ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> [ OWNED BY <replaceable class="PARAMETER">role_name</replaceable> [, ... ] ]
SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable> [ NOWAIT ]
ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
- ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+ ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }
ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable>
</varlistentry>
<varlistentry>
- <term><literal>ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></literal></term>
+ <term><literal>ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }</literal></term>
<listitem>
<para>
This form attaches an existing table (which might itself be partitioned)
- as a partition of the target table using the same syntax for
+ as a partition of the target table. The table can be attached
+ as a partition for specific values using <literal>FOR VALUES
+ </literal> or as a default partition by using <literal>DEFAULT
+ </literal>.
+ </para>
+
+ <para>
+ A partition using <literal>FOR VALUES</literal> uses same syntax for
<replaceable class="PARAMETER">partition_bound_spec</replaceable> as
<xref linkend="sql-createtable">. The partition bound specification
must correspond to the partitioning strategy and partition key of the
(See the discussion in <xref linkend="SQL-CREATEFOREIGNTABLE"> about
constraints on the foreign table.)
</para>
+
+ <para>
+ When a table has a default partition, defining a new partition changes
+ the partition constraint for the default partition. The default
+ partition can't contain any rows that would need to be moved to the new
+ partition, and will be scanned to verify that none are present. This
+ scan, like the scan of the new partition, can be avoided if an
+ appropriate <literal>CHECK</literal> constraint is present. Also like
+ the scan of the new partition, it is always skipped when the default
+ partition is a foreign table.
+ </para>
</listitem>
</varlistentry>
ATTACH PARTITION cities_ab FOR VALUES IN ('a', 'b');
</programlisting></para>
+ <para>
+ Attach a default partition to a partitioned table:
+<programlisting>
+ALTER TABLE cities
+ ATTACH PARTITION cities_partdef DEFAULT;
+</programlisting></para>
+
<para>
Detach a partition from partitioned table:
<programlisting>
{ <replaceable class="PARAMETER">column_name</replaceable> [ WITH OPTIONS ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
| <replaceable>table_constraint</replaceable> }
[, ... ]
-) ] FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+) ] { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }
[ PARTITION BY { RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
</varlistentry>
<varlistentry id="SQL-CREATETABLE-PARTITION">
- <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable></literal></term>
+ <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> { FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> | DEFAULT }</literal></term>
<listitem>
<para>
Creates the table as a <firstterm>partition</firstterm> of the specified
- parent table.
+ parent table. The table can be created either as a partition for specific
+ values using <literal>FOR VALUES</literal> or as a default partition
+ using <literal>DEFAULT</literal>.
</para>
<para>
allows precisely one value to be stored — "infinity".
</para>
+ <para>
+ If <literal>DEFAULT</literal> is specified, the table will be
+ created as a default partition of the parent table. The parent can
+ either be a list or range partitioned table. A partition key value
+ not fitting into any other partition of the given parent will be
+ routed to the default partition. There can be only one default
+ partition for a given parent table.
+ </para>
+
+ <para>
+ When a table has an existing <literal>DEFAULT</literal> partition and
+ a new partition is added to it, the existing default partition must
+ be scanned to verify that it does not contain any rows which properly
+ belong in the new partition. If the default partition contains a
+ large number of rows, this may be slow. The scan will be skipped if
+ the default partition is a foreign table or if it has a constraint which
+ proves that it cannot contain rows which should be placed in the new
+ partition.
+ </para>
+
<para>
A partition must have the same column names and types as the partitioned
table to which it belongs. If the parent is specified <literal>WITH
CREATE TABLE cities_ab_10000_to_100000
PARTITION OF cities_ab FOR VALUES FROM (10000) TO (100000);
</programlisting></para>
+
+ <para>
+ Create a default partition:
+<programlisting>
+CREATE TABLE cities_partdef
+ PARTITION OF cities DEFAULT;
+</programlisting></para>
</refsect1>
<refsect1 id="SQL-CREATETABLE-compatibility">
{
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,
{
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);
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
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 (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.
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);
* 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)
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);
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);
}
#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"
#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"
* 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
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,
MemoryContext oldcxt;
int ndatums = 0;
+ int default_index = -1;
/* List partitioning specific */
PartitionListValue **all_values = NULL;
&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);
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));
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,
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);
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 *));
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;
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;
}
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;
{
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:
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)
{
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
}
}
+/*
+ * 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
*
{
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:
* 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)
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);
*
* 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;
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)
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;
}
*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
*
* 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,
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,
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
? 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;
}
/*
* 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
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)
{
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;
}
}
+ /*
+ * 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,
&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];
}
/*
* 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)
{
ListCell *lc;
int i;
+ Assert(datums != NIL);
+
bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
bound->index = index;
bound->datums = (Datum *) palloc0(key->partnatts * sizeof(Datum));
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);
+}
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 */
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);
{
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);
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;
/*
* 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);
/*
}
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)
* 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)
{
static void
ValidatePartitionConstraints(List **wqueue, Relation scanrel,
List *scanrel_children,
- List *partConstraint)
+ List *partConstraint,
+ bool validate_default)
{
bool found_whole_row;
ListCell *lc;
/* 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)
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);
/* 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
* 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));
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);
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.
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);
_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);
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);
READ_LOCALS(PartitionBoundSpec);
READ_CHAR_FIELD(strategy);
+ READ_BOOL_FIELD(is_default);
READ_NODE_FIELD(listdatums);
READ_NODE_FIELD(lowerdatums);
READ_NODE_FIELD(upperdatums);
%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
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);
}
;
-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;
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;
}
;
$$ = (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);
$$ = (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);
$$ = (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);
$$ = (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);
/* 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;
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();
ListCell *cell;
char *sep;
+ if (spec->is_default)
+ {
+ appendStringInfoString(buf, "DEFAULT");
+ break;
+ }
+
switch (spec->strategy)
{
case PARTITION_STRATEGY_LIST:
parent_name = PQgetvalue(result, 0, 0);
partdef = PQgetvalue(result, 0, 1);
- if (PQnfields(result) == 3)
+ if (PQnfields(result) == 3 && !PQgetisnull(result, 0, 2))
partconstraintdef = PQgetvalue(result, 0, 2);
printfPQExpBuffer(&tmpbuf, _("Partition of: %s %s"), parent_name,
partdef);
printTableAddFooter(&cont, tmpbuf.data);
- if (partconstraintdef)
- {
+ /* If there isn't any constraint, show that explicitly */
+ if (partconstraintdef == NULL || partconstraintdef[0] == '\0')
+ printfPQExpBuffer(&tmpbuf, _("No partition constraint"));
+ else
printfPQExpBuffer(&tmpbuf, _("Partition constraint: %s"),
partconstraintdef);
- printTableAddFooter(&cont, tmpbuf.data);
- }
+ printTableAddFooter(&cont, tmpbuf.data);
PQclear(result);
}
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 (");
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))
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201708311
+#define CATALOG_VERSION_NO 201709081
#endif
EState *estate,
PartitionDispatchData **failed_at,
TupleTableSlot **failed_slot);
+extern Oid get_default_oid_from_partdesc(PartitionDesc partdesc);
+extern Oid get_default_partition_oid(Oid parentId);
+extern void update_default_partition_oid(Oid parentId, Oid defaultPartId);
+extern void check_default_allows_bound(Relation parent, Relation defaultRel,
+ PartitionBoundSpec *new_spec);
+extern List *get_proposed_default_constraint(List *new_part_constaints);
+
#endif /* PARTITION_H */
Oid partrelid; /* partitioned table oid */
char partstrat; /* partitioning strategy */
int16 partnatts; /* number of partition key columns */
+ Oid partdefid; /* default partition oid; InvalidOid if there
+ * isn't one */
/*
* variable-length fields start here, but we allow direct access to
* compiler constants for pg_partitioned_table
* ----------------
*/
-#define Natts_pg_partitioned_table 7
+#define Natts_pg_partitioned_table 8
#define Anum_pg_partitioned_table_partrelid 1
#define Anum_pg_partitioned_table_partstrat 2
#define Anum_pg_partitioned_table_partnatts 3
-#define Anum_pg_partitioned_table_partattrs 4
-#define Anum_pg_partitioned_table_partclass 5
-#define Anum_pg_partitioned_table_partcollation 6
-#define Anum_pg_partitioned_table_partexprs 7
+#define Anum_pg_partitioned_table_partdefid 4
+#define Anum_pg_partitioned_table_partattrs 5
+#define Anum_pg_partitioned_table_partclass 6
+#define Anum_pg_partitioned_table_partcollation 7
+#define Anum_pg_partitioned_table_partexprs 8
#endif /* PG_PARTITIONED_TABLE_H */
#include "catalog/dependency.h"
#include "catalog/objectaddress.h"
#include "nodes/parsenodes.h"
+#include "catalog/partition.h"
#include "storage/lock.h"
#include "utils/relcache.h"
extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
Oid relId, Oid oldRelId, void *noCatalogs);
+extern bool PartConstraintImpliedByRelConstraint(Relation scanrel,
+ List *partConstraint);
+
#endif /* TABLECMDS_H */
NodeTag type;
char strategy; /* see PARTITION_STRATEGY codes above */
+ bool is_default; /* is it a default partition bound? */
/* Partitioning info for LIST strategy: */
List *listdatums; /* List of Consts (or A_Consts in raw tree) */
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"
+-- check that an existing table can be attached as a default partition
+CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
+-- check attaching default partition fails if a default partition already
+-- exists
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
+ERROR: partition "fail_def_part" conflicts with existing default partition "def_part"
-- check validation when attaching list partitions
CREATE TABLE list_parted2 (
a int,
-- 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: updated partition constraint for default partition would be violated by some row
+-- 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 (
ALTER TABLE part_3_4 ALTER a SET NOT NULL;
ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
INFO: partition constraint for table "part_3_4" is implied by existing constraints
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
+INFO: partition constraint for table "list_parted2_def" is implied by existing constraints
-- check validation when attaching range partitions
CREATE TABLE range_parted (
a int,
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
INFO: partition constraint for table "part2" is implied by existing constraints
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+ERROR: partition "partr_def2" conflicts with existing default partition "partr_def1"
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+ERROR: updated partition constraint for default partition would be violated by some row
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
INFO: partition constraint for table "part_7_a_null" is implied by existing constraints
ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
INFO: partition constraint for table "part_7" is implied by existing constraints
+INFO: partition constraint for table "list_parted2_def" is implied by existing constraints
-- Same example, but check this time that the constraint correctly detects
-- violating rows
ALTER TABLE list_parted2 DETACH PARTITION part_7;
(2 rows)
ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
+INFO: partition constraint for table "list_parted2_def" is implied by existing constraints
ERROR: partition constraint is violated by some row
+-- check that leaf partitions 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 (5);
+INSERT INTO part5_def_p1 VALUES (5, 'y');
+CREATE TABLE part5_p1 (LIKE part_5);
+ALTER TABLE part_5 ATTACH PARTITION part5_p1 FOR VALUES IN ('y');
+ERROR: updated partition constraint for default partition would be violated by some row
+-- 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
ERROR: cannot alter type of column named in partition key
-- cleanup
DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE fail_def_part;
-- more tests for certain multi-level partitioning scenarios
create table p (a int, b int) partition by range (a, b);
create table p1 (b int, a int not null) partition by range (b);
ERROR: invalid bound specification for a list partition
LINE 1: ...BLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) T...
^
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR: partition "fail_default_part" conflicts with existing default partition "part_default"
-- specified literal can't be cast to the partition column data type
CREATE TABLE bools (
a bool
) 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: updated partition constraint for default partition "list_parted2_def" would be violated by some row
CREATE TABLE range_parted2 (
a int
) PARTITION BY RANGE (a);
ERROR: partition "fail_part" would overlap partition "part2"
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
ERROR: partition "fail_part" would overlap partition "part2"
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+ERROR: partition "fail_default_part" conflicts with existing default partition "range2_default"
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+ERROR: updated partition constraint for default partition "range2_default" would be violated by some row
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, maxvalue);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
ERROR: partition "fail_part" would overlap partition "part12"
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
-- more specific ranges
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);
+-- test default partition
+create table part_default partition of list_parted default;
+-- Negative test: a row, which would fit in other partition, does not fit
+-- default partition, even when inserted directly
+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_default values ('Zz', 2);
+-- test if default partition works as expected for multi-level partitioned
+-- table as well as when default partition itself is further partitioned
+drop table part_default;
+create table part_xx_yy partition of list_parted for values in ('xx', 'yy') partition by list (a);
+create table part_xx_yy_p1 partition of part_xx_yy for values in ('xx');
+create table part_xx_yy_defpart partition of part_xx_yy 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 ('de', 35);
+insert into list_parted values ('ab', 21);
+insert into list_parted values ('xx', 1);
+insert into list_parted values ('yy', 2);
+select tableoid::regclass, * from list_parted;
+ tableoid | a | b
+--------------------+----+----
+ part_cc_dd | cC | 1
+ part_ee_ff1 | ff | 1
+ part_ee_ff2 | ff | 11
+ part_xx_yy_p1 | xx | 1
+ part_xx_yy_defpart | yy | 2
+ part_null | | 0
+ part_default_p1 | cd | 25
+ part_default_p1 | ab | 21
+ part_default_p2 | de | 35
+(9 rows)
+
-- Check tuple routing for partitioned tables
-- fail
insert into range_parted values ('a', 0);
insert into range_parted values ('a');
ERROR: no partition of relation "range_parted" found for row
DETAIL: Partition key of the failing row contains (a, (b + 0)) = (a, null).
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (b, 10).
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+insert into range_parted values ('a', null);
+insert into range_parted values (null, 19);
+insert into range_parted values ('b', 20);
select tableoid::regclass, * from range_parted;
tableoid | a | b
----------+---+----
part3 | b | 1
part4 | b | 10
part4 | b | 10
-(6 rows)
+ part_def | c | 10
+ part_def | |
+ part_def | a |
+ part_def | | 19
+ part_def | b | 20
+(11 rows)
-- ok
insert into list_parted values (null, 1);
insert into list_parted values ('EE', 1);
insert into part_ee_ff values ('EE', 10);
select tableoid::regclass, * from list_parted;
- tableoid | a | b
--------------+----+----
- part_aa_bb | aA |
- part_cc_dd | cC | 1
- part_ee_ff1 | ff | 1
- part_ee_ff1 | EE | 1
- part_ee_ff2 | ff | 11
- part_ee_ff2 | EE | 10
- part_null | | 0
- part_null | | 1
-(8 rows)
+ tableoid | a | b
+--------------------+----+----
+ part_aa_bb | aA |
+ part_cc_dd | cC | 1
+ part_ee_ff1 | ff | 1
+ part_ee_ff1 | EE | 1
+ part_ee_ff2 | ff | 11
+ part_ee_ff2 | EE | 10
+ part_xx_yy_p1 | xx | 1
+ part_xx_yy_defpart | yy | 2
+ part_null | | 0
+ part_null | | 1
+ part_default_p1 | cd | 25
+ part_default_p1 | ab | 21
+ part_default_p2 | de | 35
+(13 rows)
-- some more tests to exercise tuple-routing with multi-level partitioning
create table part_gg partition of list_parted for values in ('gg') partition by range (b);
-- cleanup
drop table range_parted, list_parted;
+-- test that a default partition added as the first partition accepts any value
+-- including null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+\d+ part_default
+ Table "public.part_default"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Partition of: list_parted DEFAULT
+No partition constraint
+
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+ tableoid | a
+--------------+----
+ part_default |
+ part_default | 1
+ part_default | -1
+(3 rows)
+
+-- cleanup
+drop table list_parted;
-- more tests for certain multi-level partitioning scenarios
create table mlparted (a int, b int) partition by range (a, b);
create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
ERROR: new row for relation "mlparted5a" violates partition constraint
DETAIL: Failing row contains (b, 1, 40).
drop table mlparted5;
+alter table mlparted drop constraint check_b;
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+ERROR: no partition of relation "mlparted_def" found for row
+DETAIL: Partition key of the failing row contains (a) = (70).
+insert into mlparted_def1 values (52, 50);
+ERROR: new row for relation "mlparted_def1" violates partition constraint
+DETAIL: Failing row contains (52, 50, null).
+insert into mlparted_def2 values (34, 50);
+ERROR: new row for relation "mlparted_def2" violates partition constraint
+DETAIL: Failing row contains (34, 50, null).
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+select tableoid::regclass, * from mlparted_def;
+ tableoid | a | b | c
+---------------+----+-----+---
+ mlparted_def1 | 40 | 100 |
+ mlparted_def1 | 42 | 100 |
+ mlparted_def2 | 54 | 50 |
+ mlparted_defd | 70 | 100 |
+(4 rows)
+
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
create table key_desc (a int, b int) partition by list ((a+0));
(1 row)
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in prepared statement.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+ERROR: new row for relation "list_part_def" violates partition constraint
+DETAIL: Failing row contains (null).
+execute pstmt_def_insert(1);
+ERROR: new row for relation "list_part_def" violates partition constraint
+DETAIL: Failing row contains (1).
+create table list_part_2 partition of list_parted for values in (2);
+execute pstmt_def_insert(2);
+ERROR: new row for relation "list_part_def" violates partition constraint
+DETAIL: Failing row contains (2).
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted, list_part_null;
+deallocate pstmt_def_insert;
mlparted2|f
mlparted3|f
mlparted4|f
+mlparted_def|f
+mlparted_def1|f
+mlparted_def2|f
+mlparted_defd|f
money_data|f
num_data|f
num_exp_add|t
DETAIL: Failing row contains (b, 9).
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+\d+ part_def
+ Table "public.part_def"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | | | plain | |
+Partition of: range_parted DEFAULT
+Partition constraint: (NOT (((a = 'a'::text) AND (b >= 1) AND (b < 10)) OR ((a = 'a'::text) AND (b >= 10) AND (b < 20)) OR ((a = 'b'::text) AND (b >= 1) AND (b < 10)) OR ((a = 'b'::text) AND (b >= 10) AND (b < 20))))
+
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+ERROR: new row for relation "part_def" violates partition constraint
+DETAIL: Failing row contains (a, 9).
+create table list_parted (
+ a text,
+ b int
+) 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_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_default set a = 'x' where a = 'd';
-- cleanup
drop table range_parted;
+drop table list_parted;
-- 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);
+-- check that an existing table can be attached as a default partition
+CREATE TABLE def_part (LIKE list_parted INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION def_part DEFAULT;
+-- check attaching default partition fails if a default partition already
+-- exists
+CREATE TABLE fail_def_part (LIKE part_1 INCLUDING CONSTRAINTS);
+ALTER TABLE list_parted ATTACH PARTITION fail_def_part DEFAULT;
-- check validation when attaching list partitions
CREATE TABLE list_parted2 (
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 (
ALTER TABLE part_3_4 ALTER a SET NOT NULL;
ALTER TABLE list_parted2 ATTACH PARTITION part_3_4 FOR VALUES IN (3, 4);
+-- check if default partition scan skipped
+ALTER TABLE list_parted2_def ADD CONSTRAINT check_a CHECK (a IN (5, 6));
+CREATE TABLE part_55_66 PARTITION OF list_parted2 FOR VALUES IN (55, 66);
-- check validation when attaching range partitions
CREATE TABLE range_parted (
);
ALTER TABLE range_parted ATTACH PARTITION part2 FOR VALUES FROM (1, 10) TO (1, 20);
+-- Create default partition
+CREATE TABLE partr_def1 PARTITION OF range_parted DEFAULT;
+
+-- Only one default partition is allowed, hence, following should give error
+CREATE TABLE partr_def2 (LIKE part1 INCLUDING CONSTRAINTS);
+ALTER TABLE range_parted ATTACH PARTITION partr_def2 DEFAULT;
+
+-- Overlapping partitions cannot be attached, hence, following should give error
+INSERT INTO partr_def1 VALUES (2, 10);
+CREATE TABLE part3 (LIKE range_parted);
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (2, 10) TO (2, 20);
+
+-- Attaching partitions should be successful when there are no overlapping rows
+ALTER TABLE range_parted ATTACH partition part3 FOR VALUES FROM (3, 10) TO (3, 20);
+
-- check that leaf partitions are scanned when attaching a partitioned
-- table
CREATE TABLE part_5 (
SELECT tableoid::regclass, a, b FROM part_7 order by a;
ALTER TABLE list_parted2 ATTACH PARTITION part_7 FOR VALUES IN (7);
+-- check that leaf partitions 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 (5);
+INSERT INTO part5_def_p1 VALUES (5, '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);
-- cleanup
DROP TABLE list_parted, list_parted2, range_parted;
+DROP TABLE fail_def_part;
-- more tests for certain multi-level partitioning scenarios
create table p (a int, b int) partition by range (a, b);
-- trying to specify range for list partitioned table
CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES FROM (1) TO (2);
+-- check default partition cannot be created more than once
+CREATE TABLE part_default PARTITION OF list_parted DEFAULT;
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+
-- specified literal can't be cast to the partition column data type
CREATE TABLE bools (
a bool
) 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
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (30);
CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (10) TO (50);
+-- Create a default partition for range partitioned table
+CREATE TABLE range2_default PARTITION OF range_parted2 DEFAULT;
+
+-- More than one default partition is not allowed, so this should give error
+CREATE TABLE fail_default_part PARTITION OF range_parted2 DEFAULT;
+
+-- Check if the range for default partitions overlap
+INSERT INTO range_parted2 VALUES (85);
+CREATE TABLE fail_part PARTITION OF range_parted2 FOR VALUES FROM (80) TO (90);
+CREATE TABLE part4 PARTITION OF range_parted2 FOR VALUES FROM (90) TO (100);
+
-- now check for multi-column range partition key
CREATE TABLE range_parted3 (
a int,
CREATE TABLE part11 PARTITION OF range_parted3 FOR VALUES FROM (1, 1) TO (1, 10);
CREATE TABLE part12 PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, maxvalue);
CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, 10) TO (1, 20);
+CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
-- cannot create a partition that says column b is allowed to range
-- from -infinity to +infinity, while there exist partitions that have
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);
+-- test default partition
+create table part_default partition of list_parted default;
+-- Negative test: a row, which would fit in other partition, does not fit
+-- default partition, even when inserted directly
+insert into part_default values ('aa', 2);
+insert into part_default values (null, 2);
+-- ok
+insert into part_default values ('Zz', 2);
+-- test if default partition works as expected for multi-level partitioned
+-- table as well as when default partition itself is further partitioned
+drop table part_default;
+create table part_xx_yy partition of list_parted for values in ('xx', 'yy') partition by list (a);
+create table part_xx_yy_p1 partition of part_xx_yy for values in ('xx');
+create table part_xx_yy_defpart partition of part_xx_yy 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 ('de', 35);
+insert into list_parted values ('ab', 21);
+insert into list_parted values ('xx', 1);
+insert into list_parted values ('yy', 2);
+select tableoid::regclass, * from list_parted;
-- Check tuple routing for partitioned tables
insert into range_parted values ('b', 10);
-- fail (partition key (b+0) is null)
insert into range_parted values ('a');
-select tableoid::regclass, * from range_parted;
+-- Check default partition
+create table part_def partition of range_parted default;
+-- fail
+insert into part_def values ('b', 10);
+-- ok
+insert into part_def values ('c', 10);
+insert into range_parted values (null, null);
+insert into range_parted values ('a', null);
+insert into range_parted values (null, 19);
+insert into range_parted values ('b', 20);
+
+select tableoid::regclass, * from range_parted;
-- ok
insert into list_parted values (null, 1);
insert into list_parted (a) values ('aA');
-- cleanup
drop table range_parted, list_parted;
+-- test that a default partition added as the first partition accepts any value
+-- including null
+create table list_parted (a int) partition by list (a);
+create table part_default partition of list_parted default;
+\d+ part_default
+insert into part_default values (null);
+insert into part_default values (1);
+insert into part_default values (-1);
+select tableoid::regclass, a from list_parted;
+-- cleanup
+drop table list_parted;
+
-- more tests for certain multi-level partitioning scenarios
create table mlparted (a int, b int) partition by range (a, b);
create table mlparted1 (b int not null, a int not null) partition by range ((b+0));
create trigger mlparted5abrtrig before insert on mlparted5a for each row execute procedure mlparted5abrtrig_func();
insert into mlparted5 (a, b, c) values (1, 40, 'a');
drop table mlparted5;
+alter table mlparted drop constraint check_b;
+
+-- Check multi-level default partition
+create table mlparted_def partition of mlparted default partition by range(a);
+create table mlparted_def1 partition of mlparted_def for values from (40) to (50);
+create table mlparted_def2 partition of mlparted_def for values from (50) to (60);
+insert into mlparted values (40, 100);
+insert into mlparted_def1 values (42, 100);
+insert into mlparted_def2 values (54, 50);
+-- fail
+insert into mlparted values (70, 100);
+insert into mlparted_def1 values (52, 50);
+insert into mlparted_def2 values (34, 50);
+-- ok
+create table mlparted_defd partition of mlparted_def default;
+insert into mlparted values (70, 100);
+
+select tableoid::regclass, * from mlparted_def;
-- check that message shown after failure to find a partition shows the
-- appropriate key description (or none) in various situations
select cachebug();
select cachebug();
+
+-- Check that addition or removal of any partition is correctly dealt with by
+-- default partition table when it is being used in prepared statement.
+create table list_parted (a int) partition by list(a);
+create table list_part_null partition of list_parted for values in (null);
+create table list_part_1 partition of list_parted for values in (1);
+create table list_part_def partition of list_parted default;
+prepare pstmt_def_insert (int) as insert into list_part_def values($1);
+-- should fail
+execute pstmt_def_insert(null);
+execute pstmt_def_insert(1);
+create table list_part_2 partition of list_parted for values in (2);
+execute pstmt_def_insert(2);
+alter table list_parted detach partition list_part_null;
+-- should be ok
+execute pstmt_def_insert(null);
+drop table list_part_1;
+-- should be ok
+execute pstmt_def_insert(1);
+drop table list_parted, list_part_null;
+deallocate pstmt_def_insert;
-- ok
update range_parted set b = b + 1 where b = 10;
+-- Creating default partition for range
+create table part_def partition of range_parted default;
+\d+ part_def
+insert into range_parted values ('c', 9);
+-- ok
+update part_def set a = 'd' where a = 'c';
+-- fail
+update part_def set a = 'a' where a = 'd';
+
+create table list_parted (
+ a text,
+ b int
+) 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_default set a = 'a' where a = 'd';
+-- ok
+update list_default set a = 'x' where a = 'd';
+
-- cleanup
drop table range_parted;
+drop table list_parted;