From 29c9d232c6ab3d45e14522fd9f9e11fdad06eb4d Mon Sep 17 00:00:00 2001 From: amit Date: Thu, 14 Jul 2016 09:59:15 +0900 Subject: [PATCH 1/9] Catalog and DDL for partitioned tables. 1. In addition to a catalog for storing the partition key information, this commit also adds a new relkind to pg_class.h. A new dependency type DEPENDENCY_IGNORE is added for callers to be able to ask the dependency subsystem to ignore self-dependencies that arise when storing dependencies on objects mentioned in partition key expressions. 2. Add PARTITION BY clause to CREATE TABLE. 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 | 102 +++++++- doc/src/sgml/ref/create_table.sgml | 55 ++++ src/backend/access/common/reloptions.c | 2 + src/backend/catalog/Makefile | 6 +- src/backend/catalog/aclchk.c | 2 + src/backend/catalog/dependency.c | 2 + src/backend/catalog/heap.c | 27 ++- src/backend/catalog/objectaddress.c | 5 +- src/backend/catalog/partition.c | 394 +++++++++++++++++++++++++ src/backend/catalog/pg_depend.c | 3 + src/backend/catalog/pg_partitioned_table.c | 172 +++++++++++ src/backend/commands/analyze.c | 2 + src/backend/commands/copy.c | 6 + src/backend/commands/indexcmds.c | 7 +- src/backend/commands/lockcmds.c | 2 +- src/backend/commands/policy.c | 2 +- src/backend/commands/seclabel.c | 1 + src/backend/commands/sequence.c | 1 + src/backend/commands/tablecmds.c | 384 ++++++++++++++++++++++++- src/backend/commands/trigger.c | 7 +- src/backend/commands/vacuum.c | 1 + src/backend/executor/execMain.c | 2 + src/backend/executor/nodeModifyTable.c | 1 + src/backend/nodes/copyfuncs.c | 33 ++ src/backend/nodes/equalfuncs.c | 28 ++ src/backend/nodes/outfuncs.c | 26 ++ src/backend/parser/gram.y | 110 ++++++-- src/backend/parser/parse_agg.c | 11 + src/backend/parser/parse_expr.c | 5 + src/backend/parser/parse_utilcmd.c | 69 +++++ src/backend/rewrite/rewriteDefine.c | 1 + src/backend/rewrite/rewriteHandler.c | 1 + src/backend/tcop/utility.c | 5 +- src/backend/utils/cache/relcache.c | 19 ++- src/backend/utils/cache/syscache.c | 12 + src/include/catalog/dependency.h | 8 +- src/include/catalog/indexing.h | 3 + src/include/catalog/partition.h | 35 +++ src/include/catalog/pg_class.h | 1 + src/include/catalog/pg_partitioned_table.h | 69 +++++ src/include/catalog/pg_partitioned_table_fn.h | 29 ++ src/include/commands/defrem.h | 2 + src/include/nodes/nodes.h | 2 + src/include/nodes/parsenodes.h | 35 +++ src/include/parser/kwlist.h | 1 + src/include/parser/parse_node.h | 3 +- src/include/pg_config_manual.h | 5 + src/include/utils/rel.h | 9 + src/include/utils/syscache.h | 1 + src/test/regress/expected/alter_table.out | 46 +++ src/test/regress/expected/create_table.out | 155 ++++++++++ src/test/regress/expected/sanity_check.out | 1 + src/test/regress/sql/alter_table.sql | 34 +++ src/test/regress/sql/create_table.sql | 133 +++++++++ 54 files changed, 2033 insertions(+), 45 deletions(-) create mode 100644 src/backend/catalog/partition.c create mode 100644 src/backend/catalog/pg_partitioned_table.c create mode 100644 src/include/catalog/partition.h create mode 100644 src/include/catalog/pg_partitioned_table.h create mode 100644 src/include/catalog/pg_partitioned_table_fn.h diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 322d8d6..0b38ff7 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,100 @@ + + <structname>pg_partitioned_table</structname> + + + pg_partitioned_table + + + + The catalog pg_partitioned_table stores information + about the partition key of tables. + + + + <structname>pg_partitioned_table</> Columns + + + + + Name + Type + References + Description + + + + + + + partedrelid + oid + pg_class.oid + The OID of the pg_class entry for this partitioned table + + + + partstrat + char + + + Partitioning strategy (or method); 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 used as 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 + over the table columns, 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. + + + + + 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 partkey. 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..331ed56 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,39 @@ 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 method of + partitioning the table and the corresponding partition key. Table + thus created is called partitioned table. Key + consists of an ordered list of column names and/or expressions when + using the RANGE method, whereas only a single column or + expression can be specified when using the LIST method. + The type of a key column or an expression must have an associated + btree operator class or one must be specified along with the column + or the expression. + + + + A partitioned table is divided into sub-tables (called partitions), which + in turn, are created using separate CREATE TABLE commands. + The table itself is empty. A data row inserted into the table is mapped + to and stored in one of the partitions (if one exists) based on the + values of columns and/or expressions in the partition key and partition + rules associated with the partitions. + + + + Partitioned tables do not support UNIQUE, PRIMARY, EXCLUDE, or FOREIGN + KEY constraints; however, you can define these constraints on individual + data partitions. + + + + + + LIKE source_table [ like_option ... ] @@ -1369,6 +1404,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..032d214 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -11,11 +11,11 @@ top_builddir = ../../.. include $(top_builddir)/src/Makefile.global OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \ - objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \ + objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \ pg_constraint.o pg_conversion.o \ pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \ pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \ - pg_type.o storage.o toasting.o + pg_type.o storage.o toasting.o pg_partitioned_table.o BKIFILES = postgres.bki postgres.description postgres.shdescription @@ -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_range.h pg_transform.h pg_partitioned_table.h\ toasting.h indexing.h \ ) diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index c0df671..8a4ac7e 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames) case ACL_OBJECT_RELATION: objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION); objects = list_concat(objects, objs); + objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_TABLE); + objects = list_concat(objects, objs); objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW); objects = list_concat(objects, objs); objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW); diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 04d7840..607274d 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -717,6 +717,7 @@ findDependentObjects(const ObjectAddress *object, getObjectDescription(object)); break; default: + Assert(foundDep->deptype != DEPENDENCY_IGNORE); elog(ERROR, "unrecognized dependency type '%c' for %s", foundDep->deptype, getObjectDescription(object)); break; @@ -813,6 +814,7 @@ findDependentObjects(const ObjectAddress *object, subflags = 0; /* keep compiler quiet */ break; default: + Assert(foundDep->deptype != DEPENDENCY_IGNORE); elog(ERROR, "unrecognized dependency type '%c' for %s", foundDep->deptype, getObjectDescription(object)); subflags = 0; /* keep compiler quiet */ diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index e997b57..aafd2e6 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -48,6 +48,7 @@ #include "catalog/pg_foreign_table.h" #include "catalog/pg_inherits.h" #include "catalog/pg_namespace.h" +#include "catalog/pg_partitioned_table_fn.h" #include "catalog/pg_statistic.h" #include "catalog/pg_tablespace.h" #include "catalog/pg_type.h" @@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname, { /* Use binary-upgrade override for pg_class.oid/relfilenode? */ if (IsBinaryUpgrade && - (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE || - relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW || - relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE)) + (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE || + relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW || + relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE || + relkind == RELKIND_FOREIGN_TABLE)) { if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid)) ereport(ERROR, @@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname, switch (relkind) { case RELKIND_RELATION: + case RELKIND_PARTITIONED_TABLE: case RELKIND_VIEW: case RELKIND_MATVIEW: case RELKIND_FOREIGN_TABLE: @@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname, * such is an implementation detail: toast tables, sequences and indexes. */ if (IsUnderPostmaster && (relkind == RELKIND_RELATION || + relkind == RELKIND_PARTITIONED_TABLE || relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW || relkind == RELKIND_FOREIGN_TABLE || @@ -1353,7 +1357,8 @@ 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); } @@ -1800,6 +1805,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 && @@ -2031,6 +2042,14 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr, else attNos = NULL; + /* Remove NO INHERIT flag if rel is a partitioned table */ + 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 */ diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 9aa8174..e0d56a9 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", @@ -3249,6 +3250,7 @@ getRelationDescription(StringInfo buffer, Oid relid) switch (relForm->relkind) { case RELKIND_RELATION: + case RELKIND_PARTITIONED_TABLE: appendStringInfo(buffer, _("table %s"), relname); break; @@ -3706,6 +3708,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/partition.c b/src/backend/catalog/partition.c new file mode 100644 index 0000000..35e020c --- /dev/null +++ b/src/backend/catalog/partition.c @@ -0,0 +1,394 @@ +/*------------------------------------------------------------------------- + * + * partition.c + * Partitioning related utility functions. + * + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/misc/partition.c + * + *------------------------------------------------------------------------- +*/ + +#include "postgres.h" + +#include "access/heapam.h" +#include "access/htup_details.h" +#include "access/nbtree.h" +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/objectaddress.h" +#include "catalog/partition.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_partitioned_table.h" +#include "catalog/pg_type.h" +#include "executor/executor.h" +#include "miscadmin.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" +#include "optimizer/planmain.h" +#include "storage/lmgr.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/memutils.h" +#include "utils/fmgroids.h" +#include "utils/inval.h" +#include "utils/lsyscache.h" +#include "utils/ruleutils.h" +#include "utils/syscache.h" + +/* Type and collation information for partition key columns */ +typedef struct KeyTypeCollInfo +{ + Oid *typid; + int32 *typmod; + int16 *typlen; + bool *typbyval; + char *typalign; + Oid *typcoll; +} KeyTypeCollInfo; + +/* + * Partition key information + */ +typedef struct PartitionKeyData +{ + char strategy; /* partition strategy */ + int16 partnatts; /* number of partition attributes */ + AttrNumber *partattrs; /* partition attnums */ + Oid *partopfamily; /* OIDs of operator families */ + Oid *partopcintype; /* OIDs of opclass declared input data types */ + FmgrInfo *partsupfunc; /* lookup info for support funcs */ + List *partexprs; /* partition key expressions, if any */ + char **partcolnames; /* partition key column names */ + KeyTypeCollInfo *tcinfo; /* type and collation info (all columns) */ +} PartitionKeyData; + +/* Support RelationBuildPartitionKey() */ +static PartitionKey copy_partition_key(PartitionKey fromkey); +static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols, + KeyTypeCollInfo *tcinfo); + +/* + * Partition key related functions + */ + +/* + * RelationBuildPartitionKey + * Build and attach to relcache partition key data of relation + * + * Note that the partition key data attached to a relcache entry must be + * stored CacheMemoryContext to ensure it survives as long as the relcache + * entry. But we should be running in a less long-lived working context. + * To avoid leaking cache memory if this routine fails partway through, + * we build in working memory and then copy the completed structure into + * cache memory. + */ +void +RelationBuildPartitionKey(Relation relation) +{ + Form_pg_partitioned_table form; + Relation catalog; + HeapTuple tuple; + bool isnull; + int i; + PartitionKey key; + int2vector *partattrs; + oidvector *opclass; + KeyTypeCollInfo *tcinfo; + ListCell *partexprbin_item; + List *partexprsrc = NIL; + ListCell *partexprsrc_item; + Datum datum; + MemoryContext partkeycxt, + oldcxt; + + tuple = SearchSysCache1(PARTEDRELID, + 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; + + form = (Form_pg_partitioned_table) GETSTRUCT(tuple); + + /* Allocate in the supposedly short-lived working context */ + key = (PartitionKey) palloc0(sizeof(PartitionKeyData)); + key->strategy = form->partstrat; + key->partnatts = form->partnatts; + + /* Open the catalog for its tuple descriptor */ + catalog = heap_open(PartitionedRelationId, AccessShareLock); + datum = fastgetattr(tuple, Anum_pg_partitioned_table_partattrs, + RelationGetDescr(catalog), + &isnull); + Assert(!isnull); + partattrs = (int2vector *) DatumGetPointer(datum); + + datum = fastgetattr(tuple, Anum_pg_partitioned_table_partclass, + RelationGetDescr(catalog), + &isnull); + Assert(!isnull); + opclass = (oidvector *) DatumGetPointer(datum); + + datum = heap_getattr(tuple, + Anum_pg_partitioned_table_partexprbin, + RelationGetDescr(catalog), + &isnull); + + if (!isnull) + { + char *exprString; + Node *expr; + + exprString = TextDatumGetCString(datum); + expr = stringToNode(exprString); + pfree(exprString); + + /* + * Run the expressions through eval_const_expressions. This is + * not just an optimization, but is necessary, because eventually + * the planner will be comparing them to similarly-processed qual + * clauses, and may fail to detect valid matches without this. + * We don't bother with canonicalize_qual, however. + */ + expr = eval_const_expressions(NULL, (Node *) expr); + + /* May as well fix opfuncids too */ + fix_opfuncids((Node *) expr); + key->partexprs = (List *) expr; + + /* We should have a partexprsrc as well */ + datum = heap_getattr(tuple, + Anum_pg_partitioned_table_partexprsrc, + RelationGetDescr(catalog), + &isnull); + Assert(!isnull); + exprString = TextDatumGetCString(datum); + expr = stringToNode(exprString); + pfree(exprString); + partexprsrc = (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)); + + /* Gather type and collation info as well */ + key->tcinfo = tcinfo = (KeyTypeCollInfo *) palloc0(sizeof(KeyTypeCollInfo)); + tcinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid)); + tcinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32)); + tcinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16)); + tcinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool)); + tcinfo->typalign = (char *) palloc0(key->partnatts * sizeof(char)); + tcinfo->typcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid)); + + /* Gather partition column names (simple C strings) */ + key->partcolnames = (char **) palloc0(key->partnatts * sizeof(char *)); + + /* Copy partattrs and fill other per-attribute info */ + partexprbin_item = list_head(key->partexprs); + partexprsrc_item = list_head(partexprsrc); + for (i = 0; i < key->partnatts; i++) + { + HeapTuple tuple; + AttrNumber attno; + Form_pg_opclass form; + Oid funcid; + + key->partattrs[i] = attno = partattrs->values[i]; + + /* Collect type information */ + if (attno != 0) + { + tcinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid; + tcinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod; + tcinfo->typcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation; + } + else + { + tcinfo->typid[i] = exprType(lfirst(partexprbin_item)); + tcinfo->typmod[i] = exprTypmod(lfirst(partexprbin_item)); + tcinfo->typcoll[i] = exprCollation(lfirst(partexprbin_item)); + } + get_typlenbyvalalign(tcinfo->typid[i], + &tcinfo->typlen[i], + &tcinfo->typbyval[i], + &tcinfo->typalign[i]); + + /* Collect opfamily information */ + tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i])); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]); + + form = (Form_pg_opclass) GETSTRUCT(tuple); + key->partopfamily[i] = form->opcfamily; + key->partopcintype[i] = form->opcintype; + + /* + * A btree support function covers the cases of list and range methods + * currently supported. + */ + funcid = get_opfamily_proc(form->opcfamily, + form->opcintype, form->opcintype, + BTORDER_PROC); + + fmgr_info(funcid, &key->partsupfunc[i]); + ReleaseSysCache(tuple); + + /* Collect atttribute names */ + if (key->partattrs[i] != 0) + key->partcolnames[i] = get_relid_attribute_name(RelationGetRelid(relation), + key->partattrs[i]); + else + { + Value *str = lfirst(partexprsrc_item); + key->partcolnames[i] = pstrdup(str->val.str); + partexprsrc_item = lnext(partexprsrc_item); + } + } + + ReleaseSysCache(tuple); + heap_close(catalog, AccessShareLock); + + /* 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); +} + +/* + * Partition key information inquiry functions + */ +int +get_partition_key_strategy(PartitionKey key) +{ + return key->strategy; +} + +int +get_partition_key_natts(PartitionKey key) +{ + return key->partnatts; +} + +List * +get_partition_key_exprs(PartitionKey key) +{ + return key->partexprs; +} + +/* + * Partition key information inquiry functions - one column + */ +int16 +get_partition_col_attnum(PartitionKey key, int col) +{ + return key->partattrs[col]; +} + +Oid +get_partition_col_typid(PartitionKey key, int col) +{ + return key->tcinfo->typid[col]; +} + +int32 +get_partition_col_typmod(PartitionKey key, int col) +{ + return key->tcinfo->typmod[col]; +} + +char * +get_partition_col_name(PartitionKey key, int col) +{ + return key->partcolnames[col]; +} + +/* + * copy_partition_key + * + * The copy is allocated in the current memory context. + */ +static PartitionKey +copy_partition_key(PartitionKey fromkey) +{ + PartitionKey newkey; + int i; + + newkey = (PartitionKey) palloc0(sizeof(PartitionKeyData)); + + newkey->strategy = fromkey->strategy; + newkey->partnatts = fromkey->partnatts; + + newkey->partattrs = (AttrNumber *) + palloc0(newkey->partnatts * sizeof(AttrNumber)); + memcpy(newkey->partattrs, fromkey->partattrs, + newkey->partnatts * sizeof(AttrNumber)); + + newkey->partopfamily = (Oid *) palloc0(newkey->partnatts * sizeof(Oid)); + memcpy(newkey->partopfamily, fromkey->partopfamily, + newkey->partnatts * sizeof(Oid)); + + newkey->partopcintype = (Oid *) palloc0(newkey->partnatts * sizeof(Oid)); + memcpy(newkey->partopcintype, fromkey->partopcintype, + newkey->partnatts * sizeof(Oid)); + + newkey->partsupfunc = (FmgrInfo *) + palloc0(newkey->partnatts * sizeof(FmgrInfo)); + memcpy(newkey->partsupfunc, fromkey->partsupfunc, + newkey->partnatts * sizeof(FmgrInfo)); + + newkey->partexprs = copyObject(fromkey->partexprs); + newkey->tcinfo = copy_key_type_coll_info(newkey->partnatts, + fromkey->tcinfo); + newkey->partcolnames = (char **) palloc0(newkey->partnatts * sizeof(char *)); + for (i = 0; i < newkey->partnatts; i++) + newkey->partcolnames[i] = pstrdup(fromkey->partcolnames[i]); + + return newkey; +} + +/* + * copy_key_type_coll_info + * + * The copy is allocated in the current memory context. + */ +static KeyTypeCollInfo * +copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo) +{ + KeyTypeCollInfo *result = (KeyTypeCollInfo *) + palloc0(sizeof(KeyTypeCollInfo)); + + result->typid = (Oid *) palloc0(nkeycols * sizeof(Oid)); + memcpy(result->typid, tcinfo->typid, nkeycols * sizeof(Oid)); + + result->typmod = (int32 *) palloc0(nkeycols * sizeof(int32)); + memcpy(result->typmod, tcinfo->typmod, nkeycols * sizeof(int32)); + + result->typlen = (int16 *) palloc0(nkeycols * sizeof(int16)); + memcpy(result->typlen, tcinfo->typlen, nkeycols * sizeof(int16)); + + result->typbyval = (bool *) palloc0(nkeycols * sizeof(bool)); + memcpy(result->typbyval, tcinfo->typbyval, nkeycols * sizeof(bool)); + + result->typalign = (char *) palloc0(nkeycols * sizeof(bool)); + memcpy(result->typalign, tcinfo->typalign, nkeycols * sizeof(char)); + + result->typcoll = (Oid *) palloc0(nkeycols * sizeof(Oid)); + memcpy(result->typcoll, tcinfo->typcoll, nkeycols * sizeof(Oid)); + + return result; +} diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c index 7a0713e..6e71b44 100644 --- a/src/backend/catalog/pg_depend.c +++ b/src/backend/catalog/pg_depend.c @@ -65,6 +65,9 @@ recordMultipleDependencies(const ObjectAddress *depender, bool nulls[Natts_pg_depend]; Datum values[Natts_pg_depend]; + if (behavior == DEPENDENCY_IGNORE) + return; /* nothing to do */ + if (nreferenced <= 0) return; /* nothing to do */ diff --git a/src/backend/catalog/pg_partitioned_table.c b/src/backend/catalog/pg_partitioned_table.c new file mode 100644 index 0000000..fa4d0f5 --- /dev/null +++ b/src/backend/catalog/pg_partitioned_table.c @@ -0,0 +1,172 @@ +/*------------------------------------------------------------------------- + * + * pg_partitioned_table.c + * routines to support manipulation of the pg_partitioned_table relation + * + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/catalog/pg_partitioned_table.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "access/htup_details.h" +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/objectaddress.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_partitioned_table.h" +#include "catalog/pg_partitioned_table_fn.h" +#include "parser/parse_type.h" +#include "storage/lmgr.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/inval.h" +#include "utils/syscache.h" +#include "utils/tqual.h" + +/* + * StorePartitionKey + * Store the partition key information of rel into the catalog + */ +void +StorePartitionKey(Relation rel, + char strategy, + int16 partnatts, + AttrNumber *partattrs, + List *partexprbin, + List *partexprsrc, + Oid *partopclass) +{ + int i; + int2vector *partattrs_vec; + oidvector *partopclass_vec; + Datum partexprbinDatum; + Datum partexprsrcDatum; + 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(PARTEDRELID, + ObjectIdGetDatum(RelationGetRelid(rel))); + /* Cannot already exist */ + Assert(!HeapTupleIsValid(tuple)); + + /* + * Copy the partition key, opclass info into arrays (should we + * make the caller pass them like this to start with?) + */ + partattrs_vec = buildint2vector(partattrs, partnatts); + partopclass_vec = buildoidvector(partopclass, partnatts); + + /* Convert the partition key expressions (if any) to a text datum */ + if (partexprbin) + { + char *exprbinString; + char *exprsrcString; + + exprbinString = nodeToString(partexprbin); + exprsrcString = nodeToString(partexprsrc); + partexprbinDatum = CStringGetTextDatum(exprbinString); + partexprsrcDatum = CStringGetTextDatum(exprsrcString); + pfree(exprbinString); + pfree(exprsrcString); + } + else + partexprbinDatum = (Datum) 0; + + pg_partitioned_table = heap_open(PartitionedRelationId, RowExclusiveLock); + + MemSet(nulls, false, sizeof(nulls)); + + /* Only this can ever be NULL */ + if (!partexprbinDatum) + { + nulls[Anum_pg_partitioned_table_partexprbin - 1] = true; + nulls[Anum_pg_partitioned_table_partexprsrc - 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_partexprbin - 1] = partexprbinDatum; + values[Anum_pg_partitioned_table_partexprsrc - 1] = partexprsrcDatum; + + 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); + + /* Make this relation dependent on a few things: */ + myself.classId = RelationRelationId; + myself.objectId = RelationGetRelid(rel);; + myself.objectSubId = 0; + + /* Operator class per key column */ + for (i = 0; i < partnatts; i++) + { + referenced.classId = OperatorClassRelationId; + referenced.objectId = partopclass[i]; + referenced.objectSubId = 0; + + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } + + /* + * Store dependencies on anything mentioned in the key expressions. + * However, ignore the column references which causes self-dependencies + * to be created that are undesirable. That is done by asking the + * dependency-tracking sub-system to ignore any such dependencies. + */ + if (partexprbin) + recordDependencyOnSingleRelExpr(&myself, + (Node *) partexprbin, + RelationGetRelid(rel), + DEPENDENCY_NORMAL, + DEPENDENCY_IGNORE); + /* Tell world about the key */ + CacheInvalidateRelcache(rel); + + heap_close(pg_partitioned_table, RowExclusiveLock); + heap_freetuple(tuple); +} + +/* + * RemovePartitionKeyByRelId + * Remove pg_partitioned_table entry for a relation + */ +void +RemovePartitionKeyByRelId(Oid relid) +{ + Relation rel; + HeapTuple tuple; + + rel = heap_open(PartitionedRelationId, RowExclusiveLock); + + tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for partition key of relation %u", relid); + + simple_heap_delete(rel, &tuple->t_self); + + /* Update the indexes on pg_partitioned_table */ + CatalogUpdateIndexes(rel, tuple); + + ReleaseSysCache(tuple); + heap_close(rel, RowExclusiveLock); +} diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index c617abb..c4db6f7 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options, * locked the relation. */ if (onerel->rd_rel->relkind == RELKIND_RELATION || + onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE || onerel->rd_rel->relkind == RELKIND_MATVIEW) { /* Regular table, so we'll use the regular row acquisition function */ @@ -1317,6 +1318,7 @@ 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_PARTITIONED_TABLE || childrel->rd_rel->relkind == RELKIND_MATVIEW) { /* Regular table, so use the regular row acquisition function */ diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 432b0ca..be3fbc9 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -1736,6 +1736,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..4e067d2 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); @@ -371,7 +369,8 @@ DefineIndex(Oid relationId, namespaceId = RelationGetNamespace(rel); 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 (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) @@ -1256,7 +1255,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo, /* * Resolve possibly-defaulted operator class specification */ -static Oid +Oid GetIndexOpClass(List *opclass, Oid attrType, char *accessMethodName, Oid accessMethodId) { diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c index 175d1f3..874b320 100644 --- a/src/backend/commands/lockcmds.c +++ b/src/backend/commands/lockcmds.c @@ -88,7 +88,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..e5bcb89 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))); diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c index 5bd7e12..10268be 100644 --- a/src/backend/commands/seclabel.c +++ b/src/backend/commands/seclabel.c @@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt) * are the only relkinds for which pg_dump will dump labels). */ if (relation->rd_rel->relkind != RELKIND_RELATION && + relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE && relation->rd_rel->relkind != RELKIND_VIEW && relation->rd_rel->relkind != RELKIND_MATVIEW && relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE && diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index fc3a8ee..e08fd5d 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -1475,6 +1475,7 @@ 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_PARTITIONED_TABLE || tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 86e9814..3bb5933 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -29,6 +29,7 @@ #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/objectaccess.h" +#include "catalog/partition.h" #include "catalog/pg_am.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" @@ -39,6 +40,7 @@ #include "catalog/pg_inherits_fn.h" #include "catalog/pg_namespace.h" #include "catalog/pg_opclass.h" +#include "catalog/pg_partitioned_table_fn.h" #include "catalog/pg_tablespace.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" @@ -262,6 +264,12 @@ struct DropRelationCallbackState bool concurrent; }; +/* for find_attr_reference_walker */ +typedef struct +{ + AttrNumber attnum; +} find_attr_reference_context; + /* Alter table target-type flags for ATSimplePermissions */ #define ATT_TABLE 0x0001 #define ATT_VIEW 0x0002 @@ -433,6 +441,12 @@ 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 find_attr_reference_walker(Node *node, find_attr_reference_context *context); +static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr); +static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby); +static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, + List **partexprbin, List **partexprsrc, + Oid *partopclass); /* ---------------------------------------------------------------- @@ -596,7 +610,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); /* @@ -697,6 +712,25 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, */ rel = relation_open(relationId, AccessExclusiveLock); + /* Process and store partition key, if any */ + if (stmt->partby) + { + int partnatts; + AttrNumber partattrs[PARTITION_MAX_KEYS]; + Oid partopclass[PARTITION_MAX_KEYS]; + List *partexprbin = NIL; + List *partexprsrc = NIL; + + stmt->partby = transformPartitionBy(rel, stmt->partby); + ComputePartitionAttrs(rel, stmt->partby->partParams, + partattrs, &partexprbin, &partexprsrc, + partopclass); + + partnatts = list_length(stmt->partby->partParams); + StorePartitionKey(rel, stmt->partby->strategy, partnatts, + partattrs, partexprbin, partexprsrc, partopclass); + } + /* * 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 @@ -955,7 +989,14 @@ 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) + /* + * RemoveRelations never passes RELKIND_PARTITIONED_TABLE as the relkind + * for OBJECT_TABLE relations. It is ok for the passed in relkind to be + * RELKIND_RELATION while the relation is actually a partitioned table. + */ + if (classform->relkind != relkind && + (relkind == RELKIND_RELATION && + classform->relkind != RELKIND_PARTITIONED_TABLE)) DropErrorMsgWrongType(rel->relname, classform->relkind, relkind); /* Allow DROP to either table owner or schema owner */ @@ -1293,7 +1334,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 +1563,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 table \"%s\"", parent->relname), + errdetail("Table \"%s\" is partitioned.", parent->relname))); + if (relation->rd_rel->relkind != RELKIND_RELATION && relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE) ereport(ERROR, @@ -2162,6 +2211,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing) * restriction. */ if (relkind != RELKIND_RELATION && + relkind != RELKIND_PARTITIONED_TABLE && relkind != RELKIND_VIEW && relkind != RELKIND_MATVIEW && relkind != RELKIND_COMPOSITE_TYPE && @@ -4291,6 +4341,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,6 +4578,7 @@ 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_PARTITIONED_TABLE || rel->rd_rel->relkind == RELKIND_MATVIEW) { if (origTypeName) @@ -5417,6 +5469,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE * allowSystemTableMods to be turned on. */ if (rel->rd_rel->relkind != RELKIND_RELATION && + rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE && rel->rd_rel->relkind != RELKIND_MATVIEW && rel->rd_rel->relkind != RELKIND_INDEX && rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE) @@ -5691,6 +5744,74 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing, cmd->subtype = AT_DropColumnRecurse; } +/* Checks if a Var node is for a given attnum */ +static bool +find_attr_reference_walker(Node *node, find_attr_reference_context *context) +{ + if (node == NULL) + return false; + + if (IsA(node, Var)) + { + Var *variable = (Var *) node; + AttrNumber attnum = variable->varattno; + + if (attnum == context->attnum) + return true; + } + + return expression_tree_walker(node, find_attr_reference_walker, context); +} + +/* + * Checks if attnum is a partition attribute for rel + * + * Sets *is_expr if attnum is found to be referenced in some partition key + * expression. + */ +static bool +is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr) +{ + PartitionKey key; + int partnatts; + List *partexprs; + ListCell *partexpr_item; + int i; + + if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) + return false; + + key = RelationGetPartitionKey(rel); + partnatts = get_partition_key_natts(key); + partexprs = get_partition_key_exprs(key); + + partexpr_item = list_head(partexprs); + for (i = 0; i < partnatts; i++) + { + AttrNumber partatt = get_partition_col_attnum(key, i); + + if(partatt != 0) + { + *is_expr = false; + if (attnum == partatt) + return true; + } + else + { + find_attr_reference_context context; + + *is_expr = true; + context.attnum = attnum; + if (find_attr_reference_walker(lfirst(partexpr_item), &context)) + return true; + + partexpr_item = lnext(partexpr_item); + } + } + + return false; +} + /* * Return value is the address of the dropped column. */ @@ -5705,6 +5826,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 +5871,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName, errmsg("cannot drop inherited column \"%s\"", colName))); + /* Don't drop columns used in 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 +6402,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 relation \"%s\"", RelationGetRelationName(pkrel)), + errdetail("Referencing partitioned tables in foreign key constraints is not supported."))); + if (pkrel->rd_rel->relkind != RELKIND_RELATION) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -7861,6 +8002,7 @@ ATPrepAlterColumnType(List **wqueue, NewColumnValue *newval; ParseState *pstate = make_parsestate(NULL); AclResult aclresult; + bool is_expr; if (rel->rd_rel->reloftype && !recursing) ereport(ERROR, @@ -7891,6 +8033,19 @@ ATPrepAlterColumnType(List **wqueue, errmsg("cannot alter inherited column \"%s\"", colName))); + /* Don't alter columns used in 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); @@ -7906,7 +8061,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 @@ -8933,6 +9089,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock switch (tuple_class->relkind) { case RELKIND_RELATION: + case RELKIND_PARTITIONED_TABLE: case RELKIND_VIEW: case RELKIND_MATVIEW: case RELKIND_FOREIGN_TABLE: @@ -9395,6 +9552,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, switch (rel->rd_rel->relkind) { case RELKIND_RELATION: + case RELKIND_PARTITIONED_TABLE: case RELKIND_TOASTVALUE: case RELKIND_MATVIEW: (void) heap_reloptions(rel->rd_rel->relkind, newOptions, true); @@ -9817,7 +9975,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 && @@ -10016,6 +10175,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"))); } /* @@ -10067,6 +10231,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 \"%s\"", parent->relname), + errdetail("Table \"%s\" is partitioned.", parent->relname))); + /* * Check for duplicates in the list of parents, and determine the highest * inhseqno already present; we'll use the next one for the new parent. @@ -11445,6 +11616,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid, /* Fix other dependent stuff */ if (rel->rd_rel->relkind == RELKIND_RELATION || + rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE || rel->rd_rel->relkind == RELKIND_MATVIEW) { AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved); @@ -11894,7 +12066,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))); @@ -12048,6 +12220,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid, */ if (IsA(stmt, AlterObjectSchemaStmt) && relkind != RELKIND_RELATION && + relkind != RELKIND_PARTITIONED_TABLE && relkind != RELKIND_VIEW && relkind != RELKIND_MATVIEW && relkind != RELKIND_SEQUENCE && @@ -12059,3 +12232,202 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid, ReleaseSysCache(tuple); } + +/* + * Transform any expressions present in the partition key + */ +static PartitionBy * +transformPartitionBy(Relation rel, PartitionBy *partitionby) +{ + PartitionBy *partby; + ParseState *pstate; + RangeTblEntry *rte; + ListCell *l; + + partby = (PartitionBy *) makeNode(PartitionBy); + + partby->strategy = partitionby->strategy; + partby->location = partitionby->location; + partby->partParams = NIL; + + /* + * 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, partitionby->partParams) + { + ListCell *lc; + PartitionElem *pelem = (PartitionElem *) lfirst(l); + + /* Check for PARTITION BY ... (foo, foo) */ + foreach(lc, partby->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 twice 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_KEY); + + /* we have to fix its collations too */ + assign_expr_collations(pstate, pelem->expr); + + /* + * transformExpr() should have already rejected subqueries, + * aggregates, and window functions, based on the EXPR_KIND_ for + * a partition key expression. + * + * Also reject expressions returning sets; this is for consistency + * DefineRelation() will make more checks. + */ + if (expression_returns_set(pelem->expr)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("partition key expression cannot return a set"), + parser_errposition(pstate, pelem->location))); + } + + partby->partParams = lappend(partby->partParams, pelem); + } + + return partby; +} + +/* + * Compute per-partition-column information from a list of PartitionElem's + */ +static void +ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, + List **partexprbin, List **partexprsrc, + Oid *partopclass) +{ + int attn; + ListCell *lc; + + attn = 0; + foreach(lc, partParams) + { + PartitionElem *pelem = (PartitionElem *) lfirst(lc); + Oid atttype; + Oid opclassOid; + + if (pelem->name != NULL) + { + 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; + ReleaseSysCache(atttuple); + } + else + { + /* Partition key expression */ + Node *expr = pelem->expr; + + Assert(expr != NULL); + atttype = exprType(expr); + + if (IsA(expr, CollateExpr)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot use COLLATE in partition key expression"))); + + 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 + { + char *exprsrc; + + partattrs[attn] = 0; /* marks expression */ + *partexprbin = lappend(*partexprbin, expr); + + /* + * transformExpr() should have already rejected subqueries, + * aggregates, and window functions, based on the EXPR_KIND_ + * for a partition key expression. + */ + + /* + * An expression using mutable functions is probably wrong even + * even to use in a partition key + */ + expr = (Node *) expression_planner((Expr *) expr); + + if (IsA(expr, Const)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot use a constant expression as partition key"))); + + if (contain_mutable_functions(expr)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("functions in partition key expression must be marked IMMUTABLE"))); + + exprsrc = deparse_expression(expr, + deparse_context_for(RelationGetRelationName(rel), + RelationGetRelid(rel)), + false, false); + *partexprsrc = lappend(*partexprsrc, makeString(exprsrc)); + } + } + + /* + * Identify a btree opclass to use. Currently, we use only btree + * operators which seems enough for list and range partitioning. + */ + if (!pelem->opclass) + { + opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID); + + if (!OidIsValid(opclassOid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("data type %s has no default btree operator class", + format_type_be(atttype)), + errhint("You must specify an existing btree operator class or define one for the type."))); + } + else + opclassOid = GetIndexOpClass(pelem->opclass, + atttype, + "btree", + BTREE_AM_OID); + + partopclass[attn++] = opclassOid; + } +} diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 9de22a1..51b6d17 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -174,7 +174,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 && @@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid) rel = heap_open(relid, AccessExclusiveLock); if (rel->rd_rel->relkind != RELKIND_RELATION && + rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE && rel->rd_rel->relkind != RELKIND_VIEW && rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE) ereport(ERROR, @@ -1218,7 +1220,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..efa5200 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -1313,6 +1313,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params) * relation. */ if (onerel->rd_rel->relkind != RELKIND_RELATION && + onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE && onerel->rd_rel->relkind != RELKIND_MATVIEW && onerel->rd_rel->relkind != RELKIND_TOASTVALUE) { 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 af7b26c..5790edc 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind; if (relkind == RELKIND_RELATION || + relkind == RELKIND_PARTITIONED_TABLE || relkind == RELKIND_MATVIEW) { j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 4f39dad..37a60b6 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3017,6 +3017,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode) COPY_NODE_FIELD(relation); COPY_NODE_FIELD(tableElts); COPY_NODE_FIELD(inhRelations); + COPY_NODE_FIELD(partby); COPY_NODE_FIELD(ofTypename); COPY_NODE_FIELD(constraints); COPY_NODE_FIELD(options); @@ -4173,6 +4174,32 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from) return newnode; } +static PartitionBy * +_copyPartitionBy(const PartitionBy *from) +{ + + PartitionBy *newnode = makeNode(PartitionBy); + + COPY_SCALAR_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(opclass); + COPY_LOCATION_FIELD(location); + + return newnode; +} + /* **************************************************************** * pg_list.h copy functions * **************************************************************** @@ -5087,6 +5114,12 @@ copyObject(const void *from) case T_RoleSpec: retval = _copyRoleSpec(from); break; + case T_PartitionBy: + retval = _copyPartitionBy(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 4800165..a321ac1 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1167,6 +1167,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b) COMPARE_NODE_FIELD(relation); COMPARE_NODE_FIELD(tableElts); COMPARE_NODE_FIELD(inhRelations); + COMPARE_NODE_FIELD(partby); COMPARE_NODE_FIELD(ofTypename); COMPARE_NODE_FIELD(constraints); COMPARE_NODE_FIELD(options); @@ -2633,6 +2634,27 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b) return true; } +static bool +_equalPartitionBy(const PartitionBy *a, const PartitionBy *b) +{ + COMPARE_SCALAR_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(opclass); + COMPARE_LOCATION_FIELD(location); + + return true; +} + /* * Stuff from pg_list.h */ @@ -3386,6 +3408,12 @@ equal(const void *a, const void *b) case T_RoleSpec: retval = _equalRoleSpec(a, b); break; + case T_PartitionBy: + retval = _equalPartitionBy(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 90fecb1..ab75085 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2406,6 +2406,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node) WRITE_NODE_FIELD(relation); WRITE_NODE_FIELD(tableElts); WRITE_NODE_FIELD(inhRelations); + WRITE_NODE_FIELD(partby); WRITE_NODE_FIELD(ofTypename); WRITE_NODE_FIELD(constraints); WRITE_NODE_FIELD(options); @@ -3280,6 +3281,26 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node) appendStringInfo(str, " %u", node->conpfeqop[i]); } +static void +_outPartitionBy(StringInfo str, const PartitionBy *node) +{ + WRITE_NODE_TYPE("PARTITIONBY"); + + WRITE_CHAR_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(opclass); + WRITE_LOCATION_FIELD(location); +} /* * outNode - @@ -3864,6 +3885,11 @@ outNode(StringInfo str, const void *obj) break; case T_ForeignKeyCacheInfo: _outForeignKeyCacheInfo(str, obj); + case T_PartitionBy: + _outPartitionBy(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 1526c73..a95a65a 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; + PartitionBy *partby; } %type stmt schema_stmt @@ -541,6 +543,9 @@ 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 PartitionBy OptPartitionBy +%type part_elem +%type part_params /* * Non-keyword token types. These are hard-wired into the "flex" lexer. @@ -605,7 +610,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); KEY LABEL LANGUAGE LARGE_P LAST_P LATERAL_P - LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL + LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE @@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item: *****************************************************************************/ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')' - OptInherit OptWith OnCommitOption OptTableSpace + OptInherit OptPartitionBy OptWith OnCommitOption OptTableSpace { CreateStmt *n = makeNode(CreateStmt); $4->relpersistence = $2; n->relation = $4; n->tableElts = $6; n->inhRelations = $8; + n->partby = $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 OptPartitionBy OptWith + OnCommitOption OptTableSpace { CreateStmt *n = makeNode(CreateStmt); $7->relpersistence = $2; n->relation = $7; n->tableElts = $9; n->inhRelations = $11; + n->partby = $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 OptPartitionBy OptWith OnCommitOption + OptTableSpace { CreateStmt *n = makeNode(CreateStmt); $4->relpersistence = $2; n->relation = $4; n->tableElts = $7; n->inhRelations = NIL; + n->partby = $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 OptPartitionBy OptWith OnCommitOption + OptTableSpace { CreateStmt *n = makeNode(CreateStmt); $7->relpersistence = $2; n->relation = $7; n->tableElts = $10; n->inhRelations = NIL; + n->partby = $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; } @@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; } | /*EMPTY*/ { $$ = NIL; } ; +/* Optional partition key definition */ +OptPartitionBy: PartitionBy { $$ = $1; } + | /*EMPTY*/ { $$ = NULL; } + ; + +PartitionBy: PARTITION BY RANGE '(' part_params ')' + { + PartitionBy *n = makeNode(PartitionBy); + + n->strategy = PARTITION_STRAT_RANGE; + n->partParams = $5; + n->location = @1; + + $$ = n; + } + | PARTITION BY LIST '(' part_params ')' + { + PartitionBy *n = makeNode(PartitionBy); + + n->strategy = PARTITION_STRAT_LIST; + n->partParams = $5; + n->location = @1; + + $$ = n; + } + ; + +part_params: part_elem { $$ = list_make1($1); } + | part_params ',' part_elem { $$ = lappend($1, $3); } + ; + +part_elem: ColId opt_class + { + PartitionElem *n = makeNode(PartitionElem); + + n->name = $1; + n->expr = NULL; + n->opclass = $2; + n->location = @1; + $$ = n; + } + | func_expr_windowless opt_class + { + PartitionElem *n = makeNode(PartitionElem); + + n->name = NULL; + n->expr = $1; + n->opclass = $2; + n->location = @1; + $$ = n; + } + | '(' a_expr ')' opt_class + { + PartitionElem *n = makeNode(PartitionElem); + + n->name = NULL; + n->expr = $2; + n->opclass = $4; + n->location = @1; + $$ = n; + } + ; /* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */ OptWith: WITH reloptions { $$ = $2; } @@ -13782,6 +13855,7 @@ unreserved_keyword: | LAST_P | LEAKPROOF | LEVEL + | LIST | LISTEN | LOAD | LOCAL diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 481a4dd..3e8d457 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -501,6 +501,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) err = _("grouping operations are not allowed in trigger WHEN conditions"); break; + case EXPR_KIND_PARTITION_KEY: + 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 +866,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_KEY: + 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 63f7965..71c0c1c 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -1757,6 +1757,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink) case EXPR_KIND_TRIGGER_WHEN: err = _("cannot use subquery in trigger WHEN condition"); break; + case EXPR_KIND_PARTITION_KEY: + err = _("cannot use subquery in partition key expressions"); + break; /* * There is intentionally no default: case here, so that the @@ -3359,6 +3362,8 @@ ParseExprKindName(ParseExprKind exprKind) return "EXECUTE"; case EXPR_KIND_TRIGGER_WHEN: return "WHEN"; + case EXPR_KIND_PARTITION_KEY: + return "partition key expression"; /* * 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 7a2950e..85d67c1 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->partby != NULL; /* * Notice that we allow OIDs here only for plain tables, even though @@ -247,6 +249,29 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) if (stmt->ofTypename) transformOfType(&cxt, stmt->ofTypename); + if (stmt->partby) + { + int partnatts = list_length(stmt->partby->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 use more than %d columns in partition key", + PARTITION_MAX_KEYS))); + + if (stmt->partby->strategy == PARTITION_STRAT_LIST && + partnatts > 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot use more than one column in partition key"), + errdetail("Only one column allowed with list partitioning."))); + } + /* * Run through each primary element in the table creation clause. Separate * column defs from constraints, and do preliminary analysis. We have to @@ -583,6 +608,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 +623,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 +646,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 +717,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 +733,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 +749,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 +769,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; @@ -760,6 +827,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla relation = relation_openrv(table_like_clause->relation, AccessShareLock); if (relation->rd_rel->relkind != RELKIND_RELATION && + relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE && relation->rd_rel->relkind != RELKIND_VIEW && relation->rd_rel->relkind != RELKIND_MATVIEW && relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE && @@ -2518,6 +2586,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..8d28634 100644 --- a/src/backend/rewrite/rewriteDefine.c +++ b/src/backend/rewrite/rewriteDefine.c @@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename, * blocks them for users. Don't mention them in the error message. */ if (event_relation->rd_rel->relkind != RELKIND_RELATION && + event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE && event_relation->rd_rel->relkind != RELKIND_MATVIEW && event_relation->rd_rel->relkind != RELKIND_VIEW) ereport(ERROR, diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index a22a11e..41f9304 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte, TargetEntry *tle; if (target_relation->rd_rel->relkind == RELKIND_RELATION || + target_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE || target_relation->rd_rel->relkind == RELKIND_MATVIEW) { /* diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index f50ce40..f19479d 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -983,10 +983,13 @@ ProcessUtilitySlow(ParseState *pstate, { Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + char relkind = ((CreateStmt *) stmt)->partby != NULL + ? RELKIND_PARTITIONED_TABLE + : RELKIND_RELATION; /* Create the table itself */ address = DefineRelation((CreateStmt *) stmt, - RELKIND_RELATION, + relkind, InvalidOid, NULL); EventTriggerCollectSimpleCommand(address, secondaryObject, diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 79e0b1f..8cbd6e7 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -40,6 +40,7 @@ #include "catalog/index.h" #include "catalog/indexing.h" #include "catalog/namespace.h" +#include "catalog/partition.h" #include "catalog/pg_am.h" #include "catalog/pg_amproc.h" #include "catalog/pg_attrdef.h" @@ -431,6 +432,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple) switch (relation->rd_rel->relkind) { case RELKIND_RELATION: + case RELKIND_PARTITIONED_TABLE: case RELKIND_TOASTVALUE: case RELKIND_INDEX: case RELKIND_VIEW: @@ -1050,6 +1052,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 +2053,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 +2996,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_PARTITIONED_TABLE || + relkind == RELKIND_MATVIEW)) rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT; else rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING; @@ -5035,6 +5050,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..4a50cb8 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, /* PARTEDRELID */ + 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..502bc1a 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -67,6 +67,11 @@ * created only during initdb. The fields for the dependent object * contain zeroes. * + * DEPENDENCY_IGNORE ('g'): like DEPENDENCY_PIN, there is no dependent + * object; this type of entry is a signal that no dependency should be + * created between the objects in question. However, unlike pin + * dependencies, these never make it to pg_depend. + * * Other dependency flavors may be needed in future. */ @@ -77,7 +82,8 @@ typedef enum DependencyType DEPENDENCY_INTERNAL = 'i', DEPENDENCY_EXTENSION = 'e', DEPENDENCY_AUTO_EXTENSION = 'x', - DEPENDENCY_PIN = 'p' + DEPENDENCY_PIN = 'p', + DEPENDENCY_IGNORE = 'g' } DependencyType; /* 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/partition.h b/src/include/catalog/partition.h new file mode 100644 index 0000000..9c266c1 --- /dev/null +++ b/src/include/catalog/partition.h @@ -0,0 +1,35 @@ +/*------------------------------------------------------------------------- + * + * partition.h + * Header file for structures and utility functions related to + * partitioning + * + * Copyright (c) 2007-2016, PostgreSQL Global Development Group + * + * src/include/utils/partition.h + * + *------------------------------------------------------------------------- + */ +#ifndef PARTITION_H +#define PARTITION_H + +#include "fmgr.h" +#include "utils/relcache.h" + +typedef struct PartitionKeyData *PartitionKey; + +/* relcache support for partition key information */ +extern void RelationBuildPartitionKey(Relation relation); + +/* Partition key inquiry functions */ +extern int get_partition_key_strategy(PartitionKey key); +extern int get_partition_key_natts(PartitionKey key); +extern List *get_partition_key_exprs(PartitionKey key); + +/* Partition key inquiry functions - for a given column */ +extern int16 get_partition_col_attnum(PartitionKey key, int col); +extern Oid get_partition_col_typid(PartitionKey key, int col); +extern int32 get_partition_col_typmod(PartitionKey key, int col); +extern char *get_partition_col_name(PartitionKey key, int col); + +#endif /* PARTITION_H */ diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index e57b81c..ba0f745 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -154,6 +154,7 @@ DESCR(""); #define RELKIND_RELATION 'r' /* ordinary table */ +#define RELKIND_PARTITIONED_TABLE 'P' /* partitioned table */ #define RELKIND_INDEX 'i' /* secondary index */ #define RELKIND_SEQUENCE 'S' /* sequence object */ #define RELKIND_TOASTVALUE 't' /* for out-of-line values */ diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h new file mode 100644 index 0000000..db54358 --- /dev/null +++ b/src/include/catalog/pg_partitioned_table.h @@ -0,0 +1,69 @@ +/*------------------------------------------------------------------------- + * + * pg_partitioned_table.h + * definition of the system "partitioned table" relation + * along with the relation's initial contents. + * + * + * Portions Copyright (c) 1996-2015, 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; /* partition key strategy */ + int16 partnatts; /* number of partition key columns */ + + /* variable-length fields start here, but we allow direct access to partattrs */ + int2vector partattrs; /* attribute numbers of partition key + * columns */ + +#ifdef CATALOG_VARLEN + oidvector partclass; /* operator class to compare keys */ + pg_node_tree partexprbin; /* expression trees for partition key members + * that are not simple column references; one + * for each zero entry in partkey[] */ + pg_node_tree partexprsrc; +#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_partexprbin 6 +#define Anum_pg_partitioned_table_partexprsrc 7 + +#endif /* PG_PARTITIONED_TABLE_H */ diff --git a/src/include/catalog/pg_partitioned_table_fn.h b/src/include/catalog/pg_partitioned_table_fn.h new file mode 100644 index 0000000..918ce79 --- /dev/null +++ b/src/include/catalog/pg_partitioned_table_fn.h @@ -0,0 +1,29 @@ +/*------------------------------------------------------------------------- + * + * pg_partitioned_table_fn.h + * prototypes for functions in catalog/pg_partitioned_table.c + * + * + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_partitioned_table_fn.h + * + *------------------------------------------------------------------------- + */ +#ifndef PG_PARTITIONED_TABLE_FN_H +#define PG_PARTITIONED_TABLE_FN_H + +#include "utils/relcache.h" + +/* pg_partitioned_table catalog functions */ +extern void StorePartitionKey(Relation rel, + char strategy, + int16 partnatts, + AttrNumber *partattrs, + List *partexprbin, + List *partexprsrc, + Oid *partopclass); +extern void RemovePartitionKeyByRelId(Oid relid); + +#endif /* PG_PARTITIONED_TABLE_FN_H */ diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 2b894ff..c7b0af3 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 GetIndexOpClass(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 2f7efa8..c4abdf7 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -453,6 +453,8 @@ typedef enum NodeTag T_OnConflictClause, T_CommonTableExpr, T_RoleSpec, + T_PartitionElem, + T_PartitionBy, /* * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 8d3dcf4..afaa4d3 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -698,6 +698,40 @@ typedef struct XmlSerialize int location; /* token location, or -1 if unknown */ } XmlSerialize; +/* Partitioning related definitions */ + +/* + * PartitionElem - a partition key column + * + * 'name' Name of the table column included in the key + * 'expr' Expression node tree of expressional key column + * 'opclass' Operator class name associated with the column + */ +typedef struct PartitionElem +{ + NodeTag type; + char *name; /* name of column to partition on, or NULL */ + Node *expr; /* expression to partition on, or NULL */ + List *opclass; /* name of desired opclass; NIL = default */ + int location; /* token location, or -1 if unknown */ +} PartitionElem; + +/* + * PartitionBy - partition key definition including the strategy + * + * 'strategy' partition strategy to use (one of the below defined) + * 'partParams' List of PartitionElems, one for each key column + */ +#define PARTITION_STRAT_LIST 'l' +#define PARTITION_STRAT_RANGE 'r' + +typedef struct PartitionBy +{ + NodeTag type; + char strategy; + List *partParams; + int location; /* token location, or -1 if unknown */ +} PartitionBy; /**************************************************************************** * Nodes for a Query tree @@ -1752,6 +1786,7 @@ typedef struct CreateStmt List *tableElts; /* column definitions (list of ColumnDef) */ List *inhRelations; /* relations to inherit from (list of * inhRelation) */ + PartitionBy *partby; /* PARTITION BY clause */ TypeName *ofTypename; /* OF typename */ List *constraints; /* constraints (list of Constraint nodes) */ List *options; /* options from WITH clause */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 17ffef5..40da67a 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -227,6 +227,7 @@ PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD) PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD) +PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD) PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD) PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD) PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD) diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index e3e359c..a13c6fb 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_KEY /* partition key 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 ed14442..07de59f 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -94,6 +94,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 */ @@ -532,6 +535,12 @@ typedef struct ViewOptions RelationNeedsWAL(relation) && \ !IsCatalogRelation(relation)) +/* + * RelationGetPartitionKey + * Returns partition key for a relation. + */ +#define RelationGetPartitionKey(relation) ((relation)->rd_partkey) + /* 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..e727842 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -72,6 +72,7 @@ enum SysCacheIdentifier OPEROID, OPFAMILYAMNAMENSP, OPFAMILYOID, + PARTEDRELID, PROCNAMEARGSNSP, PROCOID, RANGETYPE, diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 3232cda..140026c 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -2914,3 +2914,49 @@ Table "public.test_add_column" c4 | integer | DROP TABLE test_add_column; +-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported +CREATE TABLE partitioned ( + a int +) PARTITION BY LIST (a); +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 +CREATE TABLE no_drop_or_alter_partcol ( + a int +) PARTITION BY RANGE (a); +ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a; +ERROR: cannot drop column named in partition key +ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5); +ERROR: cannot alter type of column named in partition key +CREATE TABLE no_drop_or_alter_partexpr ( + a text +) PARTITION BY RANGE ((substring(a from 1 for 1))); +ALTER TABLE no_drop_alter_partexpr DROP COLUMN a; +ERROR: relation "no_drop_alter_partexpr" does not exist +ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5); +ERROR: relation "no_drop_alter_partcol" does not exist +-- partitioned table cannot partiticipate in regular inheritance +CREATE TABLE no_inh_child ( + a int +) PARTITION BY RANGE (a); +CREATE TABLE inh_parent(a int); +ALTER TABLE no_inh_child INHERIT inh_parent; +ERROR: cannot change inheritance of partitioned table +-- 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, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent; diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out index 41ceb87..708232d 100644 --- a/src/test/regress/expected/create_table.out +++ b/src/test/regress/expected/create_table.out @@ -253,3 +253,158 @@ 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; +-- +-- CREATE TABLE PARTITION BY +-- +-- cannot combine INHERITS and PARTITION BY (although grammar allows) +CREATE TABLE fail_inh_partition_by ( + 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 fail_two_col_list_key ( + a1 int, + a2 int +) PARTITION BY LIST (a1, a2); -- fail +ERROR: cannot use more than one column in partition key +DETAIL: Only one column allowed with list partitioning. +-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported +CREATE TABLE fail_pk ( + 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 fail_fk ( + 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 fail_unique ( + a int UNIQUE +) PARTITION BY RANGE (a); +ERROR: unique constraints are not supported on partitioned tables +LINE 2: a int UNIQUE + ^ +CREATE TABLE fail_exclusion ( + 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 fail_col_used_twice ( + a int +) PARTIION BY RANGE (a, a); +ERROR: syntax error at or near "PARTIION" +LINE 3: ) PARTIION 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 fail_set_returning_expr_in_key ( + a int +) PARTITION BY RANGE (retset(a)); +ERROR: partition key expression cannot return a set +DROP FUNCTION retset(int); +CREATE TABLE fail_agg_in_key ( + a int +) PARTITION BY RANGE ((avg(a))); +ERROR: aggregate functions are not allowed in partition key expression +CREATE TABLE fail_window_fun_in_key ( + 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 fail_const_key ( + a int +) PARTITION BY RANGE (('a')); +ERROR: cannot use a constant expression as partition key +CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE; +CREATE TABLE fail_const_key ( + a int +) PARTITION BY RANGE (const_func()); +ERROR: cannot use a constant expression as partition key +DROP FUNCTION const_func(); +-- specified column must be present in the table +CREATE TABLE fail_nonexist_col ( + 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 fail_system_col_key ( + a int +) PARTITION BY RANGE (xmin); +ERROR: cannot use system column "xmin" in partition key +-- cannot use COLLATE in partition key +CREATE TABLE fail_collate_key ( + a text +) PARTITION BY RANGE ((a COLLATE "default")); +ERROR: cannot use COLLATE in partition key expression +-- functions in key must be immutable +CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL; +CREATE TABLE fail_immut_func_key ( + a int +) PARTITION BY RANGE (immut_func(a)); +ERROR: functions in partition key expression must be marked IMMUTABLE +DROP FUNCTION immut_func(int); +-- prevent using columns of unsupported types in key (type must have a btree operator class) +CREATE TABLE fail_point_type_key ( + a point +) PARTITION BY LIST (a); +ERROR: data type point has no default btree operator class +HINT: You must specify an existing btree operator class or define one for the type. +CREATE TABLE fail_point_type_key ( + a point +) PARTITION BY LIST (a point_ops); +ERROR: operator class "point_ops" does not exist for access method "btree" +CREATE TABLE fail_point_type_key ( + a point +) PARTITION BY RANGE (a); +ERROR: data type point has no default btree operator class +HINT: You must specify an existing btree operator class or define one for the type. +CREATE TABLE fail_point_type_key ( + a point +) PARTITION BY RANGE (a point_ops); +ERROR: operator class "point_ops" does not exist for access method "btree" +-- check relkind +CREATE TABLE check_relkind ( + a int +) PARTITION BY RANGE (a); +SELECT relkind FROM pg_class WHERE relname = 'check_relkind'; + relkind +--------- + P +(1 row) + +DROP TABLE check_relkind; +-- prevent a function referenced in partition key from being dropped +CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL; +CREATE TABLE dependency_matters ( + a int +) PARTITION BY RANGE (plusone(a)); +DROP FUNCTION plusone(int); +ERROR: cannot drop function plusone(integer) because other objects depend on it +DETAIL: table dependency_matters depends on function plusone(integer) +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP TABLE dependency_matters; +DROP FUNCTION plusone(int); +-- partitioned table cannot partiticipate in regular inheritance +CREATE TABLE no_inh_parted ( + a int +) PARTITION BY RANGE (a); +CREATE TABLE fail () INHERITS (no_inh_parted); +ERROR: cannot inherit from table "no_inh_parted" +DETAIL: Table "no_inh_parted" is partitioned. +DROP TABLE no_inh_parted; +-- cannot add NO INHERIT constraints to partitioned tables +CREATE TABLE no_inh_con_parted ( + a int, + CONSTRAINT check_a CHECK (a > 0) NO INHERIT +) PARTITION BY RANGE (a); +ERROR: cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted" diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index 1c087a3..022a239 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -118,6 +118,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 72e65d4..49fbab6 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -1842,3 +1842,37 @@ ALTER TABLE test_add_column ADD COLUMN c4 integer; \d test_add_column DROP TABLE test_add_column; + +-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported +CREATE TABLE partitioned ( + a int +) PARTITION BY LIST (a); +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 +CREATE TABLE no_drop_or_alter_partcol ( + a int +) PARTITION BY RANGE (a); +ALTER TABLE no_drop_or_alter_partcol DROP COLUMN a; +ALTER TABLE no_drop_or_alter_partcol ALTER COLUMN a TYPE char(5); + +CREATE TABLE no_drop_or_alter_partexpr ( + a text +) PARTITION BY RANGE ((substring(a from 1 for 1))); +ALTER TABLE no_drop_alter_partexpr DROP COLUMN a; +ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5); + +-- partitioned table cannot partiticipate in regular inheritance +CREATE TABLE no_inh_child ( + a int +) PARTITION BY RANGE (a); +CREATE TABLE inh_parent(a int); +ALTER TABLE no_inh_child INHERIT inh_parent; + +-- cannot add NO INHERIT constraint to partitioned tables +ALTER TABLE partitioned ADD CONSTRAINT chk_a CHECK (a > 0) NO INHERIT; + +DROP TABLE partitioned, no_drop_or_alter_partcol, no_drop_or_alter_partexpr, no_inh_child, inh_parent; diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql index 78bdc8b..6e4a8be 100644 --- a/src/test/regress/sql/create_table.sql +++ b/src/test/regress/sql/create_table.sql @@ -269,3 +269,136 @@ 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; + +-- +-- CREATE TABLE PARTITION BY +-- + +-- cannot combine INHERITS and PARTITION BY (although grammar allows) +CREATE TABLE fail_inh_partition_by ( + a int +) INHERITS (some_table) PARTITION BY LIST (a); + +-- cannot use more than 1 column as partition key for list partitioned table +CREATE TABLE fail_two_col_list_key ( + a1 int, + a2 int +) PARTITION BY LIST (a1, a2); -- fail + +-- PRIMARY KEY, FOREIGN KEY, UNIQUE, EXCLUSION constraints not supported +CREATE TABLE fail_pk ( + a int PRIMARY KEY +) PARTITION BY RANGE (a); +CREATE TABLE pkrel( + a int PRIMARY KEY +); + +CREATE TABLE fail_fk ( + a int REFERENCES pkrel(a) +) PARTITION BY RANGE (a); +DROP TABLE pkrel; + +CREATE TABLE fail_unique ( + a int UNIQUE +) PARTITION BY RANGE (a); + +CREATE TABLE fail_exclusion ( + a int, + EXCLUDE USING gist (a WITH &&) +) PARTITION BY RANGE (a); + +-- prevent column from being used twice in the partition key +CREATE TABLE fail_col_used_twice ( + a int +) PARTIION 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 fail_set_returning_expr_in_key ( + a int +) PARTITION BY RANGE (retset(a)); +DROP FUNCTION retset(int); + +CREATE TABLE fail_agg_in_key ( + a int +) PARTITION BY RANGE ((avg(a))); + +CREATE TABLE fail_window_fun_in_key ( + a int, + b int +) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b))); + +CREATE TABLE fail_const_key ( + a int +) PARTITION BY RANGE (('a')); + +CREATE FUNCTION const_func () RETURNS int AS $$ SELECT 1; $$ LANGUAGE SQL IMMUTABLE; +CREATE TABLE fail_const_key ( + a int +) PARTITION BY RANGE (const_func()); +DROP FUNCTION const_func(); + +-- specified column must be present in the table +CREATE TABLE fail_nonexist_col ( + a int +) PARTITION BY RANGE (b); + +-- cannot use system columns in partition key +CREATE TABLE fail_system_col_key ( + a int +) PARTITION BY RANGE (xmin); + +-- cannot use COLLATE in partition key +CREATE TABLE fail_collate_key ( + a text +) PARTITION BY RANGE ((a COLLATE "default")); + +-- functions in key must be immutable +CREATE FUNCTION immut_func (a int) RETURNS int AS $$ SELECT a + random()::int; $$ LANGUAGE SQL; +CREATE TABLE fail_immut_func_key ( + a int +) PARTITION BY RANGE (immut_func(a)); +DROP FUNCTION immut_func(int); + +-- prevent using columns of unsupported types in key (type must have a btree operator class) +CREATE TABLE fail_point_type_key ( + a point +) PARTITION BY LIST (a); +CREATE TABLE fail_point_type_key ( + a point +) PARTITION BY LIST (a point_ops); +CREATE TABLE fail_point_type_key ( + a point +) PARTITION BY RANGE (a); +CREATE TABLE fail_point_type_key ( + a point +) PARTITION BY RANGE (a point_ops); + +-- check relkind +CREATE TABLE check_relkind ( + a int +) PARTITION BY RANGE (a); +SELECT relkind FROM pg_class WHERE relname = 'check_relkind'; +DROP TABLE check_relkind; + +-- prevent a function referenced in partition key from being dropped +CREATE FUNCTION plusone(a int) RETURNS INT AS $$ SELECT a+1; $$ LANGUAGE SQL; +CREATE TABLE dependency_matters ( + a int +) PARTITION BY RANGE (plusone(a)); +DROP FUNCTION plusone(int); +DROP TABLE dependency_matters; +DROP FUNCTION plusone(int); + +-- partitioned table cannot partiticipate in regular inheritance +CREATE TABLE no_inh_parted ( + a int +) PARTITION BY RANGE (a); +CREATE TABLE fail () INHERITS (no_inh_parted); +DROP TABLE no_inh_parted; + +-- cannot add NO INHERIT constraints to partitioned tables +CREATE TABLE no_inh_con_parted ( + a int, + CONSTRAINT check_a CHECK (a > 0) NO INHERIT +) PARTITION BY RANGE (a); -- 1.7.1