pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
To: Pg Hackers <pgsql-hackers@postgresql.org>
Subject: Declarative partitioning - another take
Date: Wed, 10 Aug 2016 20:09:42 +0900
Message-ID: <ad16e2f5-fc7c-cc2d-333a-88d4aa446f96@lab.ntt.co.jp> (raw)
List-Unsubscribe: <mailto:majordomo@postgresql.org?body=unsub%20pgsql-hackers>

Hi,

Attached is the latest set of patches to implement declarative
partitioning.  There is already a commitfest entry for the same:
https://commitfest.postgresql.org/10/611/

The old discussion is here:
http://www.postgresql.org/message-id/55D3093C.5010800@lab.ntt.co.jp/

Attached patches are described below:

0001-Catalog-and-DDL-for-partition-key.patch
0002-psql-and-pg_dump-support-for-partitioned-tables.patch

These patches create the infrastructure and DDL for partitioned
tables.

In addition to a catalog for storing the partition key information, this
adds a new relkind to pg_class.h. PARTITION BY clause is added to CREATE
TABLE. Tables so created are RELKIND_PARTITIONED_REL relations which are
to be special in a number of ways, especially with regard to their
interactions with regular table inheritance features.

PARTITION BY RANGE ({ column_name | ( expression ) } [ opclass ] [, ...])
PARTITION BY LIST ({ column_name | ( expression ) } [ opclass ])


0003-Catalog-and-DDL-for-partition-bounds.patch
0004-psql-and-pg_dump-support-for-partitions.patch

These patches create the infrastructure and DDL for partitions.

Parent-child relationships of a partitioned table and its partitions are
managed behind-the-scenes with inheritance.  That means there is a
pg_inherits entry and attributes, constraints, etc. are marked with
inheritance related information appropriately.  However this case differs
from a regular inheritance relationship in a number of ways.  While the
regular inheritance imposes certain restrictions on what elements a
child's schema is allowed to contain (both at creation time and
after-the-fact), the partitioning related code imposes further
restrictions.  For example, while regular inheritance allows a child to
contain its own columns, the partitioning code disallows that.  Stuff like
NO INHERIT marking on check constraints, ONLY are ignored by the the
partitioning code.

Partition DDL includes both a way to create new partition and "attach" an
existing table as a partition of parent partitioned table.  Attempt to
drop a partition using DROP TABLE causes an error. Instead a partition
needs first to be "detached" from parent partitioned table.  On the other
hand, dropping the parent drops all the partitions if CASCADE is specified.

CREATE TABLE partition_name
    PARTITION OF parent_table [ (
  { column_name WITH OPTIONS [ column_constraint [ ... ] ]
    | table_constraint }
    [, ... ]
) ] partition_bound_spec
[ PARTITION BY {RANGE | LIST} ( { column_name | ( expression ) } [ opclass
] [, ...] )

CREATE FOREIGN TABLE [ IF NOT EXISTS ] table_name
  PARTITION OF parent_table [ (
  { column_name WITH OPTIONS [ column_constraint [ ... ] ]
    | table_constraint }
    [, ... ]
) ] partition_bound_spec
  SERVER server_name
[ OPTIONS ( option 'value' [, ... ] ) ]

ALTER TABLE parent ATTACH PARTITION partition_name partition_bound_spec [
VALIDATE | NO VALIDATE ]

ALTER TABLE parent DETACH PARTITION partition_name

partition_bound_spec is:

FOR VALUES { list_spec | range_spec }

list_spec in FOR VALUES is:

IN ( expression [, ...] )

range_spec in FOR VALUES is:

START lower-bound [ INCLUSIVE | EXCLUSIVE ] END upper-bound [ INCLUSIVE |
EXCLUSIVE ]

where lower-bound and upper-bound are:

{ ( expression [, ...] ) | UNBOUNDED }

expression can be a string literal, a numeric literal or NULL.

Note that the one can specify PARTITION BY when creating a partition
itself. That is to allow creating multi-level partitioned tables.


0005-Teach-a-few-places-to-use-partition-check-constraint.patch

A partition's bound implicitly constrains the values that are allowed in
the partition key of its rows.  The same can be applied to partitions when
inserting data *directly* into them to make sure that only the correct
data is allowed in (if a tuple has been routed from the parent, that
becomes unnecessary). To that end, ExecConstraints() now includes the
above implicit check constraint in the list of constraints it enforces.

Further, to enable constraint based partition exclusion on partitioned
tables, the planner code includes in its list of constraints the above
implicitly defined constraints.  This arrangement is temporary however and
will be rendered unnecessary when we implement special data structures and
algorithms within the planner in future versions of this patch to use
partition metadata more effectively for partition exclusion.

Note that the "constraints" referred to above are not some on-disk
structures but those generated internally on-the-fly when requested by a
caller.

0006-Introduce-a-PartitionTreeNode-data-structure.patch
0007-Tuple-routing-for-partitioned-tables.patch

These patches enable routing of tuples inserted into a partitioned table
to one of its leaf partitions.  It applies to both COPY FROM and INSERT.
First of these patches introduces a data structure that provides a
convenient means for the tuple routing code to step down a partition tree
one level at a time.  The second one modifies copy.c and executor to
implement actual tuple routing.  When inserting into a partition, its row
constraints and triggers are applied.  Note that the partition's
constraints also include the constraints defined on the parent.  This
arrangements means however that the parent's triggers are not currently
applied.

Updates are handled like they are now for inheritance sets, however, if an
update makes a row change partition, an error will be thrown.

0008-Update-DDL-Partitioning-chapter.patch

This patch updates the partitioning section in the DDL chapter to reflect
the new methods made available for creating and managing partitioned table
and its partitions.  Especially considering that it is no longer necessary
to define CHECK constraints and triggers/rules manually for constraint
exclusion and tuple routing, respectively.

TODO (in short term):
* Add more regression tests and docs
* Add PartitionOptInfo and use it to perform partition pruning more
effectively (the added infrastructure should also help pairwise joins
patch proposed by Ashutosh Bapat [1])
* Fix internal representation of list partition bounds to be more efficient


Thanks,
Amit

[1]
https://www.postgresql.org/message-id/CAFjFpRfQ8GrQvzp3jA2wnLqrHmaXna-urjm_UY9BqXj%3DEaDTSA%40mail.g...

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

0001-Catalog-and-DDL-for-partitioned-tables.patch (text/x-diff, 116.2K) download From b53631393a83a14ebe9687ad6421fcd902a6b541 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Thu, 14 Jul 2016 09:59:15 +0900 Subject: [PATCH 1/8] 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_REL relations which are special in number of ways, especially their interactions with table inheritance features. --- doc/src/sgml/catalogs.sgml | 99 +++++++ 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 | 24 ++- src/backend/catalog/objectaddress.c | 10 +- src/backend/catalog/partition.c | 392 ++++++++++++++++++++++++++++ src/backend/catalog/pg_depend.c | 3 + src/backend/catalog/pg_partitioned.c | 171 ++++++++++++ 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 | 389 +++++++++++++++++++++++++++- 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_clause.c | 9 + src/backend/parser/parse_expr.c | 5 + src/backend/parser/parse_relation.c | 7 +- src/backend/parser/parse_utilcmd.c | 71 +++++ src/backend/rewrite/rewriteDefine.c | 1 + src/backend/rewrite/rewriteHandler.c | 1 + src/backend/tcop/utility.c | 5 +- src/backend/utils/cache/relcache.c | 14 +- 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.h | 67 +++++ src/include/catalog/pg_partitioned_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 | 8 + src/include/utils/syscache.h | 1 + src/test/regress/expected/alter_table.out | 46 ++++ src/test/regress/expected/create_table.out | 160 +++++++++++ src/test/regress/expected/sanity_check.out | 1 + src/test/regress/sql/alter_table.sql | 32 +++ src/test/regress/sql/create_table.sql | 135 ++++++++++ 56 files changed, 2048 insertions(+), 46 deletions(-) create mode 100644 src/backend/catalog/partition.c create mode 100644 src/backend/catalog/pg_partitioned.c create mode 100644 src/include/catalog/partition.h create mode 100644 src/include/catalog/pg_partitioned.h create mode 100644 src/include/catalog/pg_partitioned_fn.h diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index ccb9b97..b51323a 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -226,6 +226,11 @@ </row> <row> + <entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry> + <entry>information about partitioned tables, including the partition key</entry> + </row> + + <row> <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry> <entry>row-security policies</entry> </row> @@ -4668,6 +4673,100 @@ </sect1> + <sect1 id="catalog-pg-partitioned"> + <title><structname>pg_partitioned</structname></title> + + <indexterm zone="catalog-pg-partitioned"> + <primary>pg_partitioned</primary> + </indexterm> + + <para> + The catalog <structname>pg_partitioned</structname> stores information + about the partition key of partitioned tables. + </para> + + <table> + <title><structname>pg_partitioned</> Columns</title> + + <tgroup cols="4"> + <thead> + <row> + <entry>Name</entry> + <entry>Type</entry> + <entry>References</entry> + <entry>Description</entry> + </row> + </thead> + + <tbody> + + <row> + <entry><structfield>partedrelid</structfield></entry> + <entry><type>oid</type></entry> + <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry> + <entry>The OID of the <structname>pg_class</> entry for this partitioned table</entry> + </row> + + <row> + <entry><structfield>partstrat</structfield></entry> + <entry><type>char</type></entry> + <entry></entry> + <entry> + Partitioning strategy (or method); <literal>l</> = list partitioned table, + <literal>r</> = range partitioned table + </entry> + </row> + + <row> + <entry><structfield>partnatts</structfield></entry> + <entry><type>int2</type></entry> + <entry></entry> + <entry>The number of columns in partition key</entry> + </row> + + <row> + <entry><structfield>partattrs</structfield></entry> + <entry><type>int2vector</type></entry> + <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry> + <entry> + This is an array of <structfield>partnatts</structfield> values that + indicate which table columns are used as partition key. For example, + a value of <literal>1 3</literal> 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. + </entry> + </row> + + <row> + <entry><structfield>partclass</structfield></entry> + <entry><type>oidvector</type></entry> + <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry> + <entry> + For each column in the partition key, this contains the OID of + the operator class to use. See + <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details. + </entry> + </row> + + <row> + <entry><structfield>partexprs</structfield></entry> + <entry><type>pg_node_tree</type></entry> + <entry></entry> + <entry> + Expression trees (in <function>nodeToString()</function> + representation) for partition key columns that are not simple column + references. This is a list with one element for each zero + entry in <structfield>partkey</>. Null if all partition key columns + are simple references. + </entry> + </row> + + </tbody> + </tgroup> + </table> + </sect1> + <sect1 id="catalog-pg-policy"> <title><structname>pg_policy</structname></title> diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index bf2ad64..51e6936 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 ( <replaceable>parent_table</replaceable> [, ... ] ) ] +[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ] [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ] [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ] @@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI | <replaceable>table_constraint</replaceable> } [, ... ] ) ] +[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ] [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ] [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ] @@ -314,6 +316,39 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI </varlistentry> <varlistentry> + <term><literal>PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term> + <listitem> + <para> + The optional <literal>PARTITION BY</> clause specifies a method of + partitioning the table and the corresponding partition key. Table + thus created is called <firstterm>partitioned</firstterm> table. Key + consists of an ordered list of column names and/or expressions when + using the <literal>RANGE</> method, whereas only a single column or + expression can be specified when using the <literal>LIST</> method. + The type of a key column or an expresion must have an associated + btree operator class or one must be specified along with the column + or the expression. + </para> + + <para> + A partitioned table is divided into sub-tables (called partitions), which + in turn, are created using separate <literal>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. + </para> + + <para> + Currently, there are following limitations on definition of partitioned + tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or + FOREIGN KEY constraints. + </para> + + </listitem> + </varlistentry> + + <varlistentry> <term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term> <listitem> <para> @@ -1369,6 +1404,26 @@ CREATE TABLE employees OF employee_type ( salary WITH OPTIONS DEFAULT 1000 ); </programlisting></para> + + <para> + Create a range partitioned table: +<programlisting> +CREATE TABLE measurement ( + city_id int not null, + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +</programlisting></para> + + <para> + Create a list partitioned table: +<programlisting> +CREATE TABLE cities ( + name text not null, + population int, +) PARTITION BY LIST (name); +</programlisting></para> </refsect1> <refsect1 id="SQL-CREATETABLE-compatibility"> diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index ba1f3aa..4dc4400 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_REL: 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_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..777459d 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.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.h\ toasting.h indexing.h \ ) diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index a585c3a..60554de 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_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/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..f5dcc56 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_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_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 && @@ -1985,6 +1996,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr, int keycount; int16 *attNos; Oid constrOid; + Oid relid = RelationGetRelid(rel); /* * Flatten expression to string form for storage. @@ -2031,6 +2043,10 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr, else attNos = NULL; + /* Remove NO INHERIT flag if rel is a partitioned table */ + if (relid_is_partitioned(relid)) + is_no_inherit = false; + /* * Create the Check Constraint */ diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 8068b82..6c89b08 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..f00caf2 --- /dev/null +++ b/src/backend/catalog/partition.c @@ -0,0 +1,392 @@ +/*------------------------------------------------------------------------- + * + * 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" + +/* 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 */ + 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); +static void free_key_type_coll_info(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 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))); + /* + * The following happens when we have created our pg_class entry but not + * the pg_partitioned entry yet. + */ + if (!HeapTupleIsValid(tuple)) + return; + + form = (Form_pg_partitioned) 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_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->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)); + + /* + * Copy partattrs. Further, determine and store the opfamily, opcintype, + * 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 != 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(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; + 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); + } + + ReleaseSysCache(tuple); + heap_close(catalog, AccessShareLock); + + /* Success --- now copy to the cache memory */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + relation->rd_partkey = copy_partition_key(key); + MemoryContextSwitchTo(oldcxt); + + FreePartitionKey(key); +} + +/* + * FreePartitionKey + */ +void +FreePartitionKey(PartitionKey key) +{ + if (key == NULL) + return; + + pfree(key->partattrs); + pfree(key->partopfamily); + pfree(key->partopcintype); + pfree(key->partsupfunc); + if (key->partexprs) + pfree(key->partexprs); + free_key_type_coll_info(key->tcinfo); + pfree(key); +} + +/* + * 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]; +} + +extern +int32 get_partition_col_typmod(PartitionKey key, int col) +{ + return key->tcinfo->typmod[col]; +} + +/* + * copy_partition_key + * + * The copy is allocated in the current memory context. + */ +static PartitionKey +copy_partition_key(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->partopcintype = (Oid *) palloc(newkey->partnatts * sizeof(Oid)); + memcpy(newkey->partopcintype, fromkey->partopcintype, + 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); +} 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.c b/src/backend/catalog/pg_partitioned.c new file mode 100644 index 0000000..ab967d5 --- /dev/null +++ b/src/backend/catalog/pg_partitioned.c @@ -0,0 +1,171 @@ +/*------------------------------------------------------------------------- + * + * pg_partitioned.c + * routines to support manipulation of the pg_partitioned 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.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.h" +#include "catalog/pg_partitioned_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 *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); + } + + /* + * 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 (partexprs) + recordDependencyOnSingleRelExpr(&myself, + (Node *) partexprs, + RelationGetRelid(rel), + DEPENDENCY_NORMAL, + DEPENDENCY_IGNORE); + /* 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); +} + +/* + * Does relid have a pg_partitioned entry? + */ +bool +relid_is_partitioned(Oid relid) +{ + return SearchSysCacheExists1(PARTEDRELID, ObjectIdGetDatum(relid)); +} diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index 9ac7122..678e079 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_REL || onerel->rd_rel->relkind == RELKIND_MATVIEW) { /* Regular table, so we'll use the regular row acquisition function */ @@ -1323,6 +1324,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_REL || 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 f45b330..53b2226 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 d14d540..e8d25e8 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_REL) { 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..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 bc2e4af..cadc9e4 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 c98f981..5dcd4cf 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 86e9814..e070a1a 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_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,13 @@ 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(Oid relid, List *partParams, + AttrNumber *partattrs, + List **partexprs, + Oid *partoplass); /* ---------------------------------------------------------------- @@ -596,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); /* @@ -697,6 +713,30 @@ 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 *partexprs = NIL; + + stmt->partby = transformPartitionBy(rel, stmt->partby); + ComputePartitionAttrs(relationId, stmt->partby->partParams, + partattrs, &partexprs, partopclass); + + partnatts = list_length(stmt->partby->partParams); + StorePartitionKey(rel, stmt->partby->strategy, partnatts, + partattrs, partexprs, partopclass); + + /* + * Bump the command counter to make the newly-created pg_partitioned + * tuple visible so that the constraint code invoked below can know + * that the relation is a partitioned table. + */ + CommandCounterIncrement(); + } + /* * 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 +995,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 */ @@ -1293,7 +1341,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", @@ -1521,8 +1570,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", @@ -2162,6 +2219,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 && @@ -4291,6 +4349,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: @@ -4527,6 +4586,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) @@ -5417,6 +5477,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) @@ -5691,6 +5752,77 @@ 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_REL) + { + *is_expr = false; + 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 +5837,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 +5882,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 +6413,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), @@ -7861,6 +8013,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 +8044,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); @@ -7906,7 +8072,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 @@ -8933,6 +9100,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: @@ -9395,6 +9563,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); @@ -9817,7 +9986,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 && @@ -10016,6 +10186,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"))); } /* @@ -10067,6 +10242,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. @@ -11445,6 +11626,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); @@ -11894,7 +12076,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))); @@ -12048,6 +12230,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 && @@ -12059,3 +12242,195 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid, ReleaseSysCache(tuple); } + +/* + * transformPartitionBy + * 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 *column; + PartitionElem *pelem = (PartitionElem *) lfirst(l); + + /* Check for PARTITION BY ... (foo, foo) */ + foreach(column, partby->partParams) + { + PartitionElem *pparam = (PartitionElem *) lfirst(column); + + 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; +} + +/* + * ComputePartitionAttrs + * Compute per-partition-column information from partParams + */ +static void +ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs, + List **partexprs, 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(relid, 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 + { + partattrs[attn] = 0; /* marks expression */ + *partexprs = lappend(*partexprs, 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"))); + } + } + + /* + * Identify the opclass to use. At the moment, we use "btree" operators + * that 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 99a659a..6da6f32 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 0563e63..804e56f 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -1315,6 +1315,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_REL && 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..3c73e81 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: @@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType) switch (rel->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 af7b26c..8e2da9c 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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 3244c76..700d7be 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2999,6 +2999,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); @@ -4154,6 +4155,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 * **************************************************************** @@ -5065,6 +5092,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 1eb6799..9554c2f 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1156,6 +1156,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); @@ -2619,6 +2620,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 */ @@ -3369,6 +3391,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 acaf4ea..4d5e37e 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node) WRITE_NODE_FIELD(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); @@ -3266,6 +3267,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 - @@ -3847,6 +3868,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 0cae446..e912a2d 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -227,6 +227,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); struct ImportQual *importqual; InsertStmt *istmt; VariableSetStmt *vsetstmt; + PartitionElem *partelem; + PartitionBy *partby; } %type <node> stmt schema_stmt @@ -539,6 +541,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); opt_frame_clause frame_extent frame_bound %type <str> opt_existing_window_name %type <boolean> opt_if_not_exists +%type <partby> PartitionBy OptPartitionBy +%type <partelem> part_elem +%type <list> part_params /* * Non-keyword token types. These are hard-wired into the "flex" lexer. @@ -603,7 +608,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; } @@ -13846,6 +13919,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_clause.c b/src/backend/parser/parse_clause.c index 751de4b..296ab89 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -21,8 +21,10 @@ #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_partitioned_fn.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "nodes/makefuncs.h" @@ -200,6 +202,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. + */ + 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 cead212..a97f727 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -1748,6 +1748,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 @@ -3297,6 +3300,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_relation.c b/src/backend/parser/parse_relation.c index 1e3ecbc..76bb427 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -20,6 +20,8 @@ #include "access/sysattr.h" #include "catalog/heap.h" #include "catalog/namespace.h" +#include "catalog/partition.h" +#include "catalog/pg_partitioned_fn.h" #include "catalog/pg_type.h" #include "funcapi.h" #include "nodes/makefuncs.h" @@ -1216,9 +1218,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 e98fad0..be21a8e 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -33,12 +33,14 @@ #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" #include "catalog/pg_constraint_fn.h" #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" +#include "catalog/pg_partitioned_fn.h" #include "catalog/pg_type.h" #include "commands/comment.h" #include "commands/defrem.h" @@ -87,6 +89,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 +232,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 +251,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 @@ -582,6 +609,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: @@ -591,6 +624,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); @@ -608,6 +647,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 @@ -673,6 +718,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; @@ -683,6 +734,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; @@ -693,6 +750,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; @@ -707,6 +770,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; @@ -759,6 +828,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 && @@ -2517,6 +2587,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, cxt.blist = NIL; cxt.alist = NIL; cxt.pkey = NULL; + 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 f82d891..eb71efc 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/cache/relcache.c b/src/backend/utils/cache/relcache.c index 8d2ad01..3787441 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: @@ -1053,6 +1055,12 @@ 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_REL) + RelationBuildPartitionKey(relation); + else + relation->rd_partkey = NULL; + /* * if it's an index, initialize index-related information */ @@ -2050,6 +2058,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); @@ -2991,7 +3000,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; @@ -5045,6 +5056,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/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..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..acff099 --- /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); +extern void FreePartitionKey(PartitionKey key); + +/* 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); + +#endif /* PARTITION_H */ diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index e57b81c..57c7ca4 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_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_partitioned_fn.h b/src/include/catalog/pg_partitioned_fn.h new file mode 100644 index 0000000..3e97740 --- /dev/null +++ b/src/include/catalog/pg_partitioned_fn.h @@ -0,0 +1,29 @@ +/*------------------------------------------------------------------------- + * + * pg_partitioned_fn.h + * prototypes for functions in catalog/pg_partitioned.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_fn.h + * + *------------------------------------------------------------------------- + */ +#ifndef PG_PARTITIONED_FN_H +#define PG_PARTITIONED_FN_H + +#include "utils/relcache.h" + +/* pg_partitioned catalog functions */ +extern void StorePartitionKey(Relation rel, + char strategy, + int16 partnatts, + AttrNumber *partattrs, + List *partexprs, + Oid *partopclass); +extern void RemovePartitionKeyByRelId(Oid relid); +extern bool relid_is_partitioned(Oid relid); + +#endif /* PG_PARTITIONED_FN_H */ diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index b064eb4..9fe31ce 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(CreateFunctionStmt *stmt, const char *queryString); diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 6b850e4..c316af6 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -452,6 +452,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 1481fff..2b77579 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -697,6 +697,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 @@ -1751,6 +1785,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..e05d122 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -94,6 +94,8 @@ typedef struct RelationData List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */ bool rd_fkeyvalid; /* true if list has been computed */ + 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 +534,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..cb105af 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 &&); + ^ +DROP TABLE partitioned; +-- 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 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 +DROP TABLE no_drop_or_alter_partcol, no_drop_or_alter_partexpr; +-- 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 +DROP TABLE no_inh_child, inh_parent, partitioned; +ERROR: table "partitioned" does not exist diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out index 41ceb87..fb7a1b3 100644 --- a/src/test/regress/expected/create_table.out +++ b/src/test/regress/expected/create_table.out @@ -253,3 +253,163 @@ 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: partitioned 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 partitioned table "no_inh_parted" +DROP TABLE no_inh_parted; +-- NO INHERIT ignored on constraints +CREATE TABLE no_inh_con_parted ( + a int, + CONSTRAINT check_a CHECK (a > 0) NO INHERIT +) PARTITION BY RANGE (a); +SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass; + connoinherit +-------------- + f +(1 row) + +DROP 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..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/alter_table.sql b/src/test/regress/sql/alter_table.sql index 72e65d4..f11f856 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -1842,3 +1842,35 @@ 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 &&); +DROP TABLE partitioned; + +-- 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); +DROP TABLE no_drop_or_alter_partcol, no_drop_or_alter_partexpr; + +-- 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; +DROP TABLE no_inh_child, inh_parent, partitioned; diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql index 78bdc8b..cb17517 100644 --- a/src/test/regress/sql/create_table.sql +++ b/src/test/regress/sql/create_table.sql @@ -269,3 +269,138 @@ 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; + +-- NO INHERIT ignored on constraints +CREATE TABLE no_inh_con_parted ( + a int, + CONSTRAINT check_a CHECK (a > 0) NO INHERIT +) PARTITION BY RANGE (a); +SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass; +DROP TABLE no_inh_con_parted; -- 1.7.1
0002-psql-and-pg_dump-support-for-partitioned-tables.patch (text/x-diff, 22.8K) download From 65965857175167028ed90262ce6deb090c93e57f Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Tue, 12 Jul 2016 17:20:23 +0900 Subject: [PATCH 2/8] psql and pg_dump support for partitioned tables. Takes care of both the partition key deparse stuff and the new relkind. --- src/backend/utils/adt/ruleutils.c | 147 ++++++++++++++++++++++++++++ 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/pg_proc.h | 2 + src/include/utils/builtins.h | 1 + src/test/regress/expected/create_table.out | 26 +++++ src/test/regress/sql/create_table.sql | 13 +++ 9 files changed, 267 insertions(+), 26 deletions(-) diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index ec966c7..4691b5b 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, bool missing_ok); +static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags); static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand, int prettyFlags, bool missing_ok); static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname, @@ -1389,6 +1391,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 [ 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 4ee10fc..a127672 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -1253,9 +1253,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)"); @@ -2117,6 +2118,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 && @@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables) int i_reloftype; int i_relpages; int i_changed_acl; + int i_partkeydef; /* Make sure we are in proper schema */ selectSourceSchema(fout, "pg_catalog"); @@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables) "OR %s IS NOT NULL " "OR %s IS NOT NULL" "))" - "AS changed_acl " + "AS changed_acl, " + "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 " @@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables) "(c.oid = pip.objoid " "AND pip.classoid = 'pg_class'::regclass " "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, @@ -5306,7 +5312,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); @@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables) i_toastreloptions = PQfnumber(res, "toast_reloptions"); i_reloftype = PQfnumber(res, "reloftype"); i_changed_acl = PQfnumber(res, "changed_acl"); + i_partkeydef = PQfnumber(res, "partkeydef"); if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300) { @@ -5938,6 +5946,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 */ @@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables) * We only need to lock the table for certain components; see * pg_dump.h */ - 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) && (tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK)) { resetPQExpBuffer(query); @@ -6083,7 +6094,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); @@ -15436,6 +15450,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)); } @@ -15496,6 +15513,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++) @@ -15514,7 +15532,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 @@ -15731,6 +15750,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 2bfa2d9..0292859 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -310,6 +310,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 27be102..3ba8a90 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -856,6 +856,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"), @@ -865,6 +866,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"); @@ -911,7 +913,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 @@ -1555,8 +1557,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)"); } @@ -1621,6 +1623,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\"", @@ -1634,8 +1644,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"); @@ -1655,12 +1665,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"); } @@ -1760,7 +1770,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); @@ -1768,14 +1778,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 */ @@ -1914,7 +1943,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; @@ -2473,7 +2502,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; @@ -2684,7 +2713,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 @@ -3018,6 +3047,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"), @@ -3029,6 +3059,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")); @@ -3067,7 +3098,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 8469d9f..cd4eb7c 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/pg_proc.h b/src/include/catalog/pg_proc.h index 270dd21..5a444d9 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/utils/builtins.h b/src/include/utils/builtins.h index 8cebc86..351bfd3 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -722,6 +722,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out index fb7a1b3..4fc8c06 100644 --- a/src/test/regress/expected/create_table.out +++ b/src/test/regress/expected/create_table.out @@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg (1 row) DROP TABLE no_inh_con_parted; +-- Partition key in describe output +CREATE TABLE describe_range_key ( + a int, + b int +) PARTITION BY RANGE ((a+b)); +\d describe_range_key +Partitioned table "public.describe_range_key" + Column | Type | Modifiers +--------+---------+----------- + a | integer | + b | integer | +Partition Key: PARTITION BY RANGE (((a + b))) + +CREATE TABLE describe_list_key ( + a int, + b int +) PARTITION BY LIST (a); +\d describe_list_key +Partitioned table "public.describe_list_key" + Column | Type | Modifiers +--------+---------+----------- + a | integer | + b | integer | +Partition Key: PARTITION BY LIST (a) + +DROP TABLE describe_range_key, describe_list_key; diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql index cb17517..7c7a18a 100644 --- a/src/test/regress/sql/create_table.sql +++ b/src/test/regress/sql/create_table.sql @@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted ( ) PARTITION BY RANGE (a); SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass; DROP TABLE no_inh_con_parted; + +-- Partition key in describe output +CREATE TABLE describe_range_key ( + a int, + b int +) PARTITION BY RANGE ((a+b)); +\d describe_range_key +CREATE TABLE describe_list_key ( + a int, + b int +) PARTITION BY LIST (a); +\d describe_list_key +DROP TABLE describe_range_key, describe_list_key; -- 1.7.1
0003-Catalog-and-DDL-for-partitions.patch (text/x-diff, 196.3K) download From 867b570df34a0e8f9a3d9aef5954b9111c88dd48 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Thu, 14 Jul 2016 14:38:08 +0900 Subject: [PATCH 3/8] Catalog and DDL for partitions. Parent-child relationships of a partitioned table and its partitions are managed with regular inheritance (pg_inherits catalog) so not much here about that. DDL includes both a way to create new partition and "attach" an existing table as a partition. Attempt to drop a partition using DROP TABLE causes an error. Instead a partition needs first to be "detached". On the other hand, dropping the parent drops all the partitions if CASCADE specified. Add a field to relcache for storing a "partition descriptor" of a partitioned table which has hopefully all the information about a table's partitions that someone might want to do something with. --- doc/src/sgml/catalogs.sgml | 56 ++ doc/src/sgml/ref/alter_table.sgml | 122 +++- doc/src/sgml/ref/create_foreign_table.sgml | 28 + doc/src/sgml/ref/create_table.sgml | 110 ++- src/backend/catalog/Makefile | 4 +- src/backend/catalog/heap.c | 13 +- src/backend/catalog/partition.c | 1484 ++++++++++++++++++++++++++++ src/backend/catalog/pg_partition.c | 106 ++ src/backend/commands/foreigncmds.c | 17 + src/backend/commands/lockcmds.c | 4 +- src/backend/commands/sequence.c | 1 + src/backend/commands/tablecmds.c | 703 +++++++++++--- src/backend/nodes/copyfuncs.c | 48 + src/backend/nodes/equalfuncs.c | 42 + src/backend/nodes/outfuncs.c | 27 + src/backend/nodes/readfuncs.c | 33 + src/backend/parser/gram.y | 209 ++++- src/backend/parser/parse_agg.c | 6 + src/backend/parser/parse_expr.c | 23 + src/backend/parser/parse_utilcmd.c | 334 +++++++- src/backend/utils/cache/relcache.c | 73 ++- src/backend/utils/cache/syscache.c | 12 + src/include/catalog/indexing.h | 3 + src/include/catalog/partition.h | 24 + src/include/catalog/pg_partition.h | 54 + src/include/catalog/pg_partition_fn.h | 22 + src/include/nodes/nodes.h | 3 + src/include/nodes/parsenodes.h | 43 +- src/include/parser/kwlist.h | 3 + src/include/parser/parse_node.h | 3 +- src/include/utils/rel.h | 9 + src/include/utils/syscache.h | 1 + src/test/regress/expected/alter_table.out | 185 ++++- src/test/regress/expected/create_table.out | 199 ++++ src/test/regress/expected/sanity_check.out | 1 + src/test/regress/sql/alter_table.sql | 153 +++- src/test/regress/sql/create_table.sql | 135 +++ 37 files changed, 4140 insertions(+), 153 deletions(-) create mode 100644 src/backend/catalog/pg_partition.c create mode 100644 src/include/catalog/pg_partition.h create mode 100644 src/include/catalog/pg_partition_fn.h diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index b51323a..d5b3b5d 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -226,6 +226,11 @@ </row> <row> + <entry><link linkend="catalog-pg-partition"><structname>pg_partition</structname></link></entry> + <entry>information about partitions</entry> + </row> + + <row> <entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry> <entry>information about partitioned tables, including the partition key</entry> </row> @@ -4673,6 +4678,57 @@ </sect1> + <sect1 id="catalog-pg-partition"> + <title><structname>pg_partition</structname></title> + + <indexterm zone="catalog-pg-partition"> + <primary>pg_partition</primary> + </indexterm> + + <para> + The catalog <structname>pg_partition</structname> stores partition bounds + of tables that are partitions. + </para> + + <table> + <title><structname>pg_partition</> Columns</title> + + <tgroup cols="4"> + <thead> + <row> + <entry>Name</entry> + <entry>Type</entry> + <entry>References</entry> + <entry>Description</entry> + </row> + </thead> + + <tbody> + + <row> + <entry><structfield>partrelid</structfield></entry> + <entry><type>oid</type></entry> + <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry> + <entry>The OID of the <structname>pg_class</> entry for this partition</entry> + </row> + + <row> + <entry><structfield>partbound</structfield></entry> + <entry><type>pg_node_tree</type></entry> + <entry></entry> + <entry> + Expression tree (in <function>nodeToString()</function> representation) + for partition boundary value. For list partitions, it is simply a list + of values. For range partitions, it consists of lower and upper bound + values along with their inclusivity. + </entry> + </row> + + </tbody> + </tgroup> + </table> + </sect1> + <sect1 id="catalog-pg-partitioned"> <title><structname>pg_partitioned</structname></title> diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 5ca211e..54ef48b 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -77,12 +77,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> NOT OF OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER } REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING } + ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ] + DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase> [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ] { UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable> [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] + </synopsis> </refsynopsisdiv> @@ -166,6 +169,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> values or to reject null values. You can only use <literal>SET NOT NULL</> when the column contains no null values. </para> + + <para> + If this table is a partition, one cannot perform <literal>DROP NOT NULL</> + on a column if it is marked not null in the parent table. + not null. + </para> </listitem> </varlistentry> @@ -704,6 +713,57 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><literal>ATTACH PARTITION</literal></term> + <listitem> + <para> + This form attaches an existing non-partitioned table as partition of the + target table. Partition bound specification must correspond with the + partition method and the key of the target table. The table being + attached must have all the columns of the target table and no more. + Also, it must have all the matching constraints as the target table, + that is, with a matching name and a matching expression. That inclues + <literal>NOT NULL</> and <literal>CHECK</> constraints. + <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and + <literal>FOREIGN KEY</literal> constraints are not considered, but that + might change in the future. + </para> + + <para> + If <literal>NO VALIDATE</literal> clause is specified, full scan to + check existing rows in the table being attached to see if they fall + within the specified bounds is skipped. This allows to quickly load a + large amount of data into the partitioned table typically called roll-in. + The default behavior is to scan the table to perform the check, as if + the <literal> VALIDATE</literal> clause were specified. + </para> + + <para> + Note that once the table becomes a partition, it will be dropped or + truncated when the parent table is dropped or truncated. Dropping + it directly using <literal>DROP TABLE</literal> will fail; it must + first be detached from the parent. However, truncating a partition + directly works. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>DETACH PARTITION</literal></term> + <listitem> + <para> + This form detaches specified partition of the target table. The + detached partition continues to exist as a standalone table with no ties + remaining with the partitioned table. + </para> + <para> + Note that unlike the <literal>ATTACH PARTITION</> command, a partition + being detached can be itself partitioned. In that case, it continues + to exist as such. + </para> + </listitem> + </varlistentry> + </variablelist> </para> @@ -722,7 +782,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> To change the schema or tablespace of a table, you must also have <literal>CREATE</literal> privilege on the new schema or tablespace. To add the table as a new child of a parent table, you must own the - parent table as well. + parent table as well. That applies to both adding as a inheritance child + and attaching a new partition to a partitioned table. To alter the owner, you must also be a direct or indirect member of the new owning role, and that role must have <literal>CREATE</literal> privilege on the table's schema. (These restrictions enforce that altering the owner @@ -758,7 +819,10 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> that table is altered. If <literal>ONLY</> is not specified, the table and all its descendant tables (if any) are altered. Optionally, <literal>*</> can be specified after the table name to explicitly - indicate that descendant tables are included. + indicate that descendant tables are included. Note that whether + <literal>ONLY</> or <literal>*</> is specified has no effect in case + of a partitioned table; descendant tables (in this case, partitions) + are always included. </para> </listitem> </varlistentry> @@ -936,6 +1000,24 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><replaceable class="PARAMETER">partition_name</replaceable></term> + <listitem> + <para> + The name of the table to attach as a new partition to or detach from this table. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="PARAMETER">partition_bound_spec</replaceable></term> + <listitem> + <para> + The partition bound specification for a new partition. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> @@ -976,6 +1058,12 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> </para> <para> + Similarly, when attaching a new partition the source table is scanned to + verify that existing rows fall within the specified bounds, unless + <literal>NO VALIDATE</> option is spcified. + </para> + + <para> The main reason for providing the option to specify multiple changes in a single <command>ALTER TABLE</> is that multiple table scans or rewrites can thereby be combined into a single pass over the table. @@ -1044,11 +1132,18 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable> </para> <para> + With partitioned tables, the <literal>ONLY</> specification is simply + ignored and changes such as adding, dropping, changing the type of, or + renaming a column or a constraint are always applied to the partitions. + </para> + + <para> The <literal>TRIGGER</>, <literal>CLUSTER</>, <literal>OWNER</>, and <literal>TABLESPACE</> actions never recurse to descendant tables; that is, they always act as though <literal>ONLY</> were specified. Adding a constraint recurses only for <literal>CHECK</> constraints - that are not marked <literal>NO INHERIT</>. + that are not marked <literal>NO INHERIT</>. If the table is partitioned, + the <literal>NO INHERIT</> specification is ignored and recursion occurs. </para> <para> @@ -1227,6 +1322,27 @@ ALTER TABLE distributors DROP CONSTRAINT distributors_pkey, ADD CONSTRAINT distributors_pkey PRIMARY KEY USING INDEX dist_id_temp_idx; </programlisting></para> + <para> + Attach a partition to range partitioned table: +<programlisting> +ALTER TABLE measurement + ATTACH PARTITION measurement_y2016m07 FOR VALUES START ('2016-07-01') END ('2016-08-01'); +</programlisting></para> + + <para> + Attach a partition to list partitioned table and ask to skip checking existing rows: +<programlisting> +ALTER TABLE cities + ATTACH PARTITION cities_west FOR VALUES IN ('los angeles', 'san fransisco') NO VALIDATE; +</programlisting></para> + + <para> + Detach a partition from partitioned table: +<programlisting> +ALTER TABLE cities + DETACH PARTITION measurement_y2015m12; +</programlisting></para> + </refsect1> <refsect1> diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml index 413b033..efd0c0d 100644 --- a/doc/src/sgml/ref/create_foreign_table.sgml +++ b/doc/src/sgml/ref/create_foreign_table.sgml @@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name SERVER <replaceable class="parameter">server_name</replaceable> [ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] +CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> + PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ ( + { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ] + | <replaceable>table_constraint</replaceable> } + [, ... ] +) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable> + SERVER <replaceable class="parameter">server_name</replaceable> +[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ] + <phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase> [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ] @@ -68,6 +77,14 @@ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ] </para> <para> + If <literal>PARTITION OF</literal> clause is specified then the table is + created as a partition of <literal>parent_table</literal> with specified + bounds. However, unlike regular tables, one cannot specify + <literal>PARTITION BY</literal> clause which means foreign tables can + only be created as leaf partitions. + </para> + + <para> To be able to create a foreign table, you must have <literal>USAGE</literal> privilege on the foreign server, as well as <literal>USAGE</literal> privilege on all column types used in the table. @@ -314,6 +331,17 @@ CREATE FOREIGN TABLE films ( SERVER film_server; </programlisting></para> + <para> + Create foreign table <structname>measurement_y2016m07</>, which will be + accessed through the server <structname>server_07</>, that is partition + of the range partitioned table <structname>measurement</>: + +<programlisting> +CREATE FOREIGN TABLE measurement_y2016m07 + PARTITION OF measurement FOR VALUES START ('2016-07-01') END ('2016-08-01'); + SERVER server_07; +</programlisting></para> + </refsect1> <refsect1 id="SQL-CREATEFOREIGNTABLE-compatibility"> diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 51e6936..c960705 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -44,6 +44,17 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ] [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ] +CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable> + PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ ( + { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ] + | <replaceable>table_constraint</replaceable> } + [, ... ] +) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable> +[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) +[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ] +[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ] +[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ] + <phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase> [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ] @@ -72,6 +83,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL } +<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase> + +FOR VALUES { <replaceable class="PARAMETER">list_spec</replaceable> | <replaceable class="PARAMETER">range_spec</replaceable> } + <phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase> [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ] @@ -80,8 +95,20 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI <phrase><replaceable class="PARAMETER">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase> { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] -</synopsis> +<phrase><replaceable class="PARAMETER">list_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase> + +IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) + +<phrase><replaceable class="PARAMETER">range_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase> + +START <replaceable class="PARAMETER">lower-bound</replaceable> [ INCLUSIVE | EXCLUSIVE ] END <replaceable class="PARAMETER">upper-bound</replaceable> [ INCLUSIVE | EXCLUSIVE ] + +<phrase>where <replaceable class="PARAMETER">lower-bound</replaceable> and <replaceable class="PARAMETER">upper-bound</replaceable> are:</phrase> + +{ ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | UNBOUNDED } + +</synopsis> </refsynopsisdiv> <refsect1 id="SQL-CREATETABLE-description"> @@ -232,6 +259,49 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI </varlistentry> <varlistentry> + <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term> + <listitem> + <para> + Creates the table as <firstterm>partition</firstterm> of the specified + parent table (name optionally schema-qualified). + </para> + + <para> + A partition bound specification must be present and must correspond with + partition method and key of the parent table. It is checked using the + specification that the new partition does not overlap with any existing + partitions of the parent. + </para> + + <para> + A partition cannot have columns other than those inherited from the + parent. That includes the <structfield>oid</> column, which can be + specified using the <literal>WITH (OIDS)</literal> clause. On the other + hand, if parent has the <structfield>oid</> column, the partition + inherits the same, overriding the <literal>WITH (OIDS=FALSE)</literal> + clause, if any. Defaults and constraints can optionally be specified + for each of the inherited columns, which override those in the parent. + One can also specify table constraints, in addition to those inherited + from the parent. Note that all subsequent schema modifications to the + parent propagate to partition. + </para> + + <para> + Any data row subsequently inserted into the parent table is mapped to + and stored in the partition, provided partition key of the row falls + within the partition bounds. + </para> + + <para> + A partition is dropped or truncated when the parent table is dropped or + truncated. Dropping it directly using <literal>DROP TABLE</literal> + will fail; it must first be <firstterm>detached</> from the parent. + However, truncating a partition directly works. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><replaceable class="PARAMETER">column_name</replaceable></term> <listitem> <para> @@ -340,6 +410,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI </para> <para> + When specifying for a table being created as partition, one needs to + use column names from the parent table as part of the key. + </para> + + <para> Currently, there are following limitations on definition of partitioned tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or FOREIGN KEY constraints. @@ -1422,7 +1497,38 @@ CREATE TABLE measurement ( CREATE TABLE cities ( name text not null, population int, -) PARTITION BY LIST (name); +) PARTITION BY LIST (lower(name)); +</programlisting></para> + + <para> + Create partition of a range partitioned table: +<programlisting> +CREATE TABLE measurement_y2016m07 + PARTITION OF measurement ( + unitsales WITH OPTIONS DEFAULT 0 +) FOR VALUES START ('2016-07-01') END ('2016-08-01'); +</programlisting></para> + + <para> + Create partition of a list partitioned table: +<programlisting> +CREATE TABLE cities_west + PARTITION OF cities ( + CONSTRAINT city_id_nonzero CHECK (city_id != 0) +) FOR VALUES IN ('los angeles', 'san fransisco'); +</programlisting></para> + + <para> + Create partition of a list partitioned table that itself is further + partitioned and then create its partition: +<programlisting> +CREATE TABLE cities_west + PARTITION OF cities ( + CONSTRAINT city_id_nonzero CHECK (city_id != 0) +) FOR VALUES IN ('los angeles', 'san fransisco') PARTITION BY RANGE (population); + +CREATE TABLE cities_west_10000_to_100000 + PARTITION OF cities_west FOR VALUES START (10000) END (100000); </programlisting></para> </refsect1> diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index 777459d..2e6f754 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -15,7 +15,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.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_partitioned.o + pg_type.o storage.o toasting.o pg_partitioned.o pg_partition.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_partitioned.h\ + pg_collation.h pg_range.h pg_transform.h pg_partitioned.h pg_partition.h\ toasting.h indexing.h \ ) diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index f5dcc56..566d633 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" @@ -48,6 +49,7 @@ #include "catalog/pg_foreign_table.h" #include "catalog/pg_inherits.h" #include "catalog/pg_namespace.h" +#include "catalog/pg_partition_fn.h" #include "catalog/pg_partitioned_fn.h" #include "catalog/pg_statistic.h" #include "catalog/pg_tablespace.h" @@ -1810,6 +1812,9 @@ heap_drop_with_catalog(Oid relid) if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL) RemovePartitionKeyByRelId(relid); + if (relid_is_partition(relid)) + RemovePartitionEntryByRelId(relid); + /* * Schedule unlinking of the relation's physical files at commit. */ @@ -2043,8 +2048,8 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr, else attNos = NULL; - /* Remove NO INHERIT flag if rel is a partitioned table */ - if (relid_is_partitioned(relid)) + /* Discard NO INHERIT, if relation is a partitioned table or a partition */ + if (relid_is_partitioned(relid) || relid_is_partition(relid)) is_no_inherit = false; /* @@ -2472,7 +2477,9 @@ MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr, con->conislocal = true; else con->coninhcount++; - if (is_no_inherit) + + /* Discard the NO INHERIT flag if the relation is a partition */ + if (is_no_inherit && !relid_is_partition(RelationGetRelid(rel))) { Assert(is_local); con->connoinherit = true; diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index f00caf2..109d391 100644 --- a/src/backend/catalog/partition.c +++ b/src/backend/catalog/partition.c @@ -18,19 +18,28 @@ #include "access/heapam.h" #include "access/htup_details.h" #include "access/nbtree.h" +#include "access/sysattr.h" #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/objectaddress.h" #include "catalog/partition.h" #include "catalog/pg_collation.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_inherits_fn.h" #include "catalog/pg_opclass.h" +#include "catalog/pg_partition.h" +#include "catalog/pg_partition_fn.h" #include "catalog/pg_partitioned.h" +#include "catalog/pg_partitioned_fn.h" #include "catalog/pg_type.h" #include "executor/executor.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "nodes/parsenodes.h" #include "optimizer/clauses.h" #include "optimizer/planmain.h" +#include "optimizer/var.h" #include "storage/lmgr.h" #include "utils/array.h" #include "utils/builtins.h" @@ -39,6 +48,7 @@ #include "utils/fmgroids.h" #include "utils/inval.h" #include "utils/lsyscache.h" +#include "utils/rel.h" #include "utils/syscache.h" /* Type and collation information for partition key columns */ @@ -67,12 +77,111 @@ typedef struct PartitionKeyData KeyTypeCollInfo *tcinfo; /* type and collation info (all columns) */ } PartitionKeyData; +/* Internal representation of a list partition bound */ +typedef struct PartitionListInfo +{ + int nvalues; /* number of values in the following array */ + Datum *values; /* values contained in the list */ + bool *nulls; +} PartitionListInfo; + +/* Internal representation of a range partition bound */ +typedef struct RangeBound +{ + Datum *val; /* composite bound value, if any */ + bool infinite; /* bound is +/- infinity */ + bool inclusive; /* bound is inclusive (vs exclusive) */ + bool lower; /* this is the lower (vs upper) bound */ +} RangeBound; + +typedef struct PartitionRangeInfo +{ + RangeBound *lower; + RangeBound *upper; +} PartitionRangeInfo; + +/* + * Information about a single partition + */ +typedef struct PartitionInfoData +{ + Oid oid; /* partition OID */ + PartitionListInfo *list; /* list partition info */ + PartitionRangeInfo *range; /* range partition info */ +} PartitionInfoData; + /* Support RelationBuildPartitionKey() */ static PartitionKey copy_partition_key(PartitionKey fromkey); static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo); static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo); +/* Support RelationBuildPartitionDesc() */ +static List *get_partitions(Oid relid, int lockmode); +static PartitionInfo *get_partition_info(Relation rel, int *nparts); +static void free_partition_info(PartitionInfo *p, int num); +static int32 partition_cmp(const void *a, const void *b, void *arg); + +/* Support check_new_partition_bound() */ +static bool list_overlaps_existing_partition(PartitionKey key, + PartitionListSpec *list_spec, + PartitionDesc pdesc, + Oid *with); +static bool partition_range_empty(PartitionKey key, + PartitionRangeSpec *range_spec); +static bool range_overlaps_existing_partition(PartitionKey key, + PartitionRangeSpec *range_spec, + PartitionDesc pdesc, + Oid *with); + +/* Support get_check_qual_from_partbound */ +typedef struct translate_var_attno_mutator_context +{ + AttrNumber old_attno; + AttrNumber new_attno; +} translate_var_attno_mutator_context; + +static Node *translate_var_attno(Node *expr, AttrNumber attno, + AttrNumber new_attno); +static Node *translate_var_attno_mutator(Node *node, + translate_var_attno_mutator_context *cxt); +static List *get_check_qual_for_list(PartitionKey key, PartitionListSpec *list); +static List *get_check_qual_for_range(PartitionKey key, PartitionRangeSpec *range); +static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber strategy, + bool *need_relabel); + +/* Support RelationGetPartitionCheckQual() */ +static List *generate_partition_check_qual(Relation rel); + +/* List partition related support functions */ +static PartitionListInfo *make_list_from_spec(PartitionKey key, + PartitionListSpec *list_spec); +static PartitionListInfo *copy_list_info(PartitionListInfo *src, + PartitionKey key); +static bool equal_list_info(PartitionKey key, PartitionListInfo *l1, + PartitionListInfo *l2); +static bool partition_list_values_equal(PartitionKey key, + Datum val1, Datum val2); + +/* Range partition related support functions */ +static PartitionRangeInfo *make_range_from_spec(PartitionKey key, + PartitionRangeSpec *range_spec); +static RangeBound *make_range_bound(PartitionKey key, List *val, bool inclusive, + bool lower); +static PartitionRangeInfo *copy_range_info(PartitionRangeInfo *src, + PartitionKey key); +static RangeBound *copy_range_bound(RangeBound *src, PartitionKey key); +static bool equal_range_info(PartitionKey key, PartitionRangeInfo *r1, + PartitionRangeInfo *r2); +static int32 partition_range_cmp(PartitionKey key, PartitionRangeInfo *r1, + PartitionRangeInfo *r2); +static int32 partition_range_bound_cmp(PartitionKey key, RangeBound *b1, + RangeBound *b2); +static int32 partition_range_tuple_cmp(PartitionKey key, + Datum *val1, Datum *val2); +static bool partition_range_overlaps(PartitionKey key, + PartitionRangeInfo *r1, PartitionRangeInfo *r2); + /* * Partition key related functions */ @@ -390,3 +499,1378 @@ free_key_type_coll_info(KeyTypeCollInfo *tcinfo) pfree(tcinfo->typcoll); pfree(tcinfo); } + +/* + * Partition bound and partition descriptor related functions + */ + +/* + * RelationBuildPartitionDesc + * Form relation's partition descriptor from information in pg_partition + * and store it in the relcache + * + * Not flushed from the cache by RelationClearRelation() unless changed because + * of addition or removal of partitions. + */ +void +RelationBuildPartitionDesc(Relation relation) +{ + int i, + nparts; + PartitionKey key = RelationGetPartitionKey(relation); + PartitionDesc result; + PartitionInfo *parts; + MemoryContext oldcxt; + + /* + * The following happens when we have created our pg_class entry but not + * the pg_partitioned entry yet. + */ + if (key == NULL) + return; + + /* Read partition bound information from the catalog */ + parts = get_partition_info(relation, &nparts); + + relation->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext, + RelationGetRelationName(relation), + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + oldcxt = MemoryContextSwitchTo(relation->rd_pdcxt); + result = (PartitionDescData *) palloc0(sizeof(PartitionDescData)); + + result->nparts = nparts; + if (parts) + { + result->parts = (PartitionInfoData **) + palloc0(nparts * sizeof(PartitionInfoData *)); + /* + * Sort range partitions in ascending order of their ranges before + * continuing. + */ + qsort_arg(parts, nparts, sizeof(PartitionInfo), partition_cmp, key); + + for (i = 0; i < nparts; i++) + { + PartitionInfoData *part; + part = (PartitionInfoData *) palloc0(sizeof(PartitionInfoData)); + part->oid = parts[i]->oid; + switch (key->strategy) + { + case PARTITION_STRAT_LIST: + part->list = copy_list_info(parts[i]->list, key); + break; + case PARTITION_STRAT_RANGE: + part->range = copy_range_info(parts[i]->range, key); + break; + } + + result->parts[i] = part; + } + + /* Clean up */ + free_partition_info(parts, nparts); + } + + MemoryContextSwitchTo(oldcxt); + relation->rd_partdesc = result; +} + +/* + * Are two partitions p1 and p2 equal? + */ +bool +partition_equal(PartitionKey key, PartitionInfo p1, PartitionInfo p2) +{ + + if (p1->oid != p2->oid) + return false; + + switch (key->strategy) + { + case PARTITION_STRAT_LIST: + if (!equal_list_info(key, p1->list, p2->list)) + return false; + break; + case PARTITION_STRAT_RANGE: + if (!equal_range_info(key, p1->range, p2->range)) + return false; + break; + } + + return true; +} + +/* + * check_new_partition_bound + * + * Call partition method specific routines to check if the new partition's + * bound overlaps with any of partitions of parent. Some partition types may + * have still other validations to perform, for example, a range partition + * partition with an empty range is not allowed. + */ +void +check_new_partition_bound(Oid parentId, Node *bound) +{ + Relation parent = heap_open(parentId, AccessShareLock); + PartitionKey key = RelationGetPartitionKey(parent); + PartitionDesc pdesc = RelationGetPartitionDesc(parent); + ParseState *pstate = make_parsestate(NULL); + Oid with; + + switch (key->strategy) + { + case PARTITION_STRAT_LIST: + { + PartitionListSpec *list; + + Assert(IsA(bound, PartitionListSpec)); + list = (PartitionListSpec *) bound; + if (list_overlaps_existing_partition(key, list, pdesc, &with)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("new partition's list of values overlaps with" + " partition \"%s\" of \"%s\"", + get_rel_name(with), get_rel_name(parentId)), + errhint("Please specify a list of values that does not" + " overlap with any existing partition's list."), + parser_errposition(pstate, list->location))); + break; + } + + case PARTITION_STRAT_RANGE: + { + PartitionRangeSpec *range; + + Assert(IsA(bound, PartitionRangeSpec)); + range = (PartitionRangeSpec *) bound; + if (partition_range_empty(key, range)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("new partition's range contains no element"), + errhint("Please specify a non-empty range."), + parser_errposition(pstate, range->location))); + + if (range_overlaps_existing_partition(key, range, pdesc, &with)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("new partition's range overlaps with partition" + " \"%s\" of \"%s\"", + get_rel_name(with), get_rel_name(parentId)), + errhint("Please specify a range that does not overlap" + " with any existing partition's range."), + parser_errposition(pstate, range->location))); + break; + } + } + + heap_close(parent, AccessShareLock); +} + +/* + * get_partition_parent + * + * Returns inheritance parent of relid by scanning pg_inherits + */ +Oid +get_partition_parent(Oid relid) +{ + Form_pg_inherits form; + Relation catalogRelation; + SysScanDesc scan; + ScanKeyData key[2]; + HeapTuple tuple; + + catalogRelation = heap_open(InheritsRelationId, AccessShareLock); + + ScanKeyInit(&key[0], + Anum_pg_inherits_inhrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + ScanKeyInit(&key[1], + Anum_pg_inherits_inhseqno, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(1)); + + scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true, + NULL, 2, key); + + tuple = systable_getnext(scan); + Assert(HeapTupleIsValid(tuple)); + + form = (Form_pg_inherits) GETSTRUCT(tuple); + + systable_endscan(scan); + heap_close(catalogRelation, AccessShareLock); + + return form->inhparent; +} + +/* + * get_check_qual_from_partbound + * Given a parser node for partition bound, return the list of executable + * expressions as partition predicate + */ +List * +get_check_qual_from_partbound(Relation rel, Relation parent, Node *bound) +{ + PartitionKey key = RelationGetPartitionKey(parent); + List *my_check; + int i; + ListCell *partexprs_item; + + /* + * First convert from transformed parser node representation to the + * internal representation + */ + if (IsA(bound, PartitionListSpec)) + { + PartitionListSpec *list_spec = (PartitionListSpec *) bound; + + Assert(key->strategy == PARTITION_STRAT_LIST); + my_check = get_check_qual_for_list(key, list_spec); + } + else if (IsA(bound, PartitionRangeSpec)) + { + PartitionRangeSpec *range_spec = (PartitionRangeSpec *) bound; + + Assert(key->strategy == PARTITION_STRAT_RANGE); + my_check = get_check_qual_for_range(key, range_spec); + } + + /* + * Translate vars in the generated expression with the correct attnos. + * Note that the vars in my_expr bear attnos dictated by key which carries + * physical attnos of the parent. We must allow for a case where physical + * attnos of a partition can be different from the parent for partitions + * that are "attached". + */ + partexprs_item = list_head(key->partexprs); + for (i = 0; i < key->partnatts; i++) + { + AttrNumber attno = key->partattrs[i], + new_attno; + char *attname; + + if (attno != 0) + { + /* Simple column reference */ + attname = get_attname(RelationGetRelid(parent), attno); + new_attno = get_attnum(RelationGetRelid(rel), attname); + + if (new_attno != attno) + my_check = (List *) translate_var_attno((Node *) my_check, + attno, + new_attno); + } + else + { + /* Arbitrary expression */ + Node *expr = (Node *) lfirst(partexprs_item); + Bitmapset *expr_attrs = NULL; + int index; + + /* Find all attributes referenced and translate each reference */ + pull_varattnos(expr, 1, &expr_attrs); + partexprs_item = lnext(partexprs_item); + + index = -1; + while ((index = bms_next_member(expr_attrs, index)) > 0) + { + AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber; + + attname = get_attname(RelationGetRelid(parent), attno); + new_attno = get_attnum(RelationGetRelid(rel), attname); + + if (new_attno != attno) + my_check = (List *) translate_var_attno((Node *) my_check, + attno, + new_attno); + } + } + } + + return my_check; +} + +/* + * RelationGetPartitionCheckQual + * Returns a list of OpExpr's (or a ScalarArrayOpExpr's) as partition + * predicate + */ +List * +RelationGetPartitionCheckQual(Relation rel) +{ + /* Quick exit */ + if (!relid_is_partition(RelationGetRelid(rel))) + return NULL; + + /* Quick copy */ + if (rel->rd_partcheck) + return copyObject(rel->rd_partcheck); + + /* Nope, so generate. */ + return generate_partition_check_qual(rel); +} + +/* Module-local functions */ + +/* + * Wrapper around find_inheritance_children for use by partitioning + * related code + */ +static List * +get_partitions(Oid relid, int lockmode) +{ + return find_inheritance_children(relid, lockmode); +} + +/* + * get_partition_info + * Return information of partitions of rel + * + * Returns number of partitions in *nparts. + */ +static PartitionInfo * +get_partition_info(Relation rel, int *nparts) +{ + int i, + n; + Relation partrel; + List *partoids; + ListCell *cell; + PartitionInfo *parts; + PartitionKey key = RelationGetPartitionKey(rel); + + /* Read info from pg_inherits */ + partoids = get_partitions(RelationGetRelid(rel), NoLock); + + partrel = heap_open(PartitionRelationId, AccessShareLock); + + n = list_length(partoids); + parts = (PartitionInfoData **) palloc0(n * sizeof(PartitionInfoData *)); + + i = 0; + foreach(cell, partoids) + { + Oid partrelid = lfirst_oid(cell); + HeapTuple tuple; + Datum datum; + bool isnull; + Node *bound; + + tuple = SearchSysCache1(PARTRELID, partrelid); + + /* + * Happens when we have created the pg_inherits entry but not the + * pg_partition entry yet. + */ + if (!HeapTupleIsValid(tuple)) + continue; + + parts[i] = (PartitionInfoData *) palloc0(sizeof(PartitionInfoData)); + parts[i]->oid = partrelid; + + datum = fastgetattr(tuple, + Anum_pg_partition_partbound, + partrel->rd_att, + &isnull); + + bound = stringToNode(TextDatumGetCString(datum)); + + switch (key->strategy) + { + case PARTITION_STRAT_LIST: + { + PartitionListSpec *list_spec; + + Assert(IsA(bound, PartitionListSpec)); + list_spec = (PartitionListSpec *) bound; + parts[i]->list = make_list_from_spec(key, list_spec); + } + break; + + case PARTITION_STRAT_RANGE: + { + PartitionRangeSpec *range_spec; + + Assert(IsA(bound, PartitionRangeSpec)); + range_spec = (PartitionRangeSpec *) bound; + parts[i]->range = make_range_from_spec(key, range_spec); + } + break; + } + + ReleaseSysCache(tuple); + i++; + } + + heap_close(partrel, AccessShareLock); + + *nparts = i; + if (*nparts == 0 && parts) + { + pfree(parts); + parts = NULL; + } + + return parts; +} + +/* + * Helper routine to deep-free a PartitionInfo array + */ +static void +free_partition_info(PartitionInfoData **p, int num) +{ + int i; + + for (i = 0; i < num; i++) + { + if (p[i]->list) + { + pfree(p[i]->list->values); + pfree(p[i]->list); + } + + if (p[i]->range) + { + if (p[i]->range->lower->val) + pfree(p[i]->range->lower->val); + pfree(p[i]->range->lower); + + if (p[i]->range->upper->val) + pfree(p[i]->range->upper->val); + pfree(p[i]->range->upper); + } + + pfree(p[i]); + } + + pfree(p); +} + +/* + * partition_cmp + * Compare two (range) partitions + * + * Used as a callback to qsort_arg for sorting partitions. + */ +static int32 +partition_cmp(const void *a, const void *b, void *arg) +{ + PartitionKey key = (PartitionKey) arg; + + if (key->strategy == PARTITION_STRAT_LIST) + return 0; + + return partition_range_cmp((PartitionKey) arg, + (*(const PartitionInfoData **) a)->range, + (*(const PartitionInfoData **) b)->range); +} + +/* Local support functions for check_new_partition_bound() */ + +/* + * list_overlaps_existing_partition + * + * Does a new list partition's list of values overlap that of any of existing + * partitions? + */ +static bool +list_overlaps_existing_partition(PartitionKey key, + PartitionListSpec *list_spec, + PartitionDesc pdesc, + Oid *with) +{ + PartitionListInfo *new_list; + int i; + + if (pdesc->nparts == 0) + return false; + + new_list = make_list_from_spec(key, list_spec); + + for (i = 0; i < pdesc->nparts; i++) + { + int j; + + /* + * No value in an existing partition's list of values should occur + * in the list of values of the new partition. + */ + for (j = 0; j < pdesc->parts[i]->list->nvalues; j++) + { + Datum existing_val = pdesc->parts[i]->list->values[j]; + bool existing_val_null = pdesc->parts[i]->list->nulls[j]; + int k; + + for (k = 0; k < new_list->nvalues; k++) + { + Datum new_val = new_list->values[k]; + bool new_val_null = new_list->nulls[k]; + + if ((existing_val_null && new_val_null) || + (!existing_val_null && !new_val_null && + partition_list_values_equal(key, + existing_val, + new_val))) + { + *with = pdesc->parts[i]->oid; + return true; + } + } + } + } + + return false; +} + + +/* + * Is a new partition's range empty? + */ +static bool +partition_range_empty(PartitionKey key, PartitionRangeSpec *range_spec) +{ + PartitionRangeInfo *range; + RangeBound *lower, + *upper; + + range = make_range_from_spec(key, range_spec); + lower = range->lower; + upper = range->upper; + + /* + * Range is not empty if one (and only one) of the bounds is infinity. + * Both cannot be infinite because of how the syntax is specified. + */ + Assert(!lower->infinite || !upper->infinite); + if (lower->infinite || upper->infinite) + return false; + + /* + * If upper < lower, then it's outright empty. Also if lower = upper + * and either is exclusive. + */ + if (partition_range_tuple_cmp(key, upper->val, lower->val) < 0 || + (partition_range_tuple_cmp(key, lower->val, upper->val) == 0 && + (!lower->inclusive || !upper->inclusive))) + return true; + + return false; +} + +/* + * range_overlaps_existing_partition + * + * Does the new range partition's range overlap that of any of existing + * partitions? + */ +static bool +range_overlaps_existing_partition(PartitionKey key, + PartitionRangeSpec *range_spec, + PartitionDesc pdesc, + Oid *with) +{ + int i; + PartitionRangeInfo *range; + + if (pdesc->nparts == 0) + return false; + + /* Create internal representation of range from range_spec */ + range = make_range_from_spec(key, range_spec); + + /* Check with existing partitions for overlap */ + for (i = 0; i < pdesc->nparts; i++) + { + if (partition_range_overlaps(key, range, pdesc->parts[i]->range)) + { + *with = pdesc->parts[i]->oid; + return true; + } + } + + return false; +} + +/* Check two range partitions for overlap */ +static bool +partition_range_overlaps(PartitionKey key, + PartitionRangeInfo *r1, PartitionRangeInfo *r2) +{ + if (partition_range_bound_cmp(key, r1->lower, r2->lower) >= 0 && + partition_range_bound_cmp(key, r1->lower, r2->upper) <= 0) + return true; + + if (partition_range_bound_cmp(key, r2->lower, r1->lower) >= 0 && + partition_range_bound_cmp(key, r2->lower, r1->upper) <= 0) + return true; + + return false; +} + +/* Local support functions for get_check_qual_from_partbound */ + +/* + * translate_var_attno + * Changes Vars with a given attno in the provided expression tree to + * Vars with new_attno + */ +static Node * +translate_var_attno(Node *expr, AttrNumber attno, AttrNumber new_attno) +{ + translate_var_attno_mutator_context cxt; + + cxt.old_attno = attno; + cxt.new_attno = new_attno; + + return expression_tree_mutator(expr, translate_var_attno_mutator, &cxt); +} + +/* + * translate_var_attno_mutator + */ +static Node * +translate_var_attno_mutator(Node *node, + translate_var_attno_mutator_context *cxt) +{ + if (node == NULL) + return NULL; + + if (IsA(node, Var) && ((Var *) node)->varattno == cxt->old_attno) + { + Var *newvar = copyObject(node); + + newvar->varattno = cxt->new_attno; + + return (Node *) newvar; + } + + return expression_tree_mutator(node, translate_var_attno_mutator, + (void *) cxt); +} + +/* + * get_check_qual_for_list + * Get a ScalarArrayOpExpr to use as a list partition's predicate, given + * the partition key (for left operand) and list of values obtained from + * PartitionListSpec of the partition. + */ +static List * +get_check_qual_for_list(PartitionKey key, PartitionListSpec *list_spec) +{ + ArrayExpr *values_arr = makeNode(ArrayExpr); + ScalarArrayOpExpr *arrayop_expr; + Node *key_col; + Oid operoid; + bool need_relabel; + + /* Left operand is either a simple Var or arbitrary expression */ + if (key->partattrs[0] != 0) + key_col = (Node *) makeVar(1, key->partattrs[0], + key->tcinfo->typid[0], + key->tcinfo->typmod[0], + key->tcinfo->typcoll[0], + 0); + else + key_col = (Node *) copyObject(linitial(key->partexprs)); + + /* Right operand is an ArrayExpr */ + values_arr->array_typeid = get_array_type(key->tcinfo->typid[0]); + values_arr->array_collid = key->tcinfo->typcoll[0]; + values_arr->element_typeid = key->tcinfo->typid[0]; + values_arr->elements = list_spec->values; + values_arr->multidims = false; + values_arr->location = -1; + + /* Get the correct btree equality operator */ + operoid = get_partition_operator(key, 0, BTEqualStrategyNumber, + &need_relabel); + if (need_relabel) + key_col = (Node *) makeRelabelType((Expr *) key_col, + key->partopcintype[0], -1, + get_typcollation(key->partopcintype[0]), + COERCE_EXPLICIT_CAST); + + /* Build leftop = ANY (rightop) */ + arrayop_expr = makeNode(ScalarArrayOpExpr); + arrayop_expr->opno = operoid; + arrayop_expr->opfuncid = get_opcode(operoid); + arrayop_expr->useOr = true; + arrayop_expr->inputcollid = key->tcinfo->typcoll[0]; + arrayop_expr->args = list_make2(key_col, values_arr); + arrayop_expr->location = -1; + + return list_make1(arrayop_expr); +} + +/* + * get_check_qual_for_range + * Get a list of OpExpr's to use as a range partition's predicate, + * given the partition key (for left operands), lower and upper bounds + * in PartitionRangeSpec (for right operands) + * + * Note: For each column, a 'col IS NOT NULL' constraint is emitted since + * we do not allow null values in range partition key. + */ +static List * +get_check_qual_for_range(PartitionKey key, PartitionRangeSpec *spec) +{ + List *result = NIL; + ListCell *cell1, + *cell2, + *partexprs_item; + int i; + Oid operoid; + uint16 strategy; + bool need_relabel; + + /* + * Handle the case where the partition is bounded on only one side. + * + * In this case, consider only the first column of the key since + * comparison with only the first column would have determined whether + * whether a row went into such partition. In other words, it always + * follows that -INF < someval and someval < +INF. + */ + if (spec->lower == NIL || spec->upper == NIL) + { + List *values; + Const *key_val; + Node *key_col; + bool islower, + inclusive; + NullTest *keynulltest; + + if (spec->lower != NIL) + { + values = spec->lower; + islower = true; + inclusive = spec->lowerinc; + } + else + { + values = spec->upper; + islower = false; + inclusive = spec->upperinc; + } + + /* Left operand */ + if (key->partattrs[0] != 0) + key_col = (Node *) makeVar(1, key->partattrs[0], + key->tcinfo->typid[0], + key->tcinfo->typmod[0], + key->tcinfo->typcoll[0], + 0); + else + key_col = (Node *) copyObject(linitial(key->partexprs)); + + /* Right operand */ + key_val = linitial(values); + + if (islower) + strategy = inclusive ? BTGreaterEqualStrategyNumber : BTGreaterStrategyNumber; + else + strategy = inclusive ? BTLessEqualStrategyNumber : BTLessStrategyNumber; + + /* Get the correct btree operator for given strategy */ + operoid = get_partition_operator(key, 0, strategy, &need_relabel); + + if (need_relabel) + key_col = (Node *) makeRelabelType((Expr *) key_col, + key->partopcintype[0], -1, + get_typcollation(key->partopcintype[0]), + COERCE_EXPLICIT_CAST); + + /* Gin up a col IS NOT NULL test */ + keynulltest = makeNode(NullTest); + keynulltest->arg = (Expr *) key_col; + keynulltest->nulltesttype = IS_NOT_NULL; + keynulltest->argisrow = false; + keynulltest->location = -1; + + /* Build leftop op rightop and return the list containing it */ + return list_make2(keynulltest, + make_opclause(operoid, BOOLOID, + false, + (Expr *) key_col, + (Expr *) key_val, + InvalidOid, + key->tcinfo->typcoll[0])); + } + + /* + * We must consider both the lower and upper bounds. Iterate over + * columns of the key. + */ + i = 0; + partexprs_item = list_head(key->partexprs); + forboth (cell1, spec->lower, cell2, spec->upper) + { + Node *key_col; + Const *lower_val = lfirst(cell1); + Const *upper_val = lfirst(cell2); + EState *estate; + MemoryContext oldcxt; + Expr *test_expr; + ExprState *test_exprstate; + Datum test_result; + bool isNull; + bool need_relabel = false; + NullTest *keynulltest; + + /* Left operand */ + if (key->partattrs[i] != 0) + { + key_col = (Node *) makeVar(1, key->partattrs[i], + key->tcinfo->typid[i], + key->tcinfo->typmod[i], + key->tcinfo->typcoll[i], + 0); + } + else + { + key_col = (Node *) copyObject(lfirst(partexprs_item)); + partexprs_item = lnext(partexprs_item); + } + + /* Gin up a col IS NOT NULL test */ + keynulltest = makeNode(NullTest); + keynulltest->arg = (Expr *) key_col; + keynulltest->nulltesttype = IS_NOT_NULL; + keynulltest->argisrow = false; + keynulltest->location = -1; + result = lappend(result, keynulltest); + + /* + * Is lower_val = upper_val? + */ + + /* Get the correct btree equality operator for the test */ + operoid = get_partition_operator(key, i, BTEqualStrategyNumber, + &need_relabel); + + estate = CreateExecutorState(); + oldcxt = MemoryContextSwitchTo(estate->es_query_cxt); + test_expr = make_opclause(operoid, + BOOLOID, + false, + (Expr *) lower_val, + (Expr *) upper_val, + InvalidOid, + key->tcinfo->typcoll[i]); + fix_opfuncids((Node *) test_expr); + test_exprstate = ExecInitExpr(test_expr, NULL); + test_result = ExecEvalExprSwitchContext(test_exprstate, + GetPerTupleExprContext(estate), + &isNull, NULL); + MemoryContextSwitchTo(oldcxt); + FreeExecutorState(estate); + + if (DatumGetBool(test_result)) + { + /* + * Yes, build leftop eq lower_val (we use lower_val arbitrarily) + */ + if (need_relabel) + key_col = (Node *) makeRelabelType((Expr *) key_col, + key->partopcintype[i], -1, + get_typcollation(key->partopcintype[i]), + COERCE_EXPLICIT_CAST); + result = lappend(result, + make_opclause(operoid, + BOOLOID, + false, + (Expr *) key_col, + (Expr *) lower_val, + InvalidOid, + key->tcinfo->typcoll[i])); + + /* Go to the next column. */ + } + else + { + /* + * Build leftop >= lower_val and leftop <= upper_val, if leftop + * is not the last column. + * + * For the last column, determine operator inclusivity to use from + * the partition bound spec (lowerinc or upperinc). + */ + + /* Build leftop ge/gt lower_val */ + strategy = i < key->partnatts - 1 || spec->lowerinc + ? BTGreaterEqualStrategyNumber + : BTGreaterStrategyNumber; + operoid = get_partition_operator(key, i, strategy, &need_relabel); + + if (need_relabel) + key_col = (Node *) makeRelabelType((Expr *) key_col, + key->partopcintype[i], -1, + get_typcollation(key->partopcintype[i]), + COERCE_EXPLICIT_CAST); + + result = lappend(result, + make_opclause(operoid, + BOOLOID, + false, + (Expr *) key_col, + (Expr *) lower_val, + InvalidOid, + key->tcinfo->typcoll[i])); + + /* Build leftop le/lt upper_val */ + strategy = i < key->partnatts - 1 || spec->upperinc + ? BTLessEqualStrategyNumber + : BTLessStrategyNumber; + operoid = get_partition_operator(key, i, strategy, &need_relabel); + + if (need_relabel) + key_col = (Node *) makeRelabelType((Expr *) key_col, + key->partopcintype[i], -1, + get_typcollation(key->partopcintype[i]), + COERCE_EXPLICIT_CAST); + + result = lappend(result, + make_opclause(operoid, + BOOLOID, + false, + (Expr *) key_col, + (Expr *) upper_val, + InvalidOid, + key->tcinfo->typcoll[i])); + + /* No need to constrain further columns. */ + break; + } + + i++; + } + + return result; +} + +/* + * get_partition_operator + * Return oid of the operator of given strategy for a given partition + * key column. + * + * Use either the column type as the operator datatype or opclass's declared + * input type. + */ +static Oid +get_partition_operator(PartitionKey key, int col, StrategyNumber strategy, + bool *need_relabel) +{ + Oid operoid; + + if (need_relabel) + *need_relabel = false; + operoid = get_opfamily_member(key->partopfamily[col], + key->tcinfo->typid[col], + key->tcinfo->typid[col], + strategy); + + if (!OidIsValid(operoid)) + { + operoid = get_opfamily_member(key->partopfamily[col], + key->partopcintype[col], + key->partopcintype[col], + strategy); + *need_relabel = true; + } + + return operoid; +} + +/* + * generate_partition_check_qual + * Generate partition predicate from rel's pg_partition entry + * + * Result expression tree is stored CacheMemoryContext to ensure it survives + * as long as the relcache entry. But we should be running in a less long-lived + * working context. To avoid leaking cache memory if this routine fails partway + * through, we build in working memory and then copy the completed structure + * into cache memory. + */ +static List * +generate_partition_check_qual(Relation rel) +{ + HeapTuple tuple; + MemoryContext oldcxt; + Datum boundDatum; + bool isnull; + Node *bound; + List *my_check = NIL, + *result = NIL; + Oid relid = RelationGetRelid(rel); + Relation parent = heap_open(get_partition_parent(relid), NoLock); + + /* Quick copy */ + if (rel->rd_partcheck) + { + heap_close(parent, NoLock); + return copyObject(rel->rd_partcheck); + } + + /* Actually generate from pg_partition.partbound */ + tuple = SearchSysCache1(PARTRELID, relid); + Assert(HeapTupleIsValid(tuple)); + boundDatum = SysCacheGetAttr(PARTRELID, tuple, + Anum_pg_partition_partbound, + &isnull); + Assert(!isnull); + bound = stringToNode(TextDatumGetCString(boundDatum)); + + /* + * Generate executable expressions from ListPartitionSpec and + * RangePartitionSpec nodes. + */ + my_check = get_check_qual_from_partbound(rel, parent, bound); + + /* + * If parent is also a partition, add its expressions to the list. + */ + if (relid_is_partition(RelationGetRelid(parent))) + { + List *parent_check; + + parent_check = generate_partition_check_qual(parent); + result = list_concat(parent_check, my_check); + } + else + result = my_check; + + /* Save a copy in the relcache */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + rel->rd_partcheck = copyObject(result); + MemoryContextSwitchTo(oldcxt); + + ReleaseSysCache(tuple); + heap_close(parent, NoLock); + + return result; +} + +/* List partition related support functions */ + +/* + * Make a PartitionListInfo from list of values using the information in key + */ +static PartitionListInfo * +make_list_from_spec(PartitionKey key, PartitionListSpec *list_spec) +{ + PartitionListInfo *list; + ListCell *cell; + int i; + + list = (PartitionListInfo *) palloc0(sizeof(PartitionListInfo)); + list->nvalues = list_length(list_spec->values); + list->values = (Datum *) palloc0(list->nvalues * sizeof(Datum)); + list->nulls = (bool *) palloc0(list->nvalues * sizeof(bool)); + + i = 0; + foreach (cell, list_spec->values) + { + Const *val = lfirst(cell); + + if (val->constisnull) + list->nulls[i] = true; + else + list->values[i] = datumCopy(val->constvalue, + key->tcinfo->typbyval[0], + key->tcinfo->typlen[0]); + i++; + } + + return list; +} + +/* + * Helper routine to copy a list partition bound struct + */ +static PartitionListInfo * +copy_list_info(PartitionListInfo *src, PartitionKey key) +{ + int i; + PartitionListInfo *result; + + result = (PartitionListInfo *) palloc0(sizeof(PartitionListInfo)); + result->nvalues = src->nvalues; + result->values = (Datum *) palloc0(src->nvalues * sizeof(Datum)); + result->nulls = (bool *) palloc0(src->nvalues * sizeof(bool)); + + for (i = 0; i < src->nvalues; i++) + { + if (!src->nulls[i]) + result->values[i] = datumCopy(src->values[i], + key->tcinfo->typbyval[0], + key->tcinfo->typlen[0]); + else + result->nulls[i] = true; + } + return result; +} + +/* Are two list partition bounds equal (contain the same set of values)? */ +static bool +equal_list_info(PartitionKey key, + PartitionListInfo *l1, PartitionListInfo *l2) +{ + int i, + j; + + /* Check that every value (including null) in l1 also exists in l2 */ + for (i = 0; i < l1->nvalues; i++) + { + Datum l1_val = l1->values[i]; + bool l1_val_null = l1->nulls[i]; + bool found = false; + + for (j = 0; j < l2->nvalues; j++) + { + Datum l2_val = l2->values[j]; + bool l2_val_null = l2->nulls[j]; + + if ((l1_val_null && l2_val_null) || + (!l1_val_null && !l2_val_null && + partition_list_values_equal(key, l1_val, l2_val))) + { + found = true; + break; + } + } + if (!found) + return false; + } + + return true; +} + +/* Are two values val1 and val2 equal per the list partition key */ +static bool +partition_list_values_equal(PartitionKey key, Datum val1, Datum val2) +{ + int cmpval; + + cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0], + key->tcinfo->typcoll[0], + val1, val2)); + return cmpval == 0; +} + +/* Range partition related support functions */ + +/* + * Make a PartitionRangeInfo from given PartitionRangeSpec + */ +static PartitionRangeInfo * +make_range_from_spec(PartitionKey key, PartitionRangeSpec *range_spec) +{ + PartitionRangeInfo *range; + + range = (PartitionRangeInfo *) palloc0(sizeof(PartitionRangeInfo)); + range->lower = make_range_bound(key, + range_spec->lower, + range_spec->lowerinc, + true); + range->upper = make_range_bound(key, + range_spec->upper, + range_spec->upperinc, + false); + + return range; +} + +static RangeBound * +make_range_bound(PartitionKey key, List *val, bool inclusive, bool lower) +{ + RangeBound *bound; + ListCell *cell; + + bound = (RangeBound *) palloc0(sizeof(RangeBound)); + bound->infinite = (val == NIL); + bound->inclusive = inclusive; + bound->lower = lower; + + if (val) + { + int i; + + bound->val = (Datum *) palloc0(key->partnatts * sizeof(Datum)); + + i = 0; + foreach (cell, val) + { + Const *val = lfirst(cell); + + if (val->constisnull) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot specify NULL in range bound"))); + else + bound->val[i] = datumCopy(val->constvalue, + key->tcinfo->typbyval[i], + key->tcinfo->typlen[i]); + i++; + } + } + + return bound; +} + +/* + * Helper routines to copy a range partition info + */ + +static PartitionRangeInfo * +copy_range_info(PartitionRangeInfo *src, PartitionKey key) +{ + PartitionRangeInfo *result; + + result = (PartitionRangeInfo *) palloc0(sizeof(PartitionRangeInfo)); + result->lower = copy_range_bound(src->lower, key); + result->upper = copy_range_bound(src->upper, key); + + return result; +} + +static RangeBound * +copy_range_bound(RangeBound *src, PartitionKey key) +{ + int i; + int partnatts = get_partition_key_natts(key); + RangeBound *result; + + result = (RangeBound *) palloc0(sizeof(RangeBound)); + result->infinite = src->infinite; + result->inclusive = src->inclusive; + result->lower = src->lower; + + if (src->val) + { + result->val = (Datum *) palloc0(partnatts * sizeof(Datum)); + for (i = 0; i < partnatts; i++) + result->val[i] = datumCopy(src->val[i], + key->tcinfo->typbyval[i], + key->tcinfo->typlen[i]); + } + + return result; +} + +/* + * Are two range partition ranges equal? + * + * It's clear in this case that both are either lower bounds or upper bounds. + */ +static bool +equal_range_info(PartitionKey key, + PartitionRangeInfo *r1, PartitionRangeInfo *r2) +{ + return partition_range_bound_cmp(key, r1->lower, r2->lower) == 0 && + partition_range_bound_cmp(key, r1->upper, r2->upper) == 0; +} + +/* + * Compare two range partitions' ranges + */ +static int32 +partition_range_cmp(PartitionKey key, PartitionRangeInfo *r1, + PartitionRangeInfo *r2) +{ + int cmp; + + cmp = partition_range_bound_cmp(key, r1->lower, r2->lower); + if (cmp == 0) + cmp = partition_range_bound_cmp(key, r1->upper, r2->upper); + + return cmp; +} + +/* Returns for two range partition bounds whether, b1 <=, =, >= b2 */ +static int32 +partition_range_bound_cmp(PartitionKey key, RangeBound *b1, RangeBound *b2) +{ + int32 result; + + /* + * First, handle cases involving infinity, which don't require invoking + * the comparison proc. + */ + if (b1->infinite && b2->infinite) + { + /* + * Both are infinity, so they are equal unless one is lower and the + * other not. + */ + if (b1->lower == b2->lower) + return 0; + else + return b1->lower ? -1 : 1; + } + else if (b1->infinite) + return b1->lower ? -1 : 1; + else if (b2->infinite) + return b2->lower ? 1 : -1; + + /* + * Both boundaries are finite, so compare the held values. + */ + result = partition_range_tuple_cmp(key, b1->val, b2->val); + + /* + * If the comparison is anything other than equal, we're done. If they + * compare equal though, we still have to consider whether the boundaries + * are inclusive or exclusive. + */ + if (result == 0) + { + if (!b1->inclusive && !b2->inclusive) + { + /* both are exclusive */ + if (b1->lower == b2->lower) + return 0; + else + return b1->lower ? 1 : -1; + } + else if (!b1->inclusive) + return b1->lower ? 1 : -1; + else if (!b2->inclusive) + return b2->lower ? -1 : 1; + else + { + /* + * Both are inclusive and the values held are equal, so they are + * equal regardless of whether they are upper or lower boundaries, + * or a mix. + */ + return 0; + } + } + + return result; +} + +/* Compare two composite keys */ +static int32 +partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2) +{ + int32 result; + int i; + + for (i = 0; i < key->partnatts; i++) + { + result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i], + key->tcinfo->typcoll[i], + val1[i], val2[i])); + if (result != 0) + break; + } + + return result; +} diff --git a/src/backend/catalog/pg_partition.c b/src/backend/catalog/pg_partition.c new file mode 100644 index 0000000..1f946dc --- /dev/null +++ b/src/backend/catalog/pg_partition.c @@ -0,0 +1,106 @@ +/*------------------------------------------------------------------------- + * + * pg_partition.c + * routines to support manipulation of the pg_partition 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_partition.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "access/htup_details.h" +#include "catalog/indexing.h" +#include "catalog/pg_partition.h" +#include "catalog/pg_partition_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" + +/* + * StorePartitionBound + * + * Store partition boundary info in pg_partition after checking that it does + * not overlap with any existing partition. Error out if it does. + */ +void +StorePartitionBound(Oid relid, Oid parentId, Node *bound) +{ + Relation pg_partition; + Relation parent; + HeapTuple tuple; + char *boundString; + Datum values[Natts_pg_partition]; + bool nulls[Natts_pg_partition]; + + parent = heap_open(parentId, AccessShareLock); + + pg_partition = heap_open(PartitionRelationId, RowExclusiveLock); + + boundString = nodeToString(bound); + + /* Build the tuple - bound can never be null */ + MemSet(nulls, false, sizeof(nulls)); + + values[Anum_pg_partition_partrelid - 1] = ObjectIdGetDatum(relid); + values[Anum_pg_partition_partbound - 1] = CStringGetTextDatum(boundString); + + tuple = heap_form_tuple(RelationGetDescr(pg_partition), values, nulls); + + simple_heap_insert(pg_partition, tuple); + + /* Update the indexes on pg_partition */ + CatalogUpdateIndexes(pg_partition, tuple); + + /* Invalidate the parent's relcache */ + CacheInvalidateRelcache(parent); + + heap_close(parent, AccessShareLock); + heap_close(pg_partition, RowExclusiveLock); +} + +/* + * RemovePartitionByRelId + * Remove a pg_partition entry for a partition. + */ +void +RemovePartitionEntryByRelId(Oid relid) +{ + Relation rel; + HeapTuple tuple; + + rel = heap_open(PartitionRelationId, RowExclusiveLock); + tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid)); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for partition entry of relation %u", + relid); + + simple_heap_delete(rel, &tuple->t_self); + + /* Update the indexes on pg_partitioned_rel */ + CatalogUpdateIndexes(rel, tuple); + + ReleaseSysCache(tuple); + heap_close(rel, RowExclusiveLock); +} + +/* + * does relid have pg_partition entry? + */ +bool +relid_is_partition(Oid relid) +{ + return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid)); +} diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c index eb531af..bfe4e0b 100644 --- a/src/backend/commands/foreigncmds.c +++ b/src/backend/commands/foreigncmds.c @@ -1464,6 +1464,23 @@ CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid) fdw = GetForeignDataWrapper(server->fdwid); /* + * If this foreign table is a partition, check that the FDW supports + * insert. + */ + if (stmt->base.partbound != NULL) + { + FdwRoutine *fdw_routine; + + fdw_routine = GetFdwRoutine(fdw->fdwhandler); + if (fdw_routine->ExecForeignInsert == NULL) + ereport(ERROR, + (errcode(ERRCODE_FDW_NO_SCHEMAS), + errmsg("cannot create foreign table as partition"), + errdetail("foreign-data wrapper \"%s\" does not support insert", + fdw->fdwname))); + } + + /* * Insert tuple into pg_foreign_table. */ memset(values, 0, sizeof(values)); diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c index 62671a8..eb6bfbb 100644 --- a/src/backend/commands/lockcmds.c +++ b/src/backend/commands/lockcmds.c @@ -17,6 +17,7 @@ #include "access/heapam.h" #include "catalog/namespace.h" #include "catalog/pg_inherits_fn.h" +#include "catalog/pg_partitioned_fn.h" #include "commands/lockcmds.h" #include "miscadmin.h" #include "parser/parse_clause.h" @@ -63,7 +64,8 @@ LockTableCommand(LockStmt *lockstmt) RangeVarCallbackForLockTable, (void *) &lockstmt->mode); - if (recurse) + /* Force inheritance recursion, if partitioned table. */ + if (recurse || relid_is_partitioned(reloid)) LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait); } } diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 5dcd4cf..21d9f96 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -159,6 +159,7 @@ DefineSequence(CreateSeqStmt *seq) coldef->is_local = true; coldef->is_not_null = true; coldef->is_from_type = false; + coldef->is_for_partition = false; coldef->storage = 0; coldef->raw_default = NULL; coldef->cooked_default = NULL; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index e070a1a..361b5ea 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -40,6 +40,7 @@ #include "catalog/pg_inherits_fn.h" #include "catalog/pg_namespace.h" #include "catalog/pg_opclass.h" +#include "catalog/pg_partition_fn.h" #include "catalog/pg_partitioned_fn.h" #include "catalog/pg_tablespace.h" #include "catalog/pg_trigger.h" @@ -122,6 +123,19 @@ static List *on_commits = NIL; /* + * For AT ATTACH PARTITION <table> FOR VALUES <partition_bound_spec>, + * + * Struct describing partition check expression corresponding to + * <partition_bound_spec> to validate for rows in <table> during + * Phase 3 scan. + */ +typedef struct PartitionCheck +{ + Expr *expr; + List *state; +} PartitionCheck; + +/* * State information for ALTER TABLE * * The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo @@ -164,6 +178,8 @@ typedef struct AlteredTableInfo Oid newTableSpace; /* new tablespace; 0 means no change */ bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */ char newrelpersistence; /* if above is true */ + PartitionCheck *partitionCheck; /* partition check expression for + * ATTACH PARTITION validation */ /* Objects to rebuild after completing ALTER TYPE operations */ List *changedConstraintOids; /* OIDs of constraints to rebuild */ List *changedConstraintDefs; /* string definitions of same */ @@ -280,7 +296,8 @@ typedef struct static void truncate_check_rel(Relation rel); static List *MergeAttributes(List *schema, List *supers, char relpersistence, - List **supOids, List **supconstr, int *supOidCount); + List **supOids, List **supconstr, int *supOidCount, + bool is_partition); static bool MergeCheckConstraint(List *constraints, char *name, Node *expr); static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel); static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel); @@ -448,6 +465,11 @@ static void ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs, List **partexprs, Oid *partoplass); +static void CreateInheritance(Relation child_rel, Relation parent_rel); +static void RemoveInheritance(Relation child_rel, Relation parent_rel); +static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel, + PartitionCmd *cmd); +static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name); /* ---------------------------------------------------------------- @@ -590,10 +612,16 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, /* * Look up inheritance ancestors and generate relation schema, including * inherited attributes. + * + * The last parameter implicitly specifies that the table is being created + * as partition and schema consists of columns definitions corresponding + * to non-dropped columns of the parent constructed such that each + * attribute of the table is created as inherited and non-local. */ schema = MergeAttributes(schema, stmt->inhRelations, stmt->relation->relpersistence, - &inheritOids, &old_constraints, &parentOidCount); + &inheritOids, &old_constraints, &parentOidCount, + stmt->partbound != NULL); /* * Create a tuple descriptor from the relation schema. Note that this @@ -713,6 +741,20 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, */ rel = relation_open(relationId, AccessExclusiveLock); + /* Process and store partition bound. */ + if (stmt->partbound) + { + Oid parentId = linitial_oid(inheritOids); + + /* + * Check first that the new partition's bound is valid and does not + * overlap with any of existing partitions of the parent - note that + * it does not return on error. + */ + check_new_partition_bound(parentId, stmt->partbound); + StorePartitionBound(relationId, parentId, stmt->partbound); + } + /* Process and store partition key, if any */ if (stmt->partby) { @@ -728,14 +770,16 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, partnatts = list_length(stmt->partby->partParams); StorePartitionKey(rel, stmt->partby->strategy, partnatts, partattrs, partexprs, partopclass); + } - /* - * Bump the command counter to make the newly-created pg_partitioned - * tuple visible so that the constraint code invoked below can know - * that the relation is a partitioned table. - */ + /* + * Bump the command counter to make the newly-created pg_partitioned + * and/or pg_partition tuple visible so that the constraint code invoked + * below can know that the relation is a partitioned table and/or a + * partition. + */ + if (stmt->partbound || stmt->partby) CommandCounterIncrement(); - } /* * Now add any newly specified column default values and CHECK constraints @@ -990,6 +1034,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid, if (!OidIsValid(relOid)) return; + if (relid_is_partition(relOid)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is a partition of \"%s\"", rel->relname, + get_rel_name(get_partition_parent(relOid))), + errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it."))); + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid)); if (!HeapTupleIsValid(tuple)) return; /* concurrently dropped, so nothing to do */ @@ -1081,7 +1132,8 @@ ExecuteTruncate(TruncateStmt *stmt) rels = lappend(rels, rel); relids = lappend_oid(relids, myrelid); - if (recurse) + /* Force inheritance recursion, if partitioned table. */ + if (recurse || relid_is_partitioned(myrelid)) { ListCell *child; List *children; @@ -1459,7 +1511,8 @@ storage_name(char c) */ static List * MergeAttributes(List *schema, List *supers, char relpersistence, - List **supOids, List **supconstr, int *supOidCount) + List **supOids, List **supconstr, int *supOidCount, + bool is_partition) { ListCell *entry; List *inhSchema = NIL; @@ -1504,8 +1557,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence, /* * Typed table column option that does not belong to a column from - * the type. This works because the columns from the type come - * first in the list. + * the type or the partition parent. This works because the columns + * from the type or the partition parent come first in the list. */ ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), @@ -1532,6 +1585,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence, coldef->is_from_type = false; list_delete_cell(schema, rest, prev); } + else if (coldef->is_for_partition) + { + /* + * merge the column options into the column from the parent + */ + coldef->is_not_null = restdef->is_not_null; + coldef->raw_default = restdef->raw_default; + coldef->cooked_default = restdef->cooked_default; + coldef->constraints = restdef->constraints; + coldef->is_for_partition = false; /* job is done */ + list_delete_cell(schema, rest, prev); + } else ereport(ERROR, (errcode(ERRCODE_DUPLICATE_COLUMN), @@ -1570,13 +1635,23 @@ 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) + /* + * Cannot inherit from a partitioned table unless being created as + * partition. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL && !is_partition) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot inherit from partitioned table \"%s\"", parent->relname))); + /* The same if the parent is a partition */ + if (relid_is_partition(RelationGetRelid(relation)) && !is_partition) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot inherit from partition \"%s\"", + parent->relname))); + if (relation->rd_rel->relkind != RELKIND_RELATION && relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE && relation->rd_rel->relkind != RELKIND_PARTITIONED_REL) @@ -1717,6 +1792,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence, def->is_local = false; def->is_not_null = attribute->attnotnull; def->is_from_type = false; + def->is_for_partition = false; def->storage = attribute->attstorage; def->raw_default = NULL; def->cooked_default = NULL; @@ -1845,6 +1921,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence, * If we had no inherited attributes, the result schema is just the * explicitly declared columns. Otherwise, we need to merge the declared * columns into the inherited schema list. + * + * Note: In case of a partition, there are only inherited attributes that + * we copied from the parent in transformPartitionOf(). */ if (inhSchema != NIL) { @@ -1874,16 +1953,20 @@ MergeAttributes(List *schema, List *supers, char relpersistence, /* * Yes, try to merge the two column definitions. They must - * have the same type, typmod, and collation. + * have the same type, typmod, and collation. Don't output + * the notices, if partition. */ - if (exist_attno == schema_attno) - ereport(NOTICE, - (errmsg("merging column \"%s\" with inherited definition", - attributeName))); - else - ereport(NOTICE, - (errmsg("moving and merging column \"%s\" with inherited definition", attributeName), - errdetail("User-specified column moved to the position of the inherited column."))); + if (!is_partition) + { + if (exist_attno == schema_attno) + ereport(NOTICE, + (errmsg("merging column \"%s\" with inherited definition", + attributeName))); + else + ereport(NOTICE, + (errmsg("moving and merging column \"%s\" with inherited definition", attributeName), + errdetail("User-specified column moved to the position of the inherited column."))); + } def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1); typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod); typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod); @@ -1920,8 +2003,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence, storage_name(def->storage), storage_name(newdef->storage)))); - /* Mark the column as locally defined */ - def->is_local = true; + /* Mark the column as locally defined (unless partition) */ + if (!is_partition) + def->is_local = true; /* Merge of NOT NULL constraints = OR 'em together */ def->is_not_null |= newdef->is_not_null; /* If new def has a default, override previous default */ @@ -2211,6 +2295,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing) (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot rename column of typed table"))); + if (relid_is_partition(myrelid) && !recursing) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot rename column of a partition"))); + /* * Renaming the columns of sequences or toast tables doesn't actually * break anything from the system's point of view, since internal @@ -2437,11 +2526,13 @@ renameatt(RenameStmt *stmt) return InvalidObjectAddress; } + /* Force inheritance recursion, if partitioned table. */ attnum = renameatt_internal(relid, stmt->subname, /* old att name */ stmt->newname, /* new att name */ - interpretInhOption(stmt->relation->inhOpt), /* recursive? */ + relid_is_partitioned(relid) || /* recursive? */ + interpretInhOption(stmt->relation->inhOpt), false, /* recursing? */ 0, /* expected inhcount */ stmt->behavior); @@ -2589,11 +2680,13 @@ RenameConstraint(RenameStmt *stmt) } } + /* Force inheritance recursion, if partitioned table. */ return rename_constraint_internal(relid, typid, stmt->subname, stmt->newname, - stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */ + stmt->relation ? relid_is_partitioned(relid) || + interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */ false, /* recursing? */ 0 /* expected inhcount */ ); @@ -2835,8 +2928,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt) CheckTableNotInUse(rel, "ALTER TABLE"); + /* Force inheritance recursion, if partitioned table */ ATController(stmt, - rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt), + rel, stmt->cmds, + relid_is_partitioned(RelationGetRelid(rel)) || + interpretInhOption(stmt->relation->inhOpt), lockmode); } @@ -3115,6 +3211,11 @@ AlterTableGetLockLevel(List *cmds) cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def); break; + case AT_AttachPartition: + case AT_DetachPartition: + cmd_lockmode = AccessExclusiveLock; + break; + default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); @@ -3432,6 +3533,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* No command-specific prep needed */ pass = AT_PASS_MISC; break; + case AT_AttachPartition: + case AT_DetachPartition: + ATSimplePermissions(rel, ATT_TABLE); + /* No command-specific prep needed */ + pass = AT_PASS_MISC; + break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); @@ -3751,6 +3858,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, case AT_GenericOptions: ATExecGenericOptions(rel, (List *) cmd->def); break; + case AT_AttachPartition: + ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def); + break; + case AT_DetachPartition: + ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name); + break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); @@ -3936,7 +4049,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode) * Test the current data within the table against new constraints * generated by ALTER TABLE commands, but don't rebuild data. */ - if (tab->constraints != NIL || tab->new_notnull) + if (tab->constraints != NIL || tab->new_notnull || tab->partitionCheck) ATRewriteTable(tab, InvalidOid, lockmode); /* @@ -4080,6 +4193,21 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) } } + /* + * Build expression execution states for partition check expression + * as well. + */ + if (tab->partitionCheck) + { + List *expr; + + needscan = true; + Assert(tab->partitionCheck->expr); + expr = list_make1(tab->partitionCheck->expr); + tab->partitionCheck->state = (List *) + ExecPrepareExpr((Expr *) expr, estate); + } + foreach(l, tab->newvals) { NewColumnValue *ex = lfirst(l); @@ -4269,6 +4397,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) } } + if (tab->partitionCheck && + !ExecQual(tab->partitionCheck->state, econtext, true)) + ereport(ERROR, + (errcode(ERRCODE_CHECK_VIOLATION), + errmsg("table contains a row violating the partition bound specification"))); + /* Write the tuple out to the new relation */ if (newrel) heap_insert(newrel, tuple, mycid, hi_options, bistate); @@ -4466,7 +4600,8 @@ ATSimpleRecursion(List **wqueue, Relation rel, */ if (recurse && (rel->rd_rel->relkind == RELKIND_RELATION || - rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)) + rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE || + rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)) { Oid relid = RelationGetRelid(rel); ListCell *child; @@ -4788,6 +4923,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, if (recursing) ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); + if (relid_is_partition(RelationGetRelid(rel)) && !recursing) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot add column to a partition"))); + attrdesc = heap_open(AttributeRelationId, RowExclusiveLock); /* @@ -5243,6 +5383,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode) List *indexoidlist; ListCell *indexoidscan; ObjectAddress address; + Oid relid; /* * lookup the attribute @@ -5310,6 +5451,28 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode) list_free(indexoidlist); /* + * If rel is partition, throw error if the attribute is not null in + * parent + */ + relid = RelationGetRelid(rel); + if (relid_is_partition(relid)) + { + Oid parentId = get_partition_parent(relid); + Relation parent = heap_open(parentId, AccessShareLock); + TupleDesc tupDesc = RelationGetDescr(parent); + AttrNumber parent_attnum; + + parent_attnum = get_attnum(parentId, colName); + if (tupDesc->attrs[parent_attnum - 1]->attnotnull) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("column \"%s\" is not null in parent", + colName), + errhint("Please drop not null in the parent instead"))); + heap_close(parent, AccessShareLock); + } + + /* * Okay, actually perform the catalog change ... if needed */ if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull) @@ -5843,6 +6006,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName, if (recursing) ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); + if (relid_is_partition(RelationGetRelid(rel)) && !recursing) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot drop column from a partition"))); + /* * get the number of the attribute */ @@ -5888,11 +6056,13 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName, if (!is_expr) ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), - errmsg("cannot drop column named in partition key"))); + errmsg("cannot drop column named in%s partition key", + recursing ? " downstream" : ""))); else ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), - errmsg("cannot drop column referenced in partition key expression"))); + errmsg("cannot drop column referenced in%s partition key expression", + recursing ? " downstream" : ""))); } ReleaseSysCache(tuple); @@ -6326,8 +6496,9 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, /* * If adding a NO INHERIT constraint, no need to find our children. + * Remember that we discard is_no_inherit for partitioned tables. */ - if (constr->is_no_inherit) + if (constr->is_no_inherit && !relid_is_partitioned(RelationGetRelid(rel))) return address; /* @@ -7888,7 +8059,9 @@ ATExecDropConstraint(Relation rel, const char *constrName, /* * Propagate to children as appropriate. Unlike most other ALTER * routines, we have to do this one level of recursion at a time; we can't - * use find_all_inheritors to do it in one pass. + * use find_all_inheritors to do it in one pass. Note that if the parent + * is a partitioned table, we propagate to children (partitions) despite + * is_no_inherit_constraint. */ if (!is_no_inherit_constraint) children = find_inheritance_children(RelationGetRelid(rel), lockmode); @@ -8020,6 +8193,11 @@ ATPrepAlterColumnType(List **wqueue, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot alter column type of typed table"))); + if (relid_is_partition(RelationGetRelid(rel)) && !recursing) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot alter column type of a partition"))); + /* lookup the attribute so we can check inheritance status */ tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName); if (!HeapTupleIsValid(tuple)) @@ -8050,11 +8228,13 @@ ATPrepAlterColumnType(List **wqueue, if (!is_expr) ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), - errmsg("cannot alter column named in partition key"))); + errmsg("cannot alter column named in%s partition key", + recursing ? " downstream" : ""))); else ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), - errmsg("cannot alter column referenced in partition key expression"))); + errmsg("cannot alter column referenced in%s partition key expression", + recursing ? " downstream" : ""))); } /* Look up the target type */ @@ -10187,6 +10367,11 @@ ATPrepAddInherit(Relation child_rel) (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot change inheritance of typed table"))); + if (relid_is_partition(RelationGetRelid(child_rel))) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot change inheritance of a partition"))); + if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -10199,12 +10384,7 @@ ATPrepAddInherit(Relation child_rel) static ObjectAddress ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) { - Relation parent_rel, - catalogRelation; - SysScanDesc scan; - ScanKeyData key; - HeapTuple inheritsTuple; - int32 inhseqno; + Relation parent_rel; List *children; ObjectAddress address; @@ -10248,37 +10428,10 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) (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. - * (Note: get RowExclusiveLock because we will write pg_inherits below.) - * - * Note: we do not reject the case where the child already inherits from - * the parent indirectly; CREATE TABLE doesn't reject comparable cases. - */ - catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock); - ScanKeyInit(&key, - Anum_pg_inherits_inhrelid, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(RelationGetRelid(child_rel))); - scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, - true, NULL, 1, &key); - - /* inhseqno sequences start at 1 */ - inhseqno = 0; - while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan))) - { - Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple); - - if (inh->inhparent == RelationGetRelid(parent_rel)) - ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_TABLE), - errmsg("relation \"%s\" would be inherited from more than once", - RelationGetRelationName(parent_rel)))); - if (inh->inhseqno > inhseqno) - inhseqno = inh->inhseqno; - } - systable_endscan(scan); + if (relid_is_partition(RelationGetRelid(parent_rel))) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot inherit from a partition"))); /* * Prevent circularity by seeing if proposed parent inherits from child. @@ -10313,6 +10466,69 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) RelationGetRelationName(child_rel), RelationGetRelationName(parent_rel)))); + /* OK to create inheritance */ + CreateInheritance(child_rel, parent_rel); + + ObjectAddressSet(address, RelationRelationId, + RelationGetRelid(parent_rel)); + + /* keep our lock on the parent relation until commit */ + heap_close(parent_rel, NoLock); + + return address; +} + +/* + * CreateInheritance + * Catalog manipulation portion of creating inheritance between a child + * table and a parent table. + * + * Common to ATExecAddInherit() and ATExecAttachPartition(). + */ +static void +CreateInheritance(Relation child_rel, Relation parent_rel) +{ + Relation catalogRelation; + SysScanDesc scan; + ScanKeyData key; + HeapTuple inheritsTuple; + int32 inhseqno; + + /* Note: get RowExclusiveLock because we will write pg_inherits below. */ + catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock); + + /* + * Check for duplicates in the list of parents, and determine the highest + * inhseqno already present; we'll use the next one for the new parent. + * Also, if proposed child is a partition, it cannot already be inheriting. + * + * Note: we do not reject the case where the child already inherits from + * the parent indirectly; CREATE TABLE doesn't reject comparable cases. + */ + ScanKeyInit(&key, + Anum_pg_inherits_inhrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(child_rel))); + scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, + true, NULL, 1, &key); + + /* inhseqno sequences start at 1 */ + inhseqno = 0; + while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan))) + { + Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple); + + if (inh->inhparent == RelationGetRelid(parent_rel)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_TABLE), + errmsg("relation \"%s\" would be inherited from more than once", + RelationGetRelationName(parent_rel)))); + + if (inh->inhseqno > inhseqno) + inhseqno = inh->inhseqno; + } + systable_endscan(scan); + /* Match up the columns and bump attinhcount as needed */ MergeAttributesIntoExisting(child_rel, parent_rel); @@ -10327,16 +10543,8 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) inhseqno + 1, catalogRelation); - ObjectAddressSet(address, RelationRelationId, - RelationGetRelid(parent_rel)); - /* Now we're done with pg_inherits */ heap_close(catalogRelation, RowExclusiveLock); - - /* keep our lock on the parent relation until commit */ - heap_close(parent_rel, NoLock); - - return address; } /* @@ -10387,7 +10595,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc) * Check columns in child table match up with columns in parent, and increment * their attinhcount. * - * Called by ATExecAddInherit + * Called by CreateInheritance * * Currently all parent columns must be found in child. Missing columns are an * error. One day we might consider creating new columns like CREATE TABLE @@ -10405,11 +10613,13 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel) int parent_natts; TupleDesc tupleDesc; HeapTuple tuple; + bool is_attach_partition; attrrel = heap_open(AttributeRelationId, RowExclusiveLock); tupleDesc = RelationGetDescr(parent_rel); parent_natts = tupleDesc->natts; + is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel)); for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++) { @@ -10432,14 +10642,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel) attribute->atttypmod != childatt->atttypmod) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("child table \"%s\" has different type for column \"%s\"", + errmsg("%s table \"%s\" has different type for column \"%s\"", + is_attach_partition ? "source" : "child", RelationGetRelationName(child_rel), attributeName))); if (attribute->attcollation != childatt->attcollation) ereport(ERROR, (errcode(ERRCODE_COLLATION_MISMATCH), - errmsg("child table \"%s\" has different collation for column \"%s\"", + errmsg("%s table \"%s\" has different collation for column \"%s\"", + is_attach_partition ? "source" : "child", RelationGetRelationName(child_rel), attributeName))); @@ -10450,8 +10662,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel) if (attribute->attnotnull && !childatt->attnotnull) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("column \"%s\" in child table must be marked NOT NULL", - attributeName))); + errmsg("column \"%s\" in %s table must be marked NOT NULL", + attributeName, + is_attach_partition ? "source" : "child"))); /* * OK, bump the child column's inheritance count. (If we fail @@ -10466,7 +10679,8 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel) { ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("child table is missing column \"%s\"", + errmsg("%s table is missing column \"%s\"", + is_attach_partition ? "source" : "child", attributeName))); } } @@ -10480,7 +10694,7 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel) * * Constraints that are marked ONLY in the parent are ignored. * - * Called by ATExecAddInherit + * Called by CreateInheritance * * Currently all constraints in parent must be present in the child. One day we * may consider adding new constraints like CREATE TABLE does. @@ -10499,9 +10713,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel) SysScanDesc parent_scan; ScanKeyData parent_key; HeapTuple parent_tuple; + bool is_attach_partition; catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock); tuple_desc = RelationGetDescr(catalog_relation); + is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel)); /* Outer loop scans through the parent's constraint definitions */ ScanKeyInit(&parent_key, @@ -10549,16 +10765,21 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel) if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("child table \"%s\" has different definition for check constraint \"%s\"", + errmsg("%s table \"%s\" has different definition for check constraint \"%s\"", + is_attach_partition ? "source" : "child", RelationGetRelationName(child_rel), NameStr(parent_con->conname)))); - /* If the constraint is "no inherit" then cannot merge */ - if (child_con->connoinherit) + /* + * If the constraint is "no inherit" then cannot merge, unless + * this child table is a partition being attached. + */ + if (child_con->connoinherit && !is_attach_partition) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), - errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"", + errmsg("constraint \"%s\" conflicts with non-inherited constraint on %s table \"%s\"", NameStr(child_con->conname), + is_attach_partition ? "source" : "child", RelationGetRelationName(child_rel)))); /* @@ -10568,6 +10789,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel) child_copy = heap_copytuple(child_tuple); child_con = (Form_pg_constraint) GETSTRUCT(child_copy); child_con->coninhcount++; + + /* If we are becoming a partition, reset the NO INHERIT flag. */ + if (is_attach_partition) + child_con->connoinherit = false; + simple_heap_update(catalog_relation, &child_copy->t_self, child_copy); CatalogUpdateIndexes(catalog_relation, child_copy); heap_freetuple(child_copy); @@ -10581,7 +10807,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel) if (!found) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("child table is missing constraint \"%s\"", + errmsg("%s table is missing constraint \"%s\"", + is_attach_partition ? "source" : "child", NameStr(parent_con->conname)))); } @@ -10592,6 +10819,46 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel) /* * ALTER TABLE NO INHERIT * + * Return value is the address of the relation that is no longer parent. + */ +static ObjectAddress +ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) +{ + ObjectAddress address; + Relation parent_rel; + + if (relid_is_partition(RelationGetRelid(rel))) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot change inheritance of a partition"))); + + /* + * AccessShareLock on the parent is probably enough, seeing that DROP + * TABLE doesn't lock parent tables at all. We need some lock since we'll + * be inspecting the parent's schema. + */ + parent_rel = heap_openrv(parent, AccessShareLock); + + /* + * We don't bother to check ownership of the parent table --- ownership of + * the child is presumed enough rights. + */ + + /* Off to RemoveInheritance() where most of the work happens */ + RemoveInheritance(rel, parent_rel); + + /* keep our lock on the parent relation until commit */ + heap_close(parent_rel, NoLock); + + ObjectAddressSet(address, RelationRelationId, + RelationGetRelid(parent_rel)); + + return address; +} + +/* + * RemoveInheritance + * * Drop a parent from the child's parents. This just adjusts the attinhcount * and attislocal of the columns and removes the pg_inherit and pg_depend * entries. @@ -10605,13 +10872,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel) * coninhcount and conislocal for inherited constraints are adjusted in * exactly the same way. * - * Return value is the address of the relation that is no longer parent. + * Common to ATExecDropInherit() and ATExecDetachPartition(). */ -static ObjectAddress -ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) +static void +RemoveInheritance(Relation child_rel, Relation parent_rel) { - Relation parent_rel; - Oid parent_oid; Relation catalogRelation; SysScanDesc scan; ScanKeyData key[3]; @@ -10620,19 +10885,9 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) constraintTuple; List *connames; bool found = false; - ObjectAddress address; + bool is_detach_partition; - /* - * AccessShareLock on the parent is probably enough, seeing that DROP - * TABLE doesn't lock parent tables at all. We need some lock since we'll - * be inspecting the parent's schema. - */ - parent_rel = heap_openrv(parent, AccessShareLock); - - /* - * We don't bother to check ownership of the parent table --- ownership of - * the child is presumed enough rights. - */ + is_detach_partition = relid_is_partitioned(RelationGetRelid(parent_rel)); /* * Find and destroy the pg_inherits entry linking the two, or error out if @@ -10642,7 +10897,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) ScanKeyInit(&key[0], Anum_pg_inherits_inhrelid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(RelationGetRelid(rel))); + ObjectIdGetDatum(RelationGetRelid(child_rel))); scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true, NULL, 1, key); @@ -10663,11 +10918,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) heap_close(catalogRelation, RowExclusiveLock); if (!found) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("relation \"%s\" is not a parent of relation \"%s\"", - RelationGetRelationName(parent_rel), - RelationGetRelationName(rel)))); + { + if (is_detach_partition) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("relation \"%s\" is not a partition of relation \"%s\"", + RelationGetRelationName(child_rel), + RelationGetRelationName(parent_rel)))); + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("relation \"%s\" is not a parent of relation \"%s\"", + RelationGetRelationName(parent_rel), + RelationGetRelationName(child_rel)))); + } /* * Search through child columns looking for ones matching parent rel @@ -10676,7 +10940,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) ScanKeyInit(&key[0], Anum_pg_attribute_attrelid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(RelationGetRelid(rel))); + ObjectIdGetDatum(RelationGetRelid(child_rel))); scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId, true, NULL, 1, key); while (HeapTupleIsValid(attributeTuple = systable_getnext(scan))) @@ -10738,7 +11002,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) ScanKeyInit(&key[0], Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(RelationGetRelid(rel))); + ObjectIdGetDatum(RelationGetRelid(child_rel))); scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId, true, NULL, 1, key); @@ -10769,7 +11033,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) if (copy_con->coninhcount <= 0) /* shouldn't happen */ elog(ERROR, "relation %u has non-inherited constraint \"%s\"", - RelationGetRelid(rel), NameStr(copy_con->conname)); + RelationGetRelid(child_rel), NameStr(copy_con->conname)); copy_con->coninhcount--; if (copy_con->coninhcount == 0) @@ -10781,30 +11045,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode) } } - parent_oid = RelationGetRelid(parent_rel); - systable_endscan(scan); heap_close(catalogRelation, RowExclusiveLock); - drop_parent_dependency(RelationGetRelid(rel), + drop_parent_dependency(RelationGetRelid(child_rel), RelationRelationId, RelationGetRelid(parent_rel)); - /* * Post alter hook of this inherits. Since object_access_hook doesn't take * multiple object identifiers, we relay oid of parent relation using * auxiliary_id argument. */ InvokeObjectPostAlterHookArg(InheritsRelationId, - RelationGetRelid(rel), 0, + RelationGetRelid(child_rel), 0, RelationGetRelid(parent_rel), false); - - /* keep our lock on the parent relation until commit */ - heap_close(parent_rel, NoLock); - - ObjectAddressSet(address, RelationRelationId, parent_oid); - - return address; } /* @@ -12434,3 +12688,190 @@ ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs, partopclass[attn++] = opclassOid; } } + + +/* + * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES + * + * Return the address of the newly attached partition. + */ +static ObjectAddress +ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd) +{ + Relation attachRel, + inheritsRelation; + SysScanDesc scan; + ScanKeyData key; + HeapTuple tuple; + AttrNumber attno; + int natts; + TupleDesc tupleDesc; + ObjectAddress address; + + attachRel = heap_openrv(cmd->name, AccessShareLock); + + /* + * Must be owner of both parent and source table -- parent was checked by + * ATSimplePermissions call in ATPrepCmd + */ + ATSimplePermissions(attachRel, ATT_TABLE); + + if (relid_is_partition(RelationGetRelid(attachRel))) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is already a partition", + RelationGetRelationName(attachRel)))); + + if (attachRel->rd_rel->reloftype) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot attach a typed table as partition"))); + + if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_REL) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot attach a partitioned table as partition"))); + + /* attachRel should not already be a inheritance child of some relation */ + inheritsRelation = heap_open(InheritsRelationId, AccessShareLock); + ScanKeyInit(&key, + Anum_pg_inherits_inhrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(attachRel))); + scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId, + true, NULL, 1, &key); + if (HeapTupleIsValid(systable_getnext(scan))) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot attach table that is a inheritance child as partition"))); + systable_endscan(scan); + heap_close(inheritsRelation, AccessShareLock); + + /* If attachRel is temp, it must belong to this session */ + if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && + !attachRel->rd_islocaltemp) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot attach a temporary relation of another session as partition "))); + + /* If parent has OIDs, attachRel must have OIDs */ + if (attachRel->rd_rel->reloftype) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot attach a typed table as partition"))); + + /* If parent has OIDs then child must have OIDs */ + if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot attach table \"%s\" without OIDs as partition of" + " table \"%s\" with OIDs", RelationGetRelationName(attachRel), + RelationGetRelationName(rel)))); + + /* OTOH, if parent doesn't have them, do not allow in attachRel either */ + if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot attach table \"%s\" with OIDs as partition of table" + " \"%s\" without OIDs", RelationGetRelationName(attachRel), + RelationGetRelationName(rel)))); + + /* Check if there are any columns in attachRel that aren't in the parent */ + tupleDesc = RelationGetDescr(attachRel); + natts = tupleDesc->natts; + for (attno = 1; attno <= natts; attno++) + { + Form_pg_attribute attribute = tupleDesc->attrs[attno - 1]; + char *attributeName = NameStr(attribute->attname); + + /* Ignore dropped */ + if (attribute->attisdropped) + continue; + + /* Find same column in parent (matching on column name). */ + tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"", + RelationGetRelationName(attachRel), attributeName, + RelationGetRelationName(rel)), + errdetail("Table being attached should contain only the columns" + " present in parent."))); + } + + /* + * Check first that the new partition's bound is valid and does not + * overlap with any of existing partitions of the parent - note that + * it does not return on error. + */ + check_new_partition_bound(RelationGetRelid(rel), cmd->bound); + + /* Store bound into the catalog */ + StorePartitionBound(RelationGetRelid(attachRel), RelationGetRelid(rel), + cmd->bound); + + /* OK to create inheritance. Rest of the checks performed there */ + CreateInheritance(attachRel, rel); + + /* + * Set up to have the rows in table to be checked for violation of the + * partition bound spec in phase 3 scan. + */ + if (!cmd->skip_validate) + { + AlteredTableInfo *tab; + PartitionCheck *check; + List *my_check, + *parent_check = RelationGetPartitionCheckQual(rel), + *check_quals; + + tab = ATGetQueueEntry(wqueue, attachRel); + check = (PartitionCheck *) palloc0(sizeof(PartitionCheck)); + my_check = get_check_qual_from_partbound(attachRel, rel, cmd->bound); + check_quals = parent_check ? list_concat(parent_check, my_check) + : my_check; + check->expr = makeBoolExpr(AND_EXPR, check_quals, -1); + tab->partitionCheck = check; + } + + ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel)); + + /* keep our lock until commit */ + heap_close(attachRel, NoLock); + + return address; +} + +/* + * ALTER TABLE DETACH PARTITION + * + * Return the address of the relation that is no longer a partition of rel. + */ +static ObjectAddress +ATExecDetachPartition(Relation rel, RangeVar *name) +{ + Relation partRel; + ObjectAddress address; + + partRel = heap_openrv(name, AccessShareLock); + + /* All inheritance related checks are performed within the function */ + RemoveInheritance(partRel, rel); + + /* Remove the pg_partition entry */ + RemovePartitionEntryByRelId(RelationGetRelid(partRel)); + + /* + * Invalidate the relcache so that the partition is no longer included + * in our partition descriptor. + */ + CacheInvalidateRelcache(rel); + + ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel)); + + /* keep our lock until commit */ + heap_close(partRel, NoLock); + + return address; +} diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 700d7be..1323bdf 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2604,6 +2604,7 @@ _copyColumnDef(const ColumnDef *from) COPY_SCALAR_FIELD(is_local); COPY_SCALAR_FIELD(is_not_null); COPY_SCALAR_FIELD(is_from_type); + COPY_SCALAR_FIELD(is_for_partition); COPY_SCALAR_FIELD(storage); COPY_NODE_FIELD(raw_default); COPY_NODE_FIELD(cooked_default); @@ -2999,6 +3000,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode) COPY_NODE_FIELD(relation); COPY_NODE_FIELD(tableElts); COPY_NODE_FIELD(inhRelations); + COPY_NODE_FIELD(partbound); COPY_NODE_FIELD(partby); COPY_NODE_FIELD(ofTypename); COPY_NODE_FIELD(constraints); @@ -4181,6 +4183,43 @@ _copyPartitionElem(const PartitionElem *from) return newnode; } +static PartitionListSpec * +_copyPartitionListSpec(const PartitionListSpec *from) +{ + PartitionListSpec *newnode = makeNode(PartitionListSpec); + + COPY_NODE_FIELD(values); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +static PartitionRangeSpec * +_copyPartitionRangeSpec(const PartitionRangeSpec *from) +{ + PartitionRangeSpec *newnode = makeNode(PartitionRangeSpec); + + COPY_SCALAR_FIELD(lowerinc); + COPY_NODE_FIELD(lower); + COPY_SCALAR_FIELD(upperinc); + COPY_NODE_FIELD(upper); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +static PartitionCmd * +_copyPartitionCmd(const PartitionCmd *from) +{ + PartitionCmd *newnode = makeNode(PartitionCmd); + + COPY_NODE_FIELD(name); + COPY_NODE_FIELD(bound); + COPY_SCALAR_FIELD(skip_validate); + + return newnode; +} + /* **************************************************************** * pg_list.h copy functions * **************************************************************** @@ -5098,6 +5137,15 @@ copyObject(const void *from) case T_PartitionElem: retval = _copyPartitionElem(from); break; + case T_PartitionListSpec: + retval = _copyPartitionListSpec(from); + break; + case T_PartitionRangeSpec: + retval = _copyPartitionRangeSpec(from); + break; + case T_PartitionCmd: + retval = _copyPartitionCmd(from); + break; /* * MISCELLANEOUS NODES diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 9554c2f..93fc8f2 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1156,6 +1156,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b) COMPARE_NODE_FIELD(relation); COMPARE_NODE_FIELD(tableElts); COMPARE_NODE_FIELD(inhRelations); + COMPARE_NODE_FIELD(partbound); COMPARE_NODE_FIELD(partby); COMPARE_NODE_FIELD(ofTypename); COMPARE_NODE_FIELD(constraints); @@ -2363,6 +2364,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b) COMPARE_SCALAR_FIELD(is_local); COMPARE_SCALAR_FIELD(is_not_null); COMPARE_SCALAR_FIELD(is_from_type); + COMPARE_SCALAR_FIELD(is_for_partition); COMPARE_SCALAR_FIELD(storage); COMPARE_NODE_FIELD(raw_default); COMPARE_NODE_FIELD(cooked_default); @@ -2641,6 +2643,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b) return true; } +static bool +_equalPartitionListSpec(const PartitionListSpec *a, const PartitionListSpec *b) +{ + COMPARE_NODE_FIELD(values); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool +_equalPartitionRangeSpec(const PartitionRangeSpec *a, const PartitionRangeSpec *b) +{ + COMPARE_SCALAR_FIELD(lowerinc); + COMPARE_NODE_FIELD(lower); + COMPARE_SCALAR_FIELD(upperinc); + COMPARE_NODE_FIELD(upper); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool +_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b) +{ + COMPARE_NODE_FIELD(name); + COMPARE_NODE_FIELD(bound); + COMPARE_SCALAR_FIELD(skip_validate); + + return true; +} + /* * Stuff from pg_list.h */ @@ -3397,6 +3430,15 @@ equal(const void *a, const void *b) case T_PartitionElem: retval = _equalPartitionElem(a, b); break; + case T_PartitionListSpec: + retval = _equalPartitionListSpec(a, b); + break; + case T_PartitionRangeSpec: + retval = _equalPartitionRangeSpec(a, b); + break; + case T_PartitionCmd: + retval = _equalPartitionCmd(a, b); + break; default: elog(ERROR, "unrecognized node type: %d", diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 4d5e37e..2f4ccb8 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2393,6 +2393,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node) WRITE_NODE_FIELD(relation); WRITE_NODE_FIELD(tableElts); WRITE_NODE_FIELD(inhRelations); + WRITE_NODE_FIELD(partbound); WRITE_NODE_FIELD(partby); WRITE_NODE_FIELD(ofTypename); WRITE_NODE_FIELD(constraints); @@ -2573,6 +2574,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node) WRITE_BOOL_FIELD(is_local); WRITE_BOOL_FIELD(is_not_null); WRITE_BOOL_FIELD(is_from_type); + WRITE_BOOL_FIELD(is_for_partition); WRITE_CHAR_FIELD(storage); WRITE_NODE_FIELD(raw_default); WRITE_NODE_FIELD(cooked_default); @@ -3288,6 +3290,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node) WRITE_LOCATION_FIELD(location); } +static void +_outPartitionListSpec(StringInfo str, const PartitionListSpec *node) +{ + WRITE_NODE_TYPE("PARTITIONLISTVALUES"); + + WRITE_NODE_FIELD(values); +} + +static void +_outPartitionRangeSpec(StringInfo str, const PartitionRangeSpec *node) +{ + WRITE_NODE_TYPE("PARTITIONRANGE"); + + WRITE_BOOL_FIELD(lowerinc); + WRITE_NODE_FIELD(lower); + WRITE_BOOL_FIELD(upperinc); + WRITE_NODE_FIELD(upper); +} + /* * outNode - * converts a Node into ascii string and append it to 'str' @@ -3874,6 +3895,12 @@ outNode(StringInfo str, const void *obj) case T_PartitionElem: _outPartitionElem(str, obj); break; + case T_PartitionListSpec: + _outPartitionListSpec(str, obj); + break; + case T_PartitionRangeSpec: + _outPartitionRangeSpec(str, obj); + break; default: diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 94954dc..c23023a 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -2247,6 +2247,35 @@ _readExtensibleNode(void) } /* + * _readPartitionListSpec + */ +static PartitionListSpec * +_readPartitionListSpec(void) +{ + READ_LOCALS(PartitionListSpec); + + READ_NODE_FIELD(values); + + READ_DONE(); +} + +/* + * _readPartitionRangeSpec + */ +static PartitionRangeSpec * +_readPartitionRangeSpec(void) +{ + READ_LOCALS(PartitionRangeSpec); + + READ_BOOL_FIELD(lowerinc); + READ_NODE_FIELD(lower); + READ_BOOL_FIELD(upperinc); + READ_NODE_FIELD(upper); + + READ_DONE(); +} + +/* * parseNodeString * * Given a character string representing a node tree, parseNodeString creates @@ -2476,6 +2505,10 @@ parseNodeString(void) return_value = _readAlternativeSubPlan(); else if (MATCH("EXTENSIBLENODE", 14)) return_value = _readExtensibleNode(); + else if (MATCH("PARTITIONLISTVALUES", 19)) + return_value = _readPartitionListSpec(); + else if (MATCH("PARTITIONRANGE", 14)) + return_value = _readPartitionRangeSpec(); else { elog(ERROR, "badly formatted node string \"%.32s\"...", token); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index e912a2d..b3595db 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -229,6 +229,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); VariableSetStmt *vsetstmt; PartitionElem *partelem; PartitionBy *partby; + PartitionRangeSpec *partrange; } %type <node> stmt schema_stmt @@ -544,6 +545,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <partby> PartitionBy OptPartitionBy %type <partelem> part_elem %type <list> part_params +%type <list> OptPartitionElementList PartitionElementList +%type <node> PartitionElement +%type <node> ForValues +%type <node> partvalue +%type <list> partvalue_list +%type <boolean> lb_inc ub_inc +%type <list> RangeBound +%type <boolean> opt_validate_spec /* * Non-keyword token types. These are hard-wired into the "flex" lexer. @@ -569,7 +578,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); /* ordinary key words in alphabetical order */ %token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC - ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION + ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT BOOLEAN_P BOTH BY @@ -585,7 +594,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC - DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP + DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P + DOUBLE_P DROP EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN @@ -599,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); HANDLER HAVING HEADER_P HOLD HOUR_P IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P - INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P + INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION @@ -2373,6 +2383,38 @@ alter_table_cmd: n->def = (Node *)$1; $$ = (Node *) n; } + /* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */ + | ATTACH PARTITION qualified_name ForValues opt_validate_spec + { + AlterTableCmd *n = makeNode(AlterTableCmd); + PartitionCmd *cmd = makeNode(PartitionCmd); + + n->subtype = AT_AttachPartition; + cmd->name = $3; + cmd->bound = (Node *) $4; + cmd->skip_validate = $5; + n->def = (Node *) cmd; + + $$ = (Node *) n; + } + /* ALTER TABLE <name> DETACH PARTITION <partition_name> */ + | DETACH PARTITION qualified_name + { + AlterTableCmd *n = makeNode(AlterTableCmd); + PartitionCmd *cmd = makeNode(PartitionCmd); + + n->subtype = AT_DetachPartition; + cmd->name = $3; + n->def = (Node *) cmd; + + $$ = (Node *) n; + } + ; + +opt_validate_spec: + NO VALIDATE { $$ = true; } + | VALIDATE { $$ = false; } + | /* EMPTY */ { $$ = false; } ; alter_column_default: @@ -2468,6 +2510,60 @@ reloption_elem: } ; +ForValues: + /* a LIST partition */ + FOR VALUES IN_P '(' partvalue_list ')' + { + PartitionListSpec *n = makeNode(PartitionListSpec); + + n->values = $5; + n->location = @1; + + $$ = (Node *) n; + } + + /* a RANGE partition */ + | FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc + { + PartitionRangeSpec *n = makeNode(PartitionRangeSpec); + + n->lowerinc = $5; + n->lower = $4; + n->upperinc = $8; + n->upper = $7; + n->location = @1; + + $$ = (Node *) n; + } + ; + +RangeBound: + UNBOUNDED { $$ = NIL; } + | '(' partvalue_list ')' { $$ = $2; } + ; + +lb_inc: + EXCLUSIVE { $$ = false;} + | INCLUSIVE { $$ = true; } + | /* EMPTY */ { $$ = true; } + ; + +ub_inc: + INCLUSIVE { $$ = true; } + | EXCLUSIVE { $$ = false; } + | /* EMPTY */ { $$ = false; } + ; + +partvalue: + Sconst { $$ = makeStringConst($1, @1); } + | NumericOnly { $$ = makeAConst($1, @1); } + | NULL_P { $$ = makeNullAConst(@1); } + ; + +partvalue_list: + partvalue { $$ = list_make1($1); } + | partvalue_list ',' partvalue { $$ = lappend($1, $3); } + ; /***************************************************************************** * @@ -2885,6 +2981,44 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')' n->if_not_exists = true; $$ = (Node *)n; } + | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name + OptPartitionElementList ForValues OptPartitionBy OptWith + OnCommitOption OptTableSpace + { + CreateStmt *n = makeNode(CreateStmt); + $4->relpersistence = $2; + n->relation = $4; + n->tableElts = $8; + n->inhRelations = list_make1($7); + n->partbound = (Node *) $9; + n->partby = $10; + n->ofTypename = NULL; + n->constraints = NIL; + n->options = $11; + n->oncommit = $12; + n->tablespacename = $13; + n->if_not_exists = false; + $$ = (Node *)n; + } + | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF + qualified_name OptPartitionElementList ForValues OptPartitionBy + OptWith OnCommitOption OptTableSpace + { + CreateStmt *n = makeNode(CreateStmt); + $7->relpersistence = $2; + n->relation = $7; + n->tableElts = $11; + n->inhRelations = list_make1($10); + n->partbound = (Node *) $12; + n->partby = $13; + n->ofTypename = NULL; + n->constraints = NIL; + n->options = $14; + n->oncommit = $15; + n->tablespacename = $16; + n->if_not_exists = true; + $$ = (Node *)n; + } ; /* @@ -2930,6 +3064,11 @@ OptTypedTableElementList: | /*EMPTY*/ { $$ = NIL; } ; +OptPartitionElementList: + '(' PartitionElementList ')' { $$ = $2; } + | /*EMPTY*/ { $$ = NIL; } + ; + TableElementList: TableElement { @@ -2952,6 +3091,17 @@ TypedTableElementList: } ; +PartitionElementList: + PartitionElement + { + $$ = list_make1($1); + } + | PartitionElementList ',' PartitionElement + { + $$ = lappend($1, $3); + } + ; + TableElement: columnDef { $$ = $1; } | TableLikeClause { $$ = $1; } @@ -2963,6 +3113,11 @@ TypedTableElement: | TableConstraint { $$ = $1; } ; +PartitionElement: + columnOptions { $$ = $1; } + | TableConstraint { $$ = $1; } + ; + columnDef: ColId Typename create_generic_options ColQualList { ColumnDef *n = makeNode(ColumnDef); @@ -2972,6 +3127,7 @@ columnDef: ColId Typename create_generic_options ColQualList n->is_local = true; n->is_not_null = false; n->is_from_type = false; + n->is_for_partition = false; n->storage = 0; n->raw_default = NULL; n->cooked_default = NULL; @@ -2993,6 +3149,7 @@ columnOptions: ColId WITH OPTIONS ColQualList n->is_local = true; n->is_not_null = false; n->is_from_type = false; + n->is_for_partition = false; n->storage = 0; n->raw_default = NULL; n->cooked_default = NULL; @@ -4544,6 +4701,48 @@ CreateForeignTableStmt: n->options = $14; $$ = (Node *) n; } + | CREATE FOREIGN TABLE qualified_name + PARTITION OF qualified_name OptPartitionElementList ForValues + SERVER name create_generic_options + { + CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt); + $4->relpersistence = RELPERSISTENCE_PERMANENT; + n->base.relation = $4; + n->base.inhRelations = list_make1($7); + n->base.tableElts = $8; + n->base.partbound = (Node *) $9; + n->base.ofTypename = NULL; + n->base.constraints = NIL; + n->base.options = NIL; + n->base.oncommit = ONCOMMIT_NOOP; + n->base.tablespacename = NULL; + n->base.if_not_exists = false; + /* FDW-specific data */ + n->servername = $11; + n->options = $12; + $$ = (Node *) n; + } + | CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name + PARTITION OF qualified_name OptPartitionElementList ForValues + SERVER name create_generic_options + { + CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt); + $7->relpersistence = RELPERSISTENCE_PERMANENT; + n->base.relation = $7; + n->base.inhRelations = list_make1($10); + n->base.tableElts = $11; + n->base.partbound = (Node *) $12; + n->base.ofTypename = NULL; + n->base.constraints = NIL; + n->base.options = NIL; + n->base.oncommit = ONCOMMIT_NOOP; + n->base.tablespacename = NULL; + n->base.if_not_exists = true; + /* FDW-specific data */ + n->servername = $14; + n->options = $15; + $$ = (Node *) n; + } ; /***************************************************************************** @@ -11133,6 +11332,7 @@ TableFuncElement: ColId Typename opt_collate_clause n->is_local = true; n->is_not_null = false; n->is_from_type = false; + n->is_for_partition = false; n->storage = 0; n->raw_default = NULL; n->cooked_default = NULL; @@ -13812,6 +14012,7 @@ unreserved_keyword: | ASSERTION | ASSIGNMENT | AT + | ATTACH | ATTRIBUTE | BACKWARD | BEFORE @@ -13858,6 +14059,7 @@ unreserved_keyword: | DELIMITER | DELIMITERS | DEPENDS + | DETACH | DICTIONARY | DISABLE_P | DISCARD @@ -13900,6 +14102,7 @@ unreserved_keyword: | IMPLICIT_P | IMPORT_P | INCLUDING + | INCLUSIVE | INCREMENT | INDEX | INDEXES diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 3e8d457..37eb9d3 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -508,7 +508,10 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) err = _("grouping operations are not allowed in partition key expression"); break; + case EXPR_KIND_PARTITION_FOR_VALUES: + errkind = true; + break; /* * There is intentionally no default: case here, so that the @@ -869,6 +872,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, case EXPR_KIND_PARTITION_KEY: err = _("window functions are not allowed in partition key expression"); break; + case EXPR_KIND_PARTITION_FOR_VALUES: + errkind = true; + break; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index a97f727..cb0579d 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -355,6 +355,26 @@ transformExprRecurse(ParseState *pstate, Node *expr) result = (Node *) expr; break; } + case T_PartitionListSpec: + { + PartitionListSpec *list = (PartitionListSpec *) expr; + + list->values = transformExpressionList(pstate, list->values, + pstate->p_expr_kind); + result = expr; + break; + } + case T_PartitionRangeSpec: + { + PartitionRangeSpec *range = (PartitionRangeSpec *) expr; + + range->lower = transformExpressionList(pstate, range->lower, + pstate->p_expr_kind); + range->upper = transformExpressionList(pstate, range->upper, + pstate->p_expr_kind); + result = expr; + break; + } default: /* should not reach here */ @@ -1723,6 +1743,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink) case EXPR_KIND_OFFSET: case EXPR_KIND_RETURNING: case EXPR_KIND_VALUES: + case EXPR_KIND_PARTITION_FOR_VALUES: /* okay */ break; case EXPR_KIND_CHECK_CONSTRAINT: @@ -3302,6 +3323,8 @@ ParseExprKindName(ParseExprKind exprKind) return "WHEN"; case EXPR_KIND_PARTITION_KEY: return "partition key expression"; + case EXPR_KIND_PARTITION_FOR_VALUES: + return "FOR VALUES"; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index be21a8e..578ae2f 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -49,8 +49,10 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "optimizer/planner.h" #include "parser/analyze.h" #include "parser/parse_clause.h" +#include "parser/parse_coerce.h" #include "parser/parse_collate.h" #include "parser/parse_expr.h" #include "parser/parse_relation.h" @@ -90,6 +92,7 @@ typedef struct * the table */ IndexStmt *pkey; /* PRIMARY KEY index, if any */ bool ispartitioned; /* true if table is partitioned */ + Node *partbound; /* transformed FOR VALUES */ } CreateStmtContext; /* State shared by transformCreateSchemaStmt and its subroutines */ @@ -132,6 +135,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt, List *constraintList); static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column); static void setSchemaName(char *context_schema, char **stmt_schema_name); +static void transformPartitionOf(CreateStmtContext *cxt, Node *bound); +static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd); +static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent, + Node *bound); /* @@ -233,6 +240,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) cxt.alist = NIL; cxt.pkey = NULL; cxt.ispartitioned = stmt->partby != NULL; + cxt.partbound = NULL; /* * Notice that we allow OIDs here only for plain tables, even though @@ -251,11 +259,14 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) if (stmt->ofTypename) transformOfType(&cxt, stmt->ofTypename); + if (stmt->partbound) + transformPartitionOf(&cxt, stmt->partbound); + if (stmt->partby) { int partnatts = list_length(stmt->partby->partParams); - if (stmt->inhRelations) + if (stmt->inhRelations && !stmt->partbound) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("cannot create partitioned table as inheritance child"))); @@ -361,6 +372,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) */ stmt->tableElts = cxt.columns; stmt->constraints = cxt.ckconstraints; + stmt->partbound = cxt.partbound; result = lappend(cxt.blist, stmt); result = list_concat(result, cxt.alist); @@ -900,6 +912,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla def->is_local = true; def->is_not_null = attribute->attnotnull; def->is_from_type = false; + def->is_for_partition = false; def->storage = 0; def->raw_default = NULL; def->cooked_default = NULL; @@ -1119,6 +1132,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename) n->is_local = true; n->is_not_null = false; n->is_from_type = true; + n->is_for_partition = false; n->storage = 0; n->raw_default = NULL; n->cooked_default = NULL; @@ -2588,6 +2602,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, cxt.alist = NIL; cxt.pkey = NULL; cxt.ispartitioned = relid_is_partitioned(relid); + cxt.partbound = NULL; /* * The only subtypes that currently require parse transformation handling @@ -2676,6 +2691,22 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, break; } + case AT_AttachPartition: + { + PartitionCmd *partcmd = (PartitionCmd *) cmd->def; + + transformAttachPartition(&cxt, partcmd); + + /* assign transformed values */ + partcmd->bound = cxt.partbound; + } + + newcmds = lappend(newcmds, cmd); + break; + case AT_DetachPartition: + newcmds = lappend(newcmds, cmd); + break; + default: newcmds = lappend(newcmds, cmd); break; @@ -3040,3 +3071,304 @@ setSchemaName(char *context_schema, char **stmt_schema_name) "different from the one being created (%s)", *stmt_schema_name, context_schema))); } + +/* + * transformPartitionOf + * Analyze PARTITION OF ... FOR VALUES ... + */ +static void +transformPartitionOf(CreateStmtContext *cxt, Node *bound) +{ + TupleDesc tupdesc; + int i; + RangeVar *part = cxt->relation; + RangeVar *partof = linitial(cxt->inhRelations); + Relation parentRel; + + parentRel = heap_openrv(partof, AccessShareLock); + + /* Check if the target table is partitioned at all */ + if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("\"%s\" is not partitioned", + RelationGetRelationName(parentRel)))); + Assert(RelationGetPartitionKey(parentRel) != NULL); + + /* Permanent rels cannot be partitions of temporary ones */ + if (part->relpersistence != RELPERSISTENCE_TEMP && + parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot create as partition of temporary relation \"%s\"", + partof->relname))); + + /* If parent rel is temp, it must belong to this session */ + if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP && + !parentRel->rd_islocaltemp) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot create as partition of temporary relation of another session"))); + + /* + * Do not allow OIDs in a partition, if not present in the parent. But + * force them in partition, if they are present in the parent. + */ + if (cxt->hasoids && !parentRel->rd_rel->relhasoids) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot create table with OIDs as partition of table without OIDs"))); + + if (parentRel->rd_rel->relhasoids) + cxt->hasoids = true; + + tupdesc = RelationGetDescr(parentRel); + for (i = 0; i < tupdesc->natts; i++) + { + Form_pg_attribute attribute = tupdesc->attrs[i]; + ColumnDef *def; + + if (attribute->attisdropped) + continue; + + def = makeNode(ColumnDef); + def->colname = pstrdup(NameStr(attribute->attname)); + def->typeName = makeTypeNameFromOid(attribute->atttypid, + attribute->atttypmod); + def->inhcount = 1; + def->is_local = false; + def->is_not_null = attribute->attnotnull; + def->is_from_type = false; + def->is_for_partition = true; + def->storage = attribute->attstorage; + def->raw_default = NULL; + def->cooked_default = NULL; + def->collClause = NULL; + def->collOid = attribute->attcollation; + def->constraints = NIL; + def->location = -1; + + cxt->columns = lappend(cxt->columns, def); + } + + /* tranform the values */ + cxt->partbound = transformPartitionBound(cxt, parentRel, bound); + + heap_close(parentRel, AccessShareLock); +} + +/* + * transformAttachPartition + * Analyze ATTACH PARTITION ... FOR VALUES ... + */ +static void +transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd) +{ + Relation parentRel = cxt->rel; + + /* Check if the target table is partitioned at all */ + if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("\"%s\" is not partitioned", cxt->relation->relname))); + Assert(RelationGetPartitionKey(parentRel) != NULL); + + /* tranform the values */ + cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound); +} + +/* + * transformPartitionBound + * + * Transform partition bound per the partition key + */ +static Node * +transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound) +{ + int i; + ListCell *cell; + Oid parentId = RelationGetRelid(parent); + PartitionKey key = RelationGetPartitionKey(parent); + char strategy = get_partition_key_strategy(key); + int partnatts = get_partition_key_natts(key); + PartitionListSpec *list, *result_list; + PartitionRangeSpec *range, *result_range; + + switch (strategy) + { + case PARTITION_STRAT_LIST: + if (!IsA(bound, PartitionListSpec)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("invalid bound specification for a list partition"), + parser_errposition(cxt->pstate, exprLocation(bound)))); + + list = (PartitionListSpec *) transformExpr(cxt->pstate, bound, + EXPR_KIND_PARTITION_FOR_VALUES); + + result_list = makeNode(PartitionListSpec); + + foreach(cell, list->values) + { + Node *value = (Node *) lfirst(cell); + Node *orig_value = value; + Oid valuetype; + + valuetype = exprType(value); + value = coerce_to_target_type(cxt->pstate, + value, valuetype, + get_partition_col_typid(key, 0), + get_partition_col_typmod(key, 0), + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (value == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("specified value cannot be cast to type \"%s\"" + " of key column \"%s\"", + format_type_be(get_partition_col_typid(key, 0)), + get_relid_attribute_name(parentId, + get_partition_col_attnum(key, 0))), + parser_errposition(cxt->pstate, exprLocation(orig_value)))); + + /* Simplify the expression */ + value = (Node *) expression_planner((Expr *) value); + + result_list->values = lappend(result_list->values, value); + } + return (Node *) result_list; + + case PARTITION_STRAT_RANGE: + if (!IsA(bound, PartitionRangeSpec)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("invalid bound specification for a range partition"), + parser_errposition(cxt->pstate, exprLocation(bound)))); + + range = (PartitionRangeSpec *) transformExpr(cxt->pstate, bound, + EXPR_KIND_PARTITION_FOR_VALUES); + + if (!range->lower && !range->upper) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("both START and END cannot be UNBOUNDED"), + parser_errposition(cxt->pstate, range->location))); + + result_range = makeNode(PartitionRangeSpec); + result_range->lowerinc = range->lowerinc; + result_range->upperinc = range->upperinc; + + if (range->lower && list_length(range->lower) > partnatts) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("START has more values specified than number" + " of columns in the partition key"), + parser_errposition(cxt->pstate, + exprLocation(list_nth(range->lower, + list_length(range->lower) - 1))))); + else if (range->lower && list_length(range->lower) < partnatts) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("START has fewer values specified than number" + " of columns in the partition key"), + parser_errposition(cxt->pstate, + exprLocation(list_nth(range->lower, + list_length(range->lower) - 1))))); + + if (range->upper && list_length(range->upper) > partnatts) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("END has more values specified than number" + " of columns in the partition key"), + parser_errposition(cxt->pstate, + exprLocation(list_nth(range->upper, + list_length(range->upper) - 1))))); + else if (range->upper && list_length(range->upper) < partnatts) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("END has fewer values specified than number" + " of columns in the partition key"), + parser_errposition(cxt->pstate, + exprLocation(list_nth(range->upper, + list_length(range->upper) - 1))))); + + if (range->lower) + { + i = 0; + foreach (cell, range->lower) + { + Node *value = (Node *) lfirst(cell); + Node *orig_value = value; + Oid valuetype; + + valuetype = exprType(value); + value = coerce_to_target_type(cxt->pstate, + value, valuetype, + get_partition_col_typid(key, i), + get_partition_col_typmod(key, i), + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (value == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("specified value cannot be cast to type" + " \"%s\" of key column \"%s\"", + format_type_be(get_partition_col_typid(key, i)), + get_relid_attribute_name(parentId, + get_partition_col_attnum(key, i))), + parser_errposition(cxt->pstate, exprLocation(orig_value)))); + + /* Simplify the expression */ + value = (Node *) expression_planner((Expr *) value); + + result_range->lower = lappend(result_range->lower, value); + ++i; + } + } + else + result_range->lowerinc = false; + + if (range->upper) + { + i = 0; + foreach (cell, range->upper) + { + Node *value = (Node *) lfirst(cell); + Node *orig_value = value; + Oid valuetype; + + valuetype = exprType(value); + value = coerce_to_target_type(cxt->pstate, + value, valuetype, + get_partition_col_typid(key, i), + get_partition_col_typmod(key, i), + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (value == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("specified value cannot be cast to type" + " \"%s\" of key column \"%s\"", + format_type_be(get_partition_col_typid(key, i)), + get_relid_attribute_name(parentId, + get_partition_col_attnum(key, i))), + parser_errposition(cxt->pstate, exprLocation(orig_value)))); + + /* Simplify the expression */ + value = (Node *) expression_planner((Expr *) value); + + result_range->upper = lappend(result_range->upper, value); + ++i; + } + } + else + result_range->upperinc = false; + + return (Node *) result_range; + } + + return NULL; +} diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 3787441..180f8fd 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -279,6 +279,8 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid, StrategyNumber numSupport); static void RelationCacheInitFileRemoveInDir(const char *tblspcpath); static void unlink_initfile(const char *initfilename); +static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1, + PartitionDesc pdesc2); /* @@ -928,6 +930,37 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2) } /* + * equalPartitionDescs + * Compare two partition descriptors for logical equality + */ +static bool +equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1, + PartitionDesc pdesc2) +{ + int i; + + if (pdesc1 != NULL) + { + if (pdesc2 == NULL) + return false; + if (pdesc1->nparts != pdesc2->nparts) + return false; + + Assert(key != NULL || pdesc1->nparts == 0); + + for (i = 0; i < pdesc1->nparts; i++) + { + if (!partition_equal(key, pdesc1->parts[i], pdesc2->parts[i])) + return false; + } + } + else if (pdesc2 != NULL) + return false; + + return true; +} + +/* * RelationBuildDesc * * Build a relation descriptor. The caller must hold at least @@ -1055,11 +1088,18 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) relation->rd_fkeylist = NIL; relation->rd_fkeyvalid = false; - /* if it's a partitioned table, initialize key info */ + /* if a partitioned table, initialize key and partition descriptor info */ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL) + { RelationBuildPartitionKey(relation); + RelationBuildPartitionDesc(relation); + } else + { relation->rd_partkey = NULL; + relation->rd_partdesc = NULL; + relation->rd_pdcxt = NULL; + } /* * if it's an index, initialize index-related information @@ -2059,6 +2099,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc) if (relation->rd_rsdesc) MemoryContextDelete(relation->rd_rsdesc->rscxt); FreePartitionKey(relation->rd_partkey); + if (relation->rd_pdcxt) + MemoryContextDelete(relation->rd_pdcxt); + if (relation->rd_partcheck) + pfree(relation->rd_partcheck); if (relation->rd_fdwroutine) pfree(relation->rd_fdwroutine); pfree(relation); @@ -2222,6 +2266,7 @@ RelationClearRelation(Relation relation, bool rebuild) bool keep_tupdesc; bool keep_rules; bool keep_policies; + bool keep_partdesc; /* Build temporary entry, but don't link it into hashtable */ newrel = RelationBuildDesc(save_relid, false); @@ -2252,6 +2297,9 @@ RelationClearRelation(Relation relation, bool rebuild) keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att); keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules); keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc); + keep_partdesc = equalPartitionDescs(relation->rd_partkey, + relation->rd_partdesc, + newrel->rd_partdesc); /* * Perform swapping of the relcache entry contents. Within this @@ -2307,6 +2355,13 @@ RelationClearRelation(Relation relation, bool rebuild) /* pgstat_info must be preserved */ SWAPFIELD(struct PgStat_TableStatus *, pgstat_info); + /* preserve old partdesc if no logical change */ + if (keep_partdesc) + { + SWAPFIELD(PartitionDesc, rd_partdesc); + SWAPFIELD(MemoryContext, rd_pdcxt); + } + #undef SWAPFIELD /* And now we can throw away the temporary entry */ @@ -3533,6 +3588,20 @@ RelationCacheInitializePhase3(void) restart = true; } + /* + * Reload partition key and descriptor for a partitioned table. + */ + if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL) + { + RelationBuildPartitionKey(relation); + Assert(relation->rd_partkey != NULL); + + RelationBuildPartitionDesc(relation); + Assert(relation->rd_partdesc != NULL); + + restart = true; + } + /* Release hold on the relation */ RelationDecrementReferenceCount(relation); @@ -5057,6 +5126,8 @@ load_relcache_init_file(bool shared) rel->trigdesc = NULL; rel->rd_rsdesc = NULL; rel->rd_partkey = NULL; + rel->rd_partdesc = NULL; + rel->rd_partcheck = NIL; rel->rd_indexprs = NIL; rel->rd_indpred = NIL; rel->rd_exclops = NULL; diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index a6563fe..4a5783a 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -49,6 +49,7 @@ #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" #include "catalog/pg_partitioned.h" +#include "catalog/pg_partition.h" #include "catalog/pg_proc.h" #include "catalog/pg_range.h" #include "catalog/pg_rewrite.h" @@ -569,6 +570,17 @@ static const struct cachedesc cacheinfo[] = { }, 8 }, + {PartitionRelationId, /* PARTRELID */ + PartitionRelidIndexId, + 1, + { + Anum_pg_partition_partrelid, + 0, + 0, + 0 + }, + 128 + }, {PartitionedRelationId, /* PARTEDRELID */ PartitionedRelidIndexId, 1, diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index cadb741..73786c0 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -322,6 +322,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops)); #define PartitionedRelidIndexId 3351 +DECLARE_UNIQUE_INDEX(pg_partition_partrelid_index, 3354, on pg_partition using btree(partrelid oid_ops)); +#define PartitionRelidIndexId 3354 + /* 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 index acff099..db59c9a 100644 --- a/src/include/catalog/partition.h +++ b/src/include/catalog/partition.h @@ -14,10 +14,23 @@ #define PARTITION_H #include "fmgr.h" +#include "parser/parse_node.h" #include "utils/relcache.h" typedef struct PartitionKeyData *PartitionKey; +/* + * Information about partitions of a partitioned table. + */ +typedef struct PartitionInfoData *PartitionInfo; +typedef struct PartitionDescData +{ + int nparts; /* Number of partitions */ + PartitionInfo *parts; /* Array of PartitionInfoData pointers */ +} PartitionDescData; + +typedef struct PartitionDescData *PartitionDesc; + /* relcache support for partition key information */ extern void RelationBuildPartitionKey(Relation relation); extern void FreePartitionKey(PartitionKey key); @@ -32,4 +45,15 @@ 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); +/* relcache support functions for partition descriptor */ +extern void RelationBuildPartitionDesc(Relation relation); +extern bool partition_equal(PartitionKey key, PartitionInfo p1, + PartitionInfo p2); + +/* For commands/tablecmds.c's and catalog/heap.c's perusal */ +extern void check_new_partition_bound(Oid parentId, Node *bound); +extern Oid get_partition_parent(Oid relid); +extern List *get_check_qual_from_partbound(Relation rel, Relation parent, + Node *bound); +extern List *RelationGetPartitionCheckQual(Relation rel); #endif /* PARTITION_H */ diff --git a/src/include/catalog/pg_partition.h b/src/include/catalog/pg_partition.h new file mode 100644 index 0000000..d31a6b7 --- /dev/null +++ b/src/include/catalog/pg_partition.h @@ -0,0 +1,54 @@ +/*------------------------------------------------------------------------- + * + * pg_partition.h + * definition of the system "partition" relation (pg_partition) + * along with the relation's initial contents. + * + * + * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group + * + * $PostgreSQL: pgsql/src/include/catalog/pg_partition.h $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_PARTITION_H +#define PG_PARTITION_H + +#include "catalog/genbki.h" + +/* ---------------- + * pg_partitioned_rel definition. cpp turns this into + * typedef struct FormData_pg_partitioned_rel + * ---------------- + */ +#define PartitionRelationId 3353 + +CATALOG(pg_partition,3353) BKI_WITHOUT_OIDS +{ + Oid partrelid; /* partition relation OID */ + +#ifdef CATALOG_VARLEN /* variable-length fields start here */ + pg_node_tree partbound; /* node representation of partition bound */ +#endif +} FormData_pg_partition; + +/* ---------------- + * Form_pg_partition corresponds to a pointer to a tuple with + * the format of pg_partition relation. + * ---------------- + */ +typedef FormData_pg_partition *Form_pg_partition; + +/* ---------------- + * compiler constants for pg_partition + * ---------------- + */ +#define Natts_pg_partition 2 +#define Anum_pg_partition_partrelid 1 +#define Anum_pg_partition_partbound 2 + +#endif /* PG_PARTITION_H */ diff --git a/src/include/catalog/pg_partition_fn.h b/src/include/catalog/pg_partition_fn.h new file mode 100644 index 0000000..dccb774 --- /dev/null +++ b/src/include/catalog/pg_partition_fn.h @@ -0,0 +1,22 @@ +/*------------------------------------------------------------------------- + * + * pg_partition_fn.h + * prototypes for functions in catalog/pg_partition.c + * + * + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_partition_fn.h + * + *------------------------------------------------------------------------- + */ +#ifndef PG_PARTITION_FN_H +#define PG_PARTITION_FN_H + +/* pg_partition catalog functions */ +extern void StorePartitionBound(Oid relid, Oid parentId, Node *bound); +extern void RemovePartitionEntryByRelId(Oid relid); +extern bool relid_is_partition(Oid relid); + +#endif /* PG_PARTITION_FN_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index c316af6..365073b 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -405,6 +405,7 @@ typedef enum NodeTag T_AlterPolicyStmt, T_CreateTransformStmt, T_CreateAmStmt, + T_PartitionCmd, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) @@ -454,6 +455,8 @@ typedef enum NodeTag T_RoleSpec, T_PartitionElem, T_PartitionBy, + T_PartitionListSpec, + T_PartitionRangeSpec, /* * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 2b77579..e044aa7 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -592,6 +592,7 @@ typedef struct ColumnDef bool is_local; /* column has local (non-inherited) def'n */ bool is_not_null; /* NOT NULL constraint specified? */ bool is_from_type; /* column definition came from table type */ + bool is_for_partition; /* column definition is for a partition */ char storage; /* attstorage setting, or 0 for default */ Node *raw_default; /* default value (untransformed parse tree) */ Node *cooked_default; /* default value (transformed expr tree) */ @@ -732,6 +733,40 @@ typedef struct PartitionBy int location; /* token location, or -1 if unknown */ } PartitionBy; +/* + * PartitionListSpec - a list partition bound + */ +typedef struct PartitionListSpec +{ + NodeTag type; + List *values; + int location; +} PartitionListSpec; + +/* + * PartitionRangeSpec - a range partition bound + */ +typedef struct PartitionRangeSpec +{ + NodeTag type; + bool lowerinc; + List *lower; + bool upperinc; + List *upper; + int location; /* token location, or -1 if unknown */ +} PartitionRangeSpec; + +/* + * PartitionCmd - ALTER TABLE partition commands + */ +typedef struct PartitionCmd +{ + NodeTag type; + RangeVar *name; + Node *bound; + bool skip_validate; +} PartitionCmd; + /**************************************************************************** * Nodes for a Query tree ****************************************************************************/ @@ -1559,7 +1594,9 @@ typedef enum AlterTableType AT_DisableRowSecurity, /* DISABLE ROW SECURITY */ AT_ForceRowSecurity, /* FORCE ROW SECURITY */ AT_NoForceRowSecurity, /* NO FORCE ROW SECURITY */ - AT_GenericOptions /* OPTIONS (...) */ + AT_GenericOptions, /* OPTIONS (...) */ + AT_AttachPartition, /* ATTACH PARTITION */ + AT_DetachPartition /* DETACH PARTITION */ } AlterTableType; typedef struct ReplicaIdentityStmt @@ -1784,7 +1821,9 @@ typedef struct CreateStmt RangeVar *relation; /* relation to create */ List *tableElts; /* column definitions (list of ColumnDef) */ List *inhRelations; /* relations to inherit from (list of - * inhRelation) */ + * inhRelation); (ab)used also as partition + * parent */ + Node *partbound; /* FOR VALUES clause */ PartitionBy *partby; /* PARTITION BY clause */ TypeName *ofTypename; /* OF typename */ List *constraints; /* constraints (list of Constraint nodes) */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 40da67a..70c264c 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD) PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD) PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD) PG_KEYWORD("at", AT, UNRESERVED_KEYWORD) +PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD) PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD) PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD) @@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD) PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD) PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD) PG_KEYWORD("desc", DESC, RESERVED_KEYWORD) +PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD) PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD) PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD) PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD) @@ -191,6 +193,7 @@ PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD) PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD) PG_KEYWORD("in", IN_P, RESERVED_KEYWORD) PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD) +PG_KEYWORD("inclusive", INCLUSIVE, UNRESERVED_KEYWORD) PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD) PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD) PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD) diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index a13c6fb..3d45663 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -65,7 +65,8 @@ typedef enum ParseExprKind EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */ EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */ - EXPR_KIND_PARTITION_KEY /* partition key expression */ + EXPR_KIND_PARTITION_KEY, /* partition key expression */ + EXPR_KIND_PARTITION_FOR_VALUES /* partition bound */ } ParseExprKind; diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index e05d122..7121390 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -95,6 +95,9 @@ typedef struct RelationData bool rd_fkeyvalid; /* true if list has been computed */ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */ + MemoryContext rd_pdcxt; /* private context for partdesc */ + struct PartitionDescData *rd_partdesc; /* partitions, or NULL */ + List *rd_partcheck; /* partition CHECK quals */ /* data managed by RelationGetIndexList: */ List *rd_indexlist; /* list of OIDs of indexes on relation */ @@ -540,6 +543,12 @@ typedef struct ViewOptions */ #define RelationGetPartitionKey(relation) ((relation)->rd_partkey) +/* + * RelationGetPartitionDesc + * Returns partition descriptor for a relation. + */ +#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc) + /* routines in utils/cache/relcache.c */ extern void RelationIncrementReferenceCount(Relation rel); extern void RelationDecrementReferenceCount(Relation rel); diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h index e727842..4e493e0 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -72,6 +72,7 @@ enum SysCacheIdentifier OPEROID, OPFAMILYAMNAMENSP, OPFAMILYOID, + PARTRELID, PARTEDRELID, PROCNAMEARGSNSP, PROCOID, diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index cb105af..5598809 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -2958,5 +2958,186 @@ CREATE TABLE no_inh_child ( CREATE TABLE inh_parent(a int); ALTER TABLE no_inh_child INHERIT inh_parent; ERROR: cannot change inheritance of partitioned table -DROP TABLE no_inh_child, inh_parent, partitioned; -ERROR: table "partitioned" does not exist +DROP TABLE no_inh_child, inh_parent; +-- ATTACH PARTITION +-- check target table partitioned +CREATE TABLE unparted ( + a int +); +CREATE TABLE fail_part (like unparted); +ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a'); +ERROR: "unparted" is not partitioned +DROP TABLE unparted, fail_part; +-- check partition bounds compatible +CREATE TABLE list_parted ( + a int, + b char(2) NOT NULL COLLATE "en_US", + CONSTRAINT check_a CHECK (a > 0) +) PARTITION BY LIST (a); +CREATE TABLE fail_part (LIKE list_parted); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10); +ERROR: invalid bound specification for a list partition +DROP TABLE fail_part; +-- check the table being attached exists +ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1); +ERROR: relation "nonexistant" does not exist +-- XXX add ownership tests +-- check the table being attached is not itself partitioned +CREATE TABLE another_parted ( + a int +) PARTITION BY RANGE (a); +ALTER TABLE list_parted ATTACH PARTITION another_parted FOR VALUES IN (1); +ERROR: cannot attach a partitioned table as partition +DROP TABLE another_parted; +-- check the table being attached is not inheritance child of some relation +CREATE TABLE parent (LIKE list_parted); +CREATE TABLE fail_part () INHERITS (parent); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: cannot attach table that is a inheritance child as partition +DROP TABLE parent CASCADE; +NOTICE: drop cascades to table fail_part +-- check the table being attached is not a typed table +CREATE TYPE mytype AS (a int); +CREATE TABLE fail_part OF mytype; +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: cannot attach a typed table as partition +DROP TYPE mytype CASCADE; +NOTICE: drop cascades to table fail_part +-- check the existence (or non-existence) of oid column +ALTER TABLE list_parted SET WITH OIDS; +CREATE TABLE fail_part (a int); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: cannot attach table "fail_part" without OIDs as partition of table "list_parted" with OIDs +ALTER TABLE list_parted SET WITHOUT OIDS; +ALTER TABLE fail_part SET WITH OIDS; +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: cannot attach table "fail_part" with OIDs as partition of table "list_parted" without OIDs +DROP TABLE fail_part; +-- check the table being attached does not have columns not in the parent +CREATE TABLE fail_part (like list_parted, c int); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: table "fail_part" contains column "c" not found in parent "list_parted" +DETAIL: Table being attached should contain only the columns present in parent. +DROP TABLE fail_part; +-- check the table being attached has all columns of the parent +CREATE TABLE fail_part (a int); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: source table is missing column "b" +DROP TABLE fail_part; +-- check the columns of the table being attached match in type, collation and NOT NULL status +CREATE TABLE fail_part ( + a int, + b int +); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: source table "fail_part" has different type for column "b" +ALTER TABLE fail_part ALTER b TYPE char (3); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: source table "fail_part" has different type for column "b" +ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA"; +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: source table "fail_part" has different collation for column "b" +DROP TABLE fail_part; +-- check the table being attached all constraints of the parent +CREATE TABLE fail_part ( + a int, + b char(2) NOT NULL COLLATE "en_US" +); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: source table is missing constraint "check_a" +-- check the constraint of table being attached matches in definition with parent's constraint +ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: source table "fail_part" has different definition for check constraint "check_a" +DROP TABLE fail_part; +-- finally check the attincount and coninhcount after successfully attached +CREATE TABLE part_1 ( + a int, + b char(2) NOT NULL COLLATE "en_US", + CONSTRAINT check_a CHECK (a > 0) NO INHERIT +); +ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1); +SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0; + attinhcount +------------- + 1 + 1 +(2 rows) + +SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a'; + coninhcount +------------- + 1 +(1 row) + +-- also check connoinherit on the partition's constraint, it must have been turned off +SELECT connoinherit FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a'; + connoinherit +-------------- + f +(1 row) + +-- check the new partition does not overlap with existing partition +CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ERROR: new partition's list of values overlaps with partition "part_1" of "list_parted" +HINT: Please specify a list of values that does not overlap with any existing partition's list. +-- check the new partition does not contain values outside specified bound +INSERT INTO fail_part VALUES (3, 'a'); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2); +ERROR: table contains a row violating the partition bound specification +-- ok if NO VALIDATE is specified +ALTER TABLE fail_part RENAME TO part_2; +ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE; +-- check the table being attached is not already a partition +ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1); +ERROR: "part_2" is already a partition +-- DETACH PARTITION +-- check the partition being detached exists at all +ALTER TABLE list_parted DETACH PARTITION part_3; +ERROR: relation "part_3" does not exist +-- check the partition being detached is a partition (of the parent) +CREATE TABLE not_a_part (a int); +ALTER TABLE list_parted DETACH PARTITION not_a_part; +ERROR: relation "not_a_part" is not a partition of relation "list_parted" +-- check that attinhcount and coninhcount dropped to 0 after detached +ALTER TABLE list_parted DETACH PARTITION part_2; +SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_2'::regclass AND attnum > 0; + attinhcount +------------- + 0 + 0 +(2 rows) + +SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_2'::regclass AND conname = 'check_a'; + coninhcount +------------- + 0 +(1 row) + +-- Miscellaneous ALTER TABLE special behaviors for partitions +-- cannot add/drop a column to/from a partition or rename it +ALTER TABLE part_1 ADD COLUMN c text; +ERROR: cannot add column to a partition +ALTER TABLE part_1 DROP COLUMN b; +ERROR: cannot drop column from a partition +ALTER TABLE part_1 RENAME COLUMN b to c; +ERROR: cannot rename column of a partition +-- cannot alter type of a column of a partition +ALTER TABLE part_1 ALTER COLUMN b TYPE text; +ERROR: cannot alter column type of a partition +-- cannot let a partition participate in regular inheritance +CREATE TABLE inh_test () INHERITS (part_1); +ERROR: cannot inherit from partition "part_1" +CREATE TABLE inh_test (LIKE part_1); +ALTER TABLE inh_test INHERIT part_1; +ERROR: cannot inherit from a partition +ALTER TABLE part_1 INHERIT inh_test; +ERROR: cannot change inheritance of a partition +-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set +ALTER TABLE part_1 ALTER b DROP NOT NULL; +ERROR: column "b" is not null in parent +HINT: Please drop not null in the parent instead +-- cleanup +DROP TABLE list_parted CASCADE; +NOTICE: drop cascades to table part_1 diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out index 4fc8c06..9295093 100644 --- a/src/test/regress/expected/create_table.out +++ b/src/test/regress/expected/create_table.out @@ -439,3 +439,202 @@ Partitioned table "public.describe_list_key" Partition Key: PARTITION BY LIST (a) DROP TABLE describe_range_key, describe_list_key; +-- +-- CREATE TABLE PARTITION OF +-- +-- check partition bound syntax +CREATE TABLE list_parted ( + a int +) PARTITION BY LIST (a); +-- syntax allows only string literal, numeric and null to be specified for +-- a partition bound value +CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1'); +CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (1); +ERROR: new partition's list of values overlaps with partition "lpart1" of "list_parted" +HINT: Please specify a list of values that does not overlap with any existing partition's list. +CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null); +CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1'); +ERROR: syntax error at or near "int" +LINE 1: ...fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1'); + ^ +CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int); +ERROR: syntax error at or near "::" +LINE 1: ...ail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int); + ^ +-- syntax does not allow empty list of values for list partitions +CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (); +ERROR: syntax error at or near ")" +LINE 1: ... TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (); + ^ +-- trying to specify range for list partitioned table +CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2); +ERROR: invalid bound specification for a list partition +CREATE TABLE range_parted ( + a date +) PARTITION BY RANGE (a); +-- trying to specify list for range partitioned table +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a'); +ERROR: invalid bound specification for a range partition +-- both start and end bounds of a range partition cannot be UNBOUNDED +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED; +ERROR: both START and END cannot be UNBOUNDED +LINE 1: CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES... + ^ +-- each of start and end bounds must have same number of values as there +-- are columns in the partition key +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1); +ERROR: START has more values specified than number of columns in the partition key +LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z... + ^ +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1); +ERROR: END has more values specified than number of columns in the partition key +LINE 1: ...RTITION OF range_parted FOR VALUES START ('a') END ('z', 1); + ^ +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z'); +ERROR: START has more values specified than number of columns in the partition key +LINE 1: ... PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z... + ^ +-- specified literal can't be cast to the partition column data type +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b'); +ERROR: invalid input syntax for type date: "a" +LINE 1: ...rpart PARTITION OF range_parted FOR VALUES START ('a') END (... + ^ +-- check if compatible with the specified parent +-- cannot create as partition of a non-partitioned table +CREATE TABLE unparted ( + a int +); +CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a'); +ERROR: "unparted" is not partitioned +DROP TABLE unparted; +-- cannot create a permanent rel as partition of a temp rel +CREATE TEMP TABLE temp_parted ( + a int +) PARTITION BY LIST (a); +CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a'); +ERROR: cannot create as partition of temporary relation "temp_parted" +DROP TABLE temp_parted; +-- cannot create a table with oids as partition of table without oids +CREATE TABLE no_oids_parted ( + a int, + b int +) PARTITION BY RANGE (a, b) WITHOUT OIDS; +CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS; +ERROR: cannot create table with OIDs as partition of table without OIDs +DROP TABLE no_oids_parted; +-- check for partition bound overlap and other invalid specifications +CREATE TABLE list_parted2 ( + a varchar +) PARTITION BY LIST (a); +CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z'); +CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b'); +CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null); +ERROR: new partition's list of values overlaps with partition "nulls_z_part" of "list_parted2" +HINT: Please specify a list of values that does not overlap with any existing partition's list. +CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c'); +ERROR: new partition's list of values overlaps with partition "ab_part" of "list_parted2" +HINT: Please specify a list of values that does not overlap with any existing partition's list. +CREATE TABLE range_parted2 ( + a int +) PARTITION BY RANGE (a); +CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0); +ERROR: new partition's range contains no element +HINT: Please specify a non-empty range. +CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1); +ERROR: new partition's range contains no element +HINT: Please specify a non-empty range. +CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE; +CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1); +CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2); +ERROR: new partition's range overlaps with partition "part_unb_1" of "range_parted2" +HINT: Please specify a range that does not overlap with any existing partition's range. +CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE; +CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15); +ERROR: new partition's range overlaps with partition "part_2_10_inc" of "range_parted2" +HINT: Please specify a range that does not overlap with any existing partition's range. +CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20); +ERROR: new partition's range overlaps with partition "part_2_10_inc" of "range_parted2" +HINT: Please specify a range that does not overlap with any existing partition's range. +-- check for multi-column range partition key where tuple comparison occurs +CREATE TABLE range_parted3 ( + a varchar, + b int +) PARTITION BY RANGE (a, b); +CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10); +CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20); +CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25); +ERROR: new partition's range overlaps with partition "part_a_10_a_20" of "range_parted3" +HINT: Please specify a range that does not overlap with any existing partition's range. +CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10); +CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20); +CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15); +ERROR: new partition's range overlaps with partition "part_b_1_b_10" of "range_parted3" +HINT: Please specify a range that does not overlap with any existing partition's range. +-- check schema propagation from parent +CREATE TABLE parted ( + a text, + b int NOT NULL DEFAULT 1, + CONSTRAINT check_b CHECK (b > 0) NO INHERIT +) PARTITION BY LIST (a); +CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a'); +-- the above command creates inheritance +SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass; + count +------- + 1 +(1 row) + +-- NO INHERIT on a parent's constraint ignored +SELECT connoinherit FROM pg_constraint WHERE conrelid = 'part_a'::regclass AND conname = 'check_b'; + connoinherit +-------------- + f +(1 row) + +-- specify a column option overriding parent's and table constraint which is merged +CREATE TABLE part_b PARTITION OF parted ( + b WITH OPTIONS DEFAULT 10, + CONSTRAINT check_b CHECK (b > 0) +) FOR VALUES IN ('b'); +NOTICE: merging constraint "check_b" with inherited definition +SELECT conislocal, connoinherit FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b'; + conislocal | connoinherit +------------+-------------- + t | f +(1 row) + +-- specify PARTITION BY for a partition +CREATE TABLE fail_col_part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c); +ERROR: column "c" named in partition key does not exist +CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (b); +-- create a partition of partition +CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10); +-- partition cannot be dropped directly +DROP TABLE part_a; +ERROR: "part_a" is a partition of "parted" +HINT: Use ALTER TABLE DETACH PARTITION to be able to drop it. +-- need to specify CASCADE to drop partitions along with the parent +DROP TABLE parted; +ERROR: cannot drop partitioned table parted because other objects depend on it +DETAIL: table part_a depends on partitioned table parted +table part_b depends on partitioned table parted +partitioned table part_c depends on partitioned table parted +table part_c_1_10 depends on partitioned table part_c +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE; +NOTICE: drop cascades to 15 other objects +DETAIL: drop cascades to table part_a_1_a_10 +drop cascades to table part_a_10_a_20 +drop cascades to table part_b_1_b_10 +drop cascades to table part_b_10_b_20 +drop cascades to table part_1_1 +drop cascades to table part_unb_1 +drop cascades to table part_2_10_inc +drop cascades to table nulls_z_part +drop cascades to table ab_part +drop cascades to table lpart1 +drop cascades to table lpart3 +drop cascades to table part_a +drop cascades to table part_b +drop cascades to partitioned table part_c +drop cascades to table part_c_1_10 diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index b1e473d..57bde9d 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_partition|t pg_partitioned|t pg_pltemplate|t pg_policy|t diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index f11f856..b9a808f 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -1873,4 +1873,155 @@ CREATE TABLE no_inh_child ( ) PARTITION BY RANGE (a); CREATE TABLE inh_parent(a int); ALTER TABLE no_inh_child INHERIT inh_parent; -DROP TABLE no_inh_child, inh_parent, partitioned; +DROP TABLE no_inh_child, inh_parent; + +-- ATTACH PARTITION + +-- check target table partitioned +CREATE TABLE unparted ( + a int +); +CREATE TABLE fail_part (like unparted); +ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a'); +DROP TABLE unparted, fail_part; + +-- check partition bounds compatible +CREATE TABLE list_parted ( + a int, + b char(2) NOT NULL COLLATE "en_US", + CONSTRAINT check_a CHECK (a > 0) +) PARTITION BY LIST (a); +CREATE TABLE fail_part (LIKE list_parted); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES START (1) END (10); +DROP TABLE fail_part; + +-- check the table being attached exists +ALTER TABLE list_parted ATTACH PARTITION nonexistant FOR VALUES IN (1); + +-- XXX add ownership tests + +-- check the table being attached is not itself partitioned +CREATE TABLE another_parted ( + a int +) PARTITION BY RANGE (a); +ALTER TABLE list_parted ATTACH PARTITION another_parted FOR VALUES IN (1); +DROP TABLE another_parted; + +-- check the table being attached is not inheritance child of some relation +CREATE TABLE parent (LIKE list_parted); +CREATE TABLE fail_part () INHERITS (parent); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TABLE parent CASCADE; + +-- check the table being attached is not a typed table +CREATE TYPE mytype AS (a int); +CREATE TABLE fail_part OF mytype; +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TYPE mytype CASCADE; + +-- check the existence (or non-existence) of oid column +ALTER TABLE list_parted SET WITH OIDS; +CREATE TABLE fail_part (a int); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); + +ALTER TABLE list_parted SET WITHOUT OIDS; +ALTER TABLE fail_part SET WITH OIDS; +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TABLE fail_part; + +-- check the table being attached does not have columns not in the parent +CREATE TABLE fail_part (like list_parted, c int); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TABLE fail_part; + +-- check the table being attached has all columns of the parent +CREATE TABLE fail_part (a int); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TABLE fail_part; + +-- check the columns of the table being attached match in type, collation and NOT NULL status +CREATE TABLE fail_part ( + a int, + b int +); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ALTER TABLE fail_part ALTER b TYPE char (3); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +ALTER TABLE fail_part ALTER b TYPE char (2) COLLATE "en_CA"; +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TABLE fail_part; + +-- check the table being attached all constraints of the parent +CREATE TABLE fail_part ( + a int, + b char(2) NOT NULL COLLATE "en_US" +); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); + +-- check the constraint of table being attached matches in definition with parent's constraint +ALTER TABLE fail_part ADD CONSTRAINT check_a CHECK (a >= 0); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); +DROP TABLE fail_part; + +-- finally check the attincount and coninhcount after successfully attached +CREATE TABLE part_1 ( + a int, + b char(2) NOT NULL COLLATE "en_US", + CONSTRAINT check_a CHECK (a > 0) NO INHERIT +); +ALTER TABLE list_parted ATTACH PARTITION part_1 FOR VALUES IN (1); +SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_1'::regclass AND attnum > 0; +SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a'; +-- also check connoinherit on the partition's constraint, it must have been turned off +SELECT connoinherit FROM pg_constraint WHERE conrelid = 'part_1'::regclass AND conname = 'check_a'; + +-- check the new partition does not overlap with existing partition +CREATE TABLE fail_part (LIKE part_1 INCLUDING CONSTRAINTS); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (1); + +-- check the new partition does not contain values outside specified bound +INSERT INTO fail_part VALUES (3, 'a'); +ALTER TABLE list_parted ATTACH PARTITION fail_part FOR VALUES IN (2); + +-- ok if NO VALIDATE is specified +ALTER TABLE fail_part RENAME TO part_2; +ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (2) NO VALIDATE; + +-- check the table being attached is not already a partition +ALTER TABLE list_parted ATTACH PARTITION part_2 FOR VALUES IN (1); + +-- DETACH PARTITION + +-- check the partition being detached exists at all +ALTER TABLE list_parted DETACH PARTITION part_3; + +-- check the partition being detached is a partition (of the parent) +CREATE TABLE not_a_part (a int); +ALTER TABLE list_parted DETACH PARTITION not_a_part; + +-- check that attinhcount and coninhcount dropped to 0 after detached +ALTER TABLE list_parted DETACH PARTITION part_2; +SELECT attinhcount FROM pg_attribute WHERE attrelid = 'part_2'::regclass AND attnum > 0; +SELECT coninhcount FROM pg_constraint WHERE conrelid = 'part_2'::regclass AND conname = 'check_a'; + +-- Miscellaneous ALTER TABLE special behaviors for partitions + +-- cannot add/drop a column to/from a partition or rename it +ALTER TABLE part_1 ADD COLUMN c text; +ALTER TABLE part_1 DROP COLUMN b; +ALTER TABLE part_1 RENAME COLUMN b to c; + +-- cannot alter type of a column of a partition +ALTER TABLE part_1 ALTER COLUMN b TYPE text; + +-- cannot let a partition participate in regular inheritance +CREATE TABLE inh_test () INHERITS (part_1); +CREATE TABLE inh_test (LIKE part_1); +ALTER TABLE inh_test INHERIT part_1; +ALTER TABLE part_1 INHERIT inh_test; + +-- cannot alter DROP NOT NULL on a partition column if the parent has NOT NULL set +ALTER TABLE part_1 ALTER b DROP NOT NULL; + +-- cleanup +DROP TABLE list_parted CASCADE; diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql index 7c7a18a..1e14c4a 100644 --- a/src/test/regress/sql/create_table.sql +++ b/src/test/regress/sql/create_table.sql @@ -417,3 +417,138 @@ CREATE TABLE describe_list_key ( ) PARTITION BY LIST (a); \d describe_list_key DROP TABLE describe_range_key, describe_list_key; + +-- +-- CREATE TABLE PARTITION OF +-- + +-- check partition bound syntax + +CREATE TABLE list_parted ( + a int +) PARTITION BY LIST (a); +-- syntax allows only string literal, numeric and null to be specified for +-- a partition bound value +CREATE TABLE lpart1 PARTITION OF list_parted FOR VALUES IN ('1'); +CREATE TABLE lpart2 PARTITION OF list_parted FOR VALUES IN (1); +CREATE TABLE lpart3 PARTITION OF list_parted FOR VALUES IN (null); +CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (int '1'); +CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN ('1'::int); + +-- syntax does not allow empty list of values for list partitions +CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES IN (); +-- trying to specify range for list partitioned table +CREATE TABLE fail_lpart PARTITION OF list_parted FOR VALUES START (1) END (2); + +CREATE TABLE range_parted ( + a date +) PARTITION BY RANGE (a); + +-- trying to specify list for range partitioned table +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES IN ('a'); +-- both start and end bounds of a range partition cannot be UNBOUNDED +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START UNBOUNDED END UNBOUNDED; +-- each of start and end bounds must have same number of values as there +-- are columns in the partition key +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z', 1); +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('z', 1); +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a', 1) END ('z'); + +-- specified literal can't be cast to the partition column data type +CREATE TABLE fail_rpart PARTITION OF range_parted FOR VALUES START ('a') END ('b'); + +-- check if compatible with the specified parent + +-- cannot create as partition of a non-partitioned table +CREATE TABLE unparted ( + a int +); +CREATE TABLE part PARTITION OF unparted FOR VALUES IN ('a'); +DROP TABLE unparted; + +-- cannot create a permanent rel as partition of a temp rel +CREATE TEMP TABLE temp_parted ( + a int +) PARTITION BY LIST (a); +CREATE TABLE part PARTITION OF temp_parted FOR VALUES IN ('a'); +DROP TABLE temp_parted; + +-- cannot create a table with oids as partition of table without oids +CREATE TABLE no_oids_parted ( + a int, + b int +) PARTITION BY RANGE (a, b) WITHOUT OIDS; +CREATE TABLE part PARTITION OF no_oids_parted FOR VALUES IN ('a') WITH OIDS; +DROP TABLE no_oids_parted; + +-- check for partition bound overlap and other invalid specifications + +CREATE TABLE list_parted2 ( + a varchar +) PARTITION BY LIST (a); +CREATE TABLE nulls_z_part PARTITION OF list_parted2 FOR VALUES IN (null, 'z'); +CREATE TABLE ab_part PARTITION OF list_parted2 FOR VALUES IN ('a', 'b'); + +CREATE TABLE fail_nulls_part PARTITION OF list_parted2 FOR VALUES IN (null); +CREATE TABLE fail_bc_part PARTITION OF list_parted2 FOR VALUES IN ('b', 'c'); + +CREATE TABLE range_parted2 ( + a int +) PARTITION BY RANGE (a); + +CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (0); +CREATE TABLE fail_part_empty PARTITION OF range_parted2 FOR VALUES START (1) END (1); +CREATE TABLE part_1_1 PARTITION OF range_parted2 FOR VALUES START (1) END (1) INCLUSIVE; +CREATE TABLE part_unb_1 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (1); +CREATE TABLE fail_unb_2 PARTITION OF range_parted2 FOR VALUES START UNBOUNDED END (2); +CREATE TABLE part_2_10_inc PARTITION OF range_parted2 FOR VALUES START (2) END (10) INCLUSIVE; +CREATE TABLE fail_part_5_15 PARTITION OF range_parted2 FOR VALUES START (5) END (15); +CREATE TABLE fail_part_10_20 PARTITION OF range_parted2 FOR VALUES START (10) END (20); + +-- check for multi-column range partition key where tuple comparison occurs +CREATE TABLE range_parted3 ( + a varchar, + b int +) PARTITION BY RANGE (a, b); + +CREATE TABLE part_a_1_a_10 PARTITION OF range_parted3 FOR VALUES START ('a', 1) END ('a', 10); +CREATE TABLE part_a_10_a_20 PARTITION OF range_parted3 FOR VALUES START ('a', 10) END ('a', 20); +CREATE TABLE fail_part_a_15_a_25 PARTITION OF range_parted3 FOR VALUES START ('a', 15) END ('a', 25); +CREATE TABLE part_b_1_b_10 PARTITION OF range_parted3 FOR VALUES START ('b', 1) END ('b', 10); +CREATE TABLE part_b_10_b_20 PARTITION OF range_parted3 FOR VALUES START ('b', 10) END ('b', 20); +CREATE TABLE fail_part_b_5_b_15 PARTITION OF range_parted3 FOR VALUES START ('b', 5) END ('b', 15); + +-- check schema propagation from parent + +CREATE TABLE parted ( + a text, + b int NOT NULL DEFAULT 1, + CONSTRAINT check_b CHECK (b > 0) NO INHERIT +) PARTITION BY LIST (a); + +CREATE TABLE part_a PARTITION OF parted FOR VALUES IN ('a'); +-- the above command creates inheritance +SELECT count(*) FROM pg_inherits WHERE inhrelid = 'part_a'::regclass; +-- NO INHERIT on a parent's constraint ignored +SELECT connoinherit FROM pg_constraint WHERE conrelid = 'part_a'::regclass AND conname = 'check_b'; + +-- specify a column option overriding parent's and table constraint which is merged +CREATE TABLE part_b PARTITION OF parted ( + b WITH OPTIONS DEFAULT 10, + CONSTRAINT check_b CHECK (b > 0) +) FOR VALUES IN ('b'); +SELECT conislocal, connoinherit FROM pg_constraint WHERE conrelid = 'part_b'::regclass AND conname = 'check_b'; + +-- specify PARTITION BY for a partition +CREATE TABLE fail_col_part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (c); +CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (b); +-- create a partition of partition +CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10); + +-- partition cannot be dropped directly +DROP TABLE part_a; + +-- need to specify CASCADE to drop partitions along with the parent +DROP TABLE parted; + +DROP TABLE parted, list_parted, range_parted, list_parted2, range_parted2, range_parted3 CASCADE; -- 1.7.1
0004-psql-and-pg_dump-support-for-partitions.patch (text/x-diff, 17.8K) download From 12b0f657def1c6ed439959f08ecdfec4de53dc15 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Tue, 12 Jul 2016 17:50:33 +0900 Subject: [PATCH 4/8] psql and pg_dump support for partitions. Takes care of both the partition bound deparse stuff and handling parent-partition relationship (filtering pg_inherits entries pertaining to partitions and handling appropriately). --- src/backend/utils/adt/ruleutils.c | 78 ++++++++++++++++++++++ src/bin/pg_dump/common.c | 86 ++++++++++++++++++++++++ src/bin/pg_dump/pg_dump.c | 98 ++++++++++++++++++++++++++-- src/bin/pg_dump/pg_dump.h | 12 ++++ src/bin/psql/describe.c | 46 ++++++++++++- src/test/regress/expected/create_table.out | 23 +++++++ src/test/regress/sql/create_table.sql | 6 ++ 7 files changed, 340 insertions(+), 9 deletions(-) diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 4691b5b..b29fec1 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -8350,6 +8350,84 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_PartitionListSpec: + { + PartitionListSpec *list_spec = (PartitionListSpec *) node; + ListCell *cell; + char *sep; + + appendStringInfoString(buf, "FOR VALUES"); + + appendStringInfoString(buf, " IN ("); + sep = ""; + foreach (cell, list_spec->values) + { + Const *val = lfirst(cell); + + appendStringInfoString(buf, sep); + get_const_expr(val, context, -1); + sep = ", "; + } + + appendStringInfoString(buf, ")"); + } + break; + + case T_PartitionRangeSpec: + { + PartitionRangeSpec *range_spec = (PartitionRangeSpec *) node; + ListCell *cell; + char *sep; + + appendStringInfoString(buf, "FOR VALUES"); + + appendStringInfoString(buf, " START"); + if (!range_spec->lower) + appendStringInfoString(buf, " UNBOUNDED"); + else + { + appendStringInfoString(buf, " ("); + + sep = ""; + foreach (cell, range_spec->lower) + { + Const *val = lfirst(cell); + + appendStringInfoString(buf, sep); + get_const_expr(val, context, -1); + sep = ", "; + } + appendStringInfoString(buf, ")"); + + if (!range_spec->lowerinc) + appendStringInfoString(buf, " EXCLUSIVE"); + } + + appendStringInfoString(buf, " END"); + + if (!range_spec->upper) + appendStringInfoString(buf, " UNBOUNDED"); + else + { + appendStringInfoString(buf, " ("); + + sep = ""; + foreach (cell, range_spec->upper) + { + Const *val = lfirst(cell); + + appendStringInfoString(buf, sep); + get_const_expr(val, context, -1); + sep = ", "; + } + appendStringInfoString(buf, ")"); + + if (range_spec->upperinc) + appendStringInfoString(buf, " INCLUSIVE"); + } + } + break; + case T_List: { char *sep; diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index 1cbb987..c8e56bd 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -68,6 +68,8 @@ static int numextmembers; static void flagInhTables(TableInfo *tbinfo, int numTables, InhInfo *inhinfo, int numInherits); +static void flagPartitions(TableInfo *tblinfo, int numTables, + PartInfo *partinfo, int numPartitions); static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables); static DumpableObject **buildIndexArray(void *objArray, int numObjs, Size objSize); @@ -75,6 +77,8 @@ static int DOCatalogIdCompare(const void *p1, const void *p2); static int ExtensionMemberIdCompare(const void *p1, const void *p2); static void findParentsByOid(TableInfo *self, InhInfo *inhinfo, int numInherits); +static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo, + int numPartitions); static int strInArray(const char *pattern, char **arr, int arr_size); @@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr) NamespaceInfo *nspinfo; ExtensionInfo *extinfo; InhInfo *inhinfo; + PartInfo *partinfo; int numAggregates; int numInherits; + int numPartitions; int numRules; int numProcLangs; int numCasts; @@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr) inhinfo = getInherits(fout, &numInherits); if (g_verbose) + write_msg(NULL, "reading partition information\n"); + partinfo = getPartitions(fout, &numPartitions); + + if (g_verbose) write_msg(NULL, "reading event triggers\n"); getEventTriggers(fout, &numEventTriggers); @@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr) write_msg(NULL, "finding inheritance relationships\n"); flagInhTables(tblinfo, numTables, inhinfo, numInherits); + /* Link tables to partition parents, mark parents as interesting */ + if (g_verbose) + write_msg(NULL, "finding partition relationships\n"); + flagPartitions(tblinfo, numTables, partinfo, numPartitions); + if (g_verbose) write_msg(NULL, "reading column info for interesting tables\n"); getTableAttrs(fout, tblinfo, numTables); @@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables, } } +/* flagPartitions - + * Fill in parent link fields of every target table that is partition, + * and mark parents of partitions as interesting + * + * modifies tblinfo + */ +static void +flagPartitions(TableInfo *tblinfo, int numTables, + PartInfo *partinfo, int numPartitions) +{ + int i; + + for (i = 0; i < numTables; i++) + { + /* Some kinds are never partitions */ + if (tblinfo[i].relkind == RELKIND_SEQUENCE || + tblinfo[i].relkind == RELKIND_VIEW || + tblinfo[i].relkind == RELKIND_MATVIEW) + continue; + + /* Don't bother computing anything for non-target tables, either */ + if (!tblinfo[i].dobj.dump) + continue; + + /* Find the parent TableInfo and save */ + findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions); + + /* Mark the parent as interesting for getTableAttrs */ + if (tblinfo[i].partitionOf) + { + tblinfo[i].partitionOf->interesting = true; + addObjectDependency(&tblinfo[i].dobj, + tblinfo[i].partitionOf->dobj.dumpId); + } + } +} + /* flagInhAttrs - * for each dumpable table in tblinfo, flag its inherited attributes * @@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self, } /* + * findPartitionParentByOid + * find a partition's parent in tblinfo[] + */ +static void +findPartitionParentByOid(TableInfo *self, PartInfo *partinfo, + int numPartitions) +{ + Oid oid = self->dobj.catId.oid; + int i; + + for (i = 0; i < numPartitions; i++) + { + if (partinfo[i].partrelid == oid) + { + TableInfo *parent; + + parent = findTableByOid(partinfo[i].partparent); + if (parent == NULL) + { + write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n", + partinfo[i].partparent, + self->dobj.name, + oid); + exit_nicely(1); + } + self->partitionOf = parent; + + /* While we're at it, also save the partdef */ + self->partitiondef = partinfo[i].partdef; + } + } +} + +/* * parseOidArray * parse a string of numbers delimited by spaces into a character array * diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index a127672..8e4231e 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -6124,6 +6124,63 @@ getInherits(Archive *fout, int *numInherits) } /* + * getPartitions + * read all the partition inheritance and partition bound information + * from the system catalogs return them in the PartInfo* structure + * + * numPartitions is set to the number of pairs read in + */ +PartInfo * +getPartitions(Archive *fout, int *numPartitions) +{ + PGresult *res; + int ntups; + int i; + PQExpBuffer query = createPQExpBuffer(); + PartInfo *partinfo; + + int i_partrelid; + int i_partparent; + int i_partbound; + + /* Make sure we are in proper schema */ + selectSourceSchema(fout, "pg_catalog"); + + /* find all the inheritance information */ + + appendPQExpBufferStr(query, + "SELECT partrelid, inhparent AS partparent, " + " pg_get_expr(partbound, partrelid) AS partbound " + "FROM pg_partition, pg_inherits " + "WHERE partrelid = inhrelid"); + + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + + *numPartitions = ntups; + + partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo)); + + i_partrelid = PQfnumber(res, "partrelid"); + i_partparent = PQfnumber(res, "partparent"); + i_partbound = PQfnumber(res, "partbound"); + + for (i = 0; i < ntups; i++) + { + partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid)); + partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent)); + partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound)); + } + + PQclear(res); + + destroyPQExpBuffer(query); + + return partinfo; +} + +/* * getIndexes * get information about every index on a dumpable table * @@ -15301,6 +15358,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) if (tbinfo->reloftype && !dopt->binary_upgrade) appendPQExpBuffer(q, " OF %s", tbinfo->reloftype); + if (tbinfo->partitionOf && !dopt->binary_upgrade) + { + TableInfo *parentRel = tbinfo->partitionOf; + + appendPQExpBuffer(q, " PARTITION OF "); + if (parentRel->dobj.namespace != tbinfo->dobj.namespace) + appendPQExpBuffer(q, "%s.", + fmtId(parentRel->dobj.namespace->dobj.name)); + appendPQExpBufferStr(q, fmtId(parentRel->dobj.name)); + } + if (tbinfo->relkind != RELKIND_MATVIEW) { /* Dump the attributes */ @@ -15329,8 +15397,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) (!tbinfo->inhNotNull[j] || dopt->binary_upgrade)); - /* Skip column if fully defined by reloftype */ - if (tbinfo->reloftype && + /* + * Skip column if fully defined by reloftype or the + * partition parent. + */ + if ((tbinfo->reloftype || tbinfo->partitionOf) && !has_default && !has_notnull && !dopt->binary_upgrade) continue; @@ -15359,7 +15430,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) } /* Attribute type */ - if (tbinfo->reloftype && !dopt->binary_upgrade) + if ((tbinfo->reloftype || tbinfo->partitionOf) && + !dopt->binary_upgrade) { appendPQExpBufferStr(q, " WITH OPTIONS"); } @@ -15424,15 +15496,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) if (actual_atts) appendPQExpBufferStr(q, "\n)"); - else if (!(tbinfo->reloftype && !dopt->binary_upgrade)) + else if (!((tbinfo->reloftype || tbinfo->partitionOf) && + !dopt->binary_upgrade)) { /* * We must have a parenthesized attribute list, even though - * empty, when not using the OF TYPE syntax. + * empty, when not using the OF TYPE or PARTITION OF syntax. */ appendPQExpBufferStr(q, " (\n)"); } + if (tbinfo->partitiondef && !dopt->binary_upgrade) + { + appendPQExpBufferStr(q, "\n"); + appendPQExpBufferStr(q, tbinfo->partitiondef); + } + if (numParents > 0 && !dopt->binary_upgrade) { appendPQExpBufferStr(q, "\nINHERITS ("); @@ -15602,6 +15681,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) tbinfo->reloftype); } + if (tbinfo->partitionOf) + { + appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n"); + appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n", + fmtId(tbinfo->partitionOf->dobj.name), + tbinfo->dobj.name, + tbinfo->partitiondef); + } + appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n"); appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n" "SET relfrozenxid = '%u', relminmxid = '%u'\n" diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 0292859..760067a 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -320,6 +320,8 @@ typedef struct _tableInfo struct _tableDataInfo *dataObj; /* TableDataInfo, if dumping its data */ int numTriggers; /* number of triggers for table */ struct _triggerInfo *triggers; /* array of TriggerInfo structs */ + struct _tableInfo *partitionOf; /* TableInfo for the partition parent */ + char *partitiondef; /* partition key definition */ } TableInfo; typedef struct _attrDefInfo @@ -460,6 +462,15 @@ typedef struct _inhInfo Oid inhparent; /* OID of its parent */ } InhInfo; +/* PartInfo isn't a DumpableObject, just temporary state */ +typedef struct _partInfo +{ + Oid partrelid; /* OID of a partition */ + Oid partparent; /* OID of its parent */ + char *partdef; /* partition bound definition */ +} PartInfo; + + typedef struct _prsInfo { DumpableObject dobj; @@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions); extern TableInfo *getTables(Archive *fout, int *numTables); extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables); extern InhInfo *getInherits(Archive *fout, int *numInherits); +extern PartInfo *getPartitions(Archive *fout, int *numPartitions); extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables); extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables); extern RuleInfo *getRules(Archive *fout, int *numRules); diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 3ba8a90..32b84ca 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1786,6 +1786,34 @@ describeOneTableDetails(const char *schemaname, } /* Make footers */ + if (pset.sversion >= 90600) + { + /* Get the partition information */ + PGresult *result; + char *parent_name; + char *partdef; + + printfPQExpBuffer(&buf, + "SELECT inhparent::pg_catalog.regclass, pg_get_expr(partbound, partrelid)" + " FROM pg_catalog.pg_partition p" + " JOIN pg_catalog.pg_inherits i" + " ON p.partrelid = i.inhrelid" + " WHERE p.partrelid = '%s';", oid); + result = PSQLexec(buf.data); + if (!result) + goto error_return; + + if (PQntuples(result) > 0) + { + parent_name = PQgetvalue(result, 0, 0); + partdef = PQgetvalue(result, 0, 1); + printfPQExpBuffer(&tmpbuf, _("Partition Of: %s %s"), parent_name, + partdef); + printTableAddFooter(&cont, tmpbuf.data); + PQclear(result); + } + } + if (pset.sversion >= 90600 && tableinfo.relkind == 'P') { /* Get the partition key information */ @@ -2547,8 +2575,12 @@ describeOneTableDetails(const char *schemaname, PQclear(result); } - /* print inherited tables */ - printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid); + /* print inherited tables (exclude, if parent is a partitioned table) */ + printfPQExpBuffer(&buf, + "SELECT c.oid::pg_catalog.regclass" + " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i" + " WHERE c.oid=i.inhparent AND i.inhrelid = '%s'" + " AND c.relkind != 'P' ORDER BY inhseqno;", oid); result = PSQLexec(buf.data); if (!result) @@ -2577,9 +2609,15 @@ describeOneTableDetails(const char *schemaname, PQclear(result); } - /* print child tables */ + /* print child tables (exclude, if parent is a partitioned table) */ if (pset.sversion >= 80300) - printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid); + printfPQExpBuffer(&buf, + "SELECT c.oid::pg_catalog.regclass" + " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i" + " WHERE c.oid=i.inhrelid AND" + " i.inhparent = '%s' AND" + " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s' AND c.relkind != 'P')" + " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid); else printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid); diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out index 9295093..31bd1d8 100644 --- a/src/test/regress/expected/create_table.out +++ b/src/test/regress/expected/create_table.out @@ -609,6 +609,29 @@ ERROR: column "c" named in partition key does not exist CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (b); -- create a partition of partition CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10); +-- Partition bound in describe output +\d part_b + Table "public.part_b" + Column | Type | Modifiers +--------+---------+--------------------- + a | text | + b | integer | not null default 10 +Partition Of: parted FOR VALUES IN ('b') +Check constraints: + "check_b" CHECK (b > 0) + +-- Both partition bound and partition key in describe output +\d part_c + Partitioned table "public.part_c" + Column | Type | Modifiers +--------+---------+-------------------- + a | text | + b | integer | not null default 1 +Partition Of: parted FOR VALUES IN ('c') +Partition Key: PARTITION BY RANGE (b) +Check constraints: + "check_b" CHECK (b > 0) + -- partition cannot be dropped directly DROP TABLE part_a; ERROR: "part_a" is a partition of "parted" diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql index 1e14c4a..d920abb 100644 --- a/src/test/regress/sql/create_table.sql +++ b/src/test/regress/sql/create_table.sql @@ -545,6 +545,12 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE ( -- create a partition of partition CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10); +-- Partition bound in describe output +\d part_b + +-- Both partition bound and partition key in describe output +\d part_c + -- partition cannot be dropped directly DROP TABLE part_a; -- 1.7.1
0005-Teach-a-few-places-to-use-partition-check-constraint.patch (text/x-diff, 17.2K) download From f2f6f8a9e4c66f94bf4f6b1610bccfec9d5c5108 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 27 Jul 2016 16:00:09 +0900 Subject: [PATCH 5/8] Teach a few places to use partition check constraint expressions. For example, if a row is inserted directly into a partition we should make sure that it does not violate its bounds. So teach copy.c and execMain.c to apply "partition check constraint". Also, for constraint exclusion to work with partitioned tables, teach the optimizer to include check constraint expressions derived from partition bound bound info in the list of predicates it uses to perform the task. --- src/backend/commands/copy.c | 2 +- src/backend/executor/execMain.c | 76 ++++++++++++++++++++++++++++++- src/backend/executor/nodeModifyTable.c | 4 +- src/backend/optimizer/util/plancat.c | 20 ++++++++ src/include/nodes/execnodes.h | 4 ++ src/test/regress/expected/insert.out | 76 ++++++++++++++++++++++++++++++++ src/test/regress/expected/update.out | 27 +++++++++++ src/test/regress/sql/insert.sql | 56 +++++++++++++++++++++++ src/test/regress/sql/update.sql | 21 +++++++++ 9 files changed, 280 insertions(+), 6 deletions(-) diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 53b2226..76803a5 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2464,7 +2464,7 @@ CopyFrom(CopyState cstate) if (!skip_tuple) { /* Check the constraints of the tuple */ - if (cstate->rel->rd_att->constr) + if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck) ExecConstraints(resultRelInfo, slot, estate); if (useHeapMultiInsert) diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 3c73e81..489ff02 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -42,6 +42,7 @@ #include "access/transam.h" #include "access/xact.h" #include "catalog/namespace.h" +#include "catalog/partition.h" #include "commands/matview.h" #include "commands/trigger.h" #include "executor/execdebug.h" @@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo, resultRelInfo->ri_ConstraintExprs = NULL; resultRelInfo->ri_junkFilter = NULL; resultRelInfo->ri_projectReturning = NULL; + resultRelInfo->ri_PartitionCheck = + RelationGetPartitionCheckQual(resultRelationDesc); } /* @@ -1692,6 +1695,50 @@ ExecRelCheck(ResultRelInfo *resultRelInfo, return NULL; } +/* + * ExecPartitionCheck --- check that tuple meets the partition boundary + * specification. + * + * Note: This is called, *iff* resultRelInfo is the main target table. + */ +static bool +ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, + EState *estate) +{ + ExprContext *econtext; + + /* + * If first time through, build expression state tree for the partition + * check expression. Keep it in the per-query memory context so they'll + * survive throughout the query. + */ + if (resultRelInfo->ri_PartitionCheckExpr == NULL) + { + List *qual = resultRelInfo->ri_PartitionCheck; + + resultRelInfo->ri_PartitionCheckExpr = (List *) + ExecPrepareExpr((Expr *) qual, estate); + } + + /* + * We will use the EState's per-tuple context for evaluating constraint + * expressions (creating it if it's not already there). + */ + econtext = GetPerTupleExprContext(estate); + + /* Arrange for econtext's scan tuple to be the tuple under test */ + econtext->ecxt_scantuple = slot; + + /* + * NOTE: SQL specifies that a NULL result from a constraint expression + * is not to be treated as a failure. Therefore, tell ExecQual to + * return TRUE for NULL. + * + * XXX - although, it's unlikely that NULL would result. + */ + return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true); +} + void ExecConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate) @@ -1703,9 +1750,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo, Bitmapset *insertedCols; Bitmapset *updatedCols; - Assert(constr); + Assert(constr || resultRelInfo->ri_PartitionCheck); - if (constr->has_not_null) + if (constr && constr->has_not_null) { int natts = tupdesc->natts; int attrChk; @@ -1736,7 +1783,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo, } } - if (constr->num_check > 0) + if (constr && constr->num_check > 0) { const char *failed; @@ -1760,6 +1807,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo, errtableconstraint(rel, failed))); } } + + if (resultRelInfo->ri_PartitionCheck) + { + if (!ExecPartitionCheck(resultRelInfo, slot, estate)) + { + char *val_desc; + + insertedCols = GetInsertedColumns(resultRelInfo, estate); + updatedCols = GetUpdatedColumns(resultRelInfo, estate); + modifiedCols = bms_union(insertedCols, updatedCols); + val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), + slot, + tupdesc, + modifiedCols, + 64); + ereport(ERROR, + (errcode(ERRCODE_CHECK_VIOLATION), + errmsg("new row violates the partition boundary" + " specification of \"%s\"", + RelationGetRelationName(rel)), + val_desc ? errdetail("Failing row contains %s.", val_desc) : 0)); + } + } } /* diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 8e2da9c..3ed321e 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -354,7 +354,7 @@ ExecInsert(ModifyTableState *mtstate, /* * Check the constraints of the tuple */ - if (resultRelationDesc->rd_att->constr) + if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck) ExecConstraints(resultRelInfo, slot, estate); if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0) @@ -907,7 +907,7 @@ lreplace:; /* * Check the constraints of the tuple */ - if (resultRelationDesc->rd_att->constr) + if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck) ExecConstraints(resultRelInfo, slot, estate); /* diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 5d18206..249a5aa 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -27,6 +27,7 @@ #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/heap.h" +#include "catalog/partition.h" #include "catalog/pg_am.h" #include "foreign/fdwapi.h" #include "miscadmin.h" @@ -1127,6 +1128,7 @@ get_relation_constraints(PlannerInfo *root, Index varno = rel->relid; Relation relation; TupleConstr *constr; + List *pcqual; /* * We assume the relation has already been safely locked. @@ -1212,6 +1214,24 @@ get_relation_constraints(PlannerInfo *root, } } + /* Append partition predicates, if any */ + pcqual = RelationGetPartitionCheckQual(relation); + if (pcqual) + { + /* + * Run each expression through const-simplification and + * canonicalization similar to check constraints. + */ + pcqual = (List *) eval_const_expressions(root, (Node *) pcqual); + pcqual = (List *) canonicalize_qual((Expr *) pcqual); + + /* Fix Vars to have the desired varno */ + if (varno != 1) + ChangeVarNodes((Node *) pcqual, 1, varno, 0); + + result = list_concat(result, pcqual); + } + heap_close(relation, NoLock); return result; diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index e7fd7bd..31ca9ed 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -319,6 +319,8 @@ typedef struct JunkFilter * projectReturning for computing a RETURNING list * onConflictSetProj for computing ON CONFLICT DO UPDATE SET * onConflictSetWhere list of ON CONFLICT DO UPDATE exprs (qual) + * PartitionCheck partition check expression + * PartitionCheckExpr partition check expression state * ---------------- */ typedef struct ResultRelInfo @@ -343,6 +345,8 @@ typedef struct ResultRelInfo ProjectionInfo *ri_projectReturning; ProjectionInfo *ri_onConflictSetProj; List *ri_onConflictSetWhere; + List *ri_PartitionCheck; + List *ri_PartitionCheckExpr; } ResultRelInfo; /* ---------------- diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out index 70107b5..589efb8 100644 --- a/src/test/regress/expected/insert.out +++ b/src/test/regress/expected/insert.out @@ -160,3 +160,79 @@ Rules: drop table inserttest2; drop table inserttest; drop type insert_test_type; +-- direct partition inserts should check partition bound constraint +create table range_parted ( + a text, + b int +) partition by range (a, b); +create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10); +create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20); +create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10); +create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20); +-- fail +insert into part_a_1_a_10 values ('a', 11); +ERROR: new row violates the partition boundary specification of "part_a_1_a_10" +DETAIL: Failing row contains (a, 11). +insert into part_a_1_a_10 values ('b', 1); +ERROR: new row violates the partition boundary specification of "part_a_1_a_10" +DETAIL: Failing row contains (b, 1). +-- ok +insert into part_a_1_a_10 values ('a', 1); +-- fail +insert into part_b_10_b_20 values ('b', 21); +ERROR: new row violates the partition boundary specification of "part_b_10_b_20" +DETAIL: Failing row contains (b, 21). +insert into part_b_10_b_20 values ('a', 10); +ERROR: new row violates the partition boundary specification of "part_b_10_b_20" +DETAIL: Failing row contains (a, 10). +-- ok +insert into part_b_10_b_20 values ('b', 10); +-- fail (a is null but a range partition key column should not be null) +insert into part_b_10_b_20(b) values (10); +ERROR: new row violates the partition boundary specification of "part_b_10_b_20" +DETAIL: Failing row contains (null, 10). +create table list_parted ( + a text, + b int +) partition by list (upper(a)); +create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB'); +create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD'); +-- fail +insert into part_AA_BB values ('cc', 1); +ERROR: new row violates the partition boundary specification of "part_aa_bb" +DETAIL: Failing row contains (cc, 1). +insert into part_AA_BB values ('AAa', 1); +ERROR: new row violates the partition boundary specification of "part_aa_bb" +DETAIL: Failing row contains (AAa, 1). +-- ok +insert into part_CC_DD values ('cC', 1); +-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values) +-- insert into part_AA_BB (b) values (1); +-- check in case of multi-level partitioned table +create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b); +create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10); +create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20); +-- fail (both its own and all ancestors' partition bound spec applies) +insert into part_EE_FF_1_10 values ('EE', 11); +ERROR: new row violates the partition boundary specification of "part_ee_ff_1_10" +DETAIL: Failing row contains (EE, 11). +insert into part_EE_FF_1_10 values ('cc', 1); +ERROR: new row violates the partition boundary specification of "part_ee_ff_1_10" +DETAIL: Failing row contains (cc, 1). +-- ok +insert into part_EE_FF_1_10 values ('ff', 1); +insert into part_EE_FF_10_20 values ('ff', 11); +-- cleanup +drop table range_parted cascade; +NOTICE: drop cascades to 4 other objects +DETAIL: drop cascades to table part_a_1_a_10 +drop cascades to table part_a_10_a_20 +drop cascades to table part_b_1_b_10 +drop cascades to table part_b_10_b_20 +drop table list_parted cascade; +NOTICE: drop cascades to 5 other objects +DETAIL: drop cascades to table part_aa_bb +drop cascades to table part_cc_dd +drop cascades to partitioned table part_ee_ff +drop cascades to table part_ee_ff_1_10 +drop cascades to table part_ee_ff_10_20 diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out index adc1fd7..df6eb30 100644 --- a/src/test/regress/expected/update.out +++ b/src/test/regress/expected/update.out @@ -182,3 +182,30 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a) DROP TABLE update_test; DROP TABLE upsert_test; +-- update to a partition should check partition bound constraint for the new tuple +create table range_parted ( + a text, + b int +) partition by range (a, b); +create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10); +create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20); +create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10); +create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20); +insert into part_a_1_a_10 values ('a', 1); +insert into part_b_10_b_20 values ('b', 10); +-- fail +update part_a_1_a_10 set a = 'b' where a = 'a'; +ERROR: new row violates the partition boundary specification of "part_a_1_a_10" +DETAIL: Failing row contains (b, 1). +update range_parted set b = b - 1 where b = 10; +ERROR: new row violates the partition boundary specification of "part_b_10_b_20" +DETAIL: Failing row contains (b, 9). +-- ok +update range_parted set b = b + 1 where b = 10; +-- cleanup +drop table range_parted cascade; +NOTICE: drop cascades to 4 other objects +DETAIL: drop cascades to table part_a_1_a_10 +drop cascades to table part_a_10_a_20 +drop cascades to table part_b_1_b_10 +drop cascades to table part_b_10_b_20 diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql index 7924d5d..4bf042e 100644 --- a/src/test/regress/sql/insert.sql +++ b/src/test/regress/sql/insert.sql @@ -84,3 +84,59 @@ create rule irule3 as on insert to inserttest2 do also drop table inserttest2; drop table inserttest; drop type insert_test_type; + +-- direct partition inserts should check partition bound constraint +create table range_parted ( + a text, + b int +) partition by range (a, b); +create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10); +create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20); +create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10); +create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20); + +-- fail +insert into part_a_1_a_10 values ('a', 11); +insert into part_a_1_a_10 values ('b', 1); +-- ok +insert into part_a_1_a_10 values ('a', 1); +-- fail +insert into part_b_10_b_20 values ('b', 21); +insert into part_b_10_b_20 values ('a', 10); +-- ok +insert into part_b_10_b_20 values ('b', 10); + +-- fail (a is null but a range partition key column should not be null) +insert into part_b_10_b_20(b) values (10); + +create table list_parted ( + a text, + b int +) partition by list (upper(a)); +create table part_AA_BB partition of list_parted FOR VALUES IN ('AA', 'BB'); +create table part_CC_DD partition of list_parted FOR VALUES IN ('CC', 'DD'); + +-- fail +insert into part_AA_BB values ('cc', 1); +insert into part_AA_BB values ('AAa', 1); +-- ok +insert into part_CC_DD values ('cC', 1); + +-- XXX - fail (a is null but part_AA_BB does not allow nulls in its list of values) +-- insert into part_AA_BB (b) values (1); + +-- check in case of multi-level partitioned table +create table part_EE_FF partition of list_parted for values in ('EE', 'FF') partition by range (b); +create table part_EE_FF_1_10 partition of part_EE_FF for values start (1) end (10); +create table part_EE_FF_10_20 partition of part_EE_FF for values start (10) end (20); + +-- fail (both its own and all ancestors' partition bound spec applies) +insert into part_EE_FF_1_10 values ('EE', 11); +insert into part_EE_FF_1_10 values ('cc', 1); +-- ok +insert into part_EE_FF_1_10 values ('ff', 1); +insert into part_EE_FF_10_20 values ('ff', 11); + +-- cleanup +drop table range_parted cascade; +drop table list_parted cascade; diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql index 5637c68..4997877 100644 --- a/src/test/regress/sql/update.sql +++ b/src/test/regress/sql/update.sql @@ -96,3 +96,24 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a) DROP TABLE update_test; DROP TABLE upsert_test; + +-- update to a partition should check partition bound constraint for the new tuple +create table range_parted ( + a text, + b int +) partition by range (a, b); +create table part_a_1_a_10 partition of range_parted for values start ('a', 1) end ('a', 10); +create table part_a_10_a_20 partition of range_parted for values start ('a', 10) end ('a', 20); +create table part_b_1_b_10 partition of range_parted for values start ('b', 1) end ('b', 10); +create table part_b_10_b_20 partition of range_parted for values start ('b', 10) end ('b', 20); +insert into part_a_1_a_10 values ('a', 1); +insert into part_b_10_b_20 values ('b', 10); + +-- fail +update part_a_1_a_10 set a = 'b' where a = 'a'; +update range_parted set b = b - 1 where b = 10; +-- ok +update range_parted set b = b + 1 where b = 10; + +-- cleanup +drop table range_parted cascade; -- 1.7.1
0006-Introduce-a-PartitionTreeNode-data-structure.patch (text/x-diff, 8.5K) download From e47a4f83a69948cfdaba26914079009696cbeccd Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 27 Jul 2016 15:47:39 +0900 Subject: [PATCH 6/8] Introduce a PartitionTreeNode data structure. It encapsulates the tree structure of a partition hierarchy which can be arbitrarily deeply nested. Every node in the tree represents a partitioned table. The only currently envisioned application is for tuple-routing. --- src/backend/catalog/partition.c | 231 +++++++++++++++++++++++++++++++++++++++ src/include/catalog/partition.h | 6 + 2 files changed, 237 insertions(+), 0 deletions(-) diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index 109d391..70bfd88 100644 --- a/src/backend/catalog/partition.c +++ b/src/backend/catalog/partition.c @@ -110,6 +110,61 @@ typedef struct PartitionInfoData PartitionRangeInfo *range; /* range partition info */ } PartitionInfoData; +/* + * PartitionKeyExecInfo + * + * This struct holds the information needed to extract partition + * column values from a heap tuple. + * + * Key copy of the rd_partkey of rel + * ExpressionState exec state for expressions, or NIL if none + */ +typedef struct PartitionKeyExecInfo +{ + NodeTag type; + PartitionKey pi_Key; + List *pi_ExpressionState; /* list of ExprState */ +} PartitionKeyExecInfo; + +/* + * Partition tree node (corresponding to one partitioned table in the + * partition tree) + * + * pkinfo PartitionKey executor state + * + * pdesc Info about immediate partitions (see + * PartitionDescData) + * + * index If a partition ourselves, index in the parent's + * partition array + * + * num_leaf_partitions Number of leaf partitions in the partition + * tree rooted at this node + * + * offset 0-based index of the first leaf partition + * in the partition tree rooted at this node + * + * downlink Link to our leftmost child node (ie, corresponding + * to first of our partitions that is itself + * partitioned) + * + * next Link to the right sibling node on a given level + * (ie, corresponding to the next partition on the same + * level that is itself partitioned) + */ +typedef struct PartitionTreeNodeData +{ + PartitionKeyExecInfo *pkinfo; + PartitionDesc pdesc; + Oid relid; + int index; + int offset; + int num_leaf_parts; + + struct PartitionTreeNodeData *downlink; + struct PartitionTreeNodeData *next; +} PartitionTreeNodeData; + /* Support RelationBuildPartitionKey() */ static PartitionKey copy_partition_key(PartitionKey fromkey); static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols, @@ -153,6 +208,10 @@ static Oid get_partition_operator(PartitionKey key, int col, StrategyNumber stra /* Support RelationGetPartitionCheckQual() */ static List *generate_partition_check_qual(Relation rel); +/* Support RelationGetPartitionTreeNode() */ +static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset); +static int get_leaf_partition_count(PartitionTreeNode ptnode); + /* List partition related support functions */ static PartitionListInfo *make_list_from_spec(PartitionKey key, PartitionListSpec *list_spec); @@ -815,6 +874,81 @@ RelationGetPartitionCheckQual(Relation rel) return generate_partition_check_qual(rel); } +/* + * RelationGetPartitionTreeNode + * Recursively form partition tree rooted at this rel's node + */ +PartitionTreeNode +RelationGetPartitionTreeNode(Relation rel) +{ + PartitionTreeNode root; + + /* + * We recurse to build the PartitionTreeNodes for any partitions in the + * partition hierarchy that are themselves partitioned. + */ + root = GetPartitionTreeNodeRecurse(rel, 0); + root->index = 0; + root->num_leaf_parts = get_leaf_partition_count(root); + + return root; +} + +/* + * get_leaf_partition_oids + * Returns a list of all leaf-level partitions of relation with OID + * 'relid'. + */ +List * +get_leaf_partition_oids(Oid relid, int lockmode) +{ + List *partitions, + *result = NIL; + ListCell *lc; + + partitions = get_partitions(relid, lockmode); + + foreach(lc, partitions) + { + Oid myoid = lfirst_oid(lc); + + if (relid_is_partitioned(myoid)) + result = list_concat(result, + get_leaf_partition_oids(myoid, lockmode)); + + result = lappend_oid(result, myoid); + } + + return result; +} + +/* + * get_leaf_partition_oids_v2 + * Recursively compute the list of OIDs of leaf partitions in the + * partition tree rooted at ptnode + */ +List * +get_leaf_partition_oids_v2(PartitionTreeNode ptnode) +{ + int i; + List *result = NIL; + PartitionTreeNode node = ptnode->downlink; + + for (i = 0; i < ptnode->pdesc->nparts; i++) + { + /* Indexes 0..(node->index - 1) are leaf partitions */ + if (node && i == node->index) + { + result = list_concat(result, get_leaf_partition_oids_v2(node)); + node = node->next; + } + else + result = lappend_oid(result, ptnode->pdesc->parts[i]->oid); + } + + return result; +} + /* Module-local functions */ /* @@ -1556,6 +1690,103 @@ generate_partition_check_qual(Relation rel) return result; } +/* + * GetPartitionTreeNodeRecurse + * Workhorse of RelationGetPartitionTreeNode + * + * 'offset' is 0-based index of the first leaf node in this subtree. During + * the first invocation, a 0 will be pass + */ +static PartitionTreeNode +GetPartitionTreeNodeRecurse(Relation rel, int offset) +{ + PartitionTreeNode parent, + prev; + int i; + + /* First build our own node */ + parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData)); + parent->pkinfo = NULL; + parent->pdesc = RelationGetPartitionDesc(rel); + parent->relid = RelationGetRelid(rel); + parent->offset = offset; + parent->downlink = NULL; + parent->next = NULL; + + /* + * Go through rel's partitions and recursively add nodes for partitions + * that are themselves partitioned. Link parent to the first child node + * using 'downlink'. Each new child node is linked to its right sibling + * using 'next'. Offset value passed when creating a child node is + * determined by looking at the left node if one exists or the parent + * node if it is the first child node of this level. + */ + prev = NULL; + for (i = 0; i < parent->pdesc->nparts; i++) + { + Oid relid = parent->pdesc->parts[i]->oid; + int offset; + Relation rel; + PartitionTreeNode child; + + /* Skip a leaf partition */ + if (!relid_is_partitioned(relid)) + continue; + + rel = heap_open(relid, AccessShareLock); + + if (prev) + offset = prev->offset + prev->num_leaf_parts + + (i - prev->index - 1); + else + offset = parent->offset + i; + + child = GetPartitionTreeNodeRecurse(rel, offset); + child->index = i; + child->num_leaf_parts = get_leaf_partition_count(child); + + heap_close(rel, AccessShareLock); + + /* Found our first child; link to it. */ + if (parent->downlink == NULL) + parent->downlink = child; + + /* Link new node to the left sibling, if any */ + if (prev) + prev->next = child; + prev = child; + } + + return parent; +} + +/* + * get_leaf_partition_count + * Recursively count the number of leaf partitions in the partition + * tree rooted at ptnode + */ +static int +get_leaf_partition_count(PartitionTreeNode ptnode) +{ + int i; + int result = 0; + PartitionTreeNode node = ptnode->downlink; + + for (i = 0; i < ptnode->pdesc->nparts; i++) + { + /* Indexes 0..(node->index - 1) are of leaf partitions */ + if (node && i == node->index) + { + result += get_leaf_partition_count(node); + node = node->next; + } + else + result += 1; + } + + return result; +} + /* List partition related support functions */ /* diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h index db59c9a..89c22dc 100644 --- a/src/include/catalog/partition.h +++ b/src/include/catalog/partition.h @@ -30,6 +30,7 @@ typedef struct PartitionDescData } PartitionDescData; typedef struct PartitionDescData *PartitionDesc; +typedef struct PartitionTreeNodeData *PartitionTreeNode; /* relcache support for partition key information */ extern void RelationBuildPartitionKey(Relation relation); @@ -56,4 +57,9 @@ extern Oid get_partition_parent(Oid relid); extern List *get_check_qual_from_partbound(Relation rel, Relation parent, Node *bound); extern List *RelationGetPartitionCheckQual(Relation rel); + +/* For tuple routing */ +extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel); +extern List *get_leaf_partition_oids(Oid relid, int lockmode); +extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode); #endif /* PARTITION_H */ -- 1.7.1
0007-Tuple-routing-for-partitioned-tables.patch (text/x-diff, 39.8K) download From f34dd88452ec38035d1e77ee1073b9c9c2503966 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Wed, 27 Jul 2016 16:59:21 +0900 Subject: [PATCH 7/8] Tuple routing for partitioned tables. Both COPY FROM and INSERT. --- src/backend/catalog/partition.c | 339 ++++++++++++++++++++++++++++++- src/backend/commands/copy.c | 198 ++++++++++++++++++- src/backend/commands/tablecmds.c | 1 + src/backend/executor/execMain.c | 46 ++++- src/backend/executor/nodeModifyTable.c | 116 +++++++++++ src/backend/optimizer/plan/createplan.c | 60 ++++++ src/backend/optimizer/util/plancat.c | 13 ++ src/backend/parser/analyze.c | 9 + src/include/catalog/partition.h | 7 + src/include/executor/executor.h | 6 + src/include/nodes/execnodes.h | 10 + src/include/optimizer/plancat.h | 1 + src/test/regress/expected/insert.out | 59 ++++++- src/test/regress/sql/insert.sql | 28 +++ 14 files changed, 884 insertions(+), 9 deletions(-) diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index 70bfd88..569d47b 100644 --- a/src/backend/catalog/partition.c +++ b/src/backend/catalog/partition.c @@ -212,6 +212,18 @@ static List *generate_partition_check_qual(Relation rel); static PartitionTreeNode GetPartitionTreeNodeRecurse(Relation rel, int offset); static int get_leaf_partition_count(PartitionTreeNode ptnode); +/* Support get_partition_for_tuple() */ +static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel); +static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo, + TupleTableSlot *slot, + EState *estate, + Datum *values, + bool *isnull); +static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, + Datum value, bool isnull); +static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, + Datum *tuple); + /* List partition related support functions */ static PartitionListInfo *make_list_from_spec(PartitionKey key, PartitionListSpec *list_spec); @@ -240,6 +252,10 @@ static int32 partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2); static bool partition_range_overlaps(PartitionKey key, PartitionRangeInfo *r1, PartitionRangeInfo *r2); +static bool tuple_rightof_bound(PartitionKey key, Datum *tuple, RangeBound *bound); +static bool tuple_leftof_bound(PartitionKey key, Datum *tuple, RangeBound *bound); +static int range_partition_bsearch(PartitionKey key, PartitionDesc pdesc, + Datum *tuple); /* * Partition key related functions @@ -1706,7 +1722,7 @@ GetPartitionTreeNodeRecurse(Relation rel, int offset) /* First build our own node */ parent = (PartitionTreeNode) palloc0(sizeof(PartitionTreeNodeData)); - parent->pkinfo = NULL; + parent->pkinfo = BuildPartitionKeyExecInfo(rel); parent->pdesc = RelationGetPartitionDesc(rel); parent->relid = RelationGetRelid(rel); parent->offset = offset; @@ -1787,6 +1803,261 @@ get_leaf_partition_count(PartitionTreeNode ptnode) return result; } +/* + * BuildPartitionKeyExecInfo + * Construct a list of PartitionKeyExecInfo records for an open + * relation + * + * PartitionKeyExecInfo stores the information about the partition key + * that's needed when inserting tuples into a partitioned table; especially, + * partition key expression state if there are any expression columns in + * the partition key. Normally we build a PartitionKeyExecInfo for a + * partitioned table just once per command, and then use it for (potentially) + * many tuples. + * + */ +static PartitionKeyExecInfo * +BuildPartitionKeyExecInfo(Relation rel) +{ + PartitionKeyExecInfo *pkinfo; + + pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo)); + pkinfo->pi_Key = copy_partition_key(rel->rd_partkey); + pkinfo->pi_ExpressionState = NIL; + + return pkinfo; +} + +/* + * FormPartitionKeyDatum + * Construct values[] and isnull[] arrays for partition key columns + */ +static void +FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo, + TupleTableSlot *slot, + EState *estate, + Datum *values, + bool *isnull) +{ + ListCell *partexpr_item; + int i; + + if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL) + { + /* First time through, set up expression evaluation state */ + pkinfo->pi_ExpressionState = (List *) + ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs, + estate); + /* Check caller has set up context correctly */ + Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot); + } + + partexpr_item = list_head(pkinfo->pi_ExpressionState); + for (i = 0; i < pkinfo->pi_Key->partnatts; i++) + { + AttrNumber keycol = pkinfo->pi_Key->partattrs[i]; + Datum pkDatum; + bool isNull; + + if (keycol != 0) + { + /* Plain column; get the value directly from the heap tuple */ + pkDatum = slot_getattr(slot, keycol, &isNull); + } + else + { + /* Expression; need to evaluate it */ + if (partexpr_item == NULL) + elog(ERROR, "wrong number of partition key expressions"); + pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item), + GetPerTupleExprContext(estate), + &isNull, + NULL); + partexpr_item = lnext(partexpr_item); + } + values[i] = pkDatum; + isnull[i] = isNull; + } + + if (partexpr_item != NULL) + elog(ERROR, "wrong number of partition key expressions"); +} + +/* + * get_partition_for_tuple + * Recursively finds the "leaf" partition for tuple + * + * Returns -1 if no partition is found and sets *failed_at to the OID of + * the partitioned table whose partition was not found. + */ +int +get_partition_for_tuple(PartitionTreeNode ptnode, + TupleTableSlot *slot, + EState *estate, + Oid *failed_at) +{ + PartitionKeyExecInfo *pkinfo = ptnode->pkinfo; + PartitionTreeNode node; + Datum values[PARTITION_MAX_KEYS]; + bool isnull[PARTITION_MAX_KEYS]; + int i; + int index; + + /* Guard against stack overflow due to overly deep partition tree */ + check_stack_depth(); + + if (ptnode->pdesc->nparts == 0) + { + *failed_at = ptnode->relid; + return -1; + } + + /* Extract partition key from tuple */ + Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot); + FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull); + + /* Disallow nulls, if range partition key */ + for (i = 0; i < pkinfo->pi_Key->partnatts; i++) + if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRAT_RANGE) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("range partition key contains null"))); + + switch (pkinfo->pi_Key->strategy) + { + case PARTITION_STRAT_LIST: + index = list_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc, + values[0], isnull[0]); + break; + + case PARTITION_STRAT_RANGE: + index = range_partition_for_tuple(pkinfo->pi_Key, ptnode->pdesc, + values); + break; + } + + /* No partition found at this level */ + if (index < 0) + { + *failed_at = ptnode->relid; + return index; + } + + /* Don't recurse if the index'th partition is a leaf partition. */ + if (!relid_is_partitioned(ptnode->pdesc->parts[index]->oid)) + { + PartitionTreeNode prev; + + /* + * Index returned above is the array index within pdesc->parts[] of + * the parent rel, however, we want to return the leaf partition index + * across the whole partition tree. Note that some partitions within + * pdesc->parts[] may be partitioned themselves and hence stand for + * the leaf partitions in their partition subtrees. We would need to + * skip past the indexes of leaf partitions of all such partition + * subtrees if they are to left of the above returned index. In fact, + * finding the PartitionTreeNode of the rightmost subtree is enough + * since its offset counts the leaf partitions on its left including + * those of partition subtrees to its left. + */ + prev = node = ptnode->downlink; + if (node && node->index < index) + { + /* + * Find the partition tree node such that its index value is the + * greatest value less than the above returned index. + */ + while (node) + { + if (node->index > index) + { + node = prev; + break; + } + + prev = node; + node = node->next; + } + + if (!node) + node = prev; + Assert (node != NULL); + + return node->offset + node->num_leaf_parts + + (index - node->index - 1); + } + else + /* + * The easy case where we don't have any partition subtree to the + * left of the index. + */ + return ptnode->offset + index; + + } + + /* + * Recurse by locating the index'th partition's node and passing it down + */ + node = ptnode->downlink; + while (node->next != NULL && node->index != index) + node = node->next; + Assert (node != NULL); + + return get_partition_for_tuple(node, slot, estate, failed_at); +} + +/* + * list_partition_for_tuple + * Find the list partition for a tuple + * + * Returns -1 if none found. + */ +static int +list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, + Datum value, bool isnull) +{ + int i; + + Assert(pdesc->nparts > 0); + + for (i = 0; i < pdesc->nparts; i++) + { + int j; + + for (j = 0; j < pdesc->parts[i]->list->nvalues; j++) + { + if (isnull) + { + if (pdesc->parts[i]->list->nulls[j]) + return i; + continue; + } + + if (!pdesc->parts[i]->list->nulls[j] && + partition_list_values_equal(key, + pdesc->parts[i]->list->values[j], + value)) + return i; + } + } + + return -1; +} + +/* + * range_partition_for_tuple + * Search the range partition for a range key ('values') + * + * Returns -1 if none found. + */ +static int +range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *tuple) +{ + Assert(pdesc->nparts > 0); + + return range_partition_bsearch(key, pdesc, tuple); +} + /* List partition related support functions */ /* @@ -2105,3 +2376,69 @@ partition_range_tuple_cmp(PartitionKey key, Datum *val1, Datum *val2) return result; } + +/* + * range_partition_bsearch + * Workhorse of range_partition_for_tuple + */ +static int +range_partition_bsearch(PartitionKey key, PartitionDesc pdesc, + Datum *tuple) +{ + int low, high; + + /* Good ol' bsearch */ + low = 0; + high = pdesc->nparts - 1; + while (low <= high) + { + int idx = (low + high) / 2; + + if (pdesc->parts[idx]->range->upper->infinite) + { + if (tuple_rightof_bound(key, tuple, pdesc->parts[idx]->range->lower)) + return idx; + + break; + } + else if (tuple_leftof_bound(key, tuple, pdesc->parts[idx]->range->upper)) + { + if (pdesc->parts[idx]->range->lower->infinite) + return idx; + + if (tuple_rightof_bound(key, tuple, pdesc->parts[idx]->range->lower)) + return idx; + + high = idx - 1; + continue; + } + + low = idx + 1; + } + + return -1; +} + +/* Does range key lie to the right of partition bound */ +static bool +tuple_rightof_bound(PartitionKey key, Datum *tuple, RangeBound *bound) +{ + int32 cmpval = partition_range_tuple_cmp(key, tuple, bound->val); + + if (!cmpval) + return bound->lower ? bound->inclusive : !bound->inclusive; + + return cmpval > 0; +} + +/* Does range key lie to the left of partition bound */ +static bool +tuple_leftof_bound(PartitionKey key, Datum *tuple, RangeBound *bound) +{ + int32 cmpval = partition_range_tuple_cmp(key, tuple, bound->val); + + if (!cmpval) + return !bound->lower ? bound->inclusive : !bound->inclusive; + + return cmpval < 0; +} diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 76803a5..3741d9f 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -30,6 +30,7 @@ #include "commands/defrem.h" #include "commands/trigger.h" #include "executor/executor.h" +#include "foreign/fdwapi.h" #include "libpq/libpq.h" #include "libpq/pqformat.h" #include "mb/pg_wchar.h" @@ -161,6 +162,11 @@ typedef struct CopyStateData ExprState **defexprs; /* array of default att expressions */ bool volatile_defexprs; /* is any of defexprs volatile? */ List *range_table; + PartitionTreeNode ptnode; /* partition descriptor node tree */ + ResultRelInfo *partitions; + TupleConversionMap **partition_tupconv_maps; + List *partition_fdw_priv_lists; + int num_partitions; /* * These variables are used to reduce overhead in textual COPY FROM. @@ -1364,6 +1370,87 @@ BeginCopy(bool is_from, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("table \"%s\" does not have OIDs", RelationGetRelationName(cstate->rel)))); + + /* + * Initialize state for CopyFrom tuple routing. Watch out for + * any foreign partitions. + */ + if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_REL) + { + List *leaf_part_oids; + ListCell *cell; + int i; + int num_leaf_parts; + ResultRelInfo *leaf_rel_rri; + PlannerInfo *root = makeNode(PlannerInfo); /* mostly dummy */ + Query *parse = makeNode(Query); /* ditto */ + ModifyTable *plan = makeNode(ModifyTable); /* ditto */ + RangeTblEntry *fdw_rte = makeNode(RangeTblEntry); /* ditto */ + List *fdw_private_lists = NIL; + + cstate->ptnode = RelationGetPartitionTreeNode(rel); + leaf_part_oids = get_leaf_partition_oids_v2(cstate->ptnode); + num_leaf_parts = list_length(leaf_part_oids); + + cstate->num_partitions = num_leaf_parts; + cstate->partitions = (ResultRelInfo *) + palloc0(num_leaf_parts * sizeof(ResultRelInfo)); + cstate->partition_tupconv_maps = (TupleConversionMap **) + palloc0(num_leaf_parts * sizeof(TupleConversionMap *)); + + /* For use below, iff a partition found to be a foreign table */ + plan->operation = CMD_INSERT; + plan->plans = list_make1(makeNode(Result)); + fdw_rte->rtekind = RTE_RELATION; + fdw_rte->relkind = RELKIND_FOREIGN_TABLE; + parse->rtable = list_make1(fdw_rte); + root->parse = parse; + + leaf_rel_rri = cstate->partitions; + i = 0; + foreach(cell, leaf_part_oids) + { + Relation leaf_rel; + + leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock); + InitResultRelInfo(leaf_rel_rri, + leaf_rel, + 1, /* dummy */ + false, /* no need for partition check */ + 0); + + /* Open partition indices */ + ExecOpenIndices(leaf_rel_rri, false); + + /* Special dance for foreign tables */ + if (leaf_rel_rri->ri_FdwRoutine) + { + List *fdw_private; + + fdw_rte->relid = RelationGetRelid(leaf_rel); + fdw_private = leaf_rel_rri->ri_FdwRoutine->PlanForeignModify(root, + plan, + 1, + 0); + fdw_private_lists = lappend(fdw_private_lists, fdw_private); + } + + if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel))) + cstate->partition_tupconv_maps[i] = + convert_tuples_by_name(tupDesc, + RelationGetDescr(leaf_rel), + gettext_noop("could not convert row type")); + + leaf_rel_rri++; + i++; + } + + cstate->partition_fdw_priv_lists = fdw_private_lists; + pfree(fdw_rte); + pfree(plan); + pfree(parse); + pfree(root); + } } else { @@ -1659,6 +1746,8 @@ ClosePipeToProgram(CopyState cstate) static void EndCopy(CopyState cstate) { + int i; + if (cstate->is_program) { ClosePipeToProgram(cstate); @@ -1672,6 +1761,23 @@ EndCopy(CopyState cstate) cstate->filename))); } + /* Close all partitions and indices thereof */ + for (i = 0; i < cstate->num_partitions; i++) + { + ResultRelInfo *resultRelInfo = cstate->partitions + i; + + ExecCloseIndices(resultRelInfo); + heap_close(resultRelInfo->ri_RelationDesc, NoLock); + + /* XXX - EState not handy here to pass to EndForeignModify() */ + if (resultRelInfo->ri_FdwRoutine && + resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL) + resultRelInfo->ri_FdwRoutine->EndForeignModify(NULL, resultRelInfo); + + if (cstate->partition_tupconv_maps[i]) + pfree(cstate->partition_tupconv_maps[i]); + } + MemoryContextDelete(cstate->copycontext); pfree(cstate); } @@ -2216,6 +2322,7 @@ CopyFrom(CopyState cstate) Datum *values; bool *nulls; ResultRelInfo *resultRelInfo; + ResultRelInfo *saved_resultRelInfo = NULL; EState *estate = CreateExecutorState(); /* for ExecConstraints() */ ExprContext *econtext; TupleTableSlot *myslot; @@ -2236,7 +2343,8 @@ CopyFrom(CopyState cstate) Assert(cstate->rel); - if (cstate->rel->rd_rel->relkind != RELKIND_RELATION) + if (cstate->rel->rd_rel->relkind != RELKIND_RELATION && + cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_REL) { if (cstate->rel->rd_rel->relkind == RELKIND_VIEW) ereport(ERROR, @@ -2344,6 +2452,7 @@ CopyFrom(CopyState cstate) InitResultRelInfo(resultRelInfo, cstate->rel, 1, /* dummy rangetable index */ + true, /* do load partition check expression */ 0); ExecOpenIndices(resultRelInfo, false); @@ -2371,6 +2480,7 @@ CopyFrom(CopyState cstate) if ((resultRelInfo->ri_TrigDesc != NULL && (resultRelInfo->ri_TrigDesc->trig_insert_before_row || resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) || + cstate->ptnode != NULL || cstate->volatile_defexprs) { useHeapMultiInsert = false; @@ -2392,10 +2502,46 @@ CopyFrom(CopyState cstate) */ ExecBSInsertTriggers(estate, resultRelInfo); + /* Initialize FDW partition insert plans */ + if (cstate->ptnode) + { + int i, + j; + List *fdw_private_lists = cstate->partition_fdw_priv_lists; + ModifyTableState *mtstate = makeNode(ModifyTableState); + ResultRelInfo *leaf_part_rri; + + /* Mostly dummy containing enough state for BeginForeignModify */ + mtstate->ps.state = estate; + mtstate->operation = CMD_INSERT; + + j = 0; + leaf_part_rri = cstate->partitions; + for (i = 0; i < cstate->num_partitions; i++) + { + if (leaf_part_rri->ri_FdwRoutine) + { + List *fdw_private; + + Assert(fdw_private_lists); + fdw_private = list_nth(fdw_private_lists, j++); + leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate, + leaf_part_rri, + fdw_private, + 0, 0); + } + leaf_part_rri++; + } + } + values = (Datum *) palloc(tupDesc->natts * sizeof(Datum)); nulls = (bool *) palloc(tupDesc->natts * sizeof(bool)); - bistate = GetBulkInsertState(); + if (useHeapMultiInsert) + bistate = GetBulkInsertState(); + else + bistate = NULL; + econtext = GetPerTupleExprContext(estate); /* Set up callback to identify error line number */ @@ -2447,6 +2593,31 @@ CopyFrom(CopyState cstate) slot = myslot; ExecStoreTuple(tuple, slot, InvalidBuffer, false); + /* Determine the partition */ + saved_resultRelInfo = resultRelInfo; + if (cstate->ptnode) + { + int i_leaf_partition; + TupleConversionMap *map; + + econtext->ecxt_scantuple = slot; + i_leaf_partition = ExecFindPartition(resultRelInfo, + cstate->ptnode, + slot, + estate); + Assert(i_leaf_partition >= 0 && + i_leaf_partition < cstate->num_partitions); + + resultRelInfo = cstate->partitions + i_leaf_partition; + estate->es_result_relation_info = resultRelInfo; + + map = cstate->partition_tupconv_maps[i_leaf_partition]; + if (map) + tuple = do_convert_tuple(tuple, map); + + tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc); + } + skip_tuple = false; /* BEFORE ROW INSERT Triggers */ @@ -2467,7 +2638,16 @@ CopyFrom(CopyState cstate) if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck) ExecConstraints(resultRelInfo, slot, estate); - if (useHeapMultiInsert) + if (resultRelInfo->ri_FdwRoutine) + { + resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate, + resultRelInfo, + slot, + NULL); + /* AFTER ROW INSERT Triggers */ + ExecARInsertTriggers(estate, resultRelInfo, tuple, NIL); + } + else if (useHeapMultiInsert) { /* Add this tuple to the tuple buffer */ if (nBufferedTuples == 0) @@ -2497,7 +2677,8 @@ CopyFrom(CopyState cstate) List *recheckIndexes = NIL; /* OK, store the tuple and create index entries for it */ - heap_insert(cstate->rel, tuple, mycid, hi_options, bistate); + heap_insert(resultRelInfo->ri_RelationDesc, + tuple, mycid, hi_options, bistate); if (resultRelInfo->ri_NumIndices > 0) recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), @@ -2517,6 +2698,12 @@ CopyFrom(CopyState cstate) * tuples inserted by an INSERT command. */ processed++; + + if (saved_resultRelInfo) + { + resultRelInfo = saved_resultRelInfo; + estate->es_result_relation_info = resultRelInfo; + } } } @@ -2530,7 +2717,8 @@ CopyFrom(CopyState cstate) /* Done, clean up */ error_context_stack = errcallback.previous; - FreeBulkInsertState(bistate); + if (bistate) + FreeBulkInsertState(bistate); MemoryContextSwitchTo(oldcontext); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 361b5ea..90d9bc9 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -1253,6 +1253,7 @@ ExecuteTruncate(TruncateStmt *stmt) InitResultRelInfo(resultRelInfo, rel, 0, /* dummy rangetable index */ + false, 0); resultRelInfo++; } diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 489ff02..a411ba3 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags) InitResultRelInfo(resultRelInfo, resultRelation, resultRelationIndex, + true, estate->es_instrument); resultRelInfo++; } @@ -1215,6 +1216,7 @@ void InitResultRelInfo(ResultRelInfo *resultRelInfo, Relation resultRelationDesc, Index resultRelationIndex, + bool load_partition_check, int instrument_options) { MemSet(resultRelInfo, 0, sizeof(ResultRelInfo)); @@ -1252,8 +1254,9 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo, resultRelInfo->ri_ConstraintExprs = NULL; resultRelInfo->ri_junkFilter = NULL; resultRelInfo->ri_projectReturning = NULL; - resultRelInfo->ri_PartitionCheck = - RelationGetPartitionCheckQual(resultRelationDesc); + if (load_partition_check) + resultRelInfo->ri_PartitionCheck = + RelationGetPartitionCheckQual(resultRelationDesc); } /* @@ -1316,6 +1319,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid) InitResultRelInfo(rInfo, rel, 0, /* dummy rangetable index */ + true, estate->es_instrument); estate->es_trig_target_relations = lappend(estate->es_trig_target_relations, rInfo); @@ -2997,3 +3001,41 @@ EvalPlanQualEnd(EPQState *epqstate) epqstate->planstate = NULL; epqstate->origslot = NULL; } + +int +ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionTreeNode ptnode, + TupleTableSlot *slot, EState *estate) +{ + int i_leaf_partition; + Oid failed_at; + + i_leaf_partition = get_partition_for_tuple(ptnode, slot, estate, + &failed_at); + + if (i_leaf_partition < 0) + { + Relation rel = resultRelInfo->ri_RelationDesc; + char *val_desc; + Bitmapset *insertedCols, + *updatedCols, + *modifiedCols; + TupleDesc tupDesc = RelationGetDescr(rel); + + insertedCols = GetInsertedColumns(resultRelInfo, estate); + updatedCols = GetUpdatedColumns(resultRelInfo, estate); + modifiedCols = bms_union(insertedCols, updatedCols); + val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), + slot, + tupDesc, + modifiedCols, + 64); + Assert(OidIsValid(failed_at)); + ereport(ERROR, + (errcode(ERRCODE_CHECK_VIOLATION), + errmsg("no partition of relation \"%s\" found for row", + get_rel_name(failed_at)), + val_desc ? errdetail("Failing row contains %s.", val_desc) : 0)); + } + + return i_leaf_partition; +} diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 3ed321e..94b757b 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -243,6 +243,7 @@ ExecInsert(ModifyTableState *mtstate, { HeapTuple tuple; ResultRelInfo *resultRelInfo; + ResultRelInfo *saved_resultRelInfo = NULL; Relation resultRelationDesc; Oid newId; List *recheckIndexes = NIL; @@ -257,6 +258,31 @@ ExecInsert(ModifyTableState *mtstate, * get information on the (current) result relation */ resultRelInfo = estate->es_result_relation_info; + + saved_resultRelInfo = resultRelInfo; + + if (mtstate->mt_partition_tree_root) + { + int i_leaf_partition; + ExprContext *econtext = GetPerTupleExprContext(estate); + TupleConversionMap *map; + + econtext->ecxt_scantuple = slot; + i_leaf_partition = ExecFindPartition(resultRelInfo, + mtstate->mt_partition_tree_root, + slot, + estate); + Assert(i_leaf_partition >= 0 && + i_leaf_partition < mtstate->mt_num_partitions); + + resultRelInfo = mtstate->mt_partitions + i_leaf_partition; + estate->es_result_relation_info = resultRelInfo; + + map = mtstate->mt_partition_tupconv_maps[i_leaf_partition]; + if (map) + tuple = do_convert_tuple(tuple, map); + } + resultRelationDesc = resultRelInfo->ri_RelationDesc; /* @@ -496,6 +522,12 @@ ExecInsert(ModifyTableState *mtstate, list_free(recheckIndexes); + if (saved_resultRelInfo) + { + resultRelInfo = saved_resultRelInfo; + estate->es_result_relation_info = resultRelInfo; + } + /* * Check any WITH CHECK OPTION constraints from parent views. We are * required to do this after testing all constraints and uniqueness @@ -1550,6 +1582,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) Plan *subplan; ListCell *l; int i; + Relation rel; /* check for unsupported flags */ Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); @@ -1640,6 +1673,72 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) estate->es_result_relation_info = saved_resultRelInfo; + /* Build state for INSERT tuple routing */ + rel = mtstate->resultRelInfo->ri_RelationDesc; + if (operation == CMD_INSERT && + rel->rd_rel->relkind == RELKIND_PARTITIONED_REL) + { + int i, + j, + num_leaf_parts; + List *leaf_part_oids; + ListCell *cell; + ResultRelInfo *leaf_rel_rri; + + mtstate->mt_partition_tree_root = RelationGetPartitionTreeNode(rel); + leaf_part_oids = get_leaf_partition_oids_v2(mtstate->mt_partition_tree_root); + num_leaf_parts = list_length(leaf_part_oids); + + mtstate->mt_num_partitions = num_leaf_parts; + mtstate->mt_partitions = (ResultRelInfo *) + palloc0(num_leaf_parts * sizeof(ResultRelInfo)); + mtstate->mt_partition_tupconv_maps = (TupleConversionMap **) + palloc0(num_leaf_parts * sizeof(TupleConversionMap *)); + + leaf_rel_rri = mtstate->mt_partitions; + i = j = 0; + foreach(cell, leaf_part_oids) + { + Relation leaf_rel; + + leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock); + InitResultRelInfo(leaf_rel_rri, + leaf_rel, + 1, /* dummy */ + false, /* no need for partition checks */ + eflags); + + /* Open partition indices (note: ON CONFLICT unsupported)*/ + if (leaf_rel_rri->ri_RelationDesc->rd_rel->relhasindex && + operation != CMD_DELETE && + leaf_rel_rri->ri_IndexRelationDescs == NULL) + ExecOpenIndices(leaf_rel_rri, false); + + if (leaf_rel_rri->ri_FdwRoutine) + { + /* As many fdw_private's in fdwPrivLists as FDW partitions */ + List *fdw_private = (List *) list_nth(node->fdwPrivLists, j); + + leaf_rel_rri->ri_FdwRoutine->BeginForeignModify(mtstate, + leaf_rel_rri, + fdw_private, + 0, + eflags); + j++; + } + + if (!equalTupleDescs(RelationGetDescr(rel), + RelationGetDescr(leaf_rel))) + mtstate->mt_partition_tupconv_maps[i] = + convert_tuples_by_name(RelationGetDescr(rel), + RelationGetDescr(leaf_rel), + gettext_noop("could not convert row type")); + + leaf_rel_rri++; + i++; + } + } + /* * Initialize any WITH CHECK OPTION constraints if needed. */ @@ -1957,6 +2056,23 @@ ExecEndModifyTable(ModifyTableState *node) resultRelInfo); } + /* Close all partitions and indices thereof */ + for (i = 0; i < node->mt_num_partitions; i++) + { + ResultRelInfo *resultRelInfo = node->mt_partitions + i; + + ExecCloseIndices(resultRelInfo); + heap_close(resultRelInfo->ri_RelationDesc, NoLock); + + if (resultRelInfo->ri_FdwRoutine && + resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL) + resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state, + resultRelInfo); + + if (node->mt_partition_tupconv_maps[i]) + pfree(node->mt_partition_tupconv_maps[i]); + } + /* * Free the exprcontext */ diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 54d601f..9c60ed5 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -22,6 +22,7 @@ #include "access/stratnum.h" #include "access/sysattr.h" #include "catalog/pg_class.h" +#include "catalog/pg_partitioned_fn.h" #include "foreign/fdwapi.h" #include "miscadmin.h" #include "nodes/extensible.h" @@ -6151,6 +6152,65 @@ make_modifytable(PlannerInfo *root, node->fdwPrivLists = fdw_private_list; node->fdwDirectModifyPlans = direct_modify_plans; + /* Collect insert plans for all FDW-managed partitions */ + if (node->operation == CMD_INSERT) + { + RangeTblEntry *rte, + **saved_simple_rte_array; + List *partition_oids; + + Assert(list_length(resultRelations) == 1); + rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable); + Assert(rte->rtekind == RTE_RELATION); + + if (!relid_is_partitioned(rte->relid)) + return node; + + partition_oids = get_leaf_partition_oids(rte->relid, NoLock); + + /* Discard any previous content which is useless anyway */ + fdw_private_list = NIL; + + /* To force FDW driver fetch the intended RTE */ + saved_simple_rte_array = root->simple_rte_array; + root->simple_rte_array = (RangeTblEntry **) + palloc0(2 * sizeof(RangeTblEntry *)); + foreach(lc, partition_oids) + { + Oid myoid = lfirst_oid(lc); + FdwRoutine *fdwroutine; + List *fdw_private; + + if (!oid_is_foreign_table(myoid)) + continue; + + fdwroutine = GetFdwRoutineByRelId(myoid); + if (fdwroutine && fdwroutine->PlanForeignModify) + { + RangeTblEntry *fdw_rte; + + fdw_rte = copyObject(rte); + fdw_rte->relid = myoid; + fdw_rte->relkind = RELKIND_FOREIGN_TABLE; + + /* Assumes PlanForeignModify() uses planner_rt_fetch(). */ + root->simple_rte_array[1] = fdw_rte; + + fdw_private = fdwroutine->PlanForeignModify(root, node, 1, 0); + pfree(fdw_rte); + } + else + fdw_private = NIL; + + fdw_private_list = lappend(fdw_private_list, fdw_private); + } + + pfree(root->simple_rte_array); + root->simple_rte_array = saved_simple_rte_array; + + node->fdwPrivLists = fdw_private_list; + } + return node; } diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 249a5aa..b623209 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1703,3 +1703,16 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event) heap_close(relation, NoLock); return result; } + +bool +oid_is_foreign_table(Oid relid) +{ + Relation rel; + char relkind; + + rel = heap_open(relid, NoLock); + relkind = rel->rd_rel->relkind; + heap_close(rel, NoLock); + + return relkind == RELKIND_FOREIGN_TABLE; +} diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index eac86cc..e4a4dd1 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -25,6 +25,7 @@ #include "postgres.h" #include "access/sysattr.h" +#include "catalog/pg_partitioned_fn.h" #include "catalog/pg_type.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -797,8 +798,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) /* Process ON CONFLICT, if any. */ if (stmt->onConflictClause) + { + /* Bail out if target relation is partitioned table */ + if (relid_is_partitioned(pstate->p_target_rangetblentry->relid)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ON CONFLICT clause is not supported with partitioned tables"))); + qry->onConflict = transformOnConflictClause(pstate, stmt->onConflictClause); + } /* * If we have a RETURNING clause, we need to add the target relation to diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h index 89c22dc..c3180b7 100644 --- a/src/include/catalog/partition.h +++ b/src/include/catalog/partition.h @@ -14,6 +14,8 @@ #define PARTITION_H #include "fmgr.h" +#include "executor/tuptable.h" +#include "nodes/execnodes.h" #include "parser/parse_node.h" #include "utils/relcache.h" @@ -62,4 +64,9 @@ extern List *RelationGetPartitionCheckQual(Relation rel); extern PartitionTreeNode RelationGetPartitionTreeNode(Relation rel); extern List *get_leaf_partition_oids(Oid relid, int lockmode); extern List *get_leaf_partition_oids_v2(PartitionTreeNode ptnode); + +extern int get_partition_for_tuple(PartitionTreeNode ptnode, + TupleTableSlot *slot, + EState *estate, + Oid *failed_at); #endif /* PARTITION_H */ diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 39521ed..93a9cf3 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -14,6 +14,7 @@ #ifndef EXECUTOR_H #define EXECUTOR_H +#include "catalog/partition.h" #include "executor/execdesc.h" #include "nodes/parsenodes.h" @@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation); extern void InitResultRelInfo(ResultRelInfo *resultRelInfo, Relation resultRelationDesc, Index resultRelationIndex, + bool load_partition_check, int instrument_options); extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid); extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids); @@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate, extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti, HeapTuple tuple); extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti); +extern int ExecFindPartition(ResultRelInfo *resultRelInfo, + PartitionTreeNode ptnode, + TupleTableSlot *slot, + EState *estate); #define EvalPlanQualSetSlot(epqstate, slot) ((epqstate)->origslot = (slot)) extern void EvalPlanQualFetchRowMarks(EPQState *epqstate); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 31ca9ed..8943172 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -16,6 +16,7 @@ #include "access/genam.h" #include "access/heapam.h" +#include "access/tupconvert.h" #include "executor/instrument.h" #include "lib/pairingheap.h" #include "nodes/params.h" @@ -1140,6 +1141,15 @@ typedef struct ModifyTableState * tlist */ TupleTableSlot *mt_conflproj; /* CONFLICT ... SET ... projection * target */ + struct PartitionTreeNodeData *mt_partition_tree_root; + /* Partition descriptor node tree */ + ResultRelInfo *mt_partitions; /* Per leaf partition target + * relations */ + TupleConversionMap **mt_partition_tupconv_maps; + /* Per leaf partition + * tuple conversion map */ + int mt_num_partitions; /* Number of leaf partition target + * relations in the above array */ } ModifyTableState; /* ---------------- diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index 125274e..fac606c 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -56,5 +56,6 @@ extern Selectivity join_selectivity(PlannerInfo *root, SpecialJoinInfo *sjinfo); extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event); +extern bool oid_is_foreign_table(Oid relid); #endif /* PLANCAT_H */ diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out index 589efb8..760ec87 100644 --- a/src/test/regress/expected/insert.out +++ b/src/test/regress/expected/insert.out @@ -222,6 +222,62 @@ DETAIL: Failing row contains (cc, 1). -- ok insert into part_EE_FF_1_10 values ('ff', 1); insert into part_EE_FF_10_20 values ('ff', 11); +-- Check tuple routing for partitioned tables +-- fail +insert into range_parted values ('a', 0); +ERROR: no partition of relation "range_parted" found for row +DETAIL: Failing row contains (a, 0). +-- ok +insert into range_parted values ('a', 1); +insert into range_parted values ('a', 10); +-- fail +insert into range_parted values ('a', 20); +ERROR: no partition of relation "range_parted" found for row +DETAIL: Failing row contains (a, 20). +-- ok +insert into range_parted values ('b', 1); +insert into range_parted values ('b', 10); +select tableoid::regclass, * from range_parted; + tableoid | a | b +----------------+---+---- + part_a_1_a_10 | a | 1 + part_a_1_a_10 | a | 1 + part_a_10_a_20 | a | 10 + part_b_1_b_10 | b | 1 + part_b_10_b_20 | b | 10 + part_b_10_b_20 | b | 10 +(6 rows) + +-- fail (no list partition defined which accepts nulls) +insert into list_parted (b) values (1); +ERROR: no partition of relation "list_parted" found for row +DETAIL: Failing row contains (null, 1). +create table part_nulls partition of list_parted for values in (null); +-- ok +insert into list_parted (b) values (1); +insert into list_parted (a) values ('aA'); +-- fail (partition of part_EE_FF not found) +insert into list_parted values ('EE', 0); +ERROR: no partition of relation "part_ee_ff" found for row +DETAIL: Failing row contains (EE, 0). +insert into part_EE_FF values ('EE', 0); +ERROR: no partition of relation "part_ee_ff" found for row +DETAIL: Failing row contains (EE, 0). +-- ok +insert into list_parted values ('EE', 1); +insert into part_EE_FF values ('EE', 10); +select tableoid::regclass, * from list_parted; + tableoid | a | b +------------------+----+---- + part_aa_bb | aA | + part_cc_dd | cC | 1 + part_nulls | | 1 + part_ee_ff_1_10 | ff | 1 + part_ee_ff_1_10 | EE | 1 + part_ee_ff_10_20 | ff | 11 + part_ee_ff_10_20 | EE | 10 +(7 rows) + -- cleanup drop table range_parted cascade; NOTICE: drop cascades to 4 other objects @@ -230,9 +286,10 @@ drop cascades to table part_a_10_a_20 drop cascades to table part_b_1_b_10 drop cascades to table part_b_10_b_20 drop table list_parted cascade; -NOTICE: drop cascades to 5 other objects +NOTICE: drop cascades to 6 other objects DETAIL: drop cascades to table part_aa_bb drop cascades to table part_cc_dd drop cascades to partitioned table part_ee_ff drop cascades to table part_ee_ff_1_10 drop cascades to table part_ee_ff_10_20 +drop cascades to table part_nulls diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql index 4bf042e..d1b5a09 100644 --- a/src/test/regress/sql/insert.sql +++ b/src/test/regress/sql/insert.sql @@ -137,6 +137,34 @@ insert into part_EE_FF_1_10 values ('cc', 1); insert into part_EE_FF_1_10 values ('ff', 1); insert into part_EE_FF_10_20 values ('ff', 11); +-- Check tuple routing for partitioned tables + +-- fail +insert into range_parted values ('a', 0); +-- ok +insert into range_parted values ('a', 1); +insert into range_parted values ('a', 10); +-- fail +insert into range_parted values ('a', 20); +-- ok +insert into range_parted values ('b', 1); +insert into range_parted values ('b', 10); +select tableoid::regclass, * from range_parted; + +-- fail (no list partition defined which accepts nulls) +insert into list_parted (b) values (1); +create table part_nulls partition of list_parted for values in (null); +-- ok +insert into list_parted (b) values (1); +insert into list_parted (a) values ('aA'); +-- fail (partition of part_EE_FF not found) +insert into list_parted values ('EE', 0); +insert into part_EE_FF values ('EE', 0); +-- ok +insert into list_parted values ('EE', 1); +insert into part_EE_FF values ('EE', 10); +select tableoid::regclass, * from list_parted; + -- cleanup drop table range_parted cascade; drop table list_parted cascade; -- 1.7.1
0008-Update-DDL-Partitioning-chapter-to-reflect-new-devel.patch (text/x-diff, 24.7K) download From f0955db793a105c326f598281eb45868be8e0045 Mon Sep 17 00:00:00 2001 From: amit <amitlangote09@gmail.com> Date: Thu, 28 Jul 2016 13:40:02 +0900 Subject: [PATCH 8/8] Update DDL Partitioning chapter to reflect new developments. --- doc/src/sgml/ddl.sgml | 402 ++++++++++--------------------------------------- 1 files changed, 83 insertions(+), 319 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 954c3a9..4196695 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -2761,7 +2761,7 @@ VALUES ('Albany', NULL, NULL, 'NY'); <para> Bulk loads and deletes can be accomplished by adding or removing partitions, if that requirement is planned into the partitioning design. - <command>ALTER TABLE NO INHERIT</> and <command>DROP TABLE</> are + <command>ALTER TABLE DETACH PARTITION</> and <command>DROP TABLE</> are both far faster than a bulk operation. These commands also entirely avoid the <command>VACUUM</command> overhead caused by a bulk <command>DELETE</>. @@ -2783,12 +2783,15 @@ VALUES ('Albany', NULL, NULL, 'NY'); </para> <para> - Currently, <productname>PostgreSQL</productname> supports partitioning - via table inheritance. Each partition must be created as a child - table of a single parent table. The parent table itself is normally - empty; it exists just to represent the entire data set. You should be - familiar with inheritance (see <xref linkend="ddl-inherit">) before - attempting to set up partitioning. + Currently, <productname>PostgreSQL</productname> provides a way to + specify the partition key of table along with two methods of partitioning + to choose from. Individual partitions of a partitioned table are created + using separate <literal>CREATE TABLE</> commands where you must specify + the partition bound such that it does not overlap with any existing + partitions of the parent table. The parent table itself is empty; + it exists just to represent the entire data set. See <xref + linkend="sql-createtable"> and <xref linkend="sql-createforeigntable"> + for more details on the exact syntax to use for above mentioned commands. </para> <para> @@ -2832,59 +2835,22 @@ VALUES ('Albany', NULL, NULL, 'NY'); <orderedlist spacing="compact"> <listitem> <para> - Create the <quote>master</quote> table, from which all of the - partitions will inherit. + Create the <quote>partitioned</quote> table. </para> <para> This table will contain no data. Do not define any check constraints on this table, unless you intend them to be applied equally to all partitions. There is no point - in defining any indexes or unique constraints on it, either. + in defining any indexes or unique constraints on it, either, + since the notion of global uniqueness is not yet implemented. </para> </listitem> <listitem> <para> - Create several <quote>child</quote> tables that each inherit from - the master table. Normally, these tables will not add any columns - to the set inherited from the master. - </para> - - <para> - We will refer to the child tables as partitions, though they - are in every way normal <productname>PostgreSQL</> tables - (or, possibly, foreign tables). - </para> - </listitem> - - <listitem> - <para> - Add table constraints to the partition tables to define the - allowed key values in each partition. - </para> - - <para> - Typical examples would be: -<programlisting> -CHECK ( x = 1 ) -CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' )) -CHECK ( outletID &gt;= 100 AND outletID &lt; 200 ) -</programlisting> - Ensure that the constraints guarantee that there is no overlap - between the key values permitted in different partitions. A common - mistake is to set up range constraints like: -<programlisting> -CHECK ( outletID BETWEEN 100 AND 200 ) -CHECK ( outletID BETWEEN 200 AND 300 ) -</programlisting> - This is wrong since it is not clear which partition the key value - 200 belongs in. - </para> - - <para> - Note that there is no difference in - syntax between range and list partitioning; those terms are - descriptive only. + Create several <quote>partitions</quote> of the above created + partitioned table. Partitions are in every way normal + <productname>PostgreSQL</> tables (or, possibly, foreign tables). </para> </listitem> @@ -2901,8 +2867,10 @@ CHECK ( outletID BETWEEN 200 AND 300 ) <listitem> <para> - Optionally, define a trigger or rule to redirect data inserted into - the master table to the appropriate partition. + Note that a data row inserted into the master table will be mapped + to and stored in the appropriate partition. If some row does not + fall within any of existing partitions, an error will be thrown. + You must create the missing partition explicitly. </para> </listitem> @@ -2930,7 +2898,7 @@ CREATE TABLE measurement ( logdate date not null, peaktemp int, unitsales int -); +) PARTITION BY RANGE (logdate); </programlisting> We know that most queries will access just the last week's, month's or @@ -2961,12 +2929,12 @@ CREATE TABLE measurement ( Next we create one partition for each active month: <programlisting> -CREATE TABLE measurement_y2006m02 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2016m07 PARTITION OF measurement FOR VALUES START ('2016-07-01') END ('2016-08-01'); +CREATE TABLE measurement_y2016m08 PARTITION OF measurement FOR VALUES START ('2016-08-01') END ('2016-09-01'); ... -CREATE TABLE measurement_y2007m11 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( ) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); +CREATE TABLE measurement_y2017m04 PARTITION OF measurement FOR VALUES START ('2017-04-01') END ('2017-05-01'); +CREATE TABLE measurement_y2017m05 PARTITION OF measurement FOR VALUES START ('2017-05-01') END ('2017-06-01'); +CREATE TABLE measurement_y2017m06 PARTITION OF measurement FOR VALUES START ('2017-06-01') END ('2017-07-01'); </programlisting> Each of the partitions are complete tables in their own right, @@ -2976,36 +2944,9 @@ CREATE TABLE measurement_y2008m01 ( ) INHERITS (measurement); <para> This solves one of our problems: deleting old data. Each - month, all we will need to do is perform a <command>DROP - TABLE</command> on the oldest child table and create a new - child table for the new month's data. - </para> - </listitem> - - <listitem> - <para> - We must provide non-overlapping table constraints. Rather than - just creating the partition tables as above, the table creation - script should really be: - -<programlisting> -CREATE TABLE measurement_y2006m02 ( - CHECK ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' ) -) INHERITS (measurement); -CREATE TABLE measurement_y2006m03 ( - CHECK ( logdate &gt;= DATE '2006-03-01' AND logdate &lt; DATE '2006-04-01' ) -) INHERITS (measurement); -... -CREATE TABLE measurement_y2007m11 ( - CHECK ( logdate &gt;= DATE '2007-11-01' AND logdate &lt; DATE '2007-12-01' ) -) INHERITS (measurement); -CREATE TABLE measurement_y2007m12 ( - CHECK ( logdate &gt;= DATE '2007-12-01' AND logdate &lt; DATE '2008-01-01' ) -) INHERITS (measurement); -CREATE TABLE measurement_y2008m01 ( - CHECK ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' ) -) INHERITS (measurement); -</programlisting> + month, all we will need to do is perform a <command>ALTER TABLE + measurement DETACH PARTITION</command> on the oldest child table + and create a new partition for the new month's data. </para> </listitem> @@ -3014,110 +2955,19 @@ CREATE TABLE measurement_y2008m01 ( We probably need indexes on the key columns too: <programlisting> -CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate); -CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate); +CREATE INDEX measurement_y2016m07_logdate ON measurement_y2016m07 (logdate); +CREATE INDEX measurement_y2016m08_logdate ON measurement_y2016m08 (logdate); ... -CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate); -CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate); -CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate); +CREATE INDEX measurement_y2017m04_logdate ON measurement_y2017m04 (logdate); +CREATE INDEX measurement_y2017m05_logdate ON measurement_y2017m05 (logdate); +CREATE INDEX measurement_y2017m06_logdate ON measurement_y2017m06 (logdate); </programlisting> We choose not to add further indexes at this time. </para> </listitem> - - <listitem> - <para> - We want our application to be able to say <literal>INSERT INTO - measurement ...</> and have the data be redirected into the - appropriate partition table. We can arrange that by attaching - a suitable trigger function to the master table. - If data will be added only to the latest partition, we can - use a very simple trigger function: - -<programlisting> -CREATE OR REPLACE FUNCTION measurement_insert_trigger() -RETURNS TRIGGER AS $$ -BEGIN - INSERT INTO measurement_y2008m01 VALUES (NEW.*); - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -</programlisting> - - After creating the function, we create a trigger which - calls the trigger function: - -<programlisting> -CREATE TRIGGER insert_measurement_trigger - BEFORE INSERT ON measurement - FOR EACH ROW EXECUTE PROCEDURE measurement_insert_trigger(); -</programlisting> - - We must redefine the trigger function each month so that it always - points to the current partition. The trigger definition does - not need to be updated, however. - </para> - - <para> - We might want to insert data and have the server automatically - locate the partition into which the row should be added. We - could do this with a more complex trigger function, for example: - -<programlisting> -CREATE OR REPLACE FUNCTION measurement_insert_trigger() -RETURNS TRIGGER AS $$ -BEGIN - IF ( NEW.logdate &gt;= DATE '2006-02-01' AND - NEW.logdate &lt; DATE '2006-03-01' ) THEN - INSERT INTO measurement_y2006m02 VALUES (NEW.*); - ELSIF ( NEW.logdate &gt;= DATE '2006-03-01' AND - NEW.logdate &lt; DATE '2006-04-01' ) THEN - INSERT INTO measurement_y2006m03 VALUES (NEW.*); - ... - ELSIF ( NEW.logdate &gt;= DATE '2008-01-01' AND - NEW.logdate &lt; DATE '2008-02-01' ) THEN - INSERT INTO measurement_y2008m01 VALUES (NEW.*); - ELSE - RAISE EXCEPTION 'Date out of range. Fix the measurement_insert_trigger() function!'; - END IF; - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -</programlisting> - - The trigger definition is the same as before. - Note that each <literal>IF</literal> test must exactly match the - <literal>CHECK</literal> constraint for its partition. - </para> - - <para> - While this function is more complex than the single-month case, - it doesn't need to be updated as often, since branches can be - added in advance of being needed. - </para> - - <note> - <para> - In practice it might be best to check the newest partition first, - if most inserts go into that partition. For simplicity we have - shown the trigger's tests in the same order as in other parts - of this example. - </para> - </note> - </listitem> </orderedlist> </para> - - <para> - As we can see, a complex partitioning scheme could require a - substantial amount of DDL. In the above example we would be - creating a new partition each month, so it might be wise to write a - script that generates the required DDL automatically. - </para> - </sect2> <sect2 id="ddl-partitioning-managing-partitions"> @@ -3135,22 +2985,17 @@ LANGUAGE plpgsql; </para> <para> - The simplest option for removing old data is simply to drop the partition + The simplest option for removing old data is simply detach the partition that is no longer necessary: <programlisting> -DROP TABLE measurement_y2006m02; +ALTER TABLE measurement DETACH PARTITION measurement_y2016m07; </programlisting> + This can very quickly delete millions of records because it doesn't have to individually delete every record. - </para> - <para> - Another option that is often preferable is to remove the partition from - the partitioned table but retain access to it as a table in its own - right: -<programlisting> -ALTER TABLE measurement_y2006m02 NO INHERIT measurement; -</programlisting> + The detached partition continues to exist as a regular table, which if + necessary can be dropped using regular <command>DROP TABLE</> command. This allows further operations to be performed on the data before it is dropped. For example, this is often a useful time to back up the data using <command>COPY</>, <application>pg_dump</>, or @@ -3165,9 +3010,7 @@ ALTER TABLE measurement_y2006m02 NO INHERIT measurement; were created above: <programlisting> -CREATE TABLE measurement_y2008m02 ( - CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' ) -) INHERITS (measurement); +CREATE TABLE measurement_y2017m07 PARTITION OF measurement FOR VALUES START ('2017-07-01') END ('2017-08-01'); </programlisting> As an alternative, it is sometimes more convenient to create the @@ -3176,13 +3019,15 @@ CREATE TABLE measurement_y2008m02 ( transformed prior to it appearing in the partitioned table: <programlisting> -CREATE TABLE measurement_y2008m02 +CREATE TABLE measurement_y2017m07 (LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS); -ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02 - CHECK ( logdate &gt;= DATE '2008-02-01' AND logdate &lt; DATE '2008-03-01' ); -\copy measurement_y2008m02 from 'measurement_y2008m02' +ALTER TABLE measurement_y2017m07 ADD CONSTRAINT y2017m07 + CHECK ( logdate &gt;= DATE '2017-07-01' AND logdate &lt; DATE '2017-08-01' ); +\copy measurement_y2017m07 from 'measurement_y2017m07' +ALTER TABLE measurement_y2017m07 DROP CONSTRAINT y2017m07; -- possibly some other data preparation work -ALTER TABLE measurement_y2008m02 INHERIT measurement; +ALTER TABLE measurement + ATTACH PARTITION measurement_y2017m07 FOR VALUES START ('2017-07-01') END ('2017-08-01'); </programlisting> </para> </sect2> @@ -3201,7 +3046,7 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement; <programlisting> SET constraint_exclusion = on; -SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; +SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2017-01-01'; </programlisting> Without constraint exclusion, the above query would scan each of @@ -3210,7 +3055,9 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; partition and try to prove that the partition need not be scanned because it could not contain any rows meeting the query's <literal>WHERE</> clause. When the planner can prove this, it - excludes the partition from the query plan. + excludes the partition from the query plan. Note that the aforementioned + constraints need not be explicitly created; they are internally derived + from the partition bound metadata. </para> <para> @@ -3220,23 +3067,23 @@ SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <programlisting> SET constraint_exclusion = off; -EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; - - QUERY PLAN ------------------------------------------------------------------------------------------------ - Aggregate (cost=158.66..158.68 rows=1 width=0) - -&gt; Append (cost=0.00..151.88 rows=2715 width=0) - -&gt; Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0) - Filter: (logdate &gt;= '2008-01-01'::date) - -&gt; Seq Scan on measurement_y2006m02 measurement (cost=0.00..30.38 rows=543 width=0) - Filter: (logdate &gt;= '2008-01-01'::date) - -&gt; Seq Scan on measurement_y2006m03 measurement (cost=0.00..30.38 rows=543 width=0) - Filter: (logdate &gt;= '2008-01-01'::date) +EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01'; + + QUERY PLAN +----------------------------------------------------------------------------------- + Aggregate (cost=866.69..866.70 rows=1 width=8) + -&gt; Append (cost=0.00..828.12 rows=15426 width=0) + -&gt; Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0) + Filter: (logdate &gt;= '2017-01-01'::date) + -&gt; Seq Scan on measurement_y2016m07 (cost=0.00..33.12 rows=617 width=0) + Filter: (logdate &gt;= '2017-01-01'::date) + -&gt; Seq Scan on measurement_y2016m08 (cost=0.00..33.12 rows=617 width=0) + Filter: (logdate &gt;= '2017-01-01'::date) ... - -&gt; Seq Scan on measurement_y2007m12 measurement (cost=0.00..30.38 rows=543 width=0) - Filter: (logdate &gt;= '2008-01-01'::date) - -&gt; Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0) - Filter: (logdate &gt;= '2008-01-01'::date) + -&gt; Seq Scan on measurement_y2018m06 (cost=0.00..33.12 rows=617 width=0) + Filter: (logdate &gt;= '2017-01-01'::date) + -&gt; Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0) + Filter: (logdate &gt;= '2017-01-01'::date) </programlisting> Some or all of the partitions might use index scans instead of @@ -3247,15 +3094,15 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; <programlisting> SET constraint_exclusion = on; -EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; - QUERY PLAN ------------------------------------------------------------------------------------------------ - Aggregate (cost=63.47..63.48 rows=1 width=0) - -&gt; Append (cost=0.00..60.75 rows=1086 width=0) - -&gt; Seq Scan on measurement (cost=0.00..30.38 rows=543 width=0) - Filter: (logdate &gt;= '2008-01-01'::date) - -&gt; Seq Scan on measurement_y2008m01 measurement (cost=0.00..30.38 rows=543 width=0) - Filter: (logdate &gt;= '2008-01-01'::date) +EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2018-07-01'; + QUERY PLAN +----------------------------------------------------------------------------------- + Aggregate (cost=34.67..34.68 rows=1 width=8) + -&gt; Append (cost=0.00..33.12 rows=618 width=0) + -&gt; Seq Scan on measurement (cost=0.00..0.00 rows=1 width=0) + Filter: (logdate &gt;= '2018-07-01'::date) + -&gt; Seq Scan on measurement_y2018m07 (cost=0.00..33.12 rows=617 width=0) + Filter: (logdate &gt;= '2018-07-01'::date) </programlisting> </para> @@ -3282,93 +3129,22 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01'; </sect2> - <sect2 id="ddl-partitioning-alternatives"> - <title>Alternative Partitioning Methods</title> - - <para> - A different approach to redirecting inserts into the appropriate - partition table is to set up rules, instead of a trigger, on the - master table. For example: - -<programlisting> -CREATE RULE measurement_insert_y2006m02 AS -ON INSERT TO measurement WHERE - ( logdate &gt;= DATE '2006-02-01' AND logdate &lt; DATE '2006-03-01' ) -DO INSTEAD - INSERT INTO measurement_y2006m02 VALUES (NEW.*); -... -CREATE RULE measurement_insert_y2008m01 AS -ON INSERT TO measurement WHERE - ( logdate &gt;= DATE '2008-01-01' AND logdate &lt; DATE '2008-02-01' ) -DO INSTEAD - INSERT INTO measurement_y2008m01 VALUES (NEW.*); -</programlisting> - - A rule has significantly more overhead than a trigger, but the overhead - is paid once per query rather than once per row, so this method might be - advantageous for bulk-insert situations. In most cases, however, the - trigger method will offer better performance. - </para> - - <para> - Be aware that <command>COPY</> ignores rules. If you want to - use <command>COPY</> to insert data, you'll need to copy into the correct - partition table rather than into the master. <command>COPY</> does fire - triggers, so you can use it normally if you use the trigger approach. - </para> - - <para> - Another disadvantage of the rule approach is that there is no simple - way to force an error if the set of rules doesn't cover the insertion - date; the data will silently go into the master table instead. - </para> - - <para> - Partitioning can also be arranged using a <literal>UNION ALL</literal> - view, instead of table inheritance. For example, - -<programlisting> -CREATE VIEW measurement AS - SELECT * FROM measurement_y2006m02 -UNION ALL SELECT * FROM measurement_y2006m03 -... -UNION ALL SELECT * FROM measurement_y2007m11 -UNION ALL SELECT * FROM measurement_y2007m12 -UNION ALL SELECT * FROM measurement_y2008m01; -</programlisting> - - However, the need to recreate the view adds an extra step to adding and - dropping individual partitions of the data set. In practice this - method has little to recommend it compared to using inheritance. - </para> - - </sect2> - <sect2 id="ddl-partitioning-caveats"> <title>Caveats</title> <para> The following caveats apply to partitioned tables: <itemizedlist> - <listitem> - <para> - There is no automatic way to verify that all of the - <literal>CHECK</literal> constraints are mutually - exclusive. It is safer to create code that generates - partitions and creates and/or modifies associated objects than - to write each by hand. - </para> - </listitem> <listitem> <para> The schemes shown here assume that the partition key column(s) of a row never change, or at least do not change enough to require it to move to another partition. An <command>UPDATE</> that attempts - to do that will fail because of the <literal>CHECK</> constraints. - If you need to handle such cases, you can put suitable update triggers - on the partition tables, but it makes management of the structure - much more complicated. + to do that will fail because of applying internally created <literal>CHECK</> + constraints. If you need to handle such cases, you can put suitable + update triggers on the partition tables, but it makes management of the + structure much more complicated. </para> </listitem> @@ -3387,9 +3163,9 @@ ANALYZE measurement; <listitem> <para> <command>INSERT</command> statements with <literal>ON CONFLICT</> - clauses are unlikely to work as expected, as the <literal>ON CONFLICT</> - action is only taken in case of unique violations on the specified - target relation, not its child relations. + clauses are currently unsupported on partitioned tables as there is + currently no reliable way to check global uniqueness across all the + partitions. </para> </listitem> @@ -3413,18 +3189,6 @@ ANALYZE measurement; <listitem> <para> - Keep the partitioning constraints simple, else the planner may not be - able to prove that partitions don't need to be visited. Use simple - equality conditions for list partitioning, or simple - range tests for range partitioning, as illustrated in the preceding - examples. A good rule of thumb is that partitioning constraints should - contain only comparisons of the partitioning column(s) to constants - using B-tree-indexable operators. - </para> - </listitem> - - <listitem> - <para> All constraints on all partitions of the master table are examined during constraint exclusion, so large numbers of partitions are likely to increase query planning time considerably. Partitioning using -- 1.7.1

view thread (329+ messages)  latest in thread

Message-ID: <ad16e2f5-fc7c-cc2d-333a-88d4aa446f96@lab.ntt.co.jp>
Permalink:  ../ad16e2f5-fc7c-cc2d-333a-88d4aa446f96@lab.ntt.co.jp/
Also on:    postgresql.org/message-id/ad16e2f5-fc7c-cc2d-333a-88d4aa446f96@lab.ntt.co.jp

 ·  · 

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-hackers@postgresql.org
  Cc: Langote_Amit_f8@lab.ntt.co.jp
  Subject: Re: Declarative partitioning - another take
  In-Reply-To: <ad16e2f5-fc7c-cc2d-333a-88d4aa446f96@lab.ntt.co.jp>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox