Proposal: Automatic partition creation
The previous discussion of automatic partition creation [1]/messages/by-id/alpine.DEB.2.21.1907150711080.22273@lancre has
addressed static and dynamic creation of partitions and ended up with
several syntax proposals.
In this thread, I want to continue this work.
Attached is PoC for static partition creation. The patch core is quite
straightforward. It adds one more transform clause to convert given
partitioning specification into several CREATE TABLE statements.
The patch implements following syntax:
CREATE TABLE ... PARTITION BY partition_method (list_of_columns)
partition_auto_create_clause
where partition_auto_create_clause is
CONFIGURATION [IMMEDIATE| DEFERRED] USING partition_bound_spec
and partition_bound_spec is:
MODULUS integer | VALUES IN (expr [,...]) [, ....] | INTERVAL
range_step FROM range_start TO range_end
For more examples check auto_partitions.sql in the patch.
TODO:
- CONFIGURATION is just an existing keyword, that I picked as a stub.
Ideas on better wording are welcome.
- IMMEDIATE| DEFERRED is optional, DEFERRED is not implemented yet
I wonder, is it worth placing a stub for dynamic partitioning, or we can
rather add these keywords later.
- HASH and LIST static partitioning works as expected.
Testing and feedback are welcome.
- RANGE partitioning is not really implemented in this patch.
Now it only accepts interval data type as 'interval' and respectively
date types as range_start and range_end expressions.
Only one partition is created. I found it difficult to implement the
generation of bounds using internal functions and data types.
Both existing solutions (pg_pathman and pg_partman) rely on SQL level
routines [2]https://github.com/postgrespro/pg_pathman/blob/dbcbd02e411e6acea6d97f572234746007979538/range.sql#L99.
I am going to implement this via SPI, which allow to simplify checks and
calculations. Do you see any pitfalls in this approach?
- Partition naming. Now partition names for all methods look like
$tablename_$partnum
Do we want more intelligence here? Now we have
RunObjectPostCreateHook(), which allows to rename the table.
To make it more user-friendly, we can later implement pl/pgsql function
that sets the callback, as it is done in pg_pathman set_init_callback() [3]https://github.com/postgrespro/pg_pathman#additional-parameters.
- Current design doesn't allow to create default partition
automatically. Do we need this functionality?
- Do you see any restrictions for future extensibility (dynamic
partitioning, init_callback, etc.) in the proposed design ?
I expect this to be a long discussion, so here is the wiki page [4]https://wiki.postgresql.org/wiki/Declarative_partitioning_improvements to
fix important questions and final agreements.
[1]: /messages/by-id/alpine.DEB.2.21.1907150711080.22273@lancre
/messages/by-id/alpine.DEB.2.21.1907150711080.22273@lancre
[2]: https://github.com/postgrespro/pg_pathman/blob/dbcbd02e411e6acea6d97f572234746007979538/range.sql#L99
https://github.com/postgrespro/pg_pathman/blob/dbcbd02e411e6acea6d97f572234746007979538/range.sql#L99
[3]: https://github.com/postgrespro/pg_pathman#additional-parameters
[4]: https://wiki.postgresql.org/wiki/Declarative_partitioning_improvements
--
Anastasia Lubennikova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
0001-WIP-create-partitions-automatically.patchtext/x-patch; charset=UTF-8; name=0001-WIP-create-partitions-automatically.patchDownload
From 5808c5e843cac1e1383366a5cbff116eaa433f90 Mon Sep 17 00:00:00 2001
From: anastasia <a.lubennikova@postgrespro.ru>
Date: Fri, 3 Jul 2020 03:34:24 +0300
Subject: [PATCH] WIP create partitions automatically Implement new syntax to
generate bounds for HASH, LIST and RANGE partitions. Implement automatic
partition creation for HASH and LIST. Check new regression test
'auto_partitions.sql' for syntax examples
---
src/backend/commands/tablecmds.c | 7 +
src/backend/nodes/copyfuncs.c | 33 ++++
src/backend/nodes/equalfuncs.c | 29 +++
src/backend/nodes/outfuncs.c | 28 +++
src/backend/nodes/readfuncs.c | 33 ++++
src/backend/parser/gram.y | 160 ++++++++++++++---
src/backend/parser/parse_utilcmd.c | 166 ++++++++++++++++++
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 43 +++++
src/include/parser/kwlist.h | 1 +
src/include/partitioning/partdefs.h | 4 +
src/test/regress/expected/auto_partitions.out | 0
src/test/regress/parallel_schedule | 2 +
src/test/regress/serial_schedule | 1 +
src/test/regress/sql/auto_partitions.sql | 43 +++++
15 files changed, 523 insertions(+), 29 deletions(-)
create mode 100644 src/test/regress/expected/auto_partitions.out
create mode 100644 src/test/regress/sql/auto_partitions.sql
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f79044f39f..7b2c651952 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -628,6 +628,13 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
else
partitioned = false;
+ if (!partitioned && stmt->partautocreate)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("PARTITION bounds can only be used on partitioned tables")));
+ }
+
/*
* Look up the namespace in which we are supposed to create the relation,
* check we have permission to create there, lock it against concurrent
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d8cf87e6d0..74a305c5d1 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3393,6 +3393,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(inhRelations);
COPY_NODE_FIELD(partspec);
COPY_NODE_FIELD(partbound);
+ COPY_NODE_FIELD(partautocreate);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4651,6 +4652,32 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
return newnode;
}
+static PartitionBoundAutoSpec *
+_copyPartitionBoundAutoSpec(const PartitionBoundAutoSpec *from)
+{
+ PartitionBoundAutoSpec *newnode = makeNode(PartitionBoundAutoSpec);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_SCALAR_FIELD(modulus);
+ COPY_NODE_FIELD(listdatumsList);
+ COPY_NODE_FIELD(interval);
+ COPY_NODE_FIELD(lowerdatums);
+ COPY_NODE_FIELD(upperdatums);
+
+ return newnode;
+}
+
+static PartitionAutoCreate *
+_copyPartitionAutoCreate(const PartitionAutoCreate *from)
+{
+ PartitionAutoCreate *newnode = makeNode(PartitionAutoCreate);
+
+ COPY_SCALAR_FIELD(is_deferred);
+ COPY_NODE_FIELD(bound);
+
+ return newnode;
+}
+
static PartitionRangeDatum *
_copyPartitionRangeDatum(const PartitionRangeDatum *from)
{
@@ -5700,6 +5727,12 @@ copyObjectImpl(const void *from)
case T_PartitionBoundSpec:
retval = _copyPartitionBoundSpec(from);
break;
+ case T_PartitionBoundAutoSpec:
+ retval = _copyPartitionBoundAutoSpec(from);
+ break;
+ case T_PartitionAutoCreate:
+ retval = _copyPartitionAutoCreate(from);
+ break;
case T_PartitionRangeDatum:
retval = _copyPartitionRangeDatum(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 627b026b19..5ca9ed6a90 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2917,6 +2917,29 @@ _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *
return true;
}
+static bool
+_equalPartitionBoundAutoSpec(const PartitionBoundAutoSpec *a,
+ const PartitionBoundAutoSpec *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_SCALAR_FIELD(modulus);
+ COMPARE_NODE_FIELD(listdatumsList);
+ COMPARE_NODE_FIELD(interval);
+ COMPARE_NODE_FIELD(lowerdatums);
+ COMPARE_NODE_FIELD(upperdatums);
+
+ return true;
+}
+
+static bool
+_equalPartitionAutoCreate(const PartitionAutoCreate *a, const PartitionAutoCreate *b)
+{
+ COMPARE_SCALAR_FIELD(is_deferred);
+ COMPARE_NODE_FIELD(bound);
+
+ return true;
+}
+
static bool
_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
{
@@ -3752,6 +3775,12 @@ equal(const void *a, const void *b)
case T_PartitionBoundSpec:
retval = _equalPartitionBoundSpec(a, b);
break;
+ case T_PartitionBoundAutoSpec:
+ retval = _equalPartitionBoundAutoSpec(a, b);
+ break;
+ case T_PartitionAutoCreate:
+ retval = _equalPartitionAutoCreate(a, b);
+ break;
case T_PartitionRangeDatum:
retval = _equalPartitionRangeDatum(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..35b1438197 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3661,6 +3661,28 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionBoundAutoSpec(StringInfo str, const PartitionBoundAutoSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBOUNDAUTOSPEC");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_INT_FIELD(modulus);
+ WRITE_NODE_FIELD(listdatumsList);
+ WRITE_NODE_FIELD(interval);
+ WRITE_NODE_FIELD(lowerdatums);
+ WRITE_NODE_FIELD(upperdatums);
+}
+
+static void
+_outPartitionAutoCreate(StringInfo str, const PartitionAutoCreate *node)
+{
+ WRITE_NODE_TYPE("PartitionAutoCreate");
+
+ WRITE_BOOL_FIELD(is_deferred);
+ WRITE_NODE_FIELD(bound);
+}
+
static void
_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
{
@@ -4334,6 +4356,12 @@ outNode(StringInfo str, const void *obj)
case T_PartitionBoundSpec:
_outPartitionBoundSpec(str, obj);
break;
+ case T_PartitionBoundAutoSpec:
+ _outPartitionBoundAutoSpec(str, obj);
+ break;
+ case T_PartitionAutoCreate:
+ _outPartitionAutoCreate(str, obj);
+ break;
case T_PartitionRangeDatum:
_outPartitionRangeDatum(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..fa2b08316b 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2602,6 +2602,35 @@ _readPartitionBoundSpec(void)
READ_DONE();
}
+static PartitionBoundAutoSpec *
+_readPartitionBoundAutoSpec(void)
+{
+ READ_LOCALS(PartitionBoundAutoSpec);
+
+ READ_CHAR_FIELD(strategy);
+ READ_INT_FIELD(modulus);
+ READ_NODE_FIELD(listdatumsList);
+ READ_NODE_FIELD(interval);
+ READ_NODE_FIELD(lowerdatums);
+ READ_NODE_FIELD(upperdatums);
+
+ READ_DONE();
+}
+
+/*
+ * _readPartitionAutoCreate
+ */
+static PartitionAutoCreate *
+_readPartitionAutoCreate(void)
+{
+ READ_LOCALS(PartitionAutoCreate);
+
+ READ_BOOL_FIELD(is_deferred);
+ READ_NODE_FIELD(bound);
+
+ READ_DONE();
+}
+
/*
* _readPartitionRangeDatum
*/
@@ -2880,6 +2909,10 @@ parseNodeString(void)
return_value = _readPartitionBoundSpec();
else if (MATCH("PARTITIONRANGEDATUM", 19))
return_value = _readPartitionRangeDatum();
+ else if (MATCH("PARTITIONAUTPBOUNDSPEC", 21))
+ return_value = _readPartitionAutoCreate();
+ else if (MATCH("PARTITIONBOUNDAUTOSPEC", 22))
+ return_value = _readPartitionBoundAutoSpec();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 4ff35095b8..3c62837763 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -249,6 +249,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionElem *partelem;
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
+ PartitionBoundAutoSpec *partboundautospec;
+ PartitionAutoCreate *partautocreate;
RoleSpec *rolespec;
struct SelectLimit *selectlimit;
}
@@ -599,6 +601,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
+%type <partboundautospec> PartitionBoundAutoSpec values_in_clause
+%type <partautocreate> PartitionAutoCreate OptPartitionAutoCreate
+%type <boolean> opt_part_deferred
+%type <node> range_interval
+
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
* They must be listed first so that their numeric codes do not depend on
@@ -669,7 +676,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
- MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
+ MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MODULUS MONTH_P MOVE
NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NFC NFD NFKC NFKD NO NONE
NORMALIZE NORMALIZED
@@ -3179,7 +3186,8 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptPartitionSpec table_access_method_clause OptWith
+ OptInherit OptPartitionSpec OptPartitionAutoCreate
+ table_access_method_clause OptWith
OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
@@ -3188,17 +3196,19 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->tableElts = $6;
n->inhRelations = $8;
n->partspec = $9;
+ n->partautocreate = $10;
n->ofTypename = NULL;
n->constraints = NIL;
- n->accessMethod = $10;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->accessMethod = $11;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptPartitionSpec table_access_method_clause
+ OptTableElementList ')' OptInherit OptPartitionSpec
+ OptPartitionAutoCreate table_access_method_clause
OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
@@ -3207,17 +3217,19 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->tableElts = $9;
n->inhRelations = $11;
n->partspec = $12;
+ n->partautocreate = $13;
n->ofTypename = NULL;
n->constraints = NIL;
- n->accessMethod = $13;
- n->options = $14;
- n->oncommit = $15;
- n->tablespacename = $16;
+ n->accessMethod = $14;
+ n->options = $15;
+ n->oncommit = $16;
+ n->tablespacename = $17;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptPartitionSpec table_access_method_clause
+ OptTypedTableElementList OptPartitionSpec
+ OptPartitionAutoCreate table_access_method_clause
OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
@@ -3226,18 +3238,20 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->tableElts = $7;
n->inhRelations = NIL;
n->partspec = $8;
+ n->partautocreate = $9;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->accessMethod = $9;
- n->options = $10;
- n->oncommit = $11;
- n->tablespacename = $12;
+ n->accessMethod = $10;
+ 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 OF any_name
- OptTypedTableElementList OptPartitionSpec table_access_method_clause
+ OptTypedTableElementList OptPartitionSpec
+ OptPartitionAutoCreate table_access_method_clause
OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
@@ -3246,18 +3260,20 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->tableElts = $10;
n->inhRelations = NIL;
n->partspec = $11;
+ n->partautocreate = $12;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->accessMethod = $12;
- n->options = $13;
- n->oncommit = $14;
- n->tablespacename = $15;
+ n->accessMethod = $13;
+ n->options = $14;
+ n->oncommit = $15;
+ n->tablespacename = $16;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
+ OptPartitionAutoCreate
table_access_method_clause OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
@@ -3267,17 +3283,19 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->inhRelations = list_make1($7);
n->partbound = $9;
n->partspec = $10;
+ n->partautocreate = $11;
n->ofTypename = NULL;
n->constraints = NIL;
- n->accessMethod = $11;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->accessMethod = $12;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
qualified_name OptTypedTableElementList PartitionBoundSpec OptPartitionSpec
+ OptPartitionAutoCreate
table_access_method_clause OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
@@ -3287,12 +3305,13 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->inhRelations = list_make1($10);
n->partbound = $12;
n->partspec = $13;
+ n->partautocreate = $14;
n->ofTypename = NULL;
n->constraints = NIL;
- n->accessMethod = $14;
- n->options = $15;
- n->oncommit = $16;
- n->tablespacename = $17;
+ n->accessMethod = $15;
+ n->options = $16;
+ n->oncommit = $17;
+ n->tablespacename = $18;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3958,6 +3977,88 @@ part_elem: ColId opt_collate opt_class
}
;
+/* Optional partition automatic creation specification */
+OptPartitionAutoCreate: PartitionAutoCreate { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+/* XXX
+ * CONFIGURATION is just a random keyword that exists already and fits here.
+ * Any ideas on better wording?
+ */
+PartitionAutoCreate: CONFIGURATION opt_part_deferred USING PartitionBoundAutoSpec
+ {
+ PartitionAutoCreate *n = makeNode(PartitionAutoCreate);
+
+ n->is_deferred = $2;
+ n->bound = $4;
+
+ $$ = n;
+ }
+ ;
+
+opt_part_deferred:
+ DEFERRED { $$ = true; }
+ | IMMEDIATE { $$ = false; }
+ | /* EMPTY*/ { $$ = false;}
+ ;
+
+PartitionBoundAutoSpec:
+ /* a HASH partition */
+ MODULUS Iconst
+ {
+ PartitionBoundAutoSpec *n = makeNode(PartitionBoundAutoSpec);
+
+ n->strategy = PARTITION_STRATEGY_HASH;
+ n->modulus = (int16) $2;
+
+ $$ = n;
+ }
+
+ /* a LIST partition */
+ | values_in_clause { $$ = $1; }
+
+ /* a RANGE partition */
+ | range_interval FROM '(' expr_list ')' TO '(' expr_list ')'
+ {
+ PartitionBoundAutoSpec *n = makeNode(PartitionBoundAutoSpec);
+
+ n->strategy = PARTITION_STRATEGY_RANGE;
+ n->interval = $1;
+ n->lowerdatums = $4;
+ n->upperdatums = $8;
+
+ $$ = n;
+ }
+ ;
+
+/* TODO allow not only interval, but also other types of Const values */
+range_interval:
+ ConstInterval Sconst opt_interval
+ {
+ TypeName *t = $1;
+ t->typmods = $3;
+ $$ = makeStringConstCast($2, @2, t);
+ }
+ ;
+
+values_in_clause:
+ VALUES IN_P '(' expr_list ')'
+ {
+ PartitionBoundAutoSpec *n = makeNode(PartitionBoundAutoSpec);
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatumsList = list_make1($4);
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ | values_in_clause ',' '(' expr_list ')'
+ {
+ PartitionBoundAutoSpec *n = (PartitionBoundAutoSpec *) $1;
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatumsList = lappend(n->listdatumsList, $4);
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ ;
+
+
table_access_method_clause:
USING name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
@@ -15166,6 +15267,7 @@ unreserved_keyword:
| MINUTE_P
| MINVALUE
| MODE
+ | MODULUS
| MONTH_P
| MOVE
| NAME_P
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0e4caa6ad4..e4d638aa04 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -75,6 +75,7 @@
/* State shared by transformCreateStmt and its subroutines */
typedef struct
{
+ CreateStmt *stmt; /* Initial statement */
ParseState *pstate; /* overall parser state */
const char *stmtType; /* "CREATE [FOREIGN] TABLE" or "ALTER TABLE" */
RangeVar *relation; /* relation to create */
@@ -95,6 +96,7 @@ typedef struct
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
PartitionBoundSpec *partbound; /* transformed FOR VALUES */
+ PartitionAutoCreate *partautocreate; /* transformed PartitionAutoCreate CONFIGURATION */
bool ofType; /* true if statement contains OF typename */
} CreateStmtContext;
@@ -146,6 +148,7 @@ static Const *transformPartitionBoundValue(ParseState *pstate, Node *con,
const char *colName, Oid colType, int32 colTypmod,
Oid partCollation);
+static void transformPartitionAutoCreate(CreateStmtContext *cxt);
/*
* transformCreateStmt -
@@ -233,6 +236,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.stmtType = "CREATE TABLE";
cxt.isforeign = false;
}
+ cxt.stmt = stmt;
cxt.relation = stmt->relation;
cxt.rel = NULL;
cxt.inhRelations = stmt->inhRelations;
@@ -248,6 +252,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.pkey = NULL;
cxt.ispartitioned = stmt->partspec != NULL;
cxt.partbound = stmt->partbound;
+ cxt.partautocreate = stmt->partautocreate;
cxt.ofType = (stmt->ofTypename != NULL);
Assert(!stmt->ofTypename || !stmt->inhRelations); /* grammar enforces */
@@ -323,6 +328,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
transformExtendedStatistics(&cxt);
+ /* Process partition definitions */
+ if (cxt.partautocreate)
+ transformPartitionAutoCreate(&cxt);
+
/*
* Output results.
*/
@@ -4172,3 +4181,160 @@ transformPartitionBoundValue(ParseState *pstate, Node *val,
return (Const *) value;
}
+
+
+/*
+ * Transform configuration into a set of partition bounds.
+ * Generate statements to create partition tables.
+ */
+static void
+transformPartitionAutoCreate(CreateStmtContext *cxt)
+{
+ CreateStmt *part;
+ List *partlist = NIL;
+ ListCell *lc;
+ int i = 0;
+ PartitionBoundAutoSpec *bound = cxt->partautocreate->bound;
+
+ if (cxt->partautocreate->is_deferred)
+ elog(ERROR, "Dynamic generation of partitions is not implemented yet");
+
+ /* Generate regular partbounds based on partautocreate.
+ * Generate create table statements from these partbounds/
+ */
+ if (bound->strategy == PARTITION_STRATEGY_HASH)
+ {
+ for (i = 0; i < bound->modulus; i++)
+ {
+ char *part_relname;
+
+ /*
+ * sGenerate partition name in the format:
+ * $relname_$partnum
+ *
+ * TODO: Add checks on relname length.
+ */
+ part_relname = psprintf("%s_%d", cxt->relation->relname, i);
+
+ part = makeNode(CreateStmt);
+
+ part->relation = makeRangeVar(cxt->relation->schemaname,
+ part_relname, cxt->relation->location);
+ part->tableElts = list_copy(cxt->columns);
+ /* set table as a parent */
+ part->inhRelations = lappend(part->inhRelations, cxt->relation);
+
+ /* Actual partbound generation is here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_HASH;
+ part->partbound->modulus = bound->modulus;
+ part->partbound->remainder = i;
+ part->partbound->is_default = false;
+
+ part->partspec = NULL;
+ part->partautocreate = NULL;
+ part->ofTypename = cxt->stmt->ofTypename; //TODO
+ part->constraints = list_copy(cxt->stmt->constraints); //TODO
+ part->options = cxt->stmt->options; //TODO
+ part->oncommit = cxt->stmt->oncommit; //TODO
+ part->tablespacename = cxt->stmt->tablespacename;
+ part->accessMethod = cxt->stmt->accessMethod;
+
+ elog(DEBUG1,"Debug transformPartitionAutoCreate HASH i %d MODULUS %d \n %s\n",
+ i, bound->modulus, nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+ }
+ else if (bound->strategy == PARTITION_STRATEGY_LIST)
+ {
+
+ int n_list_parts = list_length(bound->listdatumsList);
+
+ for (i = 0; i < n_list_parts; i++)
+ {
+ char *part_relname;
+ List *listdatums = (List *)
+ list_nth(bound->listdatumsList, i);
+
+ part_relname = psprintf("%s_%d", cxt->relation->relname, i);
+
+ part = makeNode(CreateStmt);
+
+ part->relation = makeRangeVar(cxt->relation->schemaname,
+ part_relname, cxt->relation->location);
+ part->tableElts = list_copy(cxt->columns);
+ /* set table as a parent */
+ part->inhRelations = lappend(part->inhRelations, cxt->relation);
+
+ /* Actual partbound generation is here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_LIST;
+ part->partbound->listdatums = list_copy(listdatums);
+ part->partbound->is_default = false;
+
+ part->partspec = NULL;
+ part->partautocreate = NULL;
+ part->ofTypename = cxt->stmt->ofTypename; //TODO
+ part->constraints = list_copy(cxt->stmt->constraints); //TODO
+ part->options = cxt->stmt->options; //TODO
+ part->oncommit = cxt->stmt->oncommit; //TODO
+ part->tablespacename = cxt->stmt->tablespacename;
+ part->accessMethod = cxt->stmt->accessMethod;
+
+ elog(DEBUG1,"Debug transformPartitionAutoCreate LIST i %d \n %s\n",i, nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+
+ }
+ else if (bound->strategy == PARTITION_STRATEGY_RANGE)
+ {
+
+ elog(WARNING, "Automatic generation of RANGE partition bounds is not implemented yet.\n"
+ "This command will only create one partition");
+ for (i = 0; i < 1; i++)
+ {
+ char *part_relname;
+
+ part_relname = psprintf("%s_%d", cxt->relation->relname, i);
+
+ part = makeNode(CreateStmt);
+
+ part->relation = makeRangeVar(cxt->relation->schemaname,
+ part_relname, cxt->relation->location);
+ part->tableElts = list_copy(cxt->columns);
+ /* set table as a parent */
+ part->inhRelations = lappend(part->inhRelations, cxt->relation);
+
+ /* Actual partbound generation is here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_RANGE;
+ /*
+ * TODO Implement partition bound generation:s
+ * add bound->interval to lowerbound, while upperbound is not reached.
+ * Can we use SPI here to simplify operations with different data types
+ * adn their operators?
+ */
+ part->partbound->lowerdatums = list_copy(bound->lowerdatums);
+ part->partbound->upperdatums = list_copy(bound->upperdatums);
+ part->partbound->is_default = false;
+
+ part->partspec = NULL;
+ part->partautocreate = NULL;
+ part->ofTypename = cxt->stmt->ofTypename; //TODO
+ part->constraints = list_copy(cxt->stmt->constraints); //TODO
+ part->options = cxt->stmt->options; //TODO
+ part->oncommit = cxt->stmt->oncommit; //TODO
+ part->tablespacename = cxt->stmt->tablespacename;
+ part->accessMethod = cxt->stmt->accessMethod;
+
+ elog(DEBUG1,"transformPartitionAutoCreate RANGE i %d \n %s\n",i, nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+ }
+
+ /* Add statemets to create each partition */
+ cxt->alist = list_concat(cxt->alist, partlist);
+}
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..cad0e5e10c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -478,6 +478,8 @@ typedef enum NodeTag
T_PartitionElem,
T_PartitionSpec,
T_PartitionBoundSpec,
+ T_PartitionBoundAutoSpec,
+ T_PartitionAutoCreate,
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5e1ffafb91..95fc993cb4 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -828,6 +828,48 @@ struct PartitionBoundSpec
int location; /* token location, or -1 if unknown */
};
+/*
+ * PartitionBoundAutoSpec - a partition bound specification
+ *
+ * This represents the rule of generating partition bounds
+ */
+struct PartitionBoundAutoSpec
+{
+ NodeTag type;
+
+ char strategy; /* see PARTITION_STRATEGY codes above */
+
+ /* Partitioning info for HASH strategy: */
+ int modulus;
+
+ /* Partitioning info for LIST strategy: */
+ List *listdatumsList; /* List of lists of Consts (or A_Consts in raw tree) */
+
+ /* Partitioning info for RANGE strategy: */
+ Node *interval; /* TODO */
+ List *lowerdatums; /* List of PartitionRangeDatums */
+ List *upperdatums; /* List of PartitionRangeDatums */
+
+};
+
+/*
+ * PartitionAutoCreate - a partition bound specification for automatic creation
+ *
+ * This represents the information needed automatically calculate partition bounds.
+ * Now only HASH strategy is implemented
+ */
+struct PartitionAutoCreate
+{
+ NodeTag type;
+
+ bool is_deferred; /* create partitions statically (on create statement)
+ * or create them dynamically (when first insertion happens)
+ * DEFERRED creation is not supported yet.
+ */
+ /* The rule of generating partition bounds */
+ PartitionBoundAutoSpec *bound;
+};
+
/*
* PartitionRangeDatum - one of the values in a range partition bound
*
@@ -2075,6 +2117,7 @@ typedef struct CreateStmt
* inhRelation) */
PartitionBoundSpec *partbound; /* FOR VALUES clause */
PartitionSpec *partspec; /* PARTITION BY clause */
+ PartitionAutoCreate *partautocreate; /* CONFIGURATION clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 08f22ce211..497c58266c 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -251,6 +251,7 @@ PG_KEYWORD("method", METHOD, UNRESERVED_KEYWORD)
PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
PG_KEYWORD("mode", MODE, UNRESERVED_KEYWORD)
+PG_KEYWORD("modulus", MODULUS, UNRESERVED_KEYWORD)
PG_KEYWORD("month", MONTH_P, UNRESERVED_KEYWORD)
PG_KEYWORD("move", MOVE, UNRESERVED_KEYWORD)
PG_KEYWORD("name", NAME_P, UNRESERVED_KEYWORD)
diff --git a/src/include/partitioning/partdefs.h b/src/include/partitioning/partdefs.h
index 6414e2c116..160b1c66ff 100644
--- a/src/include/partitioning/partdefs.h
+++ b/src/include/partitioning/partdefs.h
@@ -19,6 +19,10 @@ typedef struct PartitionKeyData *PartitionKey;
typedef struct PartitionBoundSpec PartitionBoundSpec;
+typedef struct PartitionBoundAutoSpec PartitionBoundAutoSpec;
+
+typedef struct PartitionAutoCreate PartitionAutoCreate;
+
typedef struct PartitionDescData *PartitionDesc;
typedef struct PartitionDirectoryData *PartitionDirectory;
diff --git a/src/test/regress/expected/auto_partitions.out b/src/test/regress/expected/auto_partitions.out
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 026ea880cd..6ca08bf544 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -119,5 +119,7 @@ test: event_trigger
# this test also uses event triggers, so likewise run it by itself
test: fast_default
+test: auto_partitions
+
# run stats by itself because its delay may be insufficient under heavy load
test: stats
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 979d926119..94da936c84 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -200,4 +200,5 @@ test: tuplesort
test: explain
test: event_trigger
test: fast_default
+test: auto_partitions
test: stats
diff --git a/src/test/regress/sql/auto_partitions.sql b/src/test/regress/sql/auto_partitions.sql
new file mode 100644
index 0000000000..6df1bbaf48
--- /dev/null
+++ b/src/test/regress/sql/auto_partitions.sql
@@ -0,0 +1,43 @@
+/* Dynamic generation of partitions is not implemented yet */
+CREATE TABLE tbl_hash (i int) PARTITION BY HASH (i)
+CONFIGURATION DEFERRED USING MODULUS 3;
+
+/* Hash */
+CREATE TABLE tbl_hash (i int) PARTITION BY HASH (i)
+CONFIGURATION IMMEDIATE USING MODULUS 3;
+
+\d+ tbl_hash
+
+INSERT INTO tbl_hash select * from generate_series(0,10);
+
+SELECT i from tbl_hash_0;
+SELECT i from tbl_hash_1;
+SELECT i from tbl_hash_2;
+
+DROP TABLE tbl_hash;
+
+/* List */
+CREATE TABLE tbl_list (i char) PARTITION BY LIST (i)
+CONFIGURATION IMMEDIATE USING VALUES IN ('a', 'b'), ('c', 'd'), ('e','f');
+
+\d+ tbl_list
+
+INSERT INTO tbl_list values ('a'), ('b'), ('c'), ('d'), ('e'), ('f');
+
+SELECT i from tbl_list_0;
+SELECT i from tbl_list_1;
+SELECT i from tbl_list_2;
+
+/* Must fail. No default partition */
+INSERT INTO tbl_list values ('q');
+
+DROP TABLE tbl_list;
+
+/* Range.
+ * Automatic generation of RANGE partition bounds is not implemented yet.
+ * This command will only create one partition
+ */
+CREATE TABLE tbl_range (i timestamptz) PARTITION BY RANGE (i)
+ CONFIGURATION IMMEDIATE USING INTERVAL '1 month 2 days' FROM ('2020-01-01') TO ('2020-12-31');
+
+\d+ tbl_range
\ No newline at end of file
--
2.17.1
On Mon, Jul 06, 2020 at 01:45:52PM +0300, Anastasia Lubennikova wrote:
The previous discussion of automatic partition creation [1] has addressed
static and dynamic creation of partitions and ended up with several syntax
proposals.
...
where partition_auto_create_clause is
CONFIGURATION [IMMEDIATE| DEFERRED] USING partition_bound_spec
- IMMEDIATE| DEFERRED is optional, DEFERRED is not implemented yet
I wonder, is it worth placing a stub for dynamic partitioning, or we can
rather add these keywords later.
I understand by "deferred" you mean that the partition isn't created at the
time CREATE TABLE is run but rather deferred until needed by INSERT.
For deferred, range partitioned tables, I think maybe what you'd want to
specify (and store) is the INTERVAL. If the table is partitioned by day, then
we'd date_trunc('day', time) and dynamically create that day. But if it was
partitioned by month, we'd create the month. I think you'd want to have an
ALTER command for that (we would use that to change tables between
daily/monthly based on their current size). That should also support setting
the MODULUS of a HASH partitioned table, to allow changing the size of its
partitions (currently, the user would have to more or less recreate the table
and move all its data into different partitions, but that's not ideal).
I don't know if it's important for anyone, but it would be interesting to think
about supporting sub-partitioning: partitions which are themselvese partitioned.
Like something => something_YYYY => something_YYYY_MM => something_YYYY_MM_DD.
You'd need to specify how to partition each layer of the heirarchy. In the
most general case, it could be different partition strategy.
If you have a callback function for partition renaming, I think you'd want to
pass it not just the current name of the partition, but also the "VALUES" used
in partition creation. Like (2020-04-05)TO(2020-05-06). Maybe instead, we'd
allow setting a "format" to use to construct the partition name. Like
"child.foo_bar_%Y_%m_%d". Ideally, the formats would be fixed-length
(zero-padded, etc), so failures with length can happen at "parse" time of the
statement and not at "run" time of the creation. You'd still have to handle
the case that the name already exists but isn't a partition (or is a partition
by doesn't handle the incoming tuple for some reason).
Also, maybe your "configuration" syntax would allow specifying other values.
Maybe including a retention period (as an INTERVAL for RANGE tables). That's
useful if you had a command to PRUNE the oldest partitions, like ALTER..PRUNE.
--
Justin
On Mon, Jul 6, 2020 at 6:46 AM Anastasia Lubennikova
<a.lubennikova@postgrespro.ru> wrote:
CREATE TABLE ... PARTITION BY partition_method (list_of_columns)
partition_auto_create_clausewhere partition_auto_create_clause is
CONFIGURATION [IMMEDIATE| DEFERRED] USING partition_bound_spec
and partition_bound_spec is:
MODULUS integer | VALUES IN (expr [,...]) [, ....] | INTERVAL
range_step FROM range_start TO range_end
Might be good to compare this to what other databases support.
- IMMEDIATE| DEFERRED is optional, DEFERRED is not implemented yet
I wonder, is it worth placing a stub for dynamic partitioning, or we can
rather add these keywords later.
I think we should not add any keywords we don't need immediately - and
should seek to minimize the number of new keywords that we need to
add, though compatibility with other implementations might be a good
reason for accepting some new ones.
- HASH and LIST static partitioning works as expected.
Testing and feedback are welcome.- RANGE partitioning is not really implemented in this patch.
Now it only accepts interval data type as 'interval' and respectively
date types as range_start and range_end expressions.
Only one partition is created. I found it difficult to implement the
generation of bounds using internal functions and data types.
Both existing solutions (pg_pathman and pg_partman) rely on SQL level
routines [2].
I am going to implement this via SPI, which allow to simplify checks and
calculations. Do you see any pitfalls in this approach?
I don't really see why we need SPI here. Why can't we just try to
evaluate the impression and see if we get a constant of the right
type, then use that?
I think the big problem here is identifying the operator to use. We
have no way of identifying the "plus" or "minus" operator associated
with a datatype; indeed, that constant doesn't exist. So either we (a)
limit this to a short list of data types and hard-code the operators
to be used (which is kind of sad given how extensible our type system
is) or we (b) invent some new mechanism for identifying the +/-
operators that should be used for a datatype, which was also proposed
in the context of some previous discussion of window framing options,
but which I don't think ever went anywhere (which is a lot of work) or
we (c) just look for operators called '+' and/or '-' by operator name
(which will probably make Tom throw up in his mouth a little).
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Robert Haas <robertmhaas@gmail.com> writes:
On Mon, Jul 6, 2020 at 6:46 AM Anastasia Lubennikova
<a.lubennikova@postgrespro.ru> wrote:I am going to implement this via SPI, which allow to simplify checks and
calculations. Do you see any pitfalls in this approach?
I don't really see why we need SPI here.
I would vote against any core facility that is implemented via SPI
queries. It is just too darn hard to control the semantics completely in
the face of fun stuff like varying search_path. Look at what a mess the
queries generated by the RI triggers are --- and they only have a very
small set of behaviors to worry about. I'm still only about 95% confident
they don't have security issues, too.
If you're using SPI to try to look up appropriate operators, I think
the chances of being vulnerable to security problems are 100%.
I think the big problem here is identifying the operator to use. We
have no way of identifying the "plus" or "minus" operator associated
with a datatype; indeed, that constant doesn't exist.
We did indeed solve this in connection with window functions, cf
0a459cec9. I may be misunderstanding what the problem is here,
but I think trying to reuse that infrastructure might help.
regards, tom lane
On Mon, Jul 6, 2020 at 12:10 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
We did indeed solve this in connection with window functions, cf
0a459cec9. I may be misunderstanding what the problem is here,
but I think trying to reuse that infrastructure might help.
Ah, nice. I didn't realize that we'd added that. But I'm not sure that
it helps here, because I think we need to compute the end of the
range, not just test whether something is in a range. Like, if someone
wants monthly range partitions starting on 2020-01-01, we need to be
able to figure out that the subsequent months start on 2020-02-01,
2020-03-01, 2020-04-01, etc. Is there a way to use in_range to achieve
that?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Robert Haas <robertmhaas@gmail.com> writes:
On Mon, Jul 6, 2020 at 12:10 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
We did indeed solve this in connection with window functions, cf
0a459cec9. I may be misunderstanding what the problem is here,
but I think trying to reuse that infrastructure might help.
Ah, nice. I didn't realize that we'd added that. But I'm not sure that
it helps here, because I think we need to compute the end of the
range, not just test whether something is in a range.
Yeah, I was thinking about that later, and I agree that the in_range
support function doesn't quite do the job. But we could expand on the
principle, and register addition (and subtraction?) functions as btree
support functions under the same rules as for in_range functions.
The reason in_range isn't just addition is that we wanted it to be able
to give correct answers even in cases where addition would overflow.
That's still valid for that use-case, but it doesn't apply here.
So it'd be something like "btree support function 4, registered under
amproclefttype x and amprocrighttype y, must have the signature
plus(x, y) returns x
and it gives results compatible with the opfamily's ordering of type x".
Similarly for subtraction if we think we need that.
I'm not sure if we need a formal notion of what "compatible results"
means, but it probably would be something like "if x < z according to the
opfamily sort ordering, then plus(x, y) < plus(z, y) for any given y".
Now this falls to the ground when y is a weird value like Inf or NaN,
but we'd want to exclude those as partitioning values anyway. Do we
also need some datatype-independent way of identifying such "weird
values"?
regards, tom lane
Hello Anastasia,
My 0.02 €:
The patch implements following syntax:
CREATE TABLE ... PARTITION BY partition_method (list_of_columns)
partition_auto_create_clausewhere partition_auto_create_clause is
CONFIGURATION [IMMEDIATE| DEFERRED] USING partition_bound_spec
and partition_bound_spec is:
MODULUS integer | VALUES IN (expr [,...]) [, ....] | INTERVAL range_step
FROM range_start TO range_end
ISTM That we should avoid new specific syntaxes when possible, and prefer
free keyword option style, like it is being discussed for some other
commands, because it reduces the impact on the parser.
That would suggest a more versatile partition_bound_spec which could look
like (<keyword> <constant-or-maybe-even-expr>[, …]):
For modulus, looks easy:
(MODULUS 8)
For interval, maybe something like:
(STEP ..., FROM/START ..., TO/END ...)
The key point is that for dynamic partitioning there would be no need for
boundaries, so that it could just set a point and an interval
(START/INIT/FROM??? ..., STEP ...)
For lists of values, probably it would make little sense to have dynamic
partitioning? Or maybe yes, if we could partition on a column
value/expression?! eg "MOD(id, 8)"??
What about pg_dump? Should it be able to regenerate the initial create?
[4] https://wiki.postgresql.org/wiki/Declarative_partitioning_improvements
Good point, a wiki is better than a thread for that type of things. I'll
look at this page.
--
Fabien.
On Wed, Jul 8, 2020 at 10:24 AM Fabien COELHO <coelho@cri.ensmp.fr> wrote:
Hello Anastasia,
My 0.02 €:
The patch implements following syntax:
CREATE TABLE ... PARTITION BY partition_method (list_of_columns)
partition_auto_create_clausewhere partition_auto_create_clause is
CONFIGURATION [IMMEDIATE| DEFERRED] USING partition_bound_spec
and partition_bound_spec is:
MODULUS integer | VALUES IN (expr [,...]) [, ....] | INTERVAL range_step
FROM range_start TO range_endISTM That we should avoid new specific syntaxes when possible, and prefer
free keyword option style, like it is being discussed for some other
commands, because it reduces the impact on the parser.That would suggest a more versatile partition_bound_spec which could look
like (<keyword> <constant-or-maybe-even-expr>[, …]):For modulus, looks easy:
(MODULUS 8)
For interval, maybe something like:
(STEP ..., FROM/START ..., TO/END ...)
The key point is that for dynamic partitioning there would be no need for
boundaries, so that it could just set a point and an interval(START/INIT/FROM??? ..., STEP ...)
For lists of values, probably it would make little sense to have dynamic
partitioning? Or maybe yes, if we could partition on a column
value/expression?! eg "MOD(id, 8)"??What about pg_dump? Should it be able to regenerate the initial create?
I don't think this is needed for the proposed "Automatic partitioning (static)"
which generates a bunch of CREATE TABLE statements, IIUC. Might be needed later
for "Automatic partitioning (dynamic)" where dynamic specifications need to be
stored.
[4] https://wiki.postgresql.org/wiki/Declarative_partitioning_improvements
Good point, a wiki is better than a thread for that type of things. I'll
look at this page.
+1
Regards,
Amul
On 06.07.2020 19:10, Tom Lane wrote:
Robert Haas <robertmhaas@gmail.com> writes:
On Mon, Jul 6, 2020 at 6:46 AM Anastasia Lubennikova
<a.lubennikova@postgrespro.ru> wrote:I am going to implement this via SPI, which allow to simplify checks and
calculations. Do you see any pitfalls in this approach?I don't really see why we need SPI here.
I would vote against any core facility that is implemented via SPI
queries. It is just too darn hard to control the semantics completely in
the face of fun stuff like varying search_path. Look at what a mess the
queries generated by the RI triggers are --- and they only have a very
small set of behaviors to worry about. I'm still only about 95% confident
they don't have security issues, too.If you're using SPI to try to look up appropriate operators, I think
the chances of being vulnerable to security problems are 100%.
Good to know, thank you for that. I had doubts about the internal usage
of SPI,
but didn't know what exactly can go wrong.
I think the big problem here is identifying the operator to use. We
have no way of identifying the "plus" or "minus" operator associated
with a datatype; indeed, that constant doesn't exist.We did indeed solve this in connection with window functions, cf
0a459cec9. I may be misunderstanding what the problem is here,
but I think trying to reuse that infrastructure might help.
Do we need to introduce a new support function? Is there a reason why we
can
not rely on '+' operator? I understand that the addition operator may
lack or
be overloaded for some complex datatypes, but I haven't found any
examples that
are useful for range partitioning. Both pg_pathman and pg_partman also
use '+'
to generate bounds.
I explored the code a bit more and came up with this function, which is
very
similar to generate_series_* functions, but it doesn't use SPI and looks
for
the function that implements the '+' operator, instead of direct call:
// almost pseudocode
static Const *
generate_next_bound(Const *start, Const *interval)
{
ObjectWithArgs *sum_oper_object = makeNode(ObjectWithArgs);
sum_oper_object->type = OBJECT_OPERATOR;
/* hardcode '+' operator for addition */
sum_oper_object->objname = list_make1(makeString("+"));
ltype = makeTypeNameFromOid(start->consttype, start->consttypmod);
rtype = makeTypeNameFromOid(interval->consttype,
interval->consttypmod);
sum_oper_object->objargs = list_make2(ltype, rtype);
sum_oper_oid = LookupOperWithArgs(sum_oper_object, false);
oprcode = get_opcode(sum_oper_oid);
fmgr_info(oprcode, &opproc);
next_bound->constvalue = FunctionCall2(&opproc,
start->constvalue,
interval->constvalue);
}
Thoughts?
--
Anastasia Lubennikova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Anastasia Lubennikova <a.lubennikova@postgrespro.ru> writes:
On 06.07.2020 19:10, Tom Lane wrote:
Robert Haas <robertmhaas@gmail.com> writes:
I think the big problem here is identifying the operator to use. We
have no way of identifying the "plus" or "minus" operator associated
with a datatype; indeed, that constant doesn't exist.
We did indeed solve this in connection with window functions, cf
0a459cec9. I may be misunderstanding what the problem is here,
but I think trying to reuse that infrastructure might help.
Do we need to introduce a new support function? Is there a reason why we
can not rely on '+' operator?
(1) the appropriate operator might not be named '+'
(2) even if it is, it might not be in your search_path
(3) you're vulnerable to security problems from someone capturing the
'+' operator with a better match; since you aren't writing the
operator explicitly, you can't fix that by qualifying it
(4) if the interval constant is written as an undecorated string
literal, the parser may have trouble resolving a match at all
I understand that the addition operator may lack or be overloaded for
some complex datatypes, but I haven't found any examples that are useful
for range partitioning.
"It works for all the built-in data types" isn't really a satisfactory
answer. But even just in the built-in types, consider "date":
# select oid::regoperator from pg_operator where oprname ='+' and oprleft = 'date'::regtype;
oid
--------------------------------
+(date,interval)
+(date,integer)
+(date,time without time zone)
+(date,time with time zone)
(4 rows)
It's not that immediately obvious which of these would make sense to use.
But the short answer here is that we did not accept relying on '+' being
the right thing for window function ranges, and I don't see why it is more
acceptable for partitioning ranges. The existing places where our parser
relies on implicit operator names are, without exception, problematic [1]/messages/by-id/ffefc172-a487-aa87-a0e7-472bf29735c8@gmail.com.
regards, tom lane
[1]: /messages/by-id/ffefc172-a487-aa87-a0e7-472bf29735c8@gmail.com
On 06.07.2020 13:45, Anastasia Lubennikova wrote:
The previous discussion of automatic partition creation [1] has
addressed static and dynamic creation of partitions and ended up with
several syntax proposals.
In this thread, I want to continue this work....
[1]
/messages/by-id/alpine.DEB.2.21.1907150711080.22273@lancre
Syntax proposal v2, that takes into account received feedback.
I compared the syntax of other databases. You can find an overview here
[1]: https://wiki.postgresql.org/wiki/Declarative_partitioning_improvements#Other_DBMS
seems that there is no industry standard, so every DBMS has its own
implementation. I decided to rely on a Greenplum syntax, as the most
similar to
the original PostgreSQL syntax.
New proposal is:
CREATE TABLE numbers(int number)
PARTITION BY partition_method (list_of_columns)
USING (partition_desc)
where partition_desc is:
MODULUS n
| VALUES IN (value_list), [DEFAULT PARTITION part_name]
| START ([datatype] 'start_value')
END ([datatype] 'end_value')
EVERY (partition_step), [DEFAULT PARTITION part_name]
where partition_step is:
[datatype] [number | INTERVAL] 'interval_value'
example:
CREATE TABLE years(int year)
PARTITION BY RANGE (year)
USING
(START (2006) END (2016) EVERY (1),
DEFAULT PARTITION other_years);
It is less wordy than the previous version. It uses a free keyword option
style. It covers static partitioning for all methods, default partition for
list and range methods, and can be extended to implement dynamic
partitioning
for range partitions.
[1]: https://wiki.postgresql.org/wiki/Declarative_partitioning_improvements#Other_DBMS
https://wiki.postgresql.org/wiki/Declarative_partitioning_improvements#Other_DBMS
[2]: https://wiki.postgresql.org/wiki/Declarative_partitioning_improvements#Proposal_.28is_subject_to_change.29
https://wiki.postgresql.org/wiki/Declarative_partitioning_improvements#Proposal_.28is_subject_to_change.29
--
Anastasia Lubennikova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
On 06.07.2020 17:59, Justin Pryzby wrote:
I think you'd want to have an
ALTER command for that (we would use that to change tables between
daily/monthly based on their current size). That should also support setting
the MODULUS of a HASH partitioned table, to allow changing the size of its
partitions (currently, the user would have to more or less recreate the table
and move all its data into different partitions, but that's not ideal).
New syntax fits to the ALTER command as well.
ALTER TABLE tbl
PARTITION BY HASH (number)
USING (partition_desc)
In simple cases (i.e. range partitioning granularity), it will simply
update
the rule of bound generation, saved in the catalog. More complex hash
partitions will require some rebalancing. Though, the syntax is pretty
straightforward for all cases. In the next versions, we can also add a
CONCURRENTLY keyword to cover partitioning of an existing
non-partitioned table
with data.
I don't know if it's important for anyone, but it would be interesting to think
about supporting sub-partitioning: partitions which are themselvese partitioned.
Like something => something_YYYY => something_YYYY_MM => something_YYYY_MM_DD.
You'd need to specify how to partition each layer of the heirarchy. In the
most general case, it could be different partition strategy.
I suppose it will be a natural extension of this work. Now we need to
ensure
that the proposed syntax is extensible. Greenplum syntax, which I choose
as an
example, provides subpartition syntax as well.
If you have a callback function for partition renaming, I think you'd want to
pass it not just the current name of the partition, but also the "VALUES" used
in partition creation. Like (2020-04-05)TO(2020-05-06). Maybe instead, we'd
allow setting a "format" to use to construct the partition name. Like
"child.foo_bar_%Y_%m_%d". Ideally, the formats would be fixed-length
(zero-padded, etc), so failures with length can happen at "parse" time of the
statement and not at "run" time of the creation. You'd still have to handle
the case that the name already exists but isn't a partition (or is a partition
by doesn't handle the incoming tuple for some reason).
In callback design, I want to use the best from pg_pathman's
set_init_callback().
The function accepts jsonb argument, which contains all the data about the
parent table, bounds, and so on. This information can be used to
construct name
for the partition and generate RENAME statement.
Also, maybe your "configuration" syntax would allow specifying other values.
Maybe including a retention period (as an INTERVAL for RANGE tables). That's
useful if you had a command to PRUNE the oldest partitions, like ALTER..PRUNE.
In this version, I got rid of the 'configuration' keyword. Speaking of
retention, I think that it would be hard to cover all use-cases with a
declarative syntax. While it is relatively easy to implement deletion
within a
callback function. See rotation_callback example in pg_pathman [1]https://github.com/postgrespro/pg_pathman/blob/79e11d94a147095f6e131e980033018c449f8e2e/sql/pathman_callbacks.sql#L107.
[1]: https://github.com/postgrespro/pg_pathman/blob/79e11d94a147095f6e131e980033018c449f8e2e/sql/pathman_callbacks.sql#L107
https://github.com/postgrespro/pg_pathman/blob/79e11d94a147095f6e131e980033018c449f8e2e/sql/pathman_callbacks.sql#L107
--
Anastasia Lubennikova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
On 14.07.2020 00:11, Anastasia Lubennikova wrote:
On 06.07.2020 13:45, Anastasia Lubennikova wrote:
The previous discussion of automatic partition creation [1] has
addressed static and dynamic creation of partitions and ended up with
several syntax proposals.
In this thread, I want to continue this work....
[1]
/messages/by-id/alpine.DEB.2.21.1907150711080.22273@lancreSyntax proposal v2, that takes into account received feedback.
CREATE TABLE numbers(int number)
PARTITION BY partition_method (list_of_columns)
USING (partition_desc)where partition_desc is:
MODULUS n
| VALUES IN (value_list), [DEFAULT PARTITION part_name]
| START ([datatype] 'start_value')
END ([datatype] 'end_value')
EVERY (partition_step), [DEFAULT PARTITION part_name]where partition_step is:
[datatype] [number | INTERVAL] 'interval_value'It is less wordy than the previous version. It uses a free keyword option
style. It covers static partitioning for all methods, default
partition for
list and range methods, and can be extended to implement dynamic
partitioning
for range partitions.[1]
https://wiki.postgresql.org/wiki/Declarative_partitioning_improvements#Other_DBMS
[2]
https://wiki.postgresql.org/wiki/Declarative_partitioning_improvements#Proposal_.28is_subject_to_change.29
Here is the patch for automated HASH and LIST partitioning, that
implements proposed syntax.
Range partitioning is more complicated. It will require new support
function to calculate bounds, new catalog attribute to store them and so
on. So I want to start small and implement automated range partitioning
in a separate patch later.
1) Syntax
New syntax is heavily based on Greenplum syntax for automated
partitioning with one change. Keyword "USING", that was suggested above,
causes shift/reduce conflict with "USING method" syntax of a table
access method. It seems that Greenplum folks will face this problem later.
I stick to CONFIGURATION as an existing keyword that makes sense in this
context.
Any better ideas are welcome.
Thus, current version is:
CREATE TABLE table_name (attrs)
PARTITION BY partition_method (list_of_columns)
CONFIGURATION (partition_desc)
where partition_desc is:
MODULUS n
| VALUES IN (value_list) [DEFAULT PARTITION part_name]
This syntax can be easily extended for range partitioning as well.
2) Implementation
PartitionBoundAutoSpec is a new part of PartitionSpec, that contains
information needed to generate partition bounds.
For HASH and LIST automatic partition creation, transformation happens
during parse analysis of CREATE TABLE statement.
transformPartitionAutoCreate() calculates bounds and generates
statements to create partition tables.
Partitions are named in a format: $tablename_$partnum. One can use post
create hook to rename relations.
For LIST partition one can also define a default partition.
3) TODO
The patch lacks documentation, because I expect some details may change
during discussion. Other than that, the feature is ready for review.
Regards
--
Anastasia Lubennikova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
auto_part_hash_list_v0.patchtext/x-patch; charset=UTF-8; name=auto_part_hash_list_v0.patchDownload
commit ede0c9d8f13509915ee1db724f7bcabc0365ecd5
Author: anastasia <a.lubennikova@postgrespro.ru>
Date: Tue Aug 25 03:34:15 2020 +0300
Auto generated HASH and LIST partitions.
New syntax:
CREATE TABLE tbl_hash (i int) PARTITION BY HASH (i)
CONFIGURATION (modulus 3);
CREATE TABLE tbl_list (i int) PARTITION BY LIST (i)
CONFIGURATION (values in (1, 2), (3, 4) DEFAULT PARTITION tbl_default);
---
src/backend/nodes/copyfuncs.c | 17 ++++
src/backend/nodes/equalfuncs.c | 17 ++++
src/backend/nodes/outfuncs.c | 16 ++++
src/backend/nodes/readfuncs.c | 15 ++++
src/backend/parser/gram.y | 82 ++++++++++++++++-
src/backend/parser/parse_utilcmd.c | 138 +++++++++++++++++++++++++++++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 23 +++++
src/include/partitioning/partdefs.h | 2 +
src/test/regress/expected/create_table.out | 37 ++++++++
src/test/regress/sql/create_table.sql | 23 +++++
11 files changed, 369 insertions(+), 2 deletions(-)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..cb537bce3a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4629,6 +4629,7 @@ _copyPartitionSpec(const PartitionSpec *from)
COPY_STRING_FIELD(strategy);
COPY_NODE_FIELD(partParams);
+ COPY_NODE_FIELD(autopart);
COPY_LOCATION_FIELD(location);
return newnode;
@@ -4651,6 +4652,19 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
return newnode;
}
+static PartitionBoundAutoSpec *
+_copyPartitionBoundAutoSpec(const PartitionBoundAutoSpec *from)
+{
+ PartitionBoundAutoSpec *newnode = makeNode(PartitionBoundAutoSpec);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_SCALAR_FIELD(modulus);
+ COPY_NODE_FIELD(listdatumsList);
+ COPY_NODE_FIELD(default_partition_rv);
+
+ return newnode;
+}
+
static PartitionRangeDatum *
_copyPartitionRangeDatum(const PartitionRangeDatum *from)
{
@@ -5700,6 +5714,9 @@ copyObjectImpl(const void *from)
case T_PartitionBoundSpec:
retval = _copyPartitionBoundSpec(from);
break;
+ case T_PartitionBoundAutoSpec:
+ retval = _copyPartitionBoundAutoSpec(from);
+ break;
case T_PartitionRangeDatum:
retval = _copyPartitionRangeDatum(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e3f33c40be..4e9d388534 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2898,6 +2898,7 @@ _equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
COMPARE_STRING_FIELD(strategy);
COMPARE_NODE_FIELD(partParams);
COMPARE_LOCATION_FIELD(location);
+ COMPARE_NODE_FIELD(autopart);
return true;
}
@@ -2917,6 +2918,19 @@ _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *
return true;
}
+static bool
+_equalPartitionBoundAutoSpec(const PartitionBoundAutoSpec *a,
+ const PartitionBoundAutoSpec *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_SCALAR_FIELD(modulus);
+ COMPARE_NODE_FIELD(listdatumsList);
+ COMPARE_NODE_FIELD(default_partition_rv);
+
+ return true;
+}
+
+
static bool
_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
{
@@ -3752,6 +3766,9 @@ equal(const void *a, const void *b)
case T_PartitionBoundSpec:
retval = _equalPartitionBoundSpec(a, b);
break;
+ case T_PartitionBoundAutoSpec:
+ retval = _equalPartitionBoundAutoSpec(a, b);
+ break;
case T_PartitionRangeDatum:
retval = _equalPartitionRangeDatum(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..4fd12523d8 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3643,6 +3643,7 @@ _outPartitionSpec(StringInfo str, const PartitionSpec *node)
WRITE_STRING_FIELD(strategy);
WRITE_NODE_FIELD(partParams);
+ WRITE_NODE_FIELD(autopart);
WRITE_LOCATION_FIELD(location);
}
@@ -3661,6 +3662,18 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionBoundAutoSpec(StringInfo str, const PartitionBoundAutoSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBOUNDAUTOSPEC");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_INT_FIELD(modulus);
+ WRITE_NODE_FIELD(listdatumsList);
+ WRITE_NODE_FIELD(default_partition_rv);
+
+}
+
static void
_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
{
@@ -4334,6 +4347,9 @@ outNode(StringInfo str, const void *obj)
case T_PartitionBoundSpec:
_outPartitionBoundSpec(str, obj);
break;
+ case T_PartitionBoundAutoSpec:
+ _outPartitionBoundAutoSpec(str, obj);
+ break;
case T_PartitionRangeDatum:
_outPartitionRangeDatum(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..703b413f93 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2602,6 +2602,19 @@ _readPartitionBoundSpec(void)
READ_DONE();
}
+static PartitionBoundAutoSpec *
+_readPartitionBoundAutoSpec(void)
+{
+ READ_LOCALS(PartitionBoundAutoSpec);
+
+ READ_CHAR_FIELD(strategy);
+ READ_INT_FIELD(modulus);
+ READ_NODE_FIELD(listdatumsList);
+ READ_NODE_FIELD(default_partition_rv);
+
+ READ_DONE();
+}
+
/*
* _readPartitionRangeDatum
*/
@@ -2880,6 +2893,8 @@ parseNodeString(void)
return_value = _readPartitionBoundSpec();
else if (MATCH("PARTITIONRANGEDATUM", 19))
return_value = _readPartitionRangeDatum();
+ else if (MATCH("PARTITIONBOUNDAUTOSPEC", 22))
+ return_value = _readPartitionBoundAutoSpec();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dbb47d4982..ba96fafa94 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -249,6 +249,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionElem *partelem;
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
+ PartitionBoundAutoSpec *partboundautospec;
RoleSpec *rolespec;
struct SelectLimit *selectlimit;
}
@@ -599,6 +600,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
+%type <partboundautospec> OptPartitionBoundAutoSpec values_in_clause p_desc
+%type <range> opt_default_partition_clause
+
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
* They must be listed first so that their numeric codes do not depend on
@@ -3907,14 +3911,14 @@ OptPartitionSpec: PartitionSpec { $$ = $1; }
| /*EMPTY*/ { $$ = NULL; }
;
-PartitionSpec: PARTITION BY ColId '(' part_params ')'
+PartitionSpec: PARTITION BY ColId '(' part_params ')' OptPartitionBoundAutoSpec
{
PartitionSpec *n = makeNode(PartitionSpec);
n->strategy = $3;
n->partParams = $5;
n->location = @1;
-
+ n->autopart = (Node *) $7;
$$ = n;
}
;
@@ -3958,6 +3962,80 @@ part_elem: ColId opt_collate opt_class
}
;
+OptPartitionBoundAutoSpec:
+ CONFIGURATION '(' p_desc ')'
+ {
+ $$ = $3;
+ }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+p_desc:
+ hash_partbound
+ {
+ ListCell *lc;
+ PartitionBoundAutoSpec *n = makeNode(PartitionBoundAutoSpec);
+
+ n->modulus = -1;
+
+ foreach (lc, $1)
+ {
+ DefElem *opt = lfirst_node(DefElem, lc);
+
+ if (strcmp(opt->defname, "modulus") == 0)
+ {
+ n->strategy = PARTITION_STRATEGY_HASH;
+ if (n->modulus != -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("modulus for hash partition provided more than once"),
+ parser_errposition(opt->location)));
+ n->modulus = defGetInt32(opt);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized auto partition bound specification \"%s\"",
+ opt->defname),
+ parser_errposition(opt->location)));
+ }
+
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ | values_in_clause opt_default_partition_clause
+ {
+ PartitionBoundAutoSpec *n = $1;
+ n->default_partition_rv = $2;
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ ;
+
+values_in_clause:
+ VALUES IN_P '(' expr_list ')'
+ {
+ PartitionBoundAutoSpec *n = makeNode(PartitionBoundAutoSpec);
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatumsList = list_make1($4);
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ | values_in_clause ',' '(' expr_list ')'
+ {
+ PartitionBoundAutoSpec *n = (PartitionBoundAutoSpec *) $1;
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatumsList = lappend(n->listdatumsList, $4);
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ ;
+
+opt_default_partition_clause:
+ DEFAULT PARTITION qualified_name
+ {
+ $$ = $3;
+ }
+ | /* EMPTY */
+ { $$ = NULL; }
+ ;
+
table_access_method_clause:
USING name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 6c49554def..686aabf630 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -75,6 +75,7 @@
/* State shared by transformCreateStmt and its subroutines */
typedef struct
{
+ CreateStmt *stmt; /* initial statement */
ParseState *pstate; /* overall parser state */
const char *stmtType; /* "CREATE [FOREIGN] TABLE" or "ALTER TABLE" */
RangeVar *relation; /* relation to create */
@@ -145,6 +146,7 @@ static Const *transformPartitionBoundValue(ParseState *pstate, Node *con,
const char *colName, Oid colType, int32 colTypmod,
Oid partCollation);
+static void transformPartitionAutoCreate(CreateStmtContext *cxt, PartitionSpec* partspec);
/*
* transformCreateStmt -
@@ -235,6 +237,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.stmtType = "CREATE TABLE";
cxt.isforeign = false;
}
+ cxt.stmt = stmt;
cxt.relation = stmt->relation;
cxt.rel = NULL;
cxt.inhRelations = stmt->inhRelations;
@@ -324,6 +327,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
transformExtendedStatistics(&cxt);
+ /* Process partition definitions */
+ if (stmt->partspec && stmt->partspec->autopart)
+ transformPartitionAutoCreate(&cxt, stmt->partspec);
+
/*
* Output results.
*/
@@ -4259,3 +4266,134 @@ transformPartitionBoundValue(ParseState *pstate, Node *val,
return (Const *) value;
}
+
+
+/*
+ * Transform configuration into a set of partition bounds.
+ * Generate extra statements to create partition tables.
+ */
+static void
+transformPartitionAutoCreate(CreateStmtContext *cxt, PartitionSpec* partspec)
+{
+ CreateStmt *part;
+ List *partlist = NIL;
+ ListCell *lc;
+ int i = 0;
+ PartitionBoundAutoSpec *bound = (PartitionBoundAutoSpec *) partspec->autopart;
+
+ elog(DEBUG1, "transformPartitionAutoCreate \n %s \n ", nodeToString(bound));
+
+ /*
+ * Generate regular partbounds based on autopart rule.
+ * and form create table statements from these partbounds
+ */
+ if (pg_strcasecmp(partspec->strategy, "hash") == 0)
+ {
+ if (bound->strategy != PARTITION_STRATEGY_HASH)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a hash partition"),
+ parser_errposition(cxt->pstate, exprLocation((Node *) partspec))));
+
+ for (i = 0; i < bound->modulus; i++)
+ {
+ char *part_relname;
+
+ /*
+ * Generate partition name in the format:
+ * $relname_$partnum
+ *
+ * TODO: Add checks on relname length.
+ */
+ part_relname = psprintf("%s_%d", cxt->relation->relname, i);
+
+ part = copyObject(cxt->stmt);
+
+ part->relation = makeRangeVar(cxt->relation->schemaname,
+ part_relname, cxt->relation->location);
+ /* set table as a parent */
+ part->inhRelations = lappend(part->inhRelations, cxt->relation);
+
+ /* child table is not partitioned */
+ part->partspec = NULL;
+
+ /* Actual partbound generation is here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_HASH;
+ part->partbound->modulus = bound->modulus;
+ part->partbound->remainder = i;
+ part->partbound->is_default = false;
+
+ elog(DEBUG1,"stransformPartitionAutoCreate HASH i %d MODULUS %d \n %s\n",
+ i, bound->modulus, nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+ }
+ else if (pg_strcasecmp(partspec->strategy, "list") == 0)
+ {
+
+ int n_list_parts = list_length(bound->listdatumsList);
+
+ if (bound->strategy != PARTITION_STRATEGY_LIST)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation((Node *) partspec))));
+
+ for (i = 0; i < n_list_parts; i++)
+ {
+ char *part_relname;
+ List *listdatums = (List *)
+ list_nth(bound->listdatumsList, i);
+
+ part_relname = psprintf("%s_%d", cxt->relation->relname, i);
+
+ part = copyObject(cxt->stmt);
+
+ part->relation = makeRangeVar(cxt->relation->schemaname,
+ part_relname, cxt->relation->location);
+ /* set table as a parent */
+ part->inhRelations = lappend(part->inhRelations, cxt->relation);
+
+ /* child table is not partitioned */
+ part->partspec = NULL;
+
+ /* Actual partbound generation is here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_LIST;
+ part->partbound->listdatums = list_copy(listdatums);
+ part->partbound->is_default = false;
+
+ elog(DEBUG1,"Debug transformPartitionAutoCreate LIST i %d \n %s\n",
+ i, nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+
+ if (bound->default_partition_rv)
+ {
+ part = copyObject(cxt->stmt);
+
+ part->relation = bound->default_partition_rv;
+ /* set table as a parent */
+ part->inhRelations = lappend(part->inhRelations, cxt->relation);
+
+ /* child table is not partitioned */
+ part->partspec = NULL;
+
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_LIST;
+ part->partbound->listdatums = NULL;
+ part->partbound->is_default = true;
+
+ elog(DEBUG1,"Debug transformPartitionAutoCreate LIST default partition \n %s\n",
+ nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+ }
+
+ /* Add statemets to create each partition after we create parent table */
+ cxt->alist = list_concat(cxt->alist, partlist);
+}
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..ac6fcc029e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -478,6 +478,7 @@ typedef enum NodeTag
T_PartitionElem,
T_PartitionSpec,
T_PartitionBoundSpec,
+ T_PartitionBoundAutoSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 47d4c07306..d44dc9983a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -794,6 +794,9 @@ typedef struct PartitionSpec
* 'range') */
List *partParams; /* List of PartitionElems */
int location; /* token location, or -1 if unknown */
+
+ Node *autopart; /* PartitionBoundAutoSpec -
+ * spec to generate bounds automatically */
} PartitionSpec;
/* Internal codes for partitioning strategies */
@@ -828,6 +831,26 @@ struct PartitionBoundSpec
int location; /* token location, or -1 if unknown */
};
+/*
+ * PartitionBoundAutoSpec - a partition bound specification
+ * for auto generated partitions.
+ *
+ * This represents the rule of generating partition bounds
+ */
+struct PartitionBoundAutoSpec
+{
+ NodeTag type;
+
+ char strategy; /* see PARTITION_STRATEGY codes above */
+
+ /* Partitioning info for HASH strategy: */
+ int modulus;
+
+ /* Partitioning info for LIST strategy: */
+ List *listdatumsList; /* List of lists of Consts (or A_Consts in raw tree) */
+ RangeVar *default_partition_rv; /* Name of default list partition */
+};
+
/*
* PartitionRangeDatum - one of the values in a range partition bound
*
diff --git a/src/include/partitioning/partdefs.h b/src/include/partitioning/partdefs.h
index 6414e2c116..25ecfbd1de 100644
--- a/src/include/partitioning/partdefs.h
+++ b/src/include/partitioning/partdefs.h
@@ -19,6 +19,8 @@ typedef struct PartitionKeyData *PartitionKey;
typedef struct PartitionBoundSpec PartitionBoundSpec;
+typedef struct PartitionBoundAutoSpec PartitionBoundAutoSpec;
+
typedef struct PartitionDescData *PartitionDesc;
typedef struct PartitionDirectoryData *PartitionDirectory;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1c72f23bc9..3047ed9007 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -1283,3 +1283,40 @@ Indexes:
"part_column_drop_1_10_expr_idx1" btree ((d = 2))
drop table part_column_drop;
+-- Auto generated partitions
+-- must fail because of wrong configuration
+CREATE TABLE tbl_hash_fail (i int) PARTITION BY HASH (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+ERROR: invalid bound specification for a hash partition
+LINE 1: CREATE TABLE tbl_hash_fail (i int) PARTITION BY HASH (i)
+ ^
+-- must fail because of wrong configuration
+CREATE TABLE tbl_list_fail (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (1, 3));
+ERROR: partition "tbl_list_fail_1" would overlap partition "tbl_list_fail_0"
+CREATE TABLE tbl_list (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+\d+ tbl_list
+ Partitioned table "public.tbl_list"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ i | integer | | | | plain | |
+Partition key: LIST (i)
+Partitions: tbl_list_0 FOR VALUES IN (1, 2),
+ tbl_list_1 FOR VALUES IN (3, 4),
+ tbl_default DEFAULT
+
+CREATE TABLE tbl_hash (i int) PARTITION BY HASH (i)
+CONFIGURATION (modulus 3);
+\d+ tbl_hash
+ Partitioned table "public.tbl_hash"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ i | integer | | | | plain | |
+Partition key: HASH (i)
+Partitions: tbl_hash_0 FOR VALUES WITH (modulus 3, remainder 0),
+ tbl_hash_1 FOR VALUES WITH (modulus 3, remainder 1),
+ tbl_hash_2 FOR VALUES WITH (modulus 3, remainder 2)
+
+DROP TABLE tbl_list;
+DROP TABLE tbl_hash;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 9b1adcb8ad..c82fca0a9a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -971,3 +971,26 @@ create table part_column_drop_1_10 partition of
\d part_column_drop
\d part_column_drop_1_10
drop table part_column_drop;
+
+-- Auto generated partitions
+
+-- must fail because of wrong configuration
+CREATE TABLE tbl_hash_fail (i int) PARTITION BY HASH (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+
+-- must fail because of wrong configuration
+CREATE TABLE tbl_list_fail (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (1, 3));
+
+CREATE TABLE tbl_list (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+
+\d+ tbl_list
+
+CREATE TABLE tbl_hash (i int) PARTITION BY HASH (i)
+CONFIGURATION (modulus 3);
+
+\d+ tbl_hash
+
+DROP TABLE tbl_list;
+DROP TABLE tbl_hash;
The patch lacks documentation, because I expect some details may change
during discussion. Other than that, the feature is ready for review.
Hi, hackers!
From what I've read I see there is much interest in automatic partitions
creation. (Overall discussion on the topic is partitioned into two threads:
(1)
/messages/by-id/alpine.DEB.2.21.1907150711080.22273@lancre
and
(2)
/messages/by-id/7fec3abb-c663-c0d2-8452-a46141be6d4a@postgrespro.ru
(current thread) )
There were many syntax proposals and finally, there is a patch realizing
one of them. So I'd like to review it.
The syntax proposed in the patch seems good enough for me and is in
accordance with one of the proposals in the discussion. Maybe I'd prefer
using the word AUTOMATICALLY/AUTO instead of CONFIGURATION with explicit
meaning that using this syntax we'd get already (automatically) created
partitions and don't need to create them manually, as in the existing state
of postgresql declarative partitioning.
CREATE TABLE tbl (i int) PARTITION BY HASH (i) AUTOMATICALLY (MODULUS
3); (partitions are created automatically)
vs
CREATE TABLE tbl (i int) PARTITION BY HASH (i); (partitions should be
created manually by use of PARTITION OF)
CREATE TABLE tbl (i char) PARTITION BY LIST (i) AUTOMATICALLY (VALUES
IN ('a', 'b'), ('c', 'd'), ('e','f') DEFAULT PARTITION tbl_default);
vs
CREATE TABLE tbl (i char) PARTITION BY LIST (i); (partitions should be
created manually by use of PARTITION OF)
I think this syntax can also be extended later with adding automatic
creation of RANGE partitions, with IMMEDIATE/DEFERRED for dynamic/on-demand
automatic partition creation, and with SUBPARTITION possibility.
But I don't have a strong preference for the word AUTOMATICALLY, moreover I
saw opposition to using AUTO at the top of the discussion. I suppose we can
go with the existing CONFIGURATION word.
If compare with existing declarative partitions, I think automatic creation
simplifies the process for the end-user and I'd vote for its committing
into Postgres. The patch is short and clean in code style. It has enough
comments Tests covering the new functionality are included. Yet it doesn't
have documentation and I'd suppose it's worth adding it. Even if there will
be syntax changes, I hope they will not be more than the replacement of
several words. Current syntax is described in the text of a patch.
The patch applies cleanly and installcheck-world is passed.
Some minor things:
I've got a compiler warning:
parse_utilcmd.c:4280:15: warning: unused variable 'lc' [-Wunused-variable]
When the number of partitions is over the maximum value of int32 the output
shows a generic syntax error. I don't think it is very important as it is
not the case someone will make deliberately, but maybe it's better to
output something like "Partitions number is more than the maximum supported
value"
create table test (i int, t text) partition by hash (i) configuration
(modulus 888888888888);
ERROR: syntax error at or near "888888888888"
One more piece of nitpicking. Probably we can go just with a mention in
documentation.
create table test (i int, t text) partition by hash (i) configuration
(modulus 8888);
ERROR: out of shared memory
HINT: You might need to increase max_locks_per_transaction.
Typo:
+ /* Add statemets to create each partition after we create parent table */
Overall I see the patch almost ready for commit and I'd like to meet this
functionality in v14.
Tested it and see this feature very cool and much simpler to use compared
to declarative partitioning to date.
Thanks!
--
Best regards,
Pavel Borisov
Postgres Professional: http://postgrespro.com <http://www.postgrespro.com>
On 08.09.2020 17:03, Pavel Borisov wrote:
The patch lacks documentation, because I expect some details may
change during discussion. Other than that, the feature is ready
for review.Hi, hackers!
From what I've read I see there is much interest in automatic
partitions creation. (Overall discussion on the topic is partitioned
into two threads: (1)
/messages/by-id/alpine.DEB.2.21.1907150711080.22273@lancre%C2%A0and
(2)
/messages/by-id/7fec3abb-c663-c0d2-8452-a46141be6d4a@postgrespro.ru
(current thread) )There were many syntax proposals and finally, there is a patch
realizing one of them. So I'd like to review it.The syntax proposed in the patch seems good enough for me and is in
accordance with one of the proposals in the discussion. Maybe I'd
prefer using the word AUTOMATICALLY/AUTO instead of CONFIGURATION with
explicit meaning that using this syntax we'd get already
(automatically) created partitions and don't need to create them
manually, as in the existing state of postgresql declarative
partitioning.CREATE TABLE tbl (iint) PARTITION BY HASH (i) AUTOMATICALLY (MODULUS 3); (partitions are created automatically)
vs
CREATE TABLE tbl (iint) PARTITION BY HASH (i); (partitions should be created manually by use of PARTITION OF)
CREATE TABLE tbl (i char) PARTITION BY LIST (i) AUTOMATICALLY (VALUES
IN ('a', 'b'), ('c', 'd'), ('e','f') DEFAULTPARTITION tbl_default);
vs
CREATE TABLE tbl (ichar) PARTITION BY LIST (i); (partitions should be created manually by use of PARTITION OF)I think this syntax can also be extended later with adding automatic
creation of RANGE partitions, with IMMEDIATE/DEFERRED for
dynamic/on-demand automatic partition creation, and with SUBPARTITION
possibility.But I don't have a strong preference for the word AUTOMATICALLY,
moreover I saw opposition to using AUTO at the top of the discussion.
I suppose we can go with the existing CONFIGURATION word.
I agree that 'AUTOMATICALLY' keyword is more specific and probably less
confusing for users. I've picked 'CONFIGURATION' simply because it is an
already existing keyword. It would like to hear other opinions on that.
If compare with existing declarative partitions, I think automatic
creation simplifies the process for the end-user and I'd vote for its
committing into Postgres. The patch is short and clean in code style.
It has enough comments Tests covering the new functionality are
included. Yet it doesn't have documentation and I'd suppose it's worth
adding it. Even if there will be syntax changes, I hope they will not
be more than the replacement of several words. Current syntax is
described in the text of a patch.
Fair enough. New patch contains a documentation draft. While writing it,
I also noticed, that the syntax, introduced in the patch differs from
greenpulm one. For now, list partitioning clause doesn't support
'PARTITION name' part, that is supported in greenplum. I don't think
that we aim for 100% compatibility here. Still, the ability to provide
table names is probably a good optional feature, especially for list
partitions.
What do you think?
The patch applies cleanly and installcheck-world is passed.
Some minor things:
I've got a compiler warning:
parse_utilcmd.c:4280:15: warning: unused variable 'lc' [-Wunused-variable]
Fixed. This was also caught by cfbot. This version should pass it clean.
When the number of partitions is over the maximum value of int32 the
output shows a generic syntax error. I don't think it is very
important as it is not the case someone will make deliberately, but
maybe it's better to output something like "Partitions number is more
than the maximum supported value"
create table test (i int, t text) partition by hash (i) configuration
(modulus 888888888888);
ERROR: syntax error at or near "888888888888"
This value is not a valid int32 number, thus parser throws the error
before we have a chance to handle it more gracefully.
One more piece of nitpicking. Probably we can go just with a mention
in documentation.
create table test (i int, t text) partition by hash (i) configuration
(modulus 8888);
ERROR: out of shared memory
HINT: You might need to increase max_locks_per_transaction.
Well, it looks like a legit error, when we try to lock a lot of objects
in one transaction. I will double check if we don't release a lock
somewhere.
Do we need to restrict the number of partitions, that can be created by
this statement? With what number? As far as I see, there is no such
restriction for now, just a recommendation about performance issues.
With automatic creation it becomes easier to mess with it.
Probably, it's enough to mention it in documentation and rely on users
common sense.
Typo:
+ /* Add statemets to create each partition after we create parent
table */
Fixed.
Overall I see the patch almost ready for commit and I'd like to meet
this functionality in v14.
I also hope that this patch will make it to v14, but for now, I don't
see a consensus on the syntax and some details, so I wouldn't rush.
Besides, it definitely needs more testing. I haven't thoroughly tested
following cases yet:
- how triggers and constraints are propagated to partitions;
- how does it handle some tricky clauses in list partitioning expr_list;
and so on.
Also, there is an open question about partition naming. Currently, the
patch implements dummy %tbl_%partnum name generation, which is far from
user-friendly. I think we must provide some hook or trigger function to
rename partitions after they were created.
--
Anastasia Lubennikova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
auto_part_hash_list_v1.patchtext/x-patch; charset=UTF-8; name=auto_part_hash_list_v1.patchDownload
commit 08c5f8b35c4ffcaf09b8189cd8d0dc27ce76d715
Author: anastasia <a.lubennikova@postgrespro.ru>
Date: Mon Sep 14 11:34:42 2020 +0300
Auto generated HASH and LIST partitions.
New syntax:
CREATE TABLE tbl_hash (i int) PARTITION BY HASH (i)
CONFIGURATION (modulus 3);
CREATE TABLE tbl_list (i int) PARTITION BY LIST (i)
CONFIGURATION (values in (1, 2), (3, 4) DEFAULT PARTITION tbl_default);
With documentation draft.
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 087cad184c..ff9a7eda09 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -29,6 +29,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ]
[ USING <replaceable class="parameter">method</replaceable> ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -41,6 +42,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
) ]
[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ]
[ USING <replaceable class="parameter">method</replaceable> ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -53,6 +55,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
) ] { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT }
[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ]
[ USING <replaceable class="parameter">method</replaceable> ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -96,6 +99,11 @@ FROM ( { <replaceable class="parameter">partition_bound_expr</replaceable> | MIN
TO ( { <replaceable class="parameter">partition_bound_expr</replaceable> | MINVALUE | MAXVALUE } [, ...] ) |
WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REMAINDER <replaceable class="parameter">numeric_literal</replaceable> )
+<phrase>and <replaceable class="parameter">partition_bound_auto_spec</replaceable> is:</phrase>
+
+VALUES IN ( <replaceable class="parameter">partition_bound_expr</replaceable> [, ...] ), [( <replaceable class="parameter">partition_bound_expr</replaceable> [, ...] )] [, ...] [DEFAULT PARTITION <replaceable class="parameter">defailt_part_name</replaceable>]
+MODULUS <replaceable class="parameter">numeric_literal</replaceable>
+
<phrase><replaceable class="parameter">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
[ INCLUDE ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ]
@@ -383,6 +391,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
however, you can define these constraints on individual partitions.
</para>
+ <para>
+ Range and list partitioning also support automatic creation of partitions
+ with an optional <literal>CONFIGURATION</literal> clause.
+ </para>
+
<para>
See <xref linkend="ddl-partitioning"/> for more discussion on table
partitioning.
@@ -391,6 +404,38 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ] </literal></term>
+ <listitem>
+ <para>
+ The optional <literal>CONFIGURATION</literal> clause used together
+ with <literal>PARTITION BY</literal> specifies a rule of generating bounds
+ for partitions of the partitioned table. All partitions are created automatically
+ along with the parent table.
+
+ Any indexes, constraints and user-defined row-level triggers that exist
+ in the parent table are cloned on the new partitions.
+ </para>
+
+ <para>
+ The <replaceable class="parameter">partition_bound_auto_spec</replaceable>
+ must correspond to the partitioning method and partition key of the
+ parent table, and must not overlap with any existing partition of that
+ parent. The form with <literal>VALUES IN</literal> is used for list partitioning
+ and the form with <literal>MODULUS</literal> is used for hash partitioning.
+ List partitioning can also provide a default partition using
+ <literal>DEFAULT PARTITION</literal>.
+ </para>
+
+ <para>
+ Automatic range partitioning is not supported yet.
+ </para>
+
+
+
+ </listitem>
+ </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> | DEFAULT }</literal></term>
<listitem>
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 0409a40b82..6893fa5495 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4628,6 +4628,7 @@ _copyPartitionSpec(const PartitionSpec *from)
COPY_STRING_FIELD(strategy);
COPY_NODE_FIELD(partParams);
+ COPY_NODE_FIELD(autopart);
COPY_LOCATION_FIELD(location);
return newnode;
@@ -4650,6 +4651,19 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
return newnode;
}
+static PartitionBoundAutoSpec *
+_copyPartitionBoundAutoSpec(const PartitionBoundAutoSpec *from)
+{
+ PartitionBoundAutoSpec *newnode = makeNode(PartitionBoundAutoSpec);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_SCALAR_FIELD(modulus);
+ COPY_NODE_FIELD(listdatumsList);
+ COPY_NODE_FIELD(default_partition_rv);
+
+ return newnode;
+}
+
static PartitionRangeDatum *
_copyPartitionRangeDatum(const PartitionRangeDatum *from)
{
@@ -5699,6 +5713,9 @@ copyObjectImpl(const void *from)
case T_PartitionBoundSpec:
retval = _copyPartitionBoundSpec(from);
break;
+ case T_PartitionBoundAutoSpec:
+ retval = _copyPartitionBoundAutoSpec(from);
+ break;
case T_PartitionRangeDatum:
retval = _copyPartitionRangeDatum(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2d1b987bf..ccba2471de 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2897,6 +2897,7 @@ _equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
COMPARE_STRING_FIELD(strategy);
COMPARE_NODE_FIELD(partParams);
COMPARE_LOCATION_FIELD(location);
+ COMPARE_NODE_FIELD(autopart);
return true;
}
@@ -2916,6 +2917,19 @@ _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *
return true;
}
+static bool
+_equalPartitionBoundAutoSpec(const PartitionBoundAutoSpec *a,
+ const PartitionBoundAutoSpec *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_SCALAR_FIELD(modulus);
+ COMPARE_NODE_FIELD(listdatumsList);
+ COMPARE_NODE_FIELD(default_partition_rv);
+
+ return true;
+}
+
+
static bool
_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
{
@@ -3751,6 +3765,9 @@ equal(const void *a, const void *b)
case T_PartitionBoundSpec:
retval = _equalPartitionBoundSpec(a, b);
break;
+ case T_PartitionBoundAutoSpec:
+ retval = _equalPartitionBoundAutoSpec(a, b);
+ break;
case T_PartitionRangeDatum:
retval = _equalPartitionRangeDatum(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..4fd12523d8 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3643,6 +3643,7 @@ _outPartitionSpec(StringInfo str, const PartitionSpec *node)
WRITE_STRING_FIELD(strategy);
WRITE_NODE_FIELD(partParams);
+ WRITE_NODE_FIELD(autopart);
WRITE_LOCATION_FIELD(location);
}
@@ -3661,6 +3662,18 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionBoundAutoSpec(StringInfo str, const PartitionBoundAutoSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBOUNDAUTOSPEC");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_INT_FIELD(modulus);
+ WRITE_NODE_FIELD(listdatumsList);
+ WRITE_NODE_FIELD(default_partition_rv);
+
+}
+
static void
_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
{
@@ -4334,6 +4347,9 @@ outNode(StringInfo str, const void *obj)
case T_PartitionBoundSpec:
_outPartitionBoundSpec(str, obj);
break;
+ case T_PartitionBoundAutoSpec:
+ _outPartitionBoundAutoSpec(str, obj);
+ break;
case T_PartitionRangeDatum:
_outPartitionRangeDatum(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..703b413f93 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2602,6 +2602,19 @@ _readPartitionBoundSpec(void)
READ_DONE();
}
+static PartitionBoundAutoSpec *
+_readPartitionBoundAutoSpec(void)
+{
+ READ_LOCALS(PartitionBoundAutoSpec);
+
+ READ_CHAR_FIELD(strategy);
+ READ_INT_FIELD(modulus);
+ READ_NODE_FIELD(listdatumsList);
+ READ_NODE_FIELD(default_partition_rv);
+
+ READ_DONE();
+}
+
/*
* _readPartitionRangeDatum
*/
@@ -2880,6 +2893,8 @@ parseNodeString(void)
return_value = _readPartitionBoundSpec();
else if (MATCH("PARTITIONRANGEDATUM", 19))
return_value = _readPartitionRangeDatum();
+ else if (MATCH("PARTITIONBOUNDAUTOSPEC", 22))
+ return_value = _readPartitionBoundAutoSpec();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9f47745ee2..a1f9db30d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -249,6 +249,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionElem *partelem;
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
+ PartitionBoundAutoSpec *partboundautospec;
RoleSpec *rolespec;
struct SelectLimit *selectlimit;
}
@@ -599,6 +600,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
+%type <partboundautospec> OptPartitionBoundAutoSpec values_in_clause p_desc
+%type <range> opt_default_partition_clause
+
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
* They must be listed first so that their numeric codes do not depend on
@@ -3907,14 +3911,14 @@ OptPartitionSpec: PartitionSpec { $$ = $1; }
| /*EMPTY*/ { $$ = NULL; }
;
-PartitionSpec: PARTITION BY ColId '(' part_params ')'
+PartitionSpec: PARTITION BY ColId '(' part_params ')' OptPartitionBoundAutoSpec
{
PartitionSpec *n = makeNode(PartitionSpec);
n->strategy = $3;
n->partParams = $5;
n->location = @1;
-
+ n->autopart = (Node *) $7;
$$ = n;
}
;
@@ -3958,6 +3962,80 @@ part_elem: ColId opt_collate opt_class
}
;
+OptPartitionBoundAutoSpec:
+ CONFIGURATION '(' p_desc ')'
+ {
+ $$ = $3;
+ }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+p_desc:
+ hash_partbound
+ {
+ ListCell *lc;
+ PartitionBoundAutoSpec *n = makeNode(PartitionBoundAutoSpec);
+
+ n->modulus = -1;
+
+ foreach (lc, $1)
+ {
+ DefElem *opt = lfirst_node(DefElem, lc);
+
+ if (strcmp(opt->defname, "modulus") == 0)
+ {
+ n->strategy = PARTITION_STRATEGY_HASH;
+ if (n->modulus != -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("modulus for hash partition provided more than once"),
+ parser_errposition(opt->location)));
+ n->modulus = defGetInt32(opt);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized auto partition bound specification \"%s\"",
+ opt->defname),
+ parser_errposition(opt->location)));
+ }
+
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ | values_in_clause opt_default_partition_clause
+ {
+ PartitionBoundAutoSpec *n = $1;
+ n->default_partition_rv = $2;
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ ;
+
+values_in_clause:
+ VALUES IN_P '(' expr_list ')'
+ {
+ PartitionBoundAutoSpec *n = makeNode(PartitionBoundAutoSpec);
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatumsList = list_make1($4);
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ | values_in_clause ',' '(' expr_list ')'
+ {
+ PartitionBoundAutoSpec *n = (PartitionBoundAutoSpec *) $1;
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatumsList = lappend(n->listdatumsList, $4);
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ ;
+
+opt_default_partition_clause:
+ DEFAULT PARTITION qualified_name
+ {
+ $$ = $3;
+ }
+ | /* EMPTY */
+ { $$ = NULL; }
+ ;
+
table_access_method_clause:
USING name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ec944371dd..a22bbb8ebc 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -75,6 +75,7 @@
/* State shared by transformCreateStmt and its subroutines */
typedef struct
{
+ CreateStmt *stmt; /* initial statement */
ParseState *pstate; /* overall parser state */
const char *stmtType; /* "CREATE [FOREIGN] TABLE" or "ALTER TABLE" */
RangeVar *relation; /* relation to create */
@@ -145,6 +146,7 @@ static Const *transformPartitionBoundValue(ParseState *pstate, Node *con,
const char *colName, Oid colType, int32 colTypmod,
Oid partCollation);
+static void transformPartitionAutoCreate(CreateStmtContext *cxt, PartitionSpec* partspec);
/*
* transformCreateStmt -
@@ -235,6 +237,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.stmtType = "CREATE TABLE";
cxt.isforeign = false;
}
+ cxt.stmt = stmt;
cxt.relation = stmt->relation;
cxt.rel = NULL;
cxt.inhRelations = stmt->inhRelations;
@@ -324,6 +327,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
transformExtendedStatistics(&cxt);
+ /* Process partition definitions */
+ if (stmt->partspec && stmt->partspec->autopart)
+ transformPartitionAutoCreate(&cxt, stmt->partspec);
+
/*
* Output results.
*/
@@ -4258,3 +4265,133 @@ transformPartitionBoundValue(ParseState *pstate, Node *val,
return (Const *) value;
}
+
+
+/*
+ * Transform configuration into a set of partition bounds.
+ * Generate extra statements to create partition tables.
+ */
+static void
+transformPartitionAutoCreate(CreateStmtContext *cxt, PartitionSpec* partspec)
+{
+ CreateStmt *part;
+ List *partlist = NIL;
+ int i = 0;
+ PartitionBoundAutoSpec *bound = (PartitionBoundAutoSpec *) partspec->autopart;
+
+ elog(DEBUG1, "transformPartitionAutoCreate \n %s \n ", nodeToString(bound));
+
+ /*
+ * Generate regular partbounds based on autopart rule.
+ * and form create table statements from these partbounds
+ */
+ if (pg_strcasecmp(partspec->strategy, "hash") == 0)
+ {
+ if (bound->strategy != PARTITION_STRATEGY_HASH)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a hash partition"),
+ parser_errposition(cxt->pstate, exprLocation((Node *) partspec))));
+
+ for (i = 0; i < bound->modulus; i++)
+ {
+ char *part_relname;
+
+ /*
+ * Generate partition name in the format:
+ * $relname_$partnum
+ *
+ * TODO: Add checks on relname length.
+ */
+ part_relname = psprintf("%s_%d", cxt->relation->relname, i);
+
+ part = copyObject(cxt->stmt);
+
+ part->relation = makeRangeVar(cxt->relation->schemaname,
+ part_relname, cxt->relation->location);
+ /* set table as a parent */
+ part->inhRelations = lappend(part->inhRelations, cxt->relation);
+
+ /* child table is not partitioned */
+ part->partspec = NULL;
+
+ /* Actual partbound generation is here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_HASH;
+ part->partbound->modulus = bound->modulus;
+ part->partbound->remainder = i;
+ part->partbound->is_default = false;
+
+ elog(DEBUG1,"stransformPartitionAutoCreate HASH i %d MODULUS %d \n %s\n",
+ i, bound->modulus, nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+ }
+ else if (pg_strcasecmp(partspec->strategy, "list") == 0)
+ {
+
+ int n_list_parts = list_length(bound->listdatumsList);
+
+ if (bound->strategy != PARTITION_STRATEGY_LIST)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation((Node *) partspec))));
+
+ for (i = 0; i < n_list_parts; i++)
+ {
+ char *part_relname;
+ List *listdatums = (List *)
+ list_nth(bound->listdatumsList, i);
+
+ part_relname = psprintf("%s_%d", cxt->relation->relname, i);
+
+ part = copyObject(cxt->stmt);
+
+ part->relation = makeRangeVar(cxt->relation->schemaname,
+ part_relname, cxt->relation->location);
+ /* set table as a parent */
+ part->inhRelations = lappend(part->inhRelations, cxt->relation);
+
+ /* child table is not partitioned */
+ part->partspec = NULL;
+
+ /* Actual partbound generation is here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_LIST;
+ part->partbound->listdatums = list_copy(listdatums);
+ part->partbound->is_default = false;
+
+ elog(DEBUG1,"Debug transformPartitionAutoCreate LIST i %d \n %s\n",
+ i, nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+
+ if (bound->default_partition_rv)
+ {
+ part = copyObject(cxt->stmt);
+
+ part->relation = bound->default_partition_rv;
+ /* set table as a parent */
+ part->inhRelations = lappend(part->inhRelations, cxt->relation);
+
+ /* child table is not partitioned */
+ part->partspec = NULL;
+
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_LIST;
+ part->partbound->listdatums = NULL;
+ part->partbound->is_default = true;
+
+ elog(DEBUG1,"Debug transformPartitionAutoCreate LIST default partition \n %s\n",
+ nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+ }
+
+ /* Add statements to create each partition after we create parent table */
+ cxt->alist = list_concat(cxt->alist, partlist);
+}
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..ac6fcc029e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -478,6 +478,7 @@ typedef enum NodeTag
T_PartitionElem,
T_PartitionSpec,
T_PartitionBoundSpec,
+ T_PartitionBoundAutoSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e83329fd6d..271632f0fe 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -794,6 +794,9 @@ typedef struct PartitionSpec
* 'range') */
List *partParams; /* List of PartitionElems */
int location; /* token location, or -1 if unknown */
+
+ Node *autopart; /* PartitionBoundAutoSpec -
+ * spec to generate bounds automatically */
} PartitionSpec;
/* Internal codes for partitioning strategies */
@@ -828,6 +831,26 @@ struct PartitionBoundSpec
int location; /* token location, or -1 if unknown */
};
+/*
+ * PartitionBoundAutoSpec - a partition bound specification
+ * for auto generated partitions.
+ *
+ * This represents the rule of generating partition bounds
+ */
+struct PartitionBoundAutoSpec
+{
+ NodeTag type;
+
+ char strategy; /* see PARTITION_STRATEGY codes above */
+
+ /* Partitioning info for HASH strategy: */
+ int modulus;
+
+ /* Partitioning info for LIST strategy: */
+ List *listdatumsList; /* List of lists of Consts (or A_Consts in raw tree) */
+ RangeVar *default_partition_rv; /* Name of default list partition */
+};
+
/*
* PartitionRangeDatum - one of the values in a range partition bound
*
diff --git a/src/include/partitioning/partdefs.h b/src/include/partitioning/partdefs.h
index 6414e2c116..25ecfbd1de 100644
--- a/src/include/partitioning/partdefs.h
+++ b/src/include/partitioning/partdefs.h
@@ -19,6 +19,8 @@ typedef struct PartitionKeyData *PartitionKey;
typedef struct PartitionBoundSpec PartitionBoundSpec;
+typedef struct PartitionBoundAutoSpec PartitionBoundAutoSpec;
+
typedef struct PartitionDescData *PartitionDesc;
typedef struct PartitionDirectoryData *PartitionDirectory;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1c72f23bc9..3047ed9007 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -1283,3 +1283,40 @@ Indexes:
"part_column_drop_1_10_expr_idx1" btree ((d = 2))
drop table part_column_drop;
+-- Auto generated partitions
+-- must fail because of wrong configuration
+CREATE TABLE tbl_hash_fail (i int) PARTITION BY HASH (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+ERROR: invalid bound specification for a hash partition
+LINE 1: CREATE TABLE tbl_hash_fail (i int) PARTITION BY HASH (i)
+ ^
+-- must fail because of wrong configuration
+CREATE TABLE tbl_list_fail (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (1, 3));
+ERROR: partition "tbl_list_fail_1" would overlap partition "tbl_list_fail_0"
+CREATE TABLE tbl_list (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+\d+ tbl_list
+ Partitioned table "public.tbl_list"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ i | integer | | | | plain | |
+Partition key: LIST (i)
+Partitions: tbl_list_0 FOR VALUES IN (1, 2),
+ tbl_list_1 FOR VALUES IN (3, 4),
+ tbl_default DEFAULT
+
+CREATE TABLE tbl_hash (i int) PARTITION BY HASH (i)
+CONFIGURATION (modulus 3);
+\d+ tbl_hash
+ Partitioned table "public.tbl_hash"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ i | integer | | | | plain | |
+Partition key: HASH (i)
+Partitions: tbl_hash_0 FOR VALUES WITH (modulus 3, remainder 0),
+ tbl_hash_1 FOR VALUES WITH (modulus 3, remainder 1),
+ tbl_hash_2 FOR VALUES WITH (modulus 3, remainder 2)
+
+DROP TABLE tbl_list;
+DROP TABLE tbl_hash;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 9b1adcb8ad..c82fca0a9a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -971,3 +971,26 @@ create table part_column_drop_1_10 partition of
\d part_column_drop
\d part_column_drop_1_10
drop table part_column_drop;
+
+-- Auto generated partitions
+
+-- must fail because of wrong configuration
+CREATE TABLE tbl_hash_fail (i int) PARTITION BY HASH (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+
+-- must fail because of wrong configuration
+CREATE TABLE tbl_list_fail (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (1, 3));
+
+CREATE TABLE tbl_list (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+
+\d+ tbl_list
+
+CREATE TABLE tbl_hash (i int) PARTITION BY HASH (i)
+CONFIGURATION (modulus 3);
+
+\d+ tbl_hash
+
+DROP TABLE tbl_list;
+DROP TABLE tbl_hash;
On Mon, Sep 14, 2020 at 02:38:56PM +0300, Anastasia Lubennikova wrote:
Fixed. This was also caught by cfbot. This version should pass it clean.
Please note that regression tests are failing, because of 6b2c4e59.
--
Michael
On 24.09.2020 06:27, Michael Paquier wrote:
On Mon, Sep 14, 2020 at 02:38:56PM +0300, Anastasia Lubennikova wrote:
Fixed. This was also caught by cfbot. This version should pass it clean.
Please note that regression tests are failing, because of 6b2c4e59.
--
Michael
Thank you. Updated patch is attached.
Open issues for review:
- new syntax;
- generation of partition names;
- overall patch review and testing, especially with complex partitioning
clauses.
--
Anastasia Lubennikova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
auto_part_hash_list_v2.patchtext/x-patch; charset=UTF-8; name=auto_part_hash_list_v2.patchDownload
commit faa5805b839effd9d8220eff787fb0995276c370
Author: anastasia <a.lubennikova@postgrespro.ru>
Date: Mon Sep 14 11:34:42 2020 +0300
Auto generated HASH and LIST partitions.
New syntax:
CREATE TABLE tbl_hash (i int) PARTITION BY HASH (i)
CONFIGURATION (modulus 3);
CREATE TABLE tbl_list (i int) PARTITION BY LIST (i)
CONFIGURATION (values in (1, 2), (3, 4) DEFAULT PARTITION tbl_default);
With documentation draft.
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 087cad184c..ff9a7eda09 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -29,6 +29,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ]
[ USING <replaceable class="parameter">method</replaceable> ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -41,6 +42,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
) ]
[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ]
[ USING <replaceable class="parameter">method</replaceable> ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -53,6 +55,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
) ] { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT }
[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ]
[ USING <replaceable class="parameter">method</replaceable> ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -96,6 +99,11 @@ FROM ( { <replaceable class="parameter">partition_bound_expr</replaceable> | MIN
TO ( { <replaceable class="parameter">partition_bound_expr</replaceable> | MINVALUE | MAXVALUE } [, ...] ) |
WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REMAINDER <replaceable class="parameter">numeric_literal</replaceable> )
+<phrase>and <replaceable class="parameter">partition_bound_auto_spec</replaceable> is:</phrase>
+
+VALUES IN ( <replaceable class="parameter">partition_bound_expr</replaceable> [, ...] ), [( <replaceable class="parameter">partition_bound_expr</replaceable> [, ...] )] [, ...] [DEFAULT PARTITION <replaceable class="parameter">defailt_part_name</replaceable>]
+MODULUS <replaceable class="parameter">numeric_literal</replaceable>
+
<phrase><replaceable class="parameter">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
[ INCLUDE ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ]
@@ -383,6 +391,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
however, you can define these constraints on individual partitions.
</para>
+ <para>
+ Range and list partitioning also support automatic creation of partitions
+ with an optional <literal>CONFIGURATION</literal> clause.
+ </para>
+
<para>
See <xref linkend="ddl-partitioning"/> for more discussion on table
partitioning.
@@ -391,6 +404,38 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ] </literal></term>
+ <listitem>
+ <para>
+ The optional <literal>CONFIGURATION</literal> clause used together
+ with <literal>PARTITION BY</literal> specifies a rule of generating bounds
+ for partitions of the partitioned table. All partitions are created automatically
+ along with the parent table.
+
+ Any indexes, constraints and user-defined row-level triggers that exist
+ in the parent table are cloned on the new partitions.
+ </para>
+
+ <para>
+ The <replaceable class="parameter">partition_bound_auto_spec</replaceable>
+ must correspond to the partitioning method and partition key of the
+ parent table, and must not overlap with any existing partition of that
+ parent. The form with <literal>VALUES IN</literal> is used for list partitioning
+ and the form with <literal>MODULUS</literal> is used for hash partitioning.
+ List partitioning can also provide a default partition using
+ <literal>DEFAULT PARTITION</literal>.
+ </para>
+
+ <para>
+ Automatic range partitioning is not supported yet.
+ </para>
+
+
+
+ </listitem>
+ </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> | DEFAULT }</literal></term>
<listitem>
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 0409a40b82..6893fa5495 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4628,6 +4628,7 @@ _copyPartitionSpec(const PartitionSpec *from)
COPY_STRING_FIELD(strategy);
COPY_NODE_FIELD(partParams);
+ COPY_NODE_FIELD(autopart);
COPY_LOCATION_FIELD(location);
return newnode;
@@ -4650,6 +4651,19 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
return newnode;
}
+static PartitionBoundAutoSpec *
+_copyPartitionBoundAutoSpec(const PartitionBoundAutoSpec *from)
+{
+ PartitionBoundAutoSpec *newnode = makeNode(PartitionBoundAutoSpec);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_SCALAR_FIELD(modulus);
+ COPY_NODE_FIELD(listdatumsList);
+ COPY_NODE_FIELD(default_partition_rv);
+
+ return newnode;
+}
+
static PartitionRangeDatum *
_copyPartitionRangeDatum(const PartitionRangeDatum *from)
{
@@ -5699,6 +5713,9 @@ copyObjectImpl(const void *from)
case T_PartitionBoundSpec:
retval = _copyPartitionBoundSpec(from);
break;
+ case T_PartitionBoundAutoSpec:
+ retval = _copyPartitionBoundAutoSpec(from);
+ break;
case T_PartitionRangeDatum:
retval = _copyPartitionRangeDatum(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2d1b987bf..ccba2471de 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2897,6 +2897,7 @@ _equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
COMPARE_STRING_FIELD(strategy);
COMPARE_NODE_FIELD(partParams);
COMPARE_LOCATION_FIELD(location);
+ COMPARE_NODE_FIELD(autopart);
return true;
}
@@ -2916,6 +2917,19 @@ _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *
return true;
}
+static bool
+_equalPartitionBoundAutoSpec(const PartitionBoundAutoSpec *a,
+ const PartitionBoundAutoSpec *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_SCALAR_FIELD(modulus);
+ COMPARE_NODE_FIELD(listdatumsList);
+ COMPARE_NODE_FIELD(default_partition_rv);
+
+ return true;
+}
+
+
static bool
_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
{
@@ -3751,6 +3765,9 @@ equal(const void *a, const void *b)
case T_PartitionBoundSpec:
retval = _equalPartitionBoundSpec(a, b);
break;
+ case T_PartitionBoundAutoSpec:
+ retval = _equalPartitionBoundAutoSpec(a, b);
+ break;
case T_PartitionRangeDatum:
retval = _equalPartitionRangeDatum(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..4fd12523d8 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3643,6 +3643,7 @@ _outPartitionSpec(StringInfo str, const PartitionSpec *node)
WRITE_STRING_FIELD(strategy);
WRITE_NODE_FIELD(partParams);
+ WRITE_NODE_FIELD(autopart);
WRITE_LOCATION_FIELD(location);
}
@@ -3661,6 +3662,18 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionBoundAutoSpec(StringInfo str, const PartitionBoundAutoSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBOUNDAUTOSPEC");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_INT_FIELD(modulus);
+ WRITE_NODE_FIELD(listdatumsList);
+ WRITE_NODE_FIELD(default_partition_rv);
+
+}
+
static void
_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
{
@@ -4334,6 +4347,9 @@ outNode(StringInfo str, const void *obj)
case T_PartitionBoundSpec:
_outPartitionBoundSpec(str, obj);
break;
+ case T_PartitionBoundAutoSpec:
+ _outPartitionBoundAutoSpec(str, obj);
+ break;
case T_PartitionRangeDatum:
_outPartitionRangeDatum(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..703b413f93 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2602,6 +2602,19 @@ _readPartitionBoundSpec(void)
READ_DONE();
}
+static PartitionBoundAutoSpec *
+_readPartitionBoundAutoSpec(void)
+{
+ READ_LOCALS(PartitionBoundAutoSpec);
+
+ READ_CHAR_FIELD(strategy);
+ READ_INT_FIELD(modulus);
+ READ_NODE_FIELD(listdatumsList);
+ READ_NODE_FIELD(default_partition_rv);
+
+ READ_DONE();
+}
+
/*
* _readPartitionRangeDatum
*/
@@ -2880,6 +2893,8 @@ parseNodeString(void)
return_value = _readPartitionBoundSpec();
else if (MATCH("PARTITIONRANGEDATUM", 19))
return_value = _readPartitionRangeDatum();
+ else if (MATCH("PARTITIONBOUNDAUTOSPEC", 22))
+ return_value = _readPartitionBoundAutoSpec();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 17653ef3a7..aca93b8c96 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -249,6 +249,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionElem *partelem;
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
+ PartitionBoundAutoSpec *partboundautospec;
RoleSpec *rolespec;
struct SelectLimit *selectlimit;
}
@@ -601,6 +602,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
+%type <partboundautospec> OptPartitionBoundAutoSpec values_in_clause p_desc
+%type <range> opt_default_partition_clause
+
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
* They must be listed first so that their numeric codes do not depend on
@@ -3904,14 +3908,14 @@ OptPartitionSpec: PartitionSpec { $$ = $1; }
| /*EMPTY*/ { $$ = NULL; }
;
-PartitionSpec: PARTITION BY ColId '(' part_params ')'
+PartitionSpec: PARTITION BY ColId '(' part_params ')' OptPartitionBoundAutoSpec
{
PartitionSpec *n = makeNode(PartitionSpec);
n->strategy = $3;
n->partParams = $5;
n->location = @1;
-
+ n->autopart = (Node *) $7;
$$ = n;
}
;
@@ -3955,6 +3959,80 @@ part_elem: ColId opt_collate opt_class
}
;
+OptPartitionBoundAutoSpec:
+ CONFIGURATION '(' p_desc ')'
+ {
+ $$ = $3;
+ }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+p_desc:
+ hash_partbound
+ {
+ ListCell *lc;
+ PartitionBoundAutoSpec *n = makeNode(PartitionBoundAutoSpec);
+
+ n->modulus = -1;
+
+ foreach (lc, $1)
+ {
+ DefElem *opt = lfirst_node(DefElem, lc);
+
+ if (strcmp(opt->defname, "modulus") == 0)
+ {
+ n->strategy = PARTITION_STRATEGY_HASH;
+ if (n->modulus != -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("modulus for hash partition provided more than once"),
+ parser_errposition(opt->location)));
+ n->modulus = defGetInt32(opt);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized auto partition bound specification \"%s\"",
+ opt->defname),
+ parser_errposition(opt->location)));
+ }
+
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ | values_in_clause opt_default_partition_clause
+ {
+ PartitionBoundAutoSpec *n = $1;
+ n->default_partition_rv = $2;
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ ;
+
+values_in_clause:
+ VALUES IN_P '(' expr_list ')'
+ {
+ PartitionBoundAutoSpec *n = makeNode(PartitionBoundAutoSpec);
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatumsList = list_make1($4);
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ | values_in_clause ',' '(' expr_list ')'
+ {
+ PartitionBoundAutoSpec *n = (PartitionBoundAutoSpec *) $1;
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatumsList = lappend(n->listdatumsList, $4);
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ ;
+
+opt_default_partition_clause:
+ DEFAULT PARTITION qualified_name
+ {
+ $$ = $3;
+ }
+ | /* EMPTY */
+ { $$ = NULL; }
+ ;
+
table_access_method_clause:
USING name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 164312d60e..b9ba712e36 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -75,6 +75,7 @@
/* State shared by transformCreateStmt and its subroutines */
typedef struct
{
+ CreateStmt *stmt; /* initial statement */
ParseState *pstate; /* overall parser state */
const char *stmtType; /* "CREATE [FOREIGN] TABLE" or "ALTER TABLE" */
RangeVar *relation; /* relation to create */
@@ -145,6 +146,7 @@ static Const *transformPartitionBoundValue(ParseState *pstate, Node *con,
const char *colName, Oid colType, int32 colTypmod,
Oid partCollation);
+static void transformPartitionAutoCreate(CreateStmtContext *cxt, PartitionSpec* partspec);
/*
* transformCreateStmt -
@@ -235,6 +237,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.stmtType = "CREATE TABLE";
cxt.isforeign = false;
}
+ cxt.stmt = stmt;
cxt.relation = stmt->relation;
cxt.rel = NULL;
cxt.inhRelations = stmt->inhRelations;
@@ -324,6 +327,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
transformExtendedStatistics(&cxt);
+ /* Process partition definitions */
+ if (stmt->partspec && stmt->partspec->autopart)
+ transformPartitionAutoCreate(&cxt, stmt->partspec);
+
/*
* Output results.
*/
@@ -4279,3 +4286,133 @@ transformPartitionBoundValue(ParseState *pstate, Node *val,
return (Const *) value;
}
+
+
+/*
+ * Transform configuration into a set of partition bounds.
+ * Generate extra statements to create partition tables.
+ */
+static void
+transformPartitionAutoCreate(CreateStmtContext *cxt, PartitionSpec* partspec)
+{
+ CreateStmt *part;
+ List *partlist = NIL;
+ int i = 0;
+ PartitionBoundAutoSpec *bound = (PartitionBoundAutoSpec *) partspec->autopart;
+
+ elog(DEBUG1, "transformPartitionAutoCreate \n %s \n ", nodeToString(bound));
+
+ /*
+ * Generate regular partbounds based on autopart rule.
+ * and form create table statements from these partbounds
+ */
+ if (pg_strcasecmp(partspec->strategy, "hash") == 0)
+ {
+ if (bound->strategy != PARTITION_STRATEGY_HASH)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a hash partition"),
+ parser_errposition(cxt->pstate, exprLocation((Node *) partspec))));
+
+ for (i = 0; i < bound->modulus; i++)
+ {
+ char *part_relname;
+
+ /*
+ * Generate partition name in the format:
+ * $relname_$partnum
+ *
+ * TODO: Add checks on relname length.
+ */
+ part_relname = psprintf("%s_%d", cxt->relation->relname, i);
+
+ part = copyObject(cxt->stmt);
+
+ part->relation = makeRangeVar(cxt->relation->schemaname,
+ part_relname, cxt->relation->location);
+ /* set table as a parent */
+ part->inhRelations = lappend(part->inhRelations, cxt->relation);
+
+ /* child table is not partitioned */
+ part->partspec = NULL;
+
+ /* Actual partbound generation is here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_HASH;
+ part->partbound->modulus = bound->modulus;
+ part->partbound->remainder = i;
+ part->partbound->is_default = false;
+
+ elog(DEBUG1,"stransformPartitionAutoCreate HASH i %d MODULUS %d \n %s\n",
+ i, bound->modulus, nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+ }
+ else if (pg_strcasecmp(partspec->strategy, "list") == 0)
+ {
+
+ int n_list_parts = list_length(bound->listdatumsList);
+
+ if (bound->strategy != PARTITION_STRATEGY_LIST)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation((Node *) partspec))));
+
+ for (i = 0; i < n_list_parts; i++)
+ {
+ char *part_relname;
+ List *listdatums = (List *)
+ list_nth(bound->listdatumsList, i);
+
+ part_relname = psprintf("%s_%d", cxt->relation->relname, i);
+
+ part = copyObject(cxt->stmt);
+
+ part->relation = makeRangeVar(cxt->relation->schemaname,
+ part_relname, cxt->relation->location);
+ /* set table as a parent */
+ part->inhRelations = lappend(part->inhRelations, cxt->relation);
+
+ /* child table is not partitioned */
+ part->partspec = NULL;
+
+ /* Actual partbound generation is here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_LIST;
+ part->partbound->listdatums = list_copy(listdatums);
+ part->partbound->is_default = false;
+
+ elog(DEBUG1,"Debug transformPartitionAutoCreate LIST i %d \n %s\n",
+ i, nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+
+ if (bound->default_partition_rv)
+ {
+ part = copyObject(cxt->stmt);
+
+ part->relation = bound->default_partition_rv;
+ /* set table as a parent */
+ part->inhRelations = lappend(part->inhRelations, cxt->relation);
+
+ /* child table is not partitioned */
+ part->partspec = NULL;
+
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_LIST;
+ part->partbound->listdatums = NULL;
+ part->partbound->is_default = true;
+
+ elog(DEBUG1,"Debug transformPartitionAutoCreate LIST default partition \n %s\n",
+ nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+ }
+
+ /* Add statements to create each partition after we create parent table */
+ cxt->alist = list_concat(cxt->alist, partlist);
+}
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..ac6fcc029e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -478,6 +478,7 @@ typedef enum NodeTag
T_PartitionElem,
T_PartitionSpec,
T_PartitionBoundSpec,
+ T_PartitionBoundAutoSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 60c2f45466..ff7903a0dd 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -795,6 +795,9 @@ typedef struct PartitionSpec
* 'range') */
List *partParams; /* List of PartitionElems */
int location; /* token location, or -1 if unknown */
+
+ Node *autopart; /* PartitionBoundAutoSpec -
+ * spec to generate bounds automatically */
} PartitionSpec;
/* Internal codes for partitioning strategies */
@@ -829,6 +832,26 @@ struct PartitionBoundSpec
int location; /* token location, or -1 if unknown */
};
+/*
+ * PartitionBoundAutoSpec - a partition bound specification
+ * for auto generated partitions.
+ *
+ * This represents the rule of generating partition bounds
+ */
+struct PartitionBoundAutoSpec
+{
+ NodeTag type;
+
+ char strategy; /* see PARTITION_STRATEGY codes above */
+
+ /* Partitioning info for HASH strategy: */
+ int modulus;
+
+ /* Partitioning info for LIST strategy: */
+ List *listdatumsList; /* List of lists of Consts (or A_Consts in raw tree) */
+ RangeVar *default_partition_rv; /* Name of default list partition */
+};
+
/*
* PartitionRangeDatum - one of the values in a range partition bound
*
diff --git a/src/include/partitioning/partdefs.h b/src/include/partitioning/partdefs.h
index 6414e2c116..25ecfbd1de 100644
--- a/src/include/partitioning/partdefs.h
+++ b/src/include/partitioning/partdefs.h
@@ -19,6 +19,8 @@ typedef struct PartitionKeyData *PartitionKey;
typedef struct PartitionBoundSpec PartitionBoundSpec;
+typedef struct PartitionBoundAutoSpec PartitionBoundAutoSpec;
+
typedef struct PartitionDescData *PartitionDesc;
typedef struct PartitionDirectoryData *PartitionDirectory;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41dce69cc4..1776721b7b 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -1313,3 +1313,42 @@ Indexes:
"part_column_drop_1_10_expr_idx1" btree ((d = 2))
drop table part_column_drop;
+-- Auto generated partitions
+-- must fail because of wrong configuration
+CREATE TABLE tbl_hash_fail (i int) PARTITION BY HASH (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+ERROR: invalid bound specification for a hash partition
+LINE 1: CREATE TABLE tbl_hash_fail (i int) PARTITION BY HASH (i)
+ ^
+-- must fail because of wrong configuration
+CREATE TABLE tbl_list_fail (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (1, 3));
+ERROR: partition "tbl_list_fail_1" would overlap partition "tbl_list_fail_0"
+LINE 2: CONFIGURATION (values in (1, 2), (1, 3));
+ ^
+CREATE TABLE tbl_list (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+\d+ tbl_list
+ Partitioned table "public.tbl_list"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ i | integer | | | | plain | |
+Partition key: LIST (i)
+Partitions: tbl_list_0 FOR VALUES IN (1, 2),
+ tbl_list_1 FOR VALUES IN (3, 4),
+ tbl_default DEFAULT
+
+CREATE TABLE tbl_hash (i int) PARTITION BY HASH (i)
+CONFIGURATION (modulus 3);
+\d+ tbl_hash
+ Partitioned table "public.tbl_hash"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ i | integer | | | | plain | |
+Partition key: HASH (i)
+Partitions: tbl_hash_0 FOR VALUES WITH (modulus 3, remainder 0),
+ tbl_hash_1 FOR VALUES WITH (modulus 3, remainder 1),
+ tbl_hash_2 FOR VALUES WITH (modulus 3, remainder 2)
+
+DROP TABLE tbl_list;
+DROP TABLE tbl_hash;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 9b1adcb8ad..c82fca0a9a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -971,3 +971,26 @@ create table part_column_drop_1_10 partition of
\d part_column_drop
\d part_column_drop_1_10
drop table part_column_drop;
+
+-- Auto generated partitions
+
+-- must fail because of wrong configuration
+CREATE TABLE tbl_hash_fail (i int) PARTITION BY HASH (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+
+-- must fail because of wrong configuration
+CREATE TABLE tbl_list_fail (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (1, 3));
+
+CREATE TABLE tbl_list (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+
+\d+ tbl_list
+
+CREATE TABLE tbl_hash (i int) PARTITION BY HASH (i)
+CONFIGURATION (modulus 3);
+
+\d+ tbl_hash
+
+DROP TABLE tbl_list;
+DROP TABLE tbl_hash;
Hi Anastasia,
I tested the syntax with some basic commands and it works fine, regression
tests also pass.
Couple of comments:
1. The syntax used omits the { IMMEDIATE | DEFERRED} keywords suggested in
the earlier discussions. I think it is intuitive to include IMMEDIATE with
the current implementation
so that the syntax can be extended with a DEFERRED clause in future for
dynamic partitions.
CREATE TABLE tbl_lst (i int) PARTITION BY LIST (i)
CONFIGURATION (values in (1, 2), (3, 4) DEFAULT PARTITION tbl_default);
2. One suggestion for generation of partition names is to append a unique
id to
avoid conflicts.
3. Probably, here you mean to write list and hash instead of range and list
as
per the current state.
<para>
Range and list partitioning also support automatic creation of
partitions
with an optional <literal>CONFIGURATION</literal> clause.
</para>
4. Typo in default_part_name
+VALUES IN ( <replaceable
class="parameter">partition_bound_expr</replaceable> [, ...] ), [(
<replaceable class="parameter">partition_bound_expr</replaceable> [, ...]
)] [, ...] [DEFAULT PARTITION <replaceable
class="parameter">defailt_part_name</replaceable>]
+MODULUS <replaceable class="parameter">numeric_literal</replaceable>
Thank you,
Rahila Syed
On 30.09.2020 22:58, Rahila Syed wrote:
Hi Anastasia,
I tested the syntax with some basic commands and it works fine,
regression tests also pass.
Thank you for your review.
Couple of comments:
1. The syntax used omits the { IMMEDIATE | DEFERRED} keywords
suggested in
the earlier discussions. I think it is intuitive to include IMMEDIATE
with the current implementation
so that the syntax can be extended with a DEFERRED clause in future
for dynamic partitions.CREATE TABLE tbl_lst (i int) PARTITION BY LIST (i)
CONFIGURATION (values in (1, 2), (3, 4) DEFAULT PARTITION
tbl_default);
After some consideration, I decided that we don't actually need to
introduce IMMEDIATE | DEFERRED keyword. For hash and list partitions it
will always be immediate, as the number of partitions cannot change
after we initially set it. For range partitions, on the contrary, it
doesn't make much sense to make partitions immediately, because in many
use-cases one bound will be open.
2. One suggestion for generation of partition names is to append a
unique id to
avoid conflicts.
Can you please give an example of such a conflict? I agree that current
naming scheme is far from perfect, but I think that 'tablename'_partnum
provides unique name for each partition.
3. Probably, here you mean to write list and hash instead of range and
list as
per the current state.<para>
Range and list partitioning also support automatic creation
of partitions
with an optional <literal>CONFIGURATION</literal> clause.
</para>4. Typo in default_part_name
+VALUES IN ( <replaceable class="parameter">partition_bound_expr</replaceable> [, ...] ), [( <replaceable class="parameter">partition_bound_expr</replaceable> [, ...] )] [, ...] [DEFAULT PARTITION <replaceable class="parameter">defailt_part_name</replaceable>] +MODULUS <replaceable class="parameter">numeric_literal</replaceable>
Yes, you're right. I will fix these typos in next version of the patch.
Thank you,
Rahila Syed
--
Anastasia Lubennikova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Hi,
Couple of comments:
1. The syntax used omits the { IMMEDIATE | DEFERRED} keywords suggested in
the earlier discussions. I think it is intuitive to include IMMEDIATE with
the current implementation
so that the syntax can be extended with a DEFERRED clause in future for
dynamic partitions.CREATE TABLE tbl_lst (i int) PARTITION BY LIST (i)
CONFIGURATION (values in (1, 2), (3, 4) DEFAULT PARTITION tbl_default);After some consideration, I decided that we don't actually need to
introduce IMMEDIATE | DEFERRED keyword. For hash and list partitions it
will always be immediate, as the number of partitions cannot change after
we initially set it. For range partitions, on the contrary, it doesn't make
much sense to make partitions immediately, because in many use-cases one
bound will be open.
As per discussions on this thread:
/messages/by-id/alpine.DEB.2.21.1907150711080.22273@lancre
DEFERRED clause refers to creating partitions on the fly, while the data is
being inserted.
The number of partitions and partition bounds can be the same as specified
initially
during partitioned table creation, but the actual creation of
partitions can be deferred.
This seems like a potential extension to statically created partitions even
in the case of
hash and list partitions, as it won't involve moving any existing data.
2. One suggestion for generation of partition names is to append a
unique id to
avoid conflicts.
Can you please give an example of such a conflict? I agree that current
naming scheme is far from perfect, but I think that 'tablename'_partnum
provides unique name for each partition.Sorry for not being clear earlier, I mean the partition name
'tablename_partnum' can conflict with any existing table name.
As per current impemetation, if I do the following it results in the table
name conflict.
postgres=# create table tbl_test_5_1(i int);
CREATE TABLE
postgres=# CREATE TABLE tbl_test_5 (i int) PARTITION BY LIST((tbl_test_5))
CONFIGURATION (values in
('(1)'::tbl_test_5), ('(3)'::tbl_test_5) default partition tbl_default_5);
ERROR: relation "tbl_test_5_1" already exists
Thank you,
Rahila Syed
Show quoted text
Sorry for not being clear earlier, I mean the partition name
'tablename_partnum' can conflict with any existing table name.
As per current impemetation, if I do the following it results in the table
name conflict.postgres=# create table tbl_test_5_1(i int);
CREATE TABLE
postgres=# CREATE TABLE tbl_test_5 (i int) PARTITION BY LIST((tbl_test_5))CONFIGURATION (values in
('(1)'::tbl_test_5), ('(3)'::tbl_test_5) default partition tbl_default_5);
ERROR: relation "tbl_test_5_1" already exists
Basically, it's the same thing when you try to create two tables with the
same name. It is not specific to partition creation and common for every
case that using any defaults, they can conflict with something existing.
And in this case this conflict is explicitly processes as I see from output
message.
In fact in PG there are other places when names are done in default way
e.g. in aggregates regression test it is not surprise to find in PG13:
explain (costs off)
select min(f1), max(f1) from minmaxtest;
QUERY PLAN
---------------------------------------------------------------------------------------------
Result
InitPlan 1 (returns $0)
-> Limit
-> Merge Append
Sort Key: minmaxtest.f1
-> Index Only Scan using minmaxtesti on minmaxtest
minmaxtest_1
Index Cond: (f1 IS NOT NULL)
-> Index Only Scan using minmaxtest1i on minmaxtest1
minmaxtest_2
Index Cond: (f1 IS NOT NULL)
-> Index Only Scan Backward using minmaxtest2i on
minmaxtest2 minmaxtest_3
Index Cond: (f1 IS NOT NULL)
-> Index Only Scan using minmaxtest3i on minmaxtest3
minmaxtest_4
InitPlan 2 (returns $1)
-> Limit
-> Merge Append
Sort Key: minmaxtest_5.f1 DESC
-> Index Only Scan Backward using minmaxtesti on
minmaxtest minmaxtest_6
Index Cond: (f1 IS NOT NULL)
-> Index Only Scan Backward using minmaxtest1i on
minmaxtest1 minmaxtest_7
Index Cond: (f1 IS NOT NULL)
-> Index Only Scan using minmaxtest2i on minmaxtest2
minmaxtest_8
Index Cond: (f1 IS NOT NULL)
-> Index Only Scan Backward using minmaxtest3i on
minmaxtest3 minmaxtest_9
where minmaxtest_<number> are the temporary relations
and minmaxtest<number> are real partition names (last naming is unrelated
to first)
Overall I don't see much trouble in any form of automatic naming. But there
may be a convenience to provide fixed user-specified prefix to partition
names.
Thank you,
--
Best regards,
Pavel Borisov
Postgres Professional: http://postgrespro.com <http://www.postgrespro.com>
Hi, hackers!
I added some extra tests for different cases of use of automatic partition
creation.
v3-0002 can be applied on top of the original v2 patch for correct work
with some corner cases with constraints included in this test.
As for immediate/deferred I think that now only available now is immediate,
so using word IMMEDIATE seems a little bit redundant to me. We may
introduce this word together with adding DEFERRED option. However, my
opinion is not in strong opposition to both options. Оther opinions are
very much welcome!
--
Best regards,
Pavel Borisov
Postgres Professional: http://postgrespro.com <http://www.postgrespro.com>
Attachments:
v3-0002-Minor-fix.patchapplication/octet-stream; name=v3-0002-Minor-fix.patchDownload
From 02bfc239e053309692973afce59bfd539d8ba862 Mon Sep 17 00:00:00 2001
From: Pavel Borisov <pashkin.elfe@gmail.com>
Date: Tue, 6 Oct 2020 00:54:18 +0400
Subject: [PATCH v3 2/2] Minor fix
---
src/backend/parser/parse_utilcmd.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index b9ba712e36..654f9f1c75 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -4328,6 +4328,7 @@ transformPartitionAutoCreate(CreateStmtContext *cxt, PartitionSpec* partspec)
part = copyObject(cxt->stmt);
+ part->tableElts = NULL;
part->relation = makeRangeVar(cxt->relation->schemaname,
part_relname, cxt->relation->location);
/* set table as a parent */
@@ -4369,6 +4370,7 @@ transformPartitionAutoCreate(CreateStmtContext *cxt, PartitionSpec* partspec)
part_relname = psprintf("%s_%d", cxt->relation->relname, i);
part = copyObject(cxt->stmt);
+ part->tableElts = NULL;
part->relation = makeRangeVar(cxt->relation->schemaname,
part_relname, cxt->relation->location);
--
2.28.0
v3-0001-Tests-for-automatic-hash-list-partitions-creation.patchapplication/octet-stream; name=v3-0001-Tests-for-automatic-hash-list-partitions-creation.patchDownload
From 2435ab96a65a997beee283ee2029248b6cc303c4 Mon Sep 17 00:00:00 2001
From: Pavel Borisov <pashkin.elfe@gmail.com>
Date: Mon, 5 Oct 2020 12:40:43 +0400
Subject: [PATCH v3 1/2] Tests for automatic hash & list partitions creation
---
src/test/regress/expected/create_table.out | 372 +++++++++++++++++++++
src/test/regress/sql/create_table.sql | 203 +++++++++++
2 files changed, 575 insertions(+)
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1776721b7b..4ec8e0ec0e 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -1352,3 +1352,375 @@ Partitions: tbl_hash_0 FOR VALUES WITH (modulus 3, remainder 0),
DROP TABLE tbl_list;
DROP TABLE tbl_hash;
+CREATE TABLE list_parted (a int) PARTITION BY LIST (a) CONFIGURATION (values in ('1'), (2), (2+1), (null) DEFAULT PARTITION part_default);
+\d+ list_parted
+ Partitioned table "public.list_parted"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Partition key: LIST (a)
+Partitions: list_parted_0 FOR VALUES IN (1),
+ list_parted_1 FOR VALUES IN (2),
+ list_parted_2 FOR VALUES IN (3),
+ list_parted_3 FOR VALUES IN (NULL),
+ part_default DEFAULT
+
+-- forbidden expressions for partition bound with list partitioned table
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (somename));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename.somename));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (somename.somename));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (a));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (a));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(a)));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (sum(a)));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(somename)));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (sum(somename)));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(1)));
+ERROR: aggregate functions are not allowed in partition bound
+LINE 2: (VALUES IN (sum(1)));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((select 1)));
+ERROR: cannot use subquery in partition bound
+LINE 2: (VALUES IN ((select 1)));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (generate_series(4, 6)));
+ERROR: set-returning functions are not allowed in partition bound
+LINE 2: (VALUES IN (generate_series(4, 6)));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ('1' collate "POSIX"));
+ERROR: collations are not supported by type integer
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((1+1) collate "POSIX"));
+ERROR: collations are not supported by type integer
+LINE 2: (VALUES IN ((1+1) collate "POSIX"));
+ ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ());
+ERROR: syntax error at or near ")"
+LINE 2: (VALUES IN ());
+ ^
+-- trying to specify range for list partitioned table
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES FROM (1) TO (2));
+ERROR: syntax error at or near "FROM"
+LINE 2: (VALUES FROM (1) TO (2));
+ ^
+-- trying to specify modulus and remainder for list partitioned table
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(MODULUS 10);
+ERROR: invalid bound specification for a list partition
+LINE 1: CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) ...
+ ^
+-- check default partition cannot be created more than once
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR: partition "fail_default_part" conflicts with existing default partition "part_default"
+LINE 1: ...TE 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) CONFIGURATION (VALUES IN (1));
+ERROR: specified value cannot be cast to type boolean for column "a"
+LINE 1: ...a bool) PARTITION BY LIST (a) CONFIGURATION (VALUES IN (1));
+ ^
+-- specified literal can be cast, and the cast might not be immutable
+CREATE TABLE moneyp (a money) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10), ('11'), (to_char(12, '99')::int));
+DROP TABLE moneyp;
+-- cast is immutable
+CREATE TABLE bigintp (a bigint) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10));
+-- fails due to overlap:
+CREATE TABLE bigintp_overlap PARTITION OF bigintp FOR VALUES IN ('10');
+ERROR: partition "bigintp_overlap" would overlap partition "bigintp_0"
+LINE 1: ...E bigintp_overlap PARTITION OF bigintp FOR VALUES IN ('10');
+ ^
+DROP TABLE bigintp;
+CREATE TABLE hash_parted (a int) PARTITION BY HASH (a) CONFIGURATION (MODULUS 10);
+-- all remainder values are already belong to partitions
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODULUS 30, REMAINDER 3);
+ERROR: partition "fail_part" would overlap partition "hash_parted_3"
+LINE 1: ...BLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODU...
+ ^
+-- trying to specify range for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a', 1) TO ('z');
+ERROR: invalid bound specification for a hash partition
+LINE 1: ...BLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a',...
+ ^
+-- trying to specify list value for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000);
+ERROR: invalid bound specification for a hash partition
+LINE 1: ...BLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000);
+ ^
+-- trying to create default partition for the hash partitioned table
+CREATE TABLE fail_part (a int) PARTITION BY HASH (a) CONFIGURATION (MODULUS 10
+ DEFAULT hash_default);
+ERROR: syntax error at or near "DEFAULT"
+LINE 2: DEFAULT hash_default);
+ ^
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE fail_part (a int) CONFIGURATION (MODULUS 10);
+ERROR: syntax error at or near "CONFIGURATION"
+LINE 1: CREATE TABLE fail_part (a int) CONFIGURATION (MODULUS 10);
+ ^
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE fail_part (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ('a'));
+ERROR: cannot create a permanent relation as partition of temporary relation "fail_part"
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE fail_parted2 (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),(null) DEFAULT partition tbl_default);
+ERROR: partition "fail_parted2_2" would overlap partition "fail_parted2_0"
+LINE 2: (VALUES IN (null, 'z'),('a', 'b'),(null) DEFAULT partition t...
+ ^
+CREATE TABLE fail_parted2 (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),('b', 'c') DEFAULT partition tbl_default);
+ERROR: partition "fail_parted2_2" would overlap partition "fail_parted2_1"
+LINE 2: (VALUES IN (null, 'z'),('a', 'b'),('b', 'c') DEFAULT partiti...
+ ^
+-- check default partition overlap
+CREATE TABLE list_parted2 (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b') DEFAULT partition tbl_default);
+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 "tbl_default" would be violated by some row
+-- check schema propagation from parent
+CREATE TABLE parted (a text, b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0))
+PARTITION BY LIST (a) CONFIGURATION ( VALUES IN ('a','b'),('d') );
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute
+ WHERE attrelid = 'parted_1'::regclass and attnum > 0
+ ORDER BY attnum;
+ attname | attislocal | attinhcount
+---------+------------+-------------
+ a | f | 1
+ b | f | 1
+(2 rows)
+
+-- able to specify column default, column constraint, and table constraint
+-- first check the "column specified more than once" error
+CREATE TABLE part_e_fail PARTITION OF parted (
+ b NOT NULL,
+ b DEFAULT 1,
+ b CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('e');
+ERROR: column "b" specified more than once
+CREATE TABLE part_e PARTITION OF parted (
+ b NOT NULL DEFAULT 1,
+ CONSTRAINT check_a CHECK (length(a) > 0),
+ CONSTRAINT check_b CHECK (b >= 0)
+) FOR VALUES IN ('e');
+NOTICE: merging constraint "check_a" with inherited definition
+-- conislocal should be false for any merged constraints, true otherwise
+SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass ORDER BY conislocal, coninhcount;
+ conname | conislocal | coninhcount
+---------+------------+-------------
+ check_a | f | 1
+ check_b | t | 0
+(2 rows)
+
+-- check_a can not be dropped as it is inherited
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+ERROR: cannot drop inherited constraint "check_a" of relation "part_e"
+-- check_b can be dropped as it is local
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+-- Once check_b is added to the parent, it should be made non-local for part_b
+ALTER TABLE part_e ADD CONSTRAINT check_b CHECK (b >= 0);
+ALTER TABLE parted ADD CONSTRAINT check_b CHECK (b >= 0);
+NOTICE: merging constraint "check_b" with inherited definition
+SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+ conname | conislocal | coninhcount
+---------+------------+-------------
+ check_a | f | 1
+ check_b | f | 1
+(2 rows)
+
+-- Neither check_a nor check_b are droppable from part_b
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+ERROR: cannot drop inherited constraint "check_a" of relation "part_e"
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+ERROR: cannot drop inherited constraint "check_b" of relation "part_e"
+-- And dropping it from parted should leave no trace of them on part_e, unlike
+-- traditional inheritance where they will be left behind, because they would
+-- be local constraints.
+ALTER TABLE parted DROP CONSTRAINT check_a, DROP CONSTRAINT check_b;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+ conislocal | coninhcount
+------------+-------------
+(0 rows)
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY HASH(c);
+ERROR: column "c" named in partition key does not exist
+LINE 1: ...ARTITION OF parted FOR VALUES IN ('c') PARTITION BY HASH(c);
+ ^
+CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0) FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- check that NOT NULL and default value are inherited correctly
+create table parted_notnull_inh_test (a int default 1, b int not null default 0) partition by list (a) CONFIGURATION (VALUES IN (1));
+insert into parted_notnull_inh_test (b) values (null);
+ERROR: null value in column "b" of relation "parted_notnull_inh_test_0" violates not-null constraint
+DETAIL: Failing row contains (1, null).
+-- note that a's default is preserved
+\d parted_notnull_inh_test1
+drop table parted_notnull_inh_test;
+-- Partition bound in describe output
+\d+ part_e
+ Table "public.part_e"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | not null | 1 | plain | |
+Partition of: parted FOR VALUES IN ('e')
+Partition constraint: ((a IS NOT NULL) AND (a = 'e'::text))
+
+-- Both partition bound and partition key in describe output
+\d+ part_c
+ Partitioned table "public.part_c"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | not null | 0 | plain | |
+Partition of: parted FOR VALUES IN ('c')
+Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text))
+Partition key: RANGE (b)
+Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
+
+-- a level-2 partition's constraint will include the parent's expressions
+\d+ part_c_1_10
+ Table "public.part_c_1_10"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | not null | 0 | plain | |
+Partition of: part_c FOR VALUES FROM (1) TO (10)
+Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text) AND (b IS NOT NULL) AND (b >= 1) AND (b < 10))
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+ Partitioned table "public.parted"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | text | | |
+ b | integer | | not null | 0
+Partition key: LIST (a)
+Number of partitions: 4 (Use \d+ to list them.)
+
+\d hash_parted
+ Partitioned table "public.hash_parted"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition key: HASH (a)
+Number of partitions: 10 (Use \d+ to list them.)
+
+-- cleanup
+DROP TABLE parted;
+DROP TABLE list_parted, list_parted2;
+DROP TABLE hash_parted;
+-- list partitioning on array type column
+CREATE TABLE arrlp (a int[]) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ('{1}', '{2}'));
+\d+ arrlp_1
+DROP TABLE arrlp;
+-- partition on boolean column
+create table boolspart (a bool) partition by list (a) CONFIGURATION
+(values in (true), (false));
+\d+ boolspart
+ Partitioned table "public.boolspart"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | boolean | | | | plain | |
+Partition key: LIST (a)
+Partitions: boolspart_0 FOR VALUES IN (true),
+ boolspart_1 FOR VALUES IN (false)
+
+drop table boolspart;
+-- test using a volatile expression as partition bound
+create table volatile_partbound_test (partkey timestamp) partition by list (partkey) CONFIGURATION (values in ('1970-01-01 00:00:00+00'::timestamp, current_timestamp),('1982-01-25 00:00:00+00'::timestamp));
+drop table volatile_partbound_test;
+-- tests of column drop with partition tables and indexes using
+-- predicates and expressions.
+create table part_column_drop (useless_1 int, id int, useless_2 int, d int,
+ b int, useless_3 int) partition by hash (id) CONFIGURATION (modulus 3);
+alter table part_column_drop drop column useless_1;
+alter table part_column_drop drop column useless_2;
+alter table part_column_drop drop column useless_3;
+create index part_column_drop_b_pred on part_column_drop(b) where b = 1;
+create index part_column_drop_b_expr on part_column_drop((b = 1));
+create index part_column_drop_d_pred on part_column_drop(d) where d = 2;
+create index part_column_drop_d_expr on part_column_drop((d = 2));
+create index part_column_drop_d_1_pred on part_column_drop_1(d) where d = 2;
+create index part_column_drop_d_1_expr on part_column_drop_1((d = 2));
+\d part_column_drop
+ Partitioned table "public.part_column_drop"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ d | integer | | |
+ b | integer | | |
+Partition key: HASH (id)
+Indexes:
+ "part_column_drop_b_expr" btree ((b = 1))
+ "part_column_drop_b_pred" btree (b) WHERE b = 1
+ "part_column_drop_d_expr" btree ((d = 2))
+ "part_column_drop_d_pred" btree (d) WHERE d = 2
+Number of partitions: 3 (Use \d+ to list them.)
+
+\d part_column_drop_1
+ Table "public.part_column_drop_1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ d | integer | | |
+ b | integer | | |
+Partition of: part_column_drop FOR VALUES WITH (modulus 3, remainder 1)
+Indexes:
+ "part_column_drop_1_b_idx" btree (b) WHERE b = 1
+ "part_column_drop_1_d_idx" btree (d) WHERE d = 2
+ "part_column_drop_1_expr_idx" btree ((b = 1))
+ "part_column_drop_1_expr_idx1" btree ((d = 2))
+ "part_column_drop_d_1_expr" btree ((d = 2))
+ "part_column_drop_d_1_pred" btree (d) WHERE d = 2
+
+\d part_column_drop_2
+ Table "public.part_column_drop_2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ d | integer | | |
+ b | integer | | |
+Partition of: part_column_drop FOR VALUES WITH (modulus 3, remainder 2)
+Indexes:
+ "part_column_drop_2_b_idx" btree (b) WHERE b = 1
+ "part_column_drop_2_d_idx" btree (d) WHERE d = 2
+ "part_column_drop_2_expr_idx" btree ((b = 1))
+ "part_column_drop_2_expr_idx1" btree ((d = 2))
+
+\d part_column_drop_3
+drop table part_column_drop;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index c82fca0a9a..61e5091f2c 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -994,3 +994,206 @@ CONFIGURATION (modulus 3);
DROP TABLE tbl_list;
DROP TABLE tbl_hash;
+
+CREATE TABLE list_parted (a int) PARTITION BY LIST (a) CONFIGURATION (values in ('1'), (2), (2+1), (null) DEFAULT PARTITION part_default);
+\d+ list_parted
+
+-- forbidden expressions for partition bound with list partitioned table
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename.somename));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (a));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(a)));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(somename)));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(1)));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((select 1)));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (generate_series(4, 6)));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ('1' collate "POSIX"));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((1+1) collate "POSIX"));
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ());
+-- trying to specify range for list partitioned table
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES FROM (1) TO (2));
+-- trying to specify modulus and remainder for list partitioned table
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(MODULUS 10);
+
+-- check default partition cannot be created more than once
+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) CONFIGURATION (VALUES IN (1));
+
+-- specified literal can be cast, and the cast might not be immutable
+CREATE TABLE moneyp (a money) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10), ('11'), (to_char(12, '99')::int));
+DROP TABLE moneyp;
+
+-- cast is immutable
+CREATE TABLE bigintp (a bigint) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10));
+-- fails due to overlap:
+CREATE TABLE bigintp_overlap PARTITION OF bigintp FOR VALUES IN ('10');
+DROP TABLE bigintp;
+
+CREATE TABLE hash_parted (a int) PARTITION BY HASH (a) CONFIGURATION (MODULUS 10);
+-- all remainder values are already belong to partitions
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODULUS 30, REMAINDER 3);
+-- trying to specify range for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a', 1) TO ('z');
+-- trying to specify list value for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000);
+-- trying to create default partition for the hash partitioned table
+CREATE TABLE fail_part (a int) PARTITION BY HASH (a) CONFIGURATION (MODULUS 10
+ DEFAULT hash_default);
+
+-- cannot create as partition of a non-partitioned table
+CREATE TABLE fail_part (a int) CONFIGURATION (MODULUS 10);
+
+-- cannot create a permanent rel as partition of a temp rel
+CREATE TEMP TABLE fail_part (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ('a'));
+
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE fail_parted2 (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),(null) DEFAULT partition tbl_default);
+
+CREATE TABLE fail_parted2 (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),('b', 'c') DEFAULT partition tbl_default);
+
+-- check default partition overlap
+CREATE TABLE list_parted2 (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b') DEFAULT partition tbl_default);
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (a text, b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0))
+PARTITION BY LIST (a) CONFIGURATION ( VALUES IN ('a','b'),('d') );
+
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute
+ WHERE attrelid = 'parted_1'::regclass and attnum > 0
+ ORDER BY attnum;
+
+-- able to specify column default, column constraint, and table constraint
+
+-- first check the "column specified more than once" error
+CREATE TABLE part_e_fail PARTITION OF parted (
+ b NOT NULL,
+ b DEFAULT 1,
+ b CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('e');
+
+CREATE TABLE part_e PARTITION OF parted (
+ b NOT NULL DEFAULT 1,
+ CONSTRAINT check_a CHECK (length(a) > 0),
+ CONSTRAINT check_b CHECK (b >= 0)
+) FOR VALUES IN ('e');
+-- conislocal should be false for any merged constraints, true otherwise
+SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass ORDER BY conislocal, coninhcount;
+
+-- check_a can not be dropped as it is inherited
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+-- check_b can be dropped as it is local
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+
+-- Once check_b is added to the parent, it should be made non-local for part_b
+ALTER TABLE part_e ADD CONSTRAINT check_b CHECK (b >= 0);
+ALTER TABLE parted ADD CONSTRAINT check_b CHECK (b >= 0);
+SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+
+-- Neither check_a nor check_b are droppable from part_b
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+
+-- And dropping it from parted should leave no trace of them on part_e, unlike
+-- traditional inheritance where they will be left behind, because they would
+-- be local constraints.
+ALTER TABLE parted DROP CONSTRAINT check_a, DROP CONSTRAINT check_b;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY HASH(c);
+CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0) FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+
+-- check that NOT NULL and default value are inherited correctly
+create table parted_notnull_inh_test (a int default 1, b int not null default 0) partition by list (a) CONFIGURATION (VALUES IN (1));
+insert into parted_notnull_inh_test (b) values (null);
+-- note that a's default is preserved
+\d parted_notnull_inh_test1
+drop table parted_notnull_inh_test;
+
+-- Partition bound in describe output
+\d+ part_e
+
+-- Both partition bound and partition key in describe output
+\d+ part_c
+
+-- a level-2 partition's constraint will include the parent's expressions
+\d+ part_c_1_10
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+\d hash_parted
+
+-- cleanup
+DROP TABLE parted;
+DROP TABLE list_parted, list_parted2;
+DROP TABLE hash_parted;
+-- list partitioning on array type column
+CREATE TABLE arrlp (a int[]) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ('{1}', '{2}'));
+\d+ arrlp_1
+DROP TABLE arrlp;
+
+-- partition on boolean column
+create table boolspart (a bool) partition by list (a) CONFIGURATION
+(values in (true), (false));
+\d+ boolspart
+drop table boolspart;
+
+-- test using a volatile expression as partition bound
+create table volatile_partbound_test (partkey timestamp) partition by list (partkey) CONFIGURATION (values in ('1970-01-01 00:00:00+00'::timestamp, current_timestamp),('1982-01-25 00:00:00+00'::timestamp));
+drop table volatile_partbound_test;
+
+-- tests of column drop with partition tables and indexes using
+-- predicates and expressions.
+create table part_column_drop (useless_1 int, id int, useless_2 int, d int,
+ b int, useless_3 int) partition by hash (id) CONFIGURATION (modulus 3);
+alter table part_column_drop drop column useless_1;
+alter table part_column_drop drop column useless_2;
+alter table part_column_drop drop column useless_3;
+create index part_column_drop_b_pred on part_column_drop(b) where b = 1;
+create index part_column_drop_b_expr on part_column_drop((b = 1));
+create index part_column_drop_d_pred on part_column_drop(d) where d = 2;
+create index part_column_drop_d_expr on part_column_drop((d = 2));
+create index part_column_drop_d_1_pred on part_column_drop_1(d) where d = 2;
+create index part_column_drop_d_1_expr on part_column_drop_1((d = 2));
+
+\d part_column_drop
+\d part_column_drop_1
+\d part_column_drop_2
+\d part_column_drop_3
+drop table part_column_drop;
--
2.28.0
On 06.10.2020 00:21, Pavel Borisov wrote:
Hi, hackers!
I added some extra tests for different cases of use of automatic
partition creation.
v3-0002 can be applied on top of the original v2 patch for correct
work with some corner cases with constraints included in this test.
Thank you for the tests. I've added them and the fix into the patch.
I also noticed, that some table parameters, such as persistence were not
promoted to auto generated partitions. This is fixed now. The test cases
for temp and unlogged auto partitioned tables are updated respectively.
Besides, I slightly refactored the code and fixed documentation typos,
that were reported by Rahila.
With my recent changes, one test statement, that you've added as
failing, works.
CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
(VALUES IN ('1' collate "POSIX"));
It simply ignores collate POSIX part and creates a table with following
structure:
Partitioned table "public.list_parted_fail"
Column | Type | Collation | Nullable | Default | Storage | Stats
target | Description
--------+---------+-----------+----------+---------+---------+--------------+-------------
a | integer | | | | plain
| |
Partition key: LIST (a)
Partitions: list_parted_fail_0 FOR VALUES IN (1)
Do you think that it is a bug? For now, I removed this statement from
tests just to calm down the CI.
--
Anastasia Lubennikova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
auto_part_hash_list_v3.patchtext/x-patch; charset=UTF-8; name=auto_part_hash_list_v3.patchDownload
commit 4a387cfbd43e93b2c2e363307fb9c9ca53c3f56e
Author: anastasia <a.lubennikova@postgrespro.ru>
Date: Tue Oct 6 20:23:22 2020 +0300
Auto generated HASH and LIST partitions.
New syntax:
CREATE TABLE tbl_hash (i int) PARTITION BY HASH (i)
CONFIGURATION (modulus 3);
CREATE TABLE tbl_list (i int) PARTITION BY LIST (i)
CONFIGURATION (values in (1, 2), (3, 4) DEFAULT PARTITION tbl_default);
With tests and documentation draft
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 28f844071b..4501e81bb5 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -29,6 +29,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ]
[ USING <replaceable class="parameter">method</replaceable> ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -41,6 +42,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
) ]
[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ]
[ USING <replaceable class="parameter">method</replaceable> ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -53,6 +55,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
) ] { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT }
[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ]
[ USING <replaceable class="parameter">method</replaceable> ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -96,6 +99,11 @@ FROM ( { <replaceable class="parameter">partition_bound_expr</replaceable> | MIN
TO ( { <replaceable class="parameter">partition_bound_expr</replaceable> | MINVALUE | MAXVALUE } [, ...] ) |
WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REMAINDER <replaceable class="parameter">numeric_literal</replaceable> )
+<phrase>and <replaceable class="parameter">partition_bound_auto_spec</replaceable> is:</phrase>
+
+VALUES IN ( <replaceable class="parameter">partition_bound_expr</replaceable> [, ...] ), [( <replaceable class="parameter">partition_bound_expr</replaceable> [, ...] )] [, ...] [DEFAULT PARTITION <replaceable class="parameter">default_part_name</replaceable>]
+MODULUS <replaceable class="parameter">numeric_literal</replaceable>
+
<phrase><replaceable class="parameter">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
[ INCLUDE ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ]
@@ -383,6 +391,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
however, you can define these constraints on individual partitions.
</para>
+ <para>
+ Hash and list partitioning also support automatic creation of partitions
+ with an optional <literal>CONFIGURATION</literal> clause.
+ </para>
+
<para>
See <xref linkend="ddl-partitioning"/> for more discussion on table
partitioning.
@@ -391,6 +404,42 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ] </literal></term>
+ <listitem>
+ <para>
+ The optional <literal>CONFIGURATION</literal> clause used together
+ with <literal>PARTITION BY</literal> specifies a rule of generating bounds
+ for partitions of the partitioned table. All partitions are created automatically
+ along with the parent table.
+
+ Any indexes, constraints and user-defined row-level triggers that exist
+ in the parent table are cloned on the new partitions. When using this clause,
+ <literal> CREATE TABLE </literal> statement can only contain clauses, that are
+ applicable to both relation kinds: partitioned table and regular partition tables.
+ All tables created by the statement will use same parameters, such as
+ relation persistence.
+ </para>
+
+ <para>
+ The <replaceable class="parameter">partition_bound_auto_spec</replaceable>
+ must correspond to the partitioning method and partition key of the
+ parent table, and must not overlap with any existing partition of that
+ parent. The form with <literal>VALUES IN</literal> is used for list partitioning
+ and the form with <literal>MODULUS</literal> is used for hash partitioning.
+ List partitioning can also provide a default partition using
+ <literal>DEFAULT PARTITION</literal>.
+ </para>
+
+ <para>
+ Automatic range partitioning is not supported yet.
+ </para>
+
+
+
+ </listitem>
+ </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> | DEFAULT }</literal></term>
<listitem>
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 0409a40b82..6893fa5495 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4628,6 +4628,7 @@ _copyPartitionSpec(const PartitionSpec *from)
COPY_STRING_FIELD(strategy);
COPY_NODE_FIELD(partParams);
+ COPY_NODE_FIELD(autopart);
COPY_LOCATION_FIELD(location);
return newnode;
@@ -4650,6 +4651,19 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
return newnode;
}
+static PartitionBoundAutoSpec *
+_copyPartitionBoundAutoSpec(const PartitionBoundAutoSpec *from)
+{
+ PartitionBoundAutoSpec *newnode = makeNode(PartitionBoundAutoSpec);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_SCALAR_FIELD(modulus);
+ COPY_NODE_FIELD(listdatumsList);
+ COPY_NODE_FIELD(default_partition_rv);
+
+ return newnode;
+}
+
static PartitionRangeDatum *
_copyPartitionRangeDatum(const PartitionRangeDatum *from)
{
@@ -5699,6 +5713,9 @@ copyObjectImpl(const void *from)
case T_PartitionBoundSpec:
retval = _copyPartitionBoundSpec(from);
break;
+ case T_PartitionBoundAutoSpec:
+ retval = _copyPartitionBoundAutoSpec(from);
+ break;
case T_PartitionRangeDatum:
retval = _copyPartitionRangeDatum(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2d1b987bf..ccba2471de 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2897,6 +2897,7 @@ _equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
COMPARE_STRING_FIELD(strategy);
COMPARE_NODE_FIELD(partParams);
COMPARE_LOCATION_FIELD(location);
+ COMPARE_NODE_FIELD(autopart);
return true;
}
@@ -2916,6 +2917,19 @@ _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *
return true;
}
+static bool
+_equalPartitionBoundAutoSpec(const PartitionBoundAutoSpec *a,
+ const PartitionBoundAutoSpec *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_SCALAR_FIELD(modulus);
+ COMPARE_NODE_FIELD(listdatumsList);
+ COMPARE_NODE_FIELD(default_partition_rv);
+
+ return true;
+}
+
+
static bool
_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
{
@@ -3751,6 +3765,9 @@ equal(const void *a, const void *b)
case T_PartitionBoundSpec:
retval = _equalPartitionBoundSpec(a, b);
break;
+ case T_PartitionBoundAutoSpec:
+ retval = _equalPartitionBoundAutoSpec(a, b);
+ break;
case T_PartitionRangeDatum:
retval = _equalPartitionRangeDatum(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index f0386480ab..95bf90a099 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3644,6 +3644,7 @@ _outPartitionSpec(StringInfo str, const PartitionSpec *node)
WRITE_STRING_FIELD(strategy);
WRITE_NODE_FIELD(partParams);
+ WRITE_NODE_FIELD(autopart);
WRITE_LOCATION_FIELD(location);
}
@@ -3662,6 +3663,18 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionBoundAutoSpec(StringInfo str, const PartitionBoundAutoSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBOUNDAUTOSPEC");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_INT_FIELD(modulus);
+ WRITE_NODE_FIELD(listdatumsList);
+ WRITE_NODE_FIELD(default_partition_rv);
+
+}
+
static void
_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
{
@@ -4335,6 +4348,9 @@ outNode(StringInfo str, const void *obj)
case T_PartitionBoundSpec:
_outPartitionBoundSpec(str, obj);
break;
+ case T_PartitionBoundAutoSpec:
+ _outPartitionBoundAutoSpec(str, obj);
+ break;
case T_PartitionRangeDatum:
_outPartitionRangeDatum(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 42050ab719..703b413f93 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2602,6 +2602,19 @@ _readPartitionBoundSpec(void)
READ_DONE();
}
+static PartitionBoundAutoSpec *
+_readPartitionBoundAutoSpec(void)
+{
+ READ_LOCALS(PartitionBoundAutoSpec);
+
+ READ_CHAR_FIELD(strategy);
+ READ_INT_FIELD(modulus);
+ READ_NODE_FIELD(listdatumsList);
+ READ_NODE_FIELD(default_partition_rv);
+
+ READ_DONE();
+}
+
/*
* _readPartitionRangeDatum
*/
@@ -2880,6 +2893,8 @@ parseNodeString(void)
return_value = _readPartitionBoundSpec();
else if (MATCH("PARTITIONRANGEDATUM", 19))
return_value = _readPartitionRangeDatum();
+ else if (MATCH("PARTITIONBOUNDAUTOSPEC", 22))
+ return_value = _readPartitionBoundAutoSpec();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0d101d8171..e655ff858c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -249,6 +249,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionElem *partelem;
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
+ PartitionBoundAutoSpec *partboundautospec;
RoleSpec *rolespec;
struct SelectLimit *selectlimit;
}
@@ -601,6 +602,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
+%type <partboundautospec> OptPartitionBoundAutoSpec values_in_clause p_desc
+%type <range> opt_default_partition_clause
+
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
* They must be listed first so that their numeric codes do not depend on
@@ -3904,14 +3908,14 @@ OptPartitionSpec: PartitionSpec { $$ = $1; }
| /*EMPTY*/ { $$ = NULL; }
;
-PartitionSpec: PARTITION BY ColId '(' part_params ')'
+PartitionSpec: PARTITION BY ColId '(' part_params ')' OptPartitionBoundAutoSpec
{
PartitionSpec *n = makeNode(PartitionSpec);
n->strategy = $3;
n->partParams = $5;
n->location = @1;
-
+ n->autopart = (Node *) $7;
$$ = n;
}
;
@@ -3955,6 +3959,80 @@ part_elem: ColId opt_collate opt_class
}
;
+OptPartitionBoundAutoSpec:
+ CONFIGURATION '(' p_desc ')'
+ {
+ $$ = $3;
+ }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+p_desc:
+ hash_partbound
+ {
+ ListCell *lc;
+ PartitionBoundAutoSpec *n = makeNode(PartitionBoundAutoSpec);
+
+ n->modulus = -1;
+
+ foreach (lc, $1)
+ {
+ DefElem *opt = lfirst_node(DefElem, lc);
+
+ if (strcmp(opt->defname, "modulus") == 0)
+ {
+ n->strategy = PARTITION_STRATEGY_HASH;
+ if (n->modulus != -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("modulus for hash partition provided more than once"),
+ parser_errposition(opt->location)));
+ n->modulus = defGetInt32(opt);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized auto partition bound specification \"%s\"",
+ opt->defname),
+ parser_errposition(opt->location)));
+ }
+
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ | values_in_clause opt_default_partition_clause
+ {
+ PartitionBoundAutoSpec *n = $1;
+ n->default_partition_rv = $2;
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ ;
+
+values_in_clause:
+ VALUES IN_P '(' expr_list ')'
+ {
+ PartitionBoundAutoSpec *n = makeNode(PartitionBoundAutoSpec);
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatumsList = list_make1($4);
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ | values_in_clause ',' '(' expr_list ')'
+ {
+ PartitionBoundAutoSpec *n = (PartitionBoundAutoSpec *) $1;
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatumsList = lappend(n->listdatumsList, $4);
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ ;
+
+opt_default_partition_clause:
+ DEFAULT PARTITION qualified_name
+ {
+ $$ = $3;
+ }
+ | /* EMPTY */
+ { $$ = NULL; }
+ ;
+
table_access_method_clause:
USING name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0dc03dd984..ba5c694cdc 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -75,6 +75,7 @@
/* State shared by transformCreateStmt and its subroutines */
typedef struct
{
+ CreateStmt *stmt; /* initial statement */
ParseState *pstate; /* overall parser state */
const char *stmtType; /* "CREATE [FOREIGN] TABLE" or "ALTER TABLE" */
RangeVar *relation; /* relation to create */
@@ -145,6 +146,8 @@ static Const *transformPartitionBoundValue(ParseState *pstate, Node *con,
const char *colName, Oid colType, int32 colTypmod,
Oid partCollation);
+static CreateStmt* initAutoPartitionCreateStmt(CreateStmtContext *cxt, char *part_relname);
+static void transformPartitionAutoCreate(CreateStmtContext *cxt, PartitionSpec* partspec);
/*
* transformCreateStmt -
@@ -235,6 +238,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.stmtType = "CREATE TABLE";
cxt.isforeign = false;
}
+ cxt.stmt = stmt;
cxt.relation = stmt->relation;
cxt.rel = NULL;
cxt.inhRelations = stmt->inhRelations;
@@ -324,6 +328,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
transformExtendedStatistics(&cxt);
+ /* Process partition definitions */
+ if (stmt->partspec && stmt->partspec->autopart)
+ transformPartitionAutoCreate(&cxt, stmt->partspec);
+
/*
* Output results.
*/
@@ -4236,3 +4244,144 @@ transformPartitionBoundValue(ParseState *pstate, Node *val,
return (Const *) value;
}
+
+
+/* init basic fields of auto generated partition */
+static CreateStmt*
+initAutoPartitionCreateStmt(CreateStmtContext *cxt, char *part_relname)
+{
+ CreateStmt *part;
+
+ part = copyObject(cxt->stmt);
+
+ part->relation = makeRangeVar(cxt->relation->schemaname,
+ part_relname, cxt->relation->location);
+
+ /* inherit persistence from parent relation */
+ part->relation->relpersistence = cxt->relation->relpersistence;
+ /* set parent table as a parent */
+ part->inhRelations = lappend(part->inhRelations, cxt->relation);
+
+ /*
+ * child table is not partitioned itself, at least now
+ * while we do not support multilevel auto partitioning
+ */
+ part->partspec = NULL;
+
+ /*
+ * Partition doesn't need a list of column definitions and constraints.
+ * They will be inherited from parent.
+ */
+ part->tableElts = NIL;
+ part->constraints = NIL;
+
+ return part;
+}
+
+/*
+ * Transform configuration into a set of partition bounds.
+ * Generate extra statements to create partition tables.
+ */
+static void
+transformPartitionAutoCreate(CreateStmtContext *cxt, PartitionSpec* partspec)
+{
+ CreateStmt *part;
+ List *partlist = NIL;
+ int i = 0;
+ PartitionBoundAutoSpec *bound = (PartitionBoundAutoSpec *) partspec->autopart;
+
+ elog(DEBUG1, "transformPartitionAutoCreate \n %s \n ", nodeToString(bound));
+
+ /*
+ * Generate regular partbounds based on autopart rule.
+ * and form create table statements from these partbounds
+ */
+ if (pg_strcasecmp(partspec->strategy, "hash") == 0)
+ {
+ if (bound->strategy != PARTITION_STRATEGY_HASH)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a hash partition"),
+ parser_errposition(cxt->pstate, exprLocation((Node *) partspec))));
+
+ for (i = 0; i < bound->modulus; i++)
+ {
+ char *part_relname;
+
+ /*
+ * Generate partition name in the format:
+ * $relname_$partnum
+ * All checks of name validity will be made afterwards in DefineRelation()
+ */
+ part_relname = psprintf("%s_%d", cxt->relation->relname, i);
+ part = initAutoPartitionCreateStmt(cxt, part_relname);
+
+ /* Actual partbound generation happens here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_HASH;
+ part->partbound->modulus = bound->modulus;
+ part->partbound->remainder = i;
+ part->partbound->is_default = false;
+
+ elog(DEBUG1,"stransformPartitionAutoCreate HASH i %d MODULUS %d \n %s\n",
+ i, bound->modulus, nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+ }
+ else if (pg_strcasecmp(partspec->strategy, "list") == 0)
+ {
+
+ int n_list_parts = list_length(bound->listdatumsList);
+
+ if (bound->strategy != PARTITION_STRATEGY_LIST)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation((Node *) partspec))));
+
+ for (i = 0; i < n_list_parts; i++)
+ {
+ char *part_relname;
+ List *listdatums = (List *)
+ list_nth(bound->listdatumsList, i);
+
+ part_relname = psprintf("%s_%d", cxt->relation->relname, i);
+ part = initAutoPartitionCreateStmt(cxt, part_relname);
+
+ /* Actual partbound generation happens here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_LIST;
+ part->partbound->listdatums = list_copy(listdatums);
+ part->partbound->is_default = false;
+
+ elog(DEBUG1,"Debug transformPartitionAutoCreate LIST i %d \n %s\n",
+ i, nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+
+ if (bound->default_partition_rv)
+ {
+
+ char *part_relname = bound->default_partition_rv->relname;
+ part = initAutoPartitionCreateStmt(cxt, part_relname);
+
+ /* TODO: Should we use fields from default_partition_rv, other than relname? */
+
+ /* Actual partbound generation happens here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_LIST;
+ part->partbound->listdatums = NULL;
+ part->partbound->is_default = true;
+
+ elog(DEBUG1,"Debug transformPartitionAutoCreate LIST default partition \n %s\n",
+ nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+ }
+
+ /* Add statements to create each partition after we create parent table */
+ cxt->alist = list_concat(cxt->alist, partlist);
+}
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7ddd8c011b..4c6bd005ea 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -477,6 +477,7 @@ typedef enum NodeTag
T_PartitionElem,
T_PartitionSpec,
T_PartitionBoundSpec,
+ T_PartitionBoundAutoSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 60c2f45466..ff7903a0dd 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -795,6 +795,9 @@ typedef struct PartitionSpec
* 'range') */
List *partParams; /* List of PartitionElems */
int location; /* token location, or -1 if unknown */
+
+ Node *autopart; /* PartitionBoundAutoSpec -
+ * spec to generate bounds automatically */
} PartitionSpec;
/* Internal codes for partitioning strategies */
@@ -829,6 +832,26 @@ struct PartitionBoundSpec
int location; /* token location, or -1 if unknown */
};
+/*
+ * PartitionBoundAutoSpec - a partition bound specification
+ * for auto generated partitions.
+ *
+ * This represents the rule of generating partition bounds
+ */
+struct PartitionBoundAutoSpec
+{
+ NodeTag type;
+
+ char strategy; /* see PARTITION_STRATEGY codes above */
+
+ /* Partitioning info for HASH strategy: */
+ int modulus;
+
+ /* Partitioning info for LIST strategy: */
+ List *listdatumsList; /* List of lists of Consts (or A_Consts in raw tree) */
+ RangeVar *default_partition_rv; /* Name of default list partition */
+};
+
/*
* PartitionRangeDatum - one of the values in a range partition bound
*
diff --git a/src/include/partitioning/partdefs.h b/src/include/partitioning/partdefs.h
index 6414e2c116..25ecfbd1de 100644
--- a/src/include/partitioning/partdefs.h
+++ b/src/include/partitioning/partdefs.h
@@ -19,6 +19,8 @@ typedef struct PartitionKeyData *PartitionKey;
typedef struct PartitionBoundSpec PartitionBoundSpec;
+typedef struct PartitionBoundAutoSpec PartitionBoundAutoSpec;
+
typedef struct PartitionDescData *PartitionDesc;
typedef struct PartitionDirectoryData *PartitionDirectory;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1fc266dd65..2bca260195 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -1307,3 +1307,437 @@ Indexes:
"part_column_drop_1_10_expr_idx1" btree ((d = 2))
drop table part_column_drop;
+-- Auto generated partitions
+-- must fail because of wrong configuration
+CREATE TABLE tbl_hash_fail (i int) PARTITION BY HASH (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+ERROR: invalid bound specification for a hash partition
+LINE 1: CREATE TABLE tbl_hash_fail (i int) PARTITION BY HASH (i)
+ ^
+-- must fail because of wrong configuration
+CREATE TABLE tbl_list_fail (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (1, 3));
+ERROR: partition "tbl_list_fail_1" would overlap partition "tbl_list_fail_0"
+LINE 2: CONFIGURATION (values in (1, 2), (1, 3));
+ ^
+CREATE TABLE tbl_list (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+\d+ tbl_list
+ Partitioned table "public.tbl_list"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ i | integer | | | | plain | |
+Partition key: LIST (i)
+Partitions: tbl_list_0 FOR VALUES IN (1, 2),
+ tbl_list_1 FOR VALUES IN (3, 4),
+ tbl_default DEFAULT
+
+CREATE TABLE tbl_hash (i int) PARTITION BY HASH (i)
+CONFIGURATION (modulus 3);
+\d+ tbl_hash
+ Partitioned table "public.tbl_hash"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ i | integer | | | | plain | |
+Partition key: HASH (i)
+Partitions: tbl_hash_0 FOR VALUES WITH (modulus 3, remainder 0),
+ tbl_hash_1 FOR VALUES WITH (modulus 3, remainder 1),
+ tbl_hash_2 FOR VALUES WITH (modulus 3, remainder 2)
+
+DROP TABLE tbl_list;
+DROP TABLE tbl_hash;
+-- forbidden expressions for partition bound with list partitioned table
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (somename));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename.somename));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (somename.somename));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (a));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (a));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(a)));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (sum(a)));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(somename)));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (sum(somename)));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(1)));
+ERROR: aggregate functions are not allowed in partition bound
+LINE 2: (VALUES IN (sum(1)));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((select 1)));
+ERROR: cannot use subquery in partition bound
+LINE 2: (VALUES IN ((select 1)));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (generate_series(4, 6)));
+ERROR: set-returning functions are not allowed in partition bound
+LINE 2: (VALUES IN (generate_series(4, 6)));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((1+1) collate "POSIX"));
+ERROR: collations are not supported by type integer
+LINE 2: (VALUES IN ((1+1) collate "POSIX"));
+ ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ());
+ERROR: syntax error at or near ")"
+LINE 2: (VALUES IN ());
+ ^
+-- trying to specify range for list partitioned table
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES FROM (1) TO (2));
+ERROR: syntax error at or near "FROM"
+LINE 2: (VALUES FROM (1) TO (2));
+ ^
+-- trying to specify modulus and remainder for list partitioned table
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(MODULUS 10);
+ERROR: invalid bound specification for a list partition
+LINE 1: CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) ...
+ ^
+-- must succeed
+CREATE TABLE list_parted (a int)
+PARTITION BY LIST (a)
+CONFIGURATION (values in ('1'), (2), (2+1), (null) DEFAULT PARTITION part_default);
+\d+ list_parted
+ Partitioned table "public.list_parted"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Partition key: LIST (a)
+Partitions: list_parted_0 FOR VALUES IN (1),
+ list_parted_1 FOR VALUES IN (2),
+ list_parted_2 FOR VALUES IN (3),
+ list_parted_3 FOR VALUES IN (NULL),
+ part_default DEFAULT
+
+-- check default partition cannot be created more than once
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR: partition "fail_default_part" conflicts with existing default partition "part_default"
+LINE 1: ...TE 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) CONFIGURATION (VALUES IN (1));
+ERROR: specified value cannot be cast to type boolean for column "a"
+LINE 1: ...a bool) PARTITION BY LIST (a) CONFIGURATION (VALUES IN (1));
+ ^
+-- specified literal can be cast, and the cast might not be immutable
+CREATE TABLE moneyp (a money) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10), ('11'), (to_char(12, '99')::int));
+DROP TABLE moneyp;
+-- cast is immutable
+CREATE TABLE bigintp (a bigint) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10));
+-- fails due to overlap:
+CREATE TABLE bigintp_overlap PARTITION OF bigintp FOR VALUES IN ('10');
+ERROR: partition "bigintp_overlap" would overlap partition "bigintp_0"
+LINE 1: ...E bigintp_overlap PARTITION OF bigintp FOR VALUES IN ('10');
+ ^
+DROP TABLE bigintp;
+CREATE TABLE hash_parted (a int) PARTITION BY HASH (a) CONFIGURATION (MODULUS 10);
+-- all remainder values are already belong to partitions
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODULUS 30, REMAINDER 3);
+ERROR: partition "fail_part" would overlap partition "hash_parted_3"
+LINE 1: ...BLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODU...
+ ^
+-- trying to specify range for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a', 1) TO ('z');
+ERROR: invalid bound specification for a hash partition
+LINE 1: ...BLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a',...
+ ^
+-- trying to specify list value for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000);
+ERROR: invalid bound specification for a hash partition
+LINE 1: ...BLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000);
+ ^
+-- trying to create default partition for the hash partitioned table
+CREATE TABLE fail_part (a int) PARTITION BY HASH (a) CONFIGURATION (MODULUS 10
+ DEFAULT hash_default);
+ERROR: syntax error at or near "DEFAULT"
+LINE 2: DEFAULT hash_default);
+ ^
+-- cannot create auto partition of a non-partitioned table
+CREATE TABLE fail_part (a int) CONFIGURATION (MODULUS 10);
+ERROR: syntax error at or near "CONFIGURATION"
+LINE 1: CREATE TABLE fail_part (a int) CONFIGURATION (MODULUS 10);
+ ^
+-- partition table inherits relation persistence setting from parent
+CREATE TEMP TABLE temp_parted (a char) PARTITION BY LIST (a)
+CONFIGURATION (VALUES IN ('a') DEFAULT PARTITION temp_parted_default);
+\d+ temp_parted
+ Partitioned table "pg_temp_3.temp_parted"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+--------------+-----------+----------+---------+----------+--------------+-------------
+ a | character(1) | | | | extended | |
+Partition key: LIST (a)
+Partitions: temp_parted_0 FOR VALUES IN ('a'),
+ temp_parted_default DEFAULT
+
+-- partition table inherits relation persistence setting from parent
+CREATE UNLOGGED TABLE unlogged_parted (a char) PARTITION BY LIST (a)
+CONFIGURATION (VALUES IN ('a') DEFAULT PARTITION unlogged_parted_default);
+\d+ unlogged_parted
+ Unlogged partitioned table "public.unlogged_parted"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+--------------+-----------+----------+---------+----------+--------------+-------------
+ a | character(1) | | | | extended | |
+Partition key: LIST (a)
+Partitions: unlogged_parted_0 FOR VALUES IN ('a'),
+ unlogged_parted_default DEFAULT
+
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE fail_parted2 (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),(null) DEFAULT partition tbl_default);
+ERROR: partition "fail_parted2_2" would overlap partition "fail_parted2_0"
+LINE 2: (VALUES IN (null, 'z'),('a', 'b'),(null) DEFAULT partition t...
+ ^
+CREATE TABLE fail_parted2 (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),('b', 'c') DEFAULT partition tbl_default);
+ERROR: partition "fail_parted2_2" would overlap partition "fail_parted2_1"
+LINE 2: (VALUES IN (null, 'z'),('a', 'b'),('b', 'c') DEFAULT partiti...
+ ^
+-- check default partition overlap
+CREATE TABLE list_parted2 (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b') DEFAULT partition tbl_default);
+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 "tbl_default" would be violated by some row
+-- check schema propagation from parent
+CREATE TABLE parted (a text, b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0))
+PARTITION BY LIST (a) CONFIGURATION ( VALUES IN ('a','b'),('d') );
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute
+ WHERE attrelid = 'parted_1'::regclass and attnum > 0
+ ORDER BY attnum;
+ attname | attislocal | attinhcount
+---------+------------+-------------
+ a | f | 1
+ b | f | 1
+(2 rows)
+
+-- able to specify column default, column constraint, and table constraint
+-- first check the "column specified more than once" error
+CREATE TABLE part_e_fail PARTITION OF parted (
+ b NOT NULL,
+ b DEFAULT 1,
+ b CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('e');
+ERROR: column "b" specified more than once
+CREATE TABLE part_e PARTITION OF parted (
+ b NOT NULL DEFAULT 1,
+ CONSTRAINT check_a CHECK (length(a) > 0),
+ CONSTRAINT check_b CHECK (b >= 0)
+) FOR VALUES IN ('e');
+NOTICE: merging constraint "check_a" with inherited definition
+-- conislocal should be false for any merged constraints, true otherwise
+SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass ORDER BY conislocal, coninhcount;
+ conname | conislocal | coninhcount
+---------+------------+-------------
+ check_a | f | 1
+ check_b | t | 0
+(2 rows)
+
+-- check_a can not be dropped as it is inherited
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+ERROR: cannot drop inherited constraint "check_a" of relation "part_e"
+-- check_b can be dropped as it is local
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+-- Once check_b is added to the parent, it should be made non-local for part_b
+ALTER TABLE part_e ADD CONSTRAINT check_b CHECK (b >= 0);
+ALTER TABLE parted ADD CONSTRAINT check_b CHECK (b >= 0);
+NOTICE: merging constraint "check_b" with inherited definition
+SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+ conname | conislocal | coninhcount
+---------+------------+-------------
+ check_a | f | 1
+ check_b | f | 1
+(2 rows)
+
+-- Neither check_a nor check_b are droppable from part_b
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+ERROR: cannot drop inherited constraint "check_a" of relation "part_e"
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+ERROR: cannot drop inherited constraint "check_b" of relation "part_e"
+-- And dropping it from parted should leave no trace of them on part_e, unlike
+-- traditional inheritance where they will be left behind, because they would
+-- be local constraints.
+ALTER TABLE parted DROP CONSTRAINT check_a, DROP CONSTRAINT check_b;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+ conislocal | coninhcount
+------------+-------------
+(0 rows)
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY HASH(c);
+ERROR: column "c" named in partition key does not exist
+LINE 1: ...ARTITION OF parted FOR VALUES IN ('c') PARTITION BY HASH(c);
+ ^
+CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0) FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- check that NOT NULL and default value are inherited correctly
+create table parted_notnull_inh_test (a int default 1, b int not null default 0) partition by list (a) CONFIGURATION (VALUES IN (1));
+insert into parted_notnull_inh_test (b) values (null);
+ERROR: null value in column "b" of relation "parted_notnull_inh_test_0" violates not-null constraint
+DETAIL: Failing row contains (1, null).
+-- note that a's default is preserved
+\d parted_notnull_inh_test1
+drop table parted_notnull_inh_test;
+-- Partition bound in describe output
+\d+ part_e
+ Table "public.part_e"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | not null | 1 | plain | |
+Partition of: parted FOR VALUES IN ('e')
+Partition constraint: ((a IS NOT NULL) AND (a = 'e'::text))
+
+-- Both partition bound and partition key in describe output
+\d+ part_c
+ Partitioned table "public.part_c"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | not null | 0 | plain | |
+Partition of: parted FOR VALUES IN ('c')
+Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text))
+Partition key: RANGE (b)
+Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
+
+-- a level-2 partition's constraint will include the parent's expressions
+\d+ part_c_1_10
+ Table "public.part_c_1_10"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | not null | 0 | plain | |
+Partition of: part_c FOR VALUES FROM (1) TO (10)
+Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text) AND (b IS NOT NULL) AND (b >= 1) AND (b < 10))
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+ Partitioned table "public.parted"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | text | | |
+ b | integer | | not null | 0
+Partition key: LIST (a)
+Number of partitions: 4 (Use \d+ to list them.)
+
+\d hash_parted
+ Partitioned table "public.hash_parted"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition key: HASH (a)
+Number of partitions: 10 (Use \d+ to list them.)
+
+-- cleanup
+DROP TABLE parted;
+DROP TABLE list_parted;
+DROP TABLE list_parted2;
+DROP TABLE hash_parted;
+DROP TABLE temp_parted;
+DROP TABLE unlogged_parted;
+-- list partitioning on array type column
+CREATE TABLE arrlp (a int[]) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ('{1}', '{2}'));
+\d+ arrlp_1
+DROP TABLE arrlp;
+-- partition on boolean column
+create table boolspart (a bool) partition by list (a) CONFIGURATION
+(values in (true), (false));
+\d+ boolspart
+ Partitioned table "public.boolspart"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | boolean | | | | plain | |
+Partition key: LIST (a)
+Partitions: boolspart_0 FOR VALUES IN (true),
+ boolspart_1 FOR VALUES IN (false)
+
+drop table boolspart;
+-- test using a volatile expression as partition bound
+create table volatile_partbound_test (partkey timestamp) partition by list (partkey) CONFIGURATION (values in ('1970-01-01 00:00:00+00'::timestamp, current_timestamp),('1982-01-25 00:00:00+00'::timestamp));
+drop table volatile_partbound_test;
+-- tests of column drop with partition tables and indexes using
+-- predicates and expressions.
+create table part_column_drop (useless_1 int, id int, useless_2 int, d int,
+ b int, useless_3 int) partition by hash (id) CONFIGURATION (modulus 3);
+alter table part_column_drop drop column useless_1;
+alter table part_column_drop drop column useless_2;
+alter table part_column_drop drop column useless_3;
+create index part_column_drop_b_pred on part_column_drop(b) where b = 1;
+create index part_column_drop_b_expr on part_column_drop((b = 1));
+create index part_column_drop_d_pred on part_column_drop(d) where d = 2;
+create index part_column_drop_d_expr on part_column_drop((d = 2));
+create index part_column_drop_d_1_pred on part_column_drop_1(d) where d = 2;
+create index part_column_drop_d_1_expr on part_column_drop_1((d = 2));
+\d part_column_drop
+ Partitioned table "public.part_column_drop"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ d | integer | | |
+ b | integer | | |
+Partition key: HASH (id)
+Indexes:
+ "part_column_drop_b_expr" btree ((b = 1))
+ "part_column_drop_b_pred" btree (b) WHERE b = 1
+ "part_column_drop_d_expr" btree ((d = 2))
+ "part_column_drop_d_pred" btree (d) WHERE d = 2
+Number of partitions: 3 (Use \d+ to list them.)
+
+\d part_column_drop_1
+ Table "public.part_column_drop_1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ d | integer | | |
+ b | integer | | |
+Partition of: part_column_drop FOR VALUES WITH (modulus 3, remainder 1)
+Indexes:
+ "part_column_drop_1_b_idx" btree (b) WHERE b = 1
+ "part_column_drop_1_d_idx" btree (d) WHERE d = 2
+ "part_column_drop_1_expr_idx" btree ((b = 1))
+ "part_column_drop_1_expr_idx1" btree ((d = 2))
+ "part_column_drop_d_1_expr" btree ((d = 2))
+ "part_column_drop_d_1_pred" btree (d) WHERE d = 2
+
+\d part_column_drop_2
+ Table "public.part_column_drop_2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ d | integer | | |
+ b | integer | | |
+Partition of: part_column_drop FOR VALUES WITH (modulus 3, remainder 2)
+Indexes:
+ "part_column_drop_2_b_idx" btree (b) WHERE b = 1
+ "part_column_drop_2_d_idx" btree (d) WHERE d = 2
+ "part_column_drop_2_expr_idx" btree ((b = 1))
+ "part_column_drop_2_expr_idx1" btree ((d = 2))
+
+\d part_column_drop_3
+drop table part_column_drop;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cee822aa8b..53b69c221e 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -971,3 +971,241 @@ create table part_column_drop_1_10 partition of
\d part_column_drop
\d part_column_drop_1_10
drop table part_column_drop;
+
+-- Auto generated partitions
+
+-- must fail because of wrong configuration
+CREATE TABLE tbl_hash_fail (i int) PARTITION BY HASH (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+
+-- must fail because of wrong configuration
+CREATE TABLE tbl_list_fail (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (1, 3));
+
+CREATE TABLE tbl_list (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+
+\d+ tbl_list
+
+CREATE TABLE tbl_hash (i int) PARTITION BY HASH (i)
+CONFIGURATION (modulus 3);
+
+\d+ tbl_hash
+
+DROP TABLE tbl_list;
+DROP TABLE tbl_hash;
+
+-- forbidden expressions for partition bound with list partitioned table
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename.somename));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (a));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(a)));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(somename)));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(1)));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((select 1)));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (generate_series(4, 6)));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((1+1) collate "POSIX"));
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ());
+-- trying to specify range for list partitioned table
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES FROM (1) TO (2));
+-- trying to specify modulus and remainder for list partitioned table
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(MODULUS 10);
+
+-- must succeed
+CREATE TABLE list_parted (a int)
+PARTITION BY LIST (a)
+CONFIGURATION (values in ('1'), (2), (2+1), (null) DEFAULT PARTITION part_default);
+\d+ list_parted
+
+-- check default partition cannot be created more than once
+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) CONFIGURATION (VALUES IN (1));
+
+-- specified literal can be cast, and the cast might not be immutable
+CREATE TABLE moneyp (a money) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10), ('11'), (to_char(12, '99')::int));
+DROP TABLE moneyp;
+
+-- cast is immutable
+CREATE TABLE bigintp (a bigint) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10));
+-- fails due to overlap:
+CREATE TABLE bigintp_overlap PARTITION OF bigintp FOR VALUES IN ('10');
+DROP TABLE bigintp;
+
+CREATE TABLE hash_parted (a int) PARTITION BY HASH (a) CONFIGURATION (MODULUS 10);
+-- all remainder values are already belong to partitions
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODULUS 30, REMAINDER 3);
+-- trying to specify range for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a', 1) TO ('z');
+-- trying to specify list value for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000);
+-- trying to create default partition for the hash partitioned table
+CREATE TABLE fail_part (a int) PARTITION BY HASH (a) CONFIGURATION (MODULUS 10
+ DEFAULT hash_default);
+
+-- cannot create auto partition of a non-partitioned table
+CREATE TABLE fail_part (a int) CONFIGURATION (MODULUS 10);
+
+-- partition table inherits relation persistence setting from parent
+CREATE TEMP TABLE temp_parted (a char) PARTITION BY LIST (a)
+CONFIGURATION (VALUES IN ('a') DEFAULT PARTITION temp_parted_default);
+\d+ temp_parted
+
+-- partition table inherits relation persistence setting from parent
+CREATE UNLOGGED TABLE unlogged_parted (a char) PARTITION BY LIST (a)
+CONFIGURATION (VALUES IN ('a') DEFAULT PARTITION unlogged_parted_default);
+\d+ unlogged_parted
+
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE fail_parted2 (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),(null) DEFAULT partition tbl_default);
+
+CREATE TABLE fail_parted2 (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),('b', 'c') DEFAULT partition tbl_default);
+
+-- check default partition overlap
+CREATE TABLE list_parted2 (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b') DEFAULT partition tbl_default);
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (a text, b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0))
+PARTITION BY LIST (a) CONFIGURATION ( VALUES IN ('a','b'),('d') );
+
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute
+ WHERE attrelid = 'parted_1'::regclass and attnum > 0
+ ORDER BY attnum;
+
+-- able to specify column default, column constraint, and table constraint
+
+-- first check the "column specified more than once" error
+CREATE TABLE part_e_fail PARTITION OF parted (
+ b NOT NULL,
+ b DEFAULT 1,
+ b CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('e');
+
+CREATE TABLE part_e PARTITION OF parted (
+ b NOT NULL DEFAULT 1,
+ CONSTRAINT check_a CHECK (length(a) > 0),
+ CONSTRAINT check_b CHECK (b >= 0)
+) FOR VALUES IN ('e');
+-- conislocal should be false for any merged constraints, true otherwise
+SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass ORDER BY conislocal, coninhcount;
+
+-- check_a can not be dropped as it is inherited
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+-- check_b can be dropped as it is local
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+
+-- Once check_b is added to the parent, it should be made non-local for part_b
+ALTER TABLE part_e ADD CONSTRAINT check_b CHECK (b >= 0);
+ALTER TABLE parted ADD CONSTRAINT check_b CHECK (b >= 0);
+SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+
+-- Neither check_a nor check_b are droppable from part_b
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+
+-- And dropping it from parted should leave no trace of them on part_e, unlike
+-- traditional inheritance where they will be left behind, because they would
+-- be local constraints.
+ALTER TABLE parted DROP CONSTRAINT check_a, DROP CONSTRAINT check_b;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY HASH(c);
+CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0) FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+
+-- check that NOT NULL and default value are inherited correctly
+create table parted_notnull_inh_test (a int default 1, b int not null default 0) partition by list (a) CONFIGURATION (VALUES IN (1));
+insert into parted_notnull_inh_test (b) values (null);
+-- note that a's default is preserved
+\d parted_notnull_inh_test1
+drop table parted_notnull_inh_test;
+
+-- Partition bound in describe output
+\d+ part_e
+
+-- Both partition bound and partition key in describe output
+\d+ part_c
+
+-- a level-2 partition's constraint will include the parent's expressions
+\d+ part_c_1_10
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+\d hash_parted
+
+-- cleanup
+DROP TABLE parted;
+DROP TABLE list_parted;
+DROP TABLE list_parted2;
+DROP TABLE hash_parted;
+DROP TABLE temp_parted;
+DROP TABLE unlogged_parted;
+
+
+-- list partitioning on array type column
+CREATE TABLE arrlp (a int[]) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ('{1}', '{2}'));
+\d+ arrlp_1
+DROP TABLE arrlp;
+
+-- partition on boolean column
+create table boolspart (a bool) partition by list (a) CONFIGURATION
+(values in (true), (false));
+\d+ boolspart
+drop table boolspart;
+
+-- test using a volatile expression as partition bound
+create table volatile_partbound_test (partkey timestamp) partition by list (partkey) CONFIGURATION (values in ('1970-01-01 00:00:00+00'::timestamp, current_timestamp),('1982-01-25 00:00:00+00'::timestamp));
+drop table volatile_partbound_test;
+
+-- tests of column drop with partition tables and indexes using
+-- predicates and expressions.
+create table part_column_drop (useless_1 int, id int, useless_2 int, d int,
+ b int, useless_3 int) partition by hash (id) CONFIGURATION (modulus 3);
+alter table part_column_drop drop column useless_1;
+alter table part_column_drop drop column useless_2;
+alter table part_column_drop drop column useless_3;
+create index part_column_drop_b_pred on part_column_drop(b) where b = 1;
+create index part_column_drop_b_expr on part_column_drop((b = 1));
+create index part_column_drop_d_pred on part_column_drop(d) where d = 2;
+create index part_column_drop_d_expr on part_column_drop((d = 2));
+create index part_column_drop_d_1_pred on part_column_drop_1(d) where d = 2;
+create index part_column_drop_d_1_expr on part_column_drop_1((d = 2));
+
+\d part_column_drop
+\d part_column_drop_1
+\d part_column_drop_2
+\d part_column_drop_3
+drop table part_column_drop;
Do you think that it is a bug? For now, I removed this statement from
tests just to calm down the CI.
It is in accordance with changes in tests for vanilla
decralarive partitioning as per
commit 2dfa3fea88bc951d0812a18649d801f07964c9b9
Author: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon Sep 28 13:44:01 2020 -0400
Remove complaints about COLLATE clauses in partition bound values.
which my test does for automatic way in the same style. So I consider your
removal completely correct.
Thank you!
On 05.10.2020 09:36, Rahila Syed wrote:
Hi,
Couple of comments:
1. The syntax used omits the { IMMEDIATE | DEFERRED} keywords
suggested in
the earlier discussions. I think it is intuitive to
include IMMEDIATE with the current implementation
so that the syntax can be extended with a DEFERRED clause in
future for dynamic partitions.CREATE TABLE tbl_lst (i int) PARTITION BY LIST (i)
CONFIGURATION (values in (1, 2), (3, 4) DEFAULT PARTITION
tbl_default);After some consideration, I decided that we don't actually need to
introduce IMMEDIATE | DEFERRED keyword. For hash and list
partitions it will always be immediate, as the number of
partitions cannot change after we initially set it. For range
partitions, on the contrary, it doesn't make much sense to make
partitions immediately, because in many use-cases one bound will
be open.As per discussions on this thread:
/messages/by-id/alpine.DEB.2.21.1907150711080.22273@lancre
DEFERRED clause refers to creating partitions on the fly, while the
data is being inserted.
The number of partitions and partition bounds can be the same as
specified initially
during partitioned table creation, but the actual creation of
partitions can be deferred.
This seems like a potential extension to statically created partitions
even in the case of
hash and list partitions, as it won't involve moving any existing data.
Oh, now I see what you mean. The case with already existing tables will
require changes to ALTER TABLE syntax. And that's where we may want to
choose between immediate (i.e. locking) and deferred (i.e. concurrent)
creation of partitions. I think we should try to implement it with
existing keywords, maybe use 'CONCURRENTLY' keyword and it will look like:
ALTER TABLE tbl PARTITION BY ... CONFIGURATION (....) [CONCURRENTLY];
Anyway, the task of handling existing data is much more complicated,
especially the 'concurrent' case and to be honest, I haven't put much
thought into it yet.
The current patch only implements the simplest case of creating a new
partitioned table. And I don't see if CREATE TABLE needs this
immediate|deferred clause or if it will need it in the future.
Thoughts?
2. One suggestion for generation of partition names is to
append a unique id toavoid conflicts.
Can you please give an example of such a conflict? I agree that
current naming scheme is far from perfect, but I think that
'tablename'_partnum provides unique name for each partition.Sorry for not being clear earlier, I mean the partition name
'tablename_partnum' can conflict with any existing table name.
As per current impemetation, if I do the following it results in the
table name conflict.postgres=# create table tbl_test_5_1(i int);
CREATE TABLE
postgres=# CREATE TABLE tbl_test_5 (i int) PARTITION BY
LIST((tbl_test_5)) CONFIGURATION (values in ('(1)'::tbl_test_5),
('(3)'::tbl_test_5) default partition tbl_default_5);
ERROR:relation "tbl_test_5_1" already exists
I don't mind adding some specific suffix for generated partitions,
although it still may conflict with existing table names. The main
disadvantage of this idea, is that it reduces number of symbols
available for table name, which can lead to something like this:
CREATE TABLE
parteddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd (a
text, b int NOT NULL DEFAULT 0, CONSTRAINT check_aa CHECK (length(a) > 0))
PARTITION BY LIST (a) CONFIGURATION (VALUES IN ('a','b'),('c','d')
DEFAULT PARTITION parted_def) ;;
NOTICE: identifier
"parteddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
will be truncated to
"partedddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
ERROR: relation
"partedddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
already exists
The error message here is a bit confusing, as relation
'partedddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'
haven't existed before and this is a conflict between partitioned and
generated partition table name. For now, I don't know if we can handle
it more gracefully. Probably, we could truncate tablename to a shorter
size, but it doesn't provide a complete solution, because partition
number can contain several digits.
See also pg_partman documentation on the same issue:
https://github.com/pgpartman/pg_partman/blob/master/doc/pg_partman.md#naming-length-limits
--
Anastasia Lubennikova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
2. One suggestion for generation of partition names is to append a
unique id to
avoid conflicts.
Can you please give an example of such a conflict? I agree that current
naming scheme is far from perfect, but I think that 'tablename'_partnum
provides unique name for each partition.Sorry for not being clear earlier, I mean the partition name
'tablename_partnum' can conflict with any existing table name.
As per current impemetation, if I do the following it results in the table
name conflict.postgres=# create table tbl_test_5_1(i int);
CREATE TABLE
postgres=# CREATE TABLE tbl_test_5 (i int) PARTITION BY LIST((tbl_test_5))CONFIGURATION (values in
('(1)'::tbl_test_5), ('(3)'::tbl_test_5) default partition tbl_default_5);
ERROR: relation "tbl_test_5_1" already existsI don't mind adding some specific suffix for generated partitions,
although it still may conflict with existing table names. The main
disadvantage of this idea, is that it reduces number of symbols available
for table name, which can lead to something like this:CREATE TABLE
parteddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd (a
text, b int NOT NULL DEFAULT 0, CONSTRAINT check_aa CHECK (length(a) > 0))
PARTITION BY LIST (a) CONFIGURATION (VALUES IN ('a','b'),('c','d') DEFAULT
PARTITION parted_def) ;;
NOTICE: identifier
"parteddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" will
be truncated to
"partedddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
ERROR: relation
"partedddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" already
exists
doc/pg_partman.md#naming-length-limits
<https://github.com/pgpartman/pg_partman/blob/master/doc/pg_partman.md#naming-length-limits>
It seems to me that a working idea is to add a prefix to partitions is to
give the possibility to specify it for users. So the user will be able to
choose appropriate and not very long suffix to avoid conflicts.
Maybe like this:
CREATE TABLE city (a text) PARTITION BY LIST (a) CONFIGURATION (VALUES IN
('a','b'),('c','d') DEFAULT PARTITION city_other PREFIX _prt) ;
Result:
---
city_prt1
city_prt2
...
city_other
--
Best regards,
Pavel Borisov
Postgres Professional: http://postgrespro.com <http://www.postgrespro.com>
Again I've checked v3 patch. In the discussion, there are several other
ideas on its further development, so I consider the patch as the first step
to later progress. Though now the patch is fully self-sufficient in
functionality and has enough tests etc. I suppose it is ready to be
committed.
--
Best regards,
Pavel Borisov
Postgres Professional: http://postgrespro.com <http://www.postgrespro.com>
I've realized one strange effect in current grammar parsing: if I do
CREATE TABLE foo (a int) PARTITION BY LIST (a) CONFIGURATION (a 1);
ERROR: unrecognized auto partition bound specification "a"
I consulted the patch code and realized that in fact, the patch considers
it the (invalid) HASH bounds (doesn't find a word 'modulus') unless it is
specified to be (still invalid) LIST. This is due to the fact that the
grammar parser is not context-aware and in the patch, we tried to avoid the
new parser keyword MODULUS. The effect is that inside a CONFIGURATION
parentheses in case of HASH bounds we don't have a single keyword for the
parser to determine it is really a HASH case.
It doesn't make the patch work wrongly, besides it checks the validity of
all types of bounds in the HASH case even when the partitioning is not
HASH. I find this slightly bogus. This is because the parser can not
determine the type of partitioning inside the configuration clause and this
makes adding new syntax (e.g. adding RANGE partitioning configuration
inside CONFIGURATION parentheses) complicated.
So I have one more syntax proposal: to have separate keywords
inside CONFIGURATION parentheses for each partitioning type.
E.g:
CREATE TABLE foo(a int) PARTITION BY LIST(a) CONFIGURATION (FOR VALUES IN
(1,2),(3,4) DEFAULT PARTITION foo_def);
CREATE TABLE foo(a int) PARTITION BY HASH(a) CONFIGURATION (FOR VALUES WITH
MODULUS 3);
CREATE TABLE foo(a int) PARTITION BY RAGE(a) CONFIGURATION (FOR VALUES FROM
1 TO 1000 INTERVAL 10 DEFAULT PARTITION foo_def);
This proposal is in accordance with the current syntax of declarative
partitioning: CREATE TABLE foo_1 PARTITION OF foo FOR VALUES ...
Some more facultative proposals incremental to the abovementioned:
1. Omit CONFIGURATION with/without parentheses. This makes syntax closer
to (non-automatic) declarative partitioning syntax but the clause
seems less legible (in my opinion).
2. Omit just FOR VALUES. This makes the clause short, but adds a difference
to (non-automatic) declarative partitioning syntax.
I'm planning also to add RANGE partitioning syntax to this in the future
and I will be happy if all three types of the syntax could come along
easily.
I very much appreciate your views on this especially regarding that
changes can be still made easily because the patch is not committed yet.
--
Best regards,
Pavel Borisov
Postgres Professional: http://postgrespro.com <http://www.postgrespro.com>
On 2020-12-18 21:54, Pavel Borisov wrote:
I've realized one strange effect in current grammar parsing: if I do
CREATE TABLE foo (a int) PARTITION BY LIST (a) CONFIGURATION (a 1);
ERROR: unrecognized auto partition bound specification "a"I consulted the patch code and realized that in fact, the patch
considers it the (invalid) HASH bounds (doesn't find a word 'modulus')
unless it is specified to be (still invalid) LIST. This is due to the
fact that the grammar parser is not context-aware and in the patch, we
tried to avoid the new parser keyword MODULUS. The effect is that
inside a CONFIGURATION parentheses in case of HASH bounds we don't
have a single keyword for the parser to determine it is really a HASH
case.It doesn't make the patch work wrongly, besides it checks the validity
of all types of bounds in the HASH case even when the partitioning is
not HASH. I find this slightly bogus. This is because the parser can
not determine the type of partitioning inside the configuration clause
and this makes adding new syntax (e.g. adding RANGE partitioning
configuration inside CONFIGURATION parentheses) complicated.So I have one more syntax proposal: to have separate keywords inside
CONFIGURATION parentheses for each partitioning type.
E.g:
CREATE TABLE foo(a int) PARTITION BY LIST(a) CONFIGURATION (FOR VALUES
IN (1,2),(3,4) DEFAULT PARTITION foo_def);
CREATE TABLE foo(a int) PARTITION BY HASH(a) CONFIGURATION (FOR VALUES
WITH MODULUS 3);
CREATE TABLE foo(a int) PARTITION BY RAGE(a) CONFIGURATION (FOR VALUES
FROM 1 TO 1000 INTERVAL 10 DEFAULT PARTITION foo_def);This proposal is in accordance with the current syntax of declarative
partitioning: CREATE TABLE foo_1 PARTITION OF foo FOR VALUES ...Some more facultative proposals incremental to the abovementioned:
1. Omit CONFIGURATION with/without parentheses. This makes syntax
closer to (non-automatic) declarative partitioning syntax but the
clause seems less legible (in my opinion).
2. Omit just FOR VALUES. This makes the clause short, but adds a
difference to (non-automatic) declarative partitioning syntax.I'm planning also to add RANGE partitioning syntax to this in the
future and I will be happy if all three types of the syntax could come
along easily.I very much appreciate your views on this especially regarding that
changes can be still made easily because the patch is not committed
yet.--
Best regards,
Pavel BorisovPostgres Professional: http://postgrespro.com [1]
Links:
------
[1] http://www.postgrespro.com
In my view, next expressions are the golden ground here. On one hand,
not far from the original
non-automatic declarative partitioning syntax syntax, on the other hand,
omit CONFIGURATION
key-word (which is redundant here in terms of gram parsing) makes this
expressions less apprehensible for the human.
CREATE TABLE foo(a int) PARTITION BY LIST(a) CONFIGURATION (FOR VALUES
IN (1,2),(3,4) DEFAULT PARTITION foo_def);
CREATE TABLE foo(a int) PARTITION BY HASH(a) CONFIGURATION (FOR VALUES
WITH MODULUS 3);
CREATE TABLE foo(a int) PARTITION BY RAGE(a) CONFIGURATION (FOR VALUES
FROM 1 TO 1000 INTERVAL 10 DEFAULT PARTITION foo_def);
In addition to that, adding RANGE PARTITION would be much simpler since
we would have specific "branches" in gram instead of using
context-sensitive grammar and dealing with it in c-code.
---
Best regards,
Maxim Orlov.
CREATE TABLE foo(a int) PARTITION BY LIST(a) CONFIGURATION (FOR VALUES IN
(1,2),(3,4) DEFAULT PARTITION foo_def);
I would like to disagree with this syntactic approach because it would
very specific to each partition method. IMHO the syntax should be as
generic as possible. I'd suggest (probably again) a keyword/value list
which would allow to be quite adaptable without inducing any pressure on
the parser.
--
Fabien.
CREATE TABLE foo(a int) PARTITION BY LIST(a) CONFIGURATION (FOR VALUES
IN
(1,2),(3,4) DEFAULT PARTITION foo_def);
I would like to disagree with this syntactic approach because it would
very specific to each partition method. IMHO the syntax should be as
generic as possible. I'd suggest (probably again) a keyword/value list
which would allow to be quite adaptable without inducing any pressure on
the parser.
If I remember your proposal correctly it is something like
CREATE TABLE foo(...) PARTITION BY HASH AUTOMATIC (MODULUS 10);
It is still possible but there are some caveats:
1. We'll need to add keyword MODULUS (and probably AUTOMATIC) to the
parser's list. I don't against this but as far as I've heard there is some
opposition among PG community against new keywords. Maybe I am wrong.
2. The existing syntax for declarative partitioning is different to your
proposal. It is still not a big problem and your proposal makes query
shorter for several words. I'd just like to see some consensus on the
syntax. Now I must admit there are too many contradictions in opinions
which make progress slow. Also I think it is important to have a really
convenient syntaх.
2a Maybe we all who participated in the thread can vote for some variant?
2b Maybe the existing syntax for declarative partitioniong should be given
some priority as it is already committed into CREATE TABLE ... PARTITION OF
... FOR VALUES IN.. etc.
I'd be happy if everyone will join some version of the proposed syntaх in
this thread and in the previous discussion [1]/messages/by-id/alpine.DEB.2.21.1907150711080.22273@lancre. If we have a variant with
more than one supporter, sure we can develop patch based on it.
Thank you very much
and Merry Christmas!
[1]: /messages/by-id/alpine.DEB.2.21.1907150711080.22273@lancre
/messages/by-id/alpine.DEB.2.21.1907150711080.22273@lancre
--
Best regards,
Pavel Borisov
Postgres Professional: http://postgrespro.com <http://www.postgrespro.com>
HEllo.
CREATE TABLE foo(a int) PARTITION BY LIST(a) CONFIGURATION (FOR VALUES
IN
(1,2),(3,4) DEFAULT PARTITION foo_def);
I would like to disagree with this syntactic approach because it would
very specific to each partition method. IMHO the syntax should be as
generic as possible. I'd suggest (probably again) a keyword/value list
which would allow to be quite adaptable without inducing any pressure on
the parser.If I remember your proposal correctly it is something like
CREATE TABLE foo(...) PARTITION BY HASH AUTOMATIC (MODULUS 10);
Yep, that would be the spirit.
It is still possible but there are some caveats: 1. We'll need to add
keyword MODULUS (and probably AUTOMATIC) to the parser's list.
Why? We could accept anything in the list? i.e.:
(ident =? value[, ident =? value]*)
I don't against this but as far as I've heard there is some
opposition among PG community against new keywords. Maybe I am wrong.
the ident is a keyword that can be interpreted later on, not a "reserved
keyword" from a parser perspective, which is the only real issue?
The parser does not need to know about it, only the command interpreter
which will have to interpret it. AUTOMATIC is a nice parser cue to
introduce such a ident-value list.
2. The existing syntax for declarative partitioning is different to your
proposal.
Yep. I think that it was not so good a design choice from a
language/extensibility perspective.
It is still not a big problem and your proposal makes query
shorter for several words. I'd just like to see some consensus on the
syntax. Now I must admit there are too many contradictions in opinions
which make progress slow. Also I think it is important to have a really
convenient syntaх.
2a Maybe we all who participated in the thread can vote for some variant?
2b Maybe the existing syntax for declarative partitioniong should be given
some priority as it is already committed into CREATE TABLE ... PARTITION OF
... FOR VALUES IN.. etc.
I'd be happy if everyone will join some version of the proposed syntaх in
this thread and in the previous discussion [1]. If we have a variant with
more than one supporter, sure we can develop patch based on it.
Thank you very much
and Merry Christmas![1]
/messages/by-id/alpine.DEB.2.21.1907150711080.22273@lancre
--
Fabien.
Why? We could accept anything in the list? i.e.:
(ident =? value[, ident =? value]*)
I don't against this but as far as I've heard there is some
opposition among PG community against new keywords. Maybe I am wrong.the ident is a keyword that can be interpreted later on, not a "reserved
keyword" from a parser perspective, which is the only real issue?The parser does not need to know about it, only the command interpreter
which will have to interpret it. AUTOMATIC is a nice parser cue to
introduce such a ident-value list.2. The existing syntax for declarative partitioning is different to your
proposal.Yep. I think that it was not so good a design choice from a
language/extensibility perspective.
Thank you very much, Fabien. It is clear enough.
BTW could you tell me a couple of words about pros and cons of c-code
syntax parsing comparing to parsing using gram.y trees? I think both are
possible but my predisposition was that we'd better use the later if
possible.
Best regards,
Pavel Borisov
Show quoted text
BTW could you tell me a couple of words about pros and cons of c-code
syntax parsing comparing to parsing using gram.y trees?
I'd rather use an automatic tool (lexer/parser) if possible instead of
doing it by hand if I can. If you want a really nice syntax with clever
tricks, then you may need to switch to manual though, but pg/sql is not in
that class.
I think both are possible but my predisposition was that we'd better use
the later if possible.
I agree.
--
Fabien.
BTW could you tell me a couple of words about pros and cons of c-code
syntax parsing comparing to parsing using gram.y trees?I'd rather use an automatic tool (lexer/parser) if possible instead of
doing it by hand if I can. If you want a really nice syntax with clever
tricks, then you may need to switch to manual though, but pg/sql is not in
that class.I think both are possible but my predisposition was that we'd better use
the later if possible.I agree.
Thank you!
Fabien, do you consider it possible to change the syntax of declarative
partitioning too? It is problematic as it is already committed but also is
very tempting to have the same type of syntax both in automatic
partitioning and in manual (PARTITION OF...)
--
Best regards,
Pavel Borisov
Postgres Professional: http://postgrespro.com <http://www.postgrespro.com>
Fabien, do you consider it possible to change the syntax of declarative
partitioning too?
My 0.02 €: What I think does not matter much, what committers think is the
way to pass something. However, I do not think that such an idea would
pass a committer:-)
It is problematic as it is already committed but also is very tempting
to have the same type of syntax both in automatic partitioning and in
manual (PARTITION OF...)
I think that if a "common" syntax, for a given meaning of common, can be
thought of, and without breaking backward compatibility, then there may be
an argument to provide such a syntax, but I would not put too much energy
into that if I were you.
I see 3 cases:
- partition declaration but no actual table generated, the current
version.
- partition declaration with actual sub-tables generated, eg for hash
where it is pretty straightforward to know what would be needed, or for
a bounded range.
- partition declaration without generated table, but they are generated
on demand, when needed, for a range one may want weekly or monthly
without creating tables in advance, esp. if it is unbounded.
ISTM that the syntax should be clear and if possible homogeneous for all
three use cases, even if they are not implemented yet. It should also
allow easy extensibility, hence something without a strong syntax,
key/value pairs to be interpreted later.
--
Fabien.
My 0.02 €: What I think does not matter much, what committers think is the
way to pass something. However, I do not think that such an idea would
pass a committer:-)
The same idea was the reason for my proposal to make automatic partitioning
clauses to be in accordance with existing declarative syntax (even if it
seems little bit long to write words "configuration (for values" )
CREATE TABLE foo(a int) PARTITION BY LIST(a) CONFIGURATION (FOR VALUES
IN (1,2),(3,4) DEFAULT PARTITION foo_def);
CREATE TABLE foo(a int) PARTITION BY HASH(a) CONFIGURATION (FOR VALUES
WITH MODULUS 3);
CREATE TABLE foo(a int) PARTITION BY RAGE(a) CONFIGURATION (FOR VALUES
FROM 1 TO 1000 INTERVAL 10 DEFAULT PARTITION foo_def)
If we want generic (ident = value,...) then we need to introduce different
to what is already committed for manual partitioning which I considered
worse than my proposal above. Still other opinions are highly valued.
--
Best regards,
Pavel Borisov
Postgres Professional: http://postgrespro.com <http://www.postgrespro.com>
On Wed, Oct 7, 2020 at 6:26 AM Anastasia Lubennikova
<a.lubennikova@postgrespro.ru> wrote:
Do you think that it is a bug? For now, I removed this statement from
tests just to calm down the CI.
I don't think we can use \d+ on a temporary table here, because the
backend ID appears in the namespace, which is causing a failure on one
of the CI OSes due to nondeterminism:
CREATE TEMP TABLE temp_parted (a char) PARTITION BY LIST (a)
CONFIGURATION (VALUES IN ('a') DEFAULT PARTITION temp_parted_default);
\d+ temp_parted
- Partitioned table "pg_temp_3.temp_parted"
+ Partitioned table "pg_temp_4.temp_parted"
I don't think we can use \d+ on a temporary table here, because the
backend ID appears in the namespace, which is causing a failure on one
of the CI OSes due to nondeterminism:CREATE TEMP TABLE temp_parted (a char) PARTITION BY LIST (a) CONFIGURATION (VALUES IN ('a') DEFAULT PARTITION temp_parted_default); \d+ temp_parted - Partitioned table "pg_temp_3.temp_parted" + Partitioned table "pg_temp_4.temp_parted"
I've updated the tests accordingly. PFA version 4.
As none of the recent proposals to modify the syntax were seconded by
anyone, I return the previous Ready-for-committer CF status.
--
Best regards,
Pavel Borisov
Postgres Professional: http://postgrespro.com <http://www.postgrespro.com>
Attachments:
0001-Auto-generated-HASH-and-LIST-partitions-v4.patchapplication/octet-stream; name=0001-Auto-generated-HASH-and-LIST-partitions-v4.patchDownload
From b91b065e89b35212f06b245a053a4f024d428396 Mon Sep 17 00:00:00 2001
From: Pavel Borisov <pashkin.elfe@gmail.com>
Date: Tue, 3 Nov 2020 11:27:35 +0400
Subject: [PATCH] Auto generated HASH and LIST partitions (v4)
---
doc/src/sgml/ref/create_table.sgml | 49 +++
src/backend/nodes/copyfuncs.c | 17 +
src/backend/nodes/equalfuncs.c | 17 +
src/backend/nodes/outfuncs.c | 16 +
src/backend/nodes/readfuncs.c | 15 +
src/backend/parser/gram.y | 82 +++-
src/backend/parser/parse_utilcmd.c | 149 +++++++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 23 ++
src/include/partitioning/partdefs.h | 2 +
src/test/regress/expected/create_table.out | 432 +++++++++++++++++++++
src/test/regress/sql/create_table.sql | 238 ++++++++++++
12 files changed, 1039 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bc59a2d77d..5e98b75f50 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -29,6 +29,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ]
[ USING <replaceable class="parameter">method</replaceable> ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -41,6 +42,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
) ]
[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ]
[ USING <replaceable class="parameter">method</replaceable> ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -53,6 +55,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
) ] { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT }
[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ]
[ USING <replaceable class="parameter">method</replaceable> ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -96,6 +99,11 @@ FROM ( { <replaceable class="parameter">partition_bound_expr</replaceable> | MIN
TO ( { <replaceable class="parameter">partition_bound_expr</replaceable> | MINVALUE | MAXVALUE } [, ...] ) |
WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REMAINDER <replaceable class="parameter">numeric_literal</replaceable> )
+<phrase>and <replaceable class="parameter">partition_bound_auto_spec</replaceable> is:</phrase>
+
+VALUES IN ( <replaceable class="parameter">partition_bound_expr</replaceable> [, ...] ), [( <replaceable class="parameter">partition_bound_expr</replaceable> [, ...] )] [, ...] [DEFAULT PARTITION <replaceable class="parameter">default_part_name</replaceable>]
+MODULUS <replaceable class="parameter">numeric_literal</replaceable>
+
<phrase><replaceable class="parameter">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
[ INCLUDE ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ]
@@ -384,6 +392,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
however, you can define these constraints on individual partitions.
</para>
+ <para>
+ Hash and list partitioning also support automatic creation of partitions
+ with an optional <literal>CONFIGURATION</literal> clause.
+ </para>
+
<para>
See <xref linkend="ddl-partitioning"/> for more discussion on table
partitioning.
@@ -392,6 +405,42 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ] </literal></term>
+ <listitem>
+ <para>
+ The optional <literal>CONFIGURATION</literal> clause used together
+ with <literal>PARTITION BY</literal> specifies a rule of generating bounds
+ for partitions of the partitioned table. All partitions are created automatically
+ along with the parent table.
+
+ Any indexes, constraints and user-defined row-level triggers that exist
+ in the parent table are cloned on the new partitions. When using this clause,
+ <literal> CREATE TABLE </literal> statement can only contain clauses, that are
+ applicable to both relation kinds: partitioned table and regular partition tables.
+ All tables created by the statement will use same parameters, such as
+ relation persistence.
+ </para>
+
+ <para>
+ The <replaceable class="parameter">partition_bound_auto_spec</replaceable>
+ must correspond to the partitioning method and partition key of the
+ parent table, and must not overlap with any existing partition of that
+ parent. The form with <literal>VALUES IN</literal> is used for list partitioning
+ and the form with <literal>MODULUS</literal> is used for hash partitioning.
+ List partitioning can also provide a default partition using
+ <literal>DEFAULT PARTITION</literal>.
+ </para>
+
+ <para>
+ Automatic range partitioning is not supported yet.
+ </para>
+
+
+
+ </listitem>
+ </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> | DEFAULT }</literal></term>
<listitem>
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 530aac68a7..ccaf92c5c6 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4617,6 +4617,7 @@ _copyPartitionSpec(const PartitionSpec *from)
COPY_STRING_FIELD(strategy);
COPY_NODE_FIELD(partParams);
+ COPY_NODE_FIELD(autopart);
COPY_LOCATION_FIELD(location);
return newnode;
@@ -4639,6 +4640,19 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
return newnode;
}
+static PartitionBoundAutoSpec *
+_copyPartitionBoundAutoSpec(const PartitionBoundAutoSpec *from)
+{
+ PartitionBoundAutoSpec *newnode = makeNode(PartitionBoundAutoSpec);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_SCALAR_FIELD(modulus);
+ COPY_NODE_FIELD(listdatumsList);
+ COPY_NODE_FIELD(default_partition_rv);
+
+ return newnode;
+}
+
static PartitionRangeDatum *
_copyPartitionRangeDatum(const PartitionRangeDatum *from)
{
@@ -5685,6 +5699,9 @@ copyObjectImpl(const void *from)
case T_PartitionBoundSpec:
retval = _copyPartitionBoundSpec(from);
break;
+ case T_PartitionBoundAutoSpec:
+ retval = _copyPartitionBoundAutoSpec(from);
+ break;
case T_PartitionRangeDatum:
retval = _copyPartitionRangeDatum(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 0cf90ef33c..ad14bc8a8e 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2889,6 +2889,7 @@ _equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
COMPARE_STRING_FIELD(strategy);
COMPARE_NODE_FIELD(partParams);
COMPARE_LOCATION_FIELD(location);
+ COMPARE_NODE_FIELD(autopart);
return true;
}
@@ -2908,6 +2909,19 @@ _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *
return true;
}
+static bool
+_equalPartitionBoundAutoSpec(const PartitionBoundAutoSpec *a,
+ const PartitionBoundAutoSpec *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_SCALAR_FIELD(modulus);
+ COMPARE_NODE_FIELD(listdatumsList);
+ COMPARE_NODE_FIELD(default_partition_rv);
+
+ return true;
+}
+
+
static bool
_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
{
@@ -3740,6 +3754,9 @@ equal(const void *a, const void *b)
case T_PartitionBoundSpec:
retval = _equalPartitionBoundSpec(a, b);
break;
+ case T_PartitionBoundAutoSpec:
+ retval = _equalPartitionBoundAutoSpec(a, b);
+ break;
case T_PartitionRangeDatum:
retval = _equalPartitionRangeDatum(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 7e324c12e2..d917c6a312 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3641,6 +3641,7 @@ _outPartitionSpec(StringInfo str, const PartitionSpec *node)
WRITE_STRING_FIELD(strategy);
WRITE_NODE_FIELD(partParams);
+ WRITE_NODE_FIELD(autopart);
WRITE_LOCATION_FIELD(location);
}
@@ -3659,6 +3660,18 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionBoundAutoSpec(StringInfo str, const PartitionBoundAutoSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBOUNDAUTOSPEC");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_INT_FIELD(modulus);
+ WRITE_NODE_FIELD(listdatumsList);
+ WRITE_NODE_FIELD(default_partition_rv);
+
+}
+
static void
_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
{
@@ -4332,6 +4345,9 @@ outNode(StringInfo str, const void *obj)
case T_PartitionBoundSpec:
_outPartitionBoundSpec(str, obj);
break;
+ case T_PartitionBoundAutoSpec:
+ _outPartitionBoundAutoSpec(str, obj);
+ break;
case T_PartitionRangeDatum:
_outPartitionRangeDatum(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index ab7b535caa..39c121f0a6 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2600,6 +2600,19 @@ _readPartitionBoundSpec(void)
READ_DONE();
}
+static PartitionBoundAutoSpec *
+_readPartitionBoundAutoSpec(void)
+{
+ READ_LOCALS(PartitionBoundAutoSpec);
+
+ READ_CHAR_FIELD(strategy);
+ READ_INT_FIELD(modulus);
+ READ_NODE_FIELD(listdatumsList);
+ READ_NODE_FIELD(default_partition_rv);
+
+ READ_DONE();
+}
+
/*
* _readPartitionRangeDatum
*/
@@ -2878,6 +2891,8 @@ parseNodeString(void)
return_value = _readPartitionBoundSpec();
else if (MATCH("PARTITIONRANGEDATUM", 19))
return_value = _readPartitionRangeDatum();
+ else if (MATCH("PARTITIONBOUNDAUTOSPEC", 22))
+ return_value = _readPartitionBoundAutoSpec();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 357ab93fb6..22e6035a2e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -249,6 +249,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionElem *partelem;
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
+ PartitionBoundAutoSpec *partboundautospec;
RoleSpec *rolespec;
struct SelectLimit *selectlimit;
}
@@ -601,6 +602,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
+%type <partboundautospec> OptPartitionBoundAutoSpec values_in_clause p_desc
+%type <range> opt_default_partition_clause
+
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
* They must be listed first so that their numeric codes do not depend on
@@ -3911,14 +3915,14 @@ OptPartitionSpec: PartitionSpec { $$ = $1; }
| /*EMPTY*/ { $$ = NULL; }
;
-PartitionSpec: PARTITION BY ColId '(' part_params ')'
+PartitionSpec: PARTITION BY ColId '(' part_params ')' OptPartitionBoundAutoSpec
{
PartitionSpec *n = makeNode(PartitionSpec);
n->strategy = $3;
n->partParams = $5;
n->location = @1;
-
+ n->autopart = (Node *) $7;
$$ = n;
}
;
@@ -3962,6 +3966,80 @@ part_elem: ColId opt_collate opt_class
}
;
+OptPartitionBoundAutoSpec:
+ CONFIGURATION '(' p_desc ')'
+ {
+ $$ = $3;
+ }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+p_desc:
+ hash_partbound
+ {
+ ListCell *lc;
+ PartitionBoundAutoSpec *n = makeNode(PartitionBoundAutoSpec);
+
+ n->modulus = -1;
+
+ foreach (lc, $1)
+ {
+ DefElem *opt = lfirst_node(DefElem, lc);
+
+ if (strcmp(opt->defname, "modulus") == 0)
+ {
+ n->strategy = PARTITION_STRATEGY_HASH;
+ if (n->modulus != -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("modulus for hash partition provided more than once"),
+ parser_errposition(opt->location)));
+ n->modulus = defGetInt32(opt);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized auto partition bound specification \"%s\"",
+ opt->defname),
+ parser_errposition(opt->location)));
+ }
+
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ | values_in_clause opt_default_partition_clause
+ {
+ PartitionBoundAutoSpec *n = $1;
+ n->default_partition_rv = $2;
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ ;
+
+values_in_clause:
+ VALUES IN_P '(' expr_list ')'
+ {
+ PartitionBoundAutoSpec *n = makeNode(PartitionBoundAutoSpec);
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatumsList = list_make1($4);
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ | values_in_clause ',' '(' expr_list ')'
+ {
+ PartitionBoundAutoSpec *n = (PartitionBoundAutoSpec *) $1;
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatumsList = lappend(n->listdatumsList, $4);
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ ;
+
+opt_default_partition_clause:
+ DEFAULT PARTITION qualified_name
+ {
+ $$ = $3;
+ }
+ | /* EMPTY */
+ { $$ = NULL; }
+ ;
+
table_access_method_clause:
USING name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 015b0538e3..2be55b1a91 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -75,6 +75,7 @@
/* State shared by transformCreateStmt and its subroutines */
typedef struct
{
+ CreateStmt *stmt; /* initial statement */
ParseState *pstate; /* overall parser state */
const char *stmtType; /* "CREATE [FOREIGN] TABLE" or "ALTER TABLE" */
RangeVar *relation; /* relation to create */
@@ -145,6 +146,8 @@ static Const *transformPartitionBoundValue(ParseState *pstate, Node *con,
const char *colName, Oid colType, int32 colTypmod,
Oid partCollation);
+static CreateStmt* initAutoPartitionCreateStmt(CreateStmtContext *cxt, char *part_relname);
+static void transformPartitionAutoCreate(CreateStmtContext *cxt, PartitionSpec* partspec);
/*
* transformCreateStmt -
@@ -235,6 +238,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.stmtType = "CREATE TABLE";
cxt.isforeign = false;
}
+ cxt.stmt = stmt;
cxt.relation = stmt->relation;
cxt.rel = NULL;
cxt.inhRelations = stmt->inhRelations;
@@ -324,6 +328,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
transformExtendedStatistics(&cxt);
+ /* Process partition definitions */
+ if (stmt->partspec && stmt->partspec->autopart)
+ transformPartitionAutoCreate(&cxt, stmt->partspec);
+
/*
* Output results.
*/
@@ -4238,3 +4246,144 @@ transformPartitionBoundValue(ParseState *pstate, Node *val,
return (Const *) value;
}
+
+
+/* init basic fields of auto generated partition */
+static CreateStmt*
+initAutoPartitionCreateStmt(CreateStmtContext *cxt, char *part_relname)
+{
+ CreateStmt *part;
+
+ part = copyObject(cxt->stmt);
+
+ part->relation = makeRangeVar(cxt->relation->schemaname,
+ part_relname, cxt->relation->location);
+
+ /* inherit persistence from parent relation */
+ part->relation->relpersistence = cxt->relation->relpersistence;
+ /* set parent table as a parent */
+ part->inhRelations = lappend(part->inhRelations, cxt->relation);
+
+ /*
+ * child table is not partitioned itself, at least now
+ * while we do not support multilevel auto partitioning
+ */
+ part->partspec = NULL;
+
+ /*
+ * Partition doesn't need a list of column definitions and constraints.
+ * They will be inherited from parent.
+ */
+ part->tableElts = NIL;
+ part->constraints = NIL;
+
+ return part;
+}
+
+/*
+ * Transform configuration into a set of partition bounds.
+ * Generate extra statements to create partition tables.
+ */
+static void
+transformPartitionAutoCreate(CreateStmtContext *cxt, PartitionSpec* partspec)
+{
+ CreateStmt *part;
+ List *partlist = NIL;
+ int i = 0;
+ PartitionBoundAutoSpec *bound = (PartitionBoundAutoSpec *) partspec->autopart;
+
+ elog(DEBUG1, "transformPartitionAutoCreate \n %s \n ", nodeToString(bound));
+
+ /*
+ * Generate regular partbounds based on autopart rule.
+ * and form create table statements from these partbounds
+ */
+ if (pg_strcasecmp(partspec->strategy, "hash") == 0)
+ {
+ if (bound->strategy != PARTITION_STRATEGY_HASH)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a hash partition"),
+ parser_errposition(cxt->pstate, exprLocation((Node *) partspec))));
+
+ for (i = 0; i < bound->modulus; i++)
+ {
+ char *part_relname;
+
+ /*
+ * Generate partition name in the format:
+ * $relname_$partnum
+ * All checks of name validity will be made afterwards in DefineRelation()
+ */
+ part_relname = psprintf("%s_%d", cxt->relation->relname, i);
+ part = initAutoPartitionCreateStmt(cxt, part_relname);
+
+ /* Actual partbound generation happens here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_HASH;
+ part->partbound->modulus = bound->modulus;
+ part->partbound->remainder = i;
+ part->partbound->is_default = false;
+
+ elog(DEBUG1,"stransformPartitionAutoCreate HASH i %d MODULUS %d \n %s\n",
+ i, bound->modulus, nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+ }
+ else if (pg_strcasecmp(partspec->strategy, "list") == 0)
+ {
+
+ int n_list_parts = list_length(bound->listdatumsList);
+
+ if (bound->strategy != PARTITION_STRATEGY_LIST)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation((Node *) partspec))));
+
+ for (i = 0; i < n_list_parts; i++)
+ {
+ char *part_relname;
+ List *listdatums = (List *)
+ list_nth(bound->listdatumsList, i);
+
+ part_relname = psprintf("%s_%d", cxt->relation->relname, i);
+ part = initAutoPartitionCreateStmt(cxt, part_relname);
+
+ /* Actual partbound generation happens here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_LIST;
+ part->partbound->listdatums = list_copy(listdatums);
+ part->partbound->is_default = false;
+
+ elog(DEBUG1,"Debug transformPartitionAutoCreate LIST i %d \n %s\n",
+ i, nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+
+ if (bound->default_partition_rv)
+ {
+
+ char *part_relname = bound->default_partition_rv->relname;
+ part = initAutoPartitionCreateStmt(cxt, part_relname);
+
+ /* TODO: Should we use fields from default_partition_rv, other than relname? */
+
+ /* Actual partbound generation happens here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_LIST;
+ part->partbound->listdatums = NULL;
+ part->partbound->is_default = true;
+
+ elog(DEBUG1,"Debug transformPartitionAutoCreate LIST default partition \n %s\n",
+ nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+ }
+
+ /* Add statements to create each partition after we create parent table */
+ cxt->alist = list_concat(cxt->alist, partlist);
+}
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7ddd8c011b..4c6bd005ea 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -477,6 +477,7 @@ typedef enum NodeTag
T_PartitionElem,
T_PartitionSpec,
T_PartitionBoundSpec,
+ T_PartitionBoundAutoSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e1aeea2560..22d0f2c827 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -795,6 +795,9 @@ typedef struct PartitionSpec
* 'range') */
List *partParams; /* List of PartitionElems */
int location; /* token location, or -1 if unknown */
+
+ Node *autopart; /* PartitionBoundAutoSpec -
+ * spec to generate bounds automatically */
} PartitionSpec;
/* Internal codes for partitioning strategies */
@@ -829,6 +832,26 @@ struct PartitionBoundSpec
int location; /* token location, or -1 if unknown */
};
+/*
+ * PartitionBoundAutoSpec - a partition bound specification
+ * for auto generated partitions.
+ *
+ * This represents the rule of generating partition bounds
+ */
+struct PartitionBoundAutoSpec
+{
+ NodeTag type;
+
+ char strategy; /* see PARTITION_STRATEGY codes above */
+
+ /* Partitioning info for HASH strategy: */
+ int modulus;
+
+ /* Partitioning info for LIST strategy: */
+ List *listdatumsList; /* List of lists of Consts (or A_Consts in raw tree) */
+ RangeVar *default_partition_rv; /* Name of default list partition */
+};
+
/*
* PartitionRangeDatum - one of the values in a range partition bound
*
diff --git a/src/include/partitioning/partdefs.h b/src/include/partitioning/partdefs.h
index 6414e2c116..25ecfbd1de 100644
--- a/src/include/partitioning/partdefs.h
+++ b/src/include/partitioning/partdefs.h
@@ -19,6 +19,8 @@ typedef struct PartitionKeyData *PartitionKey;
typedef struct PartitionBoundSpec PartitionBoundSpec;
+typedef struct PartitionBoundAutoSpec PartitionBoundAutoSpec;
+
typedef struct PartitionDescData *PartitionDesc;
typedef struct PartitionDirectoryData *PartitionDirectory;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index ed8c01b8de..00a296f376 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -1311,3 +1311,435 @@ Indexes:
"part_column_drop_1_10_expr_idx1" btree ((d = 2))
drop table part_column_drop;
+-- Auto generated partitions
+-- must fail because of wrong configuration
+CREATE TABLE tbl_hash_fail (i int) PARTITION BY HASH (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+ERROR: invalid bound specification for a hash partition
+LINE 1: CREATE TABLE tbl_hash_fail (i int) PARTITION BY HASH (i)
+ ^
+-- must fail because of wrong configuration
+CREATE TABLE tbl_list_fail (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (1, 3));
+ERROR: partition "tbl_list_fail_1" would overlap partition "tbl_list_fail_0"
+LINE 2: CONFIGURATION (values in (1, 2), (1, 3));
+ ^
+CREATE TABLE tbl_list (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+\d+ tbl_list
+ Partitioned table "public.tbl_list"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ i | integer | | | | plain | |
+Partition key: LIST (i)
+Partitions: tbl_list_0 FOR VALUES IN (1, 2),
+ tbl_list_1 FOR VALUES IN (3, 4),
+ tbl_default DEFAULT
+
+CREATE TABLE tbl_hash (i int) PARTITION BY HASH (i)
+CONFIGURATION (modulus 3);
+\d+ tbl_hash
+ Partitioned table "public.tbl_hash"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ i | integer | | | | plain | |
+Partition key: HASH (i)
+Partitions: tbl_hash_0 FOR VALUES WITH (modulus 3, remainder 0),
+ tbl_hash_1 FOR VALUES WITH (modulus 3, remainder 1),
+ tbl_hash_2 FOR VALUES WITH (modulus 3, remainder 2)
+
+DROP TABLE tbl_list;
+DROP TABLE tbl_hash;
+-- forbidden expressions for partition bound with list partitioned table
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (somename));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename.somename));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (somename.somename));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (a));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (a));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(a)));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (sum(a)));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(somename)));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (sum(somename)));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(1)));
+ERROR: aggregate functions are not allowed in partition bound
+LINE 2: (VALUES IN (sum(1)));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((select 1)));
+ERROR: cannot use subquery in partition bound
+LINE 2: (VALUES IN ((select 1)));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (generate_series(4, 6)));
+ERROR: set-returning functions are not allowed in partition bound
+LINE 2: (VALUES IN (generate_series(4, 6)));
+ ^
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((1+1) collate "POSIX"));
+ERROR: collations are not supported by type integer
+LINE 2: (VALUES IN ((1+1) collate "POSIX"));
+ ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ());
+ERROR: syntax error at or near ")"
+LINE 2: (VALUES IN ());
+ ^
+-- trying to specify range for list partitioned table
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES FROM (1) TO (2));
+ERROR: syntax error at or near "FROM"
+LINE 2: (VALUES FROM (1) TO (2));
+ ^
+-- trying to specify modulus and remainder for list partitioned table
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(MODULUS 10);
+ERROR: invalid bound specification for a list partition
+LINE 1: CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) ...
+ ^
+-- must succeed
+CREATE TABLE list_parted (a int)
+PARTITION BY LIST (a)
+CONFIGURATION (values in ('1'), (2), (2+1), (null) DEFAULT PARTITION part_default);
+\d+ list_parted
+ Partitioned table "public.list_parted"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Partition key: LIST (a)
+Partitions: list_parted_0 FOR VALUES IN (1),
+ list_parted_1 FOR VALUES IN (2),
+ list_parted_2 FOR VALUES IN (3),
+ list_parted_3 FOR VALUES IN (NULL),
+ part_default DEFAULT
+
+-- check default partition cannot be created more than once
+CREATE TABLE fail_default_part PARTITION OF list_parted DEFAULT;
+ERROR: partition "fail_default_part" conflicts with existing default partition "part_default"
+LINE 1: ...TE 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) CONFIGURATION (VALUES IN (1));
+ERROR: specified value cannot be cast to type boolean for column "a"
+LINE 1: ...a bool) PARTITION BY LIST (a) CONFIGURATION (VALUES IN (1));
+ ^
+-- specified literal can be cast, and the cast might not be immutable
+CREATE TABLE moneyp (a money) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10), ('11'), (to_char(12, '99')::int));
+DROP TABLE moneyp;
+-- cast is immutable
+CREATE TABLE bigintp (a bigint) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10));
+-- fails due to overlap:
+CREATE TABLE bigintp_overlap PARTITION OF bigintp FOR VALUES IN ('10');
+ERROR: partition "bigintp_overlap" would overlap partition "bigintp_0"
+LINE 1: ...E bigintp_overlap PARTITION OF bigintp FOR VALUES IN ('10');
+ ^
+DROP TABLE bigintp;
+CREATE TABLE hash_parted (a int) PARTITION BY HASH (a) CONFIGURATION (MODULUS 10);
+-- all remainder values are already belong to partitions
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODULUS 30, REMAINDER 3);
+ERROR: partition "fail_part" would overlap partition "hash_parted_3"
+LINE 1: ...BLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODU...
+ ^
+-- trying to specify range for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a', 1) TO ('z');
+ERROR: invalid bound specification for a hash partition
+LINE 1: ...BLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a',...
+ ^
+-- trying to specify list value for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000);
+ERROR: invalid bound specification for a hash partition
+LINE 1: ...BLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000);
+ ^
+-- trying to create default partition for the hash partitioned table
+CREATE TABLE fail_part (a int) PARTITION BY HASH (a) CONFIGURATION (MODULUS 10
+ DEFAULT hash_default);
+ERROR: syntax error at or near "DEFAULT"
+LINE 2: DEFAULT hash_default);
+ ^
+-- cannot create auto partition of a non-partitioned table
+CREATE TABLE fail_part (a int) CONFIGURATION (MODULUS 10);
+ERROR: syntax error at or near "CONFIGURATION"
+LINE 1: CREATE TABLE fail_part (a int) CONFIGURATION (MODULUS 10);
+ ^
+-- partition table inherits relation persistence setting from parent
+CREATE TEMP TABLE temp_parted (a char) PARTITION BY LIST (a)
+CONFIGURATION (VALUES IN ('a') DEFAULT PARTITION temp_parted_default);
+\d temp_parted
+ Partitioned table "pg_temp_3.temp_parted"
+ Column | Type | Collation | Nullable | Default
+--------+--------------+-----------+----------+---------
+ a | character(1) | | |
+Partition key: LIST (a)
+Number of partitions: 2 (Use \d+ to list them.)
+
+-- partition table inherits relation persistence setting from parent
+CREATE UNLOGGED TABLE unlogged_parted (a char) PARTITION BY LIST (a)
+CONFIGURATION (VALUES IN ('a') DEFAULT PARTITION unlogged_parted_default);
+\d unlogged_parted
+ Unlogged partitioned table "public.unlogged_parted"
+ Column | Type | Collation | Nullable | Default
+--------+--------------+-----------+----------+---------
+ a | character(1) | | |
+Partition key: LIST (a)
+Number of partitions: 2 (Use \d+ to list them.)
+
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE fail_parted2 (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),(null) DEFAULT partition tbl_default);
+ERROR: partition "fail_parted2_2" would overlap partition "fail_parted2_0"
+LINE 2: (VALUES IN (null, 'z'),('a', 'b'),(null) DEFAULT partition t...
+ ^
+CREATE TABLE fail_parted2 (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),('b', 'c') DEFAULT partition tbl_default);
+ERROR: partition "fail_parted2_2" would overlap partition "fail_parted2_1"
+LINE 2: (VALUES IN (null, 'z'),('a', 'b'),('b', 'c') DEFAULT partiti...
+ ^
+-- check default partition overlap
+CREATE TABLE list_parted2 (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b') DEFAULT partition tbl_default);
+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 "tbl_default" would be violated by some row
+-- check schema propagation from parent
+CREATE TABLE parted (a text, b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0))
+PARTITION BY LIST (a) CONFIGURATION ( VALUES IN ('a','b'),('d') );
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute
+ WHERE attrelid = 'parted_1'::regclass and attnum > 0
+ ORDER BY attnum;
+ attname | attislocal | attinhcount
+---------+------------+-------------
+ a | f | 1
+ b | f | 1
+(2 rows)
+
+-- able to specify column default, column constraint, and table constraint
+-- first check the "column specified more than once" error
+CREATE TABLE part_e_fail PARTITION OF parted (
+ b NOT NULL,
+ b DEFAULT 1,
+ b CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('e');
+ERROR: column "b" specified more than once
+CREATE TABLE part_e PARTITION OF parted (
+ b NOT NULL DEFAULT 1,
+ CONSTRAINT check_a CHECK (length(a) > 0),
+ CONSTRAINT check_b CHECK (b >= 0)
+) FOR VALUES IN ('e');
+NOTICE: merging constraint "check_a" with inherited definition
+-- conislocal should be false for any merged constraints, true otherwise
+SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass ORDER BY conislocal, coninhcount;
+ conname | conislocal | coninhcount
+---------+------------+-------------
+ check_a | f | 1
+ check_b | t | 0
+(2 rows)
+
+-- check_a can not be dropped as it is inherited
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+ERROR: cannot drop inherited constraint "check_a" of relation "part_e"
+-- check_b can be dropped as it is local
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+-- Once check_b is added to the parent, it should be made non-local for part_b
+ALTER TABLE part_e ADD CONSTRAINT check_b CHECK (b >= 0);
+ALTER TABLE parted ADD CONSTRAINT check_b CHECK (b >= 0);
+NOTICE: merging constraint "check_b" with inherited definition
+SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+ conname | conislocal | coninhcount
+---------+------------+-------------
+ check_a | f | 1
+ check_b | f | 1
+(2 rows)
+
+-- Neither check_a nor check_b are droppable from part_b
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+ERROR: cannot drop inherited constraint "check_a" of relation "part_e"
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+ERROR: cannot drop inherited constraint "check_b" of relation "part_e"
+-- And dropping it from parted should leave no trace of them on part_e, unlike
+-- traditional inheritance where they will be left behind, because they would
+-- be local constraints.
+ALTER TABLE parted DROP CONSTRAINT check_a, DROP CONSTRAINT check_b;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+ conislocal | coninhcount
+------------+-------------
+(0 rows)
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY HASH(c);
+ERROR: column "c" named in partition key does not exist
+LINE 1: ...ARTITION OF parted FOR VALUES IN ('c') PARTITION BY HASH(c);
+ ^
+CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0) FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- check that NOT NULL and default value are inherited correctly
+create table parted_notnull_inh_test (a int default 1, b int not null default 0) partition by list (a) CONFIGURATION (VALUES IN (1));
+insert into parted_notnull_inh_test (b) values (null);
+ERROR: null value in column "b" of relation "parted_notnull_inh_test_0" violates not-null constraint
+DETAIL: Failing row contains (1, null).
+-- note that a's default is preserved
+\d parted_notnull_inh_test1
+drop table parted_notnull_inh_test;
+-- Partition bound in describe output
+\d+ part_e
+ Table "public.part_e"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | not null | 1 | plain | |
+Partition of: parted FOR VALUES IN ('e')
+Partition constraint: ((a IS NOT NULL) AND (a = 'e'::text))
+
+-- Both partition bound and partition key in describe output
+\d+ part_c
+ Partitioned table "public.part_c"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | not null | 0 | plain | |
+Partition of: parted FOR VALUES IN ('c')
+Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text))
+Partition key: RANGE (b)
+Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
+
+-- a level-2 partition's constraint will include the parent's expressions
+\d+ part_c_1_10
+ Table "public.part_c_1_10"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | not null | 0 | plain | |
+Partition of: part_c FOR VALUES FROM (1) TO (10)
+Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text) AND (b IS NOT NULL) AND (b >= 1) AND (b < 10))
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+ Partitioned table "public.parted"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | text | | |
+ b | integer | | not null | 0
+Partition key: LIST (a)
+Number of partitions: 4 (Use \d+ to list them.)
+
+\d hash_parted
+ Partitioned table "public.hash_parted"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition key: HASH (a)
+Number of partitions: 10 (Use \d+ to list them.)
+
+-- cleanup
+DROP TABLE parted;
+DROP TABLE list_parted;
+DROP TABLE list_parted2;
+DROP TABLE hash_parted;
+DROP TABLE temp_parted;
+DROP TABLE unlogged_parted;
+-- list partitioning on array type column
+CREATE TABLE arrlp (a int[]) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ('{1}', '{2}'));
+\d+ arrlp_1
+DROP TABLE arrlp;
+-- partition on boolean column
+create table boolspart (a bool) partition by list (a) CONFIGURATION
+(values in (true), (false));
+\d+ boolspart
+ Partitioned table "public.boolspart"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | boolean | | | | plain | |
+Partition key: LIST (a)
+Partitions: boolspart_0 FOR VALUES IN (true),
+ boolspart_1 FOR VALUES IN (false)
+
+drop table boolspart;
+-- test using a volatile expression as partition bound
+create table volatile_partbound_test (partkey timestamp) partition by list (partkey) CONFIGURATION (values in ('1970-01-01 00:00:00+00'::timestamp, current_timestamp),('1982-01-25 00:00:00+00'::timestamp));
+drop table volatile_partbound_test;
+-- tests of column drop with partition tables and indexes using
+-- predicates and expressions.
+create table part_column_drop (useless_1 int, id int, useless_2 int, d int,
+ b int, useless_3 int) partition by hash (id) CONFIGURATION (modulus 3);
+alter table part_column_drop drop column useless_1;
+alter table part_column_drop drop column useless_2;
+alter table part_column_drop drop column useless_3;
+create index part_column_drop_b_pred on part_column_drop(b) where b = 1;
+create index part_column_drop_b_expr on part_column_drop((b = 1));
+create index part_column_drop_d_pred on part_column_drop(d) where d = 2;
+create index part_column_drop_d_expr on part_column_drop((d = 2));
+create index part_column_drop_d_1_pred on part_column_drop_1(d) where d = 2;
+create index part_column_drop_d_1_expr on part_column_drop_1((d = 2));
+\d part_column_drop
+ Partitioned table "public.part_column_drop"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ d | integer | | |
+ b | integer | | |
+Partition key: HASH (id)
+Indexes:
+ "part_column_drop_b_expr" btree ((b = 1))
+ "part_column_drop_b_pred" btree (b) WHERE b = 1
+ "part_column_drop_d_expr" btree ((d = 2))
+ "part_column_drop_d_pred" btree (d) WHERE d = 2
+Number of partitions: 3 (Use \d+ to list them.)
+
+\d part_column_drop_1
+ Table "public.part_column_drop_1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ d | integer | | |
+ b | integer | | |
+Partition of: part_column_drop FOR VALUES WITH (modulus 3, remainder 1)
+Indexes:
+ "part_column_drop_1_b_idx" btree (b) WHERE b = 1
+ "part_column_drop_1_d_idx" btree (d) WHERE d = 2
+ "part_column_drop_1_expr_idx" btree ((b = 1))
+ "part_column_drop_1_expr_idx1" btree ((d = 2))
+ "part_column_drop_d_1_expr" btree ((d = 2))
+ "part_column_drop_d_1_pred" btree (d) WHERE d = 2
+
+\d part_column_drop_2
+ Table "public.part_column_drop_2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ d | integer | | |
+ b | integer | | |
+Partition of: part_column_drop FOR VALUES WITH (modulus 3, remainder 2)
+Indexes:
+ "part_column_drop_2_b_idx" btree (b) WHERE b = 1
+ "part_column_drop_2_d_idx" btree (d) WHERE d = 2
+ "part_column_drop_2_expr_idx" btree ((b = 1))
+ "part_column_drop_2_expr_idx1" btree ((d = 2))
+
+\d part_column_drop_3
+drop table part_column_drop;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index d257679ba6..53bead5e85 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -972,3 +972,241 @@ create table part_column_drop_1_10 partition of
\d part_column_drop
\d part_column_drop_1_10
drop table part_column_drop;
+
+-- Auto generated partitions
+
+-- must fail because of wrong configuration
+CREATE TABLE tbl_hash_fail (i int) PARTITION BY HASH (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+
+-- must fail because of wrong configuration
+CREATE TABLE tbl_list_fail (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (1, 3));
+
+CREATE TABLE tbl_list (i int) PARTITION BY LIST (i)
+CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
+
+\d+ tbl_list
+
+CREATE TABLE tbl_hash (i int) PARTITION BY HASH (i)
+CONFIGURATION (modulus 3);
+
+\d+ tbl_hash
+
+DROP TABLE tbl_list;
+DROP TABLE tbl_hash;
+
+-- forbidden expressions for partition bound with list partitioned table
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename.somename));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (a));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(a)));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(somename)));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(1)));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((select 1)));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (generate_series(4, 6)));
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((1+1) collate "POSIX"));
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ());
+-- trying to specify range for list partitioned table
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES FROM (1) TO (2));
+-- trying to specify modulus and remainder for list partitioned table
+CREATE TABLE list_parted_fail (a int) PARTITION BY LIST (a) CONFIGURATION
+(MODULUS 10);
+
+-- must succeed
+CREATE TABLE list_parted (a int)
+PARTITION BY LIST (a)
+CONFIGURATION (values in ('1'), (2), (2+1), (null) DEFAULT PARTITION part_default);
+\d+ list_parted
+
+-- check default partition cannot be created more than once
+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) CONFIGURATION (VALUES IN (1));
+
+-- specified literal can be cast, and the cast might not be immutable
+CREATE TABLE moneyp (a money) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10), ('11'), (to_char(12, '99')::int));
+DROP TABLE moneyp;
+
+-- cast is immutable
+CREATE TABLE bigintp (a bigint) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10));
+-- fails due to overlap:
+CREATE TABLE bigintp_overlap PARTITION OF bigintp FOR VALUES IN ('10');
+DROP TABLE bigintp;
+
+CREATE TABLE hash_parted (a int) PARTITION BY HASH (a) CONFIGURATION (MODULUS 10);
+-- all remainder values are already belong to partitions
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODULUS 30, REMAINDER 3);
+-- trying to specify range for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a', 1) TO ('z');
+-- trying to specify list value for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000);
+-- trying to create default partition for the hash partitioned table
+CREATE TABLE fail_part (a int) PARTITION BY HASH (a) CONFIGURATION (MODULUS 10
+ DEFAULT hash_default);
+
+-- cannot create auto partition of a non-partitioned table
+CREATE TABLE fail_part (a int) CONFIGURATION (MODULUS 10);
+
+-- partition table inherits relation persistence setting from parent
+CREATE TEMP TABLE temp_parted (a char) PARTITION BY LIST (a)
+CONFIGURATION (VALUES IN ('a') DEFAULT PARTITION temp_parted_default);
+\d temp_parted
+
+-- partition table inherits relation persistence setting from parent
+CREATE UNLOGGED TABLE unlogged_parted (a char) PARTITION BY LIST (a)
+CONFIGURATION (VALUES IN ('a') DEFAULT PARTITION unlogged_parted_default);
+\d unlogged_parted
+
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE fail_parted2 (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),(null) DEFAULT partition tbl_default);
+
+CREATE TABLE fail_parted2 (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),('b', 'c') DEFAULT partition tbl_default);
+
+-- check default partition overlap
+CREATE TABLE list_parted2 (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b') DEFAULT partition tbl_default);
+INSERT INTO list_parted2 VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted2 FOR VALUES IN ('W', 'X', 'Y');
+
+-- check schema propagation from parent
+
+CREATE TABLE parted (a text, b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0))
+PARTITION BY LIST (a) CONFIGURATION ( VALUES IN ('a','b'),('d') );
+
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute
+ WHERE attrelid = 'parted_1'::regclass and attnum > 0
+ ORDER BY attnum;
+
+-- able to specify column default, column constraint, and table constraint
+
+-- first check the "column specified more than once" error
+CREATE TABLE part_e_fail PARTITION OF parted (
+ b NOT NULL,
+ b DEFAULT 1,
+ b CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('e');
+
+CREATE TABLE part_e PARTITION OF parted (
+ b NOT NULL DEFAULT 1,
+ CONSTRAINT check_a CHECK (length(a) > 0),
+ CONSTRAINT check_b CHECK (b >= 0)
+) FOR VALUES IN ('e');
+-- conislocal should be false for any merged constraints, true otherwise
+SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass ORDER BY conislocal, coninhcount;
+
+-- check_a can not be dropped as it is inherited
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+-- check_b can be dropped as it is local
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+
+-- Once check_b is added to the parent, it should be made non-local for part_b
+ALTER TABLE part_e ADD CONSTRAINT check_b CHECK (b >= 0);
+ALTER TABLE parted ADD CONSTRAINT check_b CHECK (b >= 0);
+SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+
+-- Neither check_a nor check_b are droppable from part_b
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+
+-- And dropping it from parted should leave no trace of them on part_e, unlike
+-- traditional inheritance where they will be left behind, because they would
+-- be local constraints.
+ALTER TABLE parted DROP CONSTRAINT check_a, DROP CONSTRAINT check_b;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY HASH(c);
+CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0) FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+
+-- check that NOT NULL and default value are inherited correctly
+create table parted_notnull_inh_test (a int default 1, b int not null default 0) partition by list (a) CONFIGURATION (VALUES IN (1));
+insert into parted_notnull_inh_test (b) values (null);
+-- note that a's default is preserved
+\d parted_notnull_inh_test1
+drop table parted_notnull_inh_test;
+
+-- Partition bound in describe output
+\d+ part_e
+
+-- Both partition bound and partition key in describe output
+\d+ part_c
+
+-- a level-2 partition's constraint will include the parent's expressions
+\d+ part_c_1_10
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+\d hash_parted
+
+-- cleanup
+DROP TABLE parted;
+DROP TABLE list_parted;
+DROP TABLE list_parted2;
+DROP TABLE hash_parted;
+DROP TABLE temp_parted;
+DROP TABLE unlogged_parted;
+
+
+-- list partitioning on array type column
+CREATE TABLE arrlp (a int[]) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ('{1}', '{2}'));
+\d+ arrlp_1
+DROP TABLE arrlp;
+
+-- partition on boolean column
+create table boolspart (a bool) partition by list (a) CONFIGURATION
+(values in (true), (false));
+\d+ boolspart
+drop table boolspart;
+
+-- test using a volatile expression as partition bound
+create table volatile_partbound_test (partkey timestamp) partition by list (partkey) CONFIGURATION (values in ('1970-01-01 00:00:00+00'::timestamp, current_timestamp),('1982-01-25 00:00:00+00'::timestamp));
+drop table volatile_partbound_test;
+
+-- tests of column drop with partition tables and indexes using
+-- predicates and expressions.
+create table part_column_drop (useless_1 int, id int, useless_2 int, d int,
+ b int, useless_3 int) partition by hash (id) CONFIGURATION (modulus 3);
+alter table part_column_drop drop column useless_1;
+alter table part_column_drop drop column useless_2;
+alter table part_column_drop drop column useless_3;
+create index part_column_drop_b_pred on part_column_drop(b) where b = 1;
+create index part_column_drop_b_expr on part_column_drop((b = 1));
+create index part_column_drop_d_pred on part_column_drop(d) where d = 2;
+create index part_column_drop_d_expr on part_column_drop((d = 2));
+create index part_column_drop_d_1_pred on part_column_drop_1(d) where d = 2;
+create index part_column_drop_d_1_expr on part_column_drop_1((d = 2));
+
+\d part_column_drop
+\d part_column_drop_1
+\d part_column_drop_2
+\d part_column_drop_3
+drop table part_column_drop;
--
2.28.0
https://commitfest.postgresql.org/32/2694/
I don't know what committers will say, but I think that "ALTER TABLE" might be
the essential thing for this patch to support, not "CREATE". (This is similar
to ALTER..SET STATISTICS, which is not allowed in CREATE.)
The reason is that ALTER is what's important for RANGE partitions, which need
to be created dynamically (for example, to support time-series data
continuously inserting data around 'now'). I assume it's sometimes also
important for LIST. I think this patch should handle those cases better before
being commited, or else we risk implementing grammar and other user-facing interface
that fails to handle what's needed into the future (or that's non-essential).
Even if dynamic creation isn't implemented yet, it seems important to at least
implement the foundation for setting the configuration to *allow* that in the
future, in a manner that's consistent with the initial implementation for
"static" partitions.
ALTER also supports other ideas I mentioned here:
/messages/by-id/20200706145947.GX4107@telsasoft.com
- ALTER .. SET interval (for dynamic/deferred RANGE partitioning)
- ALTER .. SET modulus, for HASH partitioning, in the initial implementation,
this would allow CREATING paritions, but wouldn't attempt to handle moving
data if overlapping table already exists:
- Could also set the table-name, maybe by format string;
- Could set "retention interval" for range partitioning;
- Could set if the partitions are themselves partitioned(??)
I think once you allow setting configuration parameters like this, then you
might have an ALTER command to "effect" them, which would create any static
tables required by the configuration. maybe that'd be automatic, but if
there's an "ALTER .. APPLY PARTITIONS" command (or whatever), maybe in the
future, the command could also be used to "repartition" existing table data
into partitions with more fine/course granularity (modulus, or daily vs monthly
range, etc).
--
Justin
I have reviewed the v4 patch. The patch does not get applied on the latest
source. Kindly rebase.
However I have found few comments.
1.
+-- must fail because of wrong configuration +CREATE TABLE tbl_hash_fail (i int) PARTITION BY HASH (i) +CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);
Here some of the keywords are mentioned in UPPER CASE (Ex: CREATE TABLE,
CONFIGURATION, etc) and some are mentioned in lower case (Ex: values in,
default partition, etc). Kindly make it common. I feel making it to UPPER
CASE is better. Please take care of this in all the cases.
2. It is better to separate the failure cases and success cases in
/src/test/regress/sql/create_table.sql for better readability. All the
failure cases can be listed first and then the success cases.
3.
+ char *part_relname; + + /* + * Generate partition name in the format: + * $relname_$partnum + * All checks of name validity will be made afterwards in
DefineRelation()
+ */ + part_relname = psprintf("%s_%d", cxt->relation->relname, i);
The assignment can be done directly while declaring the variable.
Thanks & Regards,
Nitin Jadhav
On Wed, Mar 3, 2021 at 1:56 AM Justin Pryzby <pryzby@telsasoft.com> wrote:
Show quoted text
https://commitfest.postgresql.org/32/2694/
I don't know what committers will say, but I think that "ALTER TABLE"
might be
the essential thing for this patch to support, not "CREATE". (This is
similar
to ALTER..SET STATISTICS, which is not allowed in CREATE.)The reason is that ALTER is what's important for RANGE partitions, which
need
to be created dynamically (for example, to support time-series data
continuously inserting data around 'now'). I assume it's sometimes also
important for LIST. I think this patch should handle those cases better
before
being commited, or else we risk implementing grammar and other user-facing
interface
that fails to handle what's needed into the future (or that's
non-essential).
Even if dynamic creation isn't implemented yet, it seems important to at
least
implement the foundation for setting the configuration to *allow* that in
the
future, in a manner that's consistent with the initial implementation for
"static" partitions.ALTER also supports other ideas I mentioned here:
/messages/by-id/20200706145947.GX4107@telsasoft.com- ALTER .. SET interval (for dynamic/deferred RANGE partitioning)
- ALTER .. SET modulus, for HASH partitioning, in the initial
implementation,
this would allow CREATING paritions, but wouldn't attempt to handle
moving
data if overlapping table already exists:
- Could also set the table-name, maybe by format string;
- Could set "retention interval" for range partitioning;
- Could set if the partitions are themselves partitioned(??)I think once you allow setting configuration parameters like this, then you
might have an ALTER command to "effect" them, which would create any static
tables required by the configuration. maybe that'd be automatic, but if
there's an "ALTER .. APPLY PARTITIONS" command (or whatever), maybe in the
future, the command could also be used to "repartition" existing table data
into partitions with more fine/course granularity (modulus, or daily vs
monthly
range, etc).--
Justin
I have reviewed the v4 patch. The patch does not get applied on the latest
source. Kindly rebase.
However I have found few comments.1.
+-- must fail because of wrong configuration +CREATE TABLE tbl_hash_fail (i int) PARTITION BY HASH (i) +CONFIGURATION (values in (1, 2), (3, 4) default partition tbl_default);Here some of the keywords are mentioned in UPPER CASE (Ex: CREATE TABLE,
CONFIGURATION, etc) and some are mentioned in lower case (Ex: values in,
default partition, etc). Kindly make it common. I feel making it to UPPER
CASE is better. Please take care of this in all the cases.2. It is better to separate the failure cases and success cases in
/src/test/regress/sql/create_table.sql for better readability. All the
failure cases can be listed first and then the success cases.3.
+ char *part_relname; + + /* + * Generate partition name in the format: + * $relname_$partnum + * All checks of name validity will be made afterwards inDefineRelation()
+ */ + part_relname = psprintf("%s_%d", cxt->relation->relname, i);The assignment can be done directly while declaring the variable.
Thank you for your review!
I've rebased the patch and made the changes mentioned.
PFA v5.
--
Best regards,
Pavel Borisov
Postgres Professional: http://postgrespro.com <http://www.postgrespro.com>
Attachments:
v5-0001-Automatically-generate-partitions-by-LIST-and-HAS.patchapplication/octet-stream; name=v5-0001-Automatically-generate-partitions-by-LIST-and-HAS.patchDownload
From ef0fcba607641aef94a9ce9b0393e607ba476ba4 Mon Sep 17 00:00:00 2001
From: Pavel Borisov <pashkin.elfe@gmail.com>
Date: Fri, 9 Jul 2021 00:34:21 +0400
Subject: [PATCH v5] Automatically generate partitions by LIST and HASH
A patch adds CREATE TABLE statement syntax and functions to create both
a parent partitioned table and child partitions at once based on a
partitioning rules. The created partitions set can be manipulated using
existing expressions like DETACH, CREATE TABLE.. PARTITION OF etc.
It is the first step towards more complicated:
(1) partitioning BY RANGE,
(2) automatic deferred child partition creation on a first try to
insert a row with which fullfills a condition for the partition etc.
---
doc/src/sgml/ref/create_table.sgml | 49 +++
src/backend/nodes/copyfuncs.c | 17 +
src/backend/nodes/equalfuncs.c | 17 +
src/backend/nodes/outfuncs.c | 16 +
src/backend/nodes/readfuncs.c | 15 +
src/backend/parser/gram.y | 82 +++-
src/backend/parser/parse_utilcmd.c | 142 +++++++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 23 ++
src/include/partitioning/partdefs.h | 2 +
src/test/regress/expected/create_table.out | 437 +++++++++++++++++++++
src/test/regress/sql/create_table.sql | 235 +++++++++++
12 files changed, 1033 insertions(+), 3 deletions(-)
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 15aed2f251..2708e7d602 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -29,6 +29,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ]
[ USING <replaceable class="parameter">method</replaceable> ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -41,6 +42,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
) ]
[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ]
[ USING <replaceable class="parameter">method</replaceable> ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -53,6 +55,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
) ] { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT }
[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ]
[ USING <replaceable class="parameter">method</replaceable> ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -96,6 +99,11 @@ FROM ( { <replaceable class="parameter">partition_bound_expr</replaceable> | MIN
TO ( { <replaceable class="parameter">partition_bound_expr</replaceable> | MINVALUE | MAXVALUE } [, ...] ) |
WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REMAINDER <replaceable class="parameter">numeric_literal</replaceable> )
+<phrase>and <replaceable class="parameter">partition_bound_auto_spec</replaceable> is:</phrase>
+
+VALUES IN ( <replaceable class="parameter">partition_bound_expr</replaceable> [, ...] ), [( <replaceable class="parameter">partition_bound_expr</replaceable> [, ...] )] [, ...] [DEFAULT PARTITION <replaceable class="parameter">default_part_name</replaceable>]
+MODULUS <replaceable class="parameter">numeric_literal</replaceable>
+
<phrase><replaceable class="parameter">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
[ INCLUDE ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ]
@@ -409,6 +417,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
however, you can define these constraints on individual partitions.
</para>
+ <para>
+ Hash and list partitioning also support automatic creation of partitions
+ with an optional <literal>CONFIGURATION</literal> clause.
+ </para>
+
<para>
See <xref linkend="ddl-partitioning"/> for more discussion on table
partitioning.
@@ -417,6 +430,42 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ] </literal></term>
+ <listitem>
+ <para>
+ The optional <literal>CONFIGURATION</literal> clause used together
+ with <literal>PARTITION BY</literal> specifies a rule of generating bounds
+ for partitions of the partitioned table. All partitions are created automatically
+ along with the parent table.
+
+ Any indexes, constraints and user-defined row-level triggers that exist
+ in the parent table are cloned on the new partitions. When using this clause,
+ <literal> CREATE TABLE </literal> statement can only contain clauses, that are
+ applicable to both relation kinds: partitioned table and regular partition tables.
+ All tables created by the statement will use same parameters, such as
+ relation persistence.
+ </para>
+
+ <para>
+ The <replaceable class="parameter">partition_bound_auto_spec</replaceable>
+ must correspond to the partitioning method and partition key of the
+ parent table, and must not overlap with any existing partition of that
+ parent. The form with <literal>VALUES IN</literal> is used for list partitioning
+ and the form with <literal>MODULUS</literal> is used for hash partitioning.
+ List partitioning can also provide a default partition using
+ <literal>DEFAULT PARTITION</literal>.
+ </para>
+
+ <para>
+ Automatic range partitioning is not supported yet.
+ </para>
+
+
+
+ </listitem>
+ </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> | DEFAULT }</literal></term>
<listitem>
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index bd87f23784..ac58556a00 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4766,6 +4766,7 @@ _copyPartitionSpec(const PartitionSpec *from)
COPY_STRING_FIELD(strategy);
COPY_NODE_FIELD(partParams);
+ COPY_NODE_FIELD(autopart);
COPY_LOCATION_FIELD(location);
return newnode;
@@ -4788,6 +4789,19 @@ _copyPartitionBoundSpec(const PartitionBoundSpec *from)
return newnode;
}
+static PartitionBoundAutoSpec *
+_copyPartitionBoundAutoSpec(const PartitionBoundAutoSpec *from)
+{
+ PartitionBoundAutoSpec *newnode = makeNode(PartitionBoundAutoSpec);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_SCALAR_FIELD(modulus);
+ COPY_NODE_FIELD(listdatumsList);
+ COPY_NODE_FIELD(default_partition_rv);
+
+ return newnode;
+}
+
static PartitionRangeDatum *
_copyPartitionRangeDatum(const PartitionRangeDatum *from)
{
@@ -5859,6 +5873,9 @@ copyObjectImpl(const void *from)
case T_PartitionBoundSpec:
retval = _copyPartitionBoundSpec(from);
break;
+ case T_PartitionBoundAutoSpec:
+ retval = _copyPartitionBoundAutoSpec(from);
+ break;
case T_PartitionRangeDatum:
retval = _copyPartitionRangeDatum(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index dba3e6b31e..58db5c036e 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2980,6 +2980,7 @@ _equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b)
COMPARE_STRING_FIELD(strategy);
COMPARE_NODE_FIELD(partParams);
COMPARE_LOCATION_FIELD(location);
+ COMPARE_NODE_FIELD(autopart);
return true;
}
@@ -2999,6 +3000,19 @@ _equalPartitionBoundSpec(const PartitionBoundSpec *a, const PartitionBoundSpec *
return true;
}
+static bool
+_equalPartitionBoundAutoSpec(const PartitionBoundAutoSpec *a,
+ const PartitionBoundAutoSpec *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_SCALAR_FIELD(modulus);
+ COMPARE_NODE_FIELD(listdatumsList);
+ COMPARE_NODE_FIELD(default_partition_rv);
+
+ return true;
+}
+
+
static bool
_equalPartitionRangeDatum(const PartitionRangeDatum *a, const PartitionRangeDatum *b)
{
@@ -3850,6 +3864,9 @@ equal(const void *a, const void *b)
case T_PartitionBoundSpec:
retval = _equalPartitionBoundSpec(a, b);
break;
+ case T_PartitionBoundAutoSpec:
+ retval = _equalPartitionBoundAutoSpec(a, b);
+ break;
case T_PartitionRangeDatum:
retval = _equalPartitionRangeDatum(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e32b92e299..9b80085e06 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3791,6 +3791,7 @@ _outPartitionSpec(StringInfo str, const PartitionSpec *node)
WRITE_STRING_FIELD(strategy);
WRITE_NODE_FIELD(partParams);
+ WRITE_NODE_FIELD(autopart);
WRITE_LOCATION_FIELD(location);
}
@@ -3809,6 +3810,18 @@ _outPartitionBoundSpec(StringInfo str, const PartitionBoundSpec *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionBoundAutoSpec(StringInfo str, const PartitionBoundAutoSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBOUNDAUTOSPEC");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_INT_FIELD(modulus);
+ WRITE_NODE_FIELD(listdatumsList);
+ WRITE_NODE_FIELD(default_partition_rv);
+
+}
+
static void
_outPartitionRangeDatum(StringInfo str, const PartitionRangeDatum *node)
{
@@ -4515,6 +4528,9 @@ outNode(StringInfo str, const void *obj)
case T_PartitionBoundSpec:
_outPartitionBoundSpec(str, obj);
break;
+ case T_PartitionBoundAutoSpec:
+ _outPartitionBoundAutoSpec(str, obj);
+ break;
case T_PartitionRangeDatum:
_outPartitionRangeDatum(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index f0b34ecfac..dbf187a9e0 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2686,6 +2686,19 @@ _readPartitionBoundSpec(void)
READ_DONE();
}
+static PartitionBoundAutoSpec *
+_readPartitionBoundAutoSpec(void)
+{
+ READ_LOCALS(PartitionBoundAutoSpec);
+
+ READ_CHAR_FIELD(strategy);
+ READ_INT_FIELD(modulus);
+ READ_NODE_FIELD(listdatumsList);
+ READ_NODE_FIELD(default_partition_rv);
+
+ READ_DONE();
+}
+
/*
* _readPartitionRangeDatum
*/
@@ -2972,6 +2985,8 @@ parseNodeString(void)
return_value = _readPartitionBoundSpec();
else if (MATCH("PARTITIONRANGEDATUM", 19))
return_value = _readPartitionRangeDatum();
+ else if (MATCH("PARTITIONBOUNDAUTOSPEC", 22))
+ return_value = _readPartitionBoundAutoSpec();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index eb24195438..078700fc73 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -256,6 +256,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionElem *partelem;
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
+ PartitionBoundAutoSpec *partboundautospec;
RoleSpec *rolespec;
struct SelectLimit *selectlimit;
SetQuantifier setquantifier;
@@ -609,7 +610,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partboundspec> PartitionBoundSpec
%type <list> hash_partbound
%type <defelt> hash_partbound_elem
-
+%type <partboundautospec> OptPartitionBoundAutoSpec values_in_clause p_desc
+%type <range> opt_default_partition_clause
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -4008,14 +4010,14 @@ OptPartitionSpec: PartitionSpec { $$ = $1; }
| /*EMPTY*/ { $$ = NULL; }
;
-PartitionSpec: PARTITION BY ColId '(' part_params ')'
+PartitionSpec: PARTITION BY ColId '(' part_params ')' OptPartitionBoundAutoSpec
{
PartitionSpec *n = makeNode(PartitionSpec);
n->strategy = $3;
n->partParams = $5;
n->location = @1;
-
+ n->autopart = (Node *) $7;
$$ = n;
}
;
@@ -4059,6 +4061,80 @@ part_elem: ColId opt_collate opt_class
}
;
+OptPartitionBoundAutoSpec:
+ CONFIGURATION '(' p_desc ')'
+ {
+ $$ = $3;
+ }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+p_desc:
+ hash_partbound
+ {
+ ListCell *lc;
+ PartitionBoundAutoSpec *n = makeNode(PartitionBoundAutoSpec);
+
+ n->modulus = -1;
+
+ foreach (lc, $1)
+ {
+ DefElem *opt = lfirst_node(DefElem, lc);
+
+ if (strcmp(opt->defname, "modulus") == 0)
+ {
+ n->strategy = PARTITION_STRATEGY_HASH;
+ if (n->modulus != -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("modulus for hash partition provided more than once"),
+ parser_errposition(opt->location)));
+ n->modulus = defGetInt32(opt);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized auto partition bound specification \"%s\"",
+ opt->defname),
+ parser_errposition(opt->location)));
+ }
+
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ | values_in_clause opt_default_partition_clause
+ {
+ PartitionBoundAutoSpec *n = $1;
+ n->default_partition_rv = $2;
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ ;
+
+values_in_clause:
+ VALUES IN_P '(' expr_list ')'
+ {
+ PartitionBoundAutoSpec *n = makeNode(PartitionBoundAutoSpec);
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatumsList = list_make1($4);
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ | values_in_clause ',' '(' expr_list ')'
+ {
+ PartitionBoundAutoSpec *n = (PartitionBoundAutoSpec *) $1;
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatumsList = lappend(n->listdatumsList, $4);
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ ;
+
+opt_default_partition_clause:
+ DEFAULT PARTITION qualified_name
+ {
+ $$ = $3;
+ }
+ | /* EMPTY */
+ { $$ = NULL; }
+ ;
+
table_access_method_clause:
USING name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 81d3e7990c..242aa80082 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -72,6 +72,7 @@
/* State shared by transformCreateStmt and its subroutines */
typedef struct
{
+ CreateStmt *stmt; /* initial statement */
ParseState *pstate; /* overall parser state */
const char *stmtType; /* "CREATE [FOREIGN] TABLE" or "ALTER TABLE" */
RangeVar *relation; /* relation to create */
@@ -143,6 +144,8 @@ static Const *transformPartitionBoundValue(ParseState *pstate, Node *con,
const char *colName, Oid colType, int32 colTypmod,
Oid partCollation);
+static CreateStmt* initAutoPartitionCreateStmt(CreateStmtContext *cxt, char *part_relname);
+static void transformPartitionAutoCreate(CreateStmtContext *cxt, PartitionSpec* partspec);
/*
* transformCreateStmt -
@@ -226,6 +229,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.stmtType = "CREATE TABLE";
cxt.isforeign = false;
}
+ cxt.stmt = stmt;
cxt.relation = stmt->relation;
cxt.rel = NULL;
cxt.inhRelations = stmt->inhRelations;
@@ -333,6 +337,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
transformExtendedStatistics(&cxt);
+ /* Process partition definitions */
+ if (stmt->partspec && stmt->partspec->autopart)
+ transformPartitionAutoCreate(&cxt, stmt->partspec);
+
/*
* Output results.
*/
@@ -4369,3 +4377,137 @@ transformPartitionBoundValue(ParseState *pstate, Node *val,
return (Const *) value;
}
+
+
+/* init basic fields of auto generated partition */
+static CreateStmt*
+initAutoPartitionCreateStmt(CreateStmtContext *cxt, char *part_relname)
+{
+ CreateStmt *part;
+
+ part = copyObject(cxt->stmt);
+
+ part->relation = makeRangeVar(cxt->relation->schemaname,
+ part_relname, cxt->relation->location);
+
+ /* inherit persistence from parent relation */
+ part->relation->relpersistence = cxt->relation->relpersistence;
+ /* set parent table as a parent */
+ part->inhRelations = lappend(part->inhRelations, cxt->relation);
+
+ /*
+ * child table is not partitioned itself, at least now
+ * while we do not support multilevel auto partitioning
+ */
+ part->partspec = NULL;
+
+ /*
+ * Partition doesn't need a list of column definitions and constraints.
+ * They will be inherited from parent.
+ */
+ part->tableElts = NIL;
+ part->constraints = NIL;
+
+ return part;
+}
+
+/*
+ * Transform configuration into a set of partition bounds.
+ * Generate extra statements to create partition tables.
+ */
+static void
+transformPartitionAutoCreate(CreateStmtContext *cxt, PartitionSpec* partspec)
+{
+ CreateStmt *part;
+ List *partlist = NIL;
+ int i = 0;
+ PartitionBoundAutoSpec *bound = (PartitionBoundAutoSpec *) partspec->autopart;
+
+ elog(DEBUG1, "transformPartitionAutoCreate \n %s \n ", nodeToString(bound));
+
+ /*
+ * Generate regular partbounds based on autopart rule.
+ * and form create table statements from these partbounds
+ */
+ if (pg_strcasecmp(partspec->strategy, "hash") == 0)
+ {
+ if (bound->strategy != PARTITION_STRATEGY_HASH)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a hash partition"),
+ parser_errposition(cxt->pstate, exprLocation((Node *) partspec))));
+
+ for (i = 0; i < bound->modulus; i++)
+ {
+ /*
+ * Generate partition name in the format:
+ * $relname_$partnum
+ * All checks of name validity will be made afterwards in DefineRelation()
+ */
+ part = initAutoPartitionCreateStmt(cxt, psprintf("%s_%d", cxt->relation->relname, i));
+
+ /* Actual partbound generation happens here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_HASH;
+ part->partbound->modulus = bound->modulus;
+ part->partbound->remainder = i;
+ part->partbound->is_default = false;
+
+ elog(DEBUG1,"stransformPartitionAutoCreate HASH i %d MODULUS %d \n %s\n",
+ i, bound->modulus, nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+ }
+ else if (pg_strcasecmp(partspec->strategy, "list") == 0)
+ {
+
+ int n_list_parts = list_length(bound->listdatumsList);
+
+ if (bound->strategy != PARTITION_STRATEGY_LIST)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation((Node *) partspec))));
+
+ for (i = 0; i < n_list_parts; i++)
+ {
+ List *listdatums = (List *)
+ list_nth(bound->listdatumsList, i);
+
+ part = initAutoPartitionCreateStmt(cxt, psprintf("%s_%d", cxt->relation->relname, i));
+
+ /* Actual partbound generation happens here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_LIST;
+ part->partbound->listdatums = list_copy(listdatums);
+ part->partbound->is_default = false;
+
+ elog(DEBUG1,"Debug transformPartitionAutoCreate LIST i %d \n %s\n",
+ i, nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+
+ if (bound->default_partition_rv)
+ {
+ part = initAutoPartitionCreateStmt(cxt, bound->default_partition_rv->relname);
+
+ /* TODO: Should we use fields from default_partition_rv, other than relname? */
+
+ /* Actual partbound generation happens here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_LIST;
+ part->partbound->listdatums = NULL;
+ part->partbound->is_default = true;
+
+ elog(DEBUG1,"Debug transformPartitionAutoCreate LIST default partition \n %s\n",
+ nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+ }
+
+ /* Add statements to create each partition after we create parent table */
+ cxt->alist = list_concat(cxt->alist, partlist);
+}
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index d9e417bcd7..2b681bf035 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -488,6 +488,7 @@ typedef enum NodeTag
T_PartitionElem,
T_PartitionSpec,
T_PartitionBoundSpec,
+ T_PartitionBoundAutoSpec,
T_PartitionRangeDatum,
T_PartitionCmd,
T_VacuumRelation,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index def9651b34..0315e4df5c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -808,6 +808,9 @@ typedef struct PartitionSpec
* 'range') */
List *partParams; /* List of PartitionElems */
int location; /* token location, or -1 if unknown */
+
+ Node *autopart; /* PartitionBoundAutoSpec -
+ * spec to generate bounds automatically */
} PartitionSpec;
/* Internal codes for partitioning strategies */
@@ -842,6 +845,26 @@ struct PartitionBoundSpec
int location; /* token location, or -1 if unknown */
};
+/*
+ * PartitionBoundAutoSpec - a partition bound specification
+ * for auto generated partitions.
+ *
+ * This represents the rule of generating partition bounds
+ */
+struct PartitionBoundAutoSpec
+{
+ NodeTag type;
+
+ char strategy; /* see PARTITION_STRATEGY codes above */
+
+ /* Partitioning info for HASH strategy: */
+ int modulus;
+
+ /* Partitioning info for LIST strategy: */
+ List *listdatumsList; /* List of lists of Consts (or A_Consts in raw tree) */
+ RangeVar *default_partition_rv; /* Name of default list partition */
+};
+
/*
* PartitionRangeDatum - one of the values in a range partition bound
*
diff --git a/src/include/partitioning/partdefs.h b/src/include/partitioning/partdefs.h
index d742b96152..25b5c4aa38 100644
--- a/src/include/partitioning/partdefs.h
+++ b/src/include/partitioning/partdefs.h
@@ -19,6 +19,8 @@ typedef struct PartitionKeyData *PartitionKey;
typedef struct PartitionBoundSpec PartitionBoundSpec;
+typedef struct PartitionBoundAutoSpec PartitionBoundAutoSpec;
+
typedef struct PartitionDescData *PartitionDesc;
typedef struct PartitionDirectoryData *PartitionDirectory;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 96bf426d98..39b1e502c8 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -1319,3 +1319,440 @@ Indexes:
"part_column_drop_1_10_expr_idx1" btree ((d = 2))
drop table part_column_drop;
+-- Auto generated partitions
+-- partitioning type not specified
+CREATE TABLE fail_part (a int) CONFIGURATION (MODULUS 10);
+ERROR: syntax error at or near "CONFIGURATION"
+LINE 1: CREATE TABLE fail_part (a int) CONFIGURATION (MODULUS 10);
+ ^
+-- must fail because of wrong configuration
+CREATE TABLE fail_part (a int) PARTITION BY HASH (a) CONFIGURATION
+(values in (1, 2), (3, 4) DEFAULT PARTITION part_default);
+ERROR: invalid bound specification for a hash partition
+LINE 1: CREATE TABLE fail_part (a int) PARTITION BY HASH (a) CONFIGU...
+ ^
+-- forbidden expressions for partition bound with list partitioned table
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (somename));
+ ^
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename.somename));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (somename.somename));
+ ^
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (a));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (a));
+ ^
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(a)));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (sum(a)));
+ ^
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(somename)));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (sum(somename)));
+ ^
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(1)));
+ERROR: aggregate functions are not allowed in partition bound
+LINE 2: (VALUES IN (sum(1)));
+ ^
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((select 1)));
+ERROR: cannot use subquery in partition bound
+LINE 2: (VALUES IN ((select 1)));
+ ^
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (generate_series(4, 6)));
+ERROR: set-returning functions are not allowed in partition bound
+LINE 2: (VALUES IN (generate_series(4, 6)));
+ ^
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((1+1) collate "POSIX"));
+ERROR: collations are not supported by type integer
+LINE 2: (VALUES IN ((1+1) collate "POSIX"));
+ ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ());
+ERROR: syntax error at or near ")"
+LINE 2: (VALUES IN ());
+ ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES FROM (1) TO (2));
+ERROR: syntax error at or near "FROM"
+LINE 2: (VALUES FROM (1) TO (2));
+ ^
+-- trying to specify modulus and remainder for list partitioned table
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(MODULUS 10);
+ERROR: invalid bound specification for a list partition
+LINE 1: CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFI...
+ ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_parted (a bool) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (1));
+ERROR: specified value cannot be cast to type boolean for column "a"
+LINE 2: (VALUES IN (1));
+ ^
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE fail_parted (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),(null) DEFAULT PARTITION part_default);
+ERROR: partition "fail_parted_2" would overlap partition "fail_parted_0"
+LINE 2: (VALUES IN (null, 'z'),('a', 'b'),(null) DEFAULT PARTITION p...
+ ^
+CREATE TABLE fail_parted (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),('b', 'c') DEFAULT PARTITION part_default);
+ERROR: partition "fail_parted_2" would overlap partition "fail_parted_1"
+LINE 2: (VALUES IN (null, 'z'),('a', 'b'),('b', 'c') DEFAULT PARTITI...
+ ^
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(values in (1, 2), (1, 3));
+ERROR: partition "fail_parted_1" would overlap partition "fail_parted_0"
+LINE 2: (values in (1, 2), (1, 3));
+ ^
+-- trying to create default partition for the hash partitioned table
+REATE TABLE fail_parted (a int) PARTITION BY HASH (a) CONFIGURATION
+(MODULUS 10 DEFAULT PARTITION hash_default);
+ERROR: syntax error at or near "REATE"
+LINE 1: REATE TABLE fail_parted (a int) PARTITION BY HASH (a) CONFIG...
+ ^
+CREATE TABLE list_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(values in (1, 2), (3, 4) DEFAULT PARTITION part_default);
+\d+ list_parted
+ Partitioned table "public.list_parted"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Partition key: LIST (a)
+Partitions: list_parted_0 FOR VALUES IN (1, 2),
+ list_parted_1 FOR VALUES IN (3, 4),
+ part_default DEFAULT
+
+DROP TABLE list_parted;
+CREATE TABLE hash_parted (a int) PARTITION BY HASH (a) CONFIGURATION
+(modulus 3);
+\d+ hash_parted
+ Partitioned table "public.hash_parted"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Partition key: HASH (a)
+Partitions: hash_parted_0 FOR VALUES WITH (modulus 3, remainder 0),
+ hash_parted_1 FOR VALUES WITH (modulus 3, remainder 1),
+ hash_parted_2 FOR VALUES WITH (modulus 3, remainder 2)
+
+DROP TABLE hash_parted;
+CREATE TABLE list_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(values in ('1'), (2), (2+1), (null) DEFAULT PARTITION part_default);
+\d+ list_parted
+ Partitioned table "public.list_parted"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Partition key: LIST (a)
+Partitions: list_parted_0 FOR VALUES IN (1),
+ list_parted_1 FOR VALUES IN (2),
+ list_parted_2 FOR VALUES IN (3),
+ list_parted_3 FOR VALUES IN (NULL),
+ part_default DEFAULT
+
+DROP TABLE list_parted;
+-- specified literal can be cast, and the cast might not be immutable
+CREATE TABLE moneyp (a money) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10), ('11'), (to_char(12, '99')::int));
+DROP TABLE moneyp;
+-- partition table inherits relation persistence setting from parent
+CREATE TEMP TABLE temp_parted (a char) PARTITION BY LIST (a)
+CONFIGURATION (VALUES IN ('a') DEFAULT PARTITION temp_parted_default);
+\d temp_parted
+ Partitioned table "pg_temp_3.temp_parted"
+ Column | Type | Collation | Nullable | Default
+--------+--------------+-----------+----------+---------
+ a | character(1) | | |
+Partition key: LIST (a)
+Number of partitions: 2 (Use \d+ to list them.)
+
+DROP TABLE temp_parted;
+-- partition table inherits relation persistence setting from parent
+CREATE UNLOGGED TABLE unlogged_parted (a char) PARTITION BY LIST (a)
+CONFIGURATION (VALUES IN ('a') DEFAULT PARTITION unlogged_parted_default);
+\d unlogged_parted
+ Unlogged partitioned table "public.unlogged_parted"
+ Column | Type | Collation | Nullable | Default
+--------+--------------+-----------+----------+---------
+ a | character(1) | | |
+Partition key: LIST (a)
+Number of partitions: 2 (Use \d+ to list them.)
+
+DROP TABLE unlogged_parted;
+-- testing PARTITION OF on automatically generated partitioned table
+CREATE TABLE hash_parted (a int) PARTITION BY HASH (a) CONFIGURATION (MODULUS 10);
+-- all remainder values are already belong to partitions
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODULUS 30, REMAINDER 3);
+ERROR: partition "fail_part" would overlap partition "hash_parted_3"
+LINE 1: ...BLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODU...
+ ^
+-- trying to specify range for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a', 1) TO ('z');
+ERROR: invalid bound specification for a hash partition
+LINE 1: ...BLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a',...
+ ^
+-- trying to specify list value for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000);
+ERROR: invalid bound specification for a hash partition
+LINE 1: ...BLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000);
+ ^
+-- trying to add default partition to hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted DEFAULT;
+ERROR: a hash-partitioned table may not have a default partition
+\d hash_parted
+ Partitioned table "public.hash_parted"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition key: HASH (a)
+Number of partitions: 10 (Use \d+ to list them.)
+
+DROP TABLE hash_parted;
+-- cast is immutable
+CREATE TABLE bigintp (a bigint) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10));
+-- fails due to overlap:
+CREATE TABLE bigintp_overlap PARTITION OF bigintp FOR VALUES IN ('10');
+ERROR: partition "bigintp_overlap" would overlap partition "bigintp_0"
+LINE 1: ...E bigintp_overlap PARTITION OF bigintp FOR VALUES IN ('10');
+ ^
+DROP TABLE bigintp;
+-- check default partition overlap
+CREATE TABLE list_parted (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b') DEFAULT partition tbl_default);
+INSERT INTO list_parted VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('W', 'X', 'Y');
+ERROR: updated partition constraint for default partition "tbl_default" would be violated by some row
+-- trying to create already existing default partition
+CREATE TABLE fail_part PARTITION OF list_parted DEFAULT;
+ERROR: partition "fail_part" conflicts with existing default partition "tbl_default"
+LINE 1: CREATE TABLE fail_part PARTITION OF list_parted DEFAULT;
+ ^
+DROP TABLE list_parted;
+-- check schema propagation from parent
+CREATE TABLE parted (a text, b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0))
+PARTITION BY LIST (a) CONFIGURATION ( VALUES IN ('a','b'),('d') );
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute
+ WHERE attrelid = 'parted_1'::regclass and attnum > 0
+ ORDER BY attnum;
+ attname | attislocal | attinhcount
+---------+------------+-------------
+ a | f | 1
+ b | f | 1
+(2 rows)
+
+-- able to specify column default, column constraint, and table constraint
+-- first check the "column specified more than once" error
+CREATE TABLE part_e_fail PARTITION OF parted (
+ b NOT NULL,
+ b DEFAULT 1,
+ b CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('e');
+ERROR: column "b" specified more than once
+CREATE TABLE part_e PARTITION OF parted (
+ b NOT NULL DEFAULT 1,
+ CONSTRAINT check_a CHECK (length(a) > 0),
+ CONSTRAINT check_b CHECK (b >= 0)
+) FOR VALUES IN ('e');
+NOTICE: merging constraint "check_a" with inherited definition
+-- conislocal should be false for any merged constraints, true otherwise
+SELECT conname, conislocal, coninhcount FROM pg_constraint
+WHERE conrelid = 'part_e'::regclass ORDER BY conislocal, coninhcount;
+ conname | conislocal | coninhcount
+---------+------------+-------------
+ check_a | f | 1
+ check_b | t | 0
+(2 rows)
+
+-- check_a can not be dropped as it is inherited
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+ERROR: cannot drop inherited constraint "check_a" of relation "part_e"
+-- check_b can be dropped as it is local
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+-- Once check_b is added to the parent, it should be made non-local for part_b
+ALTER TABLE part_e ADD CONSTRAINT check_b CHECK (b >= 0);
+ALTER TABLE parted ADD CONSTRAINT check_b CHECK (b >= 0);
+NOTICE: merging constraint "check_b" with inherited definition
+SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+ conname | conislocal | coninhcount
+---------+------------+-------------
+ check_a | f | 1
+ check_b | f | 1
+(2 rows)
+
+-- Neither check_a nor check_b are droppable from part_b
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+ERROR: cannot drop inherited constraint "check_a" of relation "part_e"
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+ERROR: cannot drop inherited constraint "check_b" of relation "part_e"
+-- And dropping it from parted should leave no trace of them on part_e, unlike
+-- traditional inheritance where they will be left behind, because they would
+-- be local constraints.
+ALTER TABLE parted DROP CONSTRAINT check_a, DROP CONSTRAINT check_b;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+ conislocal | coninhcount
+------------+-------------
+(0 rows)
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY HASH (c);
+ERROR: column "c" named in partition key does not exist
+LINE 1: ...RTITION OF parted FOR VALUES IN ('c') PARTITION BY HASH (c);
+ ^
+CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0)
+FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- check that NOT NULL and default value are inherited correctly
+CREATE TABLE parted_notnull_inh_test (a int DEFAULT 1, b int NOT NULL DEFAULT 0)
+ PARTITION BY LIST(a) CONFIGURATION (VALUES IN (1));
+INSERT INTO parted_notnull_inh_test (b) VALUES (NULL);
+ERROR: null value in column "b" of relation "parted_notnull_inh_test_0" violates not-null constraint
+DETAIL: Failing row contains (1, null).
+-- note that a's default is preserved
+\d parted_notnull_inh_test1
+DROP TABLE parted_notnull_inh_test;
+-- Partition bound in describe output
+\d+ part_e
+ Table "public.part_e"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | not null | 1 | plain | |
+Partition of: parted FOR VALUES IN ('e')
+Partition constraint: ((a IS NOT NULL) AND (a = 'e'::text))
+
+-- Both partition bound and partition key in describe output
+\d+ part_c
+ Partitioned table "public.part_c"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | not null | 0 | plain | |
+Partition of: parted FOR VALUES IN ('c')
+Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text))
+Partition key: RANGE (b)
+Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
+
+-- a level-2 partition's constraint will include the parent's expressions
+\d+ part_c_1_10
+ Table "public.part_c_1_10"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | not null | 0 | plain | |
+Partition of: part_c FOR VALUES FROM (1) TO (10)
+Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text) AND (b IS NOT NULL) AND (b >= 1) AND (b < 10))
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+ Partitioned table "public.parted"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | text | | |
+ b | integer | | not null | 0
+Partition key: LIST (a)
+Number of partitions: 4 (Use \d+ to list them.)
+
+DROP TABLE parted;
+-- list partitioning on array type column
+CREATE TABLE arrlp (a int[]) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ('{1}', '{2}'));
+\d+ arrlp_1
+DROP TABLE arrlp;
+-- partition on boolean column
+CREATE TABLE boolspart (a bool) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (true), (false));
+\d+ boolspart
+ Partitioned table "public.boolspart"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | boolean | | | | plain | |
+Partition key: LIST (a)
+Partitions: boolspart_0 FOR VALUES IN (true),
+ boolspart_1 FOR VALUES IN (false)
+
+DROP TABLE boolspart;
+-- test using a volatile expression as partition bound
+CREATE TABLE volatile_partbound_test (partkey timestamp) PARTITION BY LIST (partkey) CONFIGURATION
+(VALUES IN ('1970-01-01 00:00:00+00'::timestamp, current_timestamp),('1982-01-25 00:00:00+00'::timestamp));
+DROP TABLE volatile_partbound_test;
+-- tests of column drop with partition tables and indexes using
+-- predicates and expressions.
+CREATE TABLE part_column_drop (useless_1 int, id int, useless_2 int, d int,
+ b int, useless_3 int) PARTITION BY HASH (id) CONFIGURATION (MODULUS 3);
+ALTER TABLE part_column_drop DROP COLUMN useless_1;
+ALTER TABLE part_column_drop DROP COLUMN useless_2;
+ALTER TABLE part_column_drop DROP COLUMN useless_3;
+CREATE INDEX part_column_drop_b_pred ON part_column_drop(b) WHERE b = 1;
+CREATE INDEX part_column_drop_b_expr ON part_column_drop((b = 1));
+CREATE INDEX part_column_drop_d_pred ON part_column_drop(d) WHERE d = 2;
+CREATE INDEX part_column_drop_d_expr ON part_column_drop((d = 2));
+CREATE INDEX part_column_drop_d_1_pred ON part_column_drop_1(d) WHERE d = 2;
+CREATE INDEX part_column_drop_d_1_expr ON part_column_drop_1((d = 2));
+\d part_column_drop
+ Partitioned table "public.part_column_drop"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ d | integer | | |
+ b | integer | | |
+Partition key: HASH (id)
+Indexes:
+ "part_column_drop_b_expr" btree ((b = 1))
+ "part_column_drop_b_pred" btree (b) WHERE b = 1
+ "part_column_drop_d_expr" btree ((d = 2))
+ "part_column_drop_d_pred" btree (d) WHERE d = 2
+Number of partitions: 3 (Use \d+ to list them.)
+
+\d part_column_drop_1
+ Table "public.part_column_drop_1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ d | integer | | |
+ b | integer | | |
+Partition of: part_column_drop FOR VALUES WITH (modulus 3, remainder 1)
+Indexes:
+ "part_column_drop_1_b_idx" btree (b) WHERE b = 1
+ "part_column_drop_1_d_idx" btree (d) WHERE d = 2
+ "part_column_drop_1_expr_idx" btree ((b = 1))
+ "part_column_drop_1_expr_idx1" btree ((d = 2))
+ "part_column_drop_d_1_expr" btree ((d = 2))
+ "part_column_drop_d_1_pred" btree (d) WHERE d = 2
+
+\d part_column_drop_2
+ Table "public.part_column_drop_2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ d | integer | | |
+ b | integer | | |
+Partition of: part_column_drop FOR VALUES WITH (modulus 3, remainder 2)
+Indexes:
+ "part_column_drop_2_b_idx" btree (b) WHERE b = 1
+ "part_column_drop_2_d_idx" btree (d) WHERE d = 2
+ "part_column_drop_2_expr_idx" btree ((b = 1))
+ "part_column_drop_2_expr_idx1" btree ((d = 2))
+
+\d part_column_drop_3
+DROP TABLE part_column_drop;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cc41f58ba2..1aeb04db8d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -975,3 +975,238 @@ create table part_column_drop_1_10 partition of
\d part_column_drop
\d part_column_drop_1_10
drop table part_column_drop;
+
+-- Auto generated partitions
+
+-- partitioning type not specified
+CREATE TABLE fail_part (a int) CONFIGURATION (MODULUS 10);
+
+-- must fail because of wrong configuration
+CREATE TABLE fail_part (a int) PARTITION BY HASH (a) CONFIGURATION
+(values in (1, 2), (3, 4) DEFAULT PARTITION part_default);
+
+-- forbidden expressions for partition bound with list partitioned table
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename));
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename.somename));
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (a));
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(a)));
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(somename)));
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(1)));
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((select 1)));
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (generate_series(4, 6)));
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((1+1) collate "POSIX"));
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ());
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES FROM (1) TO (2));
+-- trying to specify modulus and remainder for list partitioned table
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(MODULUS 10);
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_parted (a bool) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (1));
+
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE fail_parted (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),(null) DEFAULT PARTITION part_default);
+
+CREATE TABLE fail_parted (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),('b', 'c') DEFAULT PARTITION part_default);
+
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(values in (1, 2), (1, 3));
+
+-- trying to create default partition for the hash partitioned table
+REATE TABLE fail_parted (a int) PARTITION BY HASH (a) CONFIGURATION
+(MODULUS 10 DEFAULT PARTITION hash_default);
+
+CREATE TABLE list_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(values in (1, 2), (3, 4) DEFAULT PARTITION part_default);
+\d+ list_parted
+DROP TABLE list_parted;
+
+CREATE TABLE hash_parted (a int) PARTITION BY HASH (a) CONFIGURATION
+(modulus 3);
+\d+ hash_parted
+DROP TABLE hash_parted;
+
+CREATE TABLE list_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(values in ('1'), (2), (2+1), (null) DEFAULT PARTITION part_default);
+\d+ list_parted
+DROP TABLE list_parted;
+
+-- specified literal can be cast, and the cast might not be immutable
+CREATE TABLE moneyp (a money) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10), ('11'), (to_char(12, '99')::int));
+DROP TABLE moneyp;
+
+-- partition table inherits relation persistence setting from parent
+CREATE TEMP TABLE temp_parted (a char) PARTITION BY LIST (a)
+CONFIGURATION (VALUES IN ('a') DEFAULT PARTITION temp_parted_default);
+\d temp_parted
+DROP TABLE temp_parted;
+
+-- partition table inherits relation persistence setting from parent
+CREATE UNLOGGED TABLE unlogged_parted (a char) PARTITION BY LIST (a)
+CONFIGURATION (VALUES IN ('a') DEFAULT PARTITION unlogged_parted_default);
+\d unlogged_parted
+DROP TABLE unlogged_parted;
+
+-- testing PARTITION OF on automatically generated partitioned table
+
+CREATE TABLE hash_parted (a int) PARTITION BY HASH (a) CONFIGURATION (MODULUS 10);
+-- all remainder values are already belong to partitions
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODULUS 30, REMAINDER 3);
+-- trying to specify range for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a', 1) TO ('z');
+-- trying to specify list value for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000);
+-- trying to add default partition to hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted DEFAULT;
+\d hash_parted
+DROP TABLE hash_parted;
+
+-- cast is immutable
+CREATE TABLE bigintp (a bigint) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10));
+-- fails due to overlap:
+CREATE TABLE bigintp_overlap PARTITION OF bigintp FOR VALUES IN ('10');
+DROP TABLE bigintp;
+
+-- check default partition overlap
+CREATE TABLE list_parted (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b') DEFAULT partition tbl_default);
+INSERT INTO list_parted VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('W', 'X', 'Y');
+-- trying to create already existing default partition
+CREATE TABLE fail_part PARTITION OF list_parted DEFAULT;
+DROP TABLE list_parted;
+
+-- check schema propagation from parent
+CREATE TABLE parted (a text, b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0))
+PARTITION BY LIST (a) CONFIGURATION ( VALUES IN ('a','b'),('d') );
+
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute
+ WHERE attrelid = 'parted_1'::regclass and attnum > 0
+ ORDER BY attnum;
+
+-- able to specify column default, column constraint, and table constraint
+-- first check the "column specified more than once" error
+CREATE TABLE part_e_fail PARTITION OF parted (
+ b NOT NULL,
+ b DEFAULT 1,
+ b CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('e');
+
+CREATE TABLE part_e PARTITION OF parted (
+ b NOT NULL DEFAULT 1,
+ CONSTRAINT check_a CHECK (length(a) > 0),
+ CONSTRAINT check_b CHECK (b >= 0)
+) FOR VALUES IN ('e');
+-- conislocal should be false for any merged constraints, true otherwise
+SELECT conname, conislocal, coninhcount FROM pg_constraint
+WHERE conrelid = 'part_e'::regclass ORDER BY conislocal, coninhcount;
+
+-- check_a can not be dropped as it is inherited
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+-- check_b can be dropped as it is local
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+
+-- Once check_b is added to the parent, it should be made non-local for part_b
+ALTER TABLE part_e ADD CONSTRAINT check_b CHECK (b >= 0);
+ALTER TABLE parted ADD CONSTRAINT check_b CHECK (b >= 0);
+SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+
+-- Neither check_a nor check_b are droppable from part_b
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+
+-- And dropping it from parted should leave no trace of them on part_e, unlike
+-- traditional inheritance where they will be left behind, because they would
+-- be local constraints.
+ALTER TABLE parted DROP CONSTRAINT check_a, DROP CONSTRAINT check_b;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY HASH (c);
+CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0)
+FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+
+-- check that NOT NULL and default value are inherited correctly
+CREATE TABLE parted_notnull_inh_test (a int DEFAULT 1, b int NOT NULL DEFAULT 0)
+ PARTITION BY LIST(a) CONFIGURATION (VALUES IN (1));
+INSERT INTO parted_notnull_inh_test (b) VALUES (NULL);
+-- note that a's default is preserved
+\d parted_notnull_inh_test1
+DROP TABLE parted_notnull_inh_test;
+
+-- Partition bound in describe output
+\d+ part_e
+
+-- Both partition bound and partition key in describe output
+\d+ part_c
+
+-- a level-2 partition's constraint will include the parent's expressions
+\d+ part_c_1_10
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+DROP TABLE parted;
+
+-- list partitioning on array type column
+CREATE TABLE arrlp (a int[]) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ('{1}', '{2}'));
+\d+ arrlp_1
+DROP TABLE arrlp;
+
+-- partition on boolean column
+CREATE TABLE boolspart (a bool) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (true), (false));
+\d+ boolspart
+DROP TABLE boolspart;
+
+-- test using a volatile expression as partition bound
+CREATE TABLE volatile_partbound_test (partkey timestamp) PARTITION BY LIST (partkey) CONFIGURATION
+(VALUES IN ('1970-01-01 00:00:00+00'::timestamp, current_timestamp),('1982-01-25 00:00:00+00'::timestamp));
+DROP TABLE volatile_partbound_test;
+
+-- tests of column drop with partition tables and indexes using
+-- predicates and expressions.
+CREATE TABLE part_column_drop (useless_1 int, id int, useless_2 int, d int,
+ b int, useless_3 int) PARTITION BY HASH (id) CONFIGURATION (MODULUS 3);
+ALTER TABLE part_column_drop DROP COLUMN useless_1;
+ALTER TABLE part_column_drop DROP COLUMN useless_2;
+ALTER TABLE part_column_drop DROP COLUMN useless_3;
+CREATE INDEX part_column_drop_b_pred ON part_column_drop(b) WHERE b = 1;
+CREATE INDEX part_column_drop_b_expr ON part_column_drop((b = 1));
+CREATE INDEX part_column_drop_d_pred ON part_column_drop(d) WHERE d = 2;
+CREATE INDEX part_column_drop_d_expr ON part_column_drop((d = 2));
+CREATE INDEX part_column_drop_d_1_pred ON part_column_drop_1(d) WHERE d = 2;
+CREATE INDEX part_column_drop_d_1_expr ON part_column_drop_1((d = 2));
+
+\d part_column_drop
+\d part_column_drop_1
+\d part_column_drop_2
+\d part_column_drop_3
+DROP TABLE part_column_drop;
--
2.24.3 (Apple Git-128)
On Fri, Jul 9, 2021 at 6:30 AM Pavel Borisov <pashkin.elfe@gmail.com> wrote:
Thank you for your review!
I've rebased the patch and made the changes mentioned.
PFA v5.
I've set this back to "needs review" in CF.
--
John Naylor
EDB: http://www.enterprisedb.com
Thank you for your review!
I've rebased the patch and made the changes mentioned.
PFA v5.I've set this back to "needs review" in CF.
Thanks for the attention! I did the review of this patch, and the changes
I've introduced in v5 are purely cosmetic. So I'd suppose the
ready-for-committer status should not better have been changed.
So I'd like return it to ready-for-committer. If you mind against this,
please mention. The opinion of Nitin, a second reviewer, is also very much
appreciated.
--
Best regards,
Pavel Borisov
Postgres Professional: http://postgrespro.com <http://www.postgrespro.com>
On Tue, Mar 2, 2021 at 3:26 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
I don't know what committers will say, but I think that "ALTER TABLE" might be
the essential thing for this patch to support, not "CREATE". (This is similar
to ALTER..SET STATISTICS, which is not allowed in CREATE.)The reason is that ALTER is what's important for RANGE partitions, which need
to be created dynamically (for example, to support time-series data
continuously inserting data around 'now'). I assume it's sometimes also
important for LIST. I think this patch should handle those cases better before
being commited, or else we risk implementing grammar and other user-facing interface
that fails to handle what's needed into the future (or that's non-essential).
Even if dynamic creation isn't implemented yet, it seems important to at least
implement the foundation for setting the configuration to *allow* that in the
future, in a manner that's consistent with the initial implementation for
"static" partitions.
I don't think it's a hard requirement, but it's an interesting point.
My initial reactions to the patch are:
- I don't think it's a very good idea to support LIST and HASH but not
RANGE. We need a design that can work for all three partitioning
strategies, even if we don't have support for all of them in the
initial patch. If they CAN all be in the same patch, so much the
better.
- I am not very impressed with the syntax. CONFIGURATION is an odd
word that seems too generic for what we're talking about here. It
would be tempting to use a connecting word like WITH or USING except
that both would be ambiguous here, so we can't. MySQL and Oracle use
the keyword PARTITIONS -- which I realize isn't a keyword at all in
PostgreSQL right now -- to introduce the partition specification. DB2
uses no keyword at all; it seems you just say PARTITION BY
(mypartitioncol) (...partition specifications go here...). I think
either approach could work for us. Avoiding the extra keyword is a
plus, especially since I doubt we're likely to support the exact
syntax that Oracle and MySQL offer anyway - though if we do, then I'd
be in favor of inserting the PARTITIONS keyword so that people's SQL
can work without modification.
- We need to think a little bit about exactly what we're trying to do.
The simplest imaginable thing here would be to just give people a
place to put a bunch of partition specifications. So you can imagine
letting someone say PARTITION BY HASH (FOR VALUES WITH (MODULUS 2,
REMAINDER 0), FOR VALUES WITH (MODULUS 2, REMAINDER 1)). However, the
patch quite rightly rejects that approach in favor of the theory that,
at CREATE TABLE time, you're just going to want to give a modulus and
have the system create one partition for every possible remainder. But
that could be expressed even more compactly than what the patch does.
Instead of saying PARTITION BY HASH CONFIGURATION (MODULUS 4) we could
just let people say PARTITION BY HASH (4) or probably even PARTITION
BY HASH 4.
- For list partitioning, the patch falls back to just letting you put
a bunch of VALUES IN clauses in the CREATE TABLE statement. I don't
find something like PARTITION BY LIST CONFIGURATION (VALUES IN (1, 2),
(1, 3)) to be particularly readable. What are all the extra keywords
adding? We could just say PARTITION BY LIST ((1, 2), (1, 3)). I think
I would find that easier to remember; not sure what other people
think. As an alternative, PARTITION BY LIST VALUES IN (1, 2), (1, 3)
looks workable, too.
- What about range partitioning? This is an interesting case because
while in theory you could leave gaps between range partitions, in
practice people probably don't want to do that very often, and it
might be better to have a simpler syntax that caters to the common
case, since people can always create partitions individually if they
happen to want gaps. So you can imagine making something like
PARTITION BY RANGE ((MINVALUE), (42), (163)) mean create two
partitions, one from (MINVALUE) to (42) and the other from (42) to
(163). I think that would be pretty useful.
- Another possible separating keyword here would be INITIALLY, which
is already a parser keyword. So then you could have stuff like
PARTITION BY HASH INITIALLY 4, PARTITION BY LIST INITIALLY ((1, 2),
(1, 3)), PARTITION BY RANGE INITIALLY ((MINVALUE), (42), (163)).
- The patch doesn't document the naming convention for the
automatically created partitions, and it is worth thinking a bit about
how that is going to work. Do people want to be able to specify the
name of the partitioned table when they are using this syntax, or are
they happy with automatically generated names? If the latter, are they
happy with THESE automatically generated names? I guess for HASH
appending _%d where %d is the modulus is fine, but it is not necessary
so great for LIST. If I said CREATE TABLE foo ... PARTITION BY LIST
(('en'), ('ru'), ('jp')) I think I'd be hoping to end up with
partitions named foo_en, foo_ru, and foo_jp rather than foo_0, foo_1,
foo_2. Or maybe I'd rather say PARTITION BY LIST (foo_en ('en'),
foo_ru ('ru'), foo_jp ('jp')) or something like that to be explicit
about it. Not sure. But it's worth some thought. I think this comes
into focus even more clearly for range partitions, where you probably
want the partitions to follow a convention like basetablename_yyyy_mm.
- The documentation for the CONFIGURATION option doesn't match the
grammar. The documentation makes it an independent clause, so
CONFIGURATION could be specified even if PARTITION BY is not. But the
implementation makes the better choice to treat CONFIGURATION as a
further specification of PARTITION BY.
- I don't think this patch is really all that close to being ready for
committer. Beyond the design issues which seem to need more thought,
there's stuff in the patch like:
+ elog(DEBUG1,"stransformPartitionAutoCreate HASH i %d MODULUS %d \n %s\n",
+ i, bound->modulus, nodeToString(part));
Now, on the one hand, debugging elogs like this have little business
in a final patch. And, on the other hand, if we were going to include
them in the final patch, we'd probably want to at least spell the
function name correctly. Similarly, it's evident that this test case
has not been carefully reviewed by anyone, including the author:
+REATE TABLE fail_parted (a int) PARTITION BY HASH (a) CONFIGURATION
+(MODULUS 10 DEFAULT PARTITION hash_default);
Not too surprisingly, the system isn't familiar with the REATE command.
- There's some questionable error-reporting behavior in here, too, particularly:
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(values in (1, 2), (1, 3));
+ERROR: partition "fail_parted_1" would overlap partition "fail_parted_0"
+LINE 2: (values in (1, 2), (1, 3));
Since the user hasn't provided the names fail_parted_0 or
fail_parted_1, it kind of stinks to use them in an error report. The
error cursor is good, but I wonder if we need to do better. One option
would be to go to a syntax where the user specifies the partition
names explicitly, which then justifies using that name in an error
report. Another possibility would be to give a different message in
this case, like:
ERROR: partition for values in (1, 2) would overlap partition for
values in (1, 3)
Now, that would require a pretty substantial redesign of the patch,
and I'm not sure it's worth the effort. But I'm also not sure that it
isn't.
--
Robert Haas
EDB: http://www.enterprisedb.com
- I don't think it's a very good idea to support LIST and HASH but not
RANGE. We need a design that can work for all three partitioning
strategies, even if we don't have support for all of them in the
initial patch. If they CAN all be in the same patch, so much the
better.- I am not very impressed with the syntax. CONFIGURATION is an odd
word that seems too generic for what we're talking about here. It
would be tempting to use a connecting word like WITH or USING except
that both would be ambiguous here, so we can't. MySQL and Oracle use
the keyword PARTITIONS -- which I realize isn't a keyword at all in
PostgreSQL right now -- to introduce the partition specification. DB2
uses no keyword at all; it seems you just say PARTITION BY
(mypartitioncol) (...partition specifications go here...). I think
either approach could work for us. Avoiding the extra keyword is a
plus, especially since I doubt we're likely to support the exact
syntax that Oracle and MySQL offer anyway - though if we do, then I'd
be in favor of inserting the PARTITIONS keyword so that people's SQL
can work without modification.- We need to think a little bit about exactly what we're trying to do.
The simplest imaginable thing here would be to just give people a
place to put a bunch of partition specifications. So you can imagine
letting someone say PARTITION BY HASH (FOR VALUES WITH (MODULUS 2,
REMAINDER 0), FOR VALUES WITH (MODULUS 2, REMAINDER 1)). However, the
patch quite rightly rejects that approach in favor of the theory that,
at CREATE TABLE time, you're just going to want to give a modulus and
have the system create one partition for every possible remainder. But
that could be expressed even more compactly than what the patch does.
Instead of saying PARTITION BY HASH CONFIGURATION (MODULUS 4) we could
just let people say PARTITION BY HASH (4) or probably even PARTITION
BY HASH 4.- For list partitioning, the patch falls back to just letting you put
a bunch of VALUES IN clauses in the CREATE TABLE statement. I don't
find something like PARTITION BY LIST CONFIGURATION (VALUES IN (1, 2),
(1, 3)) to be particularly readable. What are all the extra keywords
adding? We could just say PARTITION BY LIST ((1, 2), (1, 3)). I think
I would find that easier to remember; not sure what other people
think. As an alternative, PARTITION BY LIST VALUES IN (1, 2), (1, 3)
looks workable, too.- What about range partitioning? This is an interesting case because
while in theory you could leave gaps between range partitions, in
practice people probably don't want to do that very often, and it
might be better to have a simpler syntax that caters to the common
case, since people can always create partitions individually if they
happen to want gaps. So you can imagine making something like
PARTITION BY RANGE ((MINVALUE), (42), (163)) mean create two
partitions, one from (MINVALUE) to (42) and the other from (42) to
(163). I think that would be pretty useful.- Another possible separating keyword here would be INITIALLY, which
is already a parser keyword. So then you could have stuff like
PARTITION BY HASH INITIALLY 4, PARTITION BY LIST INITIALLY ((1, 2),
(1, 3)), PARTITION BY RANGE INITIALLY ((MINVALUE), (42), (163)).
Robert, I've read your considerations and I have a proposal to change the
syntax to make it like:
CREATE TABLE foo (bar text) PARTITION BY LIST (bar) PARTITIONS (('US'),
('UK', 'RU'));
CREATE TABLE foo (bar text) PARTITION BY LIST (bar) PARTITIONS
(foo_us('US'), foo_uk_ru('UK', 'RU'), { DEFAULT foo_dflt | AUTOMATIC });
CREATE TABLE foo (bar int) PARTITION BY HASH (bar) PARTITIONS (5);
CREATE TABLE foo (bar int) PARTITION BY RANGE (bar) PARTITIONS (FROM 1 TO
10 INTERVAL 2, { DEFAULT foo_dflt | AUTOMATIC });
- I think using partitions syntax without any keyword at all, is quite
different from the existing pseudo-english PostgreSQL syntax. Also, it will
need two consecutive brackets divided by nothing (<partitioning
key>)(<partitions configuration>). So I think it's better to use the
keyword PARTITIONS
- from the current patch it seems like a 'syntactic sugar' only but I don't
think it is being so. From a new syntaх proposal it's seen that it can
enable three options
(1) create a fixed set of partitions with everything else comes to the
default partition
(2) create a fixed set of partitions with everything else invokes error on
insert
(3) create a set of partitions with everything else invokes a new partition
creation based on a partition key (AUTOMATIC word). Like someone will be
able to do:
CREATE TABLE foo (a varchar) PARTITION BY LIST (SUBSTRING (a, 1, 1))
PARTITIONS (('a'),('b'),('c'));
INSERT INTO foo VALUES ("doctor"); // will automatically create partition
for 'd'
INSERT INTO foo VALUES ("dam"); // will come into partition 'd'
Option (3) is not yet implemented and sure it needs much care from DBA to
not end up with the each-row-separate-partition.
- Also with option (3) and AUTOMATIC word someone will be able to do:
CREATE TABLE foo (a timestamp, t text) PARTITION BY LIST(EXTRACT (YEAR FROM
a)) PARTITIONS (('1982'),('1983'),('1984'));
INSERT INTO foo VALUES (TIMESTAMP '1986-01-01 13:30:03', 'Orwell'); //
creates '1986' partition and inserts into it
I think this option will be very useful as partitioning based on regular
intervals of time I think is quite natural and often used. And to do it we
don't need to implement arbitrary intervals (partition by range). But I
think it's also worth implementing (proposed syntax for RANGE see above);
- As for the naming of partitions I've seen what is done in Oracle:
partition names can be provided when you create an initial set, and when a
partition is created automatically on insert it will get some illegible
name chosen by the system (it even doesn't include parent table prefix).
I'd propose to implement:
(1) If partition name is not specified it has format
<parent_table_name>_<value_of_partition_key>
where <value_of_partition_key> is a remainder in HASH, the first element of
the list of values for the partition in LIST case, left range-bound in
RANGE case
(2) If it is specified (not possible at partition creation at insert
command) it is <parent_table_name>_<specified_name>
Though we'll probably need to have some rules for the abbreviation for
partition name should not exceed the relation name length limit. I think
partitions naming with plain _numbers in the existing patch is for the
purpose of increasing relation name length as little as possible for not
implementing abbreviation.
What do you think will the described approach lead to a useful patch?
Should it be done as a whole or it's possible to commit it in smaller
steps? (E.g. first part without AUTOMATIC capability, then add AUTOMATIC
capability. Or with some other order of features implementation)
My own view is that if some implementation of syntax is solidly decided, it
will promote work on more complicated logic of the patch and implement all
parts one-by-one for the feature finally become really usable (not just
helping to squash several SQL commands into one as this patch does). I see
the existing patch as the starting point of the whole work and given some
decisions on syntax I can try to rework and extend it accordingly.
Overall I consider this useful for PostgreSQL.
What do you think about it?
--
Best regards,
Pavel Borisov
Postgres Professional: http://postgrespro.com <http://www.postgrespro.com>
On Wed, Jul 14, 2021 at 7:28 AM Pavel Borisov <pashkin.elfe@gmail.com> wrote:
What do you think will the described approach lead to a useful patch? Should it be done as a whole or it's possible to commit it in smaller steps? (E.g. first part without AUTOMATIC capability, then add AUTOMATIC capability. Or with some other order of features implementation)
I would suggest that you consider on-the-fly partition creation to be
a completely separate feature from initial partition creation as part
of CREATE TABLE. I think you can have either without the other, and I
think the latter is a lot easier than the former. I doubt that
on-the-fly partition creation makes any sense at all for hash
partitions; there seems to be no reason not to pre-create all the
partitions. It's pretty straightforward to see how it should work for
LIST, but RANGE needs an interval or something to be stored in the
system catalogs so you can figure out where to put the boundaries, and
somehow you've got to identify a + operator for the relevant data
type. Tom Lane probably won't be thrilled if you suggest looking it up
based on the operator NAME. The bigger issue IMHO with on-the-fly
partition creation is avoiding deadlocks in the presence of current
inserters; I submit that without at least some kind of attempt to
avoid deadlocks and spurious errors there, it's not really a usable
scheme, and that seems hard.
On the other hand, modulo syntax details, creating partitions at
CREATE TABLE time seems relatively simple and, especially in the case
of hash partitioning, useful.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Tue, Jul 20, 2021 at 02:42:16PM -0400, Robert Haas wrote:
The bigger issue IMHO with on-the-fly
partition creation is avoiding deadlocks in the presence of current
inserters; I submit that without at least some kind of attempt to
avoid deadlocks and spurious errors there, it's not really a usable
scheme, and that seems hard.
I was thinking that for dynamic creation, there would be a DDL command to
create the necessary partitions:
-- Creates 2021-01-02, unless the month already exists:
ALTER TABLE bydate SET GRANULARITY='1day';
ALTER TABLE bydate CREATE PARTITION FOR VALUE ('2021-01-02');
I'd want it to support changing the granularity of the range partitions:
-- Creates 2021-01 unless the month already exists.
-- Errors if a day partition already exists which would overlap?
ALTER TABLE bydate SET granularity='1month';
ALTER TABLE bydate CREATE PARTITION FOR VALUE ('2021-01-03');
It could support creating ranges, which might create multiple partitions,
depending on the granularity:
ALTER TABLE bydate CREATE PARTITION FOR VALUES ('2021-01-01') TO ('2021-02-01')
Or the catalog could include not only granularity, but also endpoints:
ALTER TABLE bydate SET ENDPOINTS ('2012-01-01') ('2022-01-01')
ALTER TABLE bydate CREATE PARTITIONS; --create anything needed to fill from a->b
ALTER TABLE bydate PRUNE PARTITIONS; --drop anything outside of [a,b]
I would use this to set "fine" granularity for large tables, and "course"
granularity for tables that were previously set to "fine" granularity, but its
partitions are no longer large enough to justify it. This logic currently
exists in our application - we create partitions dynamically immediately before
inserting. But it'd be nicer if it were created asynchronously. It may create
tables which were never inserted into, which is fine - they'd be course
granularity tables (one per month).
I think this might elegantly allow both 1) subpartitioning; 2) repartitioning
to a different granularity (for which I currently have my own tool).
--
Justin
On Tue, Jul 20, 2021 at 3:13 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
On Tue, Jul 20, 2021 at 02:42:16PM -0400, Robert Haas wrote:
The bigger issue IMHO with on-the-fly
partition creation is avoiding deadlocks in the presence of current
inserters; I submit that without at least some kind of attempt to
avoid deadlocks and spurious errors there, it's not really a usable
scheme, and that seems hard.I was thinking that for dynamic creation, there would be a DDL command to
create the necessary partitions:-- Creates 2021-01-02, unless the month already exists:
ALTER TABLE bydate SET GRANULARITY='1day';
ALTER TABLE bydate CREATE PARTITION FOR VALUE ('2021-01-02');
Well, that dodges the deadlock issue with doing it implicitly, but it
also doesn't seem to offer a lot of value over just creating the
partitions in a fully manual way. I mean you could just say:
CREATE TABLE bydate_2021_02_02 PARTITION OF bydate FOR VALUES FROM
('2021-01-02') TO ('2021-02-03');
It's longer, but it's not really that bad.
--
Robert Haas
EDB: http://www.enterprisedb.com
This thread has stalled since July with review comments unanswered, I'm marking
the patch Returned with Feedback. Please feel free to resubmit when/if a new
patch is available.
--
Daniel Gustafsson https://vmware.com/
Hi,
I found that thread (and the patch), but it seems to be pretty dead.
Patch didn't apply, due to gen_node_support.pl
Can I hope for a rebirth ?
I've made a rebased patch,in case of no response...
It's just the patch from
/messages/by-id/CALT9ZEG9oKz9-dv9YYZaeeXNpZp0+teLFSz7QST28AcmERVpiw@mail.gmail.com
rebased on 17dev
Perhaps it's too early for a commit ; automatic range partitioning is still
missing and, according to
https://wiki.postgresql.org/wiki/Declarative_partitioning_improvements,
syntax is arguable.
If 'USING' it out of option (already a keyword for CREATE TABLE) and
'CONFIGURATION()' is not what we want, we should reach for a final decision
first.
I suggest OVER that is a keyword but unused in CREATE TABLE (nor ALTER
TABLE). Whatever...
For RANGE partitioning I think of four syntaxes (inspired by pg_partman)
PARTITION BY RANGE(stamp) CONFIGURATION (SPAN interval CENTER datetime BACK
integer AHEAD integer [DEFAULT [PARTITION] [defname]])
PARTITION BY RANGE(stamp) CONFIGURATION (SPAN interval
START firstfrombound END lasttobound [DEFAULT [PARTITION] [defname]])
PARTITION BY RANGE(region_id) CONFIGURATION (STEP integer START integer END
integer [DEFAULT [PARTITION] [defname]])
PARTITION BY RANGE(name) CONFIGURATION (BOUNDS (boundlist) [START
firstfrombound] [END lasttobound] [DEFAULT [PARTITION] [defname]])
Last one should solve the addition operator problem with non numeric non
timedate range.
Plus, it allows non uniform range (thinking about an "encyclopedia volume"
partitioning, you know 'A', 'B-CL', 'CL-D'...)
CREATE table (LIKE other INCLUDING PARTITIONS) should create 'table'
partitioned the same as 'other'
and
CREATE table (LIKE other INCLUDING PARTITIONS) PARTITION BY partspec
CONFIGURATION(), should create 'table' partitioned by partspec and sub
partitioned as 'other'.
Then CREATE could accept multiple PARTITION BY CONFIGURATION().
For ALTER TABLE (and automatic maintenance) to be usable, we will need
SPLIT and MERGE CONCURRENTLY (pg_pathman ?) enhanced by CREATE TABLE LIKE
to handle subpartitioning. But that's another story.
Stéphane.
Le jeu. 2 déc. 2021 à 12:20, Daniel Gustafsson <daniel@yesql.se> a écrit :
This thread has stalled since July with review comments unanswered, I'm
marking
the patch Returned with Feedback. Please feel free to resubmit when/if a
new
patch is available.--
Daniel Gustafsson https://vmware.com/
--
"Où se posaient les hirondelles avant l'invention du téléphone ?"
-- Grégoire Lacroix
Attachments:
v7-0001-This-is-a-simple-rebase-on-most-recent-17dev.patchtext/x-patch; charset=US-ASCII; name=v7-0001-This-is-a-simple-rebase-on-most-recent-17dev.patchDownload
From ff875672556a2da09cfcc6c976178abd91c74622 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Tachoires?= <sttachoires@airfrance.fr>
Date: Mon, 17 Jul 2023 13:25:29 +0200
Subject: [PATCH v7] This is a simple rebase on most recent 17dev. Due to lack
of answer from author Pavel Borisov <pashkin.elfe@gmail.com>
Previous meaningfull commit from author was:
From ef0fcba607641aef94a9ce9b0393e607ba476ba4 Mon Sep 17 00:00:00 2001
From: Pavel Borisov <pashkin.elfe@gmail.com>
Date: Fri, 9 Jul 2021 00:34:21 +0400
Subject: [PATCH v5] Automatically generate partitions by LIST and HASH
A patch adds CREATE TABLE statement syntax and functions to create both
a parent partitioned table and child partitions at once based on a
partitioning rules. The created partitions set can be manipulated using
existing expressions like DETACH, CREATE TABLE.. PARTITION OF etc.
It is the first step towards more complicated:
(1) partitioning BY RANGE,
(2) automatic deferred child partition creation on a first try to
insert a row with which fullfills a condition for the partition etc.
---
doc/src/sgml/ref/create_table.sgml | 49 +++
src/backend/parser/gram.y | 81 +++-
src/backend/parser/parse_utilcmd.c | 142 +++++++
src/include/nodes/parsenodes.h | 23 +
src/include/partitioning/partdefs.h | 2 +
src/test/regress/expected/create_table.out | 464 +++++++++++++++++++++
src/test/regress/sql/create_table.sql | 265 ++++++++++++
7 files changed, 1024 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 10ef699fab..5a87fa1a7e 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -29,6 +29,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ]
[ USING <replaceable class="parameter">method</replaceable> ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -41,6 +42,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
) ]
[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ]
[ USING <replaceable class="parameter">method</replaceable> ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -53,6 +55,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
) ] { FOR VALUES <replaceable class="parameter">partition_bound_spec</replaceable> | DEFAULT }
[ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
+[ CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ]
[ USING <replaceable class="parameter">method</replaceable> ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
@@ -96,6 +99,11 @@ FROM ( { <replaceable class="parameter">partition_bound_expr</replaceable> | MIN
TO ( { <replaceable class="parameter">partition_bound_expr</replaceable> | MINVALUE | MAXVALUE } [, ...] ) |
WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REMAINDER <replaceable class="parameter">numeric_literal</replaceable> )
+<phrase>and <replaceable class="parameter">partition_bound_auto_spec</replaceable> is:</phrase>
+
+VALUES IN ( <replaceable class="parameter">partition_bound_expr</replaceable> [, ...] ), [( <replaceable class="parameter">partition_bound_expr</replaceable> [, ...] )] [, ...] [DEFAULT PARTITION <replaceable class="parameter">default_part_name</replaceable>]
+MODULUS <replaceable class="parameter">numeric_literal</replaceable>
+
<phrase><replaceable class="parameter">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
[ INCLUDE ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ]
@@ -447,6 +455,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
however, you can define these constraints on individual partitions.
</para>
+ <para>
+ Hash and list partitioning also support automatic creation of partitions
+ with an optional <literal>CONFIGURATION</literal> clause.
+ </para>
+
<para>
See <xref linkend="ddl-partitioning"/> for more discussion on table
partitioning.
@@ -455,6 +468,42 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>CONFIGURATION ( <replaceable class="parameter">partition_bound_auto_spec</replaceable> ) ] </literal></term>
+ <listitem>
+ <para>
+ The optional <literal>CONFIGURATION</literal> clause used together
+ with <literal>PARTITION BY</literal> specifies a rule of generating bounds
+ for partitions of the partitioned table. All partitions are created automatically
+ along with the parent table.
+
+ Any indexes, constraints and user-defined row-level triggers that exist
+ in the parent table are cloned on the new partitions. When using this clause,
+ <literal> CREATE TABLE </literal> statement can only contain clauses, that are
+ applicable to both relation kinds: partitioned table and regular partition tables.
+ All tables created by the statement will use same parameters, such as
+ relation persistence.
+ </para>
+
+ <para>
+ The <replaceable class="parameter">partition_bound_auto_spec</replaceable>
+ must correspond to the partitioning method and partition key of the
+ parent table, and must not overlap with any existing partition of that
+ parent. The form with <literal>VALUES IN</literal> is used for list partitioning
+ and the form with <literal>MODULUS</literal> is used for hash partitioning.
+ List partitioning can also provide a default partition using
+ <literal>DEFAULT PARTITION</literal>.
+ </para>
+
+ <para>
+ Automatic range partitioning is not supported yet.
+ </para>
+
+
+
+ </listitem>
+ </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> | DEFAULT }</literal></term>
<listitem>
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index edb6c00ece..af650d6235 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -270,6 +270,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
PartitionElem *partelem;
PartitionSpec *partspec;
PartitionBoundSpec *partboundspec;
+ PartitionBoundAutoSpec *partboundautospec;
RoleSpec *rolespec;
PublicationObjSpec *publicationobjectspec;
struct SelectLimit *selectlimit;
@@ -659,6 +660,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
json_object_constructor_null_clause_opt
json_array_constructor_null_clause_opt
+%type <partboundautospec> OptPartitionBoundAutoSpec values_in_clause p_desc
+%type <range> opt_default_partition_clause
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -4377,14 +4380,14 @@ OptPartitionSpec: PartitionSpec { $$ = $1; }
| /*EMPTY*/ { $$ = NULL; }
;
-PartitionSpec: PARTITION BY ColId '(' part_params ')'
+PartitionSpec: PARTITION BY ColId '(' part_params ')' OptPartitionBoundAutoSpec
{
PartitionSpec *n = makeNode(PartitionSpec);
n->strategy = parsePartitionStrategy($3);
n->partParams = $5;
n->location = @1;
-
+ n->autopart = (Node *) $7;
$$ = n;
}
;
@@ -4428,6 +4431,80 @@ part_elem: ColId opt_collate opt_qualified_name
}
;
+OptPartitionBoundAutoSpec:
+ CONFIGURATION '(' p_desc ')'
+ {
+ $$ = $3;
+ }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+p_desc:
+ hash_partbound
+ {
+ ListCell *lc;
+ PartitionBoundAutoSpec *n = makeNode(PartitionBoundAutoSpec);
+
+ n->modulus = -1;
+
+ foreach (lc, $1)
+ {
+ DefElem *opt = lfirst_node(DefElem, lc);
+
+ if (strcmp(opt->defname, "modulus") == 0)
+ {
+ n->strategy = PARTITION_STRATEGY_HASH;
+ if (n->modulus != -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("modulus for hash partition provided more than once"),
+ parser_errposition(opt->location)));
+ n->modulus = defGetInt32(opt);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized auto partition bound specification \"%s\"",
+ opt->defname),
+ parser_errposition(opt->location)));
+ }
+
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ | values_in_clause opt_default_partition_clause
+ {
+ PartitionBoundAutoSpec *n = $1;
+ n->default_partition_rv = $2;
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ ;
+
+values_in_clause:
+ VALUES IN_P '(' expr_list ')'
+ {
+ PartitionBoundAutoSpec *n = makeNode(PartitionBoundAutoSpec);
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatumsList = list_make1($4);
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ | values_in_clause ',' '(' expr_list ')'
+ {
+ PartitionBoundAutoSpec *n = (PartitionBoundAutoSpec *) $1;
+ n->strategy = PARTITION_STRATEGY_LIST;
+ n->listdatumsList = lappend(n->listdatumsList, $4);
+ $$ = (PartitionBoundAutoSpec *) n;
+ }
+ ;
+
+opt_default_partition_clause:
+ DEFAULT PARTITION qualified_name
+ {
+ $$ = $3;
+ }
+ | /* EMPTY */
+ { $$ = NULL; }
+ ;
+
table_access_method_clause:
USING name { $$ = $2; }
| /*EMPTY*/ { $$ = NULL; }
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e48e9e99d3..39c9383caa 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -72,6 +72,7 @@
/* State shared by transformCreateStmt and its subroutines */
typedef struct
{
+ CreateStmt *stmt; /* initial statement */
ParseState *pstate; /* overall parser state */
const char *stmtType; /* "CREATE [FOREIGN] TABLE" or "ALTER TABLE" */
RangeVar *relation; /* relation to create */
@@ -141,6 +142,8 @@ static Const *transformPartitionBoundValue(ParseState *pstate, Node *val,
const char *colName, Oid colType, int32 colTypmod,
Oid partCollation);
+static CreateStmt* initAutoPartitionCreateStmt(CreateStmtContext *cxt, char *part_relname);
+static void transformPartitionAutoCreate(CreateStmtContext *cxt, PartitionSpec* partspec);
/*
* transformCreateStmt -
@@ -234,6 +237,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.stmtType = "CREATE TABLE";
cxt.isforeign = false;
}
+ cxt.stmt = stmt;
cxt.relation = stmt->relation;
cxt.rel = NULL;
cxt.inhRelations = stmt->inhRelations;
@@ -341,6 +345,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
transformExtendedStatistics(&cxt);
+ /* Process partition definitions */
+ if (stmt->partspec && stmt->partspec->autopart)
+ transformPartitionAutoCreate(&cxt, stmt->partspec);
+
/*
* Output results.
*/
@@ -4358,3 +4366,137 @@ transformPartitionBoundValue(ParseState *pstate, Node *val,
return (Const *) value;
}
+
+
+/* init basic fields of auto generated partition */
+static CreateStmt*
+initAutoPartitionCreateStmt(CreateStmtContext *cxt, char *part_relname)
+{
+ CreateStmt *part;
+
+ part = copyObject(cxt->stmt);
+
+ part->relation = makeRangeVar(cxt->relation->schemaname,
+ part_relname, cxt->relation->location);
+
+ /* inherit persistence from parent relation */
+ part->relation->relpersistence = cxt->relation->relpersistence;
+ /* set parent table as a parent */
+ part->inhRelations = lappend(part->inhRelations, cxt->relation);
+
+ /*
+ * child table is not partitioned itself, at least now
+ * while we do not support multilevel auto partitioning
+ */
+ part->partspec = NULL;
+
+ /*
+ * Partition doesn't need a list of column definitions and constraints.
+ * They will be inherited from parent.
+ */
+ part->tableElts = NIL;
+ part->constraints = NIL;
+
+ return part;
+}
+
+/*
+ * Transform configuration into a set of partition bounds.
+ * Generate extra statements to create partition tables.
+ */
+static void
+transformPartitionAutoCreate(CreateStmtContext *cxt, PartitionSpec* partspec)
+{
+ CreateStmt *part;
+ List *partlist = NIL;
+ int i = 0;
+ PartitionBoundAutoSpec *bound = (PartitionBoundAutoSpec *) partspec->autopart;
+
+ elog(DEBUG1, "transformPartitionAutoCreate \n %s \n ", nodeToString(bound));
+
+ /*
+ * Generate regular partbounds based on autopart rule.
+ * and form create table statements from these partbounds
+ */
+ if (partspec->strategy == PARTITION_STRATEGY_HASH)
+ {
+ if (bound->strategy != PARTITION_STRATEGY_HASH)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a hash partition"),
+ parser_errposition(cxt->pstate, exprLocation((Node *) partspec))));
+
+ for (i = 0; i < bound->modulus; i++)
+ {
+ /*
+ * Generate partition name in the format:
+ * $relname_$partnum
+ * All checks of name validity will be made afterwards in DefineRelation()
+ */
+ part = initAutoPartitionCreateStmt(cxt, psprintf("%s_%d", cxt->relation->relname, i));
+
+ /* Actual partbound generation happens here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_HASH;
+ part->partbound->modulus = bound->modulus;
+ part->partbound->remainder = i;
+ part->partbound->is_default = false;
+
+ elog(DEBUG1,"stransformPartitionAutoCreate HASH i %d MODULUS %d \n %s\n",
+ i, bound->modulus, nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+ }
+ else if (partspec->strategy == PARTITION_STRATEGY_LIST)
+ {
+
+ int n_list_parts = list_length(bound->listdatumsList);
+
+ if (bound->strategy != PARTITION_STRATEGY_LIST)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation((Node *) partspec))));
+
+ for (i = 0; i < n_list_parts; i++)
+ {
+ List *listdatums = (List *)
+ list_nth(bound->listdatumsList, i);
+
+ part = initAutoPartitionCreateStmt(cxt, psprintf("%s_%d", cxt->relation->relname, i));
+
+ /* Actual partbound generation happens here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_LIST;
+ part->partbound->listdatums = list_copy(listdatums);
+ part->partbound->is_default = false;
+
+ elog(DEBUG1,"Debug transformPartitionAutoCreate LIST i %d \n %s\n",
+ i, nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+
+ if (bound->default_partition_rv)
+ {
+ part = initAutoPartitionCreateStmt(cxt, bound->default_partition_rv->relname);
+
+ /* TODO: Should we use fields from default_partition_rv, other than relname? */
+
+ /* Actual partbound generation happens here */
+ part->partbound = makeNode(PartitionBoundSpec);
+ part->partbound->strategy = PARTITION_STRATEGY_LIST;
+ part->partbound->listdatums = NULL;
+ part->partbound->is_default = true;
+
+ elog(DEBUG1,"Debug transformPartitionAutoCreate LIST default partition \n %s\n",
+ nodeToString(part));
+
+ partlist = lappend(partlist, part);
+ }
+ }
+
+ /* Add statements to create each partition after we create parent table */
+ cxt->alist = list_concat(cxt->alist, partlist);
+}
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index efb5c3e098..a486a3c4d9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -879,6 +879,9 @@ typedef struct PartitionSpec
PartitionStrategy strategy;
List *partParams; /* List of PartitionElems */
int location; /* token location, or -1 if unknown */
+
+ Node *autopart; /* PartitionBoundAutoSpec -
+ * spec to generate bounds automatically */
} PartitionSpec;
/*
@@ -908,6 +911,26 @@ struct PartitionBoundSpec
int location; /* token location, or -1 if unknown */
};
+/*
+ * PartitionBoundAutoSpec - a partition bound specification
+ * for auto generated partitions.
+ *
+ * This represents the rule of generating partition bounds
+ */
+struct PartitionBoundAutoSpec
+{
+ NodeTag type;
+
+ char strategy; /* see PARTITION_STRATEGY codes above */
+
+ /* Partitioning info for HASH strategy: */
+ int modulus;
+
+ /* Partitioning info for LIST strategy: */
+ List *listdatumsList; /* List of lists of Consts (or A_Consts in raw tree) */
+ RangeVar *default_partition_rv; /* Name of default list partition */
+};
+
/*
* PartitionRangeDatum - one of the values in a range partition bound
*
diff --git a/src/include/partitioning/partdefs.h b/src/include/partitioning/partdefs.h
index 55bb49c816..cfbbcab9e0 100644
--- a/src/include/partitioning/partdefs.h
+++ b/src/include/partitioning/partdefs.h
@@ -19,6 +19,8 @@ typedef struct PartitionKeyData *PartitionKey;
typedef struct PartitionBoundSpec PartitionBoundSpec;
+typedef struct PartitionBoundAutoSpec PartitionBoundAutoSpec;
+
typedef struct PartitionDescData *PartitionDesc;
typedef struct PartitionDirectoryData *PartitionDirectory;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 2a0902ece2..0d1a1d3b0e 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -1110,3 +1110,467 @@ Indexes:
"part_column_drop_1_10_expr_idx1" btree ((d = 2))
drop table part_column_drop;
+-- Auto generated partitions
+-- partitioning type not specified
+CREATE TABLE fail_part (a int) CONFIGURATION (MODULUS 10);
+ERROR: syntax error at or near "CONFIGURATION"
+LINE 1: CREATE TABLE fail_part (a int) CONFIGURATION (MODULUS 10);
+ ^
+-- must fail because of wrong configuration
+CREATE TABLE fail_part (a int) PARTITION BY HASH (a) CONFIGURATION
+(values in (1, 2), (3, 4) DEFAULT PARTITION part_default);
+ERROR: invalid bound specification for a hash partition
+LINE 1: CREATE TABLE fail_part (a int) PARTITION BY HASH (a) CONFIGU...
+ ^
+-- forbidden expressions for partition bound with list partitioned table
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (somename));
+ ^
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename.somename));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (somename.somename));
+ ^
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (a));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (a));
+ ^
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(a)));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (sum(a)));
+ ^
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(somename)));
+ERROR: cannot use column reference in partition bound expression
+LINE 2: (VALUES IN (sum(somename)));
+ ^
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(1)));
+ERROR: aggregate functions are not allowed in partition bound
+LINE 2: (VALUES IN (sum(1)));
+ ^
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((select 1)));
+ERROR: cannot use subquery in partition bound
+LINE 2: (VALUES IN ((select 1)));
+ ^
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (generate_series(4, 6)));
+ERROR: set-returning functions are not allowed in partition bound
+LINE 2: (VALUES IN (generate_series(4, 6)));
+ ^
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((1+1) collate "POSIX"));
+ERROR: collations are not supported by type integer
+LINE 2: (VALUES IN ((1+1) collate "POSIX"));
+ ^
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ());
+ERROR: syntax error at or near ")"
+LINE 2: (VALUES IN ());
+ ^
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES FROM (1) TO (2));
+ERROR: syntax error at or near "FROM"
+LINE 2: (VALUES FROM (1) TO (2));
+ ^
+-- trying to specify modulus and remainder for list partitioned table
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(MODULUS 10);
+ERROR: invalid bound specification for a list partition
+LINE 1: CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFI...
+ ^
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_parted (a bool) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (1));
+ERROR: specified value cannot be cast to type boolean for column "a"
+LINE 2: (VALUES IN (1));
+ ^
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE fail_parted (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),(null) DEFAULT PARTITION part_default);
+ERROR: partition "fail_parted_2" would overlap partition "fail_parted_0"
+LINE 2: (VALUES IN (null, 'z'),('a', 'b'),(null) DEFAULT PARTITION p...
+ ^
+CREATE TABLE fail_parted (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),('b', 'c') DEFAULT PARTITION part_default);
+ERROR: partition "fail_parted_2" would overlap partition "fail_parted_1"
+LINE 2: (VALUES IN (null, 'z'),('a', 'b'),('b', 'c') DEFAULT PARTITI...
+ ^
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(values in (1, 2), (1, 3));
+ERROR: partition "fail_parted_1" would overlap partition "fail_parted_0"
+LINE 2: (values in (1, 2), (1, 3));
+ ^
+-- trying to create default partition for the hash partitioned table
+REATE TABLE fail_parted (a int) PARTITION BY HASH (a) CONFIGURATION
+(MODULUS 10 DEFAULT PARTITION hash_default);
+ERROR: syntax error at or near "REATE"
+LINE 1: REATE TABLE fail_parted (a int) PARTITION BY HASH (a) CONFIG...
+ ^
+CREATE TABLE list_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(values in (1, 2), (3, 4) DEFAULT PARTITION part_default);
+\d+ list_parted
+ Partitioned table "public.list_parted"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Partition key: LIST (a)
+Partitions: list_parted_0 FOR VALUES IN (1, 2),
+ list_parted_1 FOR VALUES IN (3, 4),
+ part_default DEFAULT
+
+DROP TABLE list_parted;
+CREATE TABLE hash_parted (a int) PARTITION BY HASH (a) CONFIGURATION
+(modulus 3);
+\d+ hash_parted
+ Partitioned table "public.hash_parted"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Partition key: HASH (a)
+Partitions: hash_parted_0 FOR VALUES WITH (modulus 3, remainder 0),
+ hash_parted_1 FOR VALUES WITH (modulus 3, remainder 1),
+ hash_parted_2 FOR VALUES WITH (modulus 3, remainder 2)
+
+DROP TABLE hash_parted;
+CREATE TABLE list_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(values in ('1'), (2), (2+1), (null) DEFAULT PARTITION part_default);
+\d+ list_parted
+ Partitioned table "public.list_parted"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | integer | | | | plain | |
+Partition key: LIST (a)
+Partitions: list_parted_0 FOR VALUES IN (1),
+ list_parted_1 FOR VALUES IN (2),
+ list_parted_2 FOR VALUES IN (3),
+ list_parted_3 FOR VALUES IN (NULL),
+ part_default DEFAULT
+
+DROP TABLE list_parted;
+-- specified literal can be cast, and the cast might not be immutable
+CREATE TABLE moneyp (a money) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10), ('11'), (to_char(12, '99')::int));
+DROP TABLE moneyp;
+-- partition table inherits relation persistence setting from parent
+CREATE TEMP TABLE temp_parted (a char) PARTITION BY LIST (a)
+CONFIGURATION (VALUES IN ('a') DEFAULT PARTITION temp_parted_default);
+-- Because \d+ will issue a pg_temp_nn that .ou will never match
+SELECT * FROM (
+ WITH RECURSIVE ancestor(name) AS (SELECT c.relname AS "name" FROM pg_catalog.pg_class c WHERE c.relkind = 'p')
+ SELECT a.relname AS "parent",
+ 'PARENT' AS "type",
+ ancest.name AS "name",
+ pg_get_partkeydef(p.oid) AS "range"
+ FROM ancestor ancest
+ JOIN pg_catalog.pg_class p ON p.relname = ancest.name
+ LEFT JOIN pg_catalog.pg_inherits i ON i.inhrelid = p.oid
+ LEFT JOIN pg_catalog.pg_class a ON a.oid = i.inhparent
+ JOIN pg_catalog.pg_namespace n ON n.oid = p.relnamespace
+ WHERE p.relkind IN ('p', 'r')
+ UNION ALL
+ SELECT parent.relname AS "parent",
+ 'CHILD' AS "type",
+ child.relname AS "name",
+ pg_get_expr(child.relpartbound, child.oid) AS "range"
+ FROM ancestor ancest
+ JOIN pg_catalog.pg_class parent ON parent.relname = ancest.name
+ JOIN pg_catalog.pg_inherits inh ON inh.inhparent = parent.oid
+ JOIN pg_catalog.pg_class child ON child.oid = inh.inhrelid
+ JOIN pg_catalog.pg_namespace namespace ON namespace.oid = child.relnamespace
+ WHERE parent.relkind IN ('p', 'r')
+ ) AS dp
+WHERE
+ "parent" = 'temp_parted'
+ORDER BY "parent" NULLS FIRST, "type" DESC, "name";
+ parent | type | name | range
+-------------+-------+---------------------+---------------------
+ temp_parted | CHILD | temp_parted_0 | FOR VALUES IN ('a')
+ temp_parted | CHILD | temp_parted_default | DEFAULT
+(2 rows)
+
+DROP TABLE temp_parted;
+-- partition table inherits relation persistence setting from parent
+CREATE UNLOGGED TABLE unlogged_parted (a char) PARTITION BY LIST (a)
+CONFIGURATION (VALUES IN ('a') DEFAULT PARTITION unlogged_parted_default);
+\d+ unlogged_parted
+ Unlogged partitioned table "public.unlogged_parted"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+--------------+-----------+----------+---------+----------+--------------+-------------
+ a | character(1) | | | | extended | |
+Partition key: LIST (a)
+Partitions: unlogged_parted_0 FOR VALUES IN ('a'),
+ unlogged_parted_default DEFAULT
+
+DROP TABLE unlogged_parted;
+-- testing PARTITION OF on automatically generated partitioned table
+CREATE TABLE hash_parted (a int) PARTITION BY HASH (a) CONFIGURATION (MODULUS 10);
+-- all remainder values are already belong to partitions
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODULUS 30, REMAINDER 3);
+ERROR: partition "fail_part" would overlap partition "hash_parted_3"
+LINE 1: ...BLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODU...
+ ^
+-- trying to specify range for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a', 1) TO ('z');
+ERROR: invalid bound specification for a hash partition
+LINE 1: ...BLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a',...
+ ^
+-- trying to specify list value for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000);
+ERROR: invalid bound specification for a hash partition
+LINE 1: ...BLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000);
+ ^
+-- trying to add default partition to hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted DEFAULT;
+ERROR: a hash-partitioned table may not have a default partition
+\d hash_parted
+ Partitioned table "public.hash_parted"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition key: HASH (a)
+Number of partitions: 10 (Use \d+ to list them.)
+
+DROP TABLE hash_parted;
+-- cast is immutable
+CREATE TABLE bigintp (a bigint) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10));
+-- fails due to overlap:
+CREATE TABLE bigintp_overlap PARTITION OF bigintp FOR VALUES IN ('10');
+ERROR: partition "bigintp_overlap" would overlap partition "bigintp_0"
+LINE 1: ...E bigintp_overlap PARTITION OF bigintp FOR VALUES IN ('10');
+ ^
+DROP TABLE bigintp;
+-- check default partition overlap
+CREATE TABLE list_parted (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b') DEFAULT partition tbl_default);
+INSERT INTO list_parted VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('W', 'X', 'Y');
+ERROR: updated partition constraint for default partition "tbl_default" would be violated by some row
+-- trying to create already existing default partition
+CREATE TABLE fail_part PARTITION OF list_parted DEFAULT;
+ERROR: partition "fail_part" conflicts with existing default partition "tbl_default"
+LINE 1: CREATE TABLE fail_part PARTITION OF list_parted DEFAULT;
+ ^
+DROP TABLE list_parted;
+-- check schema propagation from parent
+CREATE TABLE parted (a text, b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0))
+PARTITION BY LIST (a) CONFIGURATION ( VALUES IN ('a','b'),('d') );
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute
+ WHERE attrelid = 'parted_1'::regclass and attnum > 0
+ ORDER BY attnum;
+ attname | attislocal | attinhcount
+---------+------------+-------------
+ a | f | 1
+ b | f | 1
+(2 rows)
+
+-- able to specify column default, column constraint, and table constraint
+-- first check the "column specified more than once" error
+CREATE TABLE part_e_fail PARTITION OF parted (
+ b NOT NULL,
+ b DEFAULT 1,
+ b CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('e');
+ERROR: column "b" specified more than once
+CREATE TABLE part_e PARTITION OF parted (
+ b NOT NULL DEFAULT 1,
+ CONSTRAINT check_a CHECK (length(a) > 0),
+ CONSTRAINT check_b CHECK (b >= 0)
+) FOR VALUES IN ('e');
+NOTICE: merging constraint "check_a" with inherited definition
+-- conislocal should be false for any merged constraints, true otherwise
+SELECT conname, conislocal, coninhcount FROM pg_constraint
+WHERE conrelid = 'part_e'::regclass ORDER BY conislocal, coninhcount;
+ conname | conislocal | coninhcount
+---------+------------+-------------
+ check_a | f | 1
+ check_b | t | 0
+(2 rows)
+
+-- check_a can not be dropped as it is inherited
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+ERROR: cannot drop inherited constraint "check_a" of relation "part_e"
+-- check_b can be dropped as it is local
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+-- Once check_b is added to the parent, it should be made non-local for part_b
+ALTER TABLE part_e ADD CONSTRAINT check_b CHECK (b >= 0);
+ALTER TABLE parted ADD CONSTRAINT check_b CHECK (b >= 0);
+NOTICE: merging constraint "check_b" with inherited definition
+SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+ conname | conislocal | coninhcount
+---------+------------+-------------
+ check_a | f | 1
+ check_b | f | 1
+(2 rows)
+
+-- Neither check_a nor check_b are droppable from part_b
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+ERROR: cannot drop inherited constraint "check_a" of relation "part_e"
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+ERROR: cannot drop inherited constraint "check_b" of relation "part_e"
+-- And dropping it from parted should leave no trace of them on part_e, unlike
+-- traditional inheritance where they will be left behind, because they would
+-- be local constraints.
+ALTER TABLE parted DROP CONSTRAINT check_a, DROP CONSTRAINT check_b;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+ conislocal | coninhcount
+------------+-------------
+(0 rows)
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY HASH (c);
+ERROR: column "c" named in partition key does not exist
+LINE 1: ...RTITION OF parted FOR VALUES IN ('c') PARTITION BY HASH (c);
+ ^
+CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0)
+FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- check that NOT NULL and default value are inherited correctly
+CREATE TABLE parted_notnull_inh_test (a int DEFAULT 1, b int NOT NULL DEFAULT 0)
+ PARTITION BY LIST(a) CONFIGURATION (VALUES IN (1));
+INSERT INTO parted_notnull_inh_test (b) VALUES (NULL);
+ERROR: null value in column "b" of relation "parted_notnull_inh_test_0" violates not-null constraint
+DETAIL: Failing row contains (1, null).
+-- note that a's default is preserved
+\d parted_notnull_inh_test1
+DROP TABLE parted_notnull_inh_test;
+-- Partition bound in describe output
+\d+ part_e
+ Table "public.part_e"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | not null | 1 | plain | |
+Partition of: parted FOR VALUES IN ('e')
+Partition constraint: ((a IS NOT NULL) AND (a = 'e'::text))
+
+-- Both partition bound and partition key in describe output
+\d+ part_c
+ Partitioned table "public.part_c"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | not null | 0 | plain | |
+Partition of: parted FOR VALUES IN ('c')
+Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text))
+Partition key: RANGE (b)
+Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
+
+-- a level-2 partition's constraint will include the parent's expressions
+\d+ part_c_1_10
+ Table "public.part_c_1_10"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a | text | | | | extended | |
+ b | integer | | not null | 0 | plain | |
+Partition of: part_c FOR VALUES FROM (1) TO (10)
+Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text) AND (b IS NOT NULL) AND (b >= 1) AND (b < 10))
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+ Partitioned table "public.parted"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | text | | |
+ b | integer | | not null | 0
+Partition key: LIST (a)
+Number of partitions: 4 (Use \d+ to list them.)
+
+DROP TABLE parted;
+-- list partitioning on array type column
+CREATE TABLE arrlp (a int[]) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ('{1}', '{2}'));
+\d+ arrlp_1
+DROP TABLE arrlp;
+-- partition on boolean column
+CREATE TABLE boolspart (a bool) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (true), (false));
+\d+ boolspart
+ Partitioned table "public.boolspart"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a | boolean | | | | plain | |
+Partition key: LIST (a)
+Partitions: boolspart_0 FOR VALUES IN (true),
+ boolspart_1 FOR VALUES IN (false)
+
+DROP TABLE boolspart;
+-- test using a volatile expression as partition bound
+CREATE TABLE volatile_partbound_test (partkey timestamp) PARTITION BY LIST (partkey) CONFIGURATION
+(VALUES IN ('1970-01-01 00:00:00+00'::timestamp, current_timestamp),('1982-01-25 00:00:00+00'::timestamp));
+DROP TABLE volatile_partbound_test;
+-- tests of column drop with partition tables and indexes using
+-- predicates and expressions.
+CREATE TABLE part_column_drop (useless_1 int, id int, useless_2 int, d int,
+ b int, useless_3 int) PARTITION BY HASH (id) CONFIGURATION (MODULUS 3);
+ALTER TABLE part_column_drop DROP COLUMN useless_1;
+ALTER TABLE part_column_drop DROP COLUMN useless_2;
+ALTER TABLE part_column_drop DROP COLUMN useless_3;
+CREATE INDEX part_column_drop_b_pred ON part_column_drop(b) WHERE b = 1;
+CREATE INDEX part_column_drop_b_expr ON part_column_drop((b = 1));
+CREATE INDEX part_column_drop_d_pred ON part_column_drop(d) WHERE d = 2;
+CREATE INDEX part_column_drop_d_expr ON part_column_drop((d = 2));
+CREATE INDEX part_column_drop_d_1_pred ON part_column_drop_1(d) WHERE d = 2;
+CREATE INDEX part_column_drop_d_1_expr ON part_column_drop_1((d = 2));
+\d part_column_drop
+ Partitioned table "public.part_column_drop"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ d | integer | | |
+ b | integer | | |
+Partition key: HASH (id)
+Indexes:
+ "part_column_drop_b_expr" btree ((b = 1))
+ "part_column_drop_b_pred" btree (b) WHERE b = 1
+ "part_column_drop_d_expr" btree ((d = 2))
+ "part_column_drop_d_pred" btree (d) WHERE d = 2
+Number of partitions: 3 (Use \d+ to list them.)
+
+\d part_column_drop_1
+ Table "public.part_column_drop_1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ d | integer | | |
+ b | integer | | |
+Partition of: part_column_drop FOR VALUES WITH (modulus 3, remainder 1)
+Indexes:
+ "part_column_drop_1_b_idx" btree (b) WHERE b = 1
+ "part_column_drop_1_d_idx" btree (d) WHERE d = 2
+ "part_column_drop_1_expr_idx" btree ((b = 1))
+ "part_column_drop_1_expr_idx1" btree ((d = 2))
+ "part_column_drop_d_1_expr" btree ((d = 2))
+ "part_column_drop_d_1_pred" btree (d) WHERE d = 2
+
+\d part_column_drop_2
+ Table "public.part_column_drop_2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ id | integer | | |
+ d | integer | | |
+ b | integer | | |
+Partition of: part_column_drop FOR VALUES WITH (modulus 3, remainder 2)
+Indexes:
+ "part_column_drop_2_b_idx" btree (b) WHERE b = 1
+ "part_column_drop_2_d_idx" btree (d) WHERE d = 2
+ "part_column_drop_2_expr_idx" btree ((b = 1))
+ "part_column_drop_2_expr_idx1" btree ((d = 2))
+
+\d part_column_drop_3
+DROP TABLE part_column_drop;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 82ada47661..48d7686c90 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -730,3 +730,268 @@ create table part_column_drop_1_10 partition of
\d part_column_drop
\d part_column_drop_1_10
drop table part_column_drop;
+
+-- Auto generated partitions
+-- partitioning type not specified
+CREATE TABLE fail_part (a int) CONFIGURATION (MODULUS 10);
+
+-- must fail because of wrong configuration
+CREATE TABLE fail_part (a int) PARTITION BY HASH (a) CONFIGURATION
+(values in (1, 2), (3, 4) DEFAULT PARTITION part_default);
+
+-- forbidden expressions for partition bound with list partitioned table
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename));
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (somename.somename));
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (a));
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(a)));
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(somename)));
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (sum(1)));
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((select 1)));
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (generate_series(4, 6)));
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ((1+1) collate "POSIX"));
+
+-- syntax does not allow empty list of values for list partitions
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ());
+-- trying to specify range for list partitioned table
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(VALUES FROM (1) TO (2));
+-- trying to specify modulus and remainder for list partitioned table
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(MODULUS 10);
+-- specified literal can't be cast to the partition column data type
+CREATE TABLE fail_parted (a bool) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (1));
+
+-- check for partition bound overlap and other invalid specifications
+CREATE TABLE fail_parted (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),(null) DEFAULT PARTITION part_default);
+
+CREATE TABLE fail_parted (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b'),('b', 'c') DEFAULT PARTITION part_default);
+
+CREATE TABLE fail_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(values in (1, 2), (1, 3));
+
+-- trying to create default partition for the hash partitioned table
+REATE TABLE fail_parted (a int) PARTITION BY HASH (a) CONFIGURATION
+(MODULUS 10 DEFAULT PARTITION hash_default);
+
+CREATE TABLE list_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(values in (1, 2), (3, 4) DEFAULT PARTITION part_default);
+\d+ list_parted
+DROP TABLE list_parted;
+
+CREATE TABLE hash_parted (a int) PARTITION BY HASH (a) CONFIGURATION
+(modulus 3);
+\d+ hash_parted
+DROP TABLE hash_parted;
+
+CREATE TABLE list_parted (a int) PARTITION BY LIST (a) CONFIGURATION
+(values in ('1'), (2), (2+1), (null) DEFAULT PARTITION part_default);
+\d+ list_parted
+DROP TABLE list_parted;
+
+-- specified literal can be cast, and the cast might not be immutable
+CREATE TABLE moneyp (a money) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10), ('11'), (to_char(12, '99')::int));
+DROP TABLE moneyp;
+
+-- partition table inherits relation persistence setting from parent
+CREATE TEMP TABLE temp_parted (a char) PARTITION BY LIST (a)
+CONFIGURATION (VALUES IN ('a') DEFAULT PARTITION temp_parted_default);
+
+-- Because \d+ will issue a pg_temp_nn that .ou will never match
+SELECT * FROM (
+ WITH RECURSIVE ancestor(name) AS (SELECT c.relname AS "name" FROM pg_catalog.pg_class c WHERE c.relkind = 'p')
+ SELECT a.relname AS "parent",
+ 'PARENT' AS "type",
+ ancest.name AS "name",
+ pg_get_partkeydef(p.oid) AS "range"
+ FROM ancestor ancest
+ JOIN pg_catalog.pg_class p ON p.relname = ancest.name
+ LEFT JOIN pg_catalog.pg_inherits i ON i.inhrelid = p.oid
+ LEFT JOIN pg_catalog.pg_class a ON a.oid = i.inhparent
+ JOIN pg_catalog.pg_namespace n ON n.oid = p.relnamespace
+ WHERE p.relkind IN ('p', 'r')
+ UNION ALL
+ SELECT parent.relname AS "parent",
+ 'CHILD' AS "type",
+ child.relname AS "name",
+ pg_get_expr(child.relpartbound, child.oid) AS "range"
+ FROM ancestor ancest
+ JOIN pg_catalog.pg_class parent ON parent.relname = ancest.name
+ JOIN pg_catalog.pg_inherits inh ON inh.inhparent = parent.oid
+ JOIN pg_catalog.pg_class child ON child.oid = inh.inhrelid
+ JOIN pg_catalog.pg_namespace namespace ON namespace.oid = child.relnamespace
+ WHERE parent.relkind IN ('p', 'r')
+ ) AS dp
+WHERE
+ "parent" = 'temp_parted'
+ORDER BY "parent" NULLS FIRST, "type" DESC, "name";
+
+
+
+DROP TABLE temp_parted;
+
+-- partition table inherits relation persistence setting from parent
+CREATE UNLOGGED TABLE unlogged_parted (a char) PARTITION BY LIST (a)
+CONFIGURATION (VALUES IN ('a') DEFAULT PARTITION unlogged_parted_default);
+\d+ unlogged_parted
+DROP TABLE unlogged_parted;
+
+-- testing PARTITION OF on automatically generated partitioned table
+
+CREATE TABLE hash_parted (a int) PARTITION BY HASH (a) CONFIGURATION (MODULUS 10);
+-- all remainder values are already belong to partitions
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES WITH (MODULUS 30, REMAINDER 3);
+-- trying to specify range for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES FROM ('a', 1) TO ('z');
+-- trying to specify list value for the hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES IN (1000);
+-- trying to add default partition to hash partitioned table
+CREATE TABLE fail_part PARTITION OF hash_parted DEFAULT;
+\d hash_parted
+DROP TABLE hash_parted;
+
+-- cast is immutable
+CREATE TABLE bigintp (a bigint) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (10));
+-- fails due to overlap:
+CREATE TABLE bigintp_overlap PARTITION OF bigintp FOR VALUES IN ('10');
+DROP TABLE bigintp;
+
+-- check default partition overlap
+CREATE TABLE list_parted (a varchar) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (null, 'z'),('a', 'b') DEFAULT partition tbl_default);
+INSERT INTO list_parted VALUES('X');
+CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES IN ('W', 'X', 'Y');
+-- trying to create already existing default partition
+CREATE TABLE fail_part PARTITION OF list_parted DEFAULT;
+DROP TABLE list_parted;
+
+-- check schema propagation from parent
+CREATE TABLE parted (a text, b int NOT NULL DEFAULT 0,
+ CONSTRAINT check_a CHECK (length(a) > 0))
+PARTITION BY LIST (a) CONFIGURATION ( VALUES IN ('a','b'),('d') );
+
+-- only inherited attributes (never local ones)
+SELECT attname, attislocal, attinhcount FROM pg_attribute
+ WHERE attrelid = 'parted_1'::regclass and attnum > 0
+ ORDER BY attnum;
+
+-- able to specify column default, column constraint, and table constraint
+-- first check the "column specified more than once" error
+CREATE TABLE part_e_fail PARTITION OF parted (
+ b NOT NULL,
+ b DEFAULT 1,
+ b CHECK (b >= 0),
+ CONSTRAINT check_a CHECK (length(a) > 0)
+) FOR VALUES IN ('e');
+
+CREATE TABLE part_e PARTITION OF parted (
+ b NOT NULL DEFAULT 1,
+ CONSTRAINT check_a CHECK (length(a) > 0),
+ CONSTRAINT check_b CHECK (b >= 0)
+) FOR VALUES IN ('e');
+-- conislocal should be false for any merged constraints, true otherwise
+SELECT conname, conislocal, coninhcount FROM pg_constraint
+WHERE conrelid = 'part_e'::regclass ORDER BY conislocal, coninhcount;
+
+-- check_a can not be dropped as it is inherited
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+-- check_b can be dropped as it is local
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+
+-- Once check_b is added to the parent, it should be made non-local for part_b
+ALTER TABLE part_e ADD CONSTRAINT check_b CHECK (b >= 0);
+ALTER TABLE parted ADD CONSTRAINT check_b CHECK (b >= 0);
+SELECT conname, conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+
+-- Neither check_a nor check_b are droppable from part_b
+ALTER TABLE part_e DROP CONSTRAINT check_a;
+ALTER TABLE part_e DROP CONSTRAINT check_b;
+
+-- And dropping it from parted should leave no trace of them on part_e, unlike
+-- traditional inheritance where they will be left behind, because they would
+-- be local constraints.
+ALTER TABLE parted DROP CONSTRAINT check_a, DROP CONSTRAINT check_b;
+SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_e'::regclass;
+
+-- specify PARTITION BY for a partition
+CREATE TABLE fail_part_col_not_found PARTITION OF parted FOR VALUES IN ('c') PARTITION BY HASH (c);
+CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0)
+FOR VALUES IN ('c') PARTITION BY RANGE ((b));
+
+-- create a level-2 partition
+CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+
+-- check that NOT NULL and default value are inherited correctly
+CREATE TABLE parted_notnull_inh_test (a int DEFAULT 1, b int NOT NULL DEFAULT 0)
+ PARTITION BY LIST(a) CONFIGURATION (VALUES IN (1));
+INSERT INTO parted_notnull_inh_test (b) VALUES (NULL);
+-- note that a's default is preserved
+\d parted_notnull_inh_test1
+DROP TABLE parted_notnull_inh_test;
+
+-- Partition bound in describe output
+\d+ part_e
+
+-- Both partition bound and partition key in describe output
+\d+ part_c
+
+-- a level-2 partition's constraint will include the parent's expressions
+\d+ part_c_1_10
+
+-- Show partition count in the parent's describe output
+-- Tempted to include \d+ output listing partitions with bound info but
+-- output could vary depending on the order in which partition oids are
+-- returned.
+\d parted
+DROP TABLE parted;
+
+-- list partitioning on array type column
+CREATE TABLE arrlp (a int[]) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN ('{1}', '{2}'));
+\d+ arrlp_1
+DROP TABLE arrlp;
+
+-- partition on boolean column
+CREATE TABLE boolspart (a bool) PARTITION BY LIST (a) CONFIGURATION
+(VALUES IN (true), (false));
+\d+ boolspart
+DROP TABLE boolspart;
+
+-- test using a volatile expression as partition bound
+CREATE TABLE volatile_partbound_test (partkey timestamp) PARTITION BY LIST (partkey) CONFIGURATION
+(VALUES IN ('1970-01-01 00:00:00+00'::timestamp, current_timestamp),('1982-01-25 00:00:00+00'::timestamp));
+DROP TABLE volatile_partbound_test;
+
+-- tests of column drop with partition tables and indexes using
+-- predicates and expressions.
+CREATE TABLE part_column_drop (useless_1 int, id int, useless_2 int, d int,
+ b int, useless_3 int) PARTITION BY HASH (id) CONFIGURATION (MODULUS 3);
+ALTER TABLE part_column_drop DROP COLUMN useless_1;
+ALTER TABLE part_column_drop DROP COLUMN useless_2;
+ALTER TABLE part_column_drop DROP COLUMN useless_3;
+CREATE INDEX part_column_drop_b_pred ON part_column_drop(b) WHERE b = 1;
+CREATE INDEX part_column_drop_b_expr ON part_column_drop((b = 1));
+CREATE INDEX part_column_drop_d_pred ON part_column_drop(d) WHERE d = 2;
+CREATE INDEX part_column_drop_d_expr ON part_column_drop((d = 2));
+CREATE INDEX part_column_drop_d_1_pred ON part_column_drop_1(d) WHERE d = 2;
+CREATE INDEX part_column_drop_d_1_expr ON part_column_drop_1((d = 2));
+
+\d part_column_drop
+\d part_column_drop_1
+\d part_column_drop_2
+\d part_column_drop_3
+DROP TABLE part_column_drop;
--
2.34.1