diff options
author | Robert Haas <rhaas@postgresql.org> | 2016-12-07 13:17:43 -0500 |
---|---|---|
committer | Robert Haas <rhaas@postgresql.org> | 2016-12-07 13:17:55 -0500 |
commit | f0e44751d7175fa3394da2c8f85e3ceb3cdbfe63 (patch) | |
tree | d869b1c4ae0416d1e7a36adb72e3683eb8ce0266 /src/backend/parser | |
parent | b7e1ae2328f7d5a88d8916d78b4561d8ef16f99b (diff) | |
download | postgresql-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')
-rw-r--r-- | src/backend/parser/analyze.c | 8 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 347 | ||||
-rw-r--r-- | src/backend/parser/parse_agg.c | 10 | ||||
-rw-r--r-- | src/backend/parser/parse_expr.c | 5 | ||||
-rw-r--r-- | src/backend/parser/parse_func.c | 3 | ||||
-rw-r--r-- | src/backend/parser/parse_utilcmd.c | 326 |
6 files changed, 680 insertions, 19 deletions
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 1a541788eb1..73643461672 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -806,8 +806,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) /* Process ON CONFLICT, if any. */ if (stmt->onConflictClause) + { + /* Bail out if target relation is partitioned table */ + if (pstate->p_target_rangetblentry->relkind == RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ON CONFLICT clause is not supported with partitioned tables"))); + qry->onConflict = transformOnConflictClause(pstate, stmt->onConflictClause); + } /* * If we have a RETURNING clause, we need to add the target relation to diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 414348b95b4..2ed7b5259d0 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -229,6 +229,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); struct ImportQual *importqual; InsertStmt *istmt; VariableSetStmt *vsetstmt; + PartitionElem *partelem; + PartitionSpec *partspec; + PartitionRangeDatum *partrange_datum; } %type <node> stmt schema_stmt @@ -276,7 +279,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <ival> add_drop opt_asc_desc opt_nulls_order %type <node> alter_table_cmd alter_type_cmd opt_collate_clause - replica_identity + replica_identity partition_cmd %type <list> alter_table_cmds alter_type_cmds %type <dbehavior> opt_drop_behavior @@ -545,6 +548,17 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); opt_frame_clause frame_extent frame_bound %type <str> opt_existing_window_name %type <boolean> opt_if_not_exists +%type <partspec> PartitionSpec OptPartitionSpec +%type <str> part_strategy +%type <partelem> part_elem +%type <list> part_params +%type <list> OptPartitionElementList PartitionElementList +%type <node> PartitionElement +%type <node> ForValues +%type <node> partbound_datum +%type <list> partbound_datum_list +%type <partrange_datum> PartitionRangeDatum +%type <list> range_datum_list /* * Non-keyword token types. These are hard-wired into the "flex" lexer. @@ -570,7 +584,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); /* ordinary key words in alphabetical order */ %token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC - ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION + ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT BOOLEAN_P BOTH BY @@ -586,7 +600,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC - DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP + DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P + DOUBLE_P DROP EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN @@ -1787,6 +1802,24 @@ AlterTableStmt: n->missing_ok = true; $$ = (Node *)n; } + | ALTER TABLE relation_expr partition_cmd + { + AlterTableStmt *n = makeNode(AlterTableStmt); + n->relation = $3; + n->cmds = list_make1($4); + n->relkind = OBJECT_TABLE; + n->missing_ok = false; + $$ = (Node *)n; + } + | ALTER TABLE IF_P EXISTS relation_expr partition_cmd + { + AlterTableStmt *n = makeNode(AlterTableStmt); + n->relation = $5; + n->cmds = list_make1($6); + n->relkind = OBJECT_TABLE; + n->missing_ok = true; + $$ = (Node *)n; + } | ALTER TABLE ALL IN_P TABLESPACE name SET TABLESPACE name opt_nowait { AlterTableMoveAllStmt *n = @@ -1932,6 +1965,34 @@ alter_table_cmds: | alter_table_cmds ',' alter_table_cmd { $$ = lappend($1, $3); } ; +partition_cmd: + /* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */ + ATTACH PARTITION qualified_name ForValues + { + AlterTableCmd *n = makeNode(AlterTableCmd); + PartitionCmd *cmd = makeNode(PartitionCmd); + + n->subtype = AT_AttachPartition; + cmd->name = $3; + cmd->bound = (Node *) $4; + n->def = (Node *) cmd; + + $$ = (Node *) n; + } + /* ALTER TABLE <name> DETACH PARTITION <partition_name> */ + | DETACH PARTITION qualified_name + { + AlterTableCmd *n = makeNode(AlterTableCmd); + PartitionCmd *cmd = makeNode(PartitionCmd); + + n->subtype = AT_DetachPartition; + cmd->name = $3; + n->def = (Node *) cmd; + + $$ = (Node *) n; + } + ; + alter_table_cmd: /* ALTER TABLE <name> ADD <coldef> */ ADD_P columnDef @@ -2467,6 +2528,73 @@ reloption_elem: } ; +ForValues: + /* a LIST partition */ + FOR VALUES IN_P '(' partbound_datum_list ')' + { + PartitionBoundSpec *n = makeNode(PartitionBoundSpec); + + n->strategy = PARTITION_STRATEGY_LIST; + n->listdatums = $5; + n->location = @3; + + $$ = (Node *) n; + } + + /* a RANGE partition */ + | FOR VALUES FROM '(' range_datum_list ')' TO '(' range_datum_list ')' + { + PartitionBoundSpec *n = makeNode(PartitionBoundSpec); + + n->strategy = PARTITION_STRATEGY_RANGE; + n->lowerdatums = $5; + n->upperdatums = $9; + n->location = @3; + + $$ = (Node *) n; + } + ; + +partbound_datum: + Sconst { $$ = makeStringConst($1, @1); } + | NumericOnly { $$ = makeAConst($1, @1); } + | NULL_P { $$ = makeNullAConst(@1); } + ; + +partbound_datum_list: + partbound_datum { $$ = list_make1($1); } + | partbound_datum_list ',' partbound_datum + { $$ = lappend($1, $3); } + ; + +range_datum_list: + PartitionRangeDatum { $$ = list_make1($1); } + | range_datum_list ',' PartitionRangeDatum + { $$ = lappend($1, $3); } + ; + +PartitionRangeDatum: + UNBOUNDED + { + PartitionRangeDatum *n = makeNode(PartitionRangeDatum); + + n->infinite = true; + n->value = NULL; + n->location = @1; + + $$ = n; + } + | partbound_datum + { + PartitionRangeDatum *n = makeNode(PartitionRangeDatum); + + n->infinite = false; + n->value = $1; + n->location = @1; + + $$ = n; + } + ; /***************************************************************************** * @@ -2812,69 +2940,113 @@ copy_generic_opt_arg_list_item: *****************************************************************************/ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')' - OptInherit OptWith OnCommitOption OptTableSpace + OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace { CreateStmt *n = makeNode(CreateStmt); $4->relpersistence = $2; n->relation = $4; n->tableElts = $6; n->inhRelations = $8; + n->partspec = $9; n->ofTypename = NULL; n->constraints = NIL; - n->options = $9; - n->oncommit = $10; - n->tablespacename = $11; + n->options = $10; + n->oncommit = $11; + n->tablespacename = $12; n->if_not_exists = false; $$ = (Node *)n; } | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '(' - OptTableElementList ')' OptInherit OptWith OnCommitOption - OptTableSpace + OptTableElementList ')' OptInherit OptPartitionSpec OptWith + OnCommitOption OptTableSpace { CreateStmt *n = makeNode(CreateStmt); $7->relpersistence = $2; n->relation = $7; n->tableElts = $9; n->inhRelations = $11; + n->partspec = $12; n->ofTypename = NULL; n->constraints = NIL; - n->options = $12; - n->oncommit = $13; - n->tablespacename = $14; + n->options = $13; + n->oncommit = $14; + n->tablespacename = $15; n->if_not_exists = true; $$ = (Node *)n; } | CREATE OptTemp TABLE qualified_name OF any_name - OptTypedTableElementList OptWith OnCommitOption OptTableSpace + OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption + OptTableSpace { CreateStmt *n = makeNode(CreateStmt); $4->relpersistence = $2; n->relation = $4; n->tableElts = $7; n->inhRelations = NIL; + n->partspec = $8; n->ofTypename = makeTypeNameFromNameList($6); n->ofTypename->location = @6; n->constraints = NIL; - n->options = $8; - n->oncommit = $9; - n->tablespacename = $10; + n->options = $9; + n->oncommit = $10; + n->tablespacename = $11; n->if_not_exists = false; $$ = (Node *)n; } | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name - OptTypedTableElementList OptWith OnCommitOption OptTableSpace + OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption + OptTableSpace { CreateStmt *n = makeNode(CreateStmt); $7->relpersistence = $2; n->relation = $7; n->tableElts = $10; n->inhRelations = NIL; + n->partspec = $11; n->ofTypename = makeTypeNameFromNameList($9); n->ofTypename->location = @9; n->constraints = NIL; + n->options = $12; + n->oncommit = $13; + n->tablespacename = $14; + n->if_not_exists = true; + $$ = (Node *)n; + } + | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name + OptPartitionElementList ForValues OptPartitionSpec OptWith + OnCommitOption OptTableSpace + { + CreateStmt *n = makeNode(CreateStmt); + $4->relpersistence = $2; + n->relation = $4; + n->tableElts = $8; + n->inhRelations = list_make1($7); + n->partbound = (Node *) $9; + n->partspec = $10; + n->ofTypename = NULL; + n->constraints = NIL; n->options = $11; n->oncommit = $12; n->tablespacename = $13; + n->if_not_exists = false; + $$ = (Node *)n; + } + | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF + qualified_name OptPartitionElementList ForValues OptPartitionSpec + OptWith OnCommitOption OptTableSpace + { + CreateStmt *n = makeNode(CreateStmt); + $7->relpersistence = $2; + n->relation = $7; + n->tableElts = $11; + n->inhRelations = list_make1($10); + n->partbound = (Node *) $12; + n->partspec = $13; + n->ofTypename = NULL; + n->constraints = NIL; + n->options = $14; + n->oncommit = $15; + n->tablespacename = $16; n->if_not_exists = true; $$ = (Node *)n; } @@ -2923,6 +3095,11 @@ OptTypedTableElementList: | /*EMPTY*/ { $$ = NIL; } ; +OptPartitionElementList: + '(' PartitionElementList ')' { $$ = $2; } + | /*EMPTY*/ { $$ = NIL; } + ; + TableElementList: TableElement { @@ -2945,6 +3122,17 @@ TypedTableElementList: } ; +PartitionElementList: + PartitionElement + { + $$ = list_make1($1); + } + | PartitionElementList ',' PartitionElement + { + $$ = lappend($1, $3); + } + ; + TableElement: columnDef { $$ = $1; } | TableLikeClause { $$ = $1; } @@ -2956,6 +3144,28 @@ TypedTableElement: | TableConstraint { $$ = $1; } ; +PartitionElement: + TableConstraint { $$ = $1; } + | ColId ColQualList + { + ColumnDef *n = makeNode(ColumnDef); + n->colname = $1; + n->typeName = NULL; + n->inhcount = 0; + n->is_local = true; + n->is_not_null = false; + n->is_from_type = false; + n->storage = 0; + n->raw_default = NULL; + n->cooked_default = NULL; + n->collOid = InvalidOid; + SplitColQualList($2, &n->constraints, &n->collClause, + yyscanner); + n->location = @1; + $$ = (Node *) n; + } + ; + columnDef: ColId Typename create_generic_options ColQualList { ColumnDef *n = makeNode(ColumnDef); @@ -3419,6 +3629,65 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; } | /*EMPTY*/ { $$ = NIL; } ; +/* Optional partition key specification */ +OptPartitionSpec: PartitionSpec { $$ = $1; } + | /*EMPTY*/ { $$ = NULL; } + ; + +PartitionSpec: PARTITION BY part_strategy '(' part_params ')' + { + PartitionSpec *n = makeNode(PartitionSpec); + + n->strategy = $3; + n->partParams = $5; + n->location = @1; + + $$ = n; + } + ; + +part_strategy: IDENT { $$ = $1; } + | unreserved_keyword { $$ = pstrdup($1); } + ; + +part_params: part_elem { $$ = list_make1($1); } + | part_params ',' part_elem { $$ = lappend($1, $3); } + ; + +part_elem: ColId opt_collate opt_class + { + PartitionElem *n = makeNode(PartitionElem); + + n->name = $1; + n->expr = NULL; + n->collation = $2; + n->opclass = $3; + n->location = @1; + $$ = n; + } + | func_expr_windowless opt_collate opt_class + { + PartitionElem *n = makeNode(PartitionElem); + + n->name = NULL; + n->expr = $1; + n->collation = $2; + n->opclass = $3; + n->location = @1; + $$ = n; + } + | '(' a_expr ')' opt_collate opt_class + { + PartitionElem *n = makeNode(PartitionElem); + + n->name = NULL; + n->expr = $2; + n->collation = $4; + n->opclass = $5; + n->location = @1; + $$ = n; + } + ; /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */ OptWith: WITH reloptions { $$ = $2; } @@ -4484,6 +4753,48 @@ CreateForeignTableStmt: n->options = $14; $$ = (Node *) n; } + | CREATE FOREIGN TABLE qualified_name + PARTITION OF qualified_name OptPartitionElementList ForValues + SERVER name create_generic_options + { + CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt); + $4->relpersistence = RELPERSISTENCE_PERMANENT; + n->base.relation = $4; + n->base.inhRelations = list_make1($7); + n->base.tableElts = $8; + n->base.partbound = (Node *) $9; + n->base.ofTypename = NULL; + n->base.constraints = NIL; + n->base.options = NIL; + n->base.oncommit = ONCOMMIT_NOOP; + n->base.tablespacename = NULL; + n->base.if_not_exists = false; + /* FDW-specific data */ + n->servername = $11; + n->options = $12; + $$ = (Node *) n; + } + | CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name + PARTITION OF qualified_name OptPartitionElementList ForValues + SERVER name create_generic_options + { + CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt); + $7->relpersistence = RELPERSISTENCE_PERMANENT; + n->base.relation = $7; + n->base.inhRelations = list_make1($10); + n->base.tableElts = $11; + n->base.partbound = (Node *) $12; + n->base.ofTypename = NULL; + n->base.constraints = NIL; + n->base.options = NIL; + n->base.oncommit = ONCOMMIT_NOOP; + n->base.tablespacename = NULL; + n->base.if_not_exists = true; + /* FDW-specific data */ + n->servername = $14; + n->options = $15; + $$ = (Node *) n; + } ; /***************************************************************************** @@ -13703,6 +14014,7 @@ unreserved_keyword: | ASSERTION | ASSIGNMENT | AT + | ATTACH | ATTRIBUTE | BACKWARD | BEFORE @@ -13749,6 +14061,7 @@ unreserved_keyword: | DELIMITER | DELIMITERS | DEPENDS + | DETACH | DICTIONARY | DISABLE_P | DISCARD diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 481a4ddc484..92d1577030c 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -501,6 +501,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) err = _("grouping operations are not allowed in trigger WHEN conditions"); break; + case EXPR_KIND_PARTITION_EXPRESSION: + if (isAgg) + err = _("aggregate functions are not allowed in partition key expression"); + else + err = _("grouping operations are not allowed in partition key expression"); + + break; /* * There is intentionally no default: case here, so that the @@ -858,6 +865,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, case EXPR_KIND_TRIGGER_WHEN: err = _("window functions are not allowed in trigger WHEN conditions"); break; + case EXPR_KIND_PARTITION_EXPRESSION: + err = _("window functions are not allowed in partition key expression"); + break; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 17d1cbf8b32..8a2bdf06e8d 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -1843,6 +1843,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink) case EXPR_KIND_TRIGGER_WHEN: err = _("cannot use subquery in trigger WHEN condition"); break; + case EXPR_KIND_PARTITION_EXPRESSION: + err = _("cannot use subquery in partition key expression"); + break; /* * There is intentionally no default: case here, so that the @@ -3446,6 +3449,8 @@ ParseExprKindName(ParseExprKind exprKind) return "EXECUTE"; case EXPR_KIND_TRIGGER_WHEN: return "WHEN"; + case EXPR_KIND_PARTITION_EXPRESSION: + return "PARTITION BY"; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index 56c9a4293df..7d9b4157d4d 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location) case EXPR_KIND_TRIGGER_WHEN: err = _("set-returning functions are not allowed in trigger WHEN conditions"); break; + case EXPR_KIND_PARTITION_EXPRESSION: + err = _("set-returning functions are not allowed in partition key expression"); + break; /* * There is intentionally no default: case here, so that the 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; +} |