aboutsummaryrefslogtreecommitdiff
path: root/src/backend/parser/parse_utilcmd.c
diff options
context:
space:
mode:
authorRobert Haas <rhaas@postgresql.org>2016-12-07 13:17:43 -0500
committerRobert Haas <rhaas@postgresql.org>2016-12-07 13:17:55 -0500
commitf0e44751d7175fa3394da2c8f85e3ceb3cdbfe63 (patch)
treed869b1c4ae0416d1e7a36adb72e3683eb8ce0266 /src/backend/parser/parse_utilcmd.c
parentb7e1ae2328f7d5a88d8916d78b4561d8ef16f99b (diff)
downloadpostgresql-f0e44751d7175fa3394da2c8f85e3ceb3cdbfe63.tar.gz
postgresql-f0e44751d7175fa3394da2c8f85e3ceb3cdbfe63.zip
Implement table partitioning.
Table partitioning is like table inheritance and reuses much of the existing infrastructure, but there are some important differences. The parent is called a partitioned table and is always empty; it may not have indexes or non-inherited constraints, since those make no sense for a relation with no data of its own. The children are called partitions and contain all of the actual data. Each partition has an implicit partitioning constraint. Multiple inheritance is not allowed, and partitioning and inheritance can't be mixed. Partitions can't have extra columns and may not allow nulls unless the parent does. Tuples inserted into the parent are automatically routed to the correct partition, so tuple-routing ON INSERT triggers are not needed. Tuple routing isn't yet supported for partitions which are foreign tables, and it doesn't handle updates that cross partition boundaries. Currently, tables can be range-partitioned or list-partitioned. List partitioning is limited to a single column, but range partitioning can involve multiple columns. A partitioning "column" can be an expression. Because table partitioning is less general than table inheritance, it is hoped that it will be easier to reason about properties of partitions, and therefore that this will serve as a better foundation for a variety of possible optimizations, including query planner optimizations. The tuple routing based which this patch does based on the implicit partitioning constraints is an example of this, but it seems likely that many other useful optimizations are also possible. Amit Langote, reviewed and tested by Robert Haas, Ashutosh Bapat, Amit Kapila, Rajkumar Raghuwanshi, Corey Huinker, Jaime Casanova, Rushabh Lathia, Erik Rijkers, among others. Minor revisions by me.
Diffstat (limited to 'src/backend/parser/parse_utilcmd.c')
-rw-r--r--src/backend/parser/parse_utilcmd.c326
1 files changed, 324 insertions, 2 deletions
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0670bc24822..cc6a961bb46 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -47,8 +47,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -62,6 +64,7 @@
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
+#include "utils/ruleutils.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -87,6 +90,8 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -129,6 +134,7 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
/*
@@ -229,6 +235,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partspec != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +254,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partspec)
+ {
+ int partnatts = list_length(stmt->partspec->partParams);
+
+ if (stmt->inhRelations && !stmt->partbound)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create partitioned table as inheritance child")));
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot partition using more than %d columns",
+ PARTITION_MAX_KEYS)));
+
+ if (!pg_strcasecmp(stmt->partspec->strategy, "list") &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot list partition using more than one column")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -583,6 +612,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -592,6 +627,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -609,6 +650,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -674,6 +721,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -684,6 +737,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -694,6 +753,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -708,6 +773,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -763,7 +834,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table",
@@ -1854,7 +1926,8 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
rel = heap_openrv(inh, AccessShareLock);
/* check user requested inheritance from valid relkind */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
- rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -2512,6 +2585,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2594,6 +2669,19 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
break;
}
+ case AT_AttachPartition:
+ {
+ PartitionCmd *partcmd = (PartitionCmd *) cmd->def;
+
+ transformAttachPartition(&cxt, partcmd);
+
+ /* assign transformed values */
+ partcmd->bound = cxt.partbound;
+ }
+
+ newcmds = lappend(newcmds, cmd);
+ break;
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -2958,3 +3046,237 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /*
+ * We are going to try to validate the partition bound specification
+ * against the partition key of rel, so it better have one.
+ */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+
+ /* tranform the values */
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+ cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
+ cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound specification
+ */
+Node *
+transformPartitionBound(ParseState *pstate, Relation parent, Node *bound)
+{
+ PartitionBoundSpec *spec = (PartitionBoundSpec *) bound,
+ *result_spec;
+ PartitionKey key = RelationGetPartitionKey(parent);
+ char strategy = get_partition_strategy(key);
+ int partnatts = get_partition_natts(key);
+ List *partexprs = get_partition_exprs(key);
+
+ result_spec = copyObject(spec);
+
+ if (strategy == PARTITION_STRATEGY_LIST)
+ {
+ ListCell *cell;
+ char *colname;
+
+ /* Get the only column's name in case we need to output an error */
+ if (key->partattrs[0] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[0]);
+ else
+ colname = deparse_expression((Node *) linitial(partexprs),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+
+ if (spec->strategy != PARTITION_STRATEGY_LIST)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(pstate, exprLocation(bound))));
+
+ result_spec->listdatums = NIL;
+ foreach(cell, spec->listdatums)
+ {
+ A_Const *con = (A_Const *) lfirst(cell);
+ Node *value;
+ ListCell *cell2;
+ bool duplicate;
+
+ value = (Node *) make_const(pstate, &con->val, con->location);
+ value = coerce_to_target_type(pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, 0),
+ get_partition_col_typmod(key, 0),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+ format_type_be(get_partition_col_typid(key, 0)),
+ colname),
+ parser_errposition(pstate,
+ exprLocation((Node *) con))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ /* Don't add to the result if the value is a duplicate */
+ duplicate = false;
+ foreach(cell2, result_spec->listdatums)
+ {
+ Const *value2 = (Const *) lfirst(cell2);
+
+ if (equal(value, value2))
+ {
+ duplicate = true;
+ break;
+ }
+ }
+ if (duplicate)
+ continue;
+
+ result_spec->listdatums = lappend(result_spec->listdatums,
+ value);
+ }
+ }
+ else if (strategy == PARTITION_STRATEGY_RANGE)
+ {
+ ListCell *cell1,
+ *cell2;
+ int i,
+ j;
+ char *colname;
+
+ if (spec->strategy != PARTITION_STRATEGY_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a range partition"),
+ parser_errposition(pstate, exprLocation(bound))));
+
+ Assert(spec->lowerdatums != NIL && spec->upperdatums != NIL);
+
+ if (list_length(spec->lowerdatums) != partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("FROM must specify exactly one value per partitioning column")));
+ if (list_length(spec->upperdatums) != partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("TO must specify exactly one value per partitioning column")));
+
+ i = j = 0;
+ result_spec->lowerdatums = result_spec->upperdatums = NIL;
+ forboth(cell1, spec->lowerdatums, cell2, spec->upperdatums)
+ {
+ PartitionRangeDatum *ldatum,
+ *rdatum;
+ Node *value;
+ A_Const *lcon = NULL,
+ *rcon = NULL;
+
+ ldatum = (PartitionRangeDatum *) lfirst(cell1);
+ rdatum = (PartitionRangeDatum *) lfirst(cell2);
+ /* Get the column's name in case we need to output an error */
+ if (key->partattrs[i] != 0)
+ colname = get_relid_attribute_name(RelationGetRelid(parent),
+ key->partattrs[i]);
+ else
+ {
+ colname = deparse_expression((Node *) list_nth(partexprs, j),
+ deparse_context_for(RelationGetRelationName(parent),
+ RelationGetRelid(parent)),
+ false, false);
+ ++j;
+ }
+
+ if (!ldatum->infinite)
+ lcon = (A_Const *) ldatum->value;
+ if (!rdatum->infinite)
+ rcon = (A_Const *) rdatum->value;
+
+ if (lcon)
+ {
+ value = (Node *) make_const(pstate, &lcon->val, lcon->location);
+ if (((Const *) value)->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ value = coerce_to_target_type(pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(pstate, exprLocation((Node *) ldatum))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+ ldatum->value = value;
+ }
+
+ if (rcon)
+ {
+ value = (Node *) make_const(pstate, &rcon->val, rcon->location);
+ if (((Const *) value)->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ value = coerce_to_target_type(pstate,
+ value, exprType(value),
+ get_partition_col_typid(key, i),
+ get_partition_col_typmod(key, i),
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\" of column \"%s\"",
+ format_type_be(get_partition_col_typid(key, i)),
+ colname),
+ parser_errposition(pstate, exprLocation((Node *) rdatum))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+ rdatum->value = value;
+ }
+
+ result_spec->lowerdatums = lappend(result_spec->lowerdatums,
+ copyObject(ldatum));
+ result_spec->upperdatums = lappend(result_spec->upperdatums,
+ copyObject(rdatum));
+
+ ++i;
+ }
+ }
+ else
+ elog(ERROR, "unexpected partition strategy: %d", (int) strategy);
+
+ return (Node *) result_spec;
+}