>From 340f0262c07ce16fce27ae2716715238314acdbd Mon Sep 17 00:00:00 2001 From: amit Date: Thu, 14 Jul 2016 09:59:15 +0900 Subject: [PATCH 1/7] Catalog and DDL for partitioned tables. In addition to a catalog for storing the partitioning information, this commit also adds a new relkind to pg_class.h. PARTITION BY clause is added to CREATE TABLE. The tables so created are RELKIND_PARTITIONED_TABLE relations which are special in number of ways, especially their interactions with table inheritance features. --- doc/src/sgml/catalogs.sgml | 112 ++++++- doc/src/sgml/ref/create_table.sgml | 62 ++++ src/backend/access/common/reloptions.c | 2 + src/backend/catalog/Makefile | 2 +- src/backend/catalog/aclchk.c | 2 + src/backend/catalog/dependency.c | 10 +- src/backend/catalog/heap.c | 165 +++++++++- src/backend/catalog/index.c | 4 +- src/backend/catalog/objectaddress.c | 5 +- src/backend/catalog/pg_constraint.c | 2 +- src/backend/commands/analyze.c | 6 +- src/backend/commands/copy.c | 6 + src/backend/commands/indexcmds.c | 24 +- src/backend/commands/lockcmds.c | 2 +- src/backend/commands/policy.c | 5 +- src/backend/commands/seclabel.c | 3 +- src/backend/commands/sequence.c | 3 +- src/backend/commands/tablecmds.c | 509 +++++++++++++++++++++++++++- src/backend/commands/trigger.c | 16 +- src/backend/commands/vacuum.c | 3 +- src/backend/executor/execMain.c | 2 + src/backend/executor/nodeModifyTable.c | 3 +- src/backend/nodes/copyfuncs.c | 34 ++ src/backend/nodes/equalfuncs.c | 29 ++ src/backend/nodes/outfuncs.c | 28 ++ src/backend/parser/gram.y | 105 +++++- src/backend/parser/parse_agg.c | 10 + src/backend/parser/parse_expr.c | 5 + src/backend/parser/parse_func.c | 3 + src/backend/parser/parse_utilcmd.c | 73 ++++- src/backend/rewrite/rewriteDefine.c | 3 +- src/backend/rewrite/rewriteHandler.c | 3 +- src/backend/rewrite/rowsecurity.c | 3 +- src/backend/utils/cache/relcache.c | 265 ++++++++++++++- src/backend/utils/cache/syscache.c | 12 + src/include/catalog/dependency.h | 3 +- src/include/catalog/heap.h | 10 + src/include/catalog/indexing.h | 3 + src/include/catalog/pg_class.h | 1 + src/include/catalog/pg_partitioned_table.h | 76 ++++ src/include/commands/defrem.h | 2 + src/include/nodes/nodes.h | 2 + src/include/nodes/parsenodes.h | 29 ++ src/include/parser/parse_node.h | 3 +- src/include/pg_config_manual.h | 5 + src/include/utils/rel.h | 68 ++++ src/include/utils/syscache.h | 1 + src/test/regress/expected/alter_table.out | 46 +++ src/test/regress/expected/create_table.out | 168 +++++++++ src/test/regress/expected/sanity_check.out | 1 + src/test/regress/sql/alter_table.sql | 32 ++ src/test/regress/sql/create_table.sql | 146 ++++++++ 52 files changed, 2047 insertions(+), 70 deletions(-) create mode 100644 src/include/catalog/pg_partitioned_table.h diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 561e228..26e5ce8 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -226,6 +226,11 @@ + pg_partitioned_table + information about partition key of tables + + + pg_policy row-security policies @@ -1723,7 +1728,8 @@ char - r = ordinary table, i = index, + r = ordinary table, P = partitioned table, + i = index S = sequence, v = view, m = materialized view, c = composite type, t = TOAST table, @@ -4689,6 +4695,110 @@ + + <structname>pg_partitioned_table</structname> + + + pg_partitioned_table + + + + The catalog pg_partitioned_table stores + information about how tables are partitioned. + + + + <structname>pg_partitioned_table</> Columns + + + + + Name + Type + References + Description + + + + + + + partrelid + oid + pg_class.oid + The OID of the pg_class entry for this partitioned table + + + + partstrat + char + + + Partitioning strategy; l = list partitioned table, + r = range partitioned table + + + + + partnatts + int2 + + The number of columns in partition key + + + + partattrs + int2vector + pg_attribute.attnum + + This is an array of partnatts values that + indicate which table columns are part of the partition key. For + example, a value of 1 3 would mean that the first + and the third table columns make up the partition key. A zero in this + array indicates that the corresponding partition key column is an + expression, rather than a simple column reference. + + + + + partclass + oidvector + pg_opclass.oid + + For each column in the partition key, this contains the OID of the + operator class to use. See + pg_opclass for details. + + + + + partcollation + oidvector + pg_opclass.oid + + For each column in the partition key, this contains the OID of the + the collation to use for partitioning. + + + + + partexprs + pg_node_tree + + + Expression trees (in nodeToString() + representation) for partition key columns that are not simple column + references. This is a list with one element for each zero + entry in partattrs. Null if all partition key columns + are simple references. + + + + + +
+
+ <structname>pg_policy</structname> diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index bf2ad64..eeb9d59 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI [, ... ] ] ) [ INHERITS ( parent_table [, ... ] ) ] +[ 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 ] @@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI | table_constraint } [, ... ] ) ] +[ 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 ] @@ -314,6 +316,46 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI + PARTITION BY { RANGE | LIST } ( { column_name | ( expression ) } [ opclass ] [, ...] ) + + + The optional PARTITION BY clause specifies a strategy + of partitioning the table. The table thus created is called a + partitioned table. The parenthesized list of + columns or expressions forms the partition key + for the table. When using range partitioning, the partition key can + include multiple columns or expressions, but for list partitioning, the + partition key must consist of a single column or expression. If no + btree operator class is specified when creating a partitioned table, + the default btree operator class for the datatype will be used. If + there is none, an error will be reported. + + + + A partitioned table is divided into sub-tables (called partitions), + which are created using separate CREATE TABLE commands. + The partitioned table is itself empty. A data row inserted into the + table is routed to a partition based on the value of columns or + expressions in the partition key. If no existing partition matches + the values in the new row, an error will be reported. + + + + Partitioned tables do not support UNIQUE, + PRIMARY KEY, EXCLUDE, or + FOREIGN KEY constraints; however, you can define + these constraints on individual partitions. + + + + When using range partitioning, a NOT NULL constraint + is added to each non-expression column in the partition key. + + + + + + LIKE source_table [ like_option ... ] @@ -1369,6 +1411,26 @@ CREATE TABLE employees OF employee_type ( salary WITH OPTIONS DEFAULT 1000 ); + + + Create a range partitioned table: + +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); + + + + Create a list partitioned table: + +CREATE TABLE cities ( + name text not null, + population int, +) PARTITION BY LIST (name); + diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 83a97b0..34018ca 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, case RELKIND_RELATION: case RELKIND_TOASTVALUE: case RELKIND_MATVIEW: + case RELKIND_PARTITIONED_TABLE: options = heap_reloptions(classForm->relkind, datum, false); break; case RELKIND_VIEW: @@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate) return (bytea *) rdopts; case RELKIND_RELATION: case RELKIND_MATVIEW: + case RELKIND_PARTITIONED_TABLE: return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP); default: /* other relkinds are not supported */ diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index 1ce7610..362deca 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ pg_foreign_table.h pg_policy.h pg_replication_origin.h \ pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \ - pg_collation.h pg_range.h pg_transform.h \ + pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \ toasting.h indexing.h \ ) diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index c0df671..3086021 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -768,6 +768,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames) objects = list_concat(objects, objs); objs = getRelationsInNamespace(namespaceId, RELKIND_FOREIGN_TABLE); objects = list_concat(objects, objs); + objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE); + objects = list_concat(objects, objs); break; case ACL_OBJECT_SEQUENCE: objs = getRelationsInNamespace(namespaceId, RELKIND_SEQUENCE); diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 04d7840..9746f24 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -1393,7 +1393,8 @@ void recordDependencyOnSingleRelExpr(const ObjectAddress *depender, Node *expr, Oid relId, DependencyType behavior, - DependencyType self_behavior) + DependencyType self_behavior, + bool ignore_self) { find_expr_references_context context; RangeTblEntry rte; @@ -1448,9 +1449,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender, context.addrs->numrefs = outrefs; /* Record the self-dependencies */ - recordMultipleDependencies(depender, - self_addrs->refs, self_addrs->numrefs, - self_behavior); + if (!ignore_self) + recordMultipleDependencies(depender, + self_addrs->refs, self_addrs->numrefs, + self_behavior); free_object_addresses(self_addrs); } diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 0cf7b9e..d7ce4ca 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -48,6 +48,8 @@ #include "catalog/pg_foreign_table.h" #include "catalog/pg_inherits.h" #include "catalog/pg_namespace.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_partitioned_table.h" #include "catalog/pg_statistic.h" #include "catalog/pg_tablespace.h" #include "catalog/pg_type.h" @@ -1104,7 +1106,8 @@ heap_create_with_catalog(const char *relname, if (IsBinaryUpgrade && (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW || - relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE)) + relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE || + relkind == RELKIND_PARTITIONED_TABLE)) { if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid)) ereport(ERROR, @@ -1138,6 +1141,7 @@ heap_create_with_catalog(const char *relname, case RELKIND_VIEW: case RELKIND_MATVIEW: case RELKIND_FOREIGN_TABLE: + case RELKIND_PARTITIONED_TABLE: relacl = get_user_default_acl(ACL_OBJECT_RELATION, ownerid, relnamespace); break; @@ -1182,7 +1186,8 @@ heap_create_with_catalog(const char *relname, relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW || relkind == RELKIND_FOREIGN_TABLE || - relkind == RELKIND_COMPOSITE_TYPE)) + relkind == RELKIND_COMPOSITE_TYPE || + relkind == RELKIND_PARTITIONED_TABLE)) new_array_oid = AssignTypeArrayOid(); /* @@ -1354,7 +1359,9 @@ heap_create_with_catalog(const char *relname, if (relpersistence == RELPERSISTENCE_UNLOGGED) { Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW || - relkind == RELKIND_TOASTVALUE); + relkind == RELKIND_TOASTVALUE || + relkind == RELKIND_PARTITIONED_TABLE); + heap_create_init_fork(new_rel_desc); } @@ -1801,6 +1808,12 @@ heap_drop_with_catalog(Oid relid) } /* + * If a partitioned table, delete the pg_partitioned_table tuple. + */ + if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + RemovePartitionKeyByRelId(relid); + + /* * Schedule unlinking of the relation's physical files at commit. */ if (rel->rd_rel->relkind != RELKIND_VIEW && @@ -2033,6 +2046,17 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr, attNos = NULL; /* + * Partitioned tables do not contain any rows themselves, so a NO INHERIT + * constraint makes no sense. + */ + if (is_no_inherit && + rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot add NO INHERIT constraint to partitioned table \"%s\"", + RelationGetRelationName(rel)))); + + /* * Create the Check Constraint */ constrOid = @@ -3018,3 +3042,138 @@ insert_ordered_unique_oid(List *list, Oid datum) lappend_cell_oid(list, prev, datum); return list; } + +/* + * StorePartitionKey + * Store information about the partition key rel into the catalog + */ +void +StorePartitionKey(Relation rel, + char strategy, + int16 partnatts, + AttrNumber *partattrs, + List *partexprs, + Oid *partopclass, + Oid *partcollation) +{ + int i; + int2vector *partattrs_vec; + oidvector *partopclass_vec; + oidvector *partcollation_vec; + Datum partexprDatum; + Relation pg_partitioned_table; + HeapTuple tuple; + Datum values[Natts_pg_partitioned_table]; + bool nulls[Natts_pg_partitioned_table]; + ObjectAddress myself; + ObjectAddress referenced; + + Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); + + tuple = SearchSysCache1(PARTRELID, + ObjectIdGetDatum(RelationGetRelid(rel))); + + /* Copy the partition attribute numbers, opclass OIDs into arrays */ + partattrs_vec = buildint2vector(partattrs, partnatts); + partopclass_vec = buildoidvector(partopclass, partnatts); + partcollation_vec = buildoidvector(partcollation, partnatts); + + /* Convert the expressions (if any) to a text datum */ + if (partexprs) + { + char *exprString; + + exprString = nodeToString(partexprs); + partexprDatum = CStringGetTextDatum(exprString); + pfree(exprString); + } + else + partexprDatum = (Datum) 0; + + pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock); + + MemSet(nulls, false, sizeof(nulls)); + + /* Only this can ever be NULL */ + if (!partexprDatum) + nulls[Anum_pg_partitioned_table_partexprs - 1] = true; + + values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel)); + values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy); + values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts); + values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec); + values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec); + values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec); + values[Anum_pg_partitioned_table_partexprs - 1] = partexprDatum; + + tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_table), values, nulls); + + simple_heap_insert(pg_partitioned_table, tuple); + + /* Update the indexes on pg_partitioned_table */ + CatalogUpdateIndexes(pg_partitioned_table, tuple); + heap_close(pg_partitioned_table, RowExclusiveLock); + + /* Mark this relation as dependent on a few things as follows */ + myself.classId = RelationRelationId; + myself.objectId = RelationGetRelid(rel);; + myself.objectSubId = 0; + + /* Operator class and collation per key column */ + for (i = 0; i < partnatts; i++) + { + referenced.classId = OperatorClassRelationId; + referenced.objectId = partopclass[i]; + referenced.objectSubId = 0; + + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + referenced.classId = CollationRelationId; + referenced.objectId = partcollation[i]; + referenced.objectSubId = 0; + + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } + + /* + * Anything mentioned in the expressions. We must ignore the column + * references, which will depend on the table itself; there is no + * separate partition key object. + */ + if (partexprs) + recordDependencyOnSingleRelExpr(&myself, + (Node *) partexprs, + RelationGetRelid(rel), + DEPENDENCY_NORMAL, + DEPENDENCY_AUTO, true); + + /* + * We must invalidate the relcache so that the next + * CommandCounterIncrement() will cause the same to be rebuilt using the + * information in just created catalog entry. + */ + CacheInvalidateRelcache(rel); +} + +/* + * RemovePartitionKeyByRelId + * Remove pg_partitioned_table entry for a relation + */ +void +RemovePartitionKeyByRelId(Oid relid) +{ + Relation rel; + HeapTuple tuple; + + rel = heap_open(PartitionedRelationId, RowExclusiveLock); + + tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for partition key of relation %u", + relid); + + simple_heap_delete(rel, &tuple->t_self); + + ReleaseSysCache(tuple); + heap_close(rel, RowExclusiveLock); +} diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 08b646d..08b0989 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -1043,7 +1043,7 @@ index_create(Relation heapRelation, (Node *) indexInfo->ii_Expressions, heapRelationId, DEPENDENCY_NORMAL, - DEPENDENCY_AUTO); + DEPENDENCY_AUTO, false); } /* Store dependencies on anything mentioned in predicate */ @@ -1053,7 +1053,7 @@ index_create(Relation heapRelation, (Node *) indexInfo->ii_Predicate, heapRelationId, DEPENDENCY_NORMAL, - DEPENDENCY_AUTO); + DEPENDENCY_AUTO, false); } } else diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index d531d17..bb4b080 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname, RelationGetRelationName(relation)))); break; case OBJECT_TABLE: - if (relation->rd_rel->relkind != RELKIND_RELATION) + if (relation->rd_rel->relkind != RELKIND_RELATION && + relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table", @@ -3244,6 +3245,7 @@ getRelationDescription(StringInfo buffer, Oid relid) switch (relForm->relkind) { case RELKIND_RELATION: + case RELKIND_PARTITIONED_TABLE: appendStringInfo(buffer, _("table %s"), relname); break; @@ -3701,6 +3703,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId) switch (relForm->relkind) { case RELKIND_RELATION: + case RELKIND_PARTITIONED_TABLE: appendStringInfoString(buffer, "table"); break; case RELKIND_INDEX: diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 8fabe68..724b41e 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -368,7 +368,7 @@ CreateConstraintEntry(const char *constraintName, */ recordDependencyOnSingleRelExpr(&conobject, conExpr, relId, DEPENDENCY_NORMAL, - DEPENDENCY_NORMAL); + DEPENDENCY_NORMAL, false); } /* Post creation hook for new constraint */ diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index c617abb..f4afcd9 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -201,7 +201,8 @@ analyze_rel(Oid relid, RangeVar *relation, int options, * locked the relation. */ if (onerel->rd_rel->relkind == RELKIND_RELATION || - onerel->rd_rel->relkind == RELKIND_MATVIEW) + onerel->rd_rel->relkind == RELKIND_MATVIEW || + onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { /* Regular table, so we'll use the regular row acquisition function */ acquirefunc = acquire_sample_rows; @@ -1317,7 +1318,8 @@ acquire_inherited_sample_rows(Relation onerel, int elevel, /* Check table type (MATVIEW can't happen, but might as well allow) */ if (childrel->rd_rel->relkind == RELKIND_RELATION || - childrel->rd_rel->relkind == RELKIND_MATVIEW) + childrel->rd_rel->relkind == RELKIND_MATVIEW || + childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { /* Regular table, so use the regular row acquisition function */ acquirefunc = acquire_sample_rows; diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 3c81906..28b6f63 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -1751,6 +1751,12 @@ BeginCopyTo(ParseState *pstate, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot copy from sequence \"%s\"", RelationGetRelationName(rel)))); + else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot copy from partitioned table \"%s\"", + RelationGetRelationName(rel)), + errhint("Try the COPY (SELECT ...) TO variant."))); else ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 85817c6..9735bb2 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo, char *accessMethodName, Oid accessMethodId, bool amcanorder, bool isconstraint); -static Oid GetIndexOpClass(List *opclass, Oid attrType, - char *accessMethodName, Oid accessMethodId); static char *ChooseIndexName(const char *tabname, Oid namespaceId, List *colnames, List *exclusionOpNames, bool primary, bool isconstraint); @@ -383,6 +381,11 @@ DefineIndex(Oid relationId, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot create index on foreign table \"%s\"", RelationGetRelationName(rel)))); + else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot create index on partitioned table \"%s\"", + RelationGetRelationName(rel)))); else ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -1145,10 +1148,10 @@ ComputeIndexAttrs(IndexInfo *indexInfo, /* * Identify the opclass to use. */ - classOidP[attn] = GetIndexOpClass(attribute->opclass, - atttype, - accessMethodName, - accessMethodId); + classOidP[attn] = ResolveOpClass(attribute->opclass, + atttype, + accessMethodName, + accessMethodId); /* * Identify the exclusion operator, if any. @@ -1255,10 +1258,13 @@ ComputeIndexAttrs(IndexInfo *indexInfo, /* * Resolve possibly-defaulted operator class specification + * + * Note: This is used to resolve operator class specification in index and + * partition key definition. */ -static Oid -GetIndexOpClass(List *opclass, Oid attrType, - char *accessMethodName, Oid accessMethodId) +Oid +ResolveOpClass(List *opclass, Oid attrType, + char *accessMethodName, Oid accessMethodId) { char *schemaname; char *opcname; diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c index a0c0d75..9e62e00 100644 --- a/src/backend/commands/lockcmds.c +++ b/src/backend/commands/lockcmds.c @@ -87,7 +87,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid, * check */ /* Currently, we only allow plain tables to be locked */ - if (relkind != RELKIND_RELATION) + if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table", diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c index d694cf8..1757428 100644 --- a/src/backend/commands/policy.c +++ b/src/backend/commands/policy.c @@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid, rv->relname))); /* Relation type MUST be a table. */ - if (relkind != RELKIND_RELATION) + if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table", rv->relname))); @@ -376,7 +376,8 @@ RemovePolicyById(Oid policy_id) relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid; rel = heap_open(relid, AccessExclusiveLock); - if (rel->rd_rel->relkind != RELKIND_RELATION) + if (rel->rd_rel->relkind != RELKIND_RELATION && + rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table", diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c index 5bd7e12..2b0ae34 100644 --- a/src/backend/commands/seclabel.c +++ b/src/backend/commands/seclabel.c @@ -110,7 +110,8 @@ ExecSecLabelStmt(SecLabelStmt *stmt) relation->rd_rel->relkind != RELKIND_VIEW && relation->rd_rel->relkind != RELKIND_MATVIEW && relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE && - relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE) + relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE && + relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table", diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 7e37108..1ab9030 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -1475,7 +1475,8 @@ process_owned_by(Relation seqrel, List *owned_by) /* Must be a regular or foreign table */ if (!(tablerel->rd_rel->relkind == RELKIND_RELATION || - tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)) + tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE || + tablerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("referenced relation \"%s\" is not a table or foreign table", diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index f97bee5..7e2beff 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -65,6 +65,7 @@ #include "nodes/parsenodes.h" #include "optimizer/clauses.h" #include "optimizer/planner.h" +#include "optimizer/var.h" #include "parser/parse_clause.h" #include "parser/parse_coerce.h" #include "parser/parse_collate.h" @@ -252,6 +253,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = { gettext_noop("foreign table \"%s\" does not exist, skipping"), gettext_noop("\"%s\" is not a foreign table"), gettext_noop("Use DROP FOREIGN TABLE to remove a foreign table.")}, + {RELKIND_PARTITIONED_TABLE, + ERRCODE_UNDEFINED_TABLE, + gettext_noop("table \"%s\" does not exist"), + gettext_noop("table \"%s\" does not exist, skipping"), + gettext_noop("\"%s\" is not a table"), + gettext_noop("Use DROP TABLE to remove a table.")}, {'\0', 0, NULL, NULL, NULL, NULL} }; @@ -433,6 +440,10 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid, void *arg); static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid, void *arg); +static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_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); /* ---------------------------------------------------------------- @@ -492,6 +503,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("ON COMMIT can only be used on temporary tables"))); + if (stmt->partspec != NULL) + { + if (relkind != RELKIND_RELATION) + elog(ERROR, "unexpected relkind: %d", (int) relkind); + + relkind = RELKIND_PARTITIONED_TABLE; + } + /* * Look up the namespace in which we are supposed to create the relation, * check we have permission to create there, lock it against concurrent @@ -596,7 +615,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, * affect other relkinds, but it would complicate interpretOidsOption(). */ localHasOids = interpretOidsOption(stmt->options, - (relkind == RELKIND_RELATION)); + (relkind == RELKIND_RELATION || + relkind == RELKIND_PARTITIONED_TABLE)); descriptor->tdhasoid = (localHasOids || parentOidCount > 0); /* @@ -698,6 +718,65 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, rel = relation_open(relationId, AccessExclusiveLock); /* + * Process the partitioning specification (if any) and store the + * partition key information into the catalog. + */ + if (stmt->partspec) + { + char strategy; + int partnatts, + i; + AttrNumber partattrs[PARTITION_MAX_KEYS]; + Oid partopclass[PARTITION_MAX_KEYS]; + Oid partcollation[PARTITION_MAX_KEYS]; + List *partexprs = NIL; + List *cmds = NIL; + + /* + * We need to transform the raw parsetrees corresponding to partition + * expressions into executable expression trees. Like column defaults + * and CHECK constraints, we could not have done the transformation + * earlier. + */ + stmt->partspec = transformPartitionSpec(rel, stmt->partspec, + &strategy); + ComputePartitionAttrs(rel, stmt->partspec->partParams, + partattrs, &partexprs, partopclass, + partcollation); + + partnatts = list_length(stmt->partspec->partParams); + StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs, + partopclass, partcollation); + + /* Force key columns to be NOT NULL when using range partitioning */ + if (strategy == PARTITION_STRATEGY_RANGE) + { + for (i = 0; i < partnatts; i++) + { + AttrNumber partattno = partattrs[i]; + Form_pg_attribute attform = descriptor->attrs[partattno-1]; + + if (partattno != 0 && !attform->attnotnull) + { + /* Add a subcommand to make this one NOT NULL */ + AlterTableCmd *cmd = makeNode(AlterTableCmd); + + cmd->subtype = AT_SetNotNull; + cmd->name = pstrdup(NameStr(attform->attname)); + cmds = lappend(cmds, cmd); + } + } + + /* + * Although, there cannot be any partitions yet, we still need to + * pass true for recurse; ATPrepSetNotNull() complains if we don't + */ + if (cmds != NIL) + AlterTableInternal(RelationGetRelid(rel), cmds, true); + } + } + + /* * Now add any newly specified column default values and CHECK constraints * to the new relation. These are passed to us in the form of raw * parsetrees; we need to transform them to executable expression trees @@ -927,6 +1006,7 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid, HeapTuple tuple; struct DropRelationCallbackState *state; char relkind; + char expected_relkind; Form_pg_class classform; LOCKMODE heap_lockmode; @@ -955,7 +1035,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid, return; /* concurrently dropped, so nothing to do */ classform = (Form_pg_class) GETSTRUCT(tuple); - if (classform->relkind != relkind) + /* + * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE, + * but RemoveRelations() can only pass one relkind for a given relation. + * It chooses RELKIND_RELATION for both regular and partitioned tables. + * That means we must be careful before giving the wrong type error when + * the relation is RELKIND_PARTITIONED_TABLE. + */ + if (classform->relkind == RELKIND_PARTITIONED_TABLE) + expected_relkind = RELKIND_RELATION; + else + expected_relkind = classform->relkind; + + if (relkind != expected_relkind) DropErrorMsgWrongType(rel->relname, classform->relkind, relkind); /* Allow DROP to either table owner or schema owner */ @@ -1293,7 +1385,8 @@ truncate_check_rel(Relation rel) AclResult aclresult; /* Only allow truncate on regular tables */ - if (rel->rd_rel->relkind != RELKIND_RELATION) + if (rel->rd_rel->relkind != RELKIND_RELATION && + rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table", @@ -1521,6 +1614,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence, */ relation = heap_openrv(parent, ShareUpdateExclusiveLock); + /* Cannot inherit from partitioned tables */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot inherit from partitioned table \"%s\"", + parent->relname))); + if (relation->rd_rel->relkind != RELKIND_RELATION && relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE) ereport(ERROR, @@ -2166,7 +2266,8 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing) relkind != RELKIND_MATVIEW && relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_INDEX && - relkind != RELKIND_FOREIGN_TABLE) + relkind != RELKIND_FOREIGN_TABLE && + relkind != RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table, view, materialized view, composite type, index, or foreign table", @@ -4291,6 +4392,7 @@ ATSimplePermissions(Relation rel, int allowed_targets) switch (rel->rd_rel->relkind) { case RELKIND_RELATION: + case RELKIND_PARTITIONED_TABLE: actual_target = ATT_TABLE; break; case RELKIND_VIEW: @@ -4527,7 +4629,8 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation, att = rel->rd_att->attrs[pg_depend->objsubid - 1]; if (rel->rd_rel->relkind == RELKIND_RELATION || - rel->rd_rel->relkind == RELKIND_MATVIEW) + rel->rd_rel->relkind == RELKIND_MATVIEW || + rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { if (origTypeName) ereport(ERROR, @@ -5250,6 +5353,28 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode) list_free(indexoidlist); /* + * If the table is a range partitioned table, check that the column + * is not in the partition key. + */ + if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + { + PartitionKey key = RelationGetPartitionKey(rel); + int partnatts = get_partition_natts(key), + i; + + for (i = 0; i < partnatts; i++) + { + AttrNumber partattnum = get_partition_col_attnum(key, i); + + if (partattnum == attnum) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("column \"%s\" is in range partition key", + colName))); + } + } + + /* * Okay, actually perform the catalog change ... if needed */ if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull) @@ -5419,7 +5544,8 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE if (rel->rd_rel->relkind != RELKIND_RELATION && rel->rd_rel->relkind != RELKIND_MATVIEW && rel->rd_rel->relkind != RELKIND_INDEX && - rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE) + rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE && + rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table, materialized view, index, or foreign table", @@ -5692,6 +5818,68 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing, } /* + * Checks if attnum is a partition attribute for rel + * + * Sets *used_in_expr if attnum is found to be referenced in some partition + * key expression. It's possible for a column to be both used directly and + * as part of an expression; if that happens, *used_in_expr may end up as + * either true or false. That's OK for current uses of this function, because + * *used_in_expr is only used to tailor the error message text. + */ +static bool +is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr) +{ + PartitionKey key; + int partnatts; + List *partexprs; + ListCell *partexprs_item; + int i; + + if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + return false; + + key = RelationGetPartitionKey(rel); + partnatts = get_partition_natts(key); + partexprs = get_partition_exprs(key); + + partexprs_item = list_head(partexprs); + for (i = 0; i < partnatts; i++) + { + AttrNumber partattno = get_partition_col_attnum(key, i); + + if (partattno != 0) + { + if (attnum == partattno) + { + if (used_in_expr) + *used_in_expr = false; + return true; + } + } + else + { + /* Arbitrary expression */ + Node *expr = (Node *) lfirst(partexprs_item); + Bitmapset *expr_attrs = NULL; + + /* Find all attributes referenced */ + pull_varattnos(expr, 1, &expr_attrs); + partexprs_item = lnext(partexprs_item); + + if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, + expr_attrs)) + { + if (used_in_expr) + *used_in_expr = true; + return true; + } + } + } + + return false; +} + +/* * Return value is the address of the dropped column. */ static ObjectAddress @@ -5705,6 +5893,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName, AttrNumber attnum; List *children; ObjectAddress object; + bool is_expr; /* At top level, permission check was done in ATPrepCmd, else do it */ if (recursing) @@ -5749,6 +5938,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName, errmsg("cannot drop inherited column \"%s\"", colName))); + /* Don't drop columns used in the partition key */ + if (is_partition_attr(rel, attnum, &is_expr)) + { + if (!is_expr) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot drop column named in partition key"))); + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot drop column referenced in partition key expression"))); + } + ReleaseSysCache(tuple); /* @@ -6267,6 +6469,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, * Validity checks (permission checks wait till we have the column * numbers) */ + if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot reference partitioned table \"%s\"", + RelationGetRelationName(pkrel)))); + if (pkrel->rd_rel->relkind != RELKIND_RELATION) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -7886,6 +8094,7 @@ ATPrepAlterColumnType(List **wqueue, NewColumnValue *newval; ParseState *pstate = make_parsestate(NULL); AclResult aclresult; + bool is_expr; if (rel->rd_rel->reloftype && !recursing) ereport(ERROR, @@ -7916,6 +8125,19 @@ ATPrepAlterColumnType(List **wqueue, errmsg("cannot alter inherited column \"%s\"", colName))); + /* Don't alter columns used in the partition key */ + if (is_partition_attr(rel, attnum, &is_expr)) + { + if (!is_expr) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot alter type of column named in partition key"))); + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot alter type of column referenced in partition key expression"))); + } + /* Look up the target type */ typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod); @@ -7931,7 +8153,8 @@ ATPrepAlterColumnType(List **wqueue, list_make1_oid(rel->rd_rel->reltype), false); - if (tab->relkind == RELKIND_RELATION) + if (tab->relkind == RELKIND_RELATION || + tab->relkind == RELKIND_PARTITIONED_TABLE) { /* * Set up an expression to transform the old data value to the new @@ -8961,6 +9184,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock case RELKIND_VIEW: case RELKIND_MATVIEW: case RELKIND_FOREIGN_TABLE: + case RELKIND_PARTITIONED_TABLE: /* ok to change owner */ break; case RELKIND_INDEX: @@ -9422,6 +9646,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, case RELKIND_RELATION: case RELKIND_TOASTVALUE: case RELKIND_MATVIEW: + case RELKIND_PARTITIONED_TABLE: (void) heap_reloptions(rel->rd_rel->relkind, newOptions, true); break; case RELKIND_VIEW: @@ -9842,7 +10067,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt) /* Only move the object type requested */ if ((stmt->objtype == OBJECT_TABLE && - relForm->relkind != RELKIND_RELATION) || + relForm->relkind != RELKIND_RELATION && + relForm->relkind != RELKIND_PARTITIONED_TABLE) || (stmt->objtype == OBJECT_INDEX && relForm->relkind != RELKIND_INDEX) || (stmt->objtype == OBJECT_MATVIEW && @@ -10041,6 +10267,11 @@ ATPrepAddInherit(Relation child_rel) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot change inheritance of typed table"))); + + if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot change inheritance of partitioned table"))); } /* @@ -10092,6 +10323,13 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot inherit to temporary relation of another session"))); + /* Prevent partitioned tables from becoming inheritance parents */ + if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot inherit from partitioned table \"%s\"", + parent->relname))); + /* * Check for duplicates in the list of parents, and determine the highest * inhseqno already present; we'll use the next one for the new parent. @@ -11481,7 +11719,8 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid, /* Fix other dependent stuff */ if (rel->rd_rel->relkind == RELKIND_RELATION || - rel->rd_rel->relkind == RELKIND_MATVIEW) + rel->rd_rel->relkind == RELKIND_MATVIEW || + rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved); AlterSeqNamespaces(classRel, rel, oldNspOid, nspOid, @@ -11930,7 +12169,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation, if (!relkind) return; if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE && - relkind != RELKIND_MATVIEW) + relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table or materialized view", relation->relname))); @@ -12087,7 +12326,8 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid, relkind != RELKIND_VIEW && relkind != RELKIND_MATVIEW && relkind != RELKIND_SEQUENCE && - relkind != RELKIND_FOREIGN_TABLE) + relkind != RELKIND_FOREIGN_TABLE && + relkind != RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table, view, materialized view, sequence, or foreign table", @@ -12095,3 +12335,250 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid, ReleaseSysCache(tuple); } + +/* + * Transform any expressions present in the partition key + */ +static PartitionSpec * +transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy) +{ + PartitionSpec *newspec; + ParseState *pstate; + RangeTblEntry *rte; + ListCell *l; + + newspec = (PartitionSpec *) makeNode(PartitionSpec); + + newspec->strategy = partspec->strategy; + newspec->location = partspec->location; + newspec->partParams = NIL; + + /* Parse partitioning strategy name */ + if (!pg_strcasecmp(partspec->strategy, "list")) + *strategy = PARTITION_STRATEGY_LIST; + else if (!pg_strcasecmp(partspec->strategy, "range")) + *strategy = PARTITION_STRATEGY_RANGE; + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized partitioning strategy \"%s\"", + partspec->strategy))); + + /* + * Create a dummy ParseState and insert the target relation as its sole + * rangetable entry. We need a ParseState for transformExpr. + */ + pstate = make_parsestate(NULL); + rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true); + addRTEtoQuery(pstate, rte, true, true, true); + + /* take care of any partition expressions */ + foreach(l, partspec->partParams) + { + ListCell *lc; + PartitionElem *pelem = (PartitionElem *) lfirst(l); + + /* Check for PARTITION BY ... (foo, foo) */ + foreach(lc, newspec->partParams) + { + PartitionElem *pparam = (PartitionElem *) lfirst(lc); + + if (pelem->name && pparam->name && + !strcmp(pelem->name, pparam->name)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_COLUMN), + errmsg("column \"%s\" appears more than once in partition key", + pelem->name), + parser_errposition(pstate, pelem->location))); + } + + if (pelem->expr) + { + /* Now do parse transformation of the expression */ + pelem->expr = transformExpr(pstate, pelem->expr, + EXPR_KIND_PARTITION_EXPRESSION); + + /* we have to fix its collations too */ + assign_expr_collations(pstate, pelem->expr); + } + + newspec->partParams = lappend(newspec->partParams, pelem); + } + + return newspec; +} + +/* + * Compute per-partition-column information from a list of PartitionElem's + */ +static void +ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, + List **partexprs, Oid *partopclass, Oid *partcollation) +{ + int attn; + ListCell *lc; + + attn = 0; + foreach(lc, partParams) + { + PartitionElem *pelem = (PartitionElem *) lfirst(lc); + Oid atttype; + Oid attcollation; + + if (pelem->name != NULL) + { + /* Simple attribute reference */ + HeapTuple atttuple; + Form_pg_attribute attform; + + atttuple = SearchSysCacheAttName(RelationGetRelid(rel), pelem->name); + if (!HeapTupleIsValid(atttuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" named in partition key does not exist", + pelem->name))); + attform = (Form_pg_attribute) GETSTRUCT(atttuple); + + if (attform->attnum <= 0) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("cannot use system column \"%s\" in partition key", + pelem->name))); + + partattrs[attn] = attform->attnum; + atttype = attform->atttypid; + attcollation = attform->attcollation; + ReleaseSysCache(atttuple); + + /* Note that whole-row references can't happen here; see below */ + } + else + { + /* Expression */ + Node *expr = pelem->expr; + + Assert(expr != NULL); + atttype = exprType(expr); + attcollation = exprCollation(expr); + + /* + * Strip any top-level COLLATE clause. This ensures that we treat + * "x COLLATE y" and "(x COLLATE y)" alike. + */ + while (IsA(expr, CollateExpr)) + expr = (Node *) ((CollateExpr *) expr)->arg; + + if (IsA(expr, Var) && + ((Var *) expr)->varattno != InvalidAttrNumber) + { + /* + * User wrote "(column)" or "(column COLLATE something)". + * Treat it like simple attribute anyway. + */ + partattrs[attn] = ((Var *) expr)->varattno; + } + else + { + Bitmapset *expr_attrs = NULL; + + partattrs[attn] = 0; /* marks the column as expression */ + *partexprs = lappend(*partexprs, expr); + + /* + * Note that expression_planner does not change the passed in + * expression destructively and we have already saved the + * expression to be stored into the catalog above. + */ + expr = (Node *) expression_planner((Expr *) expr); + + /* + * Partition expression cannot contain mutable functions, + * because a given row must always map to the same partition + * as long as there is no change in the partition boundary + * structure. + */ + if (contain_mutable_functions(expr)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("functions in partition key expression must be marked IMMUTABLE"))); + + /* + * While it is not exactly *wrong* for an expression to be + * a constant value, it seems better to prevent such input. + */ + if (IsA(expr, Const)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot use constant expression as partition key"))); + + /* + * transformPartitionSpec() should have already rejected subqueries, + * aggregates, window functions, and SRFs, based on the EXPR_KIND_ + * for partition expressions. + */ + + /* Cannot have expressions containing whole-row references */ + pull_varattnos(expr, 1, &expr_attrs); + if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, + expr_attrs)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("partition key expressions cannot contain whole-row references"))); + } + } + + /* + * Apply collation override if any + */ + if (pelem->collation) + attcollation = get_collation_oid(pelem->collation, false); + + /* + * Check we have a collation iff it's a collatable type. The only + * expected failures here are (1) COLLATE applied to a noncollatable + * type, or (2) partition expression had an unresolved collation. + * But we might as well code this to be a complete consistency check. + */ + if (type_is_collatable(atttype)) + { + if (!OidIsValid(attcollation)) + ereport(ERROR, + (errcode(ERRCODE_INDETERMINATE_COLLATION), + errmsg("could not determine which collation to use for partition expression"), + errhint("Use the COLLATE clause to set the collation explicitly."))); + } + else + { + if (OidIsValid(attcollation)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("collations are not supported by type %s", + format_type_be(atttype)))); + } + + partcollation[attn] = attcollation; + + /* + * Identify a btree opclass to use. Currently, we use only btree + * operators, which seems enough for list and range partitioning. + */ + if (!pelem->opclass) + { + partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID); + + if (!OidIsValid(partopclass[attn])) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("data type %s has no default btree operator class", + format_type_be(atttype)), + errhint("You must specify a btree operator class or define a default btree operator class for the data type."))); + } + else + partopclass[attn] = ResolveOpClass(pelem->opclass, + atttype, + "btree", + BTREE_AM_OID); + + attn++; + } +} diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 1c264b7..02e9693 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -176,7 +176,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, * Triggers must be on tables or views, and there are additional * relation-type-specific restrictions. */ - if (rel->rd_rel->relkind == RELKIND_RELATION) + if (rel->rd_rel->relkind == RELKIND_RELATION || + rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { /* Tables can't have INSTEAD OF triggers */ if (stmt->timing != TRIGGER_TYPE_BEFORE && @@ -186,6 +187,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, errmsg("\"%s\" is a table", RelationGetRelationName(rel)), errdetail("Tables cannot have INSTEAD OF triggers."))); + /* Disallow ROW triggers on partitioned tables */ + if (stmt->row && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is a partitioned table", + RelationGetRelationName(rel)), + errdetail("Partitioned tables cannot have ROW triggers."))); } else if (rel->rd_rel->relkind == RELKIND_VIEW) { @@ -1211,7 +1219,8 @@ RemoveTriggerById(Oid trigOid) if (rel->rd_rel->relkind != RELKIND_RELATION && rel->rd_rel->relkind != RELKIND_VIEW && - rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE) + rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE && + rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table, view, or foreign table", @@ -1316,7 +1325,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid, /* only tables and views can have triggers */ if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW && - form->relkind != RELKIND_FOREIGN_TABLE) + form->relkind != RELKIND_FOREIGN_TABLE && + form->relkind != RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table, view, or foreign table", diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 58bbf55..b1be2f7 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -1314,7 +1314,8 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params) */ if (onerel->rd_rel->relkind != RELKIND_RELATION && onerel->rd_rel->relkind != RELKIND_MATVIEW && - onerel->rd_rel->relkind != RELKIND_TOASTVALUE) + onerel->rd_rel->relkind != RELKIND_TOASTVALUE && + onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) { ereport(WARNING, (errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables", diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 32bb3f9..9773272 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation) switch (resultRel->rd_rel->relkind) { case RELKIND_RELATION: + case RELKIND_PARTITIONED_TABLE: /* OK */ break; case RELKIND_SEQUENCE: @@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType) switch (rel->rd_rel->relkind) { case RELKIND_RELATION: + case RELKIND_PARTITIONED_TABLE: /* OK */ break; case RELKIND_SEQUENCE: diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index efb0c5e..29d5f57 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1886,7 +1886,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind; if (relkind == RELKIND_RELATION || - relkind == RELKIND_MATVIEW) + relkind == RELKIND_MATVIEW || + relkind == RELKIND_PARTITIONED_TABLE) { j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); if (!AttributeNumberIsValid(j->jf_junkAttNo)) diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 04e49b7..1c978c0 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3030,6 +3030,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode) COPY_NODE_FIELD(relation); COPY_NODE_FIELD(tableElts); COPY_NODE_FIELD(inhRelations); + COPY_NODE_FIELD(partspec); COPY_NODE_FIELD(ofTypename); COPY_NODE_FIELD(constraints); COPY_NODE_FIELD(options); @@ -4187,6 +4188,33 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from) return newnode; } +static PartitionSpec * +_copyPartitionSpec(const PartitionSpec *from) +{ + + PartitionSpec *newnode = makeNode(PartitionSpec); + + COPY_STRING_FIELD(strategy); + COPY_NODE_FIELD(partParams); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +static PartitionElem * +_copyPartitionElem(const PartitionElem *from) +{ + PartitionElem *newnode = makeNode(PartitionElem); + + COPY_STRING_FIELD(name); + COPY_NODE_FIELD(expr); + COPY_NODE_FIELD(collation); + COPY_NODE_FIELD(opclass); + COPY_LOCATION_FIELD(location); + + return newnode; +} + /* **************************************************************** * pg_list.h copy functions * **************************************************************** @@ -5104,6 +5132,12 @@ copyObject(const void *from) case T_TriggerTransition: retval = _copyTriggerTransition(from); break; + case T_PartitionSpec: + retval = _copyPartitionSpec(from); + break; + case T_PartitionElem: + retval = _copyPartitionElem(from); + break; /* * MISCELLANEOUS NODES diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 2eaf41c..7d0391d 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1168,6 +1168,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b) COMPARE_NODE_FIELD(relation); COMPARE_NODE_FIELD(tableElts); COMPARE_NODE_FIELD(inhRelations); + COMPARE_NODE_FIELD(partspec); COMPARE_NODE_FIELD(ofTypename); COMPARE_NODE_FIELD(constraints); COMPARE_NODE_FIELD(options); @@ -2645,6 +2646,28 @@ _equalTriggerTransition(const TriggerTransition *a, const TriggerTransition *b) return true; } +static bool +_equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b) +{ + COMPARE_STRING_FIELD(strategy); + COMPARE_NODE_FIELD(partParams); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool +_equalPartitionElem(const PartitionElem *a, const PartitionElem *b) +{ + COMPARE_STRING_FIELD(name); + COMPARE_NODE_FIELD(expr); + COMPARE_NODE_FIELD(collation); + COMPARE_NODE_FIELD(opclass); + COMPARE_LOCATION_FIELD(location); + + return true; +} + /* * Stuff from pg_list.h */ @@ -3401,6 +3424,12 @@ equal(const void *a, const void *b) case T_TriggerTransition: retval = _equalTriggerTransition(a, b); break; + case T_PartitionSpec: + retval = _equalPartitionSpec(a, b); + break; + case T_PartitionElem: + retval = _equalPartitionElem(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 748b687..323daf5 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node) WRITE_NODE_FIELD(relation); WRITE_NODE_FIELD(tableElts); WRITE_NODE_FIELD(inhRelations); + WRITE_NODE_FIELD(partspec); WRITE_NODE_FIELD(ofTypename); WRITE_NODE_FIELD(constraints); WRITE_NODE_FIELD(options); @@ -3277,6 +3278,27 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node) appendStringInfo(str, " %u", node->conpfeqop[i]); } +static void +_outPartitionSpec(StringInfo str, const PartitionSpec *node) +{ + WRITE_NODE_TYPE("PARTITIONBY"); + + WRITE_STRING_FIELD(strategy); + WRITE_NODE_FIELD(partParams); + WRITE_LOCATION_FIELD(location); +} + +static void +_outPartitionElem(StringInfo str, const PartitionElem *node) +{ + WRITE_NODE_TYPE("PARTITIONELEM"); + + WRITE_STRING_FIELD(name); + WRITE_NODE_FIELD(expr); + WRITE_NODE_FIELD(collation); + WRITE_NODE_FIELD(opclass); + WRITE_LOCATION_FIELD(location); +} /* * outNode - @@ -3865,6 +3887,12 @@ outNode(StringInfo str, const void *obj) case T_TriggerTransition: _outTriggerTransition(str, obj); break; + case T_PartitionSpec: + _outPartitionSpec(str, obj); + break; + case T_PartitionElem: + _outPartitionElem(str, obj); + break; default: diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 367bc2e..1680fea 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -229,6 +229,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); struct ImportQual *importqual; InsertStmt *istmt; VariableSetStmt *vsetstmt; + PartitionElem *partelem; + PartitionSpec *partspec; } %type stmt schema_stmt @@ -544,6 +546,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); opt_frame_clause frame_extent frame_bound %type opt_existing_window_name %type opt_if_not_exists +%type PartitionSpec OptPartitionSpec +%type part_strategy +%type part_elem +%type part_params /* * Non-keyword token types. These are hard-wired into the "flex" lexer. @@ -2811,69 +2817,75 @@ copy_generic_opt_arg_list_item: *****************************************************************************/ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')' - OptInherit OptWith OnCommitOption OptTableSpace + OptInherit OptPartitionSpec OptWith OnCommitOption OptTableSpace { CreateStmt *n = makeNode(CreateStmt); $4->relpersistence = $2; n->relation = $4; n->tableElts = $6; n->inhRelations = $8; + n->partspec = $9; n->ofTypename = NULL; n->constraints = NIL; - n->options = $9; - n->oncommit = $10; - n->tablespacename = $11; + n->options = $10; + n->oncommit = $11; + n->tablespacename = $12; n->if_not_exists = false; $$ = (Node *)n; } | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '(' - OptTableElementList ')' OptInherit OptWith OnCommitOption - OptTableSpace + OptTableElementList ')' OptInherit OptPartitionSpec OptWith + OnCommitOption OptTableSpace { CreateStmt *n = makeNode(CreateStmt); $7->relpersistence = $2; n->relation = $7; n->tableElts = $9; n->inhRelations = $11; + n->partspec = $12; n->ofTypename = NULL; n->constraints = NIL; - n->options = $12; - n->oncommit = $13; - n->tablespacename = $14; + n->options = $13; + n->oncommit = $14; + n->tablespacename = $15; n->if_not_exists = true; $$ = (Node *)n; } | CREATE OptTemp TABLE qualified_name OF any_name - OptTypedTableElementList OptWith OnCommitOption OptTableSpace + OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption + OptTableSpace { CreateStmt *n = makeNode(CreateStmt); $4->relpersistence = $2; n->relation = $4; n->tableElts = $7; n->inhRelations = NIL; + n->partspec = $8; n->ofTypename = makeTypeNameFromNameList($6); n->ofTypename->location = @6; n->constraints = NIL; - n->options = $8; - n->oncommit = $9; - n->tablespacename = $10; + n->options = $9; + n->oncommit = $10; + n->tablespacename = $11; n->if_not_exists = false; $$ = (Node *)n; } | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name - OptTypedTableElementList OptWith OnCommitOption OptTableSpace + OptTypedTableElementList OptPartitionSpec OptWith OnCommitOption + OptTableSpace { CreateStmt *n = makeNode(CreateStmt); $7->relpersistence = $2; n->relation = $7; n->tableElts = $10; n->inhRelations = NIL; + n->partspec = $11; n->ofTypename = makeTypeNameFromNameList($9); n->ofTypename->location = @9; n->constraints = NIL; - n->options = $11; - n->oncommit = $12; - n->tablespacename = $13; + n->options = $12; + n->oncommit = $13; + n->tablespacename = $14; n->if_not_exists = true; $$ = (Node *)n; } @@ -3418,6 +3430,65 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; } | /*EMPTY*/ { $$ = NIL; } ; +/* Optional partition key specification */ +OptPartitionSpec: PartitionSpec { $$ = $1; } + | /*EMPTY*/ { $$ = NULL; } + ; + +PartitionSpec: PARTITION BY part_strategy '(' part_params ')' + { + PartitionSpec *n = makeNode(PartitionSpec); + + n->strategy = $3; + n->partParams = $5; + n->location = @1; + + $$ = n; + } + ; + +part_strategy: IDENT { $$ = $1; } + | unreserved_keyword { $$ = pstrdup($1); } + ; + +part_params: part_elem { $$ = list_make1($1); } + | part_params ',' part_elem { $$ = lappend($1, $3); } + ; + +part_elem: ColId opt_collate opt_class + { + PartitionElem *n = makeNode(PartitionElem); + + n->name = $1; + n->expr = NULL; + n->collation = $2; + n->opclass = $3; + n->location = @1; + $$ = n; + } + | func_expr_windowless opt_collate opt_class + { + PartitionElem *n = makeNode(PartitionElem); + + n->name = NULL; + n->expr = $1; + n->collation = $2; + n->opclass = $3; + n->location = @1; + $$ = n; + } + | '(' a_expr ')' opt_collate opt_class + { + PartitionElem *n = makeNode(PartitionElem); + + n->name = NULL; + n->expr = $2; + n->collation = $4; + n->opclass = $5; + n->location = @1; + $$ = n; + } + ; /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */ OptWith: WITH reloptions { $$ = $2; } diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 481a4dd..92d1577 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -501,6 +501,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) err = _("grouping operations are not allowed in trigger WHEN conditions"); break; + case EXPR_KIND_PARTITION_EXPRESSION: + if (isAgg) + err = _("aggregate functions are not allowed in partition key expression"); + else + err = _("grouping operations are not allowed in partition key expression"); + + break; /* * There is intentionally no default: case here, so that the @@ -858,6 +865,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, case EXPR_KIND_TRIGGER_WHEN: err = _("window functions are not allowed in trigger WHEN conditions"); break; + case EXPR_KIND_PARTITION_EXPRESSION: + err = _("window functions are not allowed in partition key expression"); + break; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 17d1cbf..8a2bdf0 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -1843,6 +1843,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink) case EXPR_KIND_TRIGGER_WHEN: err = _("cannot use subquery in trigger WHEN condition"); break; + case EXPR_KIND_PARTITION_EXPRESSION: + err = _("cannot use subquery in partition key expression"); + break; /* * There is intentionally no default: case here, so that the @@ -3446,6 +3449,8 @@ ParseExprKindName(ParseExprKind exprKind) return "EXECUTE"; case EXPR_KIND_TRIGGER_WHEN: return "WHEN"; + case EXPR_KIND_PARTITION_EXPRESSION: + return "PARTITION BY"; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index 56c9a42..7d9b415 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -2166,6 +2166,9 @@ check_srf_call_placement(ParseState *pstate, int location) case EXPR_KIND_TRIGGER_WHEN: err = _("set-returning functions are not allowed in trigger WHEN conditions"); break; + case EXPR_KIND_PARTITION_EXPRESSION: + err = _("set-returning functions are not allowed in partition key expression"); + break; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 0670bc2..fc896a2 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -87,6 +87,7 @@ typedef struct List *alist; /* "after list" of things to do after creating * the table */ IndexStmt *pkey; /* PRIMARY KEY index, if any */ + bool ispartitioned; /* true if table is partitioned */ } CreateStmtContext; /* State shared by transformCreateSchemaStmt and its subroutines */ @@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) cxt.blist = NIL; cxt.alist = NIL; cxt.pkey = NULL; + cxt.ispartitioned = stmt->partspec != NULL; /* * Notice that we allow OIDs here only for plain tables, even though @@ -247,6 +249,28 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) if (stmt->ofTypename) transformOfType(&cxt, stmt->ofTypename); + if (stmt->partspec) + { + int partnatts = list_length(stmt->partspec->partParams); + + if (stmt->inhRelations) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot create partitioned table as inheritance child"))); + + if (partnatts > PARTITION_MAX_KEYS) + ereport(ERROR, + (errcode(ERRCODE_TOO_MANY_COLUMNS), + errmsg("cannot partition using more than %d columns", + PARTITION_MAX_KEYS))); + + if (!pg_strcasecmp(stmt->partspec->strategy, "list") && + partnatts > 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot list partition using more than one column"))); + } + /* * Run through each primary element in the table creation clause. Separate * column defs from constraints, and do preliminary analysis. We have to @@ -583,6 +607,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) errmsg("primary key constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); + if (cxt->ispartitioned) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("primary key constraints are not supported on partitioned tables"), + parser_errposition(cxt->pstate, + constraint->location))); /* FALL THRU */ case CONSTR_UNIQUE: @@ -592,6 +622,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) errmsg("unique constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); + if (cxt->ispartitioned) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unique constraints are not supported on partitioned tables"), + parser_errposition(cxt->pstate, + constraint->location))); if (constraint->keys == NIL) constraint->keys = list_make1(makeString(column->colname)); cxt->ixconstraints = lappend(cxt->ixconstraints, constraint); @@ -609,6 +645,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) errmsg("foreign key constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); + if (cxt->ispartitioned) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("foreign key constraints are not supported on partitioned tables"), + parser_errposition(cxt->pstate, + constraint->location))); /* * Fill in the current attribute's name and throw it into the @@ -674,6 +716,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint) errmsg("primary key constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); + if (cxt->ispartitioned) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("primary key constraints are not supported on partitioned tables"), + parser_errposition(cxt->pstate, + constraint->location))); cxt->ixconstraints = lappend(cxt->ixconstraints, constraint); break; @@ -684,6 +732,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint) errmsg("unique constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); + if (cxt->ispartitioned) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unique constraints are not supported on partitioned tables"), + parser_errposition(cxt->pstate, + constraint->location))); cxt->ixconstraints = lappend(cxt->ixconstraints, constraint); break; @@ -694,6 +748,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint) errmsg("exclusion constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); + if (cxt->ispartitioned) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("exclusion constraints are not supported on partitioned tables"), + parser_errposition(cxt->pstate, + constraint->location))); cxt->ixconstraints = lappend(cxt->ixconstraints, constraint); break; @@ -708,6 +768,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint) errmsg("foreign key constraints are not supported on foreign tables"), parser_errposition(cxt->pstate, constraint->location))); + if (cxt->ispartitioned) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("foreign key constraints are not supported on partitioned tables"), + parser_errposition(cxt->pstate, + constraint->location))); cxt->fkconstraints = lappend(cxt->fkconstraints, constraint); break; @@ -763,7 +829,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla relation->rd_rel->relkind != RELKIND_VIEW && relation->rd_rel->relkind != RELKIND_MATVIEW && relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE && - relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE) + relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE && + relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table", @@ -1854,7 +1921,8 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt) rel = heap_openrv(inh, AccessShareLock); /* check user requested inheritance from valid relkind */ if (rel->rd_rel->relkind != RELKIND_RELATION && - rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE) + rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE && + rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("inherited relation \"%s\" is not a table or foreign table", @@ -2512,6 +2580,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, cxt.blist = NIL; cxt.alist = NIL; cxt.pkey = NULL; + cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); /* * The only subtypes that currently require parse transformation handling diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c index f82d891..32e1328 100644 --- a/src/backend/rewrite/rewriteDefine.c +++ b/src/backend/rewrite/rewriteDefine.c @@ -261,7 +261,8 @@ DefineQueryRewrite(char *rulename, */ if (event_relation->rd_rel->relkind != RELKIND_RELATION && event_relation->rd_rel->relkind != RELKIND_MATVIEW && - event_relation->rd_rel->relkind != RELKIND_VIEW) + event_relation->rd_rel->relkind != RELKIND_VIEW && + event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table or view", diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 65c3d6e..bf4f098 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -1231,7 +1231,8 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte, TargetEntry *tle; if (target_relation->rd_rel->relkind == RELKIND_RELATION || - target_relation->rd_rel->relkind == RELKIND_MATVIEW) + target_relation->rd_rel->relkind == RELKIND_MATVIEW || + target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { /* * Emit CTID so that executor can find the row to update or delete. diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c index e029116..2871adc 100644 --- a/src/backend/rewrite/rowsecurity.c +++ b/src/backend/rewrite/rowsecurity.c @@ -121,7 +121,8 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index, *hasSubLinks = false; /* If this is not a normal relation, just return immediately */ - if (rte->relkind != RELKIND_RELATION) + if (rte->relkind != RELKIND_RELATION && + rte->relkind != RELKIND_PARTITIONED_TABLE) return; /* Switch to checkAsUser if it's set */ diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 79e0b1f..7f3ba74 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -32,6 +32,7 @@ #include "access/htup_details.h" #include "access/multixact.h" +#include "access/nbtree.h" #include "access/reloptions.h" #include "access/sysattr.h" #include "access/xact.h" @@ -49,6 +50,7 @@ #include "catalog/pg_database.h" #include "catalog/pg_namespace.h" #include "catalog/pg_opclass.h" +#include "catalog/pg_partitioned_table.h" #include "catalog/pg_proc.h" #include "catalog/pg_rewrite.h" #include "catalog/pg_shseclabel.h" @@ -258,6 +260,8 @@ static HeapTuple ScanPgRelation(Oid targetRelId, bool indexOK, bool force_non_hi static Relation AllocateRelationDesc(Form_pg_class relp); static void RelationParseRelOptions(Relation relation, HeapTuple tuple); static void RelationBuildTupleDesc(Relation relation); +static void RelationBuildPartitionKey(Relation relation); +static PartitionKey copy_partition_key(PartitionKey fromkey); static Relation RelationBuildDesc(Oid targetRelId, bool insertIt); static void RelationInitPhysicalAddr(Relation relation); static void load_critical_index(Oid indexoid, Oid heapoid); @@ -435,6 +439,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple) case RELKIND_INDEX: case RELKIND_VIEW: case RELKIND_MATVIEW: + case RELKIND_PARTITIONED_TABLE: break; default: return; @@ -796,6 +801,236 @@ RelationBuildRuleLock(Relation relation) } /* + * RelationBuildPartitionKey + * Build and attach to relcache partition key data of relation + * + * Partitioning key data is stored in CacheMemoryContext to ensure it survives + * as long as the relcache. To avoid leaking memory in that context in case + * of an error partway through this function, we build the structure in the + * working context (which must be short-lived) and copy the completed + * structure into the cache memory. + * + * Also, since the structure being created here is sufficiently complex, we + * make a private child context of CacheMemoryContext for each relation that + * has associated partition key information. That means no complicated logic + * to free individual elements whenever the relcache entry is flushed - just + * delete the context. + */ +static void +RelationBuildPartitionKey(Relation relation) +{ + Form_pg_partitioned_table form; + HeapTuple tuple; + bool isnull; + int i; + PartitionKey key; + AttrNumber *attrs; + oidvector *opclass; + oidvector *collation; + ListCell *partexprs_item; + Datum datum; + MemoryContext partkeycxt, + oldcxt; + + tuple = SearchSysCache1(PARTRELID, + ObjectIdGetDatum(RelationGetRelid(relation))); + /* + * The following happens when we have created our pg_class entry but not + * the pg_partitioned_table entry yet. + */ + if (!HeapTupleIsValid(tuple)) + return; + + key = (PartitionKey) palloc0(sizeof(PartitionKeyData)); + + /* Fixed-length attributes */ + form = (Form_pg_partitioned_table) GETSTRUCT(tuple); + key->strategy = form->partstrat; + key->partnatts = form->partnatts; + + /* + * We can rely on the first variable-length attribute being mapped to + * the relevant field of the catalog's C struct, because all previous + * attributes are non-nullable and fixed-length. + */ + attrs = form->partattrs.values; + + /* But use the hard way to retrieve further variable-length attributes */ + /* Operator class */ + datum = SysCacheGetAttr(PARTRELID, tuple, + Anum_pg_partitioned_table_partclass, &isnull); + Assert(!isnull); + opclass = (oidvector *) DatumGetPointer(datum); + + /* Collation */ + datum = SysCacheGetAttr(PARTRELID, tuple, + Anum_pg_partitioned_table_partcollation, &isnull); + Assert(!isnull); + collation = (oidvector *) DatumGetPointer(datum); + + /* Expressions */ + datum = SysCacheGetAttr(PARTRELID, tuple, + Anum_pg_partitioned_table_partexprs, &isnull); + if (!isnull) + { + char *exprString; + Node *expr; + + exprString = TextDatumGetCString(datum); + expr = stringToNode(exprString); + pfree(exprString); + + /* + * Run the expressions through const-simplification since the planner + * will be comparing them to similarly-processed qual clause operands, + * and may fail to detect valid matches without this step. We don't + * need to bother with canonicalize_qual() though, because partition + * expressions are not full-fledged qualification clauses. + */ + expr = eval_const_expressions(NULL, (Node *) expr); + + /* May as well fix opfuncids too */ + fix_opfuncids((Node *) expr); + key->partexprs = (List *) expr; + } + + key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber)); + key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid)); + key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid)); + key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo)); + + key->partcollation = (Oid *) palloc0(key->partnatts * sizeof(Oid)); + + /* Gather type and collation info as well */ + key->parttypid = (Oid *) palloc0(key->partnatts * sizeof(Oid)); + key->parttypmod = (int32 *) palloc0(key->partnatts * sizeof(int32)); + key->parttyplen = (int16 *) palloc0(key->partnatts * sizeof(int16)); + key->parttypbyval = (bool *) palloc0(key->partnatts * sizeof(bool)); + key->parttypalign = (char *) palloc0(key->partnatts * sizeof(char)); + key->parttypcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid)); + + /* Copy partattrs and fill other per-attribute info */ + memcpy(key->partattrs, attrs, key->partnatts * sizeof(int16)); + partexprs_item = list_head(key->partexprs); + for (i = 0; i < key->partnatts; i++) + { + AttrNumber attno = key->partattrs[i]; + HeapTuple opclasstup; + Form_pg_opclass opclassform; + Oid funcid; + + /* Collect opfamily information */ + opclasstup = SearchSysCache1(CLAOID, + ObjectIdGetDatum(opclass->values[i])); + if (!HeapTupleIsValid(opclasstup)) + elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]); + + opclassform = (Form_pg_opclass) GETSTRUCT(opclasstup); + key->partopfamily[i] = opclassform->opcfamily; + key->partopcintype[i] = opclassform->opcintype; + + /* + * A btree support function covers the cases of list and range methods + * currently supported. + */ + funcid = get_opfamily_proc(opclassform->opcfamily, + opclassform->opcintype, + opclassform->opcintype, + BTORDER_PROC); + + fmgr_info(funcid, &key->partsupfunc[i]); + + /* Collation */ + key->partcollation[i] = collation->values[i]; + + /* Collect type information */ + if (attno != 0) + { + key->parttypid[i] = relation->rd_att->attrs[attno - 1]->atttypid; + key->parttypmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod; + key->parttypcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation; + } + else + { + key->parttypid[i] = exprType(lfirst(partexprs_item)); + key->parttypmod[i] = exprTypmod(lfirst(partexprs_item)); + key->parttypcoll[i] = exprCollation(lfirst(partexprs_item)); + } + get_typlenbyvalalign(key->parttypid[i], + &key->parttyplen[i], + &key->parttypbyval[i], + &key->parttypalign[i]); + + ReleaseSysCache(opclasstup); + } + + ReleaseSysCache(tuple); + + /* Success --- now copy to the cache memory */ + partkeycxt = AllocSetContextCreate(CacheMemoryContext, + RelationGetRelationName(relation), + ALLOCSET_SMALL_SIZES); + relation->rd_partkeycxt = partkeycxt; + oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt); + relation->rd_partkey = copy_partition_key(key); + MemoryContextSwitchTo(oldcxt); +} + +/* + * copy_partition_key + * + * The copy is allocated in the current memory context. + */ +static PartitionKey +copy_partition_key(PartitionKey fromkey) +{ + PartitionKey newkey; + int n; + + newkey = (PartitionKey) palloc(sizeof(PartitionKeyData)); + + newkey->strategy = fromkey->strategy; + newkey->partnatts = n = fromkey->partnatts; + + newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber)); + memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber)); + + newkey->partexprs = copyObject(fromkey->partexprs); + + newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid)); + memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid)); + + newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid)); + memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid)); + + newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo)); + memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo)); + + newkey->partcollation = (Oid *) palloc(n * sizeof(Oid)); + memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid)); + + newkey->parttypid = (Oid *) palloc(n * sizeof(Oid)); + memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid)); + + newkey->parttypmod = (int32 *) palloc(n * sizeof(int32)); + memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32)); + + newkey->parttyplen = (int16 *) palloc(n * sizeof(int16)); + memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16)); + + newkey->parttypbyval = (bool *) palloc(n * sizeof(bool)); + memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool)); + + newkey->parttypalign = (char *) palloc(n * sizeof(bool)); + memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char)); + + newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid)); + memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid)); + + return newkey; +} + +/* * equalRuleLocks * * Determine whether two RuleLocks are equivalent @@ -1050,6 +1285,15 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) relation->rd_fkeylist = NIL; relation->rd_fkeyvalid = false; + /* if it's a partitioned table, initialize key info */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + RelationBuildPartitionKey(relation); + else + { + relation->rd_partkeycxt = NULL; + relation->rd_partkey = NULL; + } + /* * if it's an index, initialize index-related information */ @@ -2042,6 +2286,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc) MemoryContextDelete(relation->rd_rulescxt); if (relation->rd_rsdesc) MemoryContextDelete(relation->rd_rsdesc->rscxt); + if (relation->rd_partkeycxt) + MemoryContextDelete(relation->rd_partkeycxt); if (relation->rd_fdwroutine) pfree(relation->rd_fdwroutine); pfree(relation); @@ -2983,7 +3229,9 @@ RelationBuildLocalRelation(const char *relname, /* system relations and non-table objects don't have one */ if (!IsSystemNamespace(relnamespace) && - (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW)) + (relkind == RELKIND_RELATION || + relkind == RELKIND_MATVIEW || + relkind == RELKIND_PARTITIONED_TABLE)) rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT; else rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING; @@ -3514,6 +3762,17 @@ 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); + + restart = true; + } + /* Release hold on the relation */ RelationDecrementReferenceCount(relation); @@ -4267,6 +4526,8 @@ RelationGetIndexExpressions(Relation relation) */ result = (List *) eval_const_expressions(NULL, (Node *) result); + result = (List *) canonicalize_qual((Expr *) result); + /* May as well fix opfuncids too */ fix_opfuncids((Node *) result); @@ -5035,6 +5296,8 @@ load_relcache_init_file(bool shared) rel->rd_rulescxt = NULL; rel->trigdesc = NULL; rel->rd_rsdesc = NULL; + rel->rd_partkeycxt = NULL; + rel->rd_partkey = NULL; rel->rd_indexprs = NIL; rel->rd_indpred = NIL; rel->rd_exclops = NULL; diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 65ffe84..a3e0517 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -48,6 +48,7 @@ #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" +#include "catalog/pg_partitioned_table.h" #include "catalog/pg_proc.h" #include "catalog/pg_range.h" #include "catalog/pg_rewrite.h" @@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = { }, 8 }, + {PartitionedRelationId, /* PARTRELID */ + PartitionedRelidIndexId, + 1, + { + Anum_pg_partitioned_table_partrelid, + 0, + 0, + 0 + }, + 32 + }, {ProcedureRelationId, /* PROCNAMEARGSNSP */ ProcedureNameArgsNspIndexId, 3, diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 09b36c5..960a697 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -188,7 +188,8 @@ extern void recordDependencyOnExpr(const ObjectAddress *depender, extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender, Node *expr, Oid relId, DependencyType behavior, - DependencyType self_behavior); + DependencyType self_behavior, + bool ignore_self); extern ObjectClass getObjectClass(const ObjectAddress *object); diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index b80d8d8..11b16a9 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -134,4 +134,14 @@ extern void CheckAttributeType(const char *attname, List *containing_rowtypes, bool allow_system_table_mods); +/* pg_partitioned_table catalog manipulation functions */ +extern void StorePartitionKey(Relation rel, + char strategy, + int16 partnatts, + AttrNumber *partattrs, + List *partexprs, + Oid *partopclass, + Oid *partcollation); +extern void RemovePartitionKeyByRelId(Oid relid); + #endif /* HEAP_H */ diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index ca5eb3d..40f7576 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops)); #define ReplicationOriginNameIndex 6002 +DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops)); +#define PartitionedRelidIndexId 3351 + /* last step of initialization script: build the indexes declared above */ BUILD_INDICES diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index e57b81c..6a86c93 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -161,6 +161,7 @@ DESCR(""); #define RELKIND_COMPOSITE_TYPE 'c' /* composite type */ #define RELKIND_FOREIGN_TABLE 'f' /* foreign table */ #define RELKIND_MATVIEW 'm' /* materialized view */ +#define RELKIND_PARTITIONED_TABLE 'P' /* partitioned table */ #define RELPERSISTENCE_PERMANENT 'p' /* regular table */ #define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */ diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h new file mode 100644 index 0000000..cec54ae --- /dev/null +++ b/src/include/catalog/pg_partitioned_table.h @@ -0,0 +1,76 @@ +/*------------------------------------------------------------------------- + * + * pg_partitioned_table.h + * definition of the system "partitioned table" relation + * along with the relation's initial contents. + * + * + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * + * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_table.h $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_PARTITIONED_TABLE_H +#define PG_PARTITIONED_TABLE_H + +#include "catalog/genbki.h" + +/* ---------------- + * pg_partitioned_table definition. cpp turns this into + * typedef struct FormData_pg_partitioned_table + * ---------------- + */ +#define PartitionedRelationId 3350 + +CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS +{ + Oid partrelid; /* partitioned table oid */ + char partstrat; /* partitioning strategy */ + int16 partnatts; /* number of partition key columns */ + + /* + * variable-length fields start here, but we allow direct access to + * partattrs via the C struct. That's because the first variable-length + * field of a heap tuple can be reliably accessed using its C struct + * offset, as previous fields are all non-nullable fixed-length fields. + */ + int2vector partattrs; /* each member of the array is the + * attribute number of a partition key + * column, or 0 if the column is actually + * an expression */ + +#ifdef CATALOG_VARLEN + oidvector partclass; /* operator class to compare keys */ + oidvector partcollation; /* user-specified collation for keys */ + pg_node_tree partexprs; /* list of expressions in the partitioning + * key; one item for each zero entry in + * partattrs[] */ +#endif +} FormData_pg_partitioned_table; + +/* ---------------- + * Form_pg_partitioned_table corresponds to a pointer to a tuple with + * the format of pg_partitioned_table relation. + * ---------------- + */ +typedef FormData_pg_partitioned_table *Form_pg_partitioned_table; + +/* ---------------- + * compiler constants for pg_partitioned_table + * ---------------- + */ +#define Natts_pg_partitioned_table 7 +#define Anum_pg_partitioned_table_partrelid 1 +#define Anum_pg_partitioned_table_partstrat 2 +#define Anum_pg_partitioned_table_partnatts 3 +#define Anum_pg_partitioned_table_partattrs 4 +#define Anum_pg_partitioned_table_partclass 5 +#define Anum_pg_partitioned_table_partcollation 6 +#define Anum_pg_partitioned_table_partexprs 7 + +#endif /* PG_PARTITIONED_TABLE_H */ diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 2b894ff..d790fbf 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId, List *attributeList, List *exclusionOpNames); extern Oid GetDefaultOpClass(Oid type_id, Oid am_id); +extern Oid ResolveOpClass(List *opclass, Oid attrType, + char *accessMethodName, Oid accessMethodId); /* commands/functioncmds.c */ extern ObjectAddress CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt); diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index cb9307c..b27412c 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -454,6 +454,8 @@ typedef enum NodeTag T_CommonTableExpr, T_RoleSpec, T_TriggerTransition, + T_PartitionElem, + T_PartitionSpec, /* * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 04b1c2f..d30c82b 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -699,6 +699,34 @@ typedef struct XmlSerialize int location; /* token location, or -1 if unknown */ } XmlSerialize; +/* Partitioning related definitions */ + +/* + * PartitionElem - a column in the partition key + */ +typedef struct PartitionElem +{ + NodeTag type; + char *name; /* name of column to partition on, or NULL */ + Node *expr; /* expression to partition on, or NULL */ + List *collation; /* name of collation; NIL = default */ + List *opclass; /* name of desired opclass; NIL = default */ + int location; /* token location, or -1 if unknown */ +} PartitionElem; + +/* + * PartitionSpec - partition key specification + */ +typedef struct PartitionSpec +{ + NodeTag type; + char *strategy; /* partitioning strategy ('list' or 'range') */ + List *partParams; /* List of PartitionElems */ + int location; /* token location, or -1 if unknown */ +} PartitionSpec; + +#define PARTITION_STRATEGY_LIST 'l' +#define PARTITION_STRATEGY_RANGE 'r' /**************************************************************************** * Nodes for a Query tree @@ -1775,6 +1803,7 @@ typedef struct CreateStmt List *tableElts; /* column definitions (list of ColumnDef) */ List *inhRelations; /* relations to inherit from (list of * inhRelation) */ + 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/parse_node.h b/src/include/parser/parse_node.h index 6633586..bd6dc02 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -64,7 +64,8 @@ typedef enum ParseExprKind EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */ EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */ EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */ - EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */ + EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */ + EXPR_KIND_PARTITION_EXPRESSION /* PARTITION BY expression */ } ParseExprKind; diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h index a2b2b61..01c6c09 100644 --- a/src/include/pg_config_manual.h +++ b/src/include/pg_config_manual.h @@ -46,6 +46,11 @@ #define INDEX_MAX_KEYS 32 /* + * Maximum number of columns in a partition key + */ +#define PARTITION_MAX_KEYS 32 + +/* * Set the upper and lower bounds of sequence values. */ #define SEQ_MAXVALUE PG_INT64_MAX diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index fa15f28..60d8de3 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -45,6 +45,35 @@ typedef struct LockInfoData typedef LockInfoData *LockInfo; +/* + * Information about the partition key of a relation + */ +typedef struct PartitionKeyData +{ + char strategy; /* partitioning strategy */ + int16 partnatts; /* number of columns in the partition key */ + AttrNumber *partattrs; /* attribute numbers of columns in the + * partition key */ + List *partexprs; /* list of expressions in the partitioning + * key, or NIL */ + + Oid *partopfamily; /* OIDs of operator families */ + Oid *partopcintype; /* OIDs of opclass declared input data types */ + FmgrInfo *partsupfunc; /* lookup info for support funcs */ + + /* Partitioning collation per attribute */ + Oid *partcollation; + + /* Type information per attribute */ + Oid *parttypid; + int32 *parttypmod; + int16 *parttyplen; + bool *parttypbyval; + char *parttypalign; + Oid *parttypcoll; +} PartitionKeyData; + +typedef struct PartitionKeyData *PartitionKey; /* * Here are the contents of a relation cache entry. @@ -94,6 +123,9 @@ typedef struct RelationData List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */ bool rd_fkeyvalid; /* true if list has been computed */ + MemoryContext rd_partkeycxt; /* private memory cxt for the below */ + struct PartitionKeyData *rd_partkey; /* partition key, or NULL */ + /* data managed by RelationGetIndexList: */ List *rd_indexlist; /* list of OIDs of indexes on relation */ Oid rd_oidindex; /* OID of unique index on OID, if any */ @@ -534,6 +566,42 @@ typedef struct ViewOptions RelationNeedsWAL(relation) && \ !IsCatalogRelation(relation)) +/* + * RelationGetPartitionKey + * Returns the PartitionKey of a relation + */ +#define RelationGetPartitionKey(relation) ((relation)->rd_partkey) + +/* + * PartitionKey inquiry functions + */ +static inline int +get_partition_strategy(PartitionKey key) +{ + return key->strategy; +} + +static inline int +get_partition_natts(PartitionKey key) +{ + return key->partnatts; +} + +static inline List * +get_partition_exprs(PartitionKey key) +{ + return key->partexprs; +} + +/* + * PartitionKey inquiry functions - one column + */ +static inline int16 +get_partition_col_attnum(PartitionKey key, int col) +{ + return key->partattrs[col]; +} + /* routines in utils/cache/relcache.c */ extern void RelationIncrementReferenceCount(Relation rel); extern void RelationDecrementReferenceCount(Relation rel); diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h index 256615b..39fe947 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -72,6 +72,7 @@ enum SysCacheIdentifier OPEROID, OPFAMILYAMNAMENSP, OPFAMILYOID, + PARTRELID, PROCNAMEARGSNSP, PROCOID, RANGETYPE, diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index cf9f6d3..df6fe13 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -2974,3 +2974,49 @@ NOTICE: column "c3" of relation "test_add_column" already exists, skipping c4 | integer | | | DROP TABLE test_add_column; +-- unsupported constraint types for partitioned tables +CREATE TABLE partitioned ( + a int, + b int +) PARTITION BY RANGE (a, (a+b+1)); +ALTER TABLE partitioned ADD UNIQUE (a); +ERROR: unique constraints are not supported on partitioned tables +LINE 1: ALTER TABLE partitioned ADD UNIQUE (a); + ^ +ALTER TABLE partitioned ADD PRIMARY KEY (a); +ERROR: primary key constraints are not supported on partitioned tables +LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a); + ^ +ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah; +ERROR: foreign key constraints are not supported on partitioned tables +LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah; + ^ +ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&); +ERROR: exclusion constraints are not supported on partitioned tables +LINE 1: ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&); + ^ +-- cannot drop column that is part of the partition key +ALTER TABLE partitioned DROP COLUMN a; +ERROR: cannot drop column named in partition key +ALTER TABLE partitioned ALTER COLUMN a TYPE char(5); +ERROR: cannot alter type of column named in partition key +ALTER TABLE partitioned DROP COLUMN b; +ERROR: cannot drop column referenced in partition key expression +ALTER TABLE partitioned ALTER COLUMN b TYPE char(5); +ERROR: cannot alter type of column referenced in partition key expression +-- cannot drop NOT NULL on columns in the range partition key +ALTER TABLE partitioned ALTER COLUMN a DROP NOT NULL; +ERROR: column "a" is in range partition key +-- partitioned table cannot partiticipate in regular inheritance +CREATE TABLE foo ( + a int, + b int +); +ALTER TABLE partitioned INHERIT foo; +ERROR: cannot change inheritance of partitioned table +ALTER TABLE foo INHERIT partitioned; +ERROR: cannot inherit from partitioned table "partitioned" +-- cannot add NO INHERIT constraint to partitioned tables +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, foo; diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out index 41ceb87..410d96b 100644 --- a/src/test/regress/expected/create_table.out +++ b/src/test/regress/expected/create_table.out @@ -253,3 +253,171 @@ DROP TABLE as_select1; -- check that the oid column is added before the primary key is checked CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS; DROP TABLE oid_pk; +-- +-- Partitioned tables +-- +-- cannot combine INHERITS and PARTITION BY (although grammar allows) +CREATE TABLE partitioned ( + a int +) INHERITS (some_table) PARTITION BY LIST (a); +ERROR: cannot create partitioned table as inheritance child +-- cannot use more than 1 column as partition key for list partitioned table +CREATE TABLE partitioned ( + a1 int, + a2 int +) PARTITION BY LIST (a1, a2); -- fail +ERROR: cannot list partition using more than one column +-- unsupported constraint type for partitioned tables +CREATE TABLE partitioned ( + a int PRIMARY KEY +) PARTITION BY RANGE (a); +ERROR: primary key constraints are not supported on partitioned tables +LINE 2: a int PRIMARY KEY + ^ +CREATE TABLE pkrel ( + a int PRIMARY KEY +); +CREATE TABLE partitioned ( + a int REFERENCES pkrel(a) +) PARTITION BY RANGE (a); +ERROR: foreign key constraints are not supported on partitioned tables +LINE 2: a int REFERENCES pkrel(a) + ^ +DROP TABLE pkrel; +CREATE TABLE partitioned ( + a int UNIQUE +) PARTITION BY RANGE (a); +ERROR: unique constraints are not supported on partitioned tables +LINE 2: a int UNIQUE + ^ +CREATE TABLE partitioned ( + a int, + EXCLUDE USING gist (a WITH &&) +) PARTITION BY RANGE (a); +ERROR: exclusion constraints are not supported on partitioned tables +LINE 3: EXCLUDE USING gist (a WITH &&) + ^ +-- prevent column from being used twice in the partition key +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE (a, a); +ERROR: column "a" appears more than once in partition key +-- prevent using prohibited expressions in the key +CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE; +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE (retset(a)); +ERROR: set-returning functions are not allowed in partition key expression +DROP FUNCTION retset(int); +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE ((avg(a))); +ERROR: aggregate functions are not allowed in partition key expression +CREATE TABLE partitioned ( + a int, + b int +) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b))); +ERROR: window functions are not allowed in partition key expression +CREATE TABLE partitioned ( + a int +) PARTITION BY LIST ((a LIKE (SELECT 1))); +ERROR: cannot use subquery in partition key expression +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE (('a')); +ERROR: cannot use constant expression as partition key +CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE; +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE (const_func()); +ERROR: cannot use constant expression as partition key +DROP FUNCTION const_func(); +-- only accept "list" and "range" as partitioning strategy +CREATE TABLE partitioned ( + a int +) PARTITION BY HASH (a); +ERROR: unrecognized partitioning strategy "hash" +-- specified column must be present in the table +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE (b); +ERROR: column "b" named in partition key does not exist +-- cannot use system columns in partition key +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE (xmin); +ERROR: cannot use system column "xmin" in partition key +-- functions in key must be immutable +CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL; +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE (immut_func(a)); +ERROR: functions in partition key expression must be marked IMMUTABLE +DROP FUNCTION immut_func(int); +-- cannot contain whole-row references +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE ((partitioned)); +ERROR: partition key expressions cannot contain whole-row references +-- prevent using columns of unsupported types in key (type must have a btree operator class) +CREATE TABLE partitioned ( + a point +) PARTITION BY LIST (a); +ERROR: data type point has no default btree operator class +HINT: You must specify a btree operator class or define a default btree operator class for the data type. +CREATE TABLE partitioned ( + a point +) PARTITION BY LIST (a point_ops); +ERROR: operator class "point_ops" does not exist for access method "btree" +CREATE TABLE partitioned ( + a point +) PARTITION BY RANGE (a); +ERROR: data type point has no default btree operator class +HINT: You must specify a btree operator class or define a default btree operator class for the data type. +CREATE TABLE partitioned ( + a point +) PARTITION BY RANGE (a point_ops); +ERROR: operator class "point_ops" does not exist for access method "btree" +-- cannot add NO INHERIT constraints to partitioned tables +CREATE TABLE partitioned ( + a int, + CONSTRAINT check_a CHECK (a > 0) NO INHERIT +) PARTITION BY RANGE (a); +ERROR: cannot add NO INHERIT constraint to partitioned table "partitioned" +-- some checks after successful creation of a partitioned table +CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL; +CREATE TABLE partitioned ( + a int, + b int, + c text, + d text +) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US"); +-- check relkind +SELECT relkind FROM pg_class WHERE relname = 'partitioned'; + relkind +--------- + P +(1 row) + +-- check that range partition key columns are marked NOT NULL +SELECT attname, attnotnull FROM pg_attribute WHERE attrelid = 'partitioned'::regclass AND attnum > 0; + attname | attnotnull +---------+------------ + a | t + b | f + c | t + d | t +(4 rows) + +-- prevent a function referenced in partition key from being dropped +DROP FUNCTION plusone(int); +ERROR: cannot drop function plusone(integer) because other objects depend on it +DETAIL: table partitioned depends on function plusone(integer) +HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- partitioned table cannot partiticipate in regular inheritance +CREATE TABLE partitioned2 ( + a int +) PARTITION BY RANGE (a); +CREATE TABLE fail () INHERITS (partitioned2); +ERROR: cannot inherit from partitioned table "partitioned2" +DROP TABLE partitioned, partitioned2; diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index b1ebcf6..8fa929a 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -120,6 +120,7 @@ pg_namespace|t pg_opclass|t pg_operator|t pg_opfamily|t +pg_partitioned_table|t pg_pltemplate|t pg_policy|t pg_proc|t diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index c8eed3e..ec61b02 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -1875,3 +1875,35 @@ ALTER TABLE test_add_column ADD COLUMN c4 integer; \d test_add_column DROP TABLE test_add_column; + +-- unsupported constraint types for partitioned tables +CREATE TABLE partitioned ( + a int, + b int +) PARTITION BY RANGE (a, (a+b+1)); +ALTER TABLE partitioned ADD UNIQUE (a); +ALTER TABLE partitioned ADD PRIMARY KEY (a); +ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah; +ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&); + +-- cannot drop column that is part of the partition key +ALTER TABLE partitioned DROP COLUMN a; +ALTER TABLE partitioned ALTER COLUMN a TYPE char(5); +ALTER TABLE partitioned DROP COLUMN b; +ALTER TABLE partitioned ALTER COLUMN b TYPE char(5); + +-- cannot drop NOT NULL on columns in the range partition key +ALTER TABLE partitioned ALTER COLUMN a DROP NOT NULL; + +-- partitioned table cannot partiticipate in regular inheritance +CREATE TABLE foo ( + a int, + b int +); +ALTER TABLE partitioned INHERIT foo; +ALTER TABLE foo INHERIT partitioned; + +-- cannot add NO INHERIT constraint to partitioned tables +ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT; + +DROP TABLE partitioned, foo; diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql index 78bdc8b..b9489fc 100644 --- a/src/test/regress/sql/create_table.sql +++ b/src/test/regress/sql/create_table.sql @@ -269,3 +269,149 @@ DROP TABLE as_select1; -- check that the oid column is added before the primary key is checked CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS; DROP TABLE oid_pk; + +-- +-- Partitioned tables +-- + +-- cannot combine INHERITS and PARTITION BY (although grammar allows) +CREATE TABLE partitioned ( + a int +) INHERITS (some_table) PARTITION BY LIST (a); + +-- cannot use more than 1 column as partition key for list partitioned table +CREATE TABLE partitioned ( + a1 int, + a2 int +) PARTITION BY LIST (a1, a2); -- fail + +-- unsupported constraint type for partitioned tables +CREATE TABLE partitioned ( + a int PRIMARY KEY +) PARTITION BY RANGE (a); + +CREATE TABLE pkrel ( + a int PRIMARY KEY +); +CREATE TABLE partitioned ( + a int REFERENCES pkrel(a) +) PARTITION BY RANGE (a); +DROP TABLE pkrel; + +CREATE TABLE partitioned ( + a int UNIQUE +) PARTITION BY RANGE (a); + +CREATE TABLE partitioned ( + a int, + EXCLUDE USING gist (a WITH &&) +) PARTITION BY RANGE (a); + +-- prevent column from being used twice in the partition key +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE (a, a); + +-- prevent using prohibited expressions in the key +CREATE FUNCTION retset (a int) RETURNS SETOF int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE; +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE (retset(a)); +DROP FUNCTION retset(int); + +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE ((avg(a))); + +CREATE TABLE partitioned ( + a int, + b int +) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b))); + +CREATE TABLE partitioned ( + a int +) PARTITION BY LIST ((a LIKE (SELECT 1))); + +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE (('a')); + +CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE; +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE (const_func()); +DROP FUNCTION const_func(); + +-- only accept "list" and "range" as partitioning strategy +CREATE TABLE partitioned ( + a int +) PARTITION BY HASH (a); + +-- specified column must be present in the table +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE (b); + +-- cannot use system columns in partition key +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE (xmin); + +-- functions in key must be immutable +CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL; +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE (immut_func(a)); +DROP FUNCTION immut_func(int); + +-- cannot contain whole-row references +CREATE TABLE partitioned ( + a int +) PARTITION BY RANGE ((partitioned)); + +-- prevent using columns of unsupported types in key (type must have a btree operator class) +CREATE TABLE partitioned ( + a point +) PARTITION BY LIST (a); +CREATE TABLE partitioned ( + a point +) PARTITION BY LIST (a point_ops); +CREATE TABLE partitioned ( + a point +) PARTITION BY RANGE (a); +CREATE TABLE partitioned ( + a point +) PARTITION BY RANGE (a point_ops); + +-- cannot add NO INHERIT constraints to partitioned tables +CREATE TABLE partitioned ( + a int, + CONSTRAINT check_a CHECK (a > 0) NO INHERIT +) PARTITION BY RANGE (a); + +-- some checks after successful creation of a partitioned table +CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL; + +CREATE TABLE partitioned ( + a int, + b int, + c text, + d text +) PARTITION BY RANGE (a oid_ops, plusone(b), c collate "default", d collate "en_US"); + +-- check relkind +SELECT relkind FROM pg_class WHERE relname = 'partitioned'; + +-- check that range partition key columns are marked NOT NULL +SELECT attname, attnotnull FROM pg_attribute WHERE attrelid = 'partitioned'::regclass AND attnum > 0; + +-- prevent a function referenced in partition key from being dropped +DROP FUNCTION plusone(int); + +-- partitioned table cannot partiticipate in regular inheritance +CREATE TABLE partitioned2 ( + a int +) PARTITION BY RANGE (a); +CREATE TABLE fail () INHERITS (partitioned2); + +DROP TABLE partitioned, partitioned2; -- 1.7.1