From: amit Date: Thu, 3 Mar 2016 18:29:15 +0900 Subject: [PATCH 4/7] Add syntax to create partitions. As follows: CREATE TABLE name PARTITION OF parent [ ( { column_name WITH OPTION column_constraint | table_constraint } [, ...] ) ] partition_bound_spec [ PARTITION BY partition_key_spec ] ALTER TABLE parent ATTACH PARTITION name partition_bound_spec [ VALIDATE | NO VALIDATE ] ALTER TABLE parent DETACH PARTITION name partition_bound_spec refers to either list partition bound of form: FOR VALUES IN (list-of-expressions) or range partition bound of form: FOR VALUES range_spec where range_spec is: START lower-bound [ INCLUSIVE | EXCLUSIVE ] END upper-bound [ INCLUSIVE | EXCLUSIVE ] lower-bound and upper-bound is: { ( expression [,...] ) | UNBOUNDED } In the above, each of lower-bound and upper-bound is either a single expression for a single column key or list of expressions for a multi- column key or empty. Note that in the absence of explicit specification, lower-bound is inclusive and upper-bound is exclusive. A partition is created as inheritance child of the parent with certain differences from regular inheritance behavior. For example, when a new partition is created, it cannot contain its own column definitions or constraints (unlike CREATE TABLE child(...) INHERITS(parent)). When "attaching" a table as partition of parent, it cannot contain columns that are not contained in the parent (unlike ALTER TABLE child INHERIT parent). No dependency is created between a partition and the parent. One cannot create regular inheritance children of a partitioned table. Whereas this commit enables inheritance, it does not yet store partition bound info anywhere, IOW, the fact that a given inheritance child is a "partition" is not persisted. --- doc/src/sgml/ref/alter_table.sgml | 57 +++++ doc/src/sgml/ref/create_table.sgml | 77 ++++++- src/backend/commands/createas.c | 1 + src/backend/commands/sequence.c | 1 + src/backend/commands/tablecmds.c | 490 +++++++++++++++++++++++++++--------- src/backend/commands/view.c | 1 + src/backend/nodes/copyfuncs.c | 48 ++++ src/backend/nodes/equalfuncs.c | 42 +++ src/backend/nodes/outfuncs.c | 27 ++ src/backend/parser/gram.y | 167 ++++++++++++- src/backend/parser/parse_agg.c | 7 +- src/backend/parser/parse_expr.c | 23 ++ src/backend/parser/parse_utilcmd.c | 318 +++++++++++++++++++++++ src/include/catalog/partition.h | 1 + src/include/nodes/nodes.h | 3 + src/include/nodes/parsenodes.h | 43 +++- src/include/parser/kwlist.h | 3 + src/include/parser/parse_node.h | 3 +- 18 files changed, 1182 insertions(+), 130 deletions(-) diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 5ca211e..97744c9 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -77,12 +77,15 @@ ALTER TABLE ALL IN TABLESPACE name NOT OF OWNER TO { new_owner | CURRENT_USER | SESSION_USER } REPLICA IDENTITY { DEFAULT | USING INDEX index_name | FULL | NOTHING } + ATTACH PARTITION name partition_bound_spec [ VALIDATE | NO VALIDATE ] + DETACH PARTITION name and table_constraint_using_index is: [ CONSTRAINT constraint_name ] { UNIQUE | PRIMARY KEY } USING INDEX index_name [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] + @@ -704,6 +707,51 @@ ALTER TABLE ALL IN TABLESPACE name + + ATTACH PARTITION + + + This form attaches an existing table as partition of the target table, + provided the target table is partitioned. The partition bound + specification must correspond with partition method and key of the + partitioned table. The table being attached must have all the columns + as the target table and no more. Also, it must have all the matching + constraints as the target table. That includes NOT NULL + and CHECK constraints. UNIQUE, + PRIMARY KEY, and FOREIGN KEY + constraints are not considered, but this might change in the future. + + + + If NO VALIDATE clause is specified, existing rows in + source table are are not checked to see if they violate the partition + bound specification. This allows to quickly load a large amount of data + into the partitioned table. The default behavior is to perform the + check, as if VALIDATE clause were specified. + + + + Note that once the table becomes a partition, it will be dropped or + truncated when the parent table is dropped or truncated. Dropping + it directly using DROP TABLE will fail; it must + first be detached from the parent. However, truncating a partition + directly works. + + + + + + DETACH PARTITION + + + This form detaches specified partition of the target table, provided the + target table is partitioned and the specified partition exists. The + resulting table continues to exist as a standalone table with no ties + remaining with the parent partitioned table. + + + + @@ -936,6 +984,15 @@ ALTER TABLE ALL IN TABLESPACE name + + partition_bound_spec + + + The partition bound specification for a new partition. + + + + diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 70a7579..79fb521 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -44,6 +44,17 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ] [ TABLESPACE tablespace_name ] +CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] table_name + PARTITION OF parent_table [ ( + { column_name WITH OPTIONS [ column_constraint [ ... ] ] + | table_constraint } + [, ... ] +) ] partition_bound_spec +[ PARTITION BY {RANGE | LIST} ( { column_name | ( expression ) } [ opclass ] [, ...] ) +[ WITH ( storage_parameter [= value] [, ... ] ) | WITH OIDS | WITHOUT OIDS ] +[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ] +[ TABLESPACE tablespace_name ] + where column_constraint is: [ CONSTRAINT constraint_name ] @@ -72,6 +83,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL } +and partition_bound_spec is: + +FOR VALUES { list_spec | range_spec } + index_parameters in UNIQUE, PRIMARY KEY, and EXCLUDE constraints are: [ WITH ( storage_parameter [= value] [, ... ] ) ] @@ -80,8 +95,20 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI exclude_element in an EXCLUDE constraint is: { column_name | ( expression ) } [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] - +list_spec in FOR VALUES is: + +IN ( expression [, ...] ) + +range_spec in FOR VALUES is: + +START lower-bound [ INCLUSIVE | EXCLUSIVE ] END upper-bound [ INCLUSIVE | EXCLUSIVE ] + +where lower-bound and upper-bound are: + +{ ( expression [, ...] ) | UNBOUNDED } + + @@ -232,6 +259,49 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI + PARTITION OF parent_table + + + Creates the table as partition of the specified + parent table (name optionally schema-qualified). + + + + A partition bound specification must be present and must correspond with + partition method and key of the parent table. It is checked using the + specification that the new partition does not overlap with any existing + partitions of the parent. + + + + A partition cannot have columns other than those inherited from the + parent. That includes the oid column, which can be + specified using the WITH (OIDS) clause. On the other + hand, if parent has the oid column, the partition + inherits the same, overriding the WITH (OIDS=FALSE) + clause, if any. Defaults and constraints can optionally be specified + for each of the inherited columns, which override those in the parent. + One can also specify table constraints, in addition to those inherited + from the parent. Note that all subsequent schema modifications to the + parent propagate to partition. + + + + Any data row subsequently inserted into the parent table is mapped to + and stored in the partition, provided partition key of the row falls + within the partition bounds. + + + + A partition is dropped or truncated when the parent table is dropped or + truncated. Dropping it directly using DROP TABLE + will fail; it must first be detached from the parent. + However, truncating a partition directly works. + + + + + column_name @@ -340,6 +410,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI + When specifying for a table being created as partition, one needs to + use column names from the parent table as part of the key. + + + Currently, there are following limitations on definition of partitioned tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or FOREIGN KEY constraints. diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index cb7a145..427470c 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -342,6 +342,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) col->is_local = true; col->is_not_null = false; col->is_from_type = false; + col->is_for_partition = false; col->storage = 0; col->raw_default = NULL; col->cooked_default = NULL; diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 5dcd4cf..21d9f96 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -159,6 +159,7 @@ DefineSequence(CreateSeqStmt *seq) coldef->is_local = true; coldef->is_not_null = true; coldef->is_from_type = false; + coldef->is_for_partition = false; coldef->storage = 0; coldef->raw_default = NULL; coldef->cooked_default = NULL; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 87bf466..192cae9 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -279,7 +279,8 @@ typedef struct static void truncate_check_rel(Relation rel); static List *MergeAttributes(List *schema, List *supers, char relpersistence, - List **supOids, List **supconstr, int *supOidCount); + List **supOids, List **supconstr, int *supOidCount, + bool is_partition); static bool MergeCheckConstraint(List *constraints, char *name, Node *expr); static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel); static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel); @@ -448,6 +449,11 @@ static void ComputePartitionAttrs(Oid relid, List *partParams, static bool find_attr_reference_walker(Node *node, find_attr_reference_context *context); static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr); +static void CreateInheritance(Relation child_rel, Relation parent_rel); +static void RemoveInheritance(Relation child_rel, Relation parent_rel); +static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel, + PartitionCmd *cmd); +static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name); /* ---------------------------------------------------------------- @@ -590,10 +596,16 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, /* * Look up inheritance ancestors and generate relation schema, including * inherited attributes. + * + * The last parameter implicitly specifies that the table is being created + * as partition and schema consists of columns definitions corresponding + * to non-dropped columns of the parent constructed such that each + * attribute of the table is created as inherited and non-local. */ schema = MergeAttributes(schema, stmt->inhRelations, stmt->relation->relpersistence, - &inheritOids, &old_constraints, &parentOidCount); + &inheritOids, &old_constraints, &parentOidCount, + stmt->partbound != NULL); /* * Create a tuple descriptor from the relation schema. Note that this @@ -1452,7 +1464,8 @@ storage_name(char c) */ static List * MergeAttributes(List *schema, List *supers, char relpersistence, - List **supOids, List **supconstr, int *supOidCount) + List **supOids, List **supconstr, int *supOidCount, + bool is_partition) { ListCell *entry; List *inhSchema = NIL; @@ -1497,8 +1510,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence, /* * Typed table column option that does not belong to a column from - * the type. This works because the columns from the type come - * first in the list. + * the type or the partition parent. This works because the columns + * from the type or the partition parent come first in the list. */ ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), @@ -1525,6 +1538,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence, coldef->is_from_type = false; list_delete_cell(schema, rest, prev); } + else if (coldef->is_for_partition) + { + /* + * merge the column options into the column from the parent + */ + coldef->is_not_null = restdef->is_not_null; + coldef->raw_default = restdef->raw_default; + coldef->cooked_default = restdef->cooked_default; + coldef->constraints = restdef->constraints; + coldef->is_for_partition = false; /* job is done */ + list_delete_cell(schema, rest, prev); + } else ereport(ERROR, (errcode(ERRCODE_DUPLICATE_COLUMN), @@ -1563,8 +1588,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence, */ relation = heap_openrv(parent, ShareUpdateExclusiveLock); - /* Cannot inherit from partitioned tables */ - if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL) + /* + * Cannot inherit from a partitioned table unless being created as + * partition. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL && + !is_partition) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot inherit from partitioned table \"%s\"", @@ -1710,6 +1739,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence, def->is_local = false; def->is_not_null = attribute->attnotnull; def->is_from_type = false; + def->is_for_partition = false; def->storage = attribute->attstorage; def->raw_default = NULL; def->cooked_default = NULL; @@ -1838,6 +1868,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence, * If we had no inherited attributes, the result schema is just the * explicitly declared columns. Otherwise, we need to merge the declared * columns into the inherited schema list. + * + * Note: In case of a partition, there are only inherited attributes that + * we copied from the parent as so, in transformPartitionOf(). */ if (inhSchema != NIL) { @@ -1867,16 +1900,20 @@ MergeAttributes(List *schema, List *supers, char relpersistence, /* * Yes, try to merge the two column definitions. They must - * have the same type, typmod, and collation. + * have the same type, typmod, and collation. Don't output + * the notices, if partition, per note above. */ - if (exist_attno == schema_attno) - ereport(NOTICE, - (errmsg("merging column \"%s\" with inherited definition", - attributeName))); - else - ereport(NOTICE, - (errmsg("moving and merging column \"%s\" with inherited definition", attributeName), - errdetail("User-specified column moved to the position of the inherited column."))); + if (!is_partition) + { + if (exist_attno == schema_attno) + ereport(NOTICE, + (errmsg("merging column \"%s\" with inherited definition", + attributeName))); + else + ereport(NOTICE, + (errmsg("moving and merging column \"%s\" with inherited definition", attributeName), + errdetail("User-specified column moved to the position of the inherited column."))); + } def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1); typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod); typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod); @@ -1913,8 +1950,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence, storage_name(def->storage), storage_name(newdef->storage)))); - /* Mark the column as locally defined */ - def->is_local = true; + /* Mark the column as locally defined, unless partition */ + if (!is_partition) + def->is_local = true; /* Merge of NOT NULL constraints = OR 'em together */ def->is_not_null |= newdef->is_not_null; /* If new def has a default, override previous default */ @@ -2093,16 +2131,20 @@ StoreCatalogInheritance1(Oid relationId, Oid parentOid, heap_freetuple(tuple); /* - * Store a dependency too + * Store a dependency too, iff a regular inheritance relationship, which + * it's not, if parent is a partitioned table. */ - parentobject.classId = RelationRelationId; - parentobject.objectId = parentOid; - parentobject.objectSubId = 0; - childobject.classId = RelationRelationId; - childobject.objectId = relationId; - childobject.objectSubId = 0; + if (!relid_is_partitioned(parentOid)) + { + parentobject.classId = RelationRelationId; + parentobject.objectId = parentOid; + parentobject.objectSubId = 0; + childobject.classId = RelationRelationId; + childobject.objectId = relationId; + childobject.objectSubId = 0; - recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL); + recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL); + } /* * Post creation hook of this inheritance. Since object_access_hook @@ -3108,6 +3150,11 @@ AlterTableGetLockLevel(List *cmds) cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def); break; + case AT_AttachPartition: + case AT_DetachPartition: + cmd_lockmode = AccessExclusiveLock; + break; + default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); @@ -3425,6 +3472,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* No command-specific prep needed */ pass = AT_PASS_MISC; break; + case AT_AttachPartition: + case AT_DetachPartition: + ATSimplePermissions(rel, ATT_TABLE); + /* No command-specific prep needed */ + pass = AT_PASS_MISC; + break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); @@ -3744,6 +3797,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, case AT_GenericOptions: ATExecGenericOptions(rel, (List *) cmd->def); break; + case AT_AttachPartition: + ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def); + break; + case AT_DetachPartition: + ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name); + break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); @@ -10188,12 +10247,7 @@ ATPrepAddInherit(Relation child_rel) static ObjectAddress ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) { - Relation parent_rel, - catalogRelation; - SysScanDesc scan; - ScanKeyData key; - HeapTuple inheritsTuple; - int32 inhseqno; + Relation parent_rel; List *children; ObjectAddress address; @@ -10238,38 +10292,6 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) errmsg("cannot inherit from partitioned table \"%s\"", parent->relname))); /* - * Check for duplicates in the list of parents, and determine the highest - * inhseqno already present; we'll use the next one for the new parent. - * (Note: get RowExclusiveLock because we will write pg_inherits below.) - * - * Note: we do not reject the case where the child already inherits from - * the parent indirectly; CREATE TABLE doesn't reject comparable cases. - */ - catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock); - ScanKeyInit(&key, - Anum_pg_inherits_inhrelid, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(RelationGetRelid(child_rel))); - scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, - true, NULL, 1, &key); - - /* inhseqno sequences start at 1 */ - inhseqno = 0; - while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan))) - { - Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple); - - if (inh->inhparent == RelationGetRelid(parent_rel)) - ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_TABLE), - errmsg("relation \"%s\" would be inherited from more than once", - RelationGetRelationName(parent_rel)))); - if (inh->inhseqno > inhseqno) - inhseqno = inh->inhseqno; - } - systable_endscan(scan); - - /* * Prevent circularity by seeing if proposed parent inherits from child. * (In particular, this disallows making a rel inherit from itself.) * @@ -10302,6 +10324,69 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) RelationGetRelationName(child_rel), RelationGetRelationName(parent_rel)))); + /* OK to create inheritance */ + CreateInheritance(child_rel, parent_rel); + + ObjectAddressSet(address, RelationRelationId, + RelationGetRelid(parent_rel)); + + /* keep our lock on the parent relation until commit */ + heap_close(parent_rel, NoLock); + + return address; +} + +/* + * CreateInheritance + * Catalog manipulation portion of creating inheritance between a child + * table and a parent table. + * + * Common to ATExecAddInherit() and ATExecAttachPartition(). + */ +static void +CreateInheritance(Relation child_rel, Relation parent_rel) +{ + Relation catalogRelation; + SysScanDesc scan; + ScanKeyData key; + HeapTuple inheritsTuple; + int32 inhseqno; + + /* Note: get RowExclusiveLock because we will write pg_inherits below. */ + catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock); + + /* + * Check for duplicates in the list of parents, and determine the highest + * inhseqno already present; we'll use the next one for the new parent. + * Also, if proposed child is a partition, it cannot already be inheriting. + * + * Note: we do not reject the case where the child already inherits from + * the parent indirectly; CREATE TABLE doesn't reject comparable cases. + */ + ScanKeyInit(&key, + Anum_pg_inherits_inhrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(child_rel))); + scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, + true, NULL, 1, &key); + + /* inhseqno sequences start at 1 */ + inhseqno = 0; + while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan))) + { + Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple); + + if (inh->inhparent == RelationGetRelid(parent_rel)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_TABLE), + errmsg("relation \"%s\" would be inherited from more than once", + RelationGetRelationName(parent_rel)))); + + if (inh->inhseqno > inhseqno) + inhseqno = inh->inhseqno; + } + systable_endscan(scan); + /* Match up the columns and bump attinhcount as needed */ MergeAttributesIntoExisting(child_rel, parent_rel); @@ -10316,16 +10401,8 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) inhseqno + 1, catalogRelation); - ObjectAddressSet(address, RelationRelationId, - RelationGetRelid(parent_rel)); - /* Now we're done with pg_inherits */ heap_close(catalogRelation, RowExclusiveLock); - - /* keep our lock on the parent relation until commit */ - heap_close(parent_rel, NoLock); - - return address; } /* @@ -10376,7 +10453,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc) * Check columns in child table match up with columns in parent, and increment * their attinhcount. * - * Called by ATExecAddInherit + * Called by CreateInheritance * * Currently all parent columns must be found in child. Missing columns are an * error. One day we might consider creating new columns like CREATE TABLE @@ -10394,11 +10471,13 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel) int parent_natts; TupleDesc tupleDesc; HeapTuple tuple; + bool is_attach_partition; attrrel = heap_open(AttributeRelationId, RowExclusiveLock); tupleDesc = RelationGetDescr(parent_rel); parent_natts = tupleDesc->natts; + is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel)); for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++) { @@ -10421,14 +10500,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel) attribute->atttypmod != childatt->atttypmod) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("child table \"%s\" has different type for column \"%s\"", + errmsg("%s table \"%s\" has different type for column \"%s\"", + is_attach_partition ? "source" : "child", RelationGetRelationName(child_rel), attributeName))); if (attribute->attcollation != childatt->attcollation) ereport(ERROR, (errcode(ERRCODE_COLLATION_MISMATCH), - errmsg("child table \"%s\" has different collation for column \"%s\"", + errmsg("%s table \"%s\" has different collation for column \"%s\"", + is_attach_partition ? "source" : "child", RelationGetRelationName(child_rel), attributeName))); @@ -10439,8 +10520,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel) if (attribute->attnotnull && !childatt->attnotnull) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("column \"%s\" in child table must be marked NOT NULL", - attributeName))); + errmsg("column \"%s\" in %s table must be marked NOT NULL", + attributeName, + is_attach_partition ? "source" : "child"))); /* * OK, bump the child column's inheritance count. (If we fail @@ -10455,7 +10537,8 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel) { ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("child table is missing column \"%s\"", + errmsg("%s table is missing column \"%s\"", + is_attach_partition ? "source" : "child", attributeName))); } } @@ -10469,7 +10552,7 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel) * * Constraints that are marked ONLY in the parent are ignored. * - * Called by ATExecAddInherit + * Called by CreateInheritance * * Currently all constraints in parent must be present in the child. One day we * may consider adding new constraints like CREATE TABLE does. @@ -10488,9 +10571,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel) SysScanDesc parent_scan; ScanKeyData parent_key; HeapTuple parent_tuple; + bool is_attach_partition; catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock); tuple_desc = RelationGetDescr(catalog_relation); + is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel)); /* Outer loop scans through the parent's constraint definitions */ ScanKeyInit(&parent_key, @@ -10538,7 +10623,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel) if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("child table \"%s\" has different definition for check constraint \"%s\"", + errmsg("%s table \"%s\" has different definition for check constraint \"%s\"", + is_attach_partition ? "source" : "child", RelationGetRelationName(child_rel), NameStr(parent_con->conname)))); @@ -10546,8 +10632,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel) if (child_con->connoinherit) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"", + errmsg("constraint \"%s\" conflicts with non-inherited constraint on %s table \"%s\"", NameStr(child_con->conname), + is_attach_partition ? "source" : "child", RelationGetRelationName(child_rel)))); /* @@ -10570,7 +10657,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel) if (!found) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("child table is missing constraint \"%s\"", + errmsg("%s table is missing constraint \"%s\"", + is_attach_partition ? "source" : "child", NameStr(parent_con->conname)))); } @@ -10581,6 +10669,41 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel) /* * ALTER TABLE NO INHERIT * + * Return value is the address of the relation that is no longer parent. + */ +static ObjectAddress +ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) +{ + ObjectAddress address; + Relation parent_rel; + + /* + * AccessShareLock on the parent is probably enough, seeing that DROP + * TABLE doesn't lock parent tables at all. We need some lock since we'll + * be inspecting the parent's schema. + */ + parent_rel = heap_openrv(parent, AccessShareLock); + + /* + * We don't bother to check ownership of the parent table --- ownership of + * the child is presumed enough rights. + */ + + /* Off to RemoveInheritance() where most of the work happens */ + RemoveInheritance(rel, parent_rel); + + /* keep our lock on the parent relation until commit */ + heap_close(parent_rel, NoLock); + + ObjectAddressSet(address, RelationRelationId, + RelationGetRelid(parent_rel)); + + return address; +} + +/* + * RemoveInheritance + * * Drop a parent from the child's parents. This just adjusts the attinhcount * and attislocal of the columns and removes the pg_inherit and pg_depend * entries. @@ -10594,13 +10717,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel) * coninhcount and conislocal for inherited constraints are adjusted in * exactly the same way. * - * Return value is the address of the relation that is no longer parent. + * Common to ATExecDropInherit() and ATExecDetachPartition(). */ -static ObjectAddress -ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) +static void +RemoveInheritance(Relation child_rel, Relation parent_rel) { - Relation parent_rel; - Oid parent_oid; Relation catalogRelation; SysScanDesc scan; ScanKeyData key[3]; @@ -10609,19 +10730,9 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) constraintTuple; List *connames; bool found = false; - ObjectAddress address; + bool is_detach_partition; - /* - * AccessShareLock on the parent is probably enough, seeing that DROP - * TABLE doesn't lock parent tables at all. We need some lock since we'll - * be inspecting the parent's schema. - */ - parent_rel = heap_openrv(parent, AccessShareLock); - - /* - * We don't bother to check ownership of the parent table --- ownership of - * the child is presumed enough rights. - */ + is_detach_partition = relid_is_partitioned(RelationGetRelid(parent_rel)); /* * Find and destroy the pg_inherits entry linking the two, or error out if @@ -10631,7 +10742,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) ScanKeyInit(&key[0], Anum_pg_inherits_inhrelid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(RelationGetRelid(rel))); + ObjectIdGetDatum(RelationGetRelid(child_rel))); scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true, NULL, 1, key); @@ -10652,11 +10763,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) heap_close(catalogRelation, RowExclusiveLock); if (!found) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("relation \"%s\" is not a parent of relation \"%s\"", - RelationGetRelationName(parent_rel), - RelationGetRelationName(rel)))); + { + if (!is_detach_partition) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("relation \"%s\" is not a parent of relation \"%s\"", + RelationGetRelationName(parent_rel), + RelationGetRelationName(child_rel)))); + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("relation \"%s\" is not a partition of relation \"%s\"", + RelationGetRelationName(child_rel), + RelationGetRelationName(parent_rel)))); + } /* * Search through child columns looking for ones matching parent rel @@ -10665,7 +10785,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) ScanKeyInit(&key[0], Anum_pg_attribute_attrelid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(RelationGetRelid(rel))); + ObjectIdGetDatum(RelationGetRelid(child_rel))); scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId, true, NULL, 1, key); while (HeapTupleIsValid(attributeTuple = systable_getnext(scan))) @@ -10727,7 +10847,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) ScanKeyInit(&key[0], Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(RelationGetRelid(rel))); + ObjectIdGetDatum(RelationGetRelid(child_rel))); scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId, true, NULL, 1, key); @@ -10758,7 +10878,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) if (copy_con->coninhcount <= 0) /* shouldn't happen */ elog(ERROR, "relation %u has non-inherited constraint \"%s\"", - RelationGetRelid(rel), NameStr(copy_con->conname)); + RelationGetRelid(child_rel), NameStr(copy_con->conname)); copy_con->coninhcount--; if (copy_con->coninhcount == 0) @@ -10770,30 +10890,22 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) } } - parent_oid = RelationGetRelid(parent_rel); - systable_endscan(scan); heap_close(catalogRelation, RowExclusiveLock); - drop_parent_dependency(RelationGetRelid(rel), - RelationRelationId, - RelationGetRelid(parent_rel)); - + /* Remember that dependency exists iff regular inheritance. */ + if (!is_detach_partition) + drop_parent_dependency(RelationGetRelid(child_rel), + RelationRelationId, + RelationGetRelid(parent_rel)); /* * Post alter hook of this inherits. Since object_access_hook doesn't take * multiple object identifiers, we relay oid of parent relation using * auxiliary_id argument. */ InvokeObjectPostAlterHookArg(InheritsRelationId, - RelationGetRelid(rel), 0, + RelationGetRelid(child_rel), 0, RelationGetRelid(parent_rel), false); - - /* keep our lock on the parent relation until commit */ - heap_close(parent_rel, NoLock); - - ObjectAddressSet(address, RelationRelationId, parent_oid); - - return address; } /* @@ -12426,3 +12538,137 @@ ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs, partopclass[attn++] = opclassOid; } } + + +/* + * ALTER TABLE ATTACH PARTITION FOR VALUES + * + * Return the address of the newly attached partition. + */ +static ObjectAddress +ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd) +{ + Relation attachRel, + inheritsRelation; + SysScanDesc scan; + ScanKeyData key; + HeapTuple tuple; + AttrNumber attno; + int natts; + TupleDesc tupleDesc; + ObjectAddress address; + + attachRel = heap_openrv(cmd->name, AccessShareLock); + + /* + * Must be owner of both parent and source table -- parent was checked by + * ATSimplePermissions call in ATPrepCmd + */ + ATSimplePermissions(attachRel, ATT_TABLE); + + if (attachRel->rd_rel->reloftype) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot attach a typed table as partition"))); + + if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_REL) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot attach a partitioned table as partition"))); + + /* attachRel should not already be a inheritance child of some relation */ + inheritsRelation = heap_open(InheritsRelationId, AccessShareLock); + ScanKeyInit(&key, + Anum_pg_inherits_inhrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(attachRel))); + scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId, + true, NULL, 1, &key); + if (HeapTupleIsValid(systable_getnext(scan))) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot attach table that inherits as partition"))); + systable_endscan(scan); + heap_close(inheritsRelation, AccessShareLock); + + /* If attachRel is temp, it must belong to this session */ + if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && + !attachRel->rd_islocaltemp) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot attach a temporary relation of another session as partition "))); + + /* If parent has OIDs, attachRel must have OIDs */ + if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot attach table \"%s\" without OIDs as partition of" + " table \"%s\" with OIDs", RelationGetRelationName(attachRel), + RelationGetRelationName(rel)))); + + /* OTOH, if parent doesn't have them, do not allow in attachRel either */ + if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot attach table \"%s\" with OIDs as partition of table" + " \"%s\" without OIDs", RelationGetRelationName(attachRel), + RelationGetRelationName(rel)))); + + /* Check if there are any columns in attachRel that aren't in the parent */ + tupleDesc = RelationGetDescr(attachRel); + natts = tupleDesc->natts; + for (attno = 1; attno <= natts; attno++) + { + Form_pg_attribute attribute = tupleDesc->attrs[attno - 1]; + char *attributeName = NameStr(attribute->attname); + + /* Ignore dropped */ + if (attribute->attisdropped) + continue; + + /* Find same column in parent (matching on column name). */ + tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"", + RelationGetRelationName(attachRel), attributeName, + RelationGetRelationName(rel)), + errdetail("Table being attached should contain only the columns" + " present in parent."))); + } + + /* OK to create inheritance. Rest of the checks performed there */ + CreateInheritance(attachRel, rel); + + ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel)); + + /* keep our lock until commit */ + heap_close(attachRel, NoLock); + + return address; +} + +/* + * ALTER TABLE DETACH PARTITION + * + * Return the address of the relation that is no longer a partition of rel. + */ +static ObjectAddress +ATExecDetachPartition(Relation rel, RangeVar *name) +{ + Relation partRel; + ObjectAddress address; + + partRel = heap_openrv(name, AccessShareLock); + + /* All inheritance related checks are performed within the function */ + RemoveInheritance(partRel, rel); + + ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel)); + + /* keep our lock until commit */ + heap_close(partRel, NoLock); + + return address; +} diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c index e9d9ba2..c9c72d9 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -95,6 +95,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace, def->is_local = true; def->is_not_null = false; def->is_from_type = false; + def->is_for_partition = false; def->storage = 0; def->raw_default = NULL; def->cooked_default = NULL; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 968a3f4..111fcf5 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2606,6 +2606,7 @@ _copyColumnDef(const ColumnDef *from) COPY_SCALAR_FIELD(is_local); COPY_SCALAR_FIELD(is_not_null); COPY_SCALAR_FIELD(is_from_type); + COPY_SCALAR_FIELD(is_for_partition); COPY_SCALAR_FIELD(storage); COPY_NODE_FIELD(raw_default); COPY_NODE_FIELD(cooked_default); @@ -3001,6 +3002,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode) COPY_NODE_FIELD(relation); COPY_NODE_FIELD(tableElts); COPY_NODE_FIELD(inhRelations); + COPY_NODE_FIELD(partbound); COPY_NODE_FIELD(partby); COPY_NODE_FIELD(ofTypename); COPY_NODE_FIELD(constraints); @@ -4183,6 +4185,43 @@ _copyPartitionElem(const PartitionElem *from) return newnode; } +static PartitionListSpec * +_copyPartitionListSpec(const PartitionListSpec *from) +{ + PartitionListSpec *newnode = makeNode(PartitionListSpec); + + COPY_NODE_FIELD(values); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +static PartitionRangeSpec * +_copyPartitionRangeSpec(const PartitionRangeSpec *from) +{ + PartitionRangeSpec *newnode = makeNode(PartitionRangeSpec); + + COPY_SCALAR_FIELD(lowerinc); + COPY_NODE_FIELD(lower); + COPY_SCALAR_FIELD(upperinc); + COPY_NODE_FIELD(upper); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +static PartitionCmd * +_copyPartitionCmd(const PartitionCmd *from) +{ + PartitionCmd *newnode = makeNode(PartitionCmd); + + COPY_NODE_FIELD(name); + COPY_NODE_FIELD(bound); + COPY_SCALAR_FIELD(skip_validate); + + return newnode; +} + /* **************************************************************** * pg_list.h copy functions * **************************************************************** @@ -5082,6 +5121,15 @@ copyObject(const void *from) case T_PartitionElem: retval = _copyPartitionElem(from); break; + case T_PartitionListSpec: + retval = _copyPartitionListSpec(from); + break; + case T_PartitionRangeSpec: + retval = _copyPartitionRangeSpec(from); + break; + case T_PartitionCmd: + retval = _copyPartitionCmd(from); + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from)); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 56325fe..1a25acf 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1156,6 +1156,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b) COMPARE_NODE_FIELD(relation); COMPARE_NODE_FIELD(tableElts); COMPARE_NODE_FIELD(inhRelations); + COMPARE_NODE_FIELD(partbound); COMPARE_NODE_FIELD(partby); COMPARE_NODE_FIELD(ofTypename); COMPARE_NODE_FIELD(constraints); @@ -2363,6 +2364,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b) COMPARE_SCALAR_FIELD(is_local); COMPARE_SCALAR_FIELD(is_not_null); COMPARE_SCALAR_FIELD(is_from_type); + COMPARE_SCALAR_FIELD(is_for_partition); COMPARE_SCALAR_FIELD(storage); COMPARE_NODE_FIELD(raw_default); COMPARE_NODE_FIELD(cooked_default); @@ -2641,6 +2643,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b) return true; } +static bool +_equalPartitionListSpec(const PartitionListSpec *a, const PartitionListSpec *b) +{ + COMPARE_NODE_FIELD(values); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool +_equalPartitionRangeSpec(const PartitionRangeSpec *a, const PartitionRangeSpec *b) +{ + COMPARE_SCALAR_FIELD(lowerinc); + COMPARE_NODE_FIELD(lower); + COMPARE_SCALAR_FIELD(upperinc); + COMPARE_NODE_FIELD(upper); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool +_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b) +{ + COMPARE_NODE_FIELD(name); + COMPARE_NODE_FIELD(bound); + COMPARE_SCALAR_FIELD(skip_validate); + + return true; +} + /* * Stuff from pg_list.h */ @@ -3397,6 +3430,15 @@ equal(const void *a, const void *b) case T_PartitionElem: retval = _equalPartitionElem(a, b); break; + case T_PartitionListSpec: + retval = _equalPartitionListSpec(a, b); + break; + case T_PartitionRangeSpec: + retval = _equalPartitionRangeSpec(a, b); + break; + case T_PartitionCmd: + retval = _equalPartitionCmd(a, b); + break; default: elog(ERROR, "unrecognized node type: %d", diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 5623471..7537473 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2369,6 +2369,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node) WRITE_NODE_FIELD(relation); WRITE_NODE_FIELD(tableElts); WRITE_NODE_FIELD(inhRelations); + WRITE_NODE_FIELD(partbound); WRITE_NODE_FIELD(partby); WRITE_NODE_FIELD(ofTypename); WRITE_NODE_FIELD(constraints); @@ -2549,6 +2550,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node) WRITE_BOOL_FIELD(is_local); WRITE_BOOL_FIELD(is_not_null); WRITE_BOOL_FIELD(is_from_type); + WRITE_BOOL_FIELD(is_for_partition); WRITE_CHAR_FIELD(storage); WRITE_NODE_FIELD(raw_default); WRITE_NODE_FIELD(cooked_default); @@ -3239,6 +3241,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node) WRITE_LOCATION_FIELD(location); } +static void +_outPartitionListSpec(StringInfo str, const PartitionListSpec *node) +{ + WRITE_NODE_TYPE("PARTITIONLISTVALUES"); + + WRITE_NODE_FIELD(values); +} + +static void +_outPartitionRangeSpec(StringInfo str, const PartitionRangeSpec *node) +{ + WRITE_NODE_TYPE("PARTITIONRANGE"); + + WRITE_BOOL_FIELD(lowerinc); + WRITE_NODE_FIELD(lower); + WRITE_BOOL_FIELD(upperinc); + WRITE_NODE_FIELD(upper); +} + /* * outNode - * converts a Node into ascii string and append it to 'str' @@ -3823,6 +3844,12 @@ outNode(StringInfo str, const void *obj) case T_PartitionElem: _outPartitionElem(str, obj); break; + case T_PartitionListSpec: + _outPartitionListSpec(str, obj); + break; + case T_PartitionRangeSpec: + _outPartitionRangeSpec(str, obj); + break; default: diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 0607439..79fbb32 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -229,6 +229,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); VariableSetStmt *vsetstmt; PartitionElem *partelem; PartitionBy *partby; + PartitionRangeSpec *partrange; } %type stmt schema_stmt @@ -544,6 +545,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type PartitionBy OptPartitionBy %type part_elem %type part_params +%type OptPartitionElementList PartitionElementList +%type PartitionElement +%type ForValues +%type partvalue +%type partvalue_list +%type lb_inc ub_inc +%type RangeBound +%type opt_validate_spec /* * Non-keyword token types. These are hard-wired into the "flex" lexer. @@ -569,7 +578,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); /* ordinary key words in alphabetical order */ %token ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC - ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION + ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT BOOLEAN_P BOTH BY @@ -585,7 +594,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC - DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP + DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P + DOUBLE_P DROP EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN @@ -599,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); HANDLER HAVING HEADER_P HOLD HOUR_P IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P - INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P + INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION @@ -2373,6 +2383,38 @@ alter_table_cmd: n->def = (Node *)$1; $$ = (Node *) n; } + /* ALTER TABLE ATTACH PARTITION FOR VALUES */ + | ATTACH PARTITION qualified_name ForValues opt_validate_spec + { + AlterTableCmd *n = makeNode(AlterTableCmd); + PartitionCmd *cmd = makeNode(PartitionCmd); + + n->subtype = AT_AttachPartition; + cmd->name = $3; + cmd->bound = (Node *) $4; + cmd->skip_validate = $5; + n->def = (Node *) cmd; + + $$ = (Node *) n; + } + /* ALTER TABLE DETACH PARTITION */ + | DETACH PARTITION qualified_name + { + AlterTableCmd *n = makeNode(AlterTableCmd); + PartitionCmd *cmd = makeNode(PartitionCmd); + + n->subtype = AT_DetachPartition; + cmd->name = $3; + n->def = (Node *) cmd; + + $$ = (Node *) n; + } + ; + +opt_validate_spec: + NO VALIDATE { $$ = true; } + | VALIDATE { $$ = false; } + | /* EMPTY */ { $$ = false; } ; alter_column_default: @@ -2468,6 +2510,60 @@ reloption_elem: } ; +ForValues: + /* a LIST partition */ + FOR VALUES IN_P '(' partvalue_list ')' + { + PartitionListSpec *n = makeNode(PartitionListSpec); + + n->values = $5; + n->location = @1; + + $$ = (Node *) n; + } + + /* a RANGE partition */ + | FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc + { + PartitionRangeSpec *n = makeNode(PartitionRangeSpec); + + n->lowerinc = $5; + n->lower = $4; + n->upperinc = $8; + n->upper = $7; + n->location = @1; + + $$ = (Node *) n; + } + ; + +RangeBound: + UNBOUNDED { $$ = NIL; } + | '(' partvalue_list ')' { $$ = $2; } + ; + +lb_inc: + EXCLUSIVE { $$ = false;} + | INCLUSIVE { $$ = true; } + | /* EMPTY */ { $$ = true; } + ; + +ub_inc: + INCLUSIVE { $$ = true; } + | EXCLUSIVE { $$ = false; } + | /* EMPTY */ { $$ = false; } + ; + +partvalue: + Sconst { $$ = makeStringConst($1, @1); } + | NumericOnly { $$ = makeAConst($1, @1); } + | NULL_P { $$ = makeNullAConst(@1); } + ; + +partvalue_list: + partvalue { $$ = list_make1($1); } + | partvalue_list ',' partvalue { $$ = lappend($1, $3); } + ; /***************************************************************************** * @@ -2885,6 +2981,44 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')' n->if_not_exists = true; $$ = (Node *)n; } + | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name + OptPartitionElementList ForValues OptPartitionBy OptWith + OnCommitOption OptTableSpace + { + CreateStmt *n = makeNode(CreateStmt); + $4->relpersistence = $2; + n->relation = $4; + n->tableElts = $8; + n->inhRelations = list_make1($7); + n->partbound = (Node *) $9; + n->partby = $10; + n->ofTypename = NULL; + n->constraints = NIL; + n->options = $11; + n->oncommit = $12; + n->tablespacename = $13; + n->if_not_exists = false; + $$ = (Node *)n; + } + | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF + qualified_name OptPartitionElementList ForValues OptPartitionBy + OptWith OnCommitOption OptTableSpace + { + CreateStmt *n = makeNode(CreateStmt); + $7->relpersistence = $2; + n->relation = $7; + n->tableElts = $11; + n->inhRelations = list_make1($10); + n->partbound = (Node *) $12; + n->partby = $13; + n->ofTypename = NULL; + n->constraints = NIL; + n->options = $14; + n->oncommit = $15; + n->tablespacename = $16; + n->if_not_exists = true; + $$ = (Node *)n; + } ; /* @@ -2930,6 +3064,11 @@ OptTypedTableElementList: | /*EMPTY*/ { $$ = NIL; } ; +OptPartitionElementList: + '(' PartitionElementList ')' { $$ = $2; } + | /*EMPTY*/ { $$ = NIL; } + ; + TableElementList: TableElement { @@ -2952,6 +3091,17 @@ TypedTableElementList: } ; +PartitionElementList: + PartitionElement + { + $$ = list_make1($1); + } + | PartitionElementList ',' PartitionElement + { + $$ = lappend($1, $3); + } + ; + TableElement: columnDef { $$ = $1; } | TableLikeClause { $$ = $1; } @@ -2963,6 +3113,11 @@ TypedTableElement: | TableConstraint { $$ = $1; } ; +PartitionElement: + columnOptions { $$ = $1; } + | TableConstraint { $$ = $1; } + ; + columnDef: ColId Typename create_generic_options ColQualList { ColumnDef *n = makeNode(ColumnDef); @@ -2972,6 +3127,7 @@ columnDef: ColId Typename create_generic_options ColQualList n->is_local = true; n->is_not_null = false; n->is_from_type = false; + n->is_for_partition = false; n->storage = 0; n->raw_default = NULL; n->cooked_default = NULL; @@ -2993,6 +3149,7 @@ columnOptions: ColId WITH OPTIONS ColQualList n->is_local = true; n->is_not_null = false; n->is_from_type = false; + n->is_for_partition = false; n->storage = 0; n->raw_default = NULL; n->cooked_default = NULL; @@ -11122,6 +11279,7 @@ TableFuncElement: ColId Typename opt_collate_clause n->is_local = true; n->is_not_null = false; n->is_from_type = false; + n->is_for_partition = false; n->storage = 0; n->raw_default = NULL; n->cooked_default = NULL; @@ -13805,6 +13963,7 @@ unreserved_keyword: | ASSERTION | ASSIGNMENT | AT + | ATTACH | ATTRIBUTE | BACKWARD | BEFORE @@ -13851,6 +14010,7 @@ unreserved_keyword: | DELIMITER | DELIMITERS | DEPENDS + | DETACH | DICTIONARY | DISABLE_P | DISCARD @@ -13893,6 +14053,7 @@ unreserved_keyword: | IMPLICIT_P | IMPORT_P | INCLUDING + | INCLUSIVE | INCREMENT | INDEX | INDEXES diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 77ffd84..d71fdfe 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -493,7 +493,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) err = _("grouping operations are not allowed in partition key expression"); break; - + case EXPR_KIND_PARTITION_FOR_VALUES: + errkind = true; + break; /* * There is intentionally no default: case here, so that the @@ -854,6 +856,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, case EXPR_KIND_PARTITION_KEY: err = _("window functions are not allowed in partition key expression"); break; + case EXPR_KIND_PARTITION_FOR_VALUES: + errkind = true; + break; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 147c6da..f14d776 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -352,6 +352,26 @@ transformExprRecurse(ParseState *pstate, Node *expr) result = (Node *) expr; break; } + case T_PartitionListSpec: + { + PartitionListSpec *list = (PartitionListSpec *) expr; + + list->values = transformExpressionList(pstate, list->values, + pstate->p_expr_kind); + result = expr; + break; + } + case T_PartitionRangeSpec: + { + PartitionRangeSpec *range = (PartitionRangeSpec *) expr; + + range->lower = transformExpressionList(pstate, range->lower, + pstate->p_expr_kind); + range->upper = transformExpressionList(pstate, range->upper, + pstate->p_expr_kind); + result = expr; + break; + } default: /* should not reach here */ @@ -1698,6 +1718,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink) case EXPR_KIND_OFFSET: case EXPR_KIND_RETURNING: case EXPR_KIND_VALUES: + case EXPR_KIND_PARTITION_FOR_VALUES: /* okay */ break; case EXPR_KIND_CHECK_CONSTRAINT: @@ -3254,6 +3275,8 @@ ParseExprKindName(ParseExprKind exprKind) return "WHEN"; case EXPR_KIND_PARTITION_KEY: return "partition key expression"; + case EXPR_KIND_PARTITION_FOR_VALUES: + return "FOR VALUES"; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 465597d..e69cbfc 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -48,8 +48,10 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/planner.h" #include "parser/analyze.h" #include "parser/parse_clause.h" +#include "parser/parse_coerce.h" #include "parser/parse_collate.h" #include "parser/parse_expr.h" #include "parser/parse_relation.h" @@ -89,6 +91,7 @@ typedef struct * the table */ IndexStmt *pkey; /* PRIMARY KEY index, if any */ bool ispartitioned; /* true if table is partitioned */ + Node *partbound; /* transformed FOR VALUES */ } CreateStmtContext; /* State shared by transformCreateSchemaStmt and its subroutines */ @@ -131,6 +134,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList); static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column); static void setSchemaName(char *context_schema, char **stmt_schema_name); +static void transformPartitionOf(CreateStmtContext *cxt, Node *bound); +static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd); +static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent, + Node *bound); /* @@ -232,6 +239,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) cxt.alist = NIL; cxt.pkey = NULL; cxt.ispartitioned = stmt->partby != NULL; + cxt.partbound = NULL; /* * Notice that we allow OIDs here only for plain tables, even though @@ -250,6 +258,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) if (stmt->ofTypename) transformOfType(&cxt, stmt->ofTypename); + if (stmt->partbound) + transformPartitionOf(&cxt, stmt->partbound); + if (stmt->partby) { int partnatts = list_length(stmt->partby->partParams); @@ -355,6 +366,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) */ stmt->tableElts = cxt.columns; stmt->constraints = cxt.ckconstraints; + stmt->partbound = cxt.partbound; result = lappend(cxt.blist, stmt); result = list_concat(result, cxt.alist); @@ -894,6 +906,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla def->is_local = true; def->is_not_null = attribute->attnotnull; def->is_from_type = false; + def->is_for_partition = false; def->storage = 0; def->raw_default = NULL; def->cooked_default = NULL; @@ -1113,6 +1126,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename) n->is_local = true; n->is_not_null = false; n->is_from_type = true; + n->is_for_partition = false; n->storage = 0; n->raw_default = NULL; n->cooked_default = NULL; @@ -2580,6 +2594,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, cxt.alist = NIL; cxt.pkey = NULL; cxt.ispartitioned = relid_is_partitioned(relid); + cxt.partbound = NULL; /* * The only subtypes that currently require parse transformation handling @@ -2668,6 +2683,22 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, break; } + case AT_AttachPartition: + { + PartitionCmd *partcmd = (PartitionCmd *) cmd->def; + + transformAttachPartition(&cxt, partcmd); + + /* assign transformed values */ + partcmd->bound = cxt.partbound; + } + + newcmds = lappend(newcmds, cmd); + break; + case AT_DetachPartition: + newcmds = lappend(newcmds, cmd); + break; + default: newcmds = lappend(newcmds, cmd); break; @@ -3032,3 +3063,290 @@ setSchemaName(char *context_schema, char **stmt_schema_name) "different from the one being created (%s)", *stmt_schema_name, context_schema))); } + +/* + * transformPartitionOf + * Analyze PARTITION OF ... FOR VALUES ... + */ +static void +transformPartitionOf(CreateStmtContext *cxt, Node *bound) +{ + TupleDesc tupdesc; + int i; + Relation parentRel; + RangeVar *partof = linitial(cxt->inhRelations); + + parentRel = heap_openrv(partof, AccessShareLock); + + /* Check if the target table is partitioned at all */ + if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("\"%s\" is not partitioned", + RelationGetRelationName(parentRel)))); + Assert(RelationGetPartitionKey(parentRel) != NULL); + + /* If parent rel is temp, it must belong to this session */ + if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && + !parentRel->rd_islocaltemp) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot create as partition of temporary relation of another session"))); + + /* + * Do not allow OIDs in a partition, if not present in the parent. But + * force them in partition, if they are present in the parent. + */ + if (cxt->hasoids && !parentRel->rd_rel->relhasoids) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot create table with OIDs as partition of table without OIDs"))); + + if (parentRel->rd_rel->relhasoids) + cxt->hasoids = true; + + tupdesc = RelationGetDescr(parentRel); + for (i = 0; i < tupdesc->natts; i++) + { + Form_pg_attribute attribute = tupdesc->attrs[i]; + ColumnDef *def; + + if (attribute->attisdropped) + continue; + + def = makeNode(ColumnDef); + def->colname = pstrdup(NameStr(attribute->attname)); + def->typeName = makeTypeNameFromOid(attribute->atttypid, + attribute->atttypmod); + def->inhcount = 1; + def->is_local = false; + def->is_not_null = attribute->attnotnull; + def->is_from_type = false; + def->is_for_partition = true; + def->storage = attribute->attstorage; + def->raw_default = NULL; + def->cooked_default = NULL; + def->collClause = NULL; + def->collOid = attribute->attcollation; + def->constraints = NIL; + def->location = -1; + + cxt->columns = lappend(cxt->columns, def); + } + + /* tranform the values */ + cxt->partbound = transformPartitionBound(cxt, parentRel, bound); + + heap_close(parentRel, AccessShareLock); +} + +/* + * transformAttachPartition + * Analyze ATTACH PARTITION ... FOR VALUES ... + */ +static void +transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd) +{ + Relation parentRel = cxt->rel; + + /* Check if the target table is partitioned at all */ + if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("\"%s\" is not partitioned", cxt->relation->relname))); + Assert(RelationGetPartitionKey(parentRel) != NULL); + + /* tranform the values */ + cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound); +} + +/* + * transformPartitionBound + * + * Transform partition bound per the partition key + */ +static Node * +transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound) +{ + int i; + ListCell *cell; + Oid parentId = RelationGetRelid(parent); + PartitionKey key = RelationGetPartitionKey(parent); + PartitionListSpec *list, *result_list; + PartitionRangeSpec *range, *result_range; + + switch (key->strategy) + { + case PARTITION_STRAT_LIST: + if (!IsA(bound, PartitionListSpec)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("invalid bound specification for a list partition"), + parser_errposition(cxt->pstate, exprLocation(bound)))); + + list = (PartitionListSpec *) transformExpr(cxt->pstate, bound, + EXPR_KIND_PARTITION_FOR_VALUES); + + result_list = makeNode(PartitionListSpec); + + foreach(cell, list->values) + { + Node *value = (Node *) lfirst(cell); + Node *orig_value = value; + Oid valuetype; + + valuetype = exprType(value); + value = coerce_to_target_type(cxt->pstate, + value, valuetype, + key->tcinfo->typid[0], + key->tcinfo->typmod[0], + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (value == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("specified value cannot be cast to type \"%s\"" + " of key column \"%s\"", + format_type_be(key->tcinfo->typid[0]), + get_relid_attribute_name(parentId, key->partattrs[0])), + parser_errposition(cxt->pstate, exprLocation(orig_value)))); + + /* Simplify the expression */ + value = (Node *) expression_planner((Expr *) value); + + result_list->values = lappend(result_list->values, value); + } + return (Node *) result_list; + + case PARTITION_STRAT_RANGE: + if (!IsA(bound, PartitionRangeSpec)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("invalid bound specification for a range partition"), + parser_errposition(cxt->pstate, exprLocation(bound)))); + + range = (PartitionRangeSpec *) transformExpr(cxt->pstate, bound, + EXPR_KIND_PARTITION_FOR_VALUES); + + if (!range->lower && !range->upper) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("both START and END cannot be UNBOUNDED"), + parser_errposition(cxt->pstate, range->location))); + + result_range = makeNode(PartitionRangeSpec); + result_range->lowerinc = range->lowerinc; + result_range->upperinc = range->upperinc; + + if (range->lower) + { + if (list_length(range->lower) > key->partnatts) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("START has more values specified than number" + " of columns in the partition key"), + parser_errposition(cxt->pstate, + exprLocation(list_nth(range->lower, + list_length(range->lower) - 1))))); + else if (list_length(range->lower) < key->partnatts) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("START has fewer values specified than number" + " of columns in the partition key"), + parser_errposition(cxt->pstate, + exprLocation(list_nth(range->lower, + list_length(range->lower) - 1))))); + + i = 0; + foreach (cell, range->lower) + { + Node *value = (Node *) lfirst(cell); + Node *orig_value = value; + Oid valuetype; + + valuetype = exprType(value); + value = coerce_to_target_type(cxt->pstate, + value, valuetype, + key->tcinfo->typid[i], + key->tcinfo->typmod[i], + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (value == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("specified value cannot be cast to type" + " \"%s\" of key column \"%s\"", + format_type_be(key->tcinfo->typid[i]), + get_relid_attribute_name(parentId, key->partattrs[i])), + parser_errposition(cxt->pstate, exprLocation(orig_value)))); + + /* Simplify the expression */ + value = (Node *) expression_planner((Expr *) value); + + result_range->lower = lappend(result_range->lower, value); + ++i; + } + } + else + result_range->lowerinc = false; + + if (range->upper) + { + if (list_length(range->upper) > key->partnatts) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("END has more values specified than number" + " of columns in the partition key"), + parser_errposition(cxt->pstate, + exprLocation(list_nth(range->upper, + list_length(range->upper) - 1))))); + else if (list_length(range->upper) < key->partnatts) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("END has fewer values specified than number" + " of columns in the partition key"), + parser_errposition(cxt->pstate, + exprLocation(list_nth(range->upper, + list_length(range->upper) - 1))))); + + i = 0; + foreach (cell, range->upper) + { + Node *value = (Node *) lfirst(cell); + Node *orig_value = value; + Oid valuetype; + + valuetype = exprType(value); + value = coerce_to_target_type(cxt->pstate, + value, valuetype, + key->tcinfo->typid[i], + key->tcinfo->typmod[i], + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (value == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("specified value cannot be cast to type" + " \"%s\" of key column \"%s\"", + format_type_be(key->tcinfo->typid[i]), + get_relid_attribute_name(parentId, key->partattrs[i])), + parser_errposition(cxt->pstate, exprLocation(orig_value)))); + + /* Simplify the expression */ + value = (Node *) expression_planner((Expr *) value); + + result_range->upper = lappend(result_range->upper, value); + ++i; + } + } + else + result_range->upperinc = false; + + return (Node *) result_range; + } + + return NULL; +} diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h index 2c876c4..31dbe17 100644 --- a/src/include/catalog/partition.h +++ b/src/include/catalog/partition.h @@ -14,6 +14,7 @@ #define PARTITION_H #include "fmgr.h" +#include "parser/parse_node.h" #include "utils/relcache.h" /* Type and collation information for partition key columns */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 9e582a5..56bbe43 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -405,6 +405,7 @@ typedef enum NodeTag T_AlterPolicyStmt, T_CreateTransformStmt, T_CreateAmStmt, + T_PartitionCmd, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) @@ -454,6 +455,8 @@ typedef enum NodeTag T_RoleSpec, T_PartitionElem, T_PartitionBy, + T_PartitionListSpec, + T_PartitionRangeSpec, /* * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 4f697b8..d9dff96 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -591,6 +591,7 @@ typedef struct ColumnDef bool is_local; /* column has local (non-inherited) def'n */ bool is_not_null; /* NOT NULL constraint specified? */ bool is_from_type; /* column definition came from table type */ + bool is_for_partition; /* column definition is for a partition */ char storage; /* attstorage setting, or 0 for default */ Node *raw_default; /* default value (untransformed parse tree) */ Node *cooked_default; /* default value (transformed expr tree) */ @@ -731,6 +732,40 @@ typedef struct PartitionBy int location; /* token location, or -1 if unknown */ } PartitionBy; +/* + * PartitionRangeSpec - range type syntax for range partition bound + */ +typedef struct PartitionListSpec +{ + NodeTag type; + List *values; + int location; +} PartitionListSpec; + +/* + * PartitionRangeSpec - range type syntax for range partition bound + */ +typedef struct PartitionRangeSpec +{ + NodeTag type; + bool lowerinc; + List *lower; + bool upperinc; + List *upper; + int location; /* token location, or -1 if unknown */ +} PartitionRangeSpec; + +/* + * PartitionCmd - ALTER TABLE partition commands + */ +typedef struct PartitionCmd +{ + NodeTag type; + RangeVar *name; + Node *bound; + bool skip_validate; +} PartitionCmd; + /**************************************************************************** * Nodes for a Query tree ****************************************************************************/ @@ -1558,7 +1593,9 @@ typedef enum AlterTableType AT_DisableRowSecurity, /* DISABLE ROW SECURITY */ AT_ForceRowSecurity, /* FORCE ROW SECURITY */ AT_NoForceRowSecurity, /* NO FORCE ROW SECURITY */ - AT_GenericOptions /* OPTIONS (...) */ + AT_GenericOptions, /* OPTIONS (...) */ + AT_AttachPartition, /* ATTACH PARTITION */ + AT_DetachPartition /* DETACH PARTITION */ } AlterTableType; typedef struct ReplicaIdentityStmt @@ -1783,7 +1820,9 @@ typedef struct CreateStmt RangeVar *relation; /* relation to create */ List *tableElts; /* column definitions (list of ColumnDef) */ List *inhRelations; /* relations to inherit from (list of - * inhRelation) */ + * inhRelation); (ab)used also as partition + * parent */ + Node *partbound; /* FOR VALUES clause */ PartitionBy *partby; /* PARTITION BY clause */ TypeName *ofTypename; /* OF typename */ List *constraints; /* constraints (list of Constraint nodes) */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 40da67a..70c264c 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD) PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD) PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD) PG_KEYWORD("at", AT, UNRESERVED_KEYWORD) +PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD) PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD) PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD) @@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD) PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD) PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD) PG_KEYWORD("desc", DESC, RESERVED_KEYWORD) +PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD) PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD) PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD) PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD) @@ -191,6 +193,7 @@ PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD) PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD) PG_KEYWORD("in", IN_P, RESERVED_KEYWORD) PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD) +PG_KEYWORD("inclusive", INCLUSIVE, UNRESERVED_KEYWORD) PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD) PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD) PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD) diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index a13c6fb..3d45663 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -65,7 +65,8 @@ typedef enum ParseExprKind EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */ EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */ - EXPR_KIND_PARTITION_KEY /* partition key expression */ + EXPR_KIND_PARTITION_KEY, /* partition key expression */ + EXPR_KIND_PARTITION_FOR_VALUES /* partition bound */ } ParseExprKind; -- 1.7.1 --------------050808050504080802030900 Content-Type: text/x-diff; name="0005-Infrastructure-for-partition-metadata-storage-and-ma-v5.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0005-Infrastructure-for-partition-metadata-storage-and-ma-v5"; filename*1=".patch"