>From bb0097c43241f53e86e840bc9e316d87f7d17bf1 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. --- 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/Makefile | 2 +- src/backend/catalog/heap.c | 33 +- src/backend/catalog/index.c | 3 +- src/backend/catalog/partition.c | 1772 ++++++++++++++++++++++++++++ src/backend/catalog/toasting.c | 1 + src/backend/commands/cluster.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 | 208 ++++- src/backend/parser/parse_agg.c | 1 - src/backend/parser/parse_utilcmd.c | 366 ++++++- src/backend/utils/cache/relcache.c | 105 ++- src/include/catalog/heap.h | 4 +- src/include/catalog/partition.h | 59 + src/include/catalog/pg_class.h | 22 +- src/include/nodes/nodes.h | 3 + src/include/nodes/parsenodes.h | 42 +- src/include/parser/kwlist.h | 3 + src/include/utils/rel.h | 21 + 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 | 139 +++ 32 files changed, 4409 insertions(+), 162 deletions(-) create mode 100644 src/backend/catalog/partition.c create mode 100644 src/include/catalog/partition.h diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index ccc2b6b..1e14776 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 e48ccf2..a4fff4f 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. @@ -1043,10 +1122,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. @@ -1054,7 +1135,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. @@ -1233,6 +1315,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 893c899..dc07d32 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 @@ -1424,7 +1494,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/Makefile b/src/backend/catalog/Makefile index 362deca..2d5ac09 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -11,7 +11,7 @@ top_builddir = ../../.. include $(top_builddir)/src/Makefile.global OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \ - objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \ + objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \ pg_constraint.o pg_conversion.o \ pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \ pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \ diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 2994cd0..5a6c742 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -91,7 +91,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, @@ -771,7 +772,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]; @@ -809,6 +811,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) @@ -819,6 +822,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); @@ -852,7 +859,8 @@ AddNewRelationTuple(Relation pg_class_desc, Oid relowner, char relkind, Datum relacl, - Datum reloptions) + Datum reloptions, + Datum relpartbound) { Form_pg_class new_rel_reltup; @@ -925,11 +933,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); } @@ -1034,6 +1044,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, @@ -1269,7 +1280,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. @@ -2053,6 +2065,13 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr, (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 @@ -2479,7 +2498,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 08b0989..79714ae 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -888,7 +888,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 new file mode 100644 index 0000000..05e63c3 --- /dev/null +++ b/src/backend/catalog/partition.c @@ -0,0 +1,1772 @@ +/*------------------------------------------------------------------------- + * + * partition.c + * 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 + * + * + * IDENTIFICATION + * src/backend/catalog/partition.c + * + *------------------------------------------------------------------------- +*/ + +#include "postgres.h" + +#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_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" +#include "utils/datum.h" +#include "utils/memutils.h" +#include "utils/fmgroids.h" +#include "utils/inval.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/ruleutils.h" +#include "utils/syscache.h" + +/* + * 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 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, + PartitionBoundList *list_spec, + ListInfo *listinfo, + int *with); +static bool partition_range_empty(PartitionKey key, + PartitionBoundRange *range_spec); +static bool range_overlaps_existing_partition(PartitionKey key, + PartitionBoundRange *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, PartitionBoundList *list); +static List *get_qual_for_range(PartitionKey key, PartitionBoundRange *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, + PartitionBoundList *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, PartitionBoundRange *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); + +/* + * 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 = -1; + + /* 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_STRATEGY_LIST: + { + PartitionList **list; + int j; + + list = (PartitionList **) palloc0(nparts * sizeof(PartitionList *)); + + i = 0; + all_values_count = 0; + foreach(cell, boundspecs) + { + PartitionBoundList *list_spec; + + Assert(IsA(lfirst(cell), PartitionBoundList)); + list_spec = (PartitionBoundList *) 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->parttypbyval[0], + key->parttyplen[0]); + list_value->index = i; + all_values[j++] = list_value; + } + } + + break; + } + + case PARTITION_STRATEGY_RANGE: + { + range_parts = (RangePartition **) + palloc0(nparts * sizeof(RangePartition *)); + i = 0; + foreach(cell, boundspecs) + { + PartitionBoundRange *range_spec; + RangePartition *range_part; + + Assert(IsA(lfirst(cell), PartitionBoundRange)); + range_spec = (PartitionBoundRange *) 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_STRATEGY_LIST: + { + ListInfo *listinfo; + int *mapping; + int next_index = 0; + + listinfo = (ListInfo *) palloc0(sizeof(ListInfo)); + mapping = (int *) palloc(sizeof(int) * nparts); + + /* Initialize with invalid mapping values */ + for (i = 0; i < nparts; i++) + mapping[i] = -1; + + /* 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; + listinfo->values = (Datum *) + palloc0(all_values_count * sizeof(Datum)); + listinfo->indexes = (int *) + palloc0(all_values_count * sizeof(int)); + + /* + * Copy values and indexes. While copying the indexes, adjust + * them so that they are same for any two partitioning schemes + * with the same lists. One way to achieve this is to assign + * indexes in the same order as the least values of the + * individual lists. + */ + for (i = 0; i < all_values_count; i++) + { + listinfo->values[i] = datumCopy(all_values[i]->value, + key->parttypbyval[0], + key->parttyplen[0]); + listinfo->indexes[i] = all_values[i]->index; + + /* If this index has no mapping, assign one */ + if (mapping[all_values[i]->index] == -1) + mapping[all_values[i]->index] = next_index++; + + listinfo->indexes[i] = mapping[all_values[i]->index]; + } + + /* + * Assign a valid mapping to NULL-accepting partition, if not + * already done (could happen if such partition accepts only + * NULL and hence not covered in the above loop) + */ + if (found_null_partition) + { + Assert(null_partition_index >= 0); + if (mapping[null_partition_index] == -1) + mapping[null_partition_index] = next_index++; + } + + /* All partition must now have a valid mapping */ + Assert(next_index == nparts); + + if (found_null_partition) + listinfo->null_index = mapping[null_partition_index]; + else + listinfo->null_index = -1; + + /* + * Now assign OIDs from the original array into mapped indexes + * of the result array. Order of OIDs in the former is defined + * by the catalog scan that retrived them, whereas that in the + * latter is defined by canonicalized representation of list + * values (viz. ordering of list values). + */ + for (i = 0; i < nparts; i++) + result->oids[mapping[i]] = oids[i]; + + result->bounds->listinfo = listinfo; + pfree(mapping); + break; + } + + case PARTITION_STRATEGY_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_STRATEGY_LIST: + if (!equal_list_info(key, b1->listinfo, b2->listinfo, n)) + return false; + break; + + case PARTITION_STRATEGY_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_STRATEGY_LIST: + { + PartitionBoundList *list; + + Assert(IsA(bound, PartitionBoundList)); + list = (PartitionBoundList *) 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_STRATEGY_RANGE: + { + PartitionBoundRange *range; + + Assert(IsA(bound, PartitionBoundRange)); + range = (PartitionBoundRange *) 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. + */ +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_partition_ancestors + * + * Returns list of ancestors all the way up to the root table + */ +List * +get_partition_ancestors(Oid relid) +{ + Oid parentOID = InvalidOid; + Relation rel; + + rel = heap_open(relid, AccessShareLock); + if (rel->rd_rel->relispartition) + parentOID = get_partition_parent(relid); + heap_close(rel, AccessShareLock); + + if (!OidIsValid(parentOID)) + return NIL; + + return list_concat(list_make1_oid(parentOID), + get_partition_ancestors(parentOID)); +} + +/* + * 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, PartitionBoundList)) + { + PartitionBoundList *list_spec = (PartitionBoundList *) bound; + + Assert(key->strategy == PARTITION_STRATEGY_LIST); + my_qual = get_qual_for_list(key, list_spec); + } + else if (IsA(bound, PartitionBoundRange)) + { + PartitionBoundRange *range_spec = (PartitionBoundRange *) bound; + + Assert(key->strategy == PARTITION_STRATEGY_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, + PartitionBoundList *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, PartitionBoundRange *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, + PartitionBoundRange *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 PartitionBoundList (right operand). If the + * partition does not accept nulls, also include a IS NOT NULL test. + */ +static List * +get_qual_for_list(PartitionKey key, PartitionBoundList *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->parttypid[0], + key->parttypmod[0], + key->parttypcoll[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 = !type_is_array(key->parttypid[0]) + ? get_array_type(key->parttypid[0]) + : key->parttypid[0]; + arr->array_collid = key->parttypcoll[0]; + arr->element_typeid = key->parttypid[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->partcollation[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 PartitionBoundRange (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, PartitionBoundRange *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->parttypid[0], + key->parttypmod[0], + key->parttypcoll[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->partcollation[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->parttypid[i], + key->parttypmod[i], + key->parttypcoll[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->partcollation[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->partcollation[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->partcollation[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->partcollation[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->parttypid[col], + key->parttypid[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 PartitionBoundList + */ +static PartitionList * +make_list_from_spec(PartitionKey key, PartitionBoundList *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->parttypbyval[0], + key->parttyplen[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; + + 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; + + /* Make sure that the indexes have been "canonicalized" */ + Assert(l1->indexes[i] == l2->indexes[i]); + } + + if (l1->null_index != l2->null_index) + 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->partcollation[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 PartitionBoundRange + */ +static PartitionRange * +make_range_from_spec(PartitionKey key, PartitionBoundRange *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->parttypbyval[i], + key->parttyplen[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->parttypbyval[i], + key->parttyplen[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->partcollation[i], + val1[i], val2[i])); + if (result != 0) + break; + } + + return result; +} 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/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 237d0a2..329d0b4 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -29,6 +29,7 @@ #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/objectaccess.h" +#include "catalog/partition.h" #include "catalog/pg_am.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" @@ -163,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 */ @@ -279,7 +282,8 @@ struct DropRelationCallbackState 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); @@ -444,6 +448,11 @@ static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr); static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy); static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, List **partexprs, Oid *partopclass, Oid *partcollation); +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); /* ---------------------------------------------------------------- @@ -482,6 +491,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; @@ -594,10 +604,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 @@ -672,6 +688,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 @@ -695,6 +729,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, parentOidCount, stmt->oncommit, reloptions, + relpartbound, true, allowSystemTableMods, false, @@ -1003,6 +1038,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."))); + /* * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE, * but RemoveRelations() can only pass one relkind for a given relation. @@ -1093,7 +1135,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; @@ -1471,7 +1514,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; @@ -1516,8 +1560,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), @@ -1544,6 +1588,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), @@ -1579,18 +1635,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", @@ -1728,6 +1803,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; @@ -1856,6 +1932,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) { @@ -1885,16 +1964,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); @@ -1931,8 +2014,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 */ @@ -2222,6 +2306,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 @@ -2452,7 +2541,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); @@ -2600,11 +2689,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 */ ); @@ -2846,8 +2936,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); } @@ -3126,6 +3219,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); @@ -3443,6 +3541,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); @@ -3513,7 +3617,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); } @@ -3762,6 +3872,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); @@ -3947,7 +4063,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); /* @@ -4027,6 +4143,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 @@ -4091,6 +4208,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); @@ -4280,6 +4406,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); @@ -4477,7 +4609,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; @@ -4799,6 +4932,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); /* @@ -5321,6 +5459,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) @@ -5846,6 +6004,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 */ @@ -5938,9 +6101,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, @@ -6329,8 +6494,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; /* @@ -7892,7 +8059,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); @@ -7951,8 +8120,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, @@ -8024,6 +8195,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)) @@ -10191,6 +10367,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), @@ -10203,12 +10384,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; @@ -10253,37 +10429,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. @@ -10318,6 +10468,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); @@ -10332,16 +10545,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; } /* @@ -10392,7 +10597,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 @@ -10410,12 +10615,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]; @@ -10437,14 +10646,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))); @@ -10455,8 +10668,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 @@ -10471,7 +10686,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))); } } @@ -10485,7 +10702,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. @@ -10504,10 +10721,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, @@ -10554,7 +10775,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)))); @@ -10562,7 +10785,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)))); @@ -10573,6 +10798,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); @@ -10586,7 +10812,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)))); } @@ -10597,6 +10825,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. @@ -10610,13 +10878,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]; @@ -10625,19 +10891,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 @@ -10647,7 +10904,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); @@ -10668,11 +10925,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 @@ -10681,7 +10947,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))) @@ -10743,7 +11009,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); @@ -10774,7 +11040,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) @@ -10786,30 +11052,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; } /* @@ -12480,3 +12736,271 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, attn++; } } + +/* + * 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 f283a97..1dbfd78 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); @@ -3019,6 +3020,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode) COPY_NODE_FIELD(tableElts); COPY_NODE_FIELD(inhRelations); COPY_NODE_FIELD(partspec); + COPY_NODE_FIELD(partbound); COPY_NODE_FIELD(ofTypename); COPY_NODE_FIELD(constraints); COPY_NODE_FIELD(options); @@ -4202,6 +4204,43 @@ _copyPartitionElem(const PartitionElem *from) return newnode; } +static PartitionBoundList * +_copyPartitionBoundList(const PartitionBoundList *from) +{ + PartitionBoundList *newnode = makeNode(PartitionBoundList); + + COPY_NODE_FIELD(values); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +static PartitionBoundRange * +_copyPartitionBoundRange(const PartitionBoundRange *from) +{ + PartitionBoundRange *newnode = makeNode(PartitionBoundRange); + + 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 * **************************************************************** @@ -5122,6 +5161,15 @@ copyObject(const void *from) case T_PartitionElem: retval = _copyPartitionElem(from); break; + case T_PartitionBoundList: + retval = _copyPartitionBoundList(from); + break; + case T_PartitionBoundRange: + retval = _copyPartitionBoundRange(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 a6421d2..03e9e60 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1169,6 +1169,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b) COMPARE_NODE_FIELD(tableElts); COMPARE_NODE_FIELD(inhRelations); COMPARE_NODE_FIELD(partspec); + COMPARE_NODE_FIELD(partbound); COMPARE_NODE_FIELD(ofTypename); COMPARE_NODE_FIELD(constraints); COMPARE_NODE_FIELD(options); @@ -2376,6 +2377,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); @@ -2657,6 +2659,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b) return true; } +static bool +_equalPartitionBoundList(const PartitionBoundList *a, const PartitionBoundList *b) +{ + COMPARE_NODE_FIELD(values); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool +_equalPartitionBoundRange(const PartitionBoundRange *a, const PartitionBoundRange *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 */ @@ -3416,6 +3449,15 @@ equal(const void *a, const void *b) case T_PartitionElem: retval = _equalPartitionElem(a, b); break; + case T_PartitionBoundList: + retval = _equalPartitionBoundList(a, b); + break; + case T_PartitionBoundRange: + retval = _equalPartitionBoundRange(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 417e20a..3a507ca 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node) WRITE_NODE_FIELD(tableElts); WRITE_NODE_FIELD(inhRelations); WRITE_NODE_FIELD(partspec); + WRITE_NODE_FIELD(partbound); WRITE_NODE_FIELD(ofTypename); WRITE_NODE_FIELD(constraints); WRITE_NODE_FIELD(options); @@ -2573,6 +2574,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); @@ -3290,6 +3292,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node) WRITE_LOCATION_FIELD(location); } +static void +_outPartitionBoundList(StringInfo str, const PartitionBoundList *node) +{ + WRITE_NODE_TYPE("PARTITIONLISTVALUES"); + + WRITE_NODE_FIELD(values); +} + +static void +_outPartitionBoundRange(StringInfo str, const PartitionBoundRange *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' @@ -3880,6 +3901,12 @@ outNode(StringInfo str, const void *obj) case T_PartitionElem: _outPartitionElem(str, obj); break; + case T_PartitionBoundList: + _outPartitionBoundList(str, obj); + break; + case T_PartitionBoundRange: + _outPartitionBoundRange(str, obj); + break; default: diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 917e6c8..e11e670 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -2266,6 +2266,35 @@ _readExtensibleNode(void) } /* + * _readPartitionBoundList + */ +static PartitionBoundList * +_readPartitionBoundList(void) +{ + READ_LOCALS(PartitionBoundList); + + READ_NODE_FIELD(values); + + READ_DONE(); +} + +/* + * _readPartitionBoundRange + */ +static PartitionBoundRange * +_readPartitionBoundRange(void) +{ + READ_LOCALS(PartitionBoundRange); + + 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 @@ -2497,6 +2526,10 @@ parseNodeString(void) return_value = _readAlternativeSubPlan(); else if (MATCH("EXTENSIBLENODE", 14)) return_value = _readExtensibleNode(); + else if (MATCH("PARTITIONLISTVALUES", 19)) + return_value = _readPartitionBoundList(); + else if (MATCH("PARTITIONRANGE", 14)) + return_value = _readPartitionBoundRange(); else { elog(ERROR, "badly formatted node string \"%.32s\"...", token); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 9d32a20..d40d71c 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -547,6 +547,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type part_strategy %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. @@ -572,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 @@ -588,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 @@ -602,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 @@ -2374,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: @@ -2469,6 +2510,60 @@ reloption_elem: } ; +ForValues: + /* a LIST partition */ + FOR VALUES IN_P '(' partvalue_list ')' + { + PartitionBoundList *n = makeNode(PartitionBoundList); + + n->values = $5; + n->location = @1; + + $$ = (Node *) n; + } + + /* a RANGE partition */ + | FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc + { + PartitionBoundRange *n = makeNode(PartitionBoundRange); + + 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); } + ; /***************************************************************************** * @@ -2886,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 OptPartitionSpec OptWith + OnCommitOption OptTableSpace + { + CreateStmt *n = makeNode(CreateStmt); + $4->relpersistence = $2; + n->relation = $4; + n->tableElts = $8; + n->inhRelations = list_make1($7); + n->partbound = (Node *) $9; + n->partspec = $10; + n->ofTypename = NULL; + n->constraints = NIL; + n->options = $11; + n->oncommit = $12; + n->tablespacename = $13; + n->if_not_exists = false; + $$ = (Node *)n; + } + | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF + qualified_name OptPartitionElementList ForValues OptPartitionSpec + OptWith OnCommitOption OptTableSpace + { + CreateStmt *n = makeNode(CreateStmt); + $7->relpersistence = $2; + n->relation = $7; + n->tableElts = $11; + n->inhRelations = list_make1($10); + n->partbound = (Node *) $12; + n->partspec = $13; + n->ofTypename = NULL; + n->constraints = NIL; + n->options = $14; + n->oncommit = $15; + n->tablespacename = $16; + n->if_not_exists = true; + $$ = (Node *)n; + } ; /* @@ -2931,6 +3064,11 @@ OptTypedTableElementList: | /*EMPTY*/ { $$ = NIL; } ; +OptPartitionElementList: + '(' PartitionElementList ')' { $$ = $2; } + | /*EMPTY*/ { $$ = NIL; } + ; + TableElementList: TableElement { @@ -2953,6 +3091,17 @@ TypedTableElementList: } ; +PartitionElementList: + PartitionElement + { + $$ = list_make1($1); + } + | PartitionElementList ',' PartitionElement + { + $$ = lappend($1, $3); + } + ; + TableElement: columnDef { $$ = $1; } | TableLikeClause { $$ = $1; } @@ -2964,6 +3113,11 @@ TypedTableElement: | TableConstraint { $$ = $1; } ; +PartitionElement: + columnOptions { $$ = $1; } + | TableConstraint { $$ = $1; } + ; + columnDef: ColId Typename create_generic_options ColQualList { ColumnDef *n = makeNode(ColumnDef); @@ -2973,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; @@ -2994,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; @@ -4551,6 +4707,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; + } ; /***************************************************************************** @@ -11156,6 +11354,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; @@ -13755,6 +13954,7 @@ unreserved_keyword: | ASSERTION | ASSIGNMENT | AT + | ATTACH | ATTRIBUTE | BACKWARD | BEFORE @@ -13801,6 +14001,7 @@ unreserved_keyword: | DELIMITER | DELIMITERS | DEPENDS + | DETACH | DICTIONARY | DISABLE_P | DISCARD @@ -13843,6 +14044,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 9cb9222..92d1577 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -509,7 +509,6 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) break; - /* * There is intentionally no default: case here, so that the * compiler will warn if we add a new ParseExprKind without diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 666cc1f..f565a5d 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" @@ -62,6 +65,7 @@ #include "utils/guc.h" #include "utils/lsyscache.h" #include "utils/rel.h" +#include "utils/ruleutils.h" #include "utils/syscache.h" #include "utils/typcache.h" @@ -88,6 +92,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 +135,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 +240,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) cxt.alist = NIL; cxt.pkey = NULL; cxt.ispartitioned = stmt->partspec != NULL; + cxt.partbound = NULL; /* * Notice that we allow OIDs here only for plain tables, even though @@ -249,11 +259,14 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) if (stmt->ofTypename) transformOfType(&cxt, stmt->ofTypename); + if (stmt->partbound) + transformPartitionOf(&cxt, stmt->partbound); + if (stmt->partspec) { int partnatts = list_length(stmt->partspec->partParams); - if (stmt->inhRelations) + if (stmt->inhRelations && !stmt->partbound) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("cannot create partitioned table as inheritance child"))); @@ -359,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); @@ -898,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; @@ -1117,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; @@ -2580,6 +2596,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 @@ -2662,6 +2679,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; @@ -3026,3 +3059,334 @@ 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_strategy(key); + int partnatts = get_partition_natts(key); + List *partexprs = get_partition_exprs(key); + PartitionBoundList *list, *result_list; + PartitionBoundRange *range, *result_range; + + switch (strategy) + { + case PARTITION_STRATEGY_LIST: + { + char *colname; + + /* Get the only column's name in case we need to output an error */ + if (key->partattrs[0] != 0) + colname = get_relid_attribute_name(RelationGetRelid(parent), + key->partattrs[0]); + else + colname = deparse_expression((Node *) linitial(partexprs), + deparse_context_for(RelationGetRelationName(parent), + RelationGetRelid(parent)), + false, false); + + if (!IsA(bound, PartitionBoundList)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("invalid bound specification for a list partition"), + parser_errposition(cxt->pstate, exprLocation(bound)))); + + list = (PartitionBoundList *) bound; + result_list = makeNode(PartitionBoundList); + + foreach(cell, list->values) + { + A_Const *con = (A_Const *) lfirst(cell); + Node *value; + + value = (Node *) make_const(cxt->pstate, &con->val, con->location); + value = coerce_to_target_type(cxt->pstate, + value, exprType(value), + get_partition_col_typid(key, 0), + get_partition_col_typmod(key, 0), + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + + if (value == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("specified value cannot be cast to type \"%s\"" + " of key column \"%s\"", + format_type_be(get_partition_col_typid(key, 0)), + colname), + parser_errposition(cxt->pstate, exprLocation(value)))); + + /* Simplify the expression */ + value = (Node *) expression_planner((Expr *) value); + + result_list->values = lappend(result_list->values, value); + } + return (Node *) result_list; + } + + case PARTITION_STRATEGY_RANGE: + { + char *colname; + + if (!IsA(bound, PartitionBoundRange)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("invalid bound specification for a range partition"), + parser_errposition(cxt->pstate, exprLocation(bound)))); + + range = (PartitionBoundRange *) bound; + + 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(PartitionBoundRange); + 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) + { + A_Const *con = (A_Const *) lfirst(cell); + Node *value; + + /* Get the column's name in case we need to output an error */ + if (key->partattrs[0] != 0) + colname = get_relid_attribute_name(RelationGetRelid(parent), + key->partattrs[i]); + else + colname = deparse_expression((Node *) list_nth(partexprs, i), + deparse_context_for(RelationGetRelationName(parent), + RelationGetRelid(parent)), + false, false); + + value = (Node *) make_const(cxt->pstate, &con->val, con->location); + value = coerce_to_target_type(cxt->pstate, + value, exprType(value), + get_partition_col_typid(key, i), + get_partition_col_typmod(key, i), + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (value == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("specified value cannot be cast to type" + " \"%s\" of key column \"%s\"", + format_type_be(get_partition_col_typid(key, i)), + colname), + parser_errposition(cxt->pstate, exprLocation(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) + { + A_Const *con = (A_Const *) lfirst(cell); + Node *value; + + /* Get the column's name in case we need to output an error */ + if (key->partattrs[0] != 0) + colname = get_relid_attribute_name(RelationGetRelid(parent), + key->partattrs[i]); + else + colname = deparse_expression((Node *) list_nth(partexprs, i), + deparse_context_for(RelationGetRelationName(parent), + RelationGetRelid(parent)), + false, false); + + value = (Node *) make_const(cxt->pstate, &con->val, con->location); + value = coerce_to_target_type(cxt->pstate, + value, exprType(value), + get_partition_col_typid(key, i), + get_partition_col_typmod(key, i), + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (value == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("specified value cannot be cast to type" + " \"%s\" of key column \"%s\"", + format_type_be(get_partition_col_typid(key, i)), + colname), + parser_errposition(cxt->pstate, exprLocation(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 e80ff80..1666233 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -41,6 +41,7 @@ #include "catalog/index.h" #include "catalog/indexing.h" #include "catalog/namespace.h" +#include "catalog/partition.h" #include "catalog/pg_am.h" #include "catalog/pg_amproc.h" #include "catalog/pg_attrdef.h" @@ -282,6 +283,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); /* @@ -1161,6 +1164,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 @@ -1288,13 +1344,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; } /* @@ -2291,6 +2352,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); @@ -2439,11 +2504,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 @@ -2454,6 +2520,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); @@ -2484,6 +2551,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 @@ -2539,6 +2609,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 */ @@ -3765,6 +3842,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); @@ -5290,6 +5381,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 11b16a9..c3ad626 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 new file mode 100644 index 0000000..ea7806e --- /dev/null +++ b/src/include/catalog/partition.h @@ -0,0 +1,59 @@ +/*------------------------------------------------------------------------- + * + * partition.h + * Header file for structures and utility functions related to + * partitioning + * + * Copyright (c) 2007-2016, PostgreSQL Global Development Group + * + * src/include/catalog/partition.h + * + *------------------------------------------------------------------------- + */ +#ifndef PARTITION_H +#define PARTITION_H + +#include "fmgr.h" +#include "parser/parse_node.h" +#include "utils/rel.h" + +/* + * 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 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_partition_ancestors(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 65d0009..727dc6e 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_PartitionSpec, + T_PartitionBoundList, + T_PartitionBoundRange, /* * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index ada75bd..15e8d3d 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -593,6 +593,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) */ @@ -735,6 +736,40 @@ typedef struct PartitionSpec #define PARTITION_STRATEGY_LIST 'l' #define PARTITION_STRATEGY_RANGE 'r' +/* + * PartitionBoundList - a list partition bound + */ +typedef struct PartitionBoundList +{ + NodeTag type; + List *values; + int location; +} PartitionBoundList; + +/* + * PartitionBoundRange - a range partition bound + */ +typedef struct PartitionBoundRange +{ + NodeTag type; + bool lowerinc; + List *lower; + bool upperinc; + List *upper; + int location; /* token location, or -1 if unknown */ +} PartitionBoundRange; + +/* + * PartitionCmd - ALTER TABLE partition commands + */ +typedef struct PartitionCmd +{ + NodeTag type; + RangeVar *name; + Node *bound; + bool skip_validate; +} PartitionCmd; + /**************************************************************************** * Nodes for a Query tree ****************************************************************************/ @@ -1562,7 +1597,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 @@ -1788,7 +1825,8 @@ typedef struct CreateStmt List *tableElts; /* column definitions (list of ColumnDef) */ List *inhRelations; /* relations to inherit from (list of * inhRelation) */ - PartitionSpec *partspec; /* PARTITION BY clause */ + Node *partbound; /* FOR VALUES clause */ + PartitionSpec *partspec; /* PARTITION BY clause */ TypeName *ofTypename; /* OF typename */ List *constraints; /* constraints (list of Constraint nodes) */ List *options; /* options from WITH clause */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 17ffef5..ea6a12b 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/utils/rel.h b/src/include/utils/rel.h index f7c0ab0..fbf88e8 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -123,6 +123,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 */ @@ -598,6 +601,24 @@ get_partition_col_attnum(PartitionKey key, int col) return key->partattrs[col]; } +static inline Oid +get_partition_col_typid(PartitionKey key, int col) +{ + return key->parttypid[col]; +} + +static inline int32 +get_partition_col_typmod(PartitionKey key, int col) +{ + return key->parttypmod[col]; +} + +/* + * 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 36f487a..e0181c4 100644 --- a/src/test/regress/expected/create_table.out +++ b/src/test/regress/expected/create_table.out @@ -437,3 +437,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_not_found 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 5a0d933..7255690 100644 --- a/src/test/regress/sql/create_table.sql +++ b/src/test/regress/sql/create_table.sql @@ -419,3 +419,142 @@ 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_not_found 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