From 9dcfedb6ad2693c2ec85578b839190cac35d09d6 Mon Sep 17 00:00:00 2001 From: amit Date: Thu, 14 Jul 2016 14:38:08 +0900 Subject: [PATCH 3/9] Catalog and DDL for partitions. pg_class get snew fields: relispartition and relpartbound (a pg_node_tree). Parent-child relationships of a partitioned table and its partitions are managed with inheritance, so not much here about that. DDL includes both a way to create new partition and "attach" an existing table as partition. An existing partition can be "detached" from a table, which preserves it as a regular (or partitioned) table. Add a field to relcache for storing a "partition descriptor" of a partitioned table which has hopefully all the information about a table's partitions that someone might want to do something with. --- contrib/pg_trgm/trgm_op.c | 2 +- doc/src/sgml/catalogs.sgml | 17 + doc/src/sgml/ref/alter_table.sgml | 111 ++- doc/src/sgml/ref/create_foreign_table.sgml | 28 + doc/src/sgml/ref/create_table.sgml | 105 ++- src/backend/bootstrap/bootparse.y | 1 + src/backend/catalog/heap.c | 35 +- src/backend/catalog/index.c | 3 +- src/backend/catalog/partition.c | 1689 +++++++++++++++++++++++++++- src/backend/catalog/pg_partitioned_table.c | 2 - src/backend/catalog/toasting.c | 1 + src/backend/commands/cluster.c | 1 + src/backend/commands/lockcmds.c | 1 + src/backend/commands/sequence.c | 1 + src/backend/commands/tablecmds.c | 774 +++++++++++-- src/backend/nodes/copyfuncs.c | 48 + src/backend/nodes/equalfuncs.c | 42 + src/backend/nodes/outfuncs.c | 27 + src/backend/nodes/readfuncs.c | 33 + src/backend/parser/gram.y | 209 ++++- src/backend/parser/parse_agg.c | 6 + src/backend/parser/parse_expr.c | 23 + src/backend/parser/parse_utilcmd.c | 331 ++++++- src/backend/utils/cache/relcache.c | 104 ++- src/include/catalog/heap.h | 4 +- src/include/catalog/partition.h | 39 + src/include/catalog/pg_class.h | 22 +- 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 +- src/include/utils/rel.h | 9 + src/test/regress/expected/alter_table.out | 219 ++++ src/test/regress/expected/create_table.out | 188 +++ src/test/regress/sql/alter_table.sql | 192 ++++ src/test/regress/sql/create_table.sql | 138 +++ 36 files changed, 4291 insertions(+), 166 deletions(-) diff --git a/contrib/pg_trgm/trgm_op.c b/contrib/pg_trgm/trgm_op.c index dd0f492..1cfd341 100644 --- a/contrib/pg_trgm/trgm_op.c +++ b/contrib/pg_trgm/trgm_op.c @@ -1023,7 +1023,7 @@ trgm_presence_map(TRGM *query, TRGM *key) result = (bool *) palloc0(lenq * sizeof(bool)); - /* for each query trigram, do a binary search in the key array */ +/* for each query trigram, do a binary search in the key array */ for (i = 0; i < lenq; i++) { int lo = 0; diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 0b38ff7..444e9fe 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1846,6 +1846,13 @@ + relispartition + bool + + True if table is a partition + + + relfrozenxid xid @@ -1891,6 +1898,16 @@ Access-method-specific options, as keyword=value strings + + + relpartbound + pg_node_tree + + + If table is a partition (see relispartition), + internal representation of the partition bound + + diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 6f51cbc..421a134 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -77,6 +77,8 @@ 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 partition_name partition_bound_spec [ VALIDATE | NO VALIDATE ] + DETACH PARTITION partition_name and table_constraint_using_index is: @@ -166,6 +168,11 @@ ALTER TABLE ALL IN TABLESPACE name values or to reject null values. You can only use SET NOT NULL when the column contains no null values. + + + If this table is a partition, one cannot perform DROP NOT NULL + on a column if it is marked not null in the parent table. + @@ -704,6 +711,52 @@ ALTER TABLE ALL IN TABLESPACE name + + ATTACH PARTITION + + + This form attaches an existing table (partitioned or otherwise) as + partition of the target table. Partition bound specification must + correspond with the partition method and the key of the target table. + The table being attached must have all the columns of the target table + with matching types and no more. Also, it must have all the matching + constraints as the target table. That includes both NOT NULL + and CHECK constraints. If some CHECK + constraint of the table being attached is marked NO INHERIT, + the command will fail; such constraints must either be dropped or + recreated without the NO INHERIT mark. + Currently UNIQUE, PRIMARY KEY, and + FOREIGN KEY constraints are not considered, but that + might change in the future. + + + + A full table scan is performed on the table being attached to check that + existing rows in the table are within the specified partition bound. + If it is known in advance that no partition bound violating rows are + present in the table, the above scan can be skipped by specifying the + NO VALIDATE option. + Default behavior is to perform the scan, as if the VALIDATE + option were specified. + + + + + + DETACH PARTITION + + + This form detaches specified partition of the target table. The + detached partition continues to exist as a standalone table with no ties + remaining with the target table. + + + Note that if a partition being detached is itself a partitioned table, + it continues to exist as such. + + + + @@ -722,7 +775,9 @@ ALTER TABLE ALL IN TABLESPACE name To change the schema or tablespace of a table, you must also have CREATE privilege on the new schema or tablespace. To add the table as a new child of a parent table, you must own the - parent table as well. + parent table as well. That applies to both adding the table as a + inheritance child of a parent table and attaching a table as partition to + the table. To alter the owner, you must also be a direct or indirect member of the new owning role, and that role must have CREATE privilege on the table's schema. (These restrictions enforce that altering the owner @@ -938,6 +993,24 @@ ALTER TABLE ALL IN TABLESPACE name + + partition_name + + + The name of the table to attach as a new partition to or detach from this table. + + + + + + partition_bound_spec + + + The partition bound specification for a new partition. + + + + @@ -978,6 +1051,12 @@ ALTER TABLE ALL IN TABLESPACE name + Similarly, when attaching a new partition the source table is scanned to + verify that existing rows fall within the specified bounds, unless + NO VALIDATE option is spcified. + + + The main reason for providing the option to specify multiple changes in a single ALTER TABLE is that multiple table scans or rewrites can thereby be combined into a single pass over the table. @@ -1039,10 +1118,12 @@ ALTER TABLE ALL IN TABLESPACE name A recursive DROP COLUMN operation will remove a descendant table's column only if the descendant does not inherit that column from any other parents and never had an independent - definition of the column. A nonrecursive DROP + definition of the column (which always holds if the descendant table + is a partition). A nonrecursive DROP COLUMN (i.e., ALTER TABLE ONLY ... DROP COLUMN) never removes any descendant columns, but - instead marks them as independently defined rather than inherited. + instead marks them as independently defined rather than inherited, + unless the descendant table is a partition. @@ -1050,7 +1131,8 @@ ALTER TABLE ALL IN TABLESPACE name and TABLESPACE actions never recurse to descendant tables; that is, they always act as though ONLY were specified. Adding a constraint recurses only for CHECK constraints - that are not marked NO INHERIT. + that are not marked NO INHERIT which are unsupported if + the table is a partitioned table. @@ -1229,6 +1311,27 @@ ALTER TABLE distributors DROP CONSTRAINT distributors_pkey, ADD CONSTRAINT distributors_pkey PRIMARY KEY USING INDEX dist_id_temp_idx; + + Attach a partition to range partitioned table: + +ALTER TABLE measurement + ATTACH PARTITION measurement_y2016m07 FOR VALUES START ('2016-07-01') END ('2016-08-01'); + + + + Attach a partition to list partitioned table and ask to skip checking existing rows: + +ALTER TABLE cities + ATTACH PARTITION cities_west FOR VALUES IN ('los angeles', 'san fransisco') NO VALIDATE; + + + + Detach a partition from partitioned table: + +ALTER TABLE cities + DETACH PARTITION measurement_y2015m12; + + diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml index 413b033..007782c 100644 --- a/doc/src/sgml/ref/create_foreign_table.sgml +++ b/doc/src/sgml/ref/create_foreign_table.sgml @@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name SERVER server_name [ OPTIONS ( option 'value' [, ... ] ) ] +CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name + PARTITION OF parent_table [ ( + { column_name WITH OPTIONS [ column_constraint [ ... ] ] + | table_constraint } + [, ... ] +) ] partition_bound_spec + SERVER server_name +[ OPTIONS ( option 'value' [, ... ] ) ] + where column_constraint is: [ CONSTRAINT constraint_name ] @@ -68,6 +77,14 @@ CHECK ( expression ) [ NO INHERIT ] + If PARTITION OF clause is specified then the table is + created as a partition of parent_table with specified + bounds. However, unlike regular tables, one cannot specify + PARTITION BY clause which means foreign tables can + only be created as leaf partitions. + + + To be able to create a foreign table, you must have USAGE privilege on the foreign server, as well as USAGE privilege on all column types used in the table. @@ -314,6 +331,17 @@ CREATE FOREIGN TABLE films ( SERVER film_server; + + Create foreign table measurement_y2016m07, which will be + accessed through the server server_07, that is partition + of the range partitioned table measurement: + + +CREATE FOREIGN TABLE measurement_y2016m07 + PARTITION OF measurement FOR VALUES START ('2016-07-01') END ('2016-08-01') + SERVER server_07; + + diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 331ed56..2e7ded6 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 @@ -1422,7 +1492,38 @@ CREATE TABLE measurement ( CREATE TABLE cities ( name text not null, population int, -) PARTITION BY LIST (name); +) PARTITION BY LIST (lower(name)); + + + + Create partition of a range partitioned table: + +CREATE TABLE measurement_y2016m07 + PARTITION OF measurement ( + unitsales WITH OPTIONS DEFAULT 0 +) FOR VALUES START ('2016-07-01') END ('2016-08-01'); + + + + Create partition of a list partitioned table: + +CREATE TABLE cities_west + PARTITION OF cities ( + CONSTRAINT city_id_nonzero CHECK (city_id != 0) +) FOR VALUES IN ('los angeles', 'san fransisco'); + + + + Create partition of a list partitioned table that itself is further + partitioned and then create its partition: + +CREATE TABLE cities_west + PARTITION OF cities ( + CONSTRAINT city_id_nonzero CHECK (city_id != 0) +) FOR VALUES IN ('los angeles', 'san fransisco') PARTITION BY RANGE (population); + +CREATE TABLE cities_west_10000_to_100000 + PARTITION OF cities_west FOR VALUES START (10000) END (100000); diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y index 41d2fd4..ecf8a75 100644 --- a/src/backend/bootstrap/bootparse.y +++ b/src/backend/bootstrap/bootparse.y @@ -248,6 +248,7 @@ Boot_CreateStmt: 0, ONCOMMIT_NOOP, (Datum) 0, + (Datum) 0, false, true, false, diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index aafd2e6..16e4d22 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -90,7 +90,8 @@ static void AddNewRelationTuple(Relation pg_class_desc, Oid relowner, char relkind, Datum relacl, - Datum reloptions); + Datum reloptions, + Datum relpartbound); static ObjectAddress AddNewRelationType(const char *typeName, Oid typeNamespace, Oid new_rel_oid, @@ -770,7 +771,8 @@ InsertPgClassTuple(Relation pg_class_desc, Relation new_rel_desc, Oid new_rel_oid, Datum relacl, - Datum reloptions) + Datum reloptions, + Datum relpartbound) { Form_pg_class rd_rel = new_rel_desc->rd_rel; Datum values[Natts_pg_class]; @@ -808,6 +810,7 @@ InsertPgClassTuple(Relation pg_class_desc, values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass); values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated); values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident); + values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition); values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid); values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid); if (relacl != (Datum) 0) @@ -818,6 +821,10 @@ InsertPgClassTuple(Relation pg_class_desc, values[Anum_pg_class_reloptions - 1] = reloptions; else nulls[Anum_pg_class_reloptions - 1] = true; + if (relpartbound != (Datum) 0) + values[Anum_pg_class_relpartbound - 1] = relpartbound; + else + nulls[Anum_pg_class_relpartbound - 1] = true; tup = heap_form_tuple(RelationGetDescr(pg_class_desc), values, nulls); @@ -851,7 +858,8 @@ AddNewRelationTuple(Relation pg_class_desc, Oid relowner, char relkind, Datum relacl, - Datum reloptions) + Datum reloptions, + Datum relpartbound) { Form_pg_class new_rel_reltup; @@ -924,11 +932,13 @@ AddNewRelationTuple(Relation pg_class_desc, new_rel_reltup->reltype = new_type_oid; new_rel_reltup->reloftype = reloftype; + new_rel_reltup->relispartition = (relpartbound != (Datum) 0); + new_rel_desc->rd_att->tdtypeid = new_type_oid; /* Now build and insert the tuple */ InsertPgClassTuple(pg_class_desc, new_rel_desc, new_rel_oid, - relacl, reloptions); + relacl, reloptions, relpartbound); } @@ -1033,6 +1043,7 @@ heap_create_with_catalog(const char *relname, int oidinhcount, OnCommitAction oncommit, Datum reloptions, + Datum relpartbound, bool use_user_acl, bool allow_system_table_mods, bool is_internal, @@ -1268,7 +1279,8 @@ heap_create_with_catalog(const char *relname, ownerid, relkind, PointerGetDatum(relacl), - reloptions); + reloptions, + relpartbound); /* * now add tuples to pg_attribute for the attributes in our new relation. @@ -2042,13 +2054,20 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr, else attNos = NULL; - /* Remove NO INHERIT flag if rel is a partitioned table */ + /* Remove NO INHERIT flag if rel is a partitioned table or a partition */ if (is_no_inherit && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"", RelationGetRelationName(rel)))); + if (is_no_inherit && rel->rd_rel->relispartition) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot add NO INHERIT constraint to table \"%s\"", + RelationGetRelationName(rel)), + errdetail("Table \"%s\" is a partition.", + RelationGetRelationName(rel)))); /* * Create the Check Constraint @@ -2475,7 +2494,9 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr, con->conislocal = true; else con->coninhcount++; - if (is_no_inherit) + + /* Discard the NO INHERIT flag if the relation is a partition */ + if (is_no_inherit && !rel->rd_rel->relispartition) { Assert(is_local); con->connoinherit = true; diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index b0b43cf..bc527a9 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -871,7 +871,8 @@ index_create(Relation heapRelation, InsertPgClassTuple(pg_class, indexRelation, RelationGetRelid(indexRelation), (Datum) 0, - reloptions); + reloptions, + (Datum) 0); /* done with pg_class */ heap_close(pg_class, RowExclusiveLock); diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index 35e020c..530bc97 100644 --- a/src/backend/catalog/partition.c +++ b/src/backend/catalog/partition.c @@ -1,7 +1,7 @@ /*------------------------------------------------------------------------- * * partition.c - * Partitioning related utility functions. + * Partitioning related data structures and functions. * * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -18,19 +18,26 @@ #include "access/heapam.h" #include "access/htup_details.h" #include "access/nbtree.h" +#include "access/sysattr.h" #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/objectaddress.h" #include "catalog/partition.h" #include "catalog/pg_collation.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_inherits_fn.h" #include "catalog/pg_opclass.h" #include "catalog/pg_partitioned_table.h" +#include "catalog/pg_partitioned_table_fn.h" #include "catalog/pg_type.h" #include "executor/executor.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "nodes/parsenodes.h" #include "optimizer/clauses.h" #include "optimizer/planmain.h" +#include "optimizer/var.h" #include "storage/lmgr.h" #include "utils/array.h" #include "utils/builtins.h" @@ -40,6 +47,7 @@ #include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/ruleutils.h" +#include "utils/rel.h" #include "utils/syscache.h" /* Type and collation information for partition key columns */ @@ -69,11 +77,151 @@ typedef struct PartitionKeyData KeyTypeCollInfo *tcinfo; /* type and collation info (all columns) */ } PartitionKeyData; +/* + * Collection of bounds of a partitioned relation (either physical or + * logical relation) + * + * Depending on whether the relation in question is list or range + * partitioned, one of the fields is set. + */ +typedef struct BoundCollectionData +{ + struct ListInfo *listinfo; + struct RangeInfo *rangeinfo; +} BoundCollectionData; + +/* + * List bound collection - collection of values of all partitions of a list + * partitioned relation + * + * The values are put in a single array to be able to use binary search + * over them. Note that null values are never put into the array. + */ +typedef struct ListInfo +{ + int nvalues; /* number of values in the following array */ + Datum *values; /* values contained in lists of all the partitions */ + int *indexes; /* partition index of each individiual value; maps + * element-by-element with the values array */ + bool has_null; /* Is there a partition defined to accept nulls? */ + int null_index; /* Index of the null-accepting partition */ +} ListInfo; + +/* + * Range bound collection - sorted array of ranges of partitions of a range + * partitioned table + */ +typedef struct RangeInfo +{ + struct PartitionRange **ranges; +} RangeInfo; + +/* One partition's range */ +typedef struct PartitionRange +{ + struct PartitionRangeBound *lower; + struct PartitionRangeBound *upper; +} PartitionRange; + +/* Either bound of a range partition */ +typedef struct PartitionRangeBound +{ + Datum *val; /* composite bound value, if any */ + bool infinite; /* bound is +/- infinity */ + bool inclusive; /* bound is inclusive (vs exclusive) */ + bool lower; /* this is the lower (vs upper) bound */ +} PartitionRangeBound; + + +/* + * Following struct definitions are only used initially when initializing + * the relcache. + */ + +/* One list partition */ +typedef struct PartitionList +{ + int nvalues; + Datum *values; + bool has_null; +} PartitionList; + +/* One value coming from some (index'th) list partition */ +typedef struct ListValue +{ + Datum value; + int index; +} ListValue; + +/* This has oid because we want it to be ordered along with range */ +typedef struct RangePartition +{ + Oid oid; + PartitionRange *range; +} RangePartition; + /* Support RelationBuildPartitionKey() */ static PartitionKey copy_partition_key(PartitionKey fromkey); static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo); +/* Support RelationBuildPartitionDesc() */ +static int32 list_value_cmp(const void *a, const void *b, void *arg); +static int32 range_partition_cmp(const void *a, const void *b, void *arg); + +/* Support check_new_partition_bound() */ +static bool list_overlaps_existing_partition(PartitionKey key, + PartitionListSpec *list_spec, + ListInfo *listinfo, + int *with); +static bool partition_range_empty(PartitionKey key, + PartitionRangeSpec *range_spec); +static bool range_overlaps_existing_partition(PartitionKey key, + PartitionRangeSpec *range_spec, + RangeInfo *rangeinfo, + int n, + int *with); + +/* Support get_qual_from_partbound */ +typedef struct translate_var_attno_mutator_context +{ + AttrNumber old_attno; + AttrNumber new_attno; +} translate_var_attno_mutator_context; + +static Node *translate_var_attno(Node *expr, AttrNumber attno, + AttrNumber new_attno); +static Node *translate_var_attno_mutator(Node *node, + translate_var_attno_mutator_context *cxt); +static List *get_qual_for_list(PartitionKey key, PartitionListSpec *list); +static List *get_qual_for_range(PartitionKey key, PartitionRangeSpec *range); +static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy, + bool *need_relabel); + +/* Support RelationGetPartitionQual() */ +static List *generate_partition_qual(Relation rel, bool recurse); + +/* List partition related support functions */ +static PartitionList *make_list_from_spec(PartitionKey key, + PartitionListSpec *list_spec); +static bool equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n); +static int32 list_values_cmp(PartitionKey key, Datum val1, Datum val2); +static int bsearch_list_values(const Datum *values, int n, const Datum probe, + PartitionKey key); + +/* Range partition related support functions */ +static PartitionRange *make_range_from_spec(PartitionKey key, PartitionRangeSpec *range_spec); +static PartitionRangeBound *make_range_bound(PartitionKey key, List *val, bool inclusive, + bool lower); +static PartitionRange *copy_range(PartitionRange *src, PartitionKey key); +static PartitionRangeBound *copy_range_bound(PartitionRangeBound *src, PartitionKey key); +static bool equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n); +static int32 partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2); +static int32 partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1, + PartitionRangeBound *b2); +static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2); +static bool partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2); + /* * Partition key related functions */ @@ -392,3 +540,1542 @@ copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo) return result; } + +/* + * Partition bound and partition descriptor related functions + */ + +/* + * RelationBuildPartitionDesc + * Form rel's partition descriptor + * + * Not flushed from the cache by RelationClearRelation() unless changed because + * of addition or removal of partitions. + */ +void +RelationBuildPartitionDesc(Relation rel) +{ + List *partoids; + Oid *oids; + List *boundspecs = NIL; + ListCell *cell; + int i, + nparts; + PartitionKey key = RelationGetPartitionKey(rel); + PartitionDesc result; + MemoryContext oldcxt; + + /* List partitioning */ + ListValue **all_values; + int all_values_count; + bool found_null_partition = false; + int null_partition_index; + + /* Range partitioning */ + RangePartition **range_parts; + + /* + * The following could happen in situations where rel has a pg_class + * entry but not the pg_partitioned_table entry yet. + */ + if (key == NULL) + return; + + /* Get partition oids from pg_inherits */ + partoids = find_inheritance_children(RelationGetRelid(rel), NoLock); + nparts = list_length(partoids); + + if (nparts > 0) + { + oids = (Oid *) palloc0(nparts * sizeof(Oid)); + + /* Collect bound spec nodes in a list */ + i = 0; + foreach(cell, partoids) + { + Oid partrelid = lfirst_oid(cell); + HeapTuple tuple; + Datum datum; + bool isnull; + Node *boundspec; + + tuple = SearchSysCache1(RELOID, partrelid); + Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition); + + datum = SysCacheGetAttr(RELOID, tuple, + Anum_pg_class_relpartbound, + &isnull); + Assert(!isnull); + boundspec = stringToNode(TextDatumGetCString(datum)); + boundspecs = lappend(boundspecs, boundspec); + ReleaseSysCache(tuple); + oids[i++] = partrelid; + } + + /* Convert from node to a internal representation */ + switch (key->strategy) + { + case PARTITION_STRAT_LIST: + { + PartitionList **list; + int j; + + list = (PartitionList **) palloc0(nparts * sizeof(PartitionList *)); + + i = 0; + all_values_count = 0; + foreach(cell, boundspecs) + { + PartitionListSpec *list_spec; + + Assert(IsA(lfirst(cell), PartitionListSpec)); + list_spec = (PartitionListSpec *) lfirst(cell); + + list[i] = make_list_from_spec(key, list_spec); + + if (list[i]->has_null) + { + found_null_partition = true; + null_partition_index = i; + } + all_values_count += list[i]->nvalues; + i++; + } + + /* + * Collect all list values in one array. Alongside the value, + * we also save the index of partition the value comes from. + */ + all_values = (ListValue **) + palloc0(all_values_count * sizeof(ListValue *)); + j = 0; + for (i = 0; i < nparts; i++) + { + int k; + + for (k = 0; k < list[i]->nvalues; k++) + { + ListValue *list_value; + + list_value = (ListValue *) palloc0(sizeof(ListValue)); + list_value->value = datumCopy(list[i]->values[k], + key->tcinfo->typbyval[0], + key->tcinfo->typlen[0]); + list_value->index = i; + all_values[j++] = list_value; + } + } + + break; + } + + case PARTITION_STRAT_RANGE: + { + range_parts = (RangePartition **) + palloc0(nparts * sizeof(RangePartition *)); + i = 0; + foreach(cell, boundspecs) + { + PartitionRangeSpec *range_spec; + RangePartition *range_part; + + Assert(IsA(lfirst(cell), PartitionRangeSpec)); + range_spec = (PartitionRangeSpec *) lfirst(cell); + range_part = (RangePartition *) + palloc0(nparts * sizeof(RangePartition)); + range_part->oid = oids[i]; + range_part->range = make_range_from_spec(key, range_spec); + range_parts[i++] = range_part; + } + + break; + } + } + } + + /* Now build the actual relcache partition descriptor */ + rel->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext, + RelationGetRelationName(rel), + ALLOCSET_DEFAULT_SIZES); + oldcxt = MemoryContextSwitchTo(rel->rd_pdcxt); + + result = (PartitionDescData *) palloc0(sizeof(PartitionDescData)); + result->nparts = nparts; + if (nparts > 0) + { + result->oids = (Oid *) palloc0(nparts * sizeof(Oid)); + result->bounds = (BoundCollectionData *) + palloc0(sizeof(BoundCollectionData)); + switch (key->strategy) + { + case PARTITION_STRAT_LIST: + { + ListInfo *listinfo; + + listinfo = (ListInfo *) palloc0(sizeof(ListInfo)); + + /* + * Copy oids in the same order as they were found in the + * pg_inherits catalog + */ + for (i = 0; i < nparts; i++) + result->oids[i] = oids[i]; + + /* Sort so that we can perform binary search over values */ + qsort_arg(all_values, all_values_count, sizeof(ListValue *), + list_value_cmp, (void *) key); + + listinfo->nvalues = all_values_count; + listinfo->has_null = found_null_partition; + if (found_null_partition) + listinfo->null_index = null_partition_index; + else + listinfo->null_index = -1; + listinfo->values = (Datum *) + palloc0(all_values_count * sizeof(Datum)); + listinfo->indexes = (int *) + palloc0(all_values_count * sizeof(int)); + for (i = 0; i < all_values_count; i++) + { + listinfo->values[i] = datumCopy(all_values[i]->value, + key->tcinfo->typbyval[0], + key->tcinfo->typlen[0]); + listinfo->indexes[i] = all_values[i]->index; + } + + result->bounds->listinfo = listinfo; + break; + } + + case PARTITION_STRAT_RANGE: + { + RangeInfo *rangeinfo; + + rangeinfo = (RangeInfo *) palloc0(sizeof(RangeInfo)); + rangeinfo->ranges = (PartitionRange **) + palloc0(nparts * sizeof(PartitionRange *)); + + /* + * Sort so that we can perform binary search over ranges. + * Note that this will also sort oids together. + */ + qsort_arg(range_parts, nparts, sizeof(RangePartition *), + range_partition_cmp, (void *) key); + + for (i = 0; i < nparts; i++) + { + result->oids[i] = range_parts[i]->oid; + rangeinfo->ranges[i] = copy_range(range_parts[i]->range, + key); + } + + result->bounds->rangeinfo = rangeinfo; + break; + } + } + } + + MemoryContextSwitchTo(oldcxt); + rel->rd_partdesc = result; +} + +/* + * Are two partition bound collections logically equal? + * + * Used in the keep logic of relcache.c (ie, in RelationClearRelation()). + * This is also useful when b1 and b2 are bound collections of two separate + * relations, respectively, because BoundCollection is a canonical + * representation of a set partition bounds (for given partitioning strategy). + */ +bool +partition_bounds_equal(PartitionKey key, + BoundCollection b1, BoundCollection b2, int n) +{ + switch (key->strategy) + { + case PARTITION_STRAT_LIST: + if (!equal_list_info(key, b1->listinfo, b2->listinfo, n)) + return false; + break; + + case PARTITION_STRAT_RANGE: + if (!equal_range_info(key, b1->rangeinfo, b2->rangeinfo, n)) + return false; + break; + } + + return true; +} + +/* + * check_new_partition_bound + * + * Call partition method specific routines to check if the new partition's + * bound overlaps any of the existing partitions of parent. Some partition + * types may have still other validations to perform, for example, a range + * partition with an empty range is not valid. + */ +void +check_new_partition_bound(char *relname, Oid parentId, Node *bound) +{ + Relation parent = heap_open(parentId, NoLock); /* already locked */ + PartitionKey key = RelationGetPartitionKey(parent); + PartitionDesc pdesc = RelationGetPartitionDesc(parent); + ParseState *pstate = make_parsestate(NULL); + int with = -1; + + switch (key->strategy) + { + case PARTITION_STRAT_LIST: + { + PartitionListSpec *list; + + Assert(IsA(bound, PartitionListSpec)); + list = (PartitionListSpec *) bound; + + if (pdesc->nparts > 0) + { + ListInfo *listinfo; + + Assert(pdesc->bounds && pdesc->bounds->listinfo); + listinfo = pdesc->bounds->listinfo; + if (list_overlaps_existing_partition(key, list, listinfo, + &with)) + { + Assert(with >= 0); + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("partition \"%s\" would overlap partition \"%s\"", + relname, get_rel_name(pdesc->oids[with])), + parser_errposition(pstate, list->location))); + } + } + break; + } + + case PARTITION_STRAT_RANGE: + { + PartitionRangeSpec *range; + + Assert(IsA(bound, PartitionRangeSpec)); + range = (PartitionRangeSpec *) bound; + if (partition_range_empty(key, range)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot create range partition with empty range"), + parser_errposition(pstate, range->location))); + + if (pdesc->nparts > 0) + { + RangeInfo *rangeinfo; + + Assert(pdesc->bounds && pdesc->bounds->rangeinfo); + rangeinfo = pdesc->bounds->rangeinfo; + if (range_overlaps_existing_partition(key, range, rangeinfo, + pdesc->nparts, &with)) + { + Assert(with >= 0); + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("partition \"%s\" would overlap partition \"%s\"", + relname, get_rel_name(pdesc->oids[with])), + parser_errposition(pstate, range->location))); + } + } + break; + } + } + + heap_close(parent, NoLock); +} + +/* + * get_partition_parent + * + * Returns inheritance parent of relid by scanning pg_inherits + * + * Note: This function should be called only when it is known that 'relid' + * is a partition, that is, relid_is_partition(relid) returns true. + */ +Oid +get_partition_parent(Oid relid) +{ + Form_pg_inherits form; + Relation catalogRelation; + SysScanDesc scan; + ScanKeyData key[2]; + HeapTuple tuple; + Oid result; + + catalogRelation = heap_open(InheritsRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_inherits_inhrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + ScanKeyInit(&key[1], + Anum_pg_inherits_inhseqno, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(1)); + + scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true, + NULL, 2, key); + + tuple = systable_getnext(scan); + Assert(HeapTupleIsValid(tuple)); + + form = (Form_pg_inherits) GETSTRUCT(tuple); + result = form->inhparent; + + systable_endscan(scan); + heap_close(catalogRelation, AccessShareLock); + + return result; +} + +/* + * get_leaf_partition_oids + * Returns a list of all leaf-level partitions of relation with OID + * 'relid'. + * + * All returned OIDs will have been locked by find_inheritance_children(). + */ +List * +get_leaf_partition_oids(Oid relid, int lockmode) +{ + List *partitions, + *result = NIL; + ListCell *lc; + + partitions = find_inheritance_children(relid, lockmode); + + foreach(lc, partitions) + { + Oid myoid = lfirst_oid(lc); + Relation partRel; + + partRel = heap_open(myoid, NoLock); + + if (partRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + result = list_concat(result, + get_leaf_partition_oids(myoid, lockmode)); + else + result = lappend_oid(result, myoid); + + heap_close(partRel, NoLock); + } + + return result; +} + +/* + * get_qual_from_partbound + * Given a parser node for partition bound, return the list of executable + * expressions as partition predicate + */ +List * +get_qual_from_partbound(Relation rel, Relation parent, Node *bound) +{ + PartitionKey key = RelationGetPartitionKey(parent); + List *my_qual; + int i; + ListCell *partexprs_item; + + Assert(key); + + if (IsA(bound, PartitionListSpec)) + { + PartitionListSpec *list_spec = (PartitionListSpec *) bound; + + Assert(key->strategy == PARTITION_STRAT_LIST); + my_qual = get_qual_for_list(key, list_spec); + } + else if (IsA(bound, PartitionRangeSpec)) + { + PartitionRangeSpec *range_spec = (PartitionRangeSpec *) bound; + + Assert(key->strategy == PARTITION_STRAT_RANGE); + my_qual = get_qual_for_range(key, range_spec); + } + + /* + * Translate vars in the generated expression to have correct attnos. + * Note that the vars in my_qual bear attnos dictated by key which carries + * physical attnos of the parent. We must allow for a case where physical + * attnos of a partition can be different from the parent. + */ + partexprs_item = list_head(key->partexprs); + for (i = 0; i < key->partnatts; i++) + { + AttrNumber attno = key->partattrs[i], + new_attno; + char *attname; + + if (attno != 0) + { + /* Simple column reference */ + attname = get_attname(RelationGetRelid(parent), attno); + new_attno = get_attnum(RelationGetRelid(rel), attname); + + if (new_attno != attno) + my_qual = (List *) translate_var_attno((Node *) my_qual, + attno, + new_attno); + } + else + { + /* Arbitrary expression */ + Node *expr = (Node *) lfirst(partexprs_item); + Bitmapset *expr_attrs = NULL; + int index; + + /* Find all attributes referenced and translate each reference */ + pull_varattnos(expr, 1, &expr_attrs); + partexprs_item = lnext(partexprs_item); + + index = -1; + while ((index = bms_next_member(expr_attrs, index)) > 0) + { + AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber; + + attname = get_attname(RelationGetRelid(parent), attno); + new_attno = get_attnum(RelationGetRelid(rel), attname); + + if (new_attno != attno) + my_qual = (List *) translate_var_attno((Node *) my_qual, + attno, + new_attno); + } + } + } + + return my_qual; +} + +/* + * RelationGetPartitionQual + * + * Returns a list of partition quals + */ +List * +RelationGetPartitionQual(Relation rel, bool recurse) +{ + /* Quick exit */ + if (!rel->rd_rel->relispartition) + return NIL; + + return generate_partition_qual(rel, recurse); +} + +/* Module-local functions */ + +/* + * list_value_cmp + * + * Compare two list values + */ +static int32 +list_value_cmp(const void *a, const void *b, void *arg) +{ + return list_values_cmp((PartitionKey) arg, + (*(const ListValue **) a)->value, + (*(const ListValue **) b)->value); +} + +/* + * range_partition_cmp + * + * Compare two range partitions + */ +static int32 +range_partition_cmp(const void *a, const void *b, void *arg) +{ + return partition_range_cmp((PartitionKey) arg, + (*(const RangePartition **) a)->range, + (*(const RangePartition **) b)->range); +} + +/* + * list_overlaps_existing_partition + * + * Does a new list partition overlap any of existing partitions? + */ +static bool +list_overlaps_existing_partition(PartitionKey key, + PartitionListSpec *list_spec, + ListInfo *listinfo, + int *with) +{ + int i; + PartitionList *new_list; + + Assert(listinfo); + Assert(listinfo->nvalues > 0 || listinfo->has_null); + + new_list = make_list_from_spec(key, list_spec); + + if (new_list->has_null && listinfo->has_null) + { + *with = listinfo->null_index; + return true; + } + + for (i = 0; i < new_list->nvalues; i++) + { + int found; + + /* bsearch a new list's value in listinfo->values */ + found = bsearch_list_values(listinfo->values, + listinfo->nvalues, + new_list->values[i], + key); + if (found >= 0) + { + *with = listinfo->indexes[found]; + return true; + } + } + + return false; +} + + +/* + * Is a new partition's range empty? + */ +static bool +partition_range_empty(PartitionKey key, PartitionRangeSpec *range_spec) +{ + PartitionRange *range; + PartitionRangeBound *lower, + *upper; + + range = make_range_from_spec(key, range_spec); + lower = range->lower; + upper = range->upper; + + /* + * Range is not empty if one (and only one) of the bounds is infinity. + * Both cannot be infinity because of how the syntax is specified. + */ + Assert(!lower->infinite || !upper->infinite); + if (lower->infinite || upper->infinite) + return false; + + /* + * If upper < lower, then it's outright empty. Also if lower = upper + * and either is exclusive. + */ + if (partition_range_tuple_cmp(key, upper->val, lower->val) < 0 || + (partition_range_tuple_cmp(key, lower->val, upper->val) == 0 && + (!lower->inclusive || !upper->inclusive))) + return true; + + return false; +} + +/* + * range_overlaps_existing_partition + * + * Does the new range partition overlap any of existing partitions? + */ +static bool +range_overlaps_existing_partition(PartitionKey key, + PartitionRangeSpec *range_spec, + RangeInfo *rangeinfo, + int n, int *with) +{ + int i; + PartitionRange *range; + + /* Create internal representation of range from range_spec */ + range = make_range_from_spec(key, range_spec); + + Assert(rangeinfo); + for (i = 0; i < n; i++) + { + if (partition_range_overlaps(key, range, rangeinfo->ranges[i])) + { + *with = i; + return true; + } + } + + return false; +} + +/* Check two range partitions for overlap */ +static bool +partition_range_overlaps(PartitionKey key, PartitionRange *r1, PartitionRange *r2) +{ + if (partition_range_bound_cmp(key, r1->lower, r2->lower) >= 0 && + partition_range_bound_cmp(key, r1->lower, r2->upper) <= 0) + return true; + + if (partition_range_bound_cmp(key, r2->lower, r1->lower) >= 0 && + partition_range_bound_cmp(key, r2->lower, r1->upper) <= 0) + return true; + + return false; +} + +/* + * translate_var_attno + * + * Changes Vars with a given attno in the provided expression tree to + * Vars with new_attno + */ +static Node * +translate_var_attno(Node *expr, AttrNumber attno, AttrNumber new_attno) +{ + translate_var_attno_mutator_context cxt; + + cxt.old_attno = attno; + cxt.new_attno = new_attno; + + return expression_tree_mutator(expr, translate_var_attno_mutator, &cxt); +} + +/* + * translate_var_attno_mutator + */ +static Node * +translate_var_attno_mutator(Node *node, + translate_var_attno_mutator_context *cxt) +{ + if (node == NULL) + return NULL; + + if (IsA(node, Var) && ((Var *) node)->varattno == cxt->old_attno) + { + Var *newvar = copyObject(node); + + newvar->varattno = cxt->new_attno; + + return (Node *) newvar; + } + + return expression_tree_mutator(node, translate_var_attno_mutator, + (void *) cxt); +} + +/* + * get_qual_for_list + * + * Get a ScalarArrayOpExpr to use as a list partition's constraint, given the + * partition key (left operand) and PartitionListSpec (right operand). If the + * partition does not accept nulls, also include a IS NOT NULL test. + */ +static List * +get_qual_for_list(PartitionKey key, PartitionListSpec *list_spec) +{ + ArrayExpr *arr; + ScalarArrayOpExpr *opexpr; + ListCell *cell, + *prev, + *next; + Node *key_col; + Oid operoid; + bool need_relabel, + list_has_null = false; + NullTest *nulltest1 = NULL, + *nulltest2 = NULL; + + /* Left operand is either a simple Var or arbitrary expression */ + if (key->partattrs[0] != 0) + key_col = (Node *) makeVar(1, key->partattrs[0], + key->tcinfo->typid[0], + key->tcinfo->typmod[0], + key->tcinfo->typcoll[0], + 0); + else + key_col = (Node *) copyObject(linitial(key->partexprs)); + + /* + * If list does not accept nulls, we must add a IS NOT NULL test. + * If it does, leave out the test but remove null Const from the list + * and create a IS NULL test to be OR'd with ScalarArrayOpExpr. The + * latter because null-valued expressions does not have the desired + * behavior when used within ScalarArrayOpExpr or OpExpr. + */ + prev = NULL; + for (cell = list_head(list_spec->values); cell; cell = next) + { + Const *val = (Const *) lfirst(cell); + + next = lnext(cell); + + if (val->constisnull) + { + list_has_null = true; + list_spec->values = list_delete_cell(list_spec->values, + cell, prev); + } + else + prev = cell; + } + + if (!list_has_null) + { + /* Gin up a col IS NOT NULL test */ + nulltest1 = makeNode(NullTest); + nulltest1->arg = (Expr *) key_col; + nulltest1->nulltesttype = IS_NOT_NULL; + nulltest1->argisrow = false; + nulltest1->location = -1; + } + else + { + /* Gin up a col IS NULL test */ + nulltest2 = makeNode(NullTest); + nulltest2->arg = (Expr *) key_col; + nulltest2->nulltesttype = IS_NULL; + nulltest2->argisrow = false; + nulltest2->location = -1; + } + + /* Right operand is an ArrayExpr */ + arr = makeNode(ArrayExpr); + arr->array_typeid = get_array_type(key->tcinfo->typid[0]); + arr->array_collid = key->tcinfo->typcoll[0]; + arr->element_typeid = key->tcinfo->typid[0]; + arr->elements = list_spec->values; + arr->multidims = false; + arr->location = -1; + + /* Get the correct btree equality operator */ + operoid = get_partition_operator(key, 0, BTEqualStrategyNumber, + &need_relabel); + if (need_relabel) + key_col = (Node *) makeRelabelType((Expr *) key_col, + key->partopcintype[0], -1, + get_typcollation(key->partopcintype[0]), + COERCE_EXPLICIT_CAST); + + /* Build leftop = ANY (rightop) */ + opexpr = makeNode(ScalarArrayOpExpr); + opexpr->opno = operoid; + opexpr->opfuncid = get_opcode(operoid); + opexpr->useOr = true; + opexpr->inputcollid = key->tcinfo->typcoll[0]; + opexpr->args = list_make2(key_col, arr); + opexpr->location = -1; + + return nulltest1 ? list_make2(nulltest1, opexpr) + : (nulltest2 ? list_make1(makeBoolExpr(OR_EXPR, + list_make2(nulltest2, opexpr), + -1)) + : list_make1(opexpr)); +} + +/* + * get_qual_for_range + * + * Get a list of OpExpr's to use as a range partition's constraint, given the + * partition key (left operands) and PartitionRangeSpec (right operands) + * + * For each column, a IS NOT NULL test is emitted since we do not allow null + * values in range partition key. + */ +static List * +get_qual_for_range(PartitionKey key, PartitionRangeSpec *spec) +{ + List *result = NIL; + ListCell *cell1, + *cell2, + *partexprs_item; + int i; + Oid operoid; + uint16 strategy; + bool need_relabel; + + /* + * Handle the case where the partition is bounded on only one side. + * + * In this case, consider only the first column of the key since + * comparison with only the first column would have determined whether + * whether a row went into such partition. In other words, it always + * follows that -INF < someval and someval < +INF. + */ + if (spec->lower == NIL || spec->upper == NIL) + { + List *values; + Const *key_val; + Node *key_col; + bool islower, + inclusive; + NullTest *nulltest; + + if (spec->lower != NIL) + { + values = spec->lower; + islower = true; + inclusive = spec->lowerinc; + } + else + { + values = spec->upper; + islower = false; + inclusive = spec->upperinc; + } + + /* Left operand */ + if (key->partattrs[0] != 0) + key_col = (Node *) makeVar(1, key->partattrs[0], + key->tcinfo->typid[0], + key->tcinfo->typmod[0], + key->tcinfo->typcoll[0], + 0); + else + key_col = (Node *) copyObject(linitial(key->partexprs)); + + /* Right operand */ + key_val = linitial(values); + + if (islower) + strategy = inclusive ? BTGreaterEqualStrategyNumber : BTGreaterStrategyNumber; + else + strategy = inclusive ? BTLessEqualStrategyNumber : BTLessStrategyNumber; + + /* Get the correct btree operator for given strategy */ + operoid = get_partition_operator(key, 0, strategy, &need_relabel); + + if (need_relabel) + key_col = (Node *) makeRelabelType((Expr *) key_col, + key->partopcintype[0], -1, + get_typcollation(key->partopcintype[0]), + COERCE_EXPLICIT_CAST); + + /* Gin up a col IS NOT NULL test */ + nulltest = makeNode(NullTest); + nulltest->arg = (Expr *) key_col; + nulltest->nulltesttype = IS_NOT_NULL; + nulltest->argisrow = false; + nulltest->location = -1; + + /* Build the opexpr and return the list containing it and nulltest */ + return list_make2(nulltest, + make_opclause(operoid, BOOLOID, + false, + (Expr *) key_col, + (Expr *) key_val, + InvalidOid, + key->tcinfo->typcoll[0])); + } + + /* + * We must consider both the lower and upper bounds. Iterate over + * columns of the key. + */ + i = 0; + partexprs_item = list_head(key->partexprs); + forboth (cell1, spec->lower, cell2, spec->upper) + { + Node *key_col; + Const *lower_val = lfirst(cell1); + Const *upper_val = lfirst(cell2); + EState *estate; + MemoryContext oldcxt; + Expr *test_expr; + ExprState *test_exprstate; + Datum test_result; + bool isNull; + bool need_relabel = false; + NullTest *nulltest; + + /* Left operand */ + if (key->partattrs[i] != 0) + { + key_col = (Node *) makeVar(1, key->partattrs[i], + key->tcinfo->typid[i], + key->tcinfo->typmod[i], + key->tcinfo->typcoll[i], + 0); + } + else + { + key_col = (Node *) copyObject(lfirst(partexprs_item)); + partexprs_item = lnext(partexprs_item); + } + + /* Gin up a col IS NOT NULL test */ + nulltest = makeNode(NullTest); + nulltest->arg = (Expr *) key_col; + nulltest->nulltesttype = IS_NOT_NULL; + nulltest->argisrow = false; + nulltest->location = -1; + result = lappend(result, nulltest); + + /* + * Is lower_val = upper_val? + */ + + /* Get the correct btree equality operator for the test */ + operoid = get_partition_operator(key, i, BTEqualStrategyNumber, + &need_relabel); + + estate = CreateExecutorState(); + oldcxt = MemoryContextSwitchTo(estate->es_query_cxt); + test_expr = make_opclause(operoid, + BOOLOID, + false, + (Expr *) lower_val, + (Expr *) upper_val, + InvalidOid, + key->tcinfo->typcoll[i]); + fix_opfuncids((Node *) test_expr); + test_exprstate = ExecInitExpr(test_expr, NULL); + test_result = ExecEvalExprSwitchContext(test_exprstate, + GetPerTupleExprContext(estate), + &isNull, NULL); + MemoryContextSwitchTo(oldcxt); + FreeExecutorState(estate); + + if (DatumGetBool(test_result)) + { + /* + * Yes, build leftop eq lower_val + */ + if (need_relabel) + key_col = (Node *) makeRelabelType((Expr *) key_col, + key->partopcintype[i], -1, + get_typcollation(key->partopcintype[i]), + COERCE_EXPLICIT_CAST); + result = lappend(result, + make_opclause(operoid, + BOOLOID, + false, + (Expr *) key_col, + (Expr *) lower_val, + InvalidOid, + key->tcinfo->typcoll[i])); + + /* Go to the next column. */ + } + else + { + /* Build leftop ge/gt lower_val */ + strategy = spec->lowerinc ? BTGreaterEqualStrategyNumber + : BTGreaterStrategyNumber; + operoid = get_partition_operator(key, i, strategy, &need_relabel); + + if (need_relabel) + key_col = (Node *) makeRelabelType((Expr *) key_col, + key->partopcintype[i], -1, + get_typcollation(key->partopcintype[i]), + COERCE_EXPLICIT_CAST); + result = lappend(result, + make_opclause(operoid, + BOOLOID, + false, + (Expr *) key_col, + (Expr *) lower_val, + InvalidOid, + key->tcinfo->typcoll[i])); + + /* Build leftop le/lt upper_val */ + strategy = i < spec->upperinc ? BTLessEqualStrategyNumber + : BTLessStrategyNumber; + operoid = get_partition_operator(key, i, strategy, &need_relabel); + + if (need_relabel) + key_col = (Node *) makeRelabelType((Expr *) key_col, + key->partopcintype[i], -1, + get_typcollation(key->partopcintype[i]), + COERCE_EXPLICIT_CAST); + + result = lappend(result, + make_opclause(operoid, + BOOLOID, + false, + (Expr *) key_col, + (Expr *) upper_val, + InvalidOid, + key->tcinfo->typcoll[i])); + + /* No need to constrain further columns. */ + break; + } + + i++; + } + + return result; +} + +/* + * get_partition_operator + * + * Return oid of the operator of given strategy for a given partition key + * column. + * + * Use either the column type as the operator datatype or opclass's declared + * input type. + */ +static Oid +get_partition_operator(PartitionKey key, int col, StrategyNumber strategy, + bool *need_relabel) +{ + Oid operoid; + + if (need_relabel) + *need_relabel = false; + + operoid = get_opfamily_member(key->partopfamily[col], + key->tcinfo->typid[col], + key->tcinfo->typid[col], + strategy); + if (!OidIsValid(operoid)) + { + operoid = get_opfamily_member(key->partopfamily[col], + key->partopcintype[col], + key->partopcintype[col], + strategy); + *need_relabel = true; + } + + return operoid; +} + +/* + * generate_partition_qual + * + * Generate partition predicate from rel's partition bound expression + * + * Result expression tree is stored CacheMemoryContext to ensure it survives + * as long as the relcache entry. But we should be running in a less long-lived + * working context. To avoid leaking cache memory if this routine fails partway + * through, we build in working memory and then copy the completed structure + * into cache memory. + */ +static List * +generate_partition_qual(Relation rel, bool recurse) +{ + HeapTuple tuple; + MemoryContext oldcxt; + Datum boundDatum; + bool isnull; + Node *bound; + List *my_qual = NIL, + *result = NIL; + Relation parent; + + /* Grab at least an AccessShareLock on the parent table */ + parent = heap_open(get_partition_parent(RelationGetRelid(rel)), + AccessShareLock); + + /* Quick copy */ + if (rel->rd_partcheck) + { + if (parent->rd_rel->relispartition && recurse) + result = list_concat(generate_partition_qual(parent, true), + copyObject(rel->rd_partcheck)); + else + result = copyObject(rel->rd_partcheck); + + heap_close(parent, AccessShareLock); + return result; + } + + /* Generate from pg_class.relpartbound */ + Assert(rel->rd_rel->relispartition); + tuple = SearchSysCache1(RELOID, RelationGetRelid(rel)); + boundDatum = SysCacheGetAttr(RELOID, tuple, + Anum_pg_class_relpartbound, + &isnull); + Assert(!isnull); + bound = stringToNode(TextDatumGetCString(boundDatum)); + + /* Turn that bound into a list of equivalent check quals */ + my_qual = get_qual_from_partbound(rel, parent, bound); + + /* If requested, add parent's quals to the list (if any) */ + if (parent->rd_rel->relispartition && recurse) + { + List *parent_check; + + parent_check = generate_partition_qual(parent, true); + result = list_concat(parent_check, my_qual); + } + else + result = my_qual; + + /* Save a copy of my_qual in the relcache */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + rel->rd_partcheck = copyObject(my_qual); + MemoryContextSwitchTo(oldcxt); + + ReleaseSysCache(tuple); + heap_close(parent, AccessShareLock); + + return result; +} + +/* List partition related support functions */ + +/* + * Make a PartitionList from a PartitionListSpec + */ +static PartitionList * +make_list_from_spec(PartitionKey key, PartitionListSpec *list_spec) +{ + PartitionList *list; + ListCell *cell; + int i, + num_non_null; + + + list = (PartitionList *) palloc0(sizeof(PartitionList)); + list->has_null = false; + + /* Never put a null into the values array, flag instead */ + num_non_null = 0; + foreach (cell, list_spec->values) + { + Const *val = lfirst(cell); + + if (val->constisnull) + list->has_null = true; + else + num_non_null++; + } + + list->values = (Datum *) palloc0(num_non_null * sizeof(Datum)); + i = 0; + foreach (cell, list_spec->values) + { + Const *val = lfirst(cell); + + if (!val->constisnull) + list->values[i++] = datumCopy(val->constvalue, + key->tcinfo->typbyval[0], + key->tcinfo->typlen[0]); + } + + list->nvalues = num_non_null; + + return list; +} + +/* + * Check that two list partition bound collections are logically equal + * + * n is the number of partitions that either of l1 and l2 represents. + */ +static bool +equal_list_info(PartitionKey key, ListInfo *l1, ListInfo *l2, int n) +{ + int i; + int *mapping; + + if (l1->nvalues != l2->nvalues) + return false; + + if (l1->has_null != l2->has_null) + return false; + + for (i = 0; i < l1->nvalues; i++) + if (list_values_cmp(key, l1->values[i], l2->values[i])) + return false; + + /* + * Index start from one. A zero in nth slot means that partition n of + * the first collection has not yet been mapped with a partition from + * the second collection. + */ + mapping = (int *) palloc0((n + 1) * sizeof(int)); + for (i = 0; i < l1->nvalues; i++) + { + int l1_partno = l1->indexes[i] + 1, + l2_partno = l2->indexes[i] + 1; + + /* Encountered l1_partno for the first time, map to l2_partno */ + if (mapping[l1_partno] == 0) + mapping[l1_partno] = l2_partno; + /* + * Maintain that the mapping does not change, otherwise report + * inequality + */ + else if (mapping[l1_partno] != l2_partno) + return false; + } + + /* Check that nulls are accepted in mapped partitions */ + Assert(l1->has_null || l1->null_index == -1); + Assert(l2->has_null || l2->null_index == -1); + if (l1->has_null && mapping[l1->null_index + 1] != l2->null_index + 1) + return false; + + return true; +} + +/* Compare two list values */ +static int32 +list_values_cmp(PartitionKey key, Datum val1, Datum val2) +{ + return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0], + key->tcinfo->typcoll[0], + val1, val2)); +} + +/* Binary search for list partition values; returns -1 if not found */ +static int +bsearch_list_values(const Datum *values, int n, const Datum probe, + PartitionKey key) +{ + int lo, + hi; + + lo = 0; + hi = n - 1; + while (lo <= hi) + { + int mid = (lo + hi) / 2; + int32 res = list_values_cmp(key, probe, values[mid]); + + if (res < 0) + hi = mid - 1; + else if (res > 0) + lo = mid + 1; + else + return mid; + } + + return -1; +} + +/* Range partition related support functions */ + +/* + * Make a PartitionRange from given PartitionRangeSpec + */ +static PartitionRange * +make_range_from_spec(PartitionKey key, PartitionRangeSpec *range_spec) +{ + PartitionRange *range; + + range = (PartitionRange *) palloc0(sizeof(PartitionRange)); + range->lower = make_range_bound(key, + range_spec->lower, + range_spec->lowerinc, + true); + range->upper = make_range_bound(key, + range_spec->upper, + range_spec->upperinc, + false); + + return range; +} + +/* + * Make PartitionRangeBound with given value (possibly composite) and + * inclusivity. + */ +static PartitionRangeBound * +make_range_bound(PartitionKey key, List *val, bool inclusive, bool lower) +{ + PartitionRangeBound *bound; + ListCell *cell; + + bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound)); + bound->infinite = (val == NIL); + bound->inclusive = inclusive; + bound->lower = lower; + + if (val) + { + int i; + + bound->val = (Datum *) palloc0(key->partnatts * sizeof(Datum)); + + i = 0; + foreach (cell, val) + { + Const *val = lfirst(cell); + + if (val->constisnull) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot specify NULL in range bound"))); + else + bound->val[i] = datumCopy(val->constvalue, + key->tcinfo->typbyval[i], + key->tcinfo->typlen[i]); + i++; + } + } + + return bound; +} + +/* + * Make and return a copy of input PartitionRange. + */ +static PartitionRange * +copy_range(PartitionRange *src, PartitionKey key) +{ + PartitionRange *result; + + result = (PartitionRange *) palloc0(sizeof(PartitionRange)); + result->lower = copy_range_bound(src->lower, key); + result->upper = copy_range_bound(src->upper, key); + + return result; +} + +/* + * Make and return a copy of input PartitionRangeBound. + */ +static PartitionRangeBound * +copy_range_bound(PartitionRangeBound *src, PartitionKey key) +{ + int i; + int partnatts = key->partnatts; + PartitionRangeBound *result; + + result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound)); + result->infinite = src->infinite; + result->inclusive = src->inclusive; + result->lower = src->lower; + + if (src->val) + { + result->val = (Datum *) palloc0(partnatts * sizeof(Datum)); + for (i = 0; i < partnatts; i++) + result->val[i] = datumCopy(src->val[i], + key->tcinfo->typbyval[i], + key->tcinfo->typlen[i]); + } + + return result; +} + +/* + * Compare ranges of two partitioned relations. + * + * Both r1 and r2 are known to contain n ranges (corresponding to n + * partitions). + */ +static bool +equal_range_info(PartitionKey key, RangeInfo *r1, RangeInfo *r2, int n) +{ + int i; + + /* Compare each individual range's lower and upper bounds */ + for (i = 0; i < n; i++) + { + if (partition_range_bound_cmp(key, + r1->ranges[i]->lower, + r2->ranges[i]->lower) != 0) + return false; + + if (partition_range_bound_cmp(key, + r1->ranges[i]->upper, + r2->ranges[i]->upper) != 0) + return false; + } + + return true; +} + +/* + * Compare two non-empty ranges (cf. range_cmp) + */ +static int32 +partition_range_cmp(PartitionKey key, PartitionRange *r1, PartitionRange *r2) +{ + int cmp; + + cmp = partition_range_bound_cmp(key, r1->lower, r2->lower); + if (cmp == 0) + cmp = partition_range_bound_cmp(key, r1->upper, r2->upper); + + return cmp; +} + +/* + * Return for two range bounds whether b1 <=, =, >= b2 + */ +static int32 +partition_range_bound_cmp(PartitionKey key, PartitionRangeBound *b1, PartitionRangeBound *b2) +{ + int32 result; + + /* + * First, handle cases involving infinity, which don't require invoking + * the comparison proc. + */ + if (b1->infinite && b2->infinite) + { + /* + * Both are infinity, so they are equal unless one is lower and the + * other not. + */ + if (b1->lower == b2->lower) + return 0; + else + return b1->lower ? -1 : 1; + } + else if (b1->infinite) + return b1->lower ? -1 : 1; + else if (b2->infinite) + return b2->lower ? 1 : -1; + + /* + * Both boundaries are finite, so compare the held values. + */ + result = partition_range_tuple_cmp(key, b1->val, b2->val); + + /* + * If the comparison is anything other than equal, we're done. If they + * compare equal though, we still have to consider whether the boundaries + * are inclusive or exclusive. + */ + if (result == 0) + { + if (!b1->inclusive && !b2->inclusive) + { + /* both are exclusive */ + if (b1->lower == b2->lower) + return 0; + else + return b1->lower ? 1 : -1; + } + else if (!b1->inclusive) + return b1->lower ? 1 : -1; + else if (!b2->inclusive) + return b2->lower ? -1 : 1; + else + { + /* + * Both are inclusive and the values held are equal, so they are + * equal regardless of whether they are upper or lower boundaries, + * or a mix. + */ + return 0; + } + } + + return result; +} + +/* + * Compare two composite keys + */ +static int32 +partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2) +{ + int32 result; + int i; + + for (i = 0; i < key->partnatts; i++) + { + result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i], + key->tcinfo->typcoll[i], + val1[i], val2[i])); + if (result != 0) + break; + } + + return result; +} diff --git a/src/backend/catalog/pg_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c index fa4d0f5..453feb2 100644 --- a/src/backend/catalog/pg_partitioned_table.c +++ b/src/backend/catalog/pg_partitioned_table.c @@ -139,8 +139,6 @@ StorePartitionKey(Relation rel, RelationGetRelid(rel), DEPENDENCY_NORMAL, DEPENDENCY_IGNORE); - /* Tell world about the key */ - CacheInvalidateRelcache(rel); heap_close(pg_partitioned_table, RowExclusiveLock); heap_freetuple(tuple); diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index 564e10e..9482c10 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -276,6 +276,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, 0, ONCOMMIT_NOOP, reloptions, + (Datum) 0, false, true, true, diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index dc1f79f..417d3e2 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -675,6 +675,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence, 0, ONCOMMIT_NOOP, reloptions, + (Datum) 0, false, true, true, diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c index 874b320..106508e 100644 --- a/src/backend/commands/lockcmds.c +++ b/src/backend/commands/lockcmds.c @@ -17,6 +17,7 @@ #include "access/heapam.h" #include "catalog/namespace.h" #include "catalog/pg_inherits_fn.h" +#include "catalog/pg_partitioned_table_fn.h" #include "commands/lockcmds.h" #include "miscadmin.h" #include "parser/parse_clause.h" diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index e08fd5d..0ad14de 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -159,6 +159,7 @@ DefineSequence(ParseState *pstate, 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 3bb5933..4a7c98d 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -164,6 +164,8 @@ typedef struct AlteredTableInfo Oid newTableSpace; /* new tablespace; 0 means no change */ bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */ char newrelpersistence; /* if above is true */ + List *partition_quals; /* partition check quals for attach partition + * validation */ /* Objects to rebuild after completing ALTER TYPE operations */ List *changedConstraintOids; /* OIDs of constraints to rebuild */ List *changedConstraintDefs; /* string definitions of same */ @@ -280,7 +282,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); @@ -447,6 +450,11 @@ static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby) static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, List **partexprbin, List **partexprsrc, Oid *partopclass); +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); /* ---------------------------------------------------------------- @@ -485,6 +493,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, List *rawDefaults; List *cookedDefaults; Datum reloptions; + Datum relpartbound; ListCell *listptr; AttrNumber attnum; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; @@ -589,10 +598,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 @@ -667,6 +682,24 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, } } + /* Process and store partition bound. */ + if (stmt->partbound) + { + char *boundString; + Oid parentId = linitial_oid(inheritOids); + + /* + * Check first that the new partition's bound is valid and does not + * overlap with any of existing partitions of the parent - note that + * it does not return on error. + */ + check_new_partition_bound(relname, parentId, stmt->partbound); + boundString = nodeToString(stmt->partbound); + relpartbound = CStringGetTextDatum(boundString); + } + else + relpartbound = (Datum) 0; + /* * Create the relation. Inherited defaults and constraints are passed in * for immediate handling --- since they don't need parsing, they can be @@ -690,6 +723,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, parentOidCount, stmt->oncommit, reloptions, + relpartbound, true, allowSystemTableMods, false, @@ -989,6 +1023,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid, return; /* concurrently dropped, so nothing to do */ classform = (Form_pg_class) GETSTRUCT(tuple); + if (classform->relispartition) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is a partition of \"%s\"", rel->relname, + get_rel_name(get_partition_parent(relOid))), + errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it."))); + /* * RemoveRelations never passes RELKIND_PARTITIONED_TABLE as the relkind * for OBJECT_TABLE relations. It is ok for the passed in relkind to be @@ -1074,7 +1115,8 @@ ExecuteTruncate(TruncateStmt *stmt) rels = lappend(rels, rel); relids = lappend_oid(relids, myrelid); - if (recurse) + /* Force inheritance recursion, if partitioned table. */ + if (recurse || rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { ListCell *child; List *children; @@ -1452,7 +1494,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 +1540,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 +1568,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), @@ -1560,18 +1615,37 @@ MergeAttributes(List *schema, List *supers, char relpersistence, * on the parent table, which might otherwise be attempting to clear * the parent's relhassubclass field, if its previous children were * recently dropped. + * + * If the child table is a partition, then we instead grab an exclusive + * lock on the parent because its partition descriptor will be changed + * by addition of the new partition. */ - relation = heap_openrv(parent, ShareUpdateExclusiveLock); + if (!is_partition) + relation = heap_openrv(parent, ShareUpdateExclusiveLock); + else + relation = heap_openrv(parent, AccessExclusiveLock); - /* Cannot inherit from partitioned tables */ - if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + /* + * Cannot inherit from partitioned tables unless the child table is a + * partition. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + !is_partition) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot inherit from table \"%s\"", parent->relname), errdetail("Table \"%s\" is partitioned.", parent->relname))); + /* The same if the parent is a partition */ + if (relation->rd_rel->relispartition && !is_partition) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot inherit from partition \"%s\"", + parent->relname))); + if (relation->rd_rel->relkind != RELKIND_RELATION && - relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE) + relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE && + relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("inherited relation \"%s\" is not a table or foreign table", @@ -1709,6 +1783,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; @@ -1837,6 +1912,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 in transformPartitionOf(). */ if (inhSchema != NIL) { @@ -1866,16 +1944,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. */ - 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); @@ -1912,8 +1994,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 */ @@ -2203,6 +2286,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing) (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot rename column of typed table"))); + if (classform->relispartition && !recursing) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot rename column of a partition"))); + /* * Renaming the columns of sequences or toast tables doesn't actually * break anything from the system's point of view, since internal @@ -2433,7 +2521,7 @@ renameatt(RenameStmt *stmt) renameatt_internal(relid, stmt->subname, /* old att name */ stmt->newname, /* new att name */ - interpretInhOption(stmt->relation->inhOpt), /* recursive? */ + interpretInhOption(stmt->relation->inhOpt), false, /* recursing? */ 0, /* expected inhcount */ stmt->behavior); @@ -2581,11 +2669,12 @@ RenameConstraint(RenameStmt *stmt) } } + /* Force inheritance recursion, if partitioned table. */ return rename_constraint_internal(relid, typid, stmt->subname, stmt->newname, - stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */ + stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */ false, /* recursing? */ 0 /* expected inhcount */ ); @@ -2827,8 +2916,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt) CheckTableNotInUse(rel, "ALTER TABLE"); + /* Force inheritance recursion, if partitioned table */ ATController(stmt, - rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt), + rel, stmt->cmds, + rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE || + interpretInhOption(stmt->relation->inhOpt), lockmode); } @@ -3107,6 +3199,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); @@ -3424,6 +3521,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); @@ -3494,7 +3597,13 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode) { AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab); - if (tab->relkind == RELKIND_RELATION || + /* + * If the table is source table of ATTACH PARTITION command, following + * check is unnecessary. + */ + if (((tab->relkind == RELKIND_RELATION || + tab->relkind == RELKIND_PARTITIONED_TABLE) && + !tab->partition_quals) || tab->relkind == RELKIND_MATVIEW) AlterTableCreateToastTable(tab->relid, (Datum) 0, lockmode); } @@ -3743,6 +3852,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); @@ -3928,7 +4043,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode) * Test the current data within the table against new constraints * generated by ALTER TABLE commands, but don't rebuild data. */ - if (tab->constraints != NIL || tab->new_notnull) + if (tab->constraints != NIL || tab->new_notnull || tab->partition_quals) ATRewriteTable(tab, InvalidOid, lockmode); /* @@ -4008,6 +4123,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) CommandId mycid; BulkInsertState bistate; int hi_options; + List *partqualstate = NIL; /* * Open the relation(s). We have surely already locked the existing @@ -4072,6 +4188,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) } } + /* Build expression execution states for partition check quals */ + if (tab->partition_quals) + { + needscan = true; + partqualstate = (List *) + ExecPrepareExpr((Expr *) tab->partition_quals, + estate); + } + foreach(l, tab->newvals) { NewColumnValue *ex = lfirst(l); @@ -4261,6 +4386,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) } } + + if (partqualstate && !ExecQual(partqualstate, econtext, true)) + ereport(ERROR, + (errcode(ERRCODE_CHECK_VIOLATION), + errmsg("source table contains a row violating partition bound specification"))); + /* Write the tuple out to the new relation */ if (newrel) heap_insert(newrel, tuple, mycid, hi_options, bistate); @@ -4458,7 +4589,8 @@ ATSimpleRecursion(List **wqueue, Relation rel, */ if (recurse && (rel->rd_rel->relkind == RELKIND_RELATION || - rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)) + rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE || + rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)) { Oid relid = RelationGetRelid(rel); ListCell *child; @@ -4780,6 +4912,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, if (recursing) ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); + if (rel->rd_rel->relispartition && !recursing) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot add column to a partition"))); + attrdesc = heap_open(AttributeRelationId, RowExclusiveLock); /* @@ -5302,6 +5439,26 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode) list_free(indexoidlist); /* + * If rel is partition, throw error if we shouldn't be dropping the + * NOT NULL because it is present in the parent. + */ + if (rel->rd_rel->relispartition) + { + Oid parentId = get_partition_parent(RelationGetRelid(rel)); + Relation parent = heap_open(parentId, AccessShareLock); + TupleDesc tupDesc = RelationGetDescr(parent); + AttrNumber parent_attnum; + + parent_attnum = get_attnum(parentId, colName); + if (tupDesc->attrs[parent_attnum - 1]->attnotnull) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("column \"%s\" is marked NOT NULL in parent table", + colName))); + heap_close(parent, AccessShareLock); + } + + /* * Okay, actually perform the catalog change ... if needed */ if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull) @@ -5832,6 +5989,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName, if (recursing) ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); + if (rel->rd_rel->relispartition && !recursing) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot drop column from a partition"))); + /* * get the number of the attribute */ @@ -5924,9 +6086,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName, /* * If the child column has other definition sources, just * decrement its inheritance count; if not, recurse to delete - * it. + * it. If the child table is partition, remain in sync with + * the parent. */ - if (childatt->attinhcount == 1 && !childatt->attislocal) + if (childatt->attinhcount == 1 && + (!childatt->attislocal || childrel->rd_rel->relispartition)) { /* Time to delete this child column, too */ ATExecDropColumn(wqueue, childrel, colName, @@ -6315,8 +6479,10 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, /* * If adding a NO INHERIT constraint, no need to find our children. + * Remember that we discard is_no_inherit for partitioned tables. */ - if (constr->is_no_inherit) + if (constr->is_no_inherit && + rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) return address; /* @@ -7877,7 +8043,9 @@ ATExecDropConstraint(Relation rel, const char *constrName, /* * Propagate to children as appropriate. Unlike most other ALTER * routines, we have to do this one level of recursion at a time; we can't - * use find_all_inheritors to do it in one pass. + * use find_all_inheritors to do it in one pass. Note that if the parent + * is a partitioned table, we propagate to children (partitions) despite + * is_no_inherit_constraint. */ if (!is_no_inherit_constraint) children = find_inheritance_children(RelationGetRelid(rel), lockmode); @@ -7936,8 +8104,10 @@ ATExecDropConstraint(Relation rel, const char *constrName, /* * If the child constraint has other definition sources, just * decrement its inheritance count; if not, recurse to delete it. + * If the child table is partition, remain in sync with the parent. */ - if (con->coninhcount == 1 && !con->conislocal) + if (con->coninhcount == 1 && + (!con->conislocal || childrel->rd_rel->relispartition)) { /* Time to delete this child constraint, too */ ATExecDropConstraint(childrel, constrName, behavior, @@ -8009,6 +8179,11 @@ ATPrepAlterColumnType(List **wqueue, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot alter column type of typed table"))); + if (rel->rd_rel->relispartition && !recursing) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot alter column type of a partition"))); + /* lookup the attribute so we can check inheritance status */ tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName); if (!HeapTupleIsValid(tuple)) @@ -10176,6 +10351,11 @@ ATPrepAddInherit(Relation child_rel) (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot change inheritance of typed table"))); + if (child_rel->rd_rel->relispartition) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot change inheritance of a partition"))); + if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -10188,12 +10368,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,37 +10413,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) errmsg("cannot inherit from \"%s\"", parent->relname), errdetail("Table \"%s\" is partitioned.", 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); + /* Likewise for partitions */ + if (parent_rel->rd_rel->relispartition) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot inherit from a partition"))); /* * Prevent circularity by seeing if proposed parent inherits from child. @@ -10303,6 +10452,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); @@ -10317,16 +10529,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; } /* @@ -10377,7 +10581,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 @@ -10395,12 +10599,16 @@ 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; + if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + is_attach_partition = true; + for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++) { Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1]; @@ -10422,14 +10630,18 @@ 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(is_attach_partition + ? "source table \"%s\" has different type for column \"%s\"" + : "child table \"%s\" has different type for column \"%s\"", 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(is_attach_partition + ? "source table \"%s\" has different collation for column \"%s\"" + : "source table \"%s\" has different collation for column \"%s\"", RelationGetRelationName(child_rel), attributeName))); @@ -10440,8 +10652,10 @@ 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(is_attach_partition + ? "column \"%s\" in source table must be marked NOT NULL" + : "column \"%s\" in child table must be marked NOT NULL", + attributeName))); /* * OK, bump the child column's inheritance count. (If we fail @@ -10456,7 +10670,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel) { ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("child table is missing column \"%s\"", + errmsg(is_attach_partition + ? "source table is missing column \"%s\"" + : "child table is missing column \"%s\"", attributeName))); } } @@ -10470,7 +10686,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. @@ -10489,10 +10705,14 @@ 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); + if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + is_attach_partition = true; + /* Outer loop scans through the parent's constraint definitions */ ScanKeyInit(&parent_key, Anum_pg_constraint_conrelid, @@ -10539,7 +10759,9 @@ 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(is_attach_partition + ? "source table \"%s\" has different definition for check constraint \"%s\"" + : "child table \"%s\" has different definition for check constraint \"%s\"", RelationGetRelationName(child_rel), NameStr(parent_con->conname)))); @@ -10547,7 +10769,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(is_attach_partition + ? "constraint \"%s\" conflicts with non-inherited constraint on source table \"%s\"" + : "constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"", NameStr(child_con->conname), RelationGetRelationName(child_rel)))); @@ -10558,6 +10782,7 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel) child_copy = heap_copytuple(child_tuple); child_con = (Form_pg_constraint) GETSTRUCT(child_copy); child_con->coninhcount++; + simple_heap_update(catalog_relation, &child_copy->t_self, child_copy); CatalogUpdateIndexes(catalog_relation, child_copy); heap_freetuple(child_copy); @@ -10571,7 +10796,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel) if (!found) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("child table is missing constraint \"%s\"", + errmsg(is_attach_partition + ? "source table is missing constraint \"%s\"" + : "child table is missing constraint \"%s\"", NameStr(parent_con->conname)))); } @@ -10582,6 +10809,46 @@ 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; + + if (rel->rd_rel->relispartition) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot change inheritance of a 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. + */ + + /* 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. @@ -10595,13 +10862,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]; @@ -10610,19 +10875,10 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) constraintTuple; List *connames; bool found = false; - ObjectAddress address; + bool is_detach_partition = false; - /* - * 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. - */ + if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + is_detach_partition = true; /* * Find and destroy the pg_inherits entry linking the two, or error out if @@ -10632,7 +10888,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); @@ -10653,11 +10909,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 partition of relation \"%s\"", + RelationGetRelationName(child_rel), + RelationGetRelationName(parent_rel)))); + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("relation \"%s\" is not a parent of relation \"%s\"", + RelationGetRelationName(parent_rel), + RelationGetRelationName(child_rel)))); + } /* * Search through child columns looking for ones matching parent rel @@ -10666,7 +10931,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))) @@ -10728,7 +10993,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); @@ -10759,7 +11024,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) @@ -10771,30 +11036,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) } } - parent_oid = RelationGetRelid(parent_rel); - systable_endscan(scan); heap_close(catalogRelation, RowExclusiveLock); - drop_parent_dependency(RelationGetRelid(rel), + 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; } /* @@ -12431,3 +12686,272 @@ ComputePartitionAttrs(Relation rel, 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, + inheritsRel, + classRel; + SysScanDesc scan; + ScanKeyData key; + HeapTuple tuple, + newtuple; + Datum new_val[Natts_pg_class]; + bool isnull, + new_null[Natts_pg_class], + new_repl[Natts_pg_class]; + AttrNumber attno; + int natts; + TupleDesc tupleDesc; + ObjectAddress address; + + attachRel = heap_openrv(cmd->name, AccessExclusiveLock); + + /* + * 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->relispartition) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is already a partition", + RelationGetRelationName(attachRel)))); + + if (attachRel->rd_rel->reloftype) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot attach a typed table as partition"))); + + /* attachRel should not already be a inheritance child of some relation */ + inheritsRel = heap_open(InheritsRelationId, AccessShareLock); + ScanKeyInit(&key, + Anum_pg_inherits_inhrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(attachRel))); + scan = systable_beginscan(inheritsRel, InheritsRelidSeqnoIndexId, + true, NULL, 1, &key); + if (HeapTupleIsValid(systable_getnext(scan))) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot attach table that is a inheritance child as partition"))); + systable_endscan(scan); + heap_close(inheritsRel, 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 then child 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."))); + } + + /* + * Check that the new partition's bound is valid and does not overlap any + * of existing partitions of the parent - note that it does not return + * on error. + */ + check_new_partition_bound(RelationGetRelationName(attachRel), + RelationGetRelid(rel), + cmd->bound); + + /* Update pg_class tuple */ + classRel = heap_open(RelationRelationId, RowExclusiveLock); + tuple = SearchSysCacheCopy1(RELOID, + ObjectIdGetDatum(RelationGetRelid(attachRel))); + Assert(!((Form_pg_class) GETSTRUCT(tuple))->relispartition); + + (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound, + &isnull); + Assert(isnull); + + /* Fill in relpartbound value */ + memset(new_val, 0, sizeof(new_val)); + memset(new_null, false, sizeof(new_null)); + memset(new_repl, false, sizeof(new_repl)); + new_val[Anum_pg_class_relpartbound - 1] = CStringGetTextDatum(nodeToString(cmd->bound)); + new_null[Anum_pg_class_relpartbound - 1] = false; + new_repl[Anum_pg_class_relpartbound - 1] = true; + newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel), + new_val, new_null, new_repl); + /* Also set the flag */ + ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = true; + simple_heap_update(classRel, &newtuple->t_self, newtuple); + CatalogUpdateIndexes(classRel, newtuple); + heap_freetuple(newtuple); + heap_close(classRel, RowExclusiveLock); + + /* OK to create inheritance. Rest of the checks performed there */ + CreateInheritance(attachRel, rel); + + /* + * Set up to have the rows in table to be checked for violation of the + * partition bound spec in phase 3 scan. + */ + if (!cmd->skip_validate) + { + List *leaf_parts; + List *parent_quals; + ListCell *lc; + + /* Take an exclusive lock on the partitions to be checked */ + if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + leaf_parts = get_leaf_partition_oids(RelationGetRelid(attachRel), + AccessExclusiveLock); + else + leaf_parts = list_make1_oid(RelationGetRelid(attachRel)); + + parent_quals = RelationGetPartitionQual(rel, true); + foreach(lc, leaf_parts) + { + AlteredTableInfo *tab; + Oid leaf_relid = lfirst_oid(lc); + Relation leaf_rel; + List *my_quals; + + /* Lock already taken */ + if (leaf_relid != RelationGetRelid(attachRel)) + leaf_rel = heap_open(leaf_relid, NoLock); + else + leaf_rel = attachRel; + + /* Grab a work queue entry */ + tab = ATGetQueueEntry(wqueue, leaf_rel); + + /* + * We've got to check only the bound condition specified in this + * command and the parent's bound condition if it happens to be a + * partition. + */ + my_quals = get_qual_from_partbound(leaf_rel, rel, cmd->bound); + tab->partition_quals = list_concat(parent_quals, my_quals); + + /* keep our lock until commit */ + if (leaf_rel != attachRel) + heap_close(leaf_rel, NoLock); + } + } + + /* + * Invalidate the relcache so that this partition is now included + * in our partition descriptor. + */ + CacheInvalidateRelcache(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, + classRel; + HeapTuple tuple, + newtuple; + Datum new_val[Natts_pg_class]; + bool isnull, + new_null[Natts_pg_class], + new_repl[Natts_pg_class]; + ObjectAddress address; + + partRel = heap_openrv(name, AccessShareLock); + + /* All inheritance related checks are performed within the function */ + RemoveInheritance(partRel, rel); + + /* Update pg_class tuple */ + classRel = heap_open(RelationRelationId, RowExclusiveLock); + tuple = SearchSysCacheCopy1(RELOID, + ObjectIdGetDatum(RelationGetRelid(partRel))); + Assert(((Form_pg_class) GETSTRUCT(tuple))->relispartition); + + (void) SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound, + &isnull); + Assert(!isnull); + + /* Clear relpartbound and reset relispartition */ + memset(new_val, 0, sizeof(new_val)); + memset(new_null, false, sizeof(new_null)); + memset(new_repl, false, sizeof(new_repl)); + new_val[Anum_pg_class_relpartbound - 1] = (Datum) 0; + new_null[Anum_pg_class_relpartbound - 1] = true; + new_repl[Anum_pg_class_relpartbound - 1] = true; + newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel), + new_val, new_null, new_repl); + + ((Form_pg_class) GETSTRUCT(newtuple))->relispartition = false; + simple_heap_update(classRel, &newtuple->t_self, newtuple); + CatalogUpdateIndexes(classRel, newtuple); + heap_freetuple(newtuple); + heap_close(classRel, RowExclusiveLock); + + /* + * Invalidate the relcache so that the partition is no longer included + * in our partition descriptor. + */ + CacheInvalidateRelcache(rel); + + ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel)); + + /* keep our lock until commit */ + heap_close(partRel, NoLock); + + return address; +} diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 37a60b6..3e800fe 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2621,6 +2621,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); @@ -3017,6 +3018,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); @@ -4200,6 +4202,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 * **************************************************************** @@ -5120,6 +5159,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; /* * MISCELLANEOUS NODES diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index a321ac1..7dfe8c0 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1167,6 +1167,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); @@ -2375,6 +2376,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); @@ -2655,6 +2657,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 */ @@ -3414,6 +3447,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 ab75085..f9972c1 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2406,6 +2406,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); @@ -2587,6 +2588,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); @@ -3302,6 +3304,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' @@ -3891,6 +3912,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/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 894a48f..3dfdc37 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -2265,6 +2265,35 @@ _readExtensibleNode(void) } /* + * _readPartitionListSpec + */ +static PartitionListSpec * +_readPartitionListSpec(void) +{ + READ_LOCALS(PartitionListSpec); + + READ_NODE_FIELD(values); + + READ_DONE(); +} + +/* + * _readPartitionRangeSpec + */ +static PartitionRangeSpec * +_readPartitionRangeSpec(void) +{ + READ_LOCALS(PartitionRangeSpec); + + READ_BOOL_FIELD(lowerinc); + READ_NODE_FIELD(lower); + READ_BOOL_FIELD(upperinc); + READ_NODE_FIELD(upper); + + READ_DONE(); +} + +/* * parseNodeString * * Given a character string representing a node tree, parseNodeString creates @@ -2496,6 +2525,10 @@ parseNodeString(void) return_value = _readAlternativeSubPlan(); else if (MATCH("EXTENSIBLENODE", 14)) return_value = _readExtensibleNode(); + else if (MATCH("PARTITIONLISTVALUES", 19)) + return_value = _readPartitionListSpec(); + else if (MATCH("PARTITIONRANGE", 14)) + return_value = _readPartitionRangeSpec(); else { elog(ERROR, "badly formatted node string \"%.32s\"...", token); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index a95a65a..50657af 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -231,6 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); VariableSetStmt *vsetstmt; PartitionElem *partelem; PartitionBy *partby; + PartitionRangeSpec *partrange; } %type stmt schema_stmt @@ -546,6 +547,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. @@ -571,7 +580,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 @@ -587,7 +596,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 @@ -601,7 +611,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; @@ -4544,6 +4701,48 @@ CreateForeignTableStmt: n->options = $14; $$ = (Node *) n; } + | CREATE FOREIGN TABLE qualified_name + PARTITION OF qualified_name OptPartitionElementList ForValues + SERVER name create_generic_options + { + CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt); + $4->relpersistence = RELPERSISTENCE_PERMANENT; + n->base.relation = $4; + n->base.inhRelations = list_make1($7); + n->base.tableElts = $8; + n->base.partbound = (Node *) $9; + n->base.ofTypename = NULL; + n->base.constraints = NIL; + n->base.options = NIL; + n->base.oncommit = ONCOMMIT_NOOP; + n->base.tablespacename = NULL; + n->base.if_not_exists = false; + /* FDW-specific data */ + n->servername = $11; + n->options = $12; + $$ = (Node *) n; + } + | CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name + PARTITION OF qualified_name OptPartitionElementList ForValues + SERVER name create_generic_options + { + CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt); + $7->relpersistence = RELPERSISTENCE_PERMANENT; + n->base.relation = $7; + n->base.inhRelations = list_make1($10); + n->base.tableElts = $11; + n->base.partbound = (Node *) $12; + n->base.ofTypename = NULL; + n->base.constraints = NIL; + n->base.options = NIL; + n->base.oncommit = ONCOMMIT_NOOP; + n->base.tablespacename = NULL; + n->base.if_not_exists = true; + /* FDW-specific data */ + n->servername = $14; + n->options = $15; + $$ = (Node *) n; + } ; /***************************************************************************** @@ -11149,6 +11348,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; @@ -13748,6 +13948,7 @@ unreserved_keyword: | ASSERTION | ASSIGNMENT | AT + | ATTACH | ATTRIBUTE | BACKWARD | BEFORE @@ -13794,6 +13995,7 @@ unreserved_keyword: | DELIMITER | DELIMITERS | DEPENDS + | DETACH | DICTIONARY | DISABLE_P | DISCARD @@ -13836,6 +14038,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 3e8d457..37eb9d3 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -508,7 +508,10 @@ 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 @@ -869,6 +872,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 71c0c1c..6d4645c 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -364,6 +364,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 */ @@ -1732,6 +1752,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: @@ -3364,6 +3385,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 85d67c1..9d0438c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -33,6 +33,7 @@ #include "catalog/heap.h" #include "catalog/index.h" #include "catalog/namespace.h" +#include "catalog/partition.h" #include "catalog/pg_am.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" @@ -47,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" @@ -88,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 */ @@ -130,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); /* @@ -231,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 @@ -249,11 +258,14 @@ 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); - if (stmt->inhRelations) + if (stmt->inhRelations && !stmt->partbound) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("cannot create partitioned table as inheritance child"))); @@ -360,6 +372,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); @@ -899,6 +912,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; @@ -1118,6 +1132,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; @@ -2587,6 +2602,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, cxt.alist = NIL; cxt.pkey = NULL; cxt.ispartitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE; + cxt.partbound = NULL; /* * The only subtypes that currently require parse transformation handling @@ -2675,6 +2691,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; @@ -3039,3 +3071,300 @@ 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; + RangeVar *part = cxt->relation; + RangeVar *partof = linitial(cxt->inhRelations); + Relation parentRel; + + parentRel = heap_openrv(partof, AccessShareLock); + + /* Check if the target table is partitioned at all */ + if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("\"%s\" is not partitioned", + RelationGetRelationName(parentRel)))); + Assert(RelationGetPartitionKey(parentRel) != NULL); + + /* Permanent rels cannot be partitions of temporary ones */ + if (part->relpersistence != RELPERSISTENCE_TEMP && + parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot create as partition of temporary relation \"%s\"", + partof->relname))); + + /* 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_TABLE) + 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; + PartitionKey key = RelationGetPartitionKey(parent); + char strategy = get_partition_key_strategy(key); + int partnatts = get_partition_key_natts(key); + PartitionListSpec *list, *result_list; + PartitionRangeSpec *range, *result_range; + + switch (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, + get_partition_col_typid(key, 0), + get_partition_col_typmod(key, 0), + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (value == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("specified value cannot be cast to type \"%s\"" + " of key column \"%s\"", + format_type_be(get_partition_col_typid(key, 0)), + get_partition_col_name(key, 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 && list_length(range->lower) > 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 (range->lower && list_length(range->lower) < 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))))); + + if (range->upper && list_length(range->upper) > 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 (range->upper && list_length(range->upper) < 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))))); + + if (range->lower) + { + 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, + get_partition_col_typid(key, i), + get_partition_col_typmod(key, i), + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (value == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("specified value cannot be cast to type" + " \"%s\" of key column \"%s\"", + format_type_be(get_partition_col_typid(key, i)), + get_partition_col_name(key, 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) + { + 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, + get_partition_col_typid(key, i), + get_partition_col_typmod(key, i), + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (value == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("specified value cannot be cast to type" + " \"%s\" of key column \"%s\"", + format_type_be(get_partition_col_typid(key, i)), + get_partition_col_name(key, 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/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 8cbd6e7..cd4f7f4 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -279,6 +279,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid, StrategyNumber numSupport); static void RelationCacheInitFileRemoveInDir(const char *tblspcpath); static void unlink_initfile(const char *initfilename); +static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1, + PartitionDesc pdesc2); /* @@ -925,6 +927,59 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2) } /* + * equalPartitionDescs + * Compare two partition descriptors for logical equality + */ +static bool +equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1, + PartitionDesc pdesc2) +{ + int i; + + if (pdesc1 != NULL) + { + if (pdesc2 == NULL) + return false; + if (pdesc1->nparts != pdesc2->nparts) + return false; + + Assert(key != NULL || pdesc1->nparts == 0); + + /* + * Same oids? No mind order - in the list case, it matches the order + * in which partition oids are returned by a pg_inherits scan, whereas + * in the range case, they are in order of ranges of individual + * partitions. XXX - is the former unsafe? + */ + for (i = 0; i < pdesc1->nparts; i++) + { + if (pdesc1->oids[i] != pdesc2->oids[i]) + return false; + } + + /* + * Now compare partition bound collections. The iteration logic is + * local to partition.c. + */ + if (pdesc1->bounds != NULL) + { + if (pdesc2->bounds == NULL) + return false; + + if (!partition_bounds_equal(key, pdesc1->bounds, pdesc2->bounds, + pdesc1->nparts)) + return false; + } + else if (pdesc2->bounds != NULL) + return false; + } + else if (pdesc2 != NULL) + return false; + + return true; +} + +/* * RelationBuildDesc * * Build a relation descriptor. The caller must hold at least @@ -1052,13 +1107,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) relation->rd_fkeylist = NIL; relation->rd_fkeyvalid = false; - /* if it's a partitioned table, initialize key info */ + /* if a partitioned table, initialize key and partition descriptor info */ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { RelationBuildPartitionKey(relation); + RelationBuildPartitionDesc(relation); + } else { relation->rd_partkeycxt = NULL; relation->rd_partkey = NULL; + relation->rd_partdesc = NULL; + relation->rd_pdcxt = NULL; } /* @@ -2055,6 +2115,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc) MemoryContextDelete(relation->rd_rsdesc->rscxt); if (relation->rd_partkeycxt) MemoryContextDelete(relation->rd_partkeycxt); + if (relation->rd_pdcxt) + MemoryContextDelete(relation->rd_pdcxt); + if (relation->rd_partcheck) + pfree(relation->rd_partcheck); if (relation->rd_fdwroutine) pfree(relation->rd_fdwroutine); pfree(relation); @@ -2203,11 +2267,12 @@ RelationClearRelation(Relation relation, bool rebuild) * * When rebuilding an open relcache entry, we must preserve ref count, * rd_createSubid/rd_newRelfilenodeSubid, and rd_toastoid state. Also - * attempt to preserve the pg_class entry (rd_rel), tupledesc, and - * rewrite-rule substructures in place, because various places assume - * that these structures won't move while they are working with an - * open relcache entry. (Note: the refcount mechanism for tupledescs - * might someday allow us to remove this hack for the tupledesc.) + * attempt to preserve the pg_class entry (rd_rel), tupledesc, + * rewrite-rule, and partition descriptor substructures in place, + * because various places assume that these structures won't move while + * they are working with an open relcache entry. (Note: the refcount + * mechanism for tupledescs might someday allow us to remove this hack + * for the tupledesc.) * * Note that this process does not touch CurrentResourceOwner; which * is good because whatever ref counts the entry may have do not @@ -2218,6 +2283,7 @@ RelationClearRelation(Relation relation, bool rebuild) bool keep_tupdesc; bool keep_rules; bool keep_policies; + bool keep_partdesc; /* Build temporary entry, but don't link it into hashtable */ newrel = RelationBuildDesc(save_relid, false); @@ -2248,6 +2314,9 @@ RelationClearRelation(Relation relation, bool rebuild) keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att); keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules); keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc); + keep_partdesc = equalPartitionDescs(relation->rd_partkey, + relation->rd_partdesc, + newrel->rd_partdesc); /* * Perform swapping of the relcache entry contents. Within this @@ -2303,6 +2372,13 @@ RelationClearRelation(Relation relation, bool rebuild) /* pgstat_info must be preserved */ SWAPFIELD(struct PgStat_TableStatus *, pgstat_info); + /* preserve old partdesc if no logical change */ + if (keep_partdesc) + { + SWAPFIELD(PartitionDesc, rd_partdesc); + SWAPFIELD(MemoryContext, rd_pdcxt); + } + #undef SWAPFIELD /* And now we can throw away the temporary entry */ @@ -3529,6 +3605,20 @@ RelationCacheInitializePhase3(void) restart = true; } + /* + * Reload partition key and descriptor for a partitioned table. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + RelationBuildPartitionKey(relation); + Assert(relation->rd_partkey != NULL); + + RelationBuildPartitionDesc(relation); + Assert(relation->rd_partdesc != NULL); + + restart = true; + } + /* Release hold on the relation */ RelationDecrementReferenceCount(relation); @@ -5052,6 +5142,8 @@ load_relcache_init_file(bool shared) rel->rd_rsdesc = NULL; rel->rd_partkeycxt = NULL; rel->rd_partkey = NULL; + rel->rd_partdesc = NULL; + rel->rd_partcheck = NIL; rel->rd_indexprs = NIL; rel->rd_indpred = NIL; rel->rd_exclops = NULL; diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index b80d8d8..fcda8f0 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -68,6 +68,7 @@ extern Oid heap_create_with_catalog(const char *relname, int oidinhcount, OnCommitAction oncommit, Datum reloptions, + Datum relpartbound, bool use_user_acl, bool allow_system_table_mods, bool is_internal, @@ -93,7 +94,8 @@ extern void InsertPgClassTuple(Relation pg_class_desc, Relation new_rel_desc, Oid new_rel_oid, Datum relacl, - Datum reloptions); + Datum reloptions, + Datum relpartbound); extern List *AddRelationNewConstraints(Relation rel, List *newColDefaults, diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h index 9c266c1..f515d4d 100644 --- a/src/include/catalog/partition.h +++ b/src/include/catalog/partition.h @@ -14,10 +14,38 @@ #define PARTITION_H #include "fmgr.h" +#include "parser/parse_node.h" #include "utils/relcache.h" typedef struct PartitionKeyData *PartitionKey; +/* + * BoundCollection encapsulates a set of partition bounds of either physical + * or logical relations. It is associated with a partitioned relation of + * which the aforementioned relations are partitions. + * + * The internal structure is opaque outside partition.c. Users must pass an + * instance of this struct *and* an instance of PartitionKey to perform any + * operations with its contents. + */ +typedef struct BoundCollectionData *BoundCollection; + +/* + * Information about partitions of a partitioned table. + * + * Note: Order of elements in the oids array is arbitrary when the table + * is list partitioned. Whereas in case of a range partitioned table, they + * are ordered to match the ascending order of partition ranges. + */ +typedef struct PartitionDescData +{ + int nparts; /* Number of partitions */ + Oid *oids; /* OIDs of partitions */ + BoundCollection bounds; /* collection of list or range bounds */ +} PartitionDescData; + +typedef struct PartitionDescData *PartitionDesc; + /* relcache support for partition key information */ extern void RelationBuildPartitionKey(Relation relation); @@ -32,4 +60,15 @@ extern Oid get_partition_col_typid(PartitionKey key, int col); extern int32 get_partition_col_typmod(PartitionKey key, int col); extern char *get_partition_col_name(PartitionKey key, int col); +/* relcache support functions for partition descriptor */ +extern void RelationBuildPartitionDesc(Relation relation); +extern bool partition_bounds_equal(PartitionKey key, + BoundCollection p1, BoundCollection p2, int n); + +/* For commands/tablecmds.c's perusal */ +extern void check_new_partition_bound(char *relname, Oid parentId, Node *bound); +extern Oid get_partition_parent(Oid relid); +extern List *get_leaf_partition_oids(Oid relid, int lockmode); +extern List *get_qual_from_partbound(Relation rel, Relation parent, Node *bound); +extern List *RelationGetPartitionQual(Relation rel, bool recurse); #endif /* PARTITION_H */ diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index ba0f745..62a9377 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -70,6 +70,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO * not */ bool relispopulated; /* matview currently holds query results */ char relreplident; /* see REPLICA_IDENTITY_xxx constants */ + bool relispartition; /* is relation a partition? */ TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */ TransactionId relminmxid; /* all multixacts in this rel are >= this. * this is really a MultiXactId */ @@ -78,6 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO /* NOTE: These fields are not present in a relcache entry's rd_rel field. */ aclitem relacl[1]; /* access permissions */ text reloptions[1]; /* access-method-specific options */ + pg_node_tree relpartbound; /* partition bound node tree */ #endif } FormData_pg_class; @@ -97,7 +99,7 @@ typedef FormData_pg_class *Form_pg_class; * ---------------- */ -#define Natts_pg_class 31 +#define Natts_pg_class 33 #define Anum_pg_class_relname 1 #define Anum_pg_class_relnamespace 2 #define Anum_pg_class_reltype 3 @@ -125,10 +127,12 @@ typedef FormData_pg_class *Form_pg_class; #define Anum_pg_class_relforcerowsecurity 25 #define Anum_pg_class_relispopulated 26 #define Anum_pg_class_relreplident 27 -#define Anum_pg_class_relfrozenxid 28 -#define Anum_pg_class_relminmxid 29 -#define Anum_pg_class_relacl 30 -#define Anum_pg_class_reloptions 31 +#define Anum_pg_class_relispartition 28 +#define Anum_pg_class_relfrozenxid 29 +#define Anum_pg_class_relminmxid 30 +#define Anum_pg_class_relacl 31 +#define Anum_pg_class_reloptions 32 +#define Anum_pg_class_relpartbound 33 /* ---------------- * initial contents of pg_class @@ -143,13 +147,13 @@ typedef FormData_pg_class *Form_pg_class; * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId; * similarly, "1" in relminmxid stands for FirstMultiXactId */ -DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ )); +DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_)); DESCR(""); -DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ )); +DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n f 3 1 _null_ _null_ _null_)); DESCR(""); -DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ )); +DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_)); DESCR(""); -DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ )); +DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 33 0 t f f f f f f t n f 3 1 _null_ _null_ _null_)); DESCR(""); diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index c4abdf7..bb62112 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -406,6 +406,7 @@ typedef enum NodeTag T_AlterPolicyStmt, T_CreateTransformStmt, T_CreateAmStmt, + T_PartitionCmd, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) @@ -455,6 +456,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 afaa4d3..b925b89 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -592,6 +592,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) */ @@ -733,6 +734,40 @@ typedef struct PartitionBy int location; /* token location, or -1 if unknown */ } PartitionBy; +/* + * PartitionListSpec - a list partition bound + */ +typedef struct PartitionListSpec +{ + NodeTag type; + List *values; + int location; +} PartitionListSpec; + +/* + * PartitionRangeSpec - a 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 ****************************************************************************/ @@ -1560,7 +1595,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 @@ -1785,7 +1822,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; diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 07de59f..53c7612 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -96,6 +96,9 @@ typedef struct RelationData MemoryContext rd_partkeycxt; /* private memory cxt for the below */ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */ + MemoryContext rd_pdcxt; /* private context for partdesc */ + struct PartitionDescData *rd_partdesc; /* partitions, or NULL */ + List *rd_partcheck; /* partition CHECK quals */ /* data managed by RelationGetIndexList: */ List *rd_indexlist; /* list of OIDs of indexes on relation */ @@ -541,6 +544,12 @@ typedef struct ViewOptions */ #define RelationGetPartitionKey(relation) ((relation)->rd_partkey) +/* + * RelationGetPartitionDesc + * Returns partition descriptor for a relation. + */ +#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc) + /* routines in utils/cache/relcache.c */ extern void RelationIncrementReferenceCount(Relation rel); extern void RelationDecrementReferenceCount(Relation rel); diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 140026c..6fe7623 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -2960,3 +2960,222 @@ ERROR: cannot change inheritance of partitioned table ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT; ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned" DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent; +-- ATTACH PARTITION +-- check target table partitioned +CREATE TABLE unparted ( + a int +); +CREATE TABLE fail_part (like unparted); +ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a'); +ERROR: "unparted" is not partitioned +DROP TABLE unparted, fail_part; +-- check partition bounds compatible +CREATE TABLE list_parted ( + a int, + b char(2) NOT NULL COLLATE "en_US", + CONSTRAINT check_a CHECK (a > 0) +) PARTITION BY LIST (a); +CREATE TABLE fail_part (LIKE list_parted); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10); +ERROR: invalid bound specification for a list partition +DROP TABLE fail_part; +-- check the table being attached exists +ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1); +ERROR: relation "nonexistant" does not exist +-- check ownership of the source table +CREATE ROLE regress_test_me; +CREATE ROLE regress_test_not_me; +CREATE TABLE not_owned_by_me (LIKE list_parted); +ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me; +SET SESSION AUTHORIZATION regress_test_me; +CREATE TABLE owned_by_me ( + a int +) PARTITION BY LIST (a); +ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1); +ERROR: must be owner of relation not_owned_by_me +RESET SESSION AUTHORIZATION; +DROP TABLE owned_by_me, not_owned_by_me; +DROP ROLE regress_test_not_me; +DROP ROLE regress_test_me; +-- check the table being attached is not inheritance child of some relation +CREATE TABLE parent (LIKE list_parted); +CREATE TABLE fail_part () INHERITS (parent); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: cannot attach table that is a inheritance child as partition +DROP TABLE parent CASCADE; +NOTICE: drop cascades to table fail_part +-- check the table being attached is not a typed table +CREATE TYPE mytype AS (a int); +CREATE TABLE fail_part OF mytype; +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: cannot attach a typed table as partition +DROP TYPE mytype CASCADE; +NOTICE: drop cascades to table fail_part +-- check the existence (or non-existence) of oid column +ALTER TABLE list_parted SET WITH OIDS; +CREATE TABLE fail_part (a int); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: cannot attach table "fail_part" without OIDs as partition of table "list_parted" with OIDs +ALTER TABLE list_parted SET WITHOUT OIDS; +ALTER TABLE fail_part SET WITH OIDS; +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: cannot attach table "fail_part" with OIDs as partition of table "list_parted" without OIDs +DROP TABLE fail_part; +-- check the table being attached does not have columns not in the parent +CREATE TABLE fail_part (like list_parted, c int); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: table "fail_part" contains column "c" not found in parent "list_parted" +DETAIL: Table being attached should contain only the columns present in parent. +DROP TABLE fail_part; +-- check the table being attached has all columns of the parent +CREATE TABLE fail_part (a int); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: source table is missing column "b" +DROP TABLE fail_part; +-- check the columns of the table being attached match in type, collation and NOT NULL status +CREATE TABLE fail_part ( + a int, + b int +); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: source table "fail_part" has different type for column "b" +ALTER TABLE fail_part ALTER b TYPE char (3); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: source table "fail_part" has different type for column "b" +ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA"; +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: source table "fail_part" has different collation for column "b" +DROP TABLE fail_part; +-- check the table being attached all constraints of the parent +CREATE TABLE fail_part ( + a int, + b char(2) NOT NULL COLLATE "en_US" +); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: source table is missing constraint "check_a" +-- check the constraint of table being attached matches in definition with parent's constraint +ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: source table "fail_part" has different definition for check constraint "check_a" +DROP TABLE fail_part; +-- check attributes and constraints after partition is attached +CREATE TABLE part_1 ( + a int, + b char(2) NOT NULL COLLATE "en_US", + CONSTRAINT check_a CHECK (a > 0) NO INHERIT +); +-- fail to attach a partition with a NO INHERIT constraint +ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1); +ERROR: constraint "check_a" conflicts with non-inherited constraint on source table "part_1" +ALTER TABLE part_1 DROP CONSTRAINT check_a; +ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0); +ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1); +SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0; + attislocal | attinhcount +------------+------------- + t | 1 + t | 1 +(2 rows) + +SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a'; + conislocal | coninhcount +------------+------------- + t | 1 +(1 row) + +-- check the new partition does not overlap with existing partition +CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: partition "fail_part" would overlap partition "part_1" +-- check the new partition does not contain values outside specified bound +INSERT INTO fail_part VALUES (3, 'a'); +-- fail +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2); +ERROR: source table contains a row violating partition bound specification +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null); +ERROR: source table contains a row violating partition bound specification +DELETE FROM fail_part; +INSERT INTO fail_part VALUES (null, 'a'); +-- fail too because null is not specified in the accepted values +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2); +ERROR: source table contains a row violating partition bound specification +-- the check will be skipped, if NO VALIDATE is specified +ALTER TABLE fail_part RENAME TO part_2; +ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE; +-- same check as above but now the table being attached is itself partitioned +CREATE TABLE part_3 ( + a int, + b char(2) NOT NULL COLLATE "en_US", + CONSTRAINT check_a CHECK (a > 0) +) PARTITION BY LIST (b); +CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a'); +INSERT INTO part_3_a (a, b) VALUES (4, 'a'); +-- fail +ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3); +ERROR: source table contains a row violating partition bound specification +-- delete the faulting row and all will be ok +DELETE FROM part_3_a WHERE a NOT IN (3); +INSERT INTO part_3_a (a, b) VALUES (3, 'a'); +ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3); +-- check the table being attached is not already a partition +ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1); +ERROR: "part_2" is already a partition +-- DETACH PARTITION +-- check the partition being detached exists at all +ALTER TABLE list_parted DETACH PARTITION part_4; +ERROR: relation "part_4" does not exist +-- check the partition being detached is a partition (of the parent) +CREATE TABLE not_a_part (a int); +ALTER TABLE list_parted DETACH PARTITION not_a_part; +ERROR: relation "not_a_part" is not a partition of relation "list_parted" +-- check that attinhcount and coninhcount dropped to 0 after detached +ALTER TABLE list_parted DETACH PARTITION part_3; +SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0; + attinhcount +------------- + 0 + 0 +(2 rows) + +SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a'; + coninhcount +------------- + 0 +(1 row) + +-- Miscellaneous ALTER TABLE special behaviors for partitions +-- cannot add/drop a column to/from a partition or rename it +ALTER TABLE part_1 ADD COLUMN c text; +ERROR: cannot add column to a partition +ALTER TABLE part_1 DROP COLUMN b; +ERROR: cannot drop column from a partition +ALTER TABLE part_1 RENAME COLUMN b to c; +ERROR: cannot rename column of a partition +-- cannot alter type of a column of a partition +ALTER TABLE part_1 ALTER COLUMN b TYPE text; +ERROR: cannot alter column type of a partition +-- cannot let a partition participate in regular inheritance +CREATE TABLE inh_test () INHERITS (part_1); +ERROR: cannot inherit from partition "part_1" +CREATE TABLE inh_test (LIKE part_1); +ALTER TABLE inh_test INHERIT part_1; +ERROR: cannot inherit from a partition +ALTER TABLE part_1 INHERIT inh_test; +ERROR: cannot change inheritance of a partition +-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set +ALTER TABLE part_1 ALTER b DROP NOT NULL; +ERROR: column "b" is marked NOT NULL in parent table +-- cannot drop or alter type of partition key columns of lower levels +-- for example, part_3 is partitioned on b; +ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3); +ALTER TABLE list_parted DROP COLUMN b; +ERROR: cannot drop column named in partition key +ALTER TABLE list_parted ALTER COLUMN b TYPE text; +ERROR: cannot alter type of column named in partition key +-- cleanup +DROP TABLE list_parted CASCADE; +NOTICE: drop cascades to 4 other objects +DETAIL: drop cascades to table part_1 +drop cascades to table part_2 +drop cascades to table part_3 +drop cascades to table part_3_a diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out index 2fec847..2b12276 100644 --- a/src/test/regress/expected/create_table.out +++ b/src/test/regress/expected/create_table.out @@ -434,3 +434,191 @@ Table "public.describe_list_key" Partition Key: LIST (a) DROP TABLE describe_range_key, describe_list_key; +-- +-- CREATE TABLE PARTITION OF +-- +-- check partition bound syntax +CREATE TABLE list_parted ( + a int +) PARTITION BY LIST (a); +-- syntax allows only string literal, numeric literal and null to be specified +-- for a partition bound value +CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1'); +CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (2); +CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null); +CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1'); +ERROR: syntax error at or near "int" +LINE 1: ...fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1'); + ^ +CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int); +ERROR: syntax error at or near "::" +LINE 1: ...ail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int); + ^ +-- syntax does not allow empty list of values for list partitions +CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (); +ERROR: syntax error at or near ")" +LINE 1: ... TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (); + ^ +-- trying to specify range for list partitioned table +CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2); +ERROR: invalid bound specification for a list partition +CREATE TABLE range_parted ( + a date +) PARTITION BY RANGE (a); +-- trying to specify list for range partitioned table +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a'); +ERROR: invalid bound specification for a range partition +-- both start and end bounds of a range partition cannot be UNBOUNDED +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED; +ERROR: both START and END cannot be UNBOUNDED +LINE 1: CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES... + ^ +-- each of start and end bounds must have same number of values as there +-- are columns in the partition key +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1); +ERROR: START has more values specified than number of columns in the partition key +LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z... + ^ +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1); +ERROR: END has more values specified than number of columns in the partition key +LINE 1: ...RTITION OF range_parted FOR VALUES START ('a') END ('z', 1); + ^ +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z'); +ERROR: START has more values specified than number of columns in the partition key +LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z... + ^ +-- specified literal can't be cast to the partition column data type +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b'); +ERROR: invalid input syntax for type date: "a" +LINE 1: ...rpart PARTITION OF range_parted FOR VALUES START ('a') END (... + ^ +-- check if compatible with the specified parent +-- cannot create as partition of a non-partitioned table +CREATE TABLE unparted ( + a int +); +CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a'); +ERROR: "unparted" is not partitioned +DROP TABLE unparted; +-- cannot create a permanent rel as partition of a temp rel +CREATE TEMP TABLE temp_parted ( + a int +) PARTITION BY LIST (a); +CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a'); +ERROR: cannot create as partition of temporary relation "temp_parted" +DROP TABLE temp_parted; +-- cannot create a table with oids as partition of table without oids +CREATE TABLE no_oids_parted ( + a int, + b int +) PARTITION BY RANGE (a, b) WITHOUT OIDS; +CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS; +ERROR: cannot create table with OIDs as partition of table without OIDs +DROP TABLE no_oids_parted; +-- check for partition bound overlap and other invalid specifications +CREATE TABLE list_parted2 ( + a varchar +) PARTITION BY LIST (a); +CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z'); +CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b'); +CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null); +ERROR: partition "fail_nulls_part" would overlap partition "nulls_z_part" +CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c'); +ERROR: partition "fail_bc_part" would overlap partition "ab_part" +CREATE TABLE range_parted2 ( + a int +) PARTITION BY RANGE (a); +CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0); +ERROR: cannot create range partition with empty range +CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1); +ERROR: cannot create range partition with empty range +CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE; +CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1); +CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2); +ERROR: partition "fail_unb_2" would overlap partition "part_unb_1" +CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE; +CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15); +ERROR: partition "fail_part_5_15" would overlap partition "part_2_10_inc" +CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20); +ERROR: partition "fail_part_10_20" would overlap partition "part_2_10_inc" +-- check for multi-column range partition key where tuple comparison occurs +CREATE TABLE range_parted3 ( + a varchar, + b int +) PARTITION BY RANGE (a, b); +CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10); +CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20); +CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25); +ERROR: partition "fail_part_a_15_a_25" would overlap partition "part_a_10_a_20" +CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10); +CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20); +CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15); +ERROR: partition "fail_part_b_5_b_15" would overlap partition "part_b_1_b_10" +-- check schema propagation from parent +CREATE TABLE parted ( + a text, + b int NOT NULL DEFAULT 1, + CONSTRAINT check_b CHECK (b > 0) +) PARTITION BY LIST (a); +CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a'); +-- the above command creates inheritance +SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass; + count +------- + 1 +(1 row) + +-- specify a column option overriding parent's and a table constraint that will be merged +CREATE TABLE part_b PARTITION OF parted ( + b WITH OPTIONS DEFAULT 10, + CONSTRAINT check_b CHECK (b > 0) +) FOR VALUES IN ('b'); +NOTICE: merging constraint "check_b" with inherited definition +SELECT conislocal FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b'; + conislocal +------------ + t +(1 row) + +-- cannot add NO INHERIT constraint to a partition +CREATE TABLE fail_part_no_inh_con PARTITION OF parted ( + CONSTRAINT chk_b CHECK (b > 0) NO INHERIT +) FOR VALUES IN (null); +ERROR: cannot add NO INHERIT constraint to table "fail_part_no_inh_con" +DETAIL: Table "fail_part_no_inh_con" is a partition. +-- specify PARTITION BY for a partition +CREATE TABLE fail_col_part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c); +ERROR: column "c" named in partition key does not exist +CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (b); +-- create a partition of partition +CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10); +-- partition cannot be dropped directly +DROP TABLE part_a; +ERROR: "part_a" is a partition of "parted" +HINT: Use ALTER TABLE DETACH PARTITION to be able to drop it. +-- need to specify CASCADE to drop partitions along with the parent +DROP TABLE parted; +ERROR: cannot drop table parted because other objects depend on it +DETAIL: table part_a depends on table parted +table part_b depends on table parted +table part_c depends on table parted +table part_c_1_10 depends on table part_c +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE; +NOTICE: drop cascades to 16 other objects +DETAIL: drop cascades to table part_a_1_a_10 +drop cascades to table part_a_10_a_20 +drop cascades to table part_b_1_b_10 +drop cascades to table part_b_10_b_20 +drop cascades to table part_1_1 +drop cascades to table part_unb_1 +drop cascades to table part_2_10_inc +drop cascades to table nulls_z_part +drop cascades to table ab_part +drop cascades to table lpart1 +drop cascades to table lpart2 +drop cascades to table lpart3 +drop cascades to table part_a +drop cascades to table part_b +drop cascades to table part_c +drop cascades to table part_c_1_10 diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index 49fbab6..8c15ba2 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -1876,3 +1876,195 @@ ALTER TABLE no_inh_child INHERIT inh_parent; ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT; DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent; + +-- ATTACH PARTITION + +-- check target table partitioned +CREATE TABLE unparted ( + a int +); +CREATE TABLE fail_part (like unparted); +ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a'); +DROP TABLE unparted, fail_part; + +-- check partition bounds compatible +CREATE TABLE list_parted ( + a int, + b char(2) NOT NULL COLLATE "en_US", + CONSTRAINT check_a CHECK (a > 0) +) PARTITION BY LIST (a); +CREATE TABLE fail_part (LIKE list_parted); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10); +DROP TABLE fail_part; + +-- check the table being attached exists +ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1); + +-- check ownership of the source table +CREATE ROLE regress_test_me; +CREATE ROLE regress_test_not_me; +CREATE TABLE not_owned_by_me (LIKE list_parted); +ALTER TABLE not_owned_by_me OWNER TO regress_test_not_me; +SET SESSION AUTHORIZATION regress_test_me; +CREATE TABLE owned_by_me ( + a int +) PARTITION BY LIST (a); +ALTER TABLE owned_by_me ATTACH PARTITION not_owned_by_me FOR VALUES IN (1); +RESET SESSION AUTHORIZATION; +DROP TABLE owned_by_me, not_owned_by_me; +DROP ROLE regress_test_not_me; +DROP ROLE regress_test_me; + +-- check the table being attached is not inheritance child of some relation +CREATE TABLE parent (LIKE list_parted); +CREATE TABLE fail_part () INHERITS (parent); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TABLE parent CASCADE; + +-- check the table being attached is not a typed table +CREATE TYPE mytype AS (a int); +CREATE TABLE fail_part OF mytype; +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TYPE mytype CASCADE; + +-- check the existence (or non-existence) of oid column +ALTER TABLE list_parted SET WITH OIDS; +CREATE TABLE fail_part (a int); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); + +ALTER TABLE list_parted SET WITHOUT OIDS; +ALTER TABLE fail_part SET WITH OIDS; +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TABLE fail_part; + +-- check the table being attached does not have columns not in the parent +CREATE TABLE fail_part (like list_parted, c int); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TABLE fail_part; + +-- check the table being attached has all columns of the parent +CREATE TABLE fail_part (a int); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TABLE fail_part; + +-- check the columns of the table being attached match in type, collation and NOT NULL status +CREATE TABLE fail_part ( + a int, + b int +); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ALTER TABLE fail_part ALTER b TYPE char (3); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA"; +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TABLE fail_part; + +-- check the table being attached all constraints of the parent +CREATE TABLE fail_part ( + a int, + b char(2) NOT NULL COLLATE "en_US" +); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); + +-- check the constraint of table being attached matches in definition with parent's constraint +ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TABLE fail_part; + +-- check attributes and constraints after partition is attached +CREATE TABLE part_1 ( + a int, + b char(2) NOT NULL COLLATE "en_US", + CONSTRAINT check_a CHECK (a > 0) NO INHERIT +); + +-- fail to attach a partition with a NO INHERIT constraint +ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1); + +ALTER TABLE part_1 DROP CONSTRAINT check_a; +ALTER TABLE part_1 ADD CONSTRAINT check_a CHECK (a > 0); +ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1); + +SELECT attislocal, attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0; +SELECT conislocal, coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a'; + +-- check the new partition does not overlap with existing partition +CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); + +-- check the new partition does not contain values outside specified bound +INSERT INTO fail_part VALUES (3, 'a'); +-- fail +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (null); + +DELETE FROM fail_part; +INSERT INTO fail_part VALUES (null, 'a'); +-- fail too because null is not specified in the accepted values +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2); + +-- the check will be skipped, if NO VALIDATE is specified +ALTER TABLE fail_part RENAME TO part_2; +ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE; + +-- same check as above but now the table being attached is itself partitioned +CREATE TABLE part_3 ( + a int, + b char(2) NOT NULL COLLATE "en_US", + CONSTRAINT check_a CHECK (a > 0) +) PARTITION BY LIST (b); +CREATE TABLE part_3_a PARTITION OF part_3 FOR VALUES IN ('a'); +INSERT INTO part_3_a (a, b) VALUES (4, 'a'); + +-- fail +ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3); + +-- delete the faulting row and all will be ok +DELETE FROM part_3_a WHERE a NOT IN (3); +INSERT INTO part_3_a (a, b) VALUES (3, 'a'); +ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3); + +-- check the table being attached is not already a partition +ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1); + +-- DETACH PARTITION + +-- check the partition being detached exists at all +ALTER TABLE list_parted DETACH PARTITION part_4; + +-- check the partition being detached is a partition (of the parent) +CREATE TABLE not_a_part (a int); +ALTER TABLE list_parted DETACH PARTITION not_a_part; + +-- check that attinhcount and coninhcount dropped to 0 after detached +ALTER TABLE list_parted DETACH PARTITION part_3; +SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_3'::regclass AND attnum > 0; +SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_3'::regclass AND conname = 'check_a'; + +-- Miscellaneous ALTER TABLE special behaviors for partitions + +-- cannot add/drop a column to/from a partition or rename it +ALTER TABLE part_1 ADD COLUMN c text; +ALTER TABLE part_1 DROP COLUMN b; +ALTER TABLE part_1 RENAME COLUMN b to c; + +-- cannot alter type of a column of a partition +ALTER TABLE part_1 ALTER COLUMN b TYPE text; + +-- cannot let a partition participate in regular inheritance +CREATE TABLE inh_test () INHERITS (part_1); +CREATE TABLE inh_test (LIKE part_1); +ALTER TABLE inh_test INHERIT part_1; +ALTER TABLE part_1 INHERIT inh_test; + +-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set +ALTER TABLE part_1 ALTER b DROP NOT NULL; + +-- cannot drop or alter type of partition key columns of lower levels +-- for example, part_3 is partitioned on b; +ALTER TABLE list_parted ATTACH PARTITION part_3 FOR VALUES IN (3); +ALTER TABLE list_parted DROP COLUMN b; +ALTER TABLE list_parted ALTER COLUMN b TYPE text; + +-- cleanup +DROP TABLE list_parted CASCADE; diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql index 4dd6a0a..c38312e 100644 --- a/src/test/regress/sql/create_table.sql +++ b/src/test/regress/sql/create_table.sql @@ -415,3 +415,141 @@ CREATE TABLE describe_list_key ( ) PARTITION BY LIST (a); \d describe_list_key DROP TABLE describe_range_key, describe_list_key; + +-- +-- CREATE TABLE PARTITION OF +-- + +-- check partition bound syntax + +CREATE TABLE list_parted ( + a int +) PARTITION BY LIST (a); +-- syntax allows only string literal, numeric literal and null to be specified +-- for a partition bound value +CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1'); +CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (2); +CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null); +CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1'); +CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int); + +-- syntax does not allow empty list of values for list partitions +CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (); +-- trying to specify range for list partitioned table +CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2); + +CREATE TABLE range_parted ( + a date +) PARTITION BY RANGE (a); + +-- trying to specify list for range partitioned table +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a'); +-- both start and end bounds of a range partition cannot be UNBOUNDED +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED; +-- each of start and end bounds must have same number of values as there +-- are columns in the partition key +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1); +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1); +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z'); + +-- specified literal can't be cast to the partition column data type +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b'); + +-- check if compatible with the specified parent + +-- cannot create as partition of a non-partitioned table +CREATE TABLE unparted ( + a int +); +CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a'); +DROP TABLE unparted; + +-- cannot create a permanent rel as partition of a temp rel +CREATE TEMP TABLE temp_parted ( + a int +) PARTITION BY LIST (a); +CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a'); +DROP TABLE temp_parted; + +-- cannot create a table with oids as partition of table without oids +CREATE TABLE no_oids_parted ( + a int, + b int +) PARTITION BY RANGE (a, b) WITHOUT OIDS; +CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS; +DROP TABLE no_oids_parted; + +-- check for partition bound overlap and other invalid specifications + +CREATE TABLE list_parted2 ( + a varchar +) PARTITION BY LIST (a); +CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z'); +CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b'); + +CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null); +CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c'); + +CREATE TABLE range_parted2 ( + a int +) PARTITION BY RANGE (a); + +CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0); +CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1); +CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE; +CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1); +CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2); +CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE; +CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15); +CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20); + +-- check for multi-column range partition key where tuple comparison occurs +CREATE TABLE range_parted3 ( + a varchar, + b int +) PARTITION BY RANGE (a, b); + +CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10); +CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20); +CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25); +CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10); +CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20); +CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15); + +-- check schema propagation from parent + +CREATE TABLE parted ( + a text, + b int NOT NULL DEFAULT 1, + CONSTRAINT check_b CHECK (b > 0) +) PARTITION BY LIST (a); + +CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a'); +-- the above command creates inheritance +SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass; + +-- specify a column option overriding parent's and a table constraint that will be merged +CREATE TABLE part_b PARTITION OF parted ( + b WITH OPTIONS DEFAULT 10, + CONSTRAINT check_b CHECK (b > 0) +) FOR VALUES IN ('b'); +SELECT conislocal FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b'; + +-- cannot add NO INHERIT constraint to a partition +CREATE TABLE fail_part_no_inh_con PARTITION OF parted ( + CONSTRAINT chk_b CHECK (b > 0) NO INHERIT +) FOR VALUES IN (null); + +-- specify PARTITION BY for a partition +CREATE TABLE fail_col_part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c); +CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (b); +-- create a partition of partition +CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10); + +-- partition cannot be dropped directly +DROP TABLE part_a; + +-- need to specify CASCADE to drop partitions along with the parent +DROP TABLE parted; + +DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE; -- 1.7.1