>From 625b46704bd287700eae2755753f489a45744610 Mon Sep 17 00:00:00 2001 From: amit Date: Mon, 22 Feb 2016 17:38:55 +0900 Subject: [PATCH 2/5] Infrastructure for creation of partitioned tables. First, this adds a new system catalog pg_partitioned with: partedrelid oid partstrat char partnatts int16 partattrs int2vector partclass oidvector partexprs pg_node_tree There is a unique index on partedrelid and associated syscache PARTEDRELID. Second, a new relkind RELKIND_PARTITIONED_REL is added to pg_class. Tables created with CREATE TABLE PARTITION BY are of this relkind. Some special properties: * Has a pg_partitioned entry * Access to them unconditionally overrides specified inheritance option * Dropping or altering type of columns that are part of the partition key is disallowed * Changing inheritance (ALTER TABLE table INHERIT parent) and linking to a type (ALTER TABLE table OF type) is disallowed Partition key is represented using struct PartitionKey and built during the first relcache build of a partitioned relation. It is stored in rd_partkey using the cache context. --- doc/src/sgml/catalogs.sgml | 99 ++++++ src/backend/access/common/reloptions.c | 2 + src/backend/catalog/Makefile | 4 +- src/backend/catalog/aclchk.c | 2 + src/backend/catalog/heap.c | 19 +- src/backend/catalog/objectaddress.c | 10 +- src/backend/catalog/partition.c | 451 ++++++++++++++++++++++++++++ src/backend/commands/copy.c | 6 + src/backend/commands/indexcmds.c | 3 +- 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 | 168 ++++++++++- src/backend/commands/trigger.c | 7 +- src/backend/commands/vacuum.c | 11 +- src/backend/executor/execMain.c | 1 + src/backend/executor/nodeModifyTable.c | 1 + src/backend/parser/parse_agg.c | 6 +- src/backend/parser/parse_clause.c | 8 + src/backend/parser/parse_expr.c | 2 +- src/backend/parser/parse_relation.c | 6 +- src/backend/parser/parse_utilcmd.c | 4 +- src/backend/rewrite/rewriteDefine.c | 1 + src/backend/rewrite/rewriteHandler.c | 1 + src/backend/tcop/utility.c | 5 +- src/backend/utils/adt/ruleutils.c | 147 +++++++++ src/backend/utils/cache/relcache.c | 14 +- src/backend/utils/cache/syscache.c | 12 + src/bin/pg_dump/pg_dump.c | 36 ++- src/bin/pg_dump/pg_dump.h | 1 + src/bin/psql/describe.c | 61 +++- src/bin/psql/tab-complete.c | 6 +- src/include/catalog/indexing.h | 3 + src/include/catalog/partition.h | 55 ++++ src/include/catalog/pg_class.h | 1 + src/include/catalog/pg_partitioned.h | 67 ++++ src/include/catalog/pg_proc.h | 2 + src/include/commands/defrem.h | 4 +- src/include/utils/builtins.h | 1 + src/include/utils/rel.h | 2 + src/include/utils/syscache.h | 1 + src/test/regress/expected/create_table.out | 30 ++- src/test/regress/expected/sanity_check.out | 1 + src/test/regress/sql/create_table.sql | 21 ++ 45 files changed, 1227 insertions(+), 61 deletions(-) create mode 100644 src/backend/catalog/partition.c create mode 100644 src/include/catalog/partition.h create mode 100644 src/include/catalog/pg_partitioned.h diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index d6b60db..ba658b1 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -224,6 +224,11 @@ + pg_partitioned + information about partitioned tables, including the partition key + + + pg_policy row-security policies @@ -4682,6 +4687,100 @@ + + <structname>pg_partitioned</structname> + + + pg_partitioned + + + + The catalog pg_partitioned stores information + about the partition key of partitioned tables. + + + + <structname>pg_partitioned</> 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/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 797be63..bfccf9b 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -925,6 +925,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, case RELKIND_RELATION: case RELKIND_TOASTVALUE: case RELKIND_MATVIEW: + case RELKIND_PARTITIONED_REL: options = heap_reloptions(classForm->relkind, datum, false); break; case RELKIND_VIEW: @@ -1376,6 +1377,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate) return (bytea *) rdopts; case RELKIND_RELATION: case RELKIND_MATVIEW: + case RELKIND_PARTITIONED_REL: 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..4dc6300 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -11,7 +11,7 @@ top_builddir = ../../.. include $(top_builddir)/src/Makefile.global OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \ - objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \ + objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \ pg_constraint.o pg_conversion.o \ pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \ pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \ @@ -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.h\ toasting.h indexing.h \ ) diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 4b49bb6..c564207 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -765,6 +765,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_REL); + 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/heap.c b/src/backend/catalog/heap.c index e997b57..932a48b 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -41,6 +41,7 @@ #include "catalog/heap.h" #include "catalog/index.h" #include "catalog/objectaccess.h" +#include "catalog/partition.h" #include "catalog/pg_attrdef.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.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_REL || + 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_REL: 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_REL || 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_REL); + 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 tuples. + */ + if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL) + RemovePartitionKeyByRelId(relid); + + /* * Schedule unlinking of the relation's physical files at commit. */ if (rel->rd_rel->relkind != RELKIND_VIEW && diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 1324461..215375e 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_REL) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table", @@ -3252,6 +3253,10 @@ getRelationDescription(StringInfo buffer, Oid relid) appendStringInfo(buffer, _("table %s"), relname); break; + case RELKIND_PARTITIONED_REL: + appendStringInfo(buffer, _("partitioned table %s"), + relname); + break; case RELKIND_INDEX: appendStringInfo(buffer, _("index %s"), relname); @@ -3708,6 +3713,9 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId) case RELKIND_RELATION: appendStringInfoString(buffer, "table"); break; + case RELKIND_PARTITIONED_REL: + appendStringInfoString(buffer, "partitioned table"); + break; case RELKIND_INDEX: appendStringInfoString(buffer, "index"); break; diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c new file mode 100644 index 0000000..5e238dc --- /dev/null +++ b/src/backend/catalog/partition.c @@ -0,0 +1,451 @@ +/*------------------------------------------------------------------------- + * + * 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.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/syscache.h" + +static PartitionKey CopyPartitionKey(PartitionKey fromkey); +static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols, + KeyTypeCollInfo *tcinfo); +static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo); + +/* + * StorePartitionKey + * Store the partition keys of rel into pg_partitioned catalog + */ +void +StorePartitionKey(Relation rel, + char strategy, + int16 partnatts, + AttrNumber *partattrs, + List *partexprs, + Oid *partopclass) +{ + int i; + int2vector *partattrs_vec; + oidvector *partopclass_vec; + Datum partexprsDatum; + Relation pg_partitioned; + HeapTuple tuple; + Datum values[Natts_pg_partitioned]; + bool nulls[Natts_pg_partitioned]; + ObjectAddress myself; + ObjectAddress referenced; + + Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL); + + 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 (partexprs) + { + char *exprsString; + + exprsString = nodeToString(partexprs); + partexprsDatum = CStringGetTextDatum(exprsString); + pfree(exprsString); + } + else + partexprsDatum = (Datum) 0; + + pg_partitioned = heap_open(PartitionedRelationId, RowExclusiveLock); + + MemSet(nulls, false, sizeof(nulls)); + + /* Only this can ever be NULL */ + if (!partexprsDatum) + nulls[Anum_pg_partitioned_partexprs - 1] = true; + + values[Anum_pg_partitioned_partedrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel)); + values[Anum_pg_partitioned_partstrat - 1] = CharGetDatum(strategy); + values[Anum_pg_partitioned_partnatts - 1] = Int16GetDatum(partnatts); + values[Anum_pg_partitioned_partattrs - 1] = PointerGetDatum(partattrs_vec); + values[Anum_pg_partitioned_partclass - 1] = PointerGetDatum(partopclass_vec); + values[Anum_pg_partitioned_partexprs - 1] = partexprsDatum; + + tuple = heap_form_tuple(RelationGetDescr(pg_partitioned), values, nulls); + + simple_heap_insert(pg_partitioned, tuple); + + /* Update the indexes on pg_partitioned */ + CatalogUpdateIndexes(pg_partitioned, 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); + } + + /* + * Things inside key expressions + * + * An ugliness: normal dependencies also created on attribute references + * as part of the following. + */ + if (partexprs) + recordDependencyOnSingleRelExpr(&myself, + (Node *) partexprs, + RelationGetRelid(rel), + DEPENDENCY_NORMAL, + DEPENDENCY_NORMAL); + /* Tell world about the key */ + CacheInvalidateRelcache(rel); + + heap_close(pg_partitioned, RowExclusiveLock); + heap_freetuple(tuple); +} + +/* + * RemovePartitionKeyByRelId + * Remove pg_partitioned 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 */ + CatalogUpdateIndexes(rel, tuple); + + ReleaseSysCache(tuple); + heap_close(rel, RowExclusiveLock); +} + +/* + * 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 form; + Relation catalog; + HeapTuple tuple; + bool isnull; + int i; + PartitionKey key; + int2vector *partattrs; + oidvector *opclass; + KeyTypeCollInfo *tcinfo; + ListCell *partexprs_item; + Datum datum; + MemoryContext oldcxt; + + tuple = SearchSysCache1(PARTEDRELID, + ObjectIdGetDatum(RelationGetRelid(relation))); + /* + * Happend when relcache is built before the partition key is stored in + * system catalog + */ + if (!HeapTupleIsValid(tuple)) + return; + + form = (Form_pg_partitioned) GETSTRUCT(tuple); + + /* Allocate in the hopefully 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_partattrs, + RelationGetDescr(catalog), + &isnull); + Assert(!isnull); + partattrs = (int2vector *) DatumGetPointer(datum); + + datum = fastgetattr(tuple, Anum_pg_partitioned_partclass, + RelationGetDescr(catalog), + &isnull); + Assert(!isnull); + opclass = (oidvector *) DatumGetPointer(datum); + + datum = heap_getattr(tuple, + Anum_pg_partitioned_partexprs, + RelationGetDescr(catalog), + &isnull); + + if (!isnull) + { + char *exprsString; + Node *exprs; + + exprsString = TextDatumGetCString(datum); + exprs = stringToNode(exprsString); + pfree(exprsString); + + /* + * 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. + */ + exprs = eval_const_expressions(NULL, (Node *) exprs); + + /* May as well fix opfuncids too */ + fix_opfuncids((Node *) exprs); + key->partexprs = (List *) exprs; + } + + key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber)); + key->partopfamily = (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)); + + /* + * Copy partattrs. Further, determine and store the opfamily and + * btree support function per attribute. + */ + partexprs_item = list_head(key->partexprs); + 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 != InvalidAttrNumber) + { + 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(partexprs_item)); + tcinfo->typmod[i] = exprTypmod(lfirst(partexprs_item)); + tcinfo->typcoll[i] = exprCollation(lfirst(partexprs_item)); + partexprs_item = lnext(partexprs_item); + } + get_typlenbyvalalign(tcinfo->typid[i], + &tcinfo->typlen[i], + &tcinfo->typbyval[i], + &tcinfo->typalign[i]); + + 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; + + /* + * Btree support function is good to cover the list and range + * partitionig related needs. + */ + funcid = get_opfamily_proc(form->opcfamily, + form->opcintype, form->opcintype, + BTORDER_PROC); + + fmgr_info(funcid, &key->partsupfunc[i]); + ReleaseSysCache(tuple); + } + + ReleaseSysCache(tuple); + heap_close(catalog, AccessShareLock); + + /* Success --- now copy to the cache memory */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + relation->rd_partkey = CopyPartitionKey(key); + MemoryContextSwitchTo(oldcxt); + + FreePartitionKey(key); +} + +/* + * CopyPartitionKey + * + * The copy is allocated in the current memory context. + */ +static PartitionKey +CopyPartitionKey(PartitionKey fromkey) +{ + PartitionKey newkey; + + newkey = (PartitionKey) palloc0(sizeof(PartitionKeyData)); + + newkey->strategy = fromkey->strategy; + newkey->partnatts = fromkey->partnatts; + + newkey->partattrs = (AttrNumber *) + palloc(newkey->partnatts * sizeof(AttrNumber)); + memcpy(newkey->partattrs, fromkey->partattrs, + newkey->partnatts * sizeof(AttrNumber)); + + newkey->partopfamily = (Oid *) palloc(newkey->partnatts * sizeof(Oid)); + memcpy(newkey->partopfamily, fromkey->partopfamily, + newkey->partnatts * sizeof(Oid)); + + newkey->partsupfunc = (FmgrInfo *) + palloc(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); + + 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; +} + +/* + * free_key_type_info + */ +static void +free_key_type_coll_info(KeyTypeCollInfo *tcinfo) +{ + Assert(tcinfo != NULL); + + pfree(tcinfo->typid); + pfree(tcinfo->typmod); + pfree(tcinfo->typlen); + pfree(tcinfo->typbyval); + pfree(tcinfo->typalign); + pfree(tcinfo->typcoll); + pfree(tcinfo); +} + +/* + * Does relid have a pg_partitioned entry? + */ +bool +relid_is_partitioned(Oid relid) +{ + return SearchSysCacheExists1(PARTEDRELID, ObjectIdGetDatum(relid)); +} + +/* + * FreePartitionKeys + */ +void +FreePartitionKey(PartitionKey key) +{ + if (key == NULL) + return; + + pfree(key->partattrs); + pfree(key->partopfamily); + pfree(key->partsupfunc); + if (key->partexprs) + pfree(key->partexprs); + free_key_type_coll_info(key->tcinfo); + pfree(key); +} diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 3201476..403738d 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -1718,6 +1718,12 @@ BeginCopyTo(Relation rel, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot copy from sequence \"%s\"", RelationGetRelationName(rel)))); + else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL) + 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 fd1d7b2..ea232b1 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -367,7 +367,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_REL) { if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c index 175d1f3..62671a8 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_REL) 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 146b36c..7449273 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_REL) 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..8bc8638 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_REL && 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 f38126f..5ecad82 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -1467,6 +1467,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_REL || 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 17f4807..4976bbe 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" @@ -262,6 +263,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 @@ -438,6 +445,9 @@ static void ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs, List **partexprs, Oid *partoplass); +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); /* ---------------------------------------------------------------- @@ -601,7 +611,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_REL)); descriptor->tdhasoid = (localHasOids || parentOidCount > 0); /* @@ -715,7 +726,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, AddRelationNewConstraints(rel, rawDefaults, stmt->constraints, true, true, false); - /* Process partition key, if any */ + /* Process and store partition key, if any */ if (stmt->partby) { PartitionBy *partby; @@ -728,6 +739,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, partby = transformPartitionBy(rel, stmt->partby); ComputePartitionAttrs(relationId, stmt->partby->partParams, partattrs, &partexprs, partopclass); + StorePartitionKey(rel, partby->strategy, partnatts, partattrs, + partexprs, partopclass); } ObjectAddressSet(address, RelationRelationId, relationId); @@ -975,7 +988,15 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid, return; /* concurrently dropped, so nothing to do */ classform = (Form_pg_class) GETSTRUCT(tuple); - if (classform->relkind != relkind) + /* + * Both normal and partitioned tables are dropped using DROP TABLE. + * RemoveRelations however never passes RELKIND_PARTITIONED_REL as + * relkind for OBJECT_TABLE relations. A mismatch below may have + * to do with that. So, check. + */ + if (classform->relkind != relkind && + (relkind == RELKIND_RELATION && + classform->relkind != RELKIND_PARTITIONED_REL)) DropErrorMsgWrongType(rel->relname, classform->relkind, relkind); /* Allow DROP to either table owner or schema owner */ @@ -1313,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_REL) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table", @@ -1541,8 +1563,16 @@ MergeAttributes(List *schema, List *supers, char relpersistence, */ relation = heap_openrv(parent, ShareUpdateExclusiveLock); + /* Cannot inherit from partitioned tables */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot inherit from partitioned table \"%s\"", + parent->relname))); + if (relation->rd_rel->relkind != RELKIND_RELATION && - relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE) + relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE && + relation->rd_rel->relkind != RELKIND_PARTITIONED_REL) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("inherited relation \"%s\" is not a table or foreign table", @@ -2182,6 +2212,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing) * restriction. */ if (relkind != RELKIND_RELATION && + relkind != RELKIND_PARTITIONED_REL && relkind != RELKIND_VIEW && relkind != RELKIND_MATVIEW && relkind != RELKIND_COMPOSITE_TYPE && @@ -4313,6 +4344,7 @@ ATSimplePermissions(Relation rel, int allowed_targets) switch (rel->rd_rel->relkind) { case RELKIND_RELATION: + case RELKIND_PARTITIONED_REL: actual_target = ATT_TABLE; break; case RELKIND_VIEW: @@ -4549,6 +4581,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_REL || rel->rd_rel->relkind == RELKIND_MATVIEW) { if (origTypeName) @@ -5439,6 +5472,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_REL && rel->rd_rel->relkind != RELKIND_MATVIEW && rel->rd_rel->relkind != RELKIND_INDEX && rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE) @@ -5713,6 +5747,73 @@ 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; + ListCell *partexpr_item; + int i; + + if (rel->rd_rel->relkind != RELKIND_PARTITIONED_REL) + { + *is_expr = false; + return false; + } + + key = rel->rd_partkey; + + partexpr_item = list_head(key->partexprs); + for (i = 0; i < key->partnatts; i++) + { + AttrNumber partatt = key->partattrs[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. */ @@ -5727,6 +5828,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) @@ -5771,6 +5873,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); /* @@ -6289,6 +6404,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel, * Validity checks (permission checks wait till we have the column * numbers) */ + if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_REL) + 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), @@ -7883,6 +8004,7 @@ ATPrepAlterColumnType(List **wqueue, NewColumnValue *newval; ParseState *pstate = make_parsestate(NULL); AclResult aclresult; + bool is_expr; if (rel->rd_rel->reloftype && !recursing) ereport(ERROR, @@ -7913,6 +8035,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 column named in partition key"))); + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot alter column referenced in partition key expression"))); + } + /* Look up the target type */ typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod); @@ -7928,7 +8063,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_REL) { /* * Set up an expression to transform the old data value to the new @@ -8955,6 +9091,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock switch (tuple_class->relkind) { case RELKIND_RELATION: + case RELKIND_PARTITIONED_REL: case RELKIND_VIEW: case RELKIND_MATVIEW: case RELKIND_FOREIGN_TABLE: @@ -9417,6 +9554,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, switch (rel->rd_rel->relkind) { case RELKIND_RELATION: + case RELKIND_PARTITIONED_REL: case RELKIND_TOASTVALUE: case RELKIND_MATVIEW: (void) heap_reloptions(rel->rd_rel->relkind, newOptions, true); @@ -9839,7 +9977,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_REL) || (stmt->objtype == OBJECT_INDEX && relForm->relkind != RELKIND_INDEX) || (stmt->objtype == OBJECT_MATVIEW && @@ -10038,6 +10177,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_REL) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot change inheritance of partitioned table"))); } /* @@ -10089,6 +10233,12 @@ 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_REL) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot inherit from partitioned table \"%s\"", parent->relname))); + /* * Check for duplicates in the list of parents, and determine the highest * inhseqno already present; we'll use the next one for the new parent. @@ -11457,6 +11607,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_REL || rel->rd_rel->relkind == RELKIND_MATVIEW) { AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved); @@ -11906,7 +12057,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation, if (!relkind) return; if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE && - relkind != RELKIND_MATVIEW) + relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_REL) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table or materialized view", relation->relname))); @@ -12060,6 +12211,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid, */ if (IsA(stmt, AlterObjectSchemaStmt) && relkind != RELKIND_RELATION && + relkind != RELKIND_PARTITIONED_REL && relkind != RELKIND_VIEW && relkind != RELKIND_MATVIEW && relkind != RELKIND_SEQUENCE && diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 33107e0..a02db8b 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_REL) { /* 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_REL && 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_REL) 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 93361a0..003a30f 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -1301,9 +1301,14 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params) onerel->rd_rel->relkind != RELKIND_MATVIEW && onerel->rd_rel->relkind != RELKIND_TOASTVALUE) { - ereport(WARNING, - (errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables", - RelationGetRelationName(onerel)))); + if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_REL) + ereport(WARNING, + (errmsg("skipping \"%s\" --- cannot vacuum partitioned tables", + RelationGetRelationName(onerel)))); + else + ereport(WARNING, + (errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables", + RelationGetRelationName(onerel)))); relation_close(onerel, lmode); PopActiveSnapshot(); CommitTransactionCommand(); diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index ac02304..67492a5 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_REL: /* OK */ break; case RELKIND_SEQUENCE: diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index e62c8aa..7ad1140 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_REL || relkind == RELKIND_MATVIEW) { j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index b56fd4f..77ffd84 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -488,9 +488,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) break; case EXPR_KIND_PARTITION_KEY: if (isAgg) - err = _("aggregate functions are not allowed in partition key expressions"); + err = _("aggregate functions are not allowed in partition key expression"); else - err = _("grouping operations are not allowed in partition key expressions"); + err = _("grouping operations are not allowed in partition key expression"); break; @@ -852,7 +852,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, 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 expressions"); + err = _("window functions are not allowed in partition key expression"); break; /* diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 751de4b..307e383 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -21,6 +21,7 @@ #include "access/tsmapi.h" #include "catalog/catalog.h" #include "catalog/heap.h" +#include "catalog/partition.h" #include "catalog/pg_am.h" #include "catalog/pg_constraint_fn.h" #include "catalog/pg_type.h" @@ -200,6 +201,13 @@ setTargetTable(ParseState *pstate, RangeVar *relation, relation->alias, inh, false); pstate->p_target_rangetblentry = rte; + /* + * Override specified inheritance option, if relation is a partitioned + * table and it's a target of INSERT (ie, alsoSource == false). + */ + if (alsoSource) + rte->inh |= relid_is_partitioned(rte->relid); + /* assume new rte is at end */ rtindex = list_length(pstate->p_rtable); Assert(rte == rt_fetch(rtindex, pstate->p_rtable)); diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 70a663c..6a74c28 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -1716,7 +1716,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink) err = _("cannot use subquery in trigger WHEN condition"); break; case EXPR_KIND_PARTITION_KEY: - err = _("cannot use subquery in partition key expressions"); + err = _("cannot use subquery in partition key expression"); break; /* diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 81332b5..abcc1d6 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -20,6 +20,7 @@ #include "access/sysattr.h" #include "catalog/heap.h" #include "catalog/namespace.h" +#include "catalog/partition.h" #include "catalog/pg_type.h" #include "funcapi.h" #include "nodes/makefuncs.h" @@ -1216,9 +1217,12 @@ addRangeTableEntry(ParseState *pstate, * * The initial default on access checks is always check-for-READ-access, * which is the right thing for all except target tables. + * + * Note: we override the specified inheritance option, if relation is a + * partitioned table. */ rte->lateral = false; - rte->inh = inh; + rte->inh = inh || relid_is_partitioned(RelationGetRelid(rel)); rte->inFromCl = inFromCl; rte->requiredPerms = ACL_SELECT; diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index e267f74..465597d 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -33,6 +33,7 @@ #include "catalog/heap.h" #include "catalog/index.h" #include "catalog/namespace.h" +#include "catalog/partition.h" #include "catalog/pg_am.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" @@ -821,6 +822,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_REL && relation->rd_rel->relkind != RELKIND_VIEW && relation->rd_rel->relkind != RELKIND_MATVIEW && relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE && @@ -2577,7 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, cxt.blist = NIL; cxt.alist = NIL; cxt.pkey = NULL; - cxt.ispartitioned = false; + cxt.ispartitioned = relid_is_partitioned(relid); /* * The only subtypes that currently require parse transformation handling diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c index 96f3f37..db80c90 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_REL && 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..f429dd6 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_REL || target_relation->rd_rel->relkind == RELKIND_MATVIEW) { /* diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index ac50c2a..220cbfc 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -975,10 +975,13 @@ ProcessUtilitySlow(Node *parsetree, { Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + char relkind = ((CreateStmt *) stmt)->partby != NULL + ? RELKIND_PARTITIONED_REL + : RELKIND_RELATION; /* Create the table itself */ address = DefineRelation((CreateStmt *) stmt, - RELKIND_RELATION, + relkind, InvalidOid, NULL); EventTriggerCollectSimpleCommand(address, secondaryObject, diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 2b47e95..49f6fad 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -33,6 +33,7 @@ #include "catalog/pg_language.h" #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" +#include "catalog/pg_partitioned.h" #include "catalog/pg_proc.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" @@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno, const Oid *excludeOps, bool attrsOnly, bool showTblSpc, int prettyFlags); +static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags); static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, int prettyFlags); static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname, @@ -1289,6 +1291,151 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, return buf.data; } +/* + * pg_get_partkeydef + * + * Returns the partition key specification, ie, the following: + * + * PARTITION BY { RANGE | LIST } (column [USING opclass_name] [, ...]) + */ +Datum +pg_get_partkeydef(PG_FUNCTION_ARGS) +{ + Oid relationId = PG_GETARG_OID(0); + int prettyFlags; + + prettyFlags = PRETTYFLAG_INDENT; + PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relationId, + prettyFlags))); +} + +/* + * Internal workhorse to decompile a partition key definition. + */ +static char * +pg_get_partkeydef_worker(Oid relId, int prettyFlags) +{ + Form_pg_partitioned form; + HeapTuple tuple; + oidvector *partclass; + List *partexprs; + ListCell *partexpr_item; + List *context; + Datum datum; + bool isnull; + StringInfoData buf; + int keyno; + char *str; + char *sep; + + tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relId)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for partition key of %u", relId); + + form = (Form_pg_partitioned) GETSTRUCT(tuple); + + Assert(form->partedrelid == relId); + + /* Must get partclass, and partexprs the hard way */ + datum = SysCacheGetAttr(PARTEDRELID, tuple, + Anum_pg_partitioned_partclass, &isnull); + Assert(!isnull); + partclass = (oidvector *) DatumGetPointer(datum); + + /* + * Get the partition key expressions, if any. (NOTE: we do not use the + * relcache versions of the expressions, because we want to display + * non-const-folded expressions.) + */ + if (!heap_attisnull(tuple, Anum_pg_partitioned_partexprs)) + { + Datum exprsDatum; + bool isnull; + char *exprsString; + + exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple, + Anum_pg_partitioned_partexprs, &isnull); + Assert(!isnull); + exprsString = TextDatumGetCString(exprsDatum); + partexprs = (List *) stringToNode(exprsString); + pfree(exprsString); + } + else + partexprs = NIL; + + partexpr_item = list_head(partexprs); + context = deparse_context_for(get_relation_name(relId), relId); + + /* + * Start the partition key definition. + */ + initStringInfo(&buf); + + appendStringInfo(&buf, "PARTITION BY "); + + switch (form->partstrat) + { + case 'l': + appendStringInfo(&buf, "LIST"); + break; + case 'r': + appendStringInfo(&buf, "RANGE"); + break; + } + + /* + * Report the partition key columns + */ + appendStringInfo(&buf, " ("); + sep = ""; + for (keyno = 0; keyno < form->partnatts; keyno++) + { + AttrNumber attnum = form->partattrs.values[keyno]; + Oid keycoltype; + + appendStringInfoString(&buf, sep); + sep = ", "; + if (attnum != 0) + { + /* Simple partition key column */ + char *attname; + + attname = get_relid_attribute_name(relId, attnum); + appendStringInfoString(&buf, quote_identifier(attname)); + keycoltype = get_atttype(relId, attnum); + } + else + { + /* partition key expression */ + Node *partkey; + + if (partexpr_item == NULL) + elog(ERROR, "too few entries in partexprs list"); + partkey = (Node *) lfirst(partexpr_item); + partexpr_item = lnext(partexpr_item); + /* Deparse */ + str = deparse_expression_pretty(partkey, context, false, false, + prettyFlags, 0); + + /* Need parens if it's not a bare function call */ + if (partkey && IsA(partkey, FuncExpr) && + ((FuncExpr *) partkey)->funcformat == COERCE_EXPLICIT_CALL) + appendStringInfoString(&buf, str); + else + appendStringInfo(&buf, "(%s)", str); + keycoltype = exprType(partkey); + } + + /* Add the operator class name, if not default */ + get_opclass_name(partclass->values[keyno], keycoltype, &buf); + } + appendStringInfoChar(&buf, ')'); + + /* Clean up */ + ReleaseSysCache(tuple); + + return buf.data; +} /* * pg_get_constraintdef diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 432feef..2841745 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_REL: case RELKIND_TOASTVALUE: case RELKIND_INDEX: case RELKIND_VIEW: @@ -1049,6 +1051,12 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) else relation->rd_rsdesc = NULL; + /* if it's a partitioned table, initialize key info */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL) + RelationBuildPartitionKey(relation); + else + relation->rd_partkey = NULL; + /* * if it's an index, initialize index-related information */ @@ -2046,6 +2054,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc) MemoryContextDelete(relation->rd_rulescxt); if (relation->rd_rsdesc) MemoryContextDelete(relation->rd_rsdesc->rscxt); + FreePartitionKey(relation->rd_partkey); if (relation->rd_fdwroutine) pfree(relation->rd_fdwroutine); pfree(relation); @@ -2987,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_REL || + relkind == RELKIND_MATVIEW)) rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT; else rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING; @@ -4972,6 +4983,7 @@ load_relcache_init_file(bool shared) rel->rd_rulescxt = NULL; rel->trigdesc = NULL; rel->rd_rsdesc = 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..a6563fe 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.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_partedrelid, + 0, + 0, + 0 + }, + 128 + }, {ProcedureRelationId, /* PROCNAMEARGSNSP */ ProcedureNameArgsNspIndexId, 3, diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index e1e5bee..4c95aca 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -1234,9 +1234,10 @@ expand_table_name_patterns(Archive *fout, "SELECT c.oid" "\nFROM pg_catalog.pg_class c" "\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace" - "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n", + "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n", RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW, - RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE); + RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE, + RELKIND_PARTITIONED_REL); processSQLNamePattern(GetConnection(fout), query, cell->val, true, false, "n.nspname", "c.relname", NULL, "pg_catalog.pg_table_is_visible(c.oid)"); @@ -2064,6 +2065,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids) /* Skip FOREIGN TABLEs (no data to dump) */ if (tbinfo->relkind == RELKIND_FOREIGN_TABLE) return; + /* Skip partitioned tables (data in partitions) */ + if (tbinfo->relkind == RELKIND_PARTITIONED_REL) + return; /* Don't dump data in unlogged tables, if so requested */ if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED && @@ -5131,6 +5135,7 @@ getTables(Archive *fout, int *numTables) int i_toastreloptions; int i_reloftype; int i_relpages; + int i_partkeydef; /* Make sure we are in proper schema */ selectSourceSchema(fout, "pg_catalog"); @@ -5196,7 +5201,8 @@ getTables(Archive *fout, int *numTables) "array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, " "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text " "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, " - "tc.reloptions AS toast_reloptions " + "tc.reloptions AS toast_reloptions, " + "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef " "FROM pg_class c " "LEFT JOIN pg_depend d ON " "(c.relkind = '%c' AND " @@ -5208,7 +5214,7 @@ getTables(Archive *fout, int *numTables) "(c.oid = pip.objoid AND pip.classoid = " "(SELECT oid FROM pg_class " "WHERE relname = 'pg_class') AND pip.objsubid = 0) " - "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') " + "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') " "ORDER BY c.oid", acl_subquery->data, racl_subquery->data, @@ -5218,7 +5224,8 @@ getTables(Archive *fout, int *numTables) RELKIND_SEQUENCE, RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW, RELKIND_COMPOSITE_TYPE, - RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE); + RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE, + RELKIND_PARTITIONED_REL); destroyPQExpBuffer(acl_subquery); destroyPQExpBuffer(racl_subquery); @@ -5762,6 +5769,7 @@ getTables(Archive *fout, int *numTables) i_checkoption = PQfnumber(res, "checkoption"); i_toastreloptions = PQfnumber(res, "toast_reloptions"); i_reloftype = PQfnumber(res, "reloftype"); + i_partkeydef = PQfnumber(res, "partkeydef"); if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300) { @@ -5832,6 +5840,7 @@ getTables(Archive *fout, int *numTables) else tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption)); tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions)); + tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef)); /* other fields were zeroed above */ @@ -5858,7 +5867,9 @@ getTables(Archive *fout, int *numTables) * NOTE: it'd be kinda nice to lock other relations too, not only * plain tables, but the backend doesn't presently allow that. */ - if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION) + if (tblinfo[i].dobj.dump && + (tblinfo[i].relkind == RELKIND_RELATION || + tblinfo->relkind == RELKIND_PARTITIONED_REL)) { resetPQExpBuffer(query); appendPQExpBuffer(query, @@ -5945,7 +5956,10 @@ getInherits(Archive *fout, int *numInherits) /* find all the inheritance information */ - appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits"); + appendPQExpBufferStr(query, + "SELECT inhrelid, inhparent " + "FROM pg_inherits " + "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); @@ -15268,6 +15282,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) appendPQExpBufferChar(q, ')'); } + if (tbinfo->relkind == RELKIND_PARTITIONED_REL) + appendPQExpBuffer(q, "\n%s", tbinfo->partkeydef); + if (tbinfo->relkind == RELKIND_FOREIGN_TABLE) appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname)); } @@ -15327,6 +15344,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) */ if (dopt->binary_upgrade && (tbinfo->relkind == RELKIND_RELATION || + tbinfo->relkind == RELKIND_PARTITIONED_REL || tbinfo->relkind == RELKIND_FOREIGN_TABLE)) { for (j = 0; j < tbinfo->numatts; j++) @@ -15345,7 +15363,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout); appendPQExpBufferStr(q, "::pg_catalog.regclass;\n"); - if (tbinfo->relkind == RELKIND_RELATION) + if (tbinfo->relkind == RELKIND_RELATION || + tbinfo->relkind == RELKIND_PARTITIONED_REL) appendPQExpBuffer(q, "ALTER TABLE ONLY %s ", fmtId(tbinfo->dobj.name)); else @@ -15562,6 +15581,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) * dump properties we only have ALTER TABLE syntax for */ if ((tbinfo->relkind == RELKIND_RELATION || + tbinfo->relkind == RELKIND_PARTITIONED_REL || tbinfo->relkind == RELKIND_MATVIEW) && tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT) { diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 7314cbe..3094425 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -283,6 +283,7 @@ typedef struct _tableInfo bool *inhNotNull; /* true if NOT NULL is inherited */ struct _attrDefInfo **attrdefs; /* DEFAULT expressions */ struct _constraintInfo *checkexprs; /* CHECK constraints */ + char *partkeydef; /* partition key definition */ /* * Stuff computed only for dumpable tables. diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 0a771bd..78a1fdb 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -764,6 +764,7 @@ permissionsList(const char *pattern) " WHEN 'm' THEN '%s'" " WHEN 'S' THEN '%s'" " WHEN 'f' THEN '%s'" + " WHEN 'P' THEN '%s'" " END as \"%s\",\n" " ", gettext_noop("Schema"), @@ -773,6 +774,7 @@ permissionsList(const char *pattern) gettext_noop("materialized view"), gettext_noop("sequence"), gettext_noop("foreign table"), + gettext_noop("partitioned table"), gettext_noop("Type")); printACLColumn(&buf, "c.relacl"); @@ -819,7 +821,7 @@ permissionsList(const char *pattern) appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n" " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n" - "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n"); + "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n"); /* * Unless a schema pattern is specified, we suppress system and temp @@ -1463,8 +1465,8 @@ describeOneTableDetails(const char *schemaname, * types, and foreign tables (c.f. CommentObject() in comment.c). */ if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' || - tableinfo.relkind == 'm' || - tableinfo.relkind == 'f' || tableinfo.relkind == 'c') + tableinfo.relkind == 'm' || tableinfo.relkind == 'f' || + tableinfo.relkind == 'c' || tableinfo.relkind == 'P') appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)"); } @@ -1529,6 +1531,14 @@ describeOneTableDetails(const char *schemaname, printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""), schemaname, relationname); break; + case 'P': + if (tableinfo.relpersistence == 'u') + printfPQExpBuffer(&title, _("Unlogged partitioned table \"%s.%s\""), + schemaname, relationname); + else + printfPQExpBuffer(&title, _("Partitioned table \"%s.%s\""), + schemaname, relationname); + break; default: /* untranslated unknown relkind */ printfPQExpBuffer(&title, "?%c? \"%s.%s\"", @@ -1542,8 +1552,8 @@ describeOneTableDetails(const char *schemaname, cols = 2; if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' || - tableinfo.relkind == 'm' || - tableinfo.relkind == 'f' || tableinfo.relkind == 'c') + tableinfo.relkind == 'm' || tableinfo.relkind == 'f' || + tableinfo.relkind == 'c' || tableinfo.relkind == 'P') { show_modifiers = true; headers[cols++] = gettext_noop("Modifiers"); @@ -1563,12 +1573,12 @@ describeOneTableDetails(const char *schemaname, { headers[cols++] = gettext_noop("Storage"); if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' || - tableinfo.relkind == 'f') + tableinfo.relkind == 'f' || tableinfo.relkind == 'P') headers[cols++] = gettext_noop("Stats target"); /* Column comments, if the relkind supports this feature. */ if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' || - tableinfo.relkind == 'm' || - tableinfo.relkind == 'c' || tableinfo.relkind == 'f') + tableinfo.relkind == 'm' || tableinfo.relkind == 'c' || + tableinfo.relkind == 'f' || tableinfo.relkind == 'P') headers[cols++] = gettext_noop("Description"); } @@ -1668,7 +1678,7 @@ describeOneTableDetails(const char *schemaname, /* Statistics target, if the relkind supports this feature */ if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' || - tableinfo.relkind == 'f') + tableinfo.relkind == 'f' || tableinfo.relkind == 'P') { printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1), false, false); @@ -1676,14 +1686,33 @@ describeOneTableDetails(const char *schemaname, /* Column comments, if the relkind supports this feature. */ if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' || - tableinfo.relkind == 'm' || - tableinfo.relkind == 'c' || tableinfo.relkind == 'f') + tableinfo.relkind == 'm' || tableinfo.relkind == 'c' || + tableinfo.relkind == 'f' || tableinfo.relkind == 'P') printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2), false, false); } } /* Make footers */ + if (pset.sversion >= 90600 && tableinfo.relkind == 'P') + { + /* Get the partition key information */ + PGresult *result; + char *partkeydef; + + printfPQExpBuffer(&buf, + "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);", + oid); + result = PSQLexec(buf.data); + if (!result || PQntuples(result) != 1) + goto error_return; + + partkeydef = PQgetvalue(result, 0, 0); + printfPQExpBuffer(&tmpbuf, _("Partition Key: %s"), partkeydef); + printTableAddFooter(&cont, tmpbuf.data); + PQclear(result); + } + if (tableinfo.relkind == 'i') { /* Footer information about an index */ @@ -1822,7 +1851,7 @@ describeOneTableDetails(const char *schemaname, PQclear(result); } else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' || - tableinfo.relkind == 'f') + tableinfo.relkind == 'f' || tableinfo.relkind == 'P') { /* Footer information about a table */ PGresult *result = NULL; @@ -2381,7 +2410,7 @@ describeOneTableDetails(const char *schemaname, * Finish printing the footer information about a table. */ if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' || - tableinfo.relkind == 'f') + tableinfo.relkind == 'f' || tableinfo.relkind == 'P') { PGresult *result; int tuples; @@ -2592,7 +2621,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind, Oid tablespace, const bool newline) { /* relkinds for which we support tablespaces */ - if (relkind == 'r' || relkind == 'm' || relkind == 'i') + if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P') { /* * We ignore the database default tablespace so that users not using @@ -2926,6 +2955,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys " WHEN 'S' THEN '%s'" " WHEN 's' THEN '%s'" " WHEN 'f' THEN '%s'" + " WHEN 'P' THEN '%s'" " END as \"%s\",\n" " pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"", gettext_noop("Schema"), @@ -2937,6 +2967,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys gettext_noop("sequence"), gettext_noop("special"), gettext_noop("foreign table"), + gettext_noop("partitioned table"), gettext_noop("Type"), gettext_noop("Owner")); @@ -2975,7 +3006,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN ("); if (showTables) - appendPQExpBufferStr(&buf, "'r',"); + appendPQExpBufferStr(&buf, "'r', 'P',"); if (showViews) appendPQExpBufferStr(&buf, "'v',"); if (showMatViews) diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index a62ffe6..741ac46 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = { /* catname */ "pg_catalog.pg_class c", /* selcondition */ - "c.relkind IN ('r')", + "c.relkind IN ('r', 'P')", /* viscondition */ "pg_catalog.pg_table_is_visible(c.oid)", /* namespace */ @@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = { /* catname */ "pg_catalog.pg_class c", /* selcondition */ - "c.relkind IN ('r', 'f', 'v')", + "c.relkind IN ('r', 'f', 'v', 'P')", /* viscondition */ "pg_catalog.pg_table_is_visible(c.oid)", /* namespace */ @@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = { /* catname */ "pg_catalog.pg_class c", /* selcondition */ - "c.relkind IN ('r', 'S', 'v', 'm', 'f')", + "c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')", /* viscondition */ "pg_catalog.pg_table_is_visible(c.oid)", /* namespace */ diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index ca5eb3d..cadb741 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_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid 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..2c876c4 --- /dev/null +++ b/src/include/catalog/partition.h @@ -0,0 +1,55 @@ +/*------------------------------------------------------------------------- + * + * 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" + +/* Type and collation information for partition key columns */ +typedef struct +{ + Oid *typid; + int32 *typmod; + int16 *typlen; + bool *typbyval; + char *typalign; + Oid *typcoll; +} KeyTypeCollInfo; + +/* Partition key information for one level */ +typedef struct PartitionKeyData +{ + char strategy; /* partition strategy */ + int16 partnatts; /* number of partition attributes */ + AttrNumber *partattrs; /* partition attnums */ + Oid *partopfamily; /* OIDs of opfamily per col */ + FmgrInfo *partsupfunc; /* lookup info for support funcs */ + List *partexprs; /* partition key expressions, if any */ + KeyTypeCollInfo *tcinfo; /* type and collation info (all columns) */ +} PartitionKeyData; + +typedef struct PartitionKeyData *PartitionKey; + +extern void StorePartitionKey(Relation rel, + char strategy, + int16 partnatts, + AttrNumber *partattrs, + List *partexprs, + Oid *partopclass); +extern void RemovePartitionKeyByRelId(Oid relid); +extern void RelationBuildPartitionKey(Relation relation); +extern void FreePartitionKey(PartitionKey key); +extern bool relid_is_partitioned(Oid relid); + +#endif /* PARTITION_H */ diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index fdc67c2..149f619 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -153,6 +153,7 @@ DESCR(""); #define RELKIND_RELATION 'r' /* ordinary table */ +#define RELKIND_PARTITIONED_REL '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.h b/src/include/catalog/pg_partitioned.h new file mode 100644 index 0000000..edc52db --- /dev/null +++ b/src/include/catalog/pg_partitioned.h @@ -0,0 +1,67 @@ +/*------------------------------------------------------------------------- + * + * pg_partitioned.h + * definition of the system "partitioned" relation (pg_partitioned) + * along with the relation's initial contents. + * + * + * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group + * + * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned.h $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_PARTITIONED_REL_H +#define PG_PARTITIONED_REL_H + +#include "catalog/genbki.h" + +/* ---------------- + * pg_partitioned definition. cpp turns this into + * typedef struct FormData_pg_partitioned + * ---------------- + */ +#define PartitionedRelationId 3350 + +CATALOG(pg_partitioned,3350) BKI_WITHOUT_OIDS +{ + Oid partedrelid; /* 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 partexprs; /* expression trees for partition key members + * that are not simple column references; one + * for each zero entry in partkey[] */ +#endif +} FormData_pg_partitioned; + +/* ---------------- + * Form_pg_partitioned corresponds to a pointer to a tuple with + * the format of pg_partitioned relation. + * ---------------- + */ +typedef FormData_pg_partitioned *Form_pg_partitioned; + +/* ---------------- + * compiler constants for pg_partitioned + * ---------------- + */ +#define Natts_pg_partitioned 6 +#define Anum_pg_partitioned_partedrelid 1 +#define Anum_pg_partitioned_partstrat 2 +#define Anum_pg_partitioned_partnatts 3 +#define Anum_pg_partitioned_partattrs 4 +#define Anum_pg_partitioned_partclass 5 +#define Anum_pg_partitioned_partexprs 6 + +#endif /* PG_PARTITIONED_REL_H */ diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index bb539d4..92a7260 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -1969,6 +1969,8 @@ DATA(insert OID = 1642 ( pg_get_userbyid PGNSP PGUID 12 1 0 0 0 f f f f t f DESCR("role name by OID (with fallback)"); DATA(insert OID = 1643 ( pg_get_indexdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ )); DESCR("index description"); +DATA(insert OID = 3352 ( pg_get_partkeydef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ )); +DESCR("partition key description"); DATA(insert OID = 1662 ( pg_get_triggerdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ )); DESCR("trigger description"); DATA(insert OID = 1387 ( pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ )); diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 28024f8..ceeb41f 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -42,8 +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); +extern Oid GetIndexOpClass(List *opclass, Oid attrType, + char *accessMethodName, Oid accessMethodId); /* commands/functioncmds.c */ extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 01976a1..027665c 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -720,6 +720,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS); extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS); extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS); extern Datum pg_get_indexdef(PG_FUNCTION_ARGS); +extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS); extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS); extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS); extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS); diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b5d82d6..cf91ef8 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -90,6 +90,8 @@ typedef struct RelationData /* use "struct" here to avoid needing to include rowsecurity.h: */ struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */ + 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 */ 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/create_table.out b/src/test/regress/expected/create_table.out index aaae9f9..a4cc948 100644 --- a/src/test/regress/expected/create_table.out +++ b/src/test/regress/expected/create_table.out @@ -326,9 +326,35 @@ ERROR: cannot use a constant expression as partition key CREATE TABLE fail_agg_in_key ( a int ) PARTITION BY RANGE ((avg(a))); -ERROR: aggregate functions are not allowed in partition key expressions +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 expressions +ERROR: window functions are not allowed in partition key expression +-- 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) + +-- cannot drop column that is part of the partition key +CREATE TABLE no_drop_alter_partcol ( + a int +) PARTITION BY RANGE (a); +ALTER TABLE no_drop_alter_partcol DROP COLUMN a; +ERROR: cannot drop column named in partition key +ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5); +ERROR: cannot alter column named in partition key +CREATE TABLE no_drop_alter_partexpr ( + a text +) PARTITION BY RANGE ((substring(a from 1 for 1))); +ALTER TABLE no_drop_alter_partexpr DROP COLUMN a; +ERROR: cannot drop column referenced in partition key expression +ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5); +ERROR: cannot alter column named in partition key +DROP TABLE check_relkind, no_drop_alter_partcol, no_drop_alter_partexpr; diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index 1c087a3..b1e473d 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|t pg_pltemplate|t pg_policy|t pg_proc|t diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql index df47656..022f38b 100644 --- a/src/test/regress/sql/create_table.sql +++ b/src/test/regress/sql/create_table.sql @@ -337,3 +337,24 @@ CREATE TABLE fail_window_fun_in_key ( a int, b int ) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b))); + +-- check relkind +CREATE TABLE check_relkind ( + a int +) PARTITION BY RANGE (a); +SELECT relkind FROM pg_class WHERE relname = 'check_relkind'; + +-- cannot drop column that is part of the partition key +CREATE TABLE no_drop_alter_partcol ( + a int +) PARTITION BY RANGE (a); +ALTER TABLE no_drop_alter_partcol DROP COLUMN a; +ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5); + +CREATE TABLE no_drop_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); + +DROP TABLE check_relkind, no_drop_alter_partcol, no_drop_alter_partexpr; -- 1.7.1